From 190cf57eb1832fa2cc9b96d3df1432d2d29c172e Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 6 Aug 2019 23:57:15 +0800 Subject: [PATCH 001/258] re-format all codes --- src/AbstractApplication.php | 62 ++++-- src/AbstractHandler.php | 114 ++++++---- src/Application.php | 69 +++--- src/BuiltIn/DevServerCommand.php | 12 +- src/BuiltIn/PharController.php | 51 +++-- src/BuiltIn/SelfUpdateCommand.php | 12 +- src/Command.php | 3 +- src/Component/ErrorHandler.php | 15 +- src/Component/Formatter/HelpPanel.php | 29 ++- src/Component/Formatter/MultiList.php | 7 +- src/Component/Formatter/Padding.php | 13 +- src/Component/Formatter/Panel.php | 96 ++++---- src/Component/Formatter/Section.php | 25 ++- src/Component/Formatter/SingleList.php | 10 +- src/Component/Formatter/Table.php | 41 ++-- src/Component/Formatter/Title.php | 18 +- src/Component/Formatter/Tree.php | 12 +- src/Component/Interact/Checkbox.php | 14 +- src/Component/Interact/Choose.php | 10 +- src/Component/Interact/Confirm.php | 11 +- src/Component/Interact/LimitedAsk.php | 14 +- src/Component/Interact/Password.php | 24 +- src/Component/Interact/Question.php | 11 +- src/Component/MessageFormatter.php | 3 +- src/Component/NotifyMessage.php | 5 +- src/Component/PharCompiler.php | 269 +++++++++++++---------- src/Component/Progress/CounterText.php | 5 +- src/Component/Progress/DynamicText.php | 8 +- src/Component/Progress/SimpleBar.php | 11 +- src/Component/Progress/SimpleTextBar.php | 5 +- src/Component/Style/Alert.php | 5 +- src/Component/Style/Color.php | 52 +++-- src/Component/Style/Style.php | 36 +-- src/Component/Symbol/ArtFont.php | 12 +- src/Component/Symbol/Char.php | 8 +- src/Component/Symbol/Emoji.php | 8 +- src/Console.php | 70 +++--- src/Contract/ApplicationInterface.php | 16 +- src/Contract/ErrorHandlerInterface.php | 5 +- src/Contract/RouterInterface.php | 16 +- src/Controller.php | 82 ++++--- src/Exception/ConsoleException.php | 5 +- src/Exception/PromptException.php | 5 +- src/IO/AbstractInput.php | 32 ++- src/IO/Input.php | 25 ++- src/IO/Input/ArrayInput.php | 6 +- src/IO/InputDefinition.php | 118 +++++----- src/IO/Output.php | 6 +- src/IO/StrictInput.php | 10 +- src/Router.php | 85 ++++--- src/Traits/AdvancedFormatOutputTrait.php | 3 +- src/Traits/ApplicationHelpTrait.php | 107 +++++---- src/Traits/ControllerHelpTrait.php | 1 + src/Traits/FormatOutputAwareTrait.php | 51 +++-- src/Traits/InputOutputAwareTrait.php | 12 +- src/Traits/NameAliasTrait.php | 5 + src/Traits/RuntimeProfileTrait.php | 35 ++- src/Traits/SimpleEventTrait.php | 21 +- src/Traits/UserInteractAwareTrait.php | 19 +- src/Util/FormatUtil.php | 82 ++++--- src/Util/Helper.php | 53 +++-- src/Util/Interact.php | 32 +-- src/Util/ProgressBar.php | 30 +-- src/Util/Show.php | 98 +++++---- test/ApplicationTest.php | 9 +- 65 files changed, 1346 insertions(+), 793 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 4ce755d2..375802fb 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -8,6 +8,7 @@ namespace Inhere\Console; +use ErrorException; use Inhere\Console\Component\ErrorHandler; use Inhere\Console\Component\Style\Style; use Inhere\Console\Contract\ApplicationInterface; @@ -19,7 +20,22 @@ use Inhere\Console\Traits\ApplicationHelpTrait; use Inhere\Console\Traits\InputOutputAwareTrait; use Inhere\Console\Traits\SimpleEventTrait; +use InvalidArgumentException; +use Throwable; use Toolkit\PhpUtil\PhpHelper; +use function array_keys; +use function array_merge; +use function error_get_last; +use function header; +use function in_array; +use function is_int; +use function memory_get_usage; +use function microtime; +use function register_shutdown_function; +use function set_error_handler; +use function set_exception_handler; +use function trim; +use const PHP_SAPI; /** * Class AbstractApplication @@ -94,7 +110,7 @@ abstract class AbstractApplication implements ApplicationInterface * @param array $config * @param Input $input * @param Output $output - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function __construct(array $config = [], Input $input = null, Output $output = null) { @@ -109,14 +125,14 @@ public function __construct(array $config = [], Input $input = null, Output $out } /** - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ protected function init(): void { $this->stats = [ - 'startTime' => \microtime(1), + 'startTime' => microtime(1), 'endTime' => 0, - 'startMemory' => \memory_get_usage(), + 'startMemory' => memory_get_usage(), 'endMemory' => 0, ]; @@ -141,7 +157,7 @@ public static function getGlobalOptions(): array public function addGlobalOptions(array $options): void { if ($options) { - self::$globalOptions = \array_merge(self::$globalOptions, $options); + self::$globalOptions = array_merge(self::$globalOptions, $options); } } @@ -167,11 +183,11 @@ protected function beforeRun(): void * run application * @param bool $exit * @return int|mixed - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function run(bool $exit = true) { - $command = \trim($this->input->getCommand(), $this->delimiter); + $command = trim($this->input->getCommand(), $this->delimiter); $this->prepareRun(); @@ -187,20 +203,20 @@ public function run(bool $exit = true) // do run ... try { $result = $this->dispatch($command); - } catch (\Throwable $e) { + } catch (Throwable $e) { $this->fire(self::ON_RUN_ERROR, $e, $this); $result = $e->getCode() === 0 ? $e->getLine() : $e->getCode(); $this->handleException($e); } - $this->stats['endTime'] = \microtime(1); + $this->stats['endTime'] = microtime(1); // call 'onAfterRun' service, if it is registered. $this->fire(self::ON_AFTER_RUN, $this); $this->afterRun(); if ($exit) { - $this->stop(\is_int($result) ? $result : 0); + $this->stop(is_int($result) ? $result : 0); } return $result; @@ -256,8 +272,8 @@ public function subRun(string $command, InputInterface $input, OutputInterface $ protected function runtimeCheck(): void { // check env - if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'cli-server'], true)) { - \header('HTTP/1.1 403 Forbidden'); + if (!in_array(PHP_SAPI, ['cli', 'phpdbg', 'cli-server'], true)) { + header('HTTP/1.1 403 Forbidden'); exit(" 403 Forbidden \n\n" . " current environment is CLI. \n" . " :( Sorry! Run this script is only allowed in the terminal environment!\n,You are not allowed to access this file.\n"); @@ -266,14 +282,14 @@ protected function runtimeCheck(): void /** * register error handle - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ protected function registerErrorHandle(): void { - \set_error_handler([$this, 'handleError']); - \set_exception_handler([$this, 'handleException']); - \register_shutdown_function(function () { - if ($e = \error_get_last()) { + set_error_handler([$this, 'handleError']); + set_exception_handler([$this, 'handleException']); + register_shutdown_function(function () { + if ($e = error_get_last()) { $this->handleError($e['type'], $e['message'], $e['file'], $e['line']); } }); @@ -285,18 +301,18 @@ protected function registerErrorHandle(): void * @param string $str * @param string $file * @param int $line - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function handleError(int $num, string $str, string $file, int $line): void { - $this->handleException(new \ErrorException($str, 0, $num, $file, $line)); + $this->handleException(new ErrorException($str, 0, $num, $file, $line)); $this->stop(-1); } /** * Running exception handling - * @param \Throwable $e - * @throws \InvalidArgumentException + * @param Throwable $e + * @throws InvalidArgumentException */ public function handleException($e): void { @@ -428,7 +444,7 @@ public function getRootPath(): string */ public function getInternalCommands(): array { - return \array_keys(static::$internalCommands); + return array_keys(static::$internalCommands); } /** @@ -470,7 +486,7 @@ public function getRouter(): Router public function setConfig(array $config): void { if ($config) { - $this->config = \array_merge($this->config, $config); + $this->config = array_merge($this->config, $config); } } diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 5b3c2f65..d93af976 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -17,9 +17,37 @@ use Inhere\Console\Traits\UserInteractAwareTrait; use Inhere\Console\Util\FormatUtil; use Inhere\Console\Util\Helper; +use InvalidArgumentException; +use LogicException; +use ReflectionClass; +use ReflectionException; +use RuntimeException; use Swoole\Coroutine; use Swoole\Event; use Toolkit\PhpUtil\PhpDoc; +use function array_diff_key; +use function array_filter; +use function array_key_exists; +use function array_keys; +use function array_merge; +use function array_shift; +use function cli_set_process_title; +use function count; +use function error_get_last; +use function function_exists; +use function implode; +use function is_array; +use function is_int; +use function is_string; +use function preg_replace; +use function setproctitle; +use function sprintf; +use function strpos; +use function strtr; +use function ucfirst; +use const ARRAY_FILTER_USE_BOTH; +use const PHP_EOL; +use const PHP_OS; /** * Class AbstractHandler @@ -125,8 +153,8 @@ protected function configure(): void /** * @return InputDefinition - * @throws \LogicException - * @throws \InvalidArgumentException + * @throws LogicException + * @throws InvalidArgumentException */ protected function createDefinition(): InputDefinition { @@ -176,8 +204,8 @@ protected function annotationVars(): array * run command * @param string $command * @return int|mixed - * @throws \RuntimeException - * @throws \InvalidArgumentException + * @throws RuntimeException + * @throws InvalidArgumentException */ public function run(string $command = '') { @@ -267,20 +295,20 @@ protected function afterExecute(): void /** * prepare run - * @throws \InvalidArgumentException - * @throws \RuntimeException + * @throws InvalidArgumentException + * @throws RuntimeException */ protected function prepare(): bool { - if ($this->processTitle && 'Darwin' !== \PHP_OS) { - if (\function_exists('cli_set_process_title')) { - \cli_set_process_title($this->processTitle); - } elseif (\function_exists('setproctitle')) { - \setproctitle($this->processTitle); + if ($this->processTitle && 'Darwin' !== PHP_OS) { + if (function_exists('cli_set_process_title')) { + cli_set_process_title($this->processTitle); + } elseif (function_exists('setproctitle')) { + setproctitle($this->processTitle); } - if ($error = \error_get_last()) { - throw new \RuntimeException($error['message']); + if ($error = error_get_last()) { + throw new RuntimeException($error['message']); } } @@ -290,7 +318,7 @@ protected function prepare(): bool /** * validate input arguments and options * @return bool - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function validateInput(): bool { @@ -303,25 +331,25 @@ public function validateInput(): bool $givenArgs = $errArgs = []; foreach ($in->getArgs() as $key => $value) { - if (\is_int($key)) { + if (is_int($key)) { $givenArgs[$key] = $value; } else { $errArgs[] = $key; } } - if (\count($errArgs) > 0) { - $out->liteError(\sprintf('Unknown arguments (error: "%s").', \implode(', ', $errArgs))); + if (count($errArgs) > 0) { + $out->liteError(sprintf('Unknown arguments (error: "%s").', implode(', ', $errArgs))); return false; } $defArgs = $def->getArguments(); - $missingArgs = \array_filter(\array_keys($defArgs), function ($name, $key) use ($def, $givenArgs) { - return !\array_key_exists($key, $givenArgs) && $def->argumentIsRequired($name); - }, \ARRAY_FILTER_USE_BOTH); + $missingArgs = array_filter(array_keys($defArgs), function ($name, $key) use ($def, $givenArgs) { + return !array_key_exists($key, $givenArgs) && $def->argumentIsRequired($name); + }, ARRAY_FILTER_USE_BOTH); - if (\count($missingArgs) > 0) { - $out->liteError(\sprintf('Not enough arguments (missing: "%s").', \implode(', ', $missingArgs))); + if (count($missingArgs) > 0) { + $out->liteError(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArgs))); return false; } @@ -341,11 +369,11 @@ public function validateInput(): bool $defOpts = $def->getOptions(); // check unknown options - if ($unknown = \array_diff_key($givenOpts, $defOpts)) { - $names = \array_keys($unknown); - $first = \array_shift($names); + if ($unknown = array_diff_key($givenOpts, $defOpts)) { + $names = array_keys($unknown); + $first = array_shift($names); - throw new \InvalidArgumentException(\sprintf( + throw new InvalidArgumentException(sprintf( 'Input option is not exists (unknown: "%s").', (isset($first[1]) ? '--' : '-') . $first )); @@ -361,10 +389,10 @@ public function validateInput(): bool } } - if (\count($missingOpts) > 0) { + if (count($missingOpts) > 0) { $out->liteError( - \sprintf('Not enough options parameters (missing: "%s").', - \implode(', ', $missingOpts)) + sprintf('Not enough options parameters (missing: "%s").', + implode(', ', $missingOpts)) ); return false; } @@ -407,7 +435,7 @@ protected function addCommentsVars(array $map): void */ protected function setCommentsVar(string $name, $value): void { - $this->commentsVars[$name] = \is_array($value) ? \implode(',', $value) : (string)$value; + $this->commentsVars[$name] = is_array($value) ? implode(',', $value) : (string)$value; } /** @@ -418,7 +446,7 @@ protected function setCommentsVar(string $name, $value): void protected function parseCommentsVars(string $str): string { // not use vars - if (false === \strpos($str, self::HELP_VAR_LEFT)) { + if (false === strpos($str, self::HELP_VAR_LEFT)) { return $str; } @@ -432,7 +460,7 @@ protected function parseCommentsVars(string $str): string } } - return $map ? \strtr($str, $map) : $str; + return $map ? strtr($str, $map) : $str; } /** @@ -460,7 +488,7 @@ protected function showHelp(): bool // if has InputDefinition object. (The comment of the command will not be parsed and used at this time.) $help = $definition->getSynopsis(); // build usage - $help['usage:'] = \sprintf( + $help['usage:'] = sprintf( '%s %s %s', $this->getScriptName(), $this->getCommandName(), @@ -478,7 +506,7 @@ protected function showHelp(): bool } // output description - $this->write(\ucfirst($help[0]) . \PHP_EOL); + $this->write(ucfirst($help[0]) . PHP_EOL); unset($help[0]); $this->output->mList($help, ['sepChar' => ' ']); @@ -491,11 +519,11 @@ protected function showHelp(): bool * @param string $action * @param array $aliases * @return int - * @throws \ReflectionException + * @throws ReflectionException */ protected function showHelpByMethodAnnotations(string $method, string $action = '', array $aliases = []): int { - $ref = new \ReflectionClass($this); + $ref = new ReflectionClass($this); $name = $this->input->getCommand(); $this->logf( @@ -522,11 +550,11 @@ protected function showHelpByMethodAnnotations(string $method, string $action = if ($aliases) { $realName = $action ?: self::getName(); - $help['Command:'] = \sprintf('%s(alias: %s)', $realName, \implode(',', $aliases)); + $help['Command:'] = sprintf('%s(alias: %s)', $realName, implode(',', $aliases)); } - foreach (\array_keys(self::$annotationTags) as $tag) { - if (empty($tags[$tag]) || !\is_string($tags[$tag])) { + foreach (array_keys(self::$annotationTags) as $tag) { + if (empty($tags[$tag]) || !is_string($tags[$tag])) { // for alone command if ($tag === 'description' && $isAlone) { $help['Description:'] = self::getDescription(); @@ -542,13 +570,13 @@ protected function showHelpByMethodAnnotations(string $method, string $action = // $msg = trim($tags[$tag]); $msg = $tags[$tag]; - $tag = \ucfirst($tag); + $tag = ucfirst($tag); // for alone command if (!$msg && $tag === 'description' && $isAlone) { $msg = self::getDescription(); } else { - $msg = \preg_replace('#(\n)#', '$1 ', $msg); + $msg = preg_replace('#(\n)#', '$1 ', $msg); } $help[$tag . ':'] = $msg; @@ -556,7 +584,7 @@ protected function showHelpByMethodAnnotations(string $method, string $action = if (isset($help['Description:'])) { $description = $help['Description:'] ?: 'No description message for the command'; - $this->write(\ucfirst($description) . \PHP_EOL); + $this->write(ucfirst($description) . PHP_EOL); unset($help['Description:']); } @@ -653,7 +681,7 @@ public static function addAnnotationTag(string $name): void */ public static function setAnnotationTags(array $annotationTags, $replace = false): void { - self::$annotationTags = $replace ? $annotationTags : \array_merge(self::$annotationTags, $annotationTags); + self::$annotationTags = $replace ? $annotationTags : array_merge(self::$annotationTags, $annotationTags); } /** diff --git a/src/Application.php b/src/Application.php index b8ac302a..afa0b44c 100644 --- a/src/Application.php +++ b/src/Application.php @@ -8,7 +8,20 @@ namespace Inhere\Console; +use Closure; use Inhere\Console\Util\Helper; +use InvalidArgumentException; +use ReflectionException; +use SplFileInfo; +use function class_exists; +use function implode; +use function is_object; +use function is_string; +use function method_exists; +use function sprintf; +use function strlen; +use function strpos; +use function substr; /** * Class App @@ -25,7 +38,7 @@ class Application extends AbstractApplication */ public function controller(string $name, $class = null, $option = null) { - if (\is_string($option)) { + if (is_string($option)) { $option = [ 'description' => $option, ]; @@ -49,7 +62,7 @@ public function addGroup(string $name, $controller = null, $option = null) /** * {@inheritdoc} * @see controller() - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function addController(string $name, $class = null, $option = null) { @@ -58,7 +71,7 @@ public function addController(string $name, $class = null, $option = null) /** * @param array $controllers - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function controllers(array $controllers): void { @@ -67,7 +80,7 @@ public function controllers(array $controllers): void /** * @param array $controllers - * @throws \InvalidArgumentException + * @throws InvalidArgumentException * @deprecated please use addControllers() instead it. */ public function setControllers(array $controllers): void @@ -77,7 +90,7 @@ public function setControllers(array $controllers): void /** * @param array $controllers - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function addControllers(array $controllers): void { @@ -89,7 +102,7 @@ public function addControllers(array $controllers): void */ public function command(string $name, $handler = null, $option = null) { - if (\is_string($option)) { + if (is_string($option)) { $option = [ 'description' => $option, ]; @@ -112,7 +125,7 @@ public function addCommand(string $name, $handler = null, $option = null): self /** * @param array $commands - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function addCommands(array $commands): void { @@ -121,7 +134,7 @@ public function addCommands(array $commands): void /** * @param array $commands - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function commands(array $commands): void { @@ -138,15 +151,15 @@ public function commands(array $commands): void * @param string $namespace * @param string $basePath * @return $this - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function registerCommands(string $namespace, string $basePath): self { - $length = \strlen($basePath) + 1; + $length = strlen($basePath) + 1; $iterator = Helper::directoryIterator($basePath, $this->getFileFilter()); foreach ($iterator as $file) { - $class = $namespace . '\\' . \substr($file, $length, -4); + $class = $namespace . '\\' . substr($file, $length, -4); $this->addCommand($class); } @@ -158,15 +171,15 @@ public function registerCommands(string $namespace, string $basePath): self * @param string $namespace * @param string $basePath * @return $this - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function registerGroups(string $namespace, string $basePath): self { - $length = \strlen($basePath) + 1; + $length = strlen($basePath) + 1; $iterator = Helper::directoryIterator($basePath, $this->getFileFilter()); foreach ($iterator as $file) { - $class = $namespace . '\\' . \substr($file, $length, -4); + $class = $namespace . '\\' . substr($file, $length, -4); $this->addController($class); } @@ -174,15 +187,15 @@ public function registerGroups(string $namespace, string $basePath): self } /** - * @return \Closure + * @return Closure */ protected function getFileFilter(): callable { - return function (\SplFileInfo $f) { + return function (SplFileInfo $f) { $name = $f->getFilename(); // Skip hidden files and directories. - if (\strpos($name, '.') === 0) { + if (strpos($name, '.') === 0) { return false; } @@ -192,7 +205,7 @@ protected function getFileFilter(): callable } // php file - return $f->isFile() && \substr($name, -4) === '.php'; + return $f->isFile() && substr($name, -4) === '.php'; }; } @@ -202,8 +215,8 @@ protected function getFileFilter(): callable /** * @inheritdoc - * @throws \ReflectionException - * @throws \InvalidArgumentException + * @throws ReflectionException + * @throws InvalidArgumentException */ public function dispatch(string $name, bool $standAlone = false) { @@ -220,7 +233,7 @@ public function dispatch(string $name, bool $standAlone = false) // find similar command names by similar_text() if ($similar = Helper::findSimilar($name, $commands)) { - $this->write(\sprintf("\nMaybe what you mean is:\n %s", \implode(', ', $similar))); + $this->write(sprintf("\nMaybe what you mean is:\n %s", implode(', ', $similar))); } else { $this->showCommandList(); } @@ -240,14 +253,14 @@ public function dispatch(string $name, bool $standAlone = false) /** * run a independent command * @param string $name Command name - * @param \Closure|string $handler Command class + * @param Closure|string $handler Command class * @param array $options * @return mixed - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ protected function runCommand(string $name, $handler, array $options) { - if (\is_object($handler) && \method_exists($handler, '__invoke')) { + if (is_object($handler) && method_exists($handler, '__invoke')) { if ($this->input->getSameOpt(['h', 'help'])) { $desc = $options['description'] ?? 'No command description message'; @@ -256,7 +269,7 @@ protected function runCommand(string $name, $handler, array $options) $result = $handler($this->input, $this->output); } else { - if (!\class_exists($handler)) { + if (!class_exists($handler)) { Helper::throwInvalidArgument("The console command class [$handler] not exists!"); } @@ -283,15 +296,15 @@ protected function runCommand(string $name, $handler, array $options) * @param array $options * @param bool $standAlone * @return mixed - * @throws \ReflectionException + * @throws ReflectionException */ protected function runAction(string $group, string $action, $handler, array $options, bool $standAlone = false) { /** @var Controller $handler */ - if (\is_string($handler)) { + if (is_string($handler)) { $class = $handler; - if (!\class_exists($class)) { + if (!class_exists($class)) { Helper::throwInvalidArgument('The console controller class [%s] not exists!', $class); } diff --git a/src/BuiltIn/DevServerCommand.php b/src/BuiltIn/DevServerCommand.php index db17c9cf..6bdab3b8 100644 --- a/src/BuiltIn/DevServerCommand.php +++ b/src/BuiltIn/DevServerCommand.php @@ -9,6 +9,10 @@ namespace Inhere\Console\BuiltIn; use Inhere\Console\Command; +use Inhere\Console\IO\Input; +use Inhere\Console\IO\Output; +use const PHP_VERSION; +use function strpos; use Toolkit\Sys\Sys; /** @@ -41,8 +45,8 @@ public static function aliases(): array * file=STRING The entry file for server. e.g web/index.php * @example * {command} -S 127.0.0.1:8552 web/index.php - * @param \Inhere\Console\IO\Input $in - * @param \Inhere\Console\IO\Output $out + * @param Input $in + * @param Output $out * @return int|mixed|void */ public function execute($in, $out) @@ -51,12 +55,12 @@ public function execute($in, $out) $server = $this->getSameOpt(['H', 'host'], '127.0.0.1'); } - if (!\strpos($server, ':')) { + if (!strpos($server, ':')) { $port = $this->getSameOpt(['p', 'port'], 8552); $server .= ':' . $port; } - $version = \PHP_VERSION; + $version = PHP_VERSION; $workDir = $this->input->getPwd(); $docDir = $this->getOpt('t'); $docRoot = $docDir ? $workDir . '/' . $docDir : $workDir; diff --git a/src/BuiltIn/PharController.php b/src/BuiltIn/PharController.php index fa4d2462..f6da71d4 100644 --- a/src/BuiltIn/PharController.php +++ b/src/BuiltIn/PharController.php @@ -8,10 +8,23 @@ namespace Inhere\Console\BuiltIn; +use BadMethodCallException; +use function basename; +use Closure; +use Exception; +use function file_exists; use Inhere\Console\Component\PharCompiler; use Inhere\Console\Controller; +use Inhere\Console\IO\Input; +use Inhere\Console\IO\Output; use Inhere\Console\Util\Helper; use Inhere\Console\Util\Show; +use function is_dir; +use function is_file; +use function microtime; +use function realpath; +use RuntimeException; +use UnexpectedValueException; /** * Class PharController @@ -23,7 +36,7 @@ class PharController extends Controller protected static $description = 'Pack a project directory to phar or unpack phar to directory'; /** - * @var \Closure + * @var Closure */ private $compilerConfiger; @@ -31,7 +44,7 @@ protected function init(): void { parent::init(); - $this->addCommentsVar('defaultPkgName', \basename($this->input->getPwd())); + $this->addCommentsVar('defaultPkgName', basename($this->input->getPwd())); } /** @@ -44,17 +57,17 @@ protected function init(): void * -o, --output STRING Setting the output file name({defaultPkgName}.phar) * --fast BOOL Fast build. only add modified files by git status -s * --refresh BOOL Whether build vendor folder files on phar file exists(False) - * @param \Inhere\Console\IO\Input $in - * @param \Inhere\Console\IO\Output $out + * @param Input $in + * @param Output $out * @return int - * @throws \UnexpectedValueException - * @throws \RuntimeException - * @throws \BadMethodCallException - * @throws \Exception + * @throws UnexpectedValueException + * @throws RuntimeException + * @throws BadMethodCallException + * @throws Exception */ public function packCommand($in, $out): int { - $time = \microtime(1); + $time = microtime(1); $workDir = $in->getPwd(); $dir = $in->getOpt('dir') ?: $workDir; @@ -62,7 +75,7 @@ public function packCommand($in, $out): int $counter = null; $refresh = $in->boolOpt('refresh'); - $pharFile = $workDir . '/' . $in->sameOpt(['o', 'output'], \basename($workDir) . '.phar'); + $pharFile = $workDir . '/' . $in->sameOpt(['o', 'output'], basename($workDir) . '.phar'); // use fast build if ($this->input->boolOpt('fast')) { @@ -130,19 +143,19 @@ protected function configCompiler(string $dir): PharCompiler // use config file $configFile = $this->input->getSameOpt(['c', 'config']) ?: $dir . '/phar.build.inc'; - if ($configFile && \is_file($configFile)) { + if ($configFile && is_file($configFile)) { require $configFile; return $compiler->in($dir); } - throw new \RuntimeException("The phar build config file not exists! File: $configFile"); + throw new RuntimeException("The phar build config file not exists! File: $configFile"); } /** - * @param \Closure $compilerConfiger + * @param Closure $compilerConfiger */ - public function setCompilerConfiger(\Closure $compilerConfiger): void + public function setCompilerConfiger(Closure $compilerConfiger): void { $this->compilerConfiger = $compilerConfiger; } @@ -156,8 +169,8 @@ public function setCompilerConfiger(\Closure $compilerConfiger): void * -y, --yes BOOL Whether display goon tips message. * --overwrite BOOL Whether overwrite exists files on extract phar * @example {fullCommand} -f myapp.phar -d var/www/app - * @param \Inhere\Console\IO\Input $in - * @param \Inhere\Console\IO\Output $out + * @param Input $in + * @param Output $out * @return int */ public function unpackCommand($in, $out): int @@ -167,16 +180,16 @@ public function unpackCommand($in, $out): int } $basePath = $in->getPwd(); - $file = \realpath($basePath . '/' . $path); + $file = realpath($basePath . '/' . $path); - if (!\file_exists($file)) { + if (!file_exists($file)) { return $out->error("The phar file not exists. File: $file"); } $dir = $in->getSameOpt(['d', 'dir']) ?: $basePath; $overwrite = $in->getBoolOpt('overwrite'); - if (!\is_dir($dir)) { + if (!is_dir($dir)) { Helper::mkdir($dir); } diff --git a/src/BuiltIn/SelfUpdateCommand.php b/src/BuiltIn/SelfUpdateCommand.php index 86c1f23c..9a97a9ec 100644 --- a/src/BuiltIn/SelfUpdateCommand.php +++ b/src/BuiltIn/SelfUpdateCommand.php @@ -8,6 +8,7 @@ namespace Inhere\Console\BuiltIn; +use Exception; use Humbug\SelfUpdate\Strategy\GithubStrategy; use Humbug\SelfUpdate\Strategy\ShaStrategy; use Humbug\SelfUpdate\Updater; @@ -15,6 +16,7 @@ use Inhere\Console\Command; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; +use function strlen; /** * Class SelfUpdateCommand @@ -180,11 +182,11 @@ protected function update(Updater $updater): void $newVersion = $updater->getNewVersion(); $oldVersion = $updater->getOldVersion(); - if (\strlen($newVersion) === 40) { + if (strlen($newVersion) === 40) { $newVersion = 'dev-' . $newVersion; } - if (\strlen($oldVersion) === 40) { + if (strlen($oldVersion) === 40) { $oldVersion = 'dev-' . $oldVersion; } @@ -205,7 +207,7 @@ protected function update(Updater $updater): void $oldVersion )); } - } catch (\Exception $e) { + } catch (Exception $e) { $this->output->writeln(sprintf('Error: %s', $e->getMessage())); } $this->output->write(PHP_EOL); @@ -222,7 +224,7 @@ protected function rollback(): void } else { $this->output->writeln('Rollback failed for reasons unknown.'); } - } catch (\Exception $e) { + } catch (Exception $e) { $this->output->writeln(sprintf('Error: %s', $e->getMessage())); } } @@ -281,7 +283,7 @@ protected function printVersion(Updater $updater): void } else { $this->output->writeln(sprintf('You have the current %s build installed.', $stability)); } - } catch (\Exception $e) { + } catch (Exception $e) { $this->output->writeln(sprintf('Error: %s', $e->getMessage())); } } diff --git a/src/Command.php b/src/Command.php index b17b5bf9..49e4cf4f 100644 --- a/src/Command.php +++ b/src/Command.php @@ -9,6 +9,7 @@ namespace Inhere\Console; use Inhere\Console\Contract\CommandInterface; +use ReflectionException; /** * Class Command @@ -48,7 +49,7 @@ abstract class Command extends AbstractHandler implements CommandInterface /** * Show help information * @return bool - * @throws \ReflectionException + * @throws ReflectionException */ protected function showHelp(): bool { diff --git a/src/Component/ErrorHandler.php b/src/Component/ErrorHandler.php index c2b3e334..6f7d372a 100644 --- a/src/Component/ErrorHandler.php +++ b/src/Component/ErrorHandler.php @@ -10,7 +10,12 @@ use Inhere\Console\AbstractApplication; use Inhere\Console\Contract\ErrorHandlerInterface; +use Throwable; use Toolkit\Cli\Highlighter; +use function file_get_contents; +use function get_class; +use function sprintf; +use function str_replace; /** * Class ErrorHandler @@ -21,9 +26,9 @@ class ErrorHandler implements ErrorHandlerInterface /** * @inheritdoc */ - public function handle(\Throwable $e, AbstractApplication $app): void + public function handle(Throwable $e, AbstractApplication $app): void { - $class = \get_class($e); + $class = get_class($e); // open debug, throw exception if ($app->isDebug()) { @@ -37,8 +42,8 @@ public function handle(\Throwable $e, AbstractApplication $app): void ERR; $line = $e->getLine(); $file = $e->getFile(); - $snippet = Highlighter::create()->highlightSnippet(\file_get_contents($file), $line, 3, 3); - $message = \sprintf( + $snippet = Highlighter::create()->highlightSnippet(file_get_contents($file), $line, 3, 3); + $message = sprintf( $tpl, // $e->getCode(), $e->getMessage(), @@ -51,7 +56,7 @@ public function handle(\Throwable $e, AbstractApplication $app): void ); if ($app->getParam('hideRootPath') && ($rootPath = $app->getParam('rootPath'))) { - $message = \str_replace($rootPath, '{ROOT}', $message); + $message = str_replace($rootPath, '{ROOT}', $message); } $app->write($message, false); diff --git a/src/Component/Formatter/HelpPanel.php b/src/Component/Formatter/HelpPanel.php index e7e44397..389dc316 100644 --- a/src/Component/Formatter/HelpPanel.php +++ b/src/Component/Formatter/HelpPanel.php @@ -11,11 +11,18 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; use Inhere\Console\Util\FormatUtil; -use Inhere\Console\Util\Show; +use function array_merge; +use function implode; +use function is_array; +use function is_string; +use function trim; +use function ucfirst; +use const PHP_EOL; /** * Class HelpPanel * - method version please {@see \Inhere\Console\Util\Show::helpPanel()} + * * @package Inhere\Console\Component\Formatter */ class HelpPanel extends MessageFormatter @@ -33,7 +40,7 @@ class HelpPanel extends MessageFormatter /** * Show console help message - * @param array $config The config data + * * There are config structure. you can setting some or ignore some. will only render it when value is not empty. * [ * description string The description text. e.g 'Composer version 1.3.2' @@ -60,6 +67,8 @@ class HelpPanel extends MessageFormatter * ] * examples array|string The command usage example. e.g 'php server.php {start|reload|restart|stop} [-d]' * ] + * + * @param array $config The config data */ public static function show(array $config): void { @@ -67,7 +76,7 @@ public static function show(array $config): void $option = [ 'indentDes' => ' ', ]; - $config = \array_merge([ + $config = array_merge([ 'description' => '', 'usage' => '', @@ -85,7 +94,7 @@ public static function show(array $config): void // some option for show. if (isset($config['_opts'])) { - $option = \array_merge($option, $config['_opts']); + $option = array_merge($option, $config['_opts']); unset($config['_opts']); } @@ -102,10 +111,10 @@ public static function show(array $config): void } // if $value is array, translate array to string - if (\is_array($value)) { + if (is_array($value)) { // is natural key ['text1', 'text2'](like usage,examples) if (isset($value[0])) { - $value = \implode(\PHP_EOL . ' ', $value); + $value = implode(PHP_EOL . ' ', $value); // is key-value [ 'key1' => 'text1', 'key2' => 'text2'] } else { @@ -117,15 +126,15 @@ public static function show(array $config): void } } - if (\is_string($value)) { - $value = \trim($value); - $section = \ucfirst($section); + if (is_string($value)) { + $value = trim($value); + $section = ucfirst($section); $parts[] = "$section:\n {$value}\n"; } } if ($parts) { - Console::write(\implode("\n", $parts), false); + Console::write(implode("\n", $parts), false); } } } diff --git a/src/Component/Formatter/MultiList.php b/src/Component/Formatter/MultiList.php index c26ba7c2..df9b5e5c 100644 --- a/src/Component/Formatter/MultiList.php +++ b/src/Component/Formatter/MultiList.php @@ -4,9 +4,11 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; +use function implode; /** * Class MultiList + * * @package Inhere\Console\Component\Formatter */ class MultiList extends MessageFormatter @@ -27,12 +29,13 @@ class MultiList extends MessageFormatter * ... ... * ] * ``` + * * @param array $data * @param array $opts */ public static function show(array $data, array $opts = []): void { - $stringList = []; + $stringList = []; $ignoreEmpty = $opts['ignoreEmpty'] ?? true; $lastNewline = true; @@ -50,6 +53,6 @@ public static function show(array $data, array $opts = []): void $stringList[] = SingleList::show($list, $title, $opts); } - Console::write(\implode("\n", $stringList), $lastNewline); + Console::write(implode("\n", $stringList), $lastNewline); } } diff --git a/src/Component/Formatter/Padding.php b/src/Component/Formatter/Padding.php index 63914852..a0304f2b 100644 --- a/src/Component/Formatter/Padding.php +++ b/src/Component/Formatter/Padding.php @@ -6,9 +6,14 @@ use Inhere\Console\Console; use Inhere\Console\Util\Helper; use Toolkit\Cli\ColorTag; +use function array_merge; +use function str_pad; +use function trim; +use function ucfirst; /** * Class Padding + * * @package Inhere\Console\Component\Formatter */ class Padding extends MessageFormatter @@ -32,8 +37,8 @@ public static function show(array $data, string $title = '', array $opts = []): return; } - $string = $title ? ColorTag::wrap(\ucfirst($title), 'comment') . ":\n" : ''; - $opts = \array_merge([ + $string = $title ? ColorTag::wrap(ucfirst($title), 'comment') . ":\n" : ''; + $opts = array_merge([ 'char' => '.', 'indent' => ' ', 'padding' => 10, @@ -45,9 +50,9 @@ public static function show(array $data, string $title = '', array $opts = []): foreach ($data as $label => $value) { $value = ColorTag::wrap((string)$value, $opts['valueStyle']); - $string .= $opts['indent'] . \str_pad($label, $paddingLen, $opts['char']) . " $value\n"; + $string .= $opts['indent'] . str_pad($label, $paddingLen, $opts['char']) . " $value\n"; } - Console::write(\trim($string)); + Console::write(trim($string)); } } diff --git a/src/Component/Formatter/Panel.php b/src/Component/Formatter/Panel.php index 35de20c5..ffcb3f54 100644 --- a/src/Component/Formatter/Panel.php +++ b/src/Component/Formatter/Panel.php @@ -11,12 +11,25 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; use Inhere\Console\Util\FormatUtil; -use Inhere\Console\Util\Show; use Toolkit\StrUtil\StrBuffer; +use function array_filter; +use function array_merge; +use function ceil; +use function is_array; +use function is_bool; +use function is_numeric; +use function mb_strlen; +use function rtrim; +use function str_pad; +use function strip_tags; +use function trim; +use function ucwords; +use const PHP_EOL; /** * Class Panel * - method version please {@see \Inhere\Console\Util\Show::panel()} + * * @package Inhere\Console\Component\Formatter */ class Panel extends MessageFormatter @@ -73,9 +86,11 @@ class Panel extends MessageFormatter /** * Show information data panel - * @param mixed $data - * @param string $title - * @param array $opts + * + * @param mixed $data + * @param string $title + * @param array $opts + * * @return int */ public static function show($data, string $title = 'Information Panel', array $opts = []): int @@ -85,13 +100,13 @@ public static function show($data, string $title = 'Information Panel', array $o return -2; } - $opts = \array_merge([ + $opts = array_merge([ 'borderChar' => '*', 'ucFirst' => true, ], $opts); - $data = \is_array($data) ? \array_filter($data) : [\trim($data)]; - $title = \trim($title); + $data = is_array($data) ? array_filter($data) : [trim($data)]; + $title = trim($title); $panelData = []; // [ 'label' => 'value' ] $borderChar = $opts['borderChar']; @@ -101,37 +116,37 @@ public static function show($data, string $title = 'Information Panel', array $o foreach ($data as $label => $value) { // label exists - if (!\is_numeric($label)) { - $width = \mb_strlen($label, 'UTF-8'); + if (!is_numeric($label)) { + $width = mb_strlen($label, 'UTF-8'); $labelMaxWidth = $width > $labelMaxWidth ? $width : $labelMaxWidth; } // translate array to string - if (\is_array($value)) { + if (is_array($value)) { $temp = ''; /** @var array $value */ foreach ($value as $key => $val) { - if (\is_bool($val)) { + if (is_bool($val)) { $val = $val ? 'True' : 'False'; } else { $val = (string)$val; } - $temp .= (!\is_numeric($key) ? "$key: " : '') . "$val, "; + $temp .= (!is_numeric($key) ? "$key: " : '') . "$val, "; } - $value = \rtrim($temp, ' ,'); - } elseif (\is_bool($value)) { + $value = rtrim($temp, ' ,'); + } elseif (is_bool($value)) { $value = $value ? 'True' : 'False'; } else { - $value = \trim((string)$value); + $value = trim((string)$value); } // get value width /** @var string $value */ - $value = \trim($value); - $width = \mb_strlen(\strip_tags($value), 'UTF-8'); // must clear style tag + $value = trim($value); + $width = mb_strlen(strip_tags($value), 'UTF-8'); // must clear style tag $valueMaxWidth = $width > $valueMaxWidth ? $width : $valueMaxWidth; $panelData[$label] = $value; @@ -143,16 +158,16 @@ public static function show($data, string $title = 'Information Panel', array $o // output title if ($title) { - $title = \ucwords($title); - $titleLength = \mb_strlen($title, 'UTF-8'); + $title = ucwords($title); + $titleLength = mb_strlen($title, 'UTF-8'); $panelWidth = $panelWidth > $titleLength ? $panelWidth : $titleLength; - $indentSpace = \str_pad(' ', \ceil($panelWidth / 2) - \ceil($titleLength / 2) + 2 * 2, ' '); + $indentSpace = str_pad(' ', ceil($panelWidth / 2) - ceil($titleLength / 2) + 2 * 2, ' '); Console::write(" {$indentSpace}{$title}"); } // output panel top border if ($borderChar) { - $border = \str_pad($borderChar, $panelWidth + (3 * 3), $borderChar); + $border = str_pad($borderChar, $panelWidth + (3 * 3), $borderChar); Console::write(' ' . $border); } @@ -188,8 +203,8 @@ public function format(): string } $buffer = new StrBuffer(); - $data = \is_array($this->data) ? \array_filter($this->data) : [\trim($this->data)]; - $title = \trim($this->title); + $data = is_array($this->data) ? array_filter($this->data) : [trim($this->data)]; + $title = trim($this->title); $panelData = []; // [ 'label' => 'value' ] $borderChar = $this->borderXChar; @@ -199,37 +214,37 @@ public function format(): string foreach ($data as $label => $value) { // label exists - if (!\is_numeric($label)) { - $width = \mb_strlen($label, 'UTF-8'); + if (!is_numeric($label)) { + $width = mb_strlen($label, 'UTF-8'); $labelMaxWidth = $width > $labelMaxWidth ? $width : $labelMaxWidth; } // translate array to string - if (\is_array($value)) { + if (is_array($value)) { $temp = ''; /** @var array $value */ foreach ($value as $key => $val) { - if (\is_bool($val)) { + if (is_bool($val)) { $val = $val ? 'True' : 'False'; } else { $val = (string)$val; } - $temp .= (!\is_numeric($key) ? "$key: " : '') . "$val, "; + $temp .= (!is_numeric($key) ? "$key: " : '') . "$val, "; } - $value = \rtrim($temp, ' ,'); - } elseif (\is_bool($value)) { + $value = rtrim($temp, ' ,'); + } elseif (is_bool($value)) { $value = $value ? 'True' : 'False'; } else { - $value = \trim((string)$value); + $value = trim((string)$value); } // get value width /** @var string $value */ - $value = \trim($value); - $width = \mb_strlen(\strip_tags($value), 'UTF-8'); // must clear style tag + $value = trim($value); + $width = mb_strlen(strip_tags($value), 'UTF-8'); // must clear style tag $valueMaxWidth = $width > $valueMaxWidth ? $width : $valueMaxWidth; $panelData[$label] = $value; @@ -239,17 +254,17 @@ public function format(): string // output title if ($title) { - $title = \ucwords($title); - $titleLength = \mb_strlen($title, 'UTF-8'); + $title = ucwords($title); + $titleLength = mb_strlen($title, 'UTF-8'); $panelWidth = $panelWidth > $titleLength ? $panelWidth : $titleLength; - $indentSpace = \str_pad(' ', \ceil($panelWidth / 2) - \ceil($titleLength / 2) + 2 * 2, ' '); + $indentSpace = str_pad(' ', ceil($panelWidth / 2) - ceil($titleLength / 2) + 2 * 2, ' '); $buffer->write(" {$indentSpace}{$title}\n"); } // output panel top border if ($topBorder = $this->titleBorder) { - $border = \str_pad($topBorder, $panelWidth + (3 * 3), $topBorder); - $buffer->write(' ' . $border . \PHP_EOL); + $border = str_pad($topBorder, $panelWidth + (3 * 3), $topBorder); + $buffer->write(' ' . $border . PHP_EOL); } // output panel body @@ -265,8 +280,8 @@ public function format(): string // output panel bottom border if ($footBorder = $this->footerBorder) { - $border = \str_pad($footBorder, $panelWidth + (3 * 3), $footBorder); - $buffer->write(' ' . $border . \PHP_EOL); + $border = str_pad($footBorder, $panelWidth + (3 * 3), $footBorder); + $buffer->write(' ' . $border . PHP_EOL); } unset($panelData); @@ -275,6 +290,7 @@ public function format(): string /** * @param bool $border + * * @return $this */ public function showBorder($border): self diff --git a/src/Component/Formatter/Section.php b/src/Component/Formatter/Section.php index 68e749ad..e58b73d9 100644 --- a/src/Component/Formatter/Section.php +++ b/src/Component/Formatter/Section.php @@ -6,21 +6,30 @@ use Inhere\Console\Console; use Inhere\Console\Util\FormatUtil; use Toolkit\StrUtil\Str; +use function array_merge; +use function ceil; +use function implode; +use function is_array; +use function str_pad; +use function trim; +use function ucwords; +use const PHP_EOL; /** * Class Section + * * @package Inhere\Console\Component\Formatter */ class Section extends MessageFormatter { /** * @param string $title The title text - * @param string|array $body The section body message + * @param string|array $body The section body message * @param array $opts */ public static function show(string $title, $body, array $opts = []): void { - $opts = \array_merge([ + $opts = array_merge([ 'width' => 80, 'char' => self::CHAR_HYPHEN, 'titlePos' => self::POS_LEFT, @@ -31,11 +40,11 @@ public static function show(string $title, $body, array $opts = []): void // list($sW, $sH) = Helper::getScreenSize(); $width = (int)$opts['width']; - $char = \trim($opts['char']); + $char = trim($opts['char']); $indent = (int)$opts['indent'] >= 0 ? $opts['indent'] : 2; $indentStr = Str::pad(self::CHAR_SPACE, $indent, self::CHAR_SPACE); - $title = \ucwords(\trim($title)); + $title = ucwords(trim($title)); $tLength = Str::len($title); $width = $width > 10 ? $width : 80; @@ -43,9 +52,9 @@ public static function show(string $title, $body, array $opts = []): void if ($tLength >= $width) { $titleIndent = Str::pad(self::CHAR_SPACE, $indent, self::CHAR_SPACE); } elseif ($opts['titlePos'] === self::POS_RIGHT) { - $titleIndent = \str_pad(self::CHAR_SPACE, \ceil($width - $tLength) + $indent, self::CHAR_SPACE); + $titleIndent = str_pad(self::CHAR_SPACE, ceil($width - $tLength) + $indent, self::CHAR_SPACE); } elseif ($opts['titlePos'] === self::POS_MIDDLE) { - $titleIndent = \str_pad(self::CHAR_SPACE, \ceil(($width - $tLength) / 2) + $indent, self::CHAR_SPACE); + $titleIndent = str_pad(self::CHAR_SPACE, ceil(($width - $tLength) / 2) + $indent, self::CHAR_SPACE); } else { $titleIndent = Str::pad(self::CHAR_SPACE, $indent, self::CHAR_SPACE); } @@ -58,7 +67,7 @@ public static function show(string $title, $body, array $opts = []): void $showBBorder = (bool)$opts['bottomBorder']; if ($showTBorder || $showBBorder) { - $border = \str_pad($char, $width, $char); + $border = str_pad($char, $width, $char); if ($showTBorder) { $topBorder = "{$indentStr}$border\n"; @@ -69,7 +78,7 @@ public static function show(string $title, $body, array $opts = []): void } } - $body = \is_array($body) ? \implode(\PHP_EOL, $body) : $body; + $body = is_array($body) ? implode(PHP_EOL, $body) : $body; $body = FormatUtil::wrapText($body, 4, $opts['width']); Console::writef($template, $titleLine, $topBorder, $body, $bottomBorder); diff --git a/src/Component/Formatter/SingleList.php b/src/Component/Formatter/SingleList.php index a74d743f..7ae366f9 100644 --- a/src/Component/Formatter/SingleList.php +++ b/src/Component/Formatter/SingleList.php @@ -2,10 +2,14 @@ namespace Inhere\Console\Component\Formatter; +use function array_merge; use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; use Inhere\Console\Util\FormatUtil; +use const PHP_EOL; use Toolkit\Cli\ColorTag; +use function trim; +use function ucwords; /** * Class SingleList - Format and render a single list @@ -32,7 +36,7 @@ class SingleList extends MessageFormatter public static function show($data, string $title = '', array $opts = []) { $string = ''; - $opts = \array_merge([ + $opts = array_merge([ 'leftChar' => ' ', // 'sepChar' => ' ', 'keyStyle' => 'info', @@ -44,8 +48,8 @@ public static function show($data, string $title = '', array $opts = []) // title if ($title) { - $title = \ucwords(\trim($title)); - $string .= ColorTag::wrap($title, $opts['titleStyle']) . \PHP_EOL; + $title = ucwords(trim($title)); + $string .= ColorTag::wrap($title, $opts['titleStyle']) . PHP_EOL; } // handle item list diff --git a/src/Component/Formatter/Table.php b/src/Component/Formatter/Table.php index 01e8af97..2cef3e7f 100644 --- a/src/Component/Formatter/Table.php +++ b/src/Component/Formatter/Table.php @@ -8,10 +8,19 @@ namespace Inhere\Console\Component\Formatter; +use function array_keys; +use function array_merge; +use function array_sum; +use function ceil; +use function count; use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; +use function is_string; +use function mb_strlen; +use function str_pad; use Toolkit\Cli\ColorTag; use Toolkit\StrUtil\StrBuffer; +use function ucwords; /** * Class Table - Tabular data display @@ -78,7 +87,7 @@ public static function show(array $data, string $title = 'Data Table', array $op } $buf = new StrBuffer(); - $opts = \array_merge([ + $opts = array_merge([ 'showBorder' => true, 'leftIndent' => ' ', 'titlePos' => self::POS_LEFT, @@ -101,7 +110,7 @@ public static function show(array $data, string $title = 'Data Table', array $op $colBorderChar = $opts['colBorderChar']; $info = [ - 'rowCount' => \count($data), + 'rowCount' => count($data), 'columnCount' => 0, // how many column in the table. 'columnMaxWidth' => [], // table column max width 'tableWidth' => 0, // table width. equals to all max column width's sum. @@ -111,16 +120,16 @@ public static function show(array $data, string $title = 'Data Table', array $op foreach ($data as $row) { // collection all field name if ($rowIndex === 0) { - $head = $tableHead ?: \array_keys($row); + $head = $tableHead ?: array_keys($row); // - $info['columnCount'] = \count($row); + $info['columnCount'] = count($row); foreach ($head as $index => $name) { - if (\is_string($name)) {// maybe no column name. + if (is_string($name)) {// maybe no column name. $hasHead = true; } - $info['columnMaxWidth'][$index] = \mb_strlen($name, 'UTF-8'); + $info['columnMaxWidth'][$index] = mb_strlen($name, 'UTF-8'); } } @@ -129,14 +138,14 @@ public static function show(array $data, string $title = 'Data Table', array $op foreach ((array)$row as $value) { // collection column max width if (isset($info['columnMaxWidth'][$colIndex])) { - $colWidth = \mb_strlen($value, 'UTF-8'); + $colWidth = mb_strlen($value, 'UTF-8'); // If current column width gt old column width. override old width. if ($colWidth > $info['columnMaxWidth'][$colIndex]) { $info['columnMaxWidth'][$colIndex] = $colWidth; } } else { - $info['columnMaxWidth'][$colIndex] = \mb_strlen($value, 'UTF-8'); + $info['columnMaxWidth'][$colIndex] = mb_strlen($value, 'UTF-8'); } $colIndex++; @@ -145,19 +154,19 @@ public static function show(array $data, string $title = 'Data Table', array $op $rowIndex++; } - $tableWidth = $info['tableWidth'] = \array_sum($info['columnMaxWidth']); + $tableWidth = $info['tableWidth'] = array_sum($info['columnMaxWidth']); $columnCount = $info['columnCount']; // output title if ($title) { $tStyle = $opts['titleStyle'] ?: 'bold'; - $title = \ucwords(trim($title)); - $titleLength = \mb_strlen($title, 'UTF-8'); - $indentSpace = \str_pad(' ', \ceil($tableWidth / 2) - \ceil($titleLength / 2) + ($columnCount * 2), ' '); + $title = ucwords(trim($title)); + $titleLength = mb_strlen($title, 'UTF-8'); + $indentSpace = str_pad(' ', ceil($tableWidth / 2) - ceil($titleLength / 2) + ($columnCount * 2), ' '); $buf->write(" {$indentSpace}<$tStyle>{$title}\n"); } - $border = $leftIndent . \str_pad($rowBorderChar, $tableWidth + ($columnCount * 3) + 2, $rowBorderChar); + $border = $leftIndent . str_pad($rowBorderChar, $tableWidth + ($columnCount * 3) + 2, $rowBorderChar); // output table top border if ($showBorder) { @@ -173,7 +182,7 @@ public static function show(array $data, string $title = 'Data Table', array $op foreach ($head as $index => $name) { $colMaxWidth = $info['columnMaxWidth'][$index]; // format - $name = \str_pad($name, $colMaxWidth, ' '); + $name = str_pad($name, $colMaxWidth, ' '); $name = ColorTag::wrap($name, $opts['headStyle']); $headStr .= " {$name} {$colBorderChar}"; } @@ -182,7 +191,7 @@ public static function show(array $data, string $title = 'Data Table', array $op // head border: split head and body if ($headBorderChar = $opts['headBorderChar']) { - $headBorder = $leftIndent . \str_pad($headBorderChar, $tableWidth + ($columnCount * 3) + 2, + $headBorder = $leftIndent . str_pad($headBorderChar, $tableWidth + ($columnCount * 3) + 2, $headBorderChar); $buf->write($headBorder . "\n"); } @@ -198,7 +207,7 @@ public static function show(array $data, string $title = 'Data Table', array $op foreach ((array)$row as $value) { $colMaxWidth = $info['columnMaxWidth'][$colIndex]; // format - $value = \str_pad($value, $colMaxWidth, ' '); + $value = str_pad($value, $colMaxWidth, ' '); $value = ColorTag::wrap($value, $opts['bodyStyle']); $rowStr .= " {$value} {$colBorderChar}"; $colIndex++; diff --git a/src/Component/Formatter/Title.php b/src/Component/Formatter/Title.php index c8fe4416..31948b4a 100644 --- a/src/Component/Formatter/Title.php +++ b/src/Component/Formatter/Title.php @@ -4,8 +4,11 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; -use Inhere\Console\Util\Show; use Toolkit\StrUtil\Str; +use function array_merge; +use function ceil; +use function str_pad; +use Toolkit\Sys\Sys; /** * Class Title @@ -19,7 +22,7 @@ class Title extends MessageFormatter */ public static function show(string $title, array $opts = []): void { - $opts = \array_merge([ + $opts = array_merge([ 'width' => 80, 'char' => self::CHAR_EQUAL, 'titlePos' => self::POS_LEFT, @@ -37,19 +40,24 @@ public static function show(string $title, array $opts = []): void $tLength = Str::len($title); $width = $width > 10 ? $width : 80; + [$sw,] = Sys::getScreenSize(); + if ($sw > $width) { + $width = $sw; + } + // title position if ($tLength >= $width) { $titleIndent = Str::pad(self::CHAR_SPACE, $indent, self::CHAR_SPACE); } elseif ($opts['titlePos'] === self::POS_RIGHT) { - $titleIndent = Str::pad(self::CHAR_SPACE, \ceil($width - $tLength) + $indent, self::CHAR_SPACE); + $titleIndent = Str::pad(self::CHAR_SPACE, ceil($width - $tLength) + $indent, self::CHAR_SPACE); } elseif ($opts['titlePos'] === self::POS_MIDDLE) { - $titleIndent = Str::pad(self::CHAR_SPACE, \ceil(($width - $tLength) / 2) + $indent, self::CHAR_SPACE); + $titleIndent = Str::pad(self::CHAR_SPACE, ceil(($width - $tLength) / 2) + $indent, self::CHAR_SPACE); } else { $titleIndent = Str::pad(self::CHAR_SPACE, $indent, self::CHAR_SPACE); } $titleLine = "$titleIndent$title\n"; - $border = $indentStr . \str_pad($char, $width, $char); + $border = $indentStr . str_pad($char, $width, $char); Console::write($titleLine . $border); } diff --git a/src/Component/Formatter/Tree.php b/src/Component/Formatter/Tree.php index 8bcb0f0e..d091ecff 100644 --- a/src/Component/Formatter/Tree.php +++ b/src/Component/Formatter/Tree.php @@ -8,9 +8,13 @@ namespace Inhere\Console\Component\Formatter; +use function array_merge; use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; use Inhere\Console\Util\FormatUtil; +use function is_array; +use function is_scalar; +use function str_pad; use Toolkit\Cli\Cli; /** @@ -39,7 +43,7 @@ public static function show(array $data, array $opts = []): void if ($started) { $started = 0; - $opts = \array_merge([ + $opts = array_merge([ // 'char' => Cli::isSupportColor() ? '─' : '-', // —— 'char' => '-', 'prefix' => Cli::isSupportColor() ? '├' : '|', @@ -53,12 +57,12 @@ public static function show(array $data, array $opts = []): void } foreach ($data as $key => $value) { - if (\is_scalar($value)) { + if (is_scalar($value)) { $counter++; - $leftString = $opts['leftPadding'] . \str_pad($opts['prefix'], $opts['_level'] + 1, $opts['char']); + $leftString = $opts['leftPadding'] . str_pad($opts['prefix'], $opts['_level'] + 1, $opts['char']); Console::write($leftString . ' ' . FormatUtil::typeToString($value)); - } elseif (\is_array($value)) { + } elseif (is_array($value)) { $newOpts = $opts; $newOpts['_is_main'] = false; $newOpts['_level']++; diff --git a/src/Component/Interact/Checkbox.php b/src/Component/Interact/Checkbox.php index a349f4bf..af5af6a1 100644 --- a/src/Component/Interact/Checkbox.php +++ b/src/Component/Interact/Checkbox.php @@ -2,9 +2,15 @@ namespace Inhere\Console\Component\Interact; +use function array_filter; +use function explode; use Inhere\Console\Component\InteractMessage; use Inhere\Console\Console; use Inhere\Console\Util\Show; +use function is_array; +use function str_replace; +use function strpos; +use function trim; /** * Class Checkbox @@ -24,12 +30,12 @@ class Checkbox extends InteractMessage */ public static function select(string $description, $options, $default = null, $allowExit = true): array { - if (!$description = \trim($description)) { + if (!$description = trim($description)) { Show::error('Please provide a description text!', 1); } $sep = ','; // ',' ' ' - $options = \is_array($options) ? $options : \explode(',', $options); + $options = is_array($options) ? $options : explode(',', $options); // If default option is error if (null !== $default && !isset($options[$default])) { @@ -53,7 +59,7 @@ public static function select(string $description, $options, $default = null, $a beginChoice: $r = Console::readln("Your choice{$defText} : "); - $r = $r !== '' ? \str_replace(' ', '', \trim($r, $sep)) : ''; + $r = $r !== '' ? str_replace(' ', '', trim($r, $sep)) : ''; // empty if ($r === '') { @@ -65,7 +71,7 @@ public static function select(string $description, $options, $default = null, $a Console::write("\n Quit,ByeBye.", true, true); } - $rs = \strpos($r, $sep) ? \array_filter(\explode($sep, $r), $filter) : [$r]; + $rs = strpos($r, $sep) ? array_filter(explode($sep, $r), $filter) : [$r]; // error, try again if (!$rs) { diff --git a/src/Component/Interact/Choose.php b/src/Component/Interact/Choose.php index 40839900..22cc0458 100644 --- a/src/Component/Interact/Choose.php +++ b/src/Component/Interact/Choose.php @@ -2,9 +2,13 @@ namespace Inhere\Console\Component\Interact; +use function array_key_exists; +use function explode; use Inhere\Console\Component\InteractMessage; use Inhere\Console\Console; use Inhere\Console\Util\Show; +use function is_array; +use function trim; /** * Class Choose @@ -28,11 +32,11 @@ class Choose extends InteractMessage */ public static function one(string $description, $options, $default = null, bool $allowExit = true): string { - if (!$description = \trim($description)) { + if (!$description = trim($description)) { Show::error('Please provide a description text!', 1); } - $options = \is_array($options) ? $options : \explode(',', $options); + $options = is_array($options) ? $options : explode(',', $options); // If default option is error if (null !== $default && !isset($options[$default])) { @@ -55,7 +59,7 @@ public static function one(string $description, $options, $default = null, bool $r = Console::readln("Your choice{$defaultText} : "); // error, allow try again once. - if (!\array_key_exists($r, $options)) { + if (!array_key_exists($r, $options)) { goto beginChoice; } diff --git a/src/Component/Interact/Confirm.php b/src/Component/Interact/Confirm.php index dc8a4658..6d3afb73 100644 --- a/src/Component/Interact/Confirm.php +++ b/src/Component/Interact/Confirm.php @@ -5,6 +5,9 @@ use Inhere\Console\Component\InteractMessage; use Inhere\Console\Console; use Inhere\Console\Util\Show; +use function stripos; +use function trim; +use function ucfirst; /** * Class Confirm @@ -20,13 +23,13 @@ class Confirm extends InteractMessage */ public static function ask(string $question, bool $default = true): bool { - if (!$question = \trim($question)) { + if (!$question = trim($question)) { Show::warning('Please provide a question message!', 1); return false; } $defText = $default ? 'yes' : 'no'; - $question = \ucfirst(\trim($question, '?')); + $question = ucfirst(trim($question, '?')); $message = "$question ?\nPlease confirm (yes|no)[default:$defText]: "; while (true) { @@ -36,11 +39,11 @@ public static function ask(string $question, bool $default = true): bool return $default; } - if (0 === \stripos($answer, 'y')) { + if (0 === stripos($answer, 'y')) { return true; } - if (0 === \stripos($answer, 'n')) { + if (0 === stripos($answer, 'n')) { return false; } } diff --git a/src/Component/Interact/LimitedAsk.php b/src/Component/Interact/LimitedAsk.php index 1b4b46ec..b1332ba7 100644 --- a/src/Component/Interact/LimitedAsk.php +++ b/src/Component/Interact/LimitedAsk.php @@ -2,9 +2,13 @@ namespace Inhere\Console\Component\Interact; +use Closure; use Inhere\Console\Component\InteractMessage; use Inhere\Console\Console; use Inhere\Console\Util\Show; +use function sprintf; +use function trim; +use function ucfirst; /** * Class LimitedAsk @@ -19,7 +23,7 @@ class LimitedAsk extends InteractMessage * 否则,会连续询问 $times 次, 若仍然错误,退出 * @param string $question 问题 * @param string $default 默认值 - * @param \Closure $validator (默认验证输入是否为空)自定义回调验证输入是否符合要求; 验证成功返回true 否则 可返回错误消息 + * @param Closure $validator (默认验证输入是否为空)自定义回调验证输入是否符合要求; 验证成功返回true 否则 可返回错误消息 * @example This is an example * * ```php @@ -50,17 +54,17 @@ class LimitedAsk extends InteractMessage public static function ask( string $question, string $default = '', - \Closure $validator = null, + Closure $validator = null, int $times = 3 ): string { - if (!$question = \trim($question)) { + if (!$question = trim($question)) { Show::error('Please provide a question text!', 1); } $answer = ''; $back = $times = ($times > 6 || $times < 1) ? 3 : $times; - $question = \ucfirst($question); + $question = ucfirst($question); $hasDefault = '' !== $default; if ($hasDefault) { @@ -80,7 +84,7 @@ public static function ask( } } else { $num = $times + 1; - $answer = Console::readln(\sprintf('(You have [%s] chances to enter!) ', $num)); + $answer = Console::readln(sprintf('(You have [%s] chances to enter!) ', $num)); } // If setting verify callback diff --git a/src/Component/Interact/Password.php b/src/Component/Interact/Password.php index 7fba7e2b..ebc47d5c 100644 --- a/src/Component/Interact/Password.php +++ b/src/Component/Interact/Password.php @@ -2,8 +2,16 @@ namespace Inhere\Console\Component\Interact; +use function addslashes; +use function escapeshellarg; +use function file_put_contents; use Inhere\Console\Component\InteractMessage; +use function rtrim; +use RuntimeException; +use function shell_exec; +use function sprintf; use Toolkit\Sys\Sys; +use function unlink; /** * Class Password @@ -19,11 +27,11 @@ class Password extends InteractMessage * @return string * @link https://stackoverflow.com/questions/187736/command-line-password-prompt-in-php * @link http://www.sitepoint.com/blogs/2009/05/01/interactive-cli-password-prompt-in-php - * @throws \RuntimeException + * @throws RuntimeException */ public static function ask(string $prompt = 'Enter Password:'): string { - $prompt = $prompt ? \addslashes($prompt) : 'Enter:'; + $prompt = $prompt ? addslashes($prompt) : 'Enter:'; // $checkCmd = "/usr/bin/env bash -c 'echo OK'"; // $shell = 'echo $0'; @@ -31,7 +39,7 @@ public static function ask(string $prompt = 'Enter Password:'): string // linux, unix, git-bash if (Sys::shIsAvailable()) { // COMMAND: sh -c 'read -p "Enter Password:" -s user_input && echo $user_input' - $command = \sprintf('sh -c "read -p \'%s\' -s user_input && echo $user_input"', $prompt); + $command = sprintf('sh -c "read -p \'%s\' -s user_input && echo $user_input"', $prompt); $password = Sys::execute($command, false); print "\n"; @@ -42,16 +50,16 @@ public static function ask(string $prompt = 'Enter Password:'): string if (Sys::isWindows()) { $vbFile = Sys::getTempDir() . '/hidden_prompt_input.vbs'; - \file_put_contents($vbFile, \sprintf('wscript.echo(InputBox("%s", "", "password here"))', $prompt)); + file_put_contents($vbFile, sprintf('wscript.echo(InputBox("%s", "", "password here"))', $prompt)); - $command = 'cscript //nologo ' . \escapeshellarg($vbFile); - $password = \rtrim(\shell_exec($command)); - \unlink($vbFile); + $command = 'cscript //nologo ' . escapeshellarg($vbFile); + $password = rtrim(shell_exec($command)); + unlink($vbFile); return $password; } - throw new \RuntimeException('Can not invoke bash shell env'); + throw new RuntimeException('Can not invoke bash shell env'); } } diff --git a/src/Component/Interact/Question.php b/src/Component/Interact/Question.php index e5de3feb..694cea8b 100644 --- a/src/Component/Interact/Question.php +++ b/src/Component/Interact/Question.php @@ -2,9 +2,12 @@ namespace Inhere\Console\Component\Interact; +use Closure; use Inhere\Console\Component\InteractMessage; use Inhere\Console\Console; use Inhere\Console\Util\Show; +use function trim; +use function ucfirst; /** * Class Question @@ -46,17 +49,17 @@ class Question extends InteractMessage * ``` * @param string $question * @param string $default - * @param \Closure|null $validator Validator, must return bool. + * @param Closure|null $validator Validator, must return bool. * @return string */ - public static function ask(string $question, string $default = '', \Closure $validator = null): string + public static function ask(string $question, string $default = '', Closure $validator = null): string { - if (!$question = \trim($question)) { + if (!$question = trim($question)) { Show::error('Please provide a question text!', 1); } $defText = '' !== $default ? "(default: $default)" : ''; - $message = '' . \ucfirst($question) . "$defText "; + $message = '' . ucfirst($question) . "$defText "; askQuestion: $answer = Console::readln($message); diff --git a/src/Component/MessageFormatter.php b/src/Component/MessageFormatter.php index 51291cea..6812c6c9 100644 --- a/src/Component/MessageFormatter.php +++ b/src/Component/MessageFormatter.php @@ -10,6 +10,7 @@ use Inhere\Console\Console; use Inhere\Console\Contract\FormatterInterface; +use RuntimeException; use Toolkit\PhpUtil\PhpHelper; /** @@ -61,7 +62,7 @@ public function __toString() */ public function format(): string { - throw new \RuntimeException('Please implement the method on sub-class'); + throw new RuntimeException('Please implement the method on sub-class'); } /** diff --git a/src/Component/NotifyMessage.php b/src/Component/NotifyMessage.php index 52c63638..9580d650 100644 --- a/src/Component/NotifyMessage.php +++ b/src/Component/NotifyMessage.php @@ -8,8 +8,11 @@ namespace Inhere\Console\Component; +use RuntimeException; + /** * Class NotifyMessage - like progress, spinner .... + * * @package Inhere\Console\Component * @link https://github.com/wp-cli/php-cli-tools/tree/master/lib/cli */ @@ -36,6 +39,6 @@ public function setSpeed($speed): void public function display(): void { - throw new \RuntimeException('Please implement the method on sub-class'); + throw new RuntimeException('Please implement the method on sub-class'); } } diff --git a/src/Component/PharCompiler.php b/src/Component/PharCompiler.php index 71a02d52..810e8ba8 100644 --- a/src/Component/PharCompiler.php +++ b/src/Component/PharCompiler.php @@ -8,9 +8,54 @@ namespace Inhere\Console\Component; +use BadMethodCallException; +use Closure; +use DateTime; +use DateTimeZone; +use Exception; +use FilesystemIterator; +use Generator; use Inhere\Console\Util\Helper; +use Iterator; +use Phar; +use RuntimeException; use Seld\PharUtils\Timestamps; +use SplFileInfo; use Toolkit\Sys\Sys; +use UnexpectedValueException; +use function array_flip; +use function array_merge; +use function basename; +use function class_exists; +use function date; +use function explode; +use function file_exists; +use function file_get_contents; +use function file_put_contents; +use function function_exists; +use function in_array; +use function ini_get; +use function is_dir; +use function is_file; +use function is_string; +use function openssl_pkey_export; +use function openssl_pkey_get_details; +use function preg_replace; +use function realpath; +use function str_repeat; +use function str_replace; +use function stripos; +use function strlen; +use function strpos; +use function substr; +use function substr_count; +use function substr_replace; +use function token_get_all; +use function trim; +use function unlink; +use const T_COMMENT; +use const T_DOC_COMMENT; +use const T_WHITESPACE; /** * Class PharCompiler @@ -20,9 +65,9 @@ class PharCompiler { /** @var array */ private static $supportedSignatureTypes = [ - \Phar::SHA512 => 1, - \Phar::SHA256 => 1, - \Phar::SHA1 => 1 + Phar::SHA512 => 1, + Phar::SHA256 => 1, + Phar::SHA1 => 1 ]; /** @var resource */ @@ -47,7 +92,7 @@ class PharCompiler private $branchAliasVersion = 'UNKNOWN'; /** - * @var \DateTime + * @var DateTime */ private $versionDate; @@ -109,12 +154,12 @@ class PharCompiler private $directories = []; /** - * @var array|\Iterator The modifies files list. if not empty, will skip find dirs. + * @var array|Iterator The modifies files list. if not empty, will skip find dirs. */ private $modifies; /** - * @var \Closure[] Some events. if you want to get some info on packing. + * @var Closure[] Some events. if you want to get some info on packing. */ private $events = [ 'add' => 0, @@ -122,7 +167,7 @@ class PharCompiler ]; /** - * @var \Closure Maybe you not want strip all files. + * @var Closure Maybe you not want strip all files. */ private $stripFilter; @@ -152,7 +197,7 @@ class PharCompiler private $pharName; /** - * @var \Closure File filter + * @var Closure File filter */ private $fileFilter; @@ -162,31 +207,31 @@ class PharCompiler * @param string|array|null $files Only fetch the listed files * @param bool $overwrite * @return bool - * @throws \UnexpectedValueException - * @throws \BadMethodCallException - * @throws \RuntimeException + * @throws UnexpectedValueException + * @throws BadMethodCallException + * @throws RuntimeException */ public static function unpack(string $pharFile, string $extractTo, $files = null, $overwrite = false): bool { self::checkEnv(); - $phar = new \Phar($pharFile); + $phar = new Phar($pharFile); return $phar->extractTo($extractTo, $files, $overwrite); } /** * - * @throws \RuntimeException + * @throws RuntimeException */ private static function checkEnv(): void { - if (!\class_exists(\Phar::class, false)) { - throw new \RuntimeException("The 'phar' extension is required for build phar package"); + if (!class_exists(Phar::class, false)) { + throw new RuntimeException("The 'phar' extension is required for build phar package"); } - if (\ini_get('phar.readonly')) { - throw new \RuntimeException( + if (ini_get('phar.readonly')) { + throw new RuntimeException( "The 'phar.readonly' is 'On', build phar must setting it 'Off' or exec with 'php -d phar.readonly=0'" ); } @@ -195,16 +240,16 @@ private static function checkEnv(): void /** * PharCompiler constructor. * @param string $basePath - * @throws \RuntimeException + * @throws RuntimeException */ public function __construct(string $basePath) { self::checkEnv(); - $this->basePath = \realpath($basePath); + $this->basePath = realpath($basePath); - if (!\is_dir($this->basePath)) { - throw new \RuntimeException("The inputted project path is not exists. DIR: {$this->basePath}"); + if (!is_dir($this->basePath)) { + throw new RuntimeException("The inputted project path is not exists. DIR: {$this->basePath}"); } } @@ -214,7 +259,7 @@ public function __construct(string $basePath) */ public function addSuffix($suffixes): self { - $this->suffixes = \array_merge($this->suffixes, (array)$suffixes); + $this->suffixes = array_merge($this->suffixes, (array)$suffixes); return $this; } @@ -225,7 +270,7 @@ public function addSuffix($suffixes): self */ public function notName($filename): self { - $this->notNames = \array_merge($this->notNames, (array)$filename); + $this->notNames = array_merge($this->notNames, (array)$filename); return $this; } @@ -236,7 +281,7 @@ public function notName($filename): self */ public function addExclude($dirs): self { - $this->excludes = \array_merge($this->excludes, (array)$dirs); + $this->excludes = array_merge($this->excludes, (array)$dirs); return $this; } @@ -247,7 +292,7 @@ public function addExclude($dirs): self */ public function addFile($files): self { - $this->files = \array_merge($this->files, (array)$files); + $this->files = array_merge($this->files, (array)$files); return $this; } @@ -275,10 +320,10 @@ public function collectVersion($value): self } /** - * @param \Closure $stripFilter + * @param Closure $stripFilter * @return PharCompiler */ - public function setStripFilter(\Closure $stripFilter): PharCompiler + public function setStripFilter(Closure $stripFilter): PharCompiler { $this->stripFilter = $stripFilter; @@ -302,13 +347,14 @@ public function setShebang($shebang): PharCompiler */ public function in($dirs): self { - $this->directories = \array_merge($this->directories, (array)$dirs); + $this->directories = array_merge($this->directories, (array)$dirs); return $this; } /** - * @param array|\Iterator $modifies + * @param array|Iterator $modifies + * * @return PharCompiler */ public function setModifies($modifies): self @@ -323,37 +369,37 @@ public function setModifies($modifies): self * @param string $pharFile The full path to the file to create * @param bool $refresh * @return string - * @throws \UnexpectedValueException - * @throws \BadMethodCallException - * @throws \RuntimeException - * @throws \Exception + * @throws UnexpectedValueException + * @throws BadMethodCallException + * @throws RuntimeException + * @throws Exception */ public function pack(string $pharFile, $refresh = true): string { if (!$this->directories) { - throw new \RuntimeException("Please setting the 'directories' want building directories by 'in()'"); + throw new RuntimeException("Please setting the 'directories' want building directories by 'in()'"); } - $exists = \file_exists($pharFile); + $exists = file_exists($pharFile); if ($refresh && $exists) { - \unlink($pharFile); + unlink($pharFile); } $this->pharFile = $pharFile; - $this->pharName = $pharName = \basename($this->pharFile); - $this->excludes = \array_flip($this->excludes); + $this->pharName = $pharName = basename($this->pharFile); + $this->excludes = array_flip($this->excludes); $this->collectInformation(); - $phar = new \Phar($pharFile, 0, $pharName); + $phar = new Phar($pharFile, 0, $pharName); if ($this->key !== null) { $privateKey = ''; - \openssl_pkey_export($this->key, $privateKey); - $phar->setSignatureAlgorithm(\Phar::OPENSSL, $privateKey); - $keyDetails = \openssl_pkey_get_details($this->key); - \file_put_contents($pharFile . '.pubkey', $keyDetails['key']); + openssl_pkey_export($this->key, $privateKey); + $phar->setSignatureAlgorithm(Phar::OPENSSL, $privateKey); + $keyDetails = openssl_pkey_get_details($this->key); + file_put_contents($pharFile . '.pubkey', $keyDetails['key']); } else { $phar->setSignatureAlgorithm($this->selectSignatureType()); } @@ -364,8 +410,8 @@ public function pack(string $pharFile, $refresh = true): string // only build modifies if (!$refresh && $exists && $this->modifies) { foreach ($this->modifies as $file) { - if ('/' === $file[0] || \is_file($file = $basePath . '/' . $file)) { - $this->packFile($phar, new \SplFileInfo($file)); + if ('/' === $file[0] || is_file($file = $basePath . '/' . $file)) { + $this->packFile($phar, new SplFileInfo($file)); } } } else { @@ -379,8 +425,8 @@ public function pack(string $pharFile, $refresh = true): string // add special files foreach ($this->files as $filename) { - if ('/' === $filename[0] || \is_file($filename = $basePath . '/' . $filename)) { - $this->packFile($phar, new \SplFileInfo($filename)); + if ('/' === $filename[0] || is_file($filename = $basePath . '/' . $filename)) { + $this->packFile($phar, new SplFileInfo($filename)); } } @@ -399,10 +445,10 @@ public function pack(string $pharFile, $refresh = true): string unset($phar); // re-sign the phar with reproducible timestamp / signature - if (\class_exists(Timestamps::class)) { + if (class_exists(Timestamps::class)) { $util = new Timestamps($pharFile); $util->updateTimestamps($this->versionDate); - $util->save($pharFile, \Phar::SHA1); + $util->save($pharFile, Phar::SHA1); } return $pharFile; @@ -410,9 +456,9 @@ public function pack(string $pharFile, $refresh = true): string /** * find changed or new created files by git status. - * @return \Generator + * @return Generator */ - public function findChangedByGit(): ?\Generator + public function findChangedByGit(): ?Generator { // -u expand dir's files [, $output,] = Sys::run('git status -s -u', $this->basePath); @@ -420,47 +466,48 @@ public function findChangedByGit(): ?\Generator // 'D some.file' deleted // ' M some.file' modified // '?? some.file' new file - foreach (\explode("\n", \trim($output)) as $file) { - $file = \trim($file); + foreach (explode("\n", trim($output)) as $file) { + $file = trim($file); // only php file. - if (!\strpos($file, '.php')) { + if (!strpos($file, '.php')) { continue; } // modified files - if (\strpos($file, 'M ') === 0) { - yield \substr($file, 2); + if (strpos($file, 'M ') === 0) { + yield substr($file, 2); // new files - } elseif (\strpos($file, '?? ') === 0) { - yield \substr($file, 3); + } elseif (strpos($file, '?? ') === 0) { + yield substr($file, 3); } } } /** * @param string $directory - * @return \Iterator|\SplFileInfo[] + * + * @return Iterator|SplFileInfo[] */ protected function findFiles(string $directory) { return Helper::directoryIterator( $directory, $this->createIteratorFilter(), - \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS + FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS ); } /** * Add a file to the Phar. - * @param \Phar $phar - * @param \SplFileInfo $file + * @param Phar $phar + * @param SplFileInfo $file */ - private function packFile(\Phar $phar, \SplFileInfo $file): void + private function packFile(Phar $phar, SplFileInfo $file): void { // skip error - if (!\file_exists($file)) { + if (!file_exists($file)) { $this->reportError("File $file is not exists!"); return; } @@ -468,10 +515,10 @@ private function packFile(\Phar $phar, \SplFileInfo $file): void $this->counter++; $path = $this->getRelativeFilePath($file); $strip = $this->stripComments; - $content = \file_get_contents($file); + $content = file_get_contents($file); // clear php file comments - if ($strip && \strpos($path, '.php')) { + if ($strip && strpos($path, '.php')) { $filter = $this->stripFilter; if (!$filter || ($filter && $filter($file))) { @@ -481,7 +528,7 @@ private function packFile(\Phar $phar, \SplFileInfo $file): void // have versionFile if ($path === $this->versionFile) { - $content = \str_replace([ + $content = str_replace([ '{@package_version}', '{@package_branch_alias_version}', '{@release_date}', @@ -500,14 +547,14 @@ private function packFile(\Phar $phar, \SplFileInfo $file): void } /** - * @param \Phar $phar + * @param Phar $phar */ - private function packIndexFile(\Phar $phar): void + private function packIndexFile(Phar $phar): void { if ($this->cliIndex) { $this->counter++; $path = $this->basePath . '/' . $this->cliIndex; - $content = \preg_replace('{^#!/usr/bin/env php\s*}', '', \file_get_contents($path)); + $content = preg_replace('{^#!/usr/bin/env php\s*}', '', file_get_contents($path)); if ($cb = $this->events['add']) { $cb($this->cliIndex, $this->counter); @@ -519,7 +566,7 @@ private function packIndexFile(\Phar $phar): void if ($this->webIndex) { $this->counter++; $path = $this->basePath . '/' . $this->webIndex; - $content = \file_get_contents($path); + $content = file_get_contents($path); if ($cb = $this->events['add']) { $cb($this->webIndex, $this->counter); @@ -539,12 +586,12 @@ public function getCounter(): int /** * @return string - * @throws \RuntimeException + * @throws RuntimeException */ private function createStub(): string { // var_dump($this);die; - $date = \date('Y-m-d H:i'); + $date = date('Y-m-d H:i'); $pharName = $this->pharName; $stub = <<shebang) { - $shebang = \is_string($shebang) ? $shebang : '#!/usr/bin/env php'; + $shebang = is_string($shebang) ? $shebang : '#!/usr/bin/env php'; $stub = "$shebang\n$stub"; } @@ -577,23 +624,23 @@ private function createStub(): string } elseif ($this->webIndex) { $stub .= "\nrequire 'phar://$pharName/{$this->webIndex}';\n"; } else { - throw new \RuntimeException("'cliIndex' and 'webIndex', please set at least one"); + throw new RuntimeException("'cliIndex' and 'webIndex', please set at least one"); } return $stub . "\n__HALT_COMPILER();\n"; } /** - * @return \Closure + * @return Closure */ - private function createIteratorFilter(): \Closure + private function createIteratorFilter(): Closure { if (!$this->fileFilter) { - $this->fileFilter = function (\SplFileInfo $file) { + $this->fileFilter = function (SplFileInfo $file) { $name = $file->getFilename(); // Skip hidden files and directories. - if (\strpos($name, '.') === 0) { + if (strpos($name, '.') === 0) { return false; } @@ -603,13 +650,13 @@ private function createIteratorFilter(): \Closure } // skip exclude files. - if ($this->notNames && \in_array($name, $this->notNames, true)) { + if ($this->notNames && in_array($name, $this->notNames, true)) { return false; } if ($this->suffixes) { foreach ($this->suffixes as $suffix) { - if (\stripos($name, $suffix)) { + if (stripos($name, $suffix)) { return true; } } @@ -630,23 +677,23 @@ private function createIteratorFilter(): \Closure */ private function stripWhitespace(string $source): string { - if (!\function_exists('token_get_all')) { + if (!function_exists('token_get_all')) { return $source; } $output = ''; - foreach (\token_get_all($source) as $token) { - if (\is_string($token)) { + foreach (token_get_all($source) as $token) { + if (is_string($token)) { $output .= $token; - } elseif (\in_array($token[0], [\T_COMMENT, \T_DOC_COMMENT], true)) { - $output .= \str_repeat("\n", \substr_count($token[1], "\n")); - } elseif (\T_WHITESPACE === $token[0]) { + } elseif (in_array($token[0], [T_COMMENT, T_DOC_COMMENT], true)) { + $output .= str_repeat("\n", substr_count($token[1], "\n")); + } elseif (T_WHITESPACE === $token[0]) { // reduce wide spaces - $whitespace = \preg_replace('{[ \t]+}', ' ', $token[1]); + $whitespace = preg_replace('{[ \t]+}', ' ', $token[1]); // normalize newlines to \n - $whitespace = \preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace); + $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace); // trim leading spaces - $whitespace = \preg_replace('{\n +}', "\n", $whitespace); + $whitespace = preg_replace('{\n +}', "\n", $whitespace); $output .= $whitespace; } else { $output .= $token[1]; @@ -658,8 +705,8 @@ private function stripWhitespace(string $source): string /** * auto collect project information by git log - * @throws \RuntimeException - * @throws \Exception + * @throws RuntimeException + * @throws Exception */ private function collectInformation(): void { @@ -671,39 +718,39 @@ private function collectInformation(): void [$code, $ret,] = Sys::run('git log --pretty="%H" -n1 HEAD', $basePath); if ($code !== 0) { - throw new \RuntimeException( + throw new RuntimeException( 'Can\'t run git log. You must ensure to run compile from git repository clone and that git binary is available.' ); } - $this->version = \trim($ret); + $this->version = trim($ret); [$code, $ret,] = Sys::run('git log -n1 --pretty=%ci HEAD', $basePath); if ($code !== 0) { - throw new \RuntimeException( + throw new RuntimeException( 'Can\'t run git log. You must ensure to run compile from git repository clone and that git binary is available.' ); } - $this->versionDate = new \DateTime(\trim($ret)); - $this->versionDate->setTimezone(new \DateTimeZone('UTC')); + $this->versionDate = new DateTime(trim($ret)); + $this->versionDate->setTimezone(new DateTimeZone('UTC')); // 获取到最新的 tag [$code, $ret,] = Sys::run('git describe --tags --exact-match HEAD', $basePath); if ($code === 0) { - $this->version = \trim($ret); + $this->version = trim($ret); } else { [$code1, $ret,] = Sys::run('git branch', $basePath); if ($code1 === 0) { - $this->branchAliasVersion = \explode("\n", \trim($ret, "* \n"), 2)[0]; + $this->branchAliasVersion = explode("\n", trim($ret, "* \n"), 2)[0]; } } } /** - * @param \SplFileInfo $file + * @param SplFileInfo $file * @return string */ private function getRelativeFilePath($file): string @@ -711,10 +758,10 @@ private function getRelativeFilePath($file): string $realPath = $file->getRealPath(); $pathPrefix = $this->basePath . DIRECTORY_SEPARATOR; - $pos = \strpos($realPath, $pathPrefix); - $relativePath = $pos !== false ? \substr_replace($realPath, '', $pos, \strlen($pathPrefix)) : $realPath; + $pos = strpos($realPath, $pathPrefix); + $relativePath = $pos !== false ? substr_replace($realPath, '', $pos, strlen($pathPrefix)) : $realPath; - return \str_replace('\\', '/', $relativePath); + return str_replace('\\', '/', $relativePath); } /** @@ -730,25 +777,25 @@ private function reportError($error): void /** * add event handler * @param string $event - * @param \Closure $closure + * @param Closure $closure */ - public function on(string $event, \Closure $closure): void + public function on(string $event, Closure $closure): void { $this->events[$event] = $closure; } /** - * @param \Closure $onAdd + * @param Closure $onAdd */ - public function onAdd(\Closure $onAdd): void + public function onAdd(Closure $onAdd): void { $this->events['add'] = $onAdd; } /** - * @param \Closure $onError + * @param Closure $onError */ - public function onError(\Closure $onError): void + public function onError(Closure $onError): void { $this->events['error'] = $onError; } @@ -823,7 +870,7 @@ public function setWebIndex(string $webIndex): self } /** - * @return \Closure[] + * @return Closure[] */ public function getEvents(): array { @@ -895,7 +942,7 @@ private function selectSignatureType(): int return $this->signatureType; } - return \Phar::SHA1; + return Phar::SHA1; } /** diff --git a/src/Component/Progress/CounterText.php b/src/Component/Progress/CounterText.php index b1d52c63..2f998995 100644 --- a/src/Component/Progress/CounterText.php +++ b/src/Component/Progress/CounterText.php @@ -2,6 +2,7 @@ namespace Inhere\Console\Component\Progress; +use Generator; use Inhere\Console\Component\NotifyMessage; use Inhere\Console\Console; use Toolkit\Cli\Cli; @@ -30,9 +31,9 @@ class CounterText extends NotifyMessage * * @param string $msg * @param string $doneMsg - * @return \Generator + * @return Generator */ - public static function gen(string $msg, $doneMsg = ''): \Generator + public static function gen(string $msg, $doneMsg = ''): Generator { $counter = 0; $finished = false; diff --git a/src/Component/Progress/DynamicText.php b/src/Component/Progress/DynamicText.php index 17717c8f..e3e07cbb 100644 --- a/src/Component/Progress/DynamicText.php +++ b/src/Component/Progress/DynamicText.php @@ -2,8 +2,10 @@ namespace Inhere\Console\Component\Progress; +use Generator; use Inhere\Console\Component\NotifyMessage; use Inhere\Console\Console; +use function printf; use Toolkit\Cli\Cli; /** @@ -15,9 +17,9 @@ class DynamicText extends NotifyMessage /** * @param string $doneMsg * @param string $fixedMsg - * @return \Generator + * @return Generator */ - public static function gen(string $doneMsg, string $fixedMsg = ''): \Generator + public static function gen(string $doneMsg, string $fixedMsg = ''): Generator { $counter = 0; $finished = false; @@ -44,7 +46,7 @@ public static function gen(string $doneMsg, string $fixedMsg = ''): \Generator $finished = true; } - \printf($template, $msg); + printf($template, $msg); if ($finished) { echo "\n"; diff --git a/src/Component/Progress/SimpleBar.php b/src/Component/Progress/SimpleBar.php index 4efb805b..59a174e4 100644 --- a/src/Component/Progress/SimpleBar.php +++ b/src/Component/Progress/SimpleBar.php @@ -2,6 +2,9 @@ namespace Inhere\Console\Component\Progress; +use function array_merge; +use function ceil; +use Generator; use Inhere\Console\Component\NotifyMessage; use Inhere\Console\Console; use Toolkit\Cli\Cli; @@ -33,15 +36,15 @@ class SimpleBar extends NotifyMessage * @param int $total * @param array $opts * @internal int $current - * @return \Generator + * @return Generator */ - public static function gen(int $total, array $opts = []): \Generator + public static function gen(int $total, array $opts = []): Generator { $current = 0; $finished = false; $tplPrefix = Cli::isSupportColor() ? "\x0D\x1B[2K" : "\x0D\r"; - $opts = \array_merge([ + $opts = array_merge([ 'doneChar' => '=', 'waitChar' => ' ', 'signChar' => '>', @@ -65,7 +68,7 @@ public static function gen(int $total, array $opts = []): \Generator } $current += $step; - $percent = \ceil(($current / $total) * 100); + $percent = ceil(($current / $total) * 100); if ($percent >= 100) { $msg = $doneMsg ?: $msg; diff --git a/src/Component/Progress/SimpleTextBar.php b/src/Component/Progress/SimpleTextBar.php index 7a46deaa..0946dcb3 100644 --- a/src/Component/Progress/SimpleTextBar.php +++ b/src/Component/Progress/SimpleTextBar.php @@ -2,6 +2,7 @@ namespace Inhere\Console\Component\Progress; +use Generator; use Inhere\Console\Component\NotifyMessage; use Inhere\Console\Console; use Toolkit\Cli\Cli; @@ -17,9 +18,9 @@ class SimpleTextBar extends NotifyMessage * @param int $total * @param string $msg * @param string $doneMsg - * @return \Generator + * @return Generator */ - public static function gen(int $total, string $msg, string $doneMsg = ''): \Generator + public static function gen(int $total, string $msg, string $doneMsg = ''): Generator { $current = 0; $finished = false; diff --git a/src/Component/Style/Alert.php b/src/Component/Style/Alert.php index efd1287a..c10734c7 100644 --- a/src/Component/Style/Alert.php +++ b/src/Component/Style/Alert.php @@ -8,8 +8,11 @@ namespace Inhere\Console\Component\Style; +use function array_merge; + /** * Class Alert + * * @package Inhere\Console\Component\Style */ class Alert @@ -27,7 +30,7 @@ public static function create(string $message, string $style = 'info', array $op public static function block(string $message, string $style = 'info', array $opts = []): void { - $opts = \array_merge([ + $opts = array_merge([ 'paddingX' => 1, // line 'paddingY' => 1, // space diff --git a/src/Component/Style/Color.php b/src/Component/Style/Color.php index e07b8913..99f4bbc0 100644 --- a/src/Component/Style/Color.php +++ b/src/Component/Style/Color.php @@ -5,10 +5,20 @@ namespace Inhere\Console\Component\Style; +use function array_key_exists; +use function array_keys; +use function count; +use function explode; +use function implode; +use InvalidArgumentException; +use RuntimeException; +use function str_replace; + /** * Class Color * - fg unset 39 * - bg unset 49 + * * @package Inhere\Console\Component\Style */ class Color @@ -84,7 +94,7 @@ class Color * @param array $options * @param bool $extra * @return Color - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public static function make($fg = '', $bg = '', array $options = [], bool $extra = false): Color { @@ -96,20 +106,20 @@ public static function make($fg = '', $bg = '', array $options = [], bool $extra * * @param string $string e.g 'fg=white;bg=black;options=bold,underscore;extra=1' * @return static - * @throws \InvalidArgumentException - * @throws \RuntimeException + * @throws InvalidArgumentException + * @throws RuntimeException */ public static function makeByString($string) { $fg = $bg = ''; $extra = false; $options = []; - $parts = \explode(';', \str_replace(' ', '', $string)); + $parts = explode(';', str_replace(' ', '', $string)); foreach ($parts as $part) { - $subParts = \explode('=', $part); + $subParts = explode('=', $part); - if (\count($subParts) < 2) { + if (count($subParts) < 2) { continue; } @@ -124,10 +134,10 @@ public static function makeByString($string) $extra = (bool)$subParts[1]; break; case 'options': - $options = \explode(',', $subParts[1]); + $options = explode(',', $subParts[1]); break; default: - throw new \RuntimeException('Invalid option'); + throw new RuntimeException('Invalid option'); break; } } @@ -141,16 +151,16 @@ public static function makeByString($string) * @param string $bg Background color. e.g 'black' * @param array $options Style options. e.g ['bold', 'underscore'] * @param bool $extra - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function __construct($fg = '', $bg = '', array $options = [], bool $extra = false) { if ($fg) { - if (false === \array_key_exists($fg, static::$knownColors)) { - throw new \InvalidArgumentException( + if (false === array_key_exists($fg, static::$knownColors)) { + throw new InvalidArgumentException( sprintf('Invalid foreground color "%1$s" [%2$s]', $fg, - \implode(', ', $this->getKnownColors()) + implode(', ', $this->getKnownColors()) ) ); } @@ -159,11 +169,11 @@ public function __construct($fg = '', $bg = '', array $options = [], bool $extra } if ($bg) { - if (false === \array_key_exists($bg, static::$knownColors)) { - throw new \InvalidArgumentException( + if (false === array_key_exists($bg, static::$knownColors)) { + throw new InvalidArgumentException( sprintf('Invalid background color "%1$s" [%2$s]', $bg, - \implode(', ', $this->getKnownColors()) + implode(', ', $this->getKnownColors()) ) ); } @@ -172,11 +182,11 @@ public function __construct($fg = '', $bg = '', array $options = [], bool $extra } foreach ($options as $option) { - if (false === \array_key_exists($option, static::$knownOptions)) { - throw new \InvalidArgumentException( + if (false === array_key_exists($option, static::$knownOptions)) { + throw new InvalidArgumentException( sprintf('Invalid option "%1$s" [%2$s]', $option, - \implode(', ', $this->getKnownOptions()) + implode(', ', $this->getKnownOptions()) ) ); } @@ -212,7 +222,7 @@ public function toStyle(): string $values[] = static::$knownOptions[$option]; } - return \implode(';', $values); + return implode(';', $values); } /** @@ -222,7 +232,7 @@ public function toStyle(): string */ public function getKnownColors(bool $onlyName = true): array { - return $onlyName ? \array_keys(static::$knownColors) : static::$knownColors; + return $onlyName ? array_keys(static::$knownColors) : static::$knownColors; } /** @@ -232,6 +242,6 @@ public function getKnownColors(bool $onlyName = true): array */ public function getKnownOptions(bool $onlyName = true): array { - return $onlyName ? \array_keys(static::$knownOptions) : static::$knownOptions; + return $onlyName ? array_keys(static::$knownOptions) : static::$knownOptions; } } diff --git a/src/Component/Style/Style.php b/src/Component/Style/Style.php index 4017258c..b53c2074 100644 --- a/src/Component/Style/Style.php +++ b/src/Component/Style/Style.php @@ -10,6 +10,16 @@ namespace Inhere\Console\Component\Style; +use function array_key_exists; +use function array_keys; +use function array_merge; +use function array_values; +use InvalidArgumentException; +use function is_array; +use function is_object; +use function sprintf; +use function str_replace; +use function strpos; use Toolkit\Cli\Cli; use Toolkit\Cli\ColorTag; @@ -98,15 +108,15 @@ public function __construct($fg = '', $bg = '', array $options = []) * @param string $method * @param array $args * @return mixed|string - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function __call($method, array $args) { if (isset($args[0]) && $this->hasStyle($method)) { - return $this->format(\sprintf('<%s>%s', $method, $args[0], $method)); + return $this->format(sprintf('<%s>%s', $method, $args[0], $method)); } - throw new \InvalidArgumentException("You called method is not exists: $method"); + throw new InvalidArgumentException("You called method is not exists: $method"); } /** @@ -177,7 +187,7 @@ public function render(string $text) */ public function format(string $text) { - if (!$text || false === \strpos($text, ' $m) { $key = $matches[1][$i]; - if (\array_key_exists($key, $this->styles)) { + if (array_key_exists($key, $this->styles)) { $text = $this->replaceColor($text, $key, $matches[2][$i], (string)$this->styles[$key]); /** Custom style format @see Color::makeByString() */ - } elseif (\strpos($key, '=')) { + } elseif (strpos($key, '=')) { $text = $this->replaceColor($text, $key, $matches[2][$i], (string)Color::makeByString($key)); } } @@ -217,7 +227,7 @@ protected function replaceColor($text, $tag, $match, $style): string { $replace = self::$noColor ? $match : sprintf("\033[%sm%s\033[0m", $style, $match); - return \str_replace("<$tag>$match", $replace, $text); + return str_replace("<$tag>$match", $replace, $text); // return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); } @@ -247,11 +257,11 @@ public static function stripColor(string $string) */ public function add(string $name, $fg = '', $bg = '', array $options = [], bool $extra = false): self { - if (\is_array($fg)) { + if (is_array($fg)) { return $this->addByArray($name, $fg); } - if (\is_object($fg) && $fg instanceof Color) { + if (is_object($fg) && $fg instanceof Color) { $this->styles[$name] = $fg; } else { $this->styles[$name] = Color::make($fg, $bg, $options, $extra); @@ -282,8 +292,8 @@ public function addByArray(string $name, array $styleConfig): self 'options' => [] ]; - $config = \array_merge($style, $styleConfig); - [$fg, $bg, $extra, $options] = \array_values($config); + $config = array_merge($style, $styleConfig); + [$fg, $bg, $extra, $options] = array_values($config); $this->styles[$name] = Color::make($fg, $bg, $options, (bool)$extra); @@ -295,7 +305,7 @@ public function addByArray(string $name, array $styleConfig): self */ public function getStyleNames(): array { - return \array_keys($this->styles); + return array_keys($this->styles); } /** @@ -303,7 +313,7 @@ public function getStyleNames(): array */ public function getNames(): array { - return \array_keys($this->styles); + return array_keys($this->styles); } /** diff --git a/src/Component/Symbol/ArtFont.php b/src/Component/Symbol/ArtFont.php index b88e7e68..511d03d6 100644 --- a/src/Component/Symbol/ArtFont.php +++ b/src/Component/Symbol/ArtFont.php @@ -8,9 +8,13 @@ namespace Inhere\Console\Component\Symbol; +use function dirname; +use function file_get_contents; +use function in_array; use Inhere\Console\Console; use Inhere\Console\Util\Helper; use Inhere\Console\Util\Show; +use function is_file; use Toolkit\Cli\ColorTag; /** @@ -74,7 +78,7 @@ public function __construct() */ protected function loadInternalFonts(): void { - $path = \dirname(__DIR__) . '/BuiltIn/Resources/art-fonts/'; + $path = dirname(__DIR__) . '/BuiltIn/Resources/art-fonts/'; $group = self::INTERNAL_GROUP; foreach (self::$internalFonts as $font) { @@ -146,8 +150,8 @@ public function show(string $name, string $group = null, array $opts = []): int } elseif (isset($this->groups[$group])) { $font = $this->groups[$group] . $name . $pfxType . '.txt'; - if (\is_file($font)) { - $txt = \file_get_contents($font); + if (is_file($font)) { + $txt = file_get_contents($font); } } @@ -247,7 +251,7 @@ public function addFontContent(string $name, string $content): self */ public static function isInternalFont($name): bool { - return \in_array((string)$name, self::$internalFonts, true); + return in_array((string)$name, self::$internalFonts, true); } /** diff --git a/src/Component/Symbol/Char.php b/src/Component/Symbol/Char.php index c4474129..6c42fea7 100644 --- a/src/Component/Symbol/Char.php +++ b/src/Component/Symbol/Char.php @@ -8,9 +8,13 @@ namespace Inhere\Console\Component\Symbol; +use ReflectionClass; +use ReflectionException; + /** * Class FontSymbol * - 字体符号 + * * @package Inhere\Console\Component\Symbol */ final class Char @@ -50,12 +54,12 @@ final class Char /** * @return array - * @throws \ReflectionException + * @throws ReflectionException */ public static function getConstants(): array { if (!self::$constants) { - $objClass = new \ReflectionClass(__CLASS__); + $objClass = new ReflectionClass(__CLASS__); self::$constants = $objClass->getConstants(); } diff --git a/src/Component/Symbol/Emoji.php b/src/Component/Symbol/Emoji.php index 88be3900..4daf249e 100644 --- a/src/Component/Symbol/Emoji.php +++ b/src/Component/Symbol/Emoji.php @@ -8,8 +8,12 @@ namespace Inhere\Console\Component\Symbol; +use ReflectionClass; +use ReflectionException; + /** * Class Emoji + * * @package Inhere\Console\Component\Symbol */ final class Emoji @@ -91,12 +95,12 @@ final class Emoji /** * @return array - * @throws \ReflectionException + * @throws ReflectionException */ public static function getConstants(): array { if (!self::$constants) { - $objClass = new \ReflectionClass(__CLASS__); + $objClass = new ReflectionClass(__CLASS__); // 此处获取类中定义的全部常量 返回的是 [key=>value,...] 的数组 // key是常量名 value是常量值 diff --git a/src/Console.php b/src/Console.php index 28c2e539..38cc29a6 100644 --- a/src/Console.php +++ b/src/Console.php @@ -6,6 +6,26 @@ use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; use Toolkit\Cli\ColorTag; +use function array_merge; +use function date; +use function fflush; +use function fgets; +use function fgetss; +use function file_get_contents; +use function fwrite; +use function implode; +use function is_array; +use function is_numeric; +use function json_encode; +use function sprintf; +use function strpos; +use function trim; +use const JSON_PRETTY_PRINT; +use const JSON_UNESCAPED_SLASHES; +use const PHP_EOL; +use const STDERR; +use const STDIN; +use const STDOUT; /** * Class Console @@ -101,7 +121,7 @@ public static function style(): Style */ public static function writef(string $format, ...$args): int { - return self::write(\sprintf($format, ...$args)); + return self::write(sprintf($format, ...$args)); } /** @@ -112,7 +132,7 @@ public static function writef(string $format, ...$args): int */ public static function printf(string $format, ...$args): int { - return self::write(\sprintf($format, ...$args)); + return self::write(sprintf($format, ...$args)); } /** @@ -158,8 +178,8 @@ public static function writeln($message, $quit = false, array $opts = []): int */ public static function write($messages, $nl = true, $quit = false, array $opts = []): int { - if (\is_array($messages)) { - $messages = \implode($nl ? \PHP_EOL : '', $messages); + if (is_array($messages)) { + $messages = implode($nl ? PHP_EOL : '', $messages); } $messages = (string)$messages; @@ -172,7 +192,7 @@ public static function write($messages, $nl = true, $quit = false, array $opts = // if open buffering if (self::isBuffering()) { - self::$buffer .= $messages . ($nl ? \PHP_EOL : ''); + self::$buffer .= $messages . ($nl ? PHP_EOL : ''); if (!$quit) { return 0; @@ -182,13 +202,13 @@ public static function write($messages, $nl = true, $quit = false, array $opts = // clear buffer self::$buffer = ''; } else { - $messages .= $nl ? \PHP_EOL : ''; + $messages .= $nl ? PHP_EOL : ''; } - \fwrite($stream = $opts['stream'] ?? \STDOUT, $messages); + fwrite($stream = $opts['stream'] ?? STDOUT, $messages); if (!isset($opts['flush']) || $opts['flush']) { - \fflush($stream); + fflush($stream); } // if will quit. @@ -220,7 +240,7 @@ public static function stdout($text, $nl = true, $quit = false): void public static function stderr($text, $nl = true, $quit = -200): void { self::write($text, $nl, $quit, [ - 'stream' => \STDERR, + 'stream' => STDERR, ]); } @@ -235,7 +255,7 @@ public static function logf(int $level, string $format, ...$args): void $colorName = self::LEVEL2TAG[$level] ?? 'info'; $taggedName = ColorTag::add($levelName, $colorName); - $message = \strpos($format, '%') > 0 ? \sprintf($format, ...$args) : $format; + $message = strpos($format, '%') > 0 ? sprintf($format, ...$args) : $format; self::writef('[%s] %s', $taggedName, $message); } @@ -261,22 +281,22 @@ public static function log(string $msg, array $data = [], int $level = self::VER $userOpts = []; foreach ($opts as $n => $v) { - if (\is_numeric($n) || \strpos($n, '_') === 0) { + if (is_numeric($n) || strpos($n, '_') === 0) { $userOpts[] = "[$v]"; } else { $userOpts[] = "[$n:$v]"; } } - $optString = $userOpts ? ' ' . \implode(' ', $userOpts) : ''; + $optString = $userOpts ? ' ' . implode(' ', $userOpts) : ''; - self::write(\sprintf( + self::write(sprintf( '%s [%s]%s %s %s', - \date('Y/m/d H:i:s'), + date('Y/m/d H:i:s'), $taggedName, $optString, - \trim($msg), - $data ? \PHP_EOL . \json_encode($data, \JSON_UNESCAPED_SLASHES | \JSON_PRETTY_PRINT) : '' + trim($msg), + $data ? PHP_EOL . json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) : '' )); } @@ -298,12 +318,12 @@ public static function read($message = null, bool $nl = false, array $opts = []) self::write($message, $nl); } - $opts = \array_merge([ + $opts = array_merge([ 'length' => 1024, - 'stream' => \STDIN, + 'stream' => STDIN, ], $opts); - return \file_get_contents($opts['stream'], $opts['length']); + return file_get_contents($opts['stream'], $opts['length']); } /** @@ -323,12 +343,12 @@ public static function readln($message = null, $nl = false, array $opts = []): s self::write($message, $nl); } - $opts = \array_merge([ + $opts = array_merge([ 'length' => 1024, - 'stream' => \STDIN, + 'stream' => STDIN, ], $opts); - return \trim(\fgets($opts['stream'], $opts['length'])); + return trim(fgets($opts['stream'], $opts['length'])); } /** @@ -356,13 +376,13 @@ public static function readSafe($message = null, bool $nl = false, array $opts = self::write($message, $nl); } - $opts = \array_merge([ + $opts = array_merge([ 'length' => 1024, - 'stream' => \STDIN, + 'stream' => STDIN, 'allowTags' => null, ], $opts); - return \trim(\fgetss($opts['stream'], $opts['length'], $opts['allowTags'])); + return trim(fgetss($opts['stream'], $opts['length'], $opts['allowTags'])); } /** diff --git a/src/Contract/ApplicationInterface.php b/src/Contract/ApplicationInterface.php index 295e99ee..d705159c 100644 --- a/src/Contract/ApplicationInterface.php +++ b/src/Contract/ApplicationInterface.php @@ -8,8 +8,12 @@ namespace Inhere\Console\Contract; +use Closure; +use InvalidArgumentException; + /** * Interface ApplicationInterface + * * @package Inhere\Console\Contract */ interface ApplicationInterface @@ -51,21 +55,23 @@ public function stop(int $code = 0); * - aliases The command aliases * - description The description message * @return static - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function controller(string $name, $class = null, $option = null); /** * Register a app independent console command - * @param string|CommandInterface $name - * @param string|\Closure|CommandInterface $handler - * @param null|array|string $option + * + * @param string|CommandInterface $name + * @param string|Closure|CommandInterface $handler + * @param null|array|string $option * string: define the description message. * array: * - aliases The command aliases * - description The description message + * * @return mixed - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function command(string $name, $handler = null, $option = null); diff --git a/src/Contract/ErrorHandlerInterface.php b/src/Contract/ErrorHandlerInterface.php index 04e0100d..f186cdf8 100644 --- a/src/Contract/ErrorHandlerInterface.php +++ b/src/Contract/ErrorHandlerInterface.php @@ -10,6 +10,7 @@ use Inhere\Console\AbstractApplication; use Inhere\Console\Application; +use Throwable; /** * Interface ErrorHandlerInterface @@ -18,8 +19,8 @@ interface ErrorHandlerInterface { /** - * @param \Throwable $e + * @param Throwable $e * @param Application|AbstractApplication $app */ - public function handle(\Throwable $e, AbstractApplication $app): void; + public function handle(Throwable $e, AbstractApplication $app): void; } diff --git a/src/Contract/RouterInterface.php b/src/Contract/RouterInterface.php index 4548a66c..33aae5b4 100644 --- a/src/Contract/RouterInterface.php +++ b/src/Contract/RouterInterface.php @@ -8,8 +8,12 @@ namespace Inhere\Console\Contract; +use Closure; +use InvalidArgumentException; + /** * Interface RouterInterface + * * @package Inhere\Console\Contract */ interface RouterInterface @@ -29,20 +33,22 @@ interface RouterInterface * - aliases The command aliases * - description The description message * @return static - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function addGroup(string $name, $class = null, array $options = []): self; /** * Register a app independent console command - * @param string|CommandInterface $name - * @param string|\Closure|CommandInterface $handler - * @param array $options + * + * @param string|CommandInterface $name + * @param string|Closure|CommandInterface $handler + * @param array $options * array: * - aliases The command aliases * - description The description message + * * @return static - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function addCommand(string $name, $handler = null, array $options = []): self; diff --git a/src/Controller.php b/src/Controller.php index f77708ee..95dfde55 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -8,14 +8,32 @@ namespace Inhere\Console; +use Generator; use Inhere\Console\Contract\ControllerInterface; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; use Inhere\Console\Util\FormatUtil; use Inhere\Console\Util\Helper; +use ReflectionClass; +use ReflectionException; +use ReflectionMethod; +use ReflectionObject; use Toolkit\Cli\ColorTag; use Toolkit\PhpUtil\PhpDoc; use Toolkit\StrUtil\Str; +use function array_flip; +use function array_keys; +use function array_merge; +use function implode; +use function ksort; +use function lcfirst; +use function method_exists; +use function sprintf; +use function strpos; +use function substr; +use function trim; +use function ucfirst; +use const PHP_EOL; /** * Class Controller @@ -71,7 +89,7 @@ protected function init(): void { $list = $this->disabledCommands(); // save to property - $this->disabledCommands = $list ? \array_flip($list) : []; + $this->disabledCommands = $list ? array_flip($list) : []; self::$commandAliases = static::commandAliases(); $this->groupOptions = $this->groupOptions(); @@ -107,11 +125,11 @@ protected function disabledCommands(): array /** * @param string $command * @return int|mixed - * @throws \ReflectionException + * @throws ReflectionException */ public function run(string $command = '') { - if (!$command = \trim($command, $this->delimiter)) { + if (!$command = trim($command, $this->delimiter)) { $command = $this->defaultAction; } @@ -132,7 +150,7 @@ protected function configure(): void // eg. IndexConfigure() for indexCommand() $method = $this->action . 'Configure'; - if (\method_exists($this, $method)) { + if (method_exists($this, $method)) { $this->$method(); } } @@ -143,7 +161,7 @@ protected function configure(): void * @param Input $input * @param Output $output * @return mixed - * @throws \ReflectionException + * @throws ReflectionException */ final public function execute($input, $output) { @@ -151,16 +169,16 @@ final public function execute($input, $output) $group = static::getName(); if ($this->isDisabled($action)) { - $output->liteError(\sprintf("Sorry, The command '%s' is invalid in the group '%s'!", $action, $group)); + $output->liteError(sprintf("Sorry, The command '%s' is invalid in the group '%s'!", $action, $group)); return -1; } - $method = $this->actionSuffix ? $action . \ucfirst($this->actionSuffix) : $action; + $method = $this->actionSuffix ? $action . ucfirst($this->actionSuffix) : $action; // the action method exists and only allow access public method. - if (\method_exists($this, $method) && (($rfm = new \ReflectionMethod($this, $method)) && $rfm->isPublic())) { + if (method_exists($this, $method) && (($rfm = new ReflectionMethod($this, $method)) && $rfm->isPublic())) { // before - if (\method_exists($this, $before = 'before' . \ucfirst($action))) { + if (method_exists($this, $before = 'before' . ucfirst($action))) { $this->$before($input, $output); } @@ -168,7 +186,7 @@ final public function execute($input, $output) $result = $this->$method($input, $output); // after - if (\method_exists($this, $after = 'after' . \ucfirst($action))) { + if (method_exists($this, $after = 'after' . ucfirst($action))) { $this->$after($input, $output); } @@ -176,7 +194,7 @@ final public function execute($input, $output) } // if you defined the method '$this->notFoundCallback' , will call it - if (($notFoundCallback = $this->notFoundCallback) && \method_exists($this, $notFoundCallback)) { + if (($notFoundCallback = $this->notFoundCallback) && method_exists($this, $notFoundCallback)) { $result = $this->{$notFoundCallback}($action); } else { $result = -1; @@ -186,7 +204,7 @@ final public function execute($input, $output) $similar = Helper::findSimilar($action, $this->getAllCommandMethods(null, true)); if ($similar) { - $output->write(sprintf("\nMaybe what you mean is:\n %s", \implode(', ', $similar))); + $output->write(sprintf("\nMaybe what you mean is:\n %s", implode(', ', $similar))); } else { $this->showCommandList(); } @@ -197,7 +215,7 @@ final public function execute($input, $output) /** * @return bool - * @throws \ReflectionException + * @throws ReflectionException */ protected function showHelp(): bool { @@ -236,7 +254,7 @@ public function getGroupOptions(): array * {script} {name} index * * @return int - * @throws \ReflectionException + * @throws ReflectionException */ final public function helpCommand(): int { @@ -249,7 +267,7 @@ final public function helpCommand(): int } $action = Str::camelCase($action); - $method = $this->actionSuffix ? $action . \ucfirst($this->actionSuffix) : $action; + $method = $this->actionSuffix ? $action . ucfirst($this->actionSuffix) : $action; $aliases = $this->getCommandAliases($action); // For a specified sub-command. @@ -263,14 +281,14 @@ protected function beforeShowCommandList() /** * Display all sub-commands list of the controller class - * @throws \ReflectionException + * @throws ReflectionException */ final public function showCommandList(): void { $this->beforeShowCommandList(); - $ref = new \ReflectionClass($this); - $sName = \lcfirst(self::getName() ?: $ref->getShortName()); + $ref = new ReflectionClass($this); + $sName = lcfirst(self::getName() ?: $ref->getShortName()); if (!($classDes = self::getDescription())) { $classDes = PhpDoc::description($ref->getDocComment()) ?: 'No description for the command group'; @@ -288,7 +306,7 @@ final public function showCommandList(): void $desc = PhpDoc::firstLine($m->getDocComment()) ?: $defaultDes; // is a annotation tag - if (\strpos($desc, '@') === 0) { + if (strpos($desc, '@') === 0) { $desc = $defaultDes; } @@ -301,13 +319,13 @@ final public function showCommandList(): void } $aliases = $this->getCommandAliases($cmd); - $desc .= $aliases ? ColorTag::wrap(' [alias: ' . \implode(',', $aliases) . ']', 'info') : ''; + $desc .= $aliases ? ColorTag::wrap(' [alias: ' . implode(',', $aliases) . ']', 'info') : ''; $commands[$cmd] = $desc; } // sort commands - \ksort($commands); + ksort($commands); // move 'help' to last. if ($helpCmd = $commands['help'] ?? null) { @@ -325,10 +343,10 @@ final public function showCommandList(): void $usage = "$script {$name}{command} [--options ...] [arguments ...]"; } - $globalOptions = \array_merge(Application::getGlobalOptions(), static::$globalOptions); + $globalOptions = array_merge(Application::getGlobalOptions(), static::$globalOptions); $this->output->startBuffer(); - $this->output->write(\ucfirst($classDes) . \PHP_EOL); + $this->output->write(ucfirst($classDes) . PHP_EOL); $this->output->mList([ 'Usage:' => $usage, //'Group Name:' => "$sName", @@ -338,7 +356,7 @@ final public function showCommandList(): void 'sepChar' => ' ', ]); - $this->write(\sprintf( + $this->write(sprintf( 'More information about a command, please use: %s %s{command} -h', $script, $this->executionAlone ? '' : $name @@ -347,13 +365,13 @@ final public function showCommandList(): void } /** - * @param \ReflectionClass|null $ref + * @param ReflectionClass|null $ref * @param bool $onlyName - * @return \Generator + * @return Generator */ - protected function getAllCommandMethods(\ReflectionClass $ref = null, bool $onlyName = false): ?\Generator + protected function getAllCommandMethods(ReflectionClass $ref = null, bool $onlyName = false): ?Generator { - $ref = $ref ?: new \ReflectionObject($this); + $ref = $ref ?: new ReflectionObject($this); $suffix = $this->actionSuffix; $suffixLen = Str::len($suffix); @@ -361,9 +379,9 @@ protected function getAllCommandMethods(\ReflectionClass $ref = null, bool $only foreach ($ref->getMethods() as $m) { $mName = $m->getName(); - if ($m->isPublic() && \substr($mName, -$suffixLen) === $suffix) { + if ($m->isPublic() && substr($mName, -$suffixLen) === $suffix) { // suffix is empty ? - $cmd = $suffix ? \substr($mName, 0, -$suffixLen) : $mName; + $cmd = $suffix ? substr($mName, 0, -$suffixLen) : $mName; if ($onlyName) { yield $cmd; @@ -417,7 +435,7 @@ public function getDisabledCommands(): array public function getCommandAliases(string $name = ''): array { if ($name) { - return self::$commandAliases ? \array_keys(self::$commandAliases, $name, true) : []; + return self::$commandAliases ? array_keys(self::$commandAliases, $name, true) : []; } return self::$commandAliases; @@ -457,7 +475,7 @@ public function getDefaultAction(): string */ public function setDefaultAction(string $defaultAction) { - $this->defaultAction = \trim($defaultAction, $this->delimiter); + $this->defaultAction = trim($defaultAction, $this->delimiter); } /** diff --git a/src/Exception/ConsoleException.php b/src/Exception/ConsoleException.php index fcf088ee..2ffc54e7 100644 --- a/src/Exception/ConsoleException.php +++ b/src/Exception/ConsoleException.php @@ -8,11 +8,14 @@ namespace Inhere\Console\Exception; +use RuntimeException; + /** * Class ConsoleException + * * @package Inhere\Console\Exception */ -class ConsoleException extends \RuntimeException +class ConsoleException extends RuntimeException { } diff --git a/src/Exception/PromptException.php b/src/Exception/PromptException.php index d2aa8865..c64c0690 100644 --- a/src/Exception/PromptException.php +++ b/src/Exception/PromptException.php @@ -8,11 +8,14 @@ namespace Inhere\Console\Exception; +use RuntimeException; + /** * Class PromptException + * * @package Inhere\Console\Exception */ -class PromptException extends \RuntimeException +class PromptException extends RuntimeException { } diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index b708a85d..2537b9ee 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -8,8 +8,16 @@ namespace Inhere\Console\IO; +use function array_merge; +use function getcwd; +use InvalidArgumentException; +use function is_bool; +use function is_int; +use function trim; + /** * Class AbstractInput + * * @package Inhere\Console\IO */ abstract class AbstractInput implements InputInterface @@ -89,8 +97,8 @@ protected function findCommand(): void foreach ($this->args as $key => $value) { if ($key === 0) { - $this->command = \trim($value); - } elseif (\is_int($key)) { + $this->command = trim($value); + } elseif (is_int($key)) { $newArgs[] = $value; } else { $newArgs[$key] = $value; @@ -175,7 +183,7 @@ public function get($name, $default = null) * get a required argument * @param int|string $name argument index * @return mixed - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function getRequiredArg($name) { @@ -183,7 +191,7 @@ public function getRequiredArg($name) return $this->args[$name]; } - throw new \InvalidArgumentException("The argument '{$name}' is required"); + throw new InvalidArgumentException("The argument '{$name}' is required"); } /** @@ -295,12 +303,12 @@ public function getOption(string $name, $default = null) * get a required argument * @param string $name * @return mixed - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function getRequiredOpt(string $name) { if (null === ($val = $this->getOpt($name))) { - throw new \InvalidArgumentException("The option '{$name}' is required"); + throw new InvalidArgumentException("The option '{$name}' is required"); } return $val; @@ -366,7 +374,7 @@ public function sameOpt(array $names, $default = null) */ public function getOpts(): array { - return \array_merge($this->sOpts, $this->lOpts); + return array_merge($this->sOpts, $this->lOpts); } /** @@ -423,7 +431,7 @@ public function sBoolOpt(string $name, $default = false): bool { $val = $this->sOpt($name); - return \is_bool($val) ? $val : (bool)$default; + return is_bool($val) ? $val : (bool)$default; } /** @@ -457,7 +465,7 @@ public function getSOpts(): array */ public function setSOpts(array $sOpts, bool $replace = false): void { - $this->sOpts = $replace ? $sOpts : \array_merge($this->sOpts, $sOpts); + $this->sOpts = $replace ? $sOpts : array_merge($this->sOpts, $sOpts); } /** @@ -511,7 +519,7 @@ public function lBoolOpt(string $name, $default = false): bool { $val = $this->lOpt($name); - return \is_bool($val) ? $val : (bool)$default; + return is_bool($val) ? $val : (bool)$default; } /** @@ -545,7 +553,7 @@ public function getLOpts(): array */ public function setLOpts(array $lOpts, bool $replace = false): void { - $this->lOpts = $replace ? $lOpts : \array_merge($this->lOpts, $lOpts); + $this->lOpts = $replace ? $lOpts : array_merge($this->lOpts, $lOpts); } /** @@ -566,7 +574,7 @@ public function clearLOpts(): void public function getPwd(): string { if (!$this->pwd) { - $this->pwd = \getcwd(); + $this->pwd = getcwd(); } return $this->pwd; diff --git a/src/IO/Input.php b/src/IO/Input.php index 4fa20ad3..778463d1 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -8,7 +8,16 @@ namespace Inhere\Console\IO; +use function array_map; +use function array_shift; +use function fgets; +use function fwrite; +use function implode; +use function preg_match; +use const STDIN; +use const STDOUT; use Toolkit\Cli\Flags; +use function trim; /** * Class Input - The input information. by parse global var $argv. @@ -19,7 +28,7 @@ class Input extends AbstractInput /** * @var resource */ - protected $inputStream = \STDIN; + protected $inputStream = STDIN; /** * Input constructor. @@ -34,8 +43,8 @@ public function __construct(array $args = null, bool $parsing = true) $this->pwd = $this->getPwd(); $this->tokens = $args; - $this->script = \array_shift($args); - $this->fullScript = \implode(' ', $args); + $this->script = array_shift($args); + $this->fullScript = implode(' ', $args); if ($parsing) { // list($this->args, $this->sOpts, $this->lOpts) = InputParser::fromArgv($args); @@ -51,8 +60,8 @@ public function __construct(array $args = null, bool $parsing = true) */ public function toString(): string { - $tokens = \array_map(function ($token) { - if (\preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { + $tokens = array_map(function ($token) { + if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { return $match[1] . Flags::escapeToken($match[2]); } @@ -63,7 +72,7 @@ public function toString(): string return $token; }, $this->tokens); - return \implode(' ', $tokens); + return implode(' ', $tokens); } /** @@ -75,10 +84,10 @@ public function toString(): string public function read(string $question = '', bool $nl = false): string { if ($question) { - \fwrite(\STDOUT, $question . ($nl ? "\n" : '')); + fwrite(STDOUT, $question . ($nl ? "\n" : '')); } - return \trim(\fgets($this->inputStream)); + return trim(fgets($this->inputStream)); } /*********************************************************************************** diff --git a/src/IO/Input/ArrayInput.php b/src/IO/Input/ArrayInput.php index 882c9518..03ce1b95 100644 --- a/src/IO/Input/ArrayInput.php +++ b/src/IO/Input/ArrayInput.php @@ -8,6 +8,8 @@ namespace Inhere\Console\IO\Input; +use function array_shift; +use function implode; use Inhere\Console\IO\Input; use Toolkit\Cli\Flags; @@ -27,8 +29,8 @@ public function __construct(array $args = null, bool $parsing = true) parent::__construct([], false); $this->tokens = $args; - $this->script = \array_shift($args); - $this->fullScript = \implode(' ', $args); + $this->script = array_shift($args); + $this->fullScript = implode(' ', $args); if ($parsing && $args) { [$this->args, $this->sOpts, $this->lOpts] = Flags::parseArray($args); diff --git a/src/IO/InputDefinition.php b/src/IO/InputDefinition.php index 32d863bb..bb6137ef 100644 --- a/src/IO/InputDefinition.php +++ b/src/IO/InputDefinition.php @@ -8,8 +8,24 @@ namespace Inhere\Console\IO; +use function array_filter; +use function array_merge; +use function array_values; +use function count; +use function implode; +use InvalidArgumentException; +use function is_array; +use function is_int; +use LogicException; +use function preg_split; +use function sprintf; +use function strpos; +use function strtoupper; +use function trim; + /** * Class InputDefinition + * * @package Inhere\Console\IO * @refer \Symfony\Component\Console\Input\InputDefinition */ @@ -55,8 +71,8 @@ public static function make(array $arguments = [], array $options = []): InputDe * * @param array $arguments * @param array $options - * @throws \LogicException - * @throws \InvalidArgumentException + * @throws LogicException + * @throws InvalidArgumentException */ public function __construct(array $arguments = [], array $options = []) { @@ -71,7 +87,7 @@ public function __construct(array $arguments = [], array $options = []) /** * @param array $arguments * @return InputDefinition - * @throws \LogicException + * @throws LogicException */ public function setArguments(array $arguments): InputDefinition { @@ -83,7 +99,7 @@ public function setArguments(array $arguments): InputDefinition /** * @param array $arguments * @return $this - * @throws \LogicException + * @throws LogicException */ public function addArguments(array $arguments): self { @@ -116,45 +132,45 @@ public function addArg(string $name, int $mode = null, string $description = '', * @param string $description A description text * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) * @return $this - * @throws \LogicException + * @throws LogicException */ public function addArgument(string $name, int $mode = null, string $description = '', $default = null): self { if (null === $mode) { $mode = Input::ARG_OPTIONAL; - } elseif (!\is_int($mode) || $mode > 7 || $mode < 1) { - throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); + } elseif (!is_int($mode) || $mode > 7 || $mode < 1) { + throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); } if (isset($this->arguments[$name])) { - throw new \LogicException(sprintf('An argument with name "%s" already exists.', $name)); + throw new LogicException(sprintf('An argument with name "%s" already exists.', $name)); } if ($this->hasAnArrayArgument) { - throw new \LogicException('Cannot add an argument after an array argument.'); + throw new LogicException('Cannot add an argument after an array argument.'); } if (($required = $mode === Input::ARG_REQUIRED) && $this->hasOptional) { - throw new \LogicException('Cannot add a required argument after an optional one.'); + throw new LogicException('Cannot add a required argument after an optional one.'); } if ($isArray = ($mode === Input::ARG_IS_ARRAY)) { if (!$this->argumentIsAcceptValue($mode)) { - throw new \InvalidArgumentException('Impossible to have an option mode ARG_IS_ARRAY if the option does not accept a value.'); + throw new InvalidArgumentException('Impossible to have an option mode ARG_IS_ARRAY if the option does not accept a value.'); } $this->hasAnArrayArgument = true; if (null === $default) { $default = []; - } elseif (!\is_array($default)) { - throw new \LogicException('A default value for an array argument must be an array.'); + } elseif (!is_array($default)) { + throw new LogicException('A default value for an array argument must be an array.'); } } if ($required) { if (null !== $default) { - throw new \LogicException('Cannot set a default value except for OPTIONAL-ARGUMENT mode.'); + throw new LogicException('Cannot set a default value except for OPTIONAL-ARGUMENT mode.'); } ++$this->requiredCount; @@ -180,7 +196,7 @@ public function addArgument(string $name, int $mode = null, string $description */ public function getArgument($name, $default = null) { - $arguments = \is_int($name) ? \array_values($this->arguments) : $this->arguments; + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; if (!isset($arguments[$name])) { return $default; @@ -196,7 +212,7 @@ public function getArgument($name, $default = null) */ public function hasArgument($name): bool { - $arguments = \is_int($name) ? \array_values($this->arguments) : $this->arguments; + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; return isset($arguments[$name]); } @@ -215,7 +231,7 @@ public function getArguments(): array */ public function getArgumentCount(): int { - return \count($this->arguments); + return count($this->arguments); } /** @@ -235,8 +251,8 @@ public function getArgumentRequiredCount(): int * Sets the options * * @param array[] $options An array of InputOption objects - * @throws \LogicException - * @throws \InvalidArgumentException + * @throws LogicException + * @throws InvalidArgumentException */ public function setOptions(array $options = []): void { @@ -248,8 +264,8 @@ public function setOptions(array $options = []): void * Adds an array of option * * @param array - * @throws \LogicException - * @throws \InvalidArgumentException + * @throws LogicException + * @throws InvalidArgumentException */ public function addOptions(array $options = []): void { @@ -284,8 +300,8 @@ public function addOpt( * @param mixed $default The default value (must be null for InputOption::OPT_BOOL) * * @return $this - * @throws \InvalidArgumentException - * @throws \LogicException + * @throws InvalidArgumentException + * @throws LogicException */ public function addOption( string $name, @@ -294,12 +310,12 @@ public function addOption( string $description = '', $default = null ): self { - if (0 === \strpos($name, '-')) { - $name = \trim($name, '-'); + if (0 === strpos($name, '-')) { + $name = trim($name, '-'); } if (empty($name)) { - throw new \InvalidArgumentException('An option name cannot be empty.'); + throw new InvalidArgumentException('An option name cannot be empty.'); } if (empty($shortcut)) { @@ -308,47 +324,47 @@ public function addOption( if (null === $mode) { $mode = Input::OPT_BOOLEAN; - } elseif (!\is_int($mode) || $mode > 15 || $mode < 1) { - throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); + } elseif (!is_int($mode) || $mode > 15 || $mode < 1) { + throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); } $isArray = $mode === Input::OPT_IS_ARRAY; if ($isArray && !$this->optionIsAcceptValue($mode)) { - throw new \InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); + throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); } if (isset($this->options[$name])) { - throw new \LogicException(sprintf('An option named "%s" already exists.', $name)); + throw new LogicException(sprintf('An option named "%s" already exists.', $name)); } // set default value if (Input::OPT_BOOLEAN === (Input::OPT_BOOLEAN & $mode) && null !== $default) { - throw new \LogicException('Cannot set a default value when using OPT_BOOLEAN mode.'); + throw new LogicException('Cannot set a default value when using OPT_BOOLEAN mode.'); } if ($isArray) { if (null === $default) { $default = []; - } elseif (!\is_array($default)) { - throw new \LogicException('A default value for an array option must be an array.'); + } elseif (!is_array($default)) { + throw new LogicException('A default value for an array option must be an array.'); } } $default = $this->optionIsAcceptValue($mode) ? $default : false; if ($shortcut) { - if (\is_array($shortcut)) { - $shortcut = \implode('|', $shortcut); + if (is_array($shortcut)) { + $shortcut = implode('|', $shortcut); } - $shortcuts = \preg_split('{(\|)-?}', ltrim($shortcut, '-')); - $shortcuts = \array_filter($shortcuts); - $shortcut = \implode('|', $shortcuts); + $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); + $shortcuts = array_filter($shortcuts); + $shortcut = implode('|', $shortcuts); foreach ($shortcuts as $srt) { if (isset($this->shortcuts[$srt])) { - throw new \LogicException(sprintf('An option with shortcut "%s" already exists.', $srt)); + throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $srt)); } $this->shortcuts[$srt] = $name; @@ -370,12 +386,12 @@ public function addOption( /** * @param string $name * @return array - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function getOption(string $name): array { if (!$this->hasOption($name)) { - throw new \InvalidArgumentException(\sprintf('The "--%s" option does not exist.', $name)); + throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); } return $this->options[$name]; @@ -413,7 +429,7 @@ public function hasShortcut(string $name): bool * Gets an option info array * @param string $shortcut the Shortcut name * @return array - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function getOptionByShortcut(string $shortcut): array { @@ -423,12 +439,12 @@ public function getOptionByShortcut(string $shortcut): array /** * @param string $shortcut * @return mixed - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ private function shortcutToName(string $shortcut) { if (!isset($this->shortcuts[$shortcut])) { - throw new \InvalidArgumentException(\sprintf('The "-%s" option does not exist.', $shortcut)); + throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); } return $this->shortcuts[$shortcut]; @@ -440,7 +456,7 @@ private function shortcutToName(string $shortcut) */ private function mergeArgOptConfig(array $map): array { - return $map ? \array_merge(self::$defaultArgOptConfig, $map) : self::$defaultArgOptConfig; + return $map ? array_merge(self::$defaultArgOptConfig, $map) : self::$defaultArgOptConfig; } /** @@ -459,23 +475,23 @@ public function getSynopsis(bool $short = false): array $value = ''; if ($this->optionIsAcceptValue($option['mode'])) { - $value = \sprintf( + $value = sprintf( ' %s%s%s', $option['optional'] ? '[' : '', - \strtoupper($name), + strtoupper($name), $option['optional'] ? ']' : '' ); } - $shortcut = $option['shortcut'] ? \sprintf('-%s, ', $option['shortcut']) : ' '; - $elements[] = \sprintf('[%s--%s%s]', $shortcut, $name, $value); + $shortcut = $option['shortcut'] ? sprintf('-%s, ', $option['shortcut']) : ' '; + $elements[] = sprintf('[%s--%s%s]', $shortcut, $name, $value); $key = "{$shortcut}--{$name}"; $opts[$key] = ($option['required'] ? '*' : '') . $option['description']; } } - if ($this->arguments && \count($elements)) { + if ($this->arguments && count($elements)) { $elements[] = '[--]'; } @@ -499,7 +515,7 @@ public function getSynopsis(bool $short = false): array return [ $this->description, - 'usage:' => \implode(' ', $elements), + 'usage:' => implode(' ', $elements), 'options:' => $opts, 'arguments:' => $args, 'example:' => $this->example, diff --git a/src/IO/Output.php b/src/IO/Output.php index 94b78941..fbfe8a35 100644 --- a/src/IO/Output.php +++ b/src/IO/Output.php @@ -12,6 +12,8 @@ use Inhere\Console\Console; use Inhere\Console\Traits\FormatOutputAwareTrait; use Inhere\Console\Util\Show; +use const STDERR; +use const STDOUT; use Toolkit\Cli\Cli; /** @@ -27,14 +29,14 @@ class Output implements OutputInterface * * @var resource */ - protected $outputStream = \STDOUT; + protected $outputStream = STDOUT; /** * Error output stream * * @var resource */ - protected $errorStream = \STDERR; + protected $errorStream = STDERR; /** * 控制台窗口(字体/背景)颜色添加处理 diff --git a/src/IO/StrictInput.php b/src/IO/StrictInput.php index 78dc3630..60be62b2 100644 --- a/src/IO/StrictInput.php +++ b/src/IO/StrictInput.php @@ -8,12 +8,16 @@ namespace Inhere\Console\IO; +use function array_shift; +use function strpos; + /** * Class StrictInput * - 严格按照定义解析 * - 初始化时不全部解析,只取出 '-h' '--help' 还有命令名 * - 到运行命令时根据命令的参数选项配置(InputDefinition)来进行解析 * - @todo un-completed + * * @package Inhere\Console\IO */ class StrictInput extends Input @@ -51,7 +55,7 @@ public function __construct(array $args = null) $copy = $args; // command name - if (!empty($copy[1]) && $copy[1][0] !== '-' && false === \strpos($copy[1], '=')) { + if (!empty($copy[1]) && $copy[1][0] !== '-' && false === strpos($copy[1], '=')) { $this->setCommand($copy[1]); // unset command @@ -59,7 +63,7 @@ public function __construct(array $args = null) } // pop script name - \array_shift($copy); + array_shift($copy); $this->cleanedTokens = $copy; $this->collectPreParsed($copy); @@ -79,7 +83,7 @@ private function collectPreParsed(array $tokens): void public function parseTokens(array $allowArray = [], array $noValues = []): void { $params = $this->getTokens(); - \array_shift($params); // pop script name + array_shift($params); // pop script name } /** diff --git a/src/Router.php b/src/Router.php index 001e79c3..c30a0a34 100644 --- a/src/Router.php +++ b/src/Router.php @@ -2,11 +2,30 @@ namespace Inhere\Console; +use Closure; use Inhere\Console\Contract\CommandInterface; use Inhere\Console\Contract\ControllerInterface; use Inhere\Console\Contract\RouterInterface; use Inhere\Console\Traits\NameAliasTrait; use Inhere\Console\Util\Helper; +use InvalidArgumentException; +use function array_filter; +use function array_keys; +use function array_merge; +use function array_splice; +use function array_values; +use function class_exists; +use function count; +use function explode; +use function in_array; +use function is_int; +use function is_object; +use function is_string; +use function is_subclass_of; +use function method_exists; +use function preg_match; +use function strpos; +use function trim; /** * Class Router - match input command find command handler @@ -64,14 +83,14 @@ class Router implements RouterInterface * - aliases The command aliases * - description The description message * @return Router - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function addGroup(string $name, $class = null, array $options = []): RouterInterface { /** * @var Controller $class name is an controller class */ - if (!$class && \class_exists($name)) { + if (!$class && class_exists($name)) { $class = $name; $name = $class::getName(); } @@ -86,11 +105,11 @@ public function addGroup(string $name, $class = null, array $options = []): Rout $this->validateName($name); - if (\is_string($class) && !\class_exists($class)) { + if (is_string($class) && !class_exists($class)) { Helper::throwInvalidArgument("The console controller class [$class] not exists!"); } - if (!\is_subclass_of($class, Controller::class)) { + if (!is_subclass_of($class, Controller::class)) { Helper::throwInvalidArgument('The console controller class must is subclass of the: ' . Controller::class); } @@ -103,7 +122,7 @@ public function addGroup(string $name, $class = null, array $options = []): Rout // allow define aliases in group class by Controller::aliases() if ($aliases = $class::aliases()) { - $options['aliases'] = \array_merge($options['aliases'], $aliases); + $options['aliases'] = array_merge($options['aliases'], $aliases); } $this->controllers[$name] = [ @@ -122,21 +141,23 @@ public function addGroup(string $name, $class = null, array $options = []): Rout /** * Register a app independent console command - * @param string|CommandInterface $name - * @param string|\Closure|CommandInterface $handler - * @param array $options + * + * @param string|CommandInterface $name + * @param string|Closure|CommandInterface $handler + * @param array $options * array: * - aliases The command aliases * - description The description message + * * @return Router|RouterInterface - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function addCommand(string $name, $handler = null, array $options = []): RouterInterface { /** * @var Command $name name is an command class */ - if (!$handler && \class_exists($name)) { + if (!$handler && class_exists($name)) { $handler = $name; $name = $name::getName(); } @@ -153,12 +174,12 @@ public function addCommand(string $name, $handler = null, array $options = []): $options['aliases'] = isset($options['aliases']) ? (array)$options['aliases'] : []; - if (\is_string($handler)) { - if (!\class_exists($handler)) { + if (is_string($handler)) { + if (!class_exists($handler)) { Helper::throwInvalidArgument("The console command class [$handler] not exists!"); } - if (!\is_subclass_of($handler, Command::class)) { + if (!is_subclass_of($handler, Command::class)) { Helper::throwInvalidArgument('The console command class must is subclass of the: ' . Command::class); } @@ -170,9 +191,9 @@ public function addCommand(string $name, $handler = null, array $options = []): // allow define aliases in Command class by Command::aliases() if ($aliases = $handler::aliases()) { - $options['aliases'] = \array_merge($options['aliases'], $aliases); + $options['aliases'] = array_merge($options['aliases'], $aliases); } - } elseif (!\is_object($handler) || !\method_exists($handler, '__invoke')) { + } elseif (!is_object($handler) || !method_exists($handler, '__invoke')) { Helper::throwInvalidArgument( 'The console command handler must is an subclass of %s OR a Closure OR a object have method __invoke()', Command::class @@ -196,12 +217,12 @@ public function addCommand(string $name, $handler = null, array $options = []): /** * @param array $commands - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function addCommands(array $commands): void { foreach ($commands as $name => $handler) { - if (\is_int($name)) { + if (is_int($name)) { $this->addCommand($handler); } else { $this->addCommand($name, $handler); @@ -211,12 +232,12 @@ public function addCommands(array $commands): void /** * @param array $controllers - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function addControllers(array $controllers): void { foreach ($controllers as $name => $controller) { - if (\is_int($name)) { + if (is_int($name)) { $this->addGroup($controller); } else { $this->addGroup($name, $controller); @@ -244,7 +265,7 @@ public function addControllers(array $controllers): void public function match(string $name): array { $sep = $this->delimiter; - $name = \trim($name, $sep); + $name = trim($name, $sep); // resolve alias $realName = $this->resolveAlias($name); @@ -259,10 +280,10 @@ public function match(string $name): array $group = $realName; // like 'home:index' - if (\strpos($realName, $sep) > 0) { - $input = \array_values(\array_filter(\explode($sep, $realName))); + if (strpos($realName, $sep) > 0) { + $input = array_values(array_filter(explode($sep, $realName))); - [$group, $action] = \count($input) > 2 ? \array_splice($input, 2) : $input; + [$group, $action] = count($input) > 2 ? array_splice($input, 2) : $input; // resolve alias $group = $this->resolveAlias($group); } @@ -285,20 +306,20 @@ public function match(string $name): array /** * @param $name - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ protected function validateName(string $name): void { // '/^[a-z][\w-]*:?([a-z][\w-]+)?$/' $pattern = '/^[a-z][\w:-]+$/'; - if (1 !== \preg_match($pattern, $name)) { - throw new \InvalidArgumentException("The command name '$name' is must match: $pattern"); + if (1 !== preg_match($pattern, $name)) { + throw new InvalidArgumentException("The command name '$name' is must match: $pattern"); } // cannot be override. like: help, version if ($this->isBlocked($name)) { - throw new \InvalidArgumentException("The command name '$name' is not allowed. It is a built in command."); + throw new InvalidArgumentException("The command name '$name' is not allowed. It is a built in command."); } } @@ -320,7 +341,7 @@ public function sortedEach(callable $grpFunc, callable $cmdFunc): void */ public function getAllNames(): array { - return \array_merge($this->getCommandNames(), $this->getControllerNames()); + return array_merge($this->getCommandNames(), $this->getControllerNames()); } /** @@ -328,7 +349,7 @@ public function getAllNames(): array */ public function getControllerNames(): array { - return \array_keys($this->controllers); + return array_keys($this->controllers); } /** @@ -336,7 +357,7 @@ public function getControllerNames(): array */ public function getCommandNames(): array { - return \array_keys($this->commands); + return array_keys($this->commands); } /** @@ -379,7 +400,7 @@ public function isCommand(string $name): bool */ public function isBlocked(string $name): bool { - return \in_array($name, $this->blocked, true); + return in_array($name, $this->blocked, true); } /** @@ -411,6 +432,6 @@ public function getDelimiter(): string */ public function setDelimiter(string $delimiter): void { - $this->delimiter = \trim($delimiter) ?: ':'; + $this->delimiter = trim($delimiter) ?: ':'; } } diff --git a/src/Traits/AdvancedFormatOutputTrait.php b/src/Traits/AdvancedFormatOutputTrait.php index 775b1935..b9951948 100644 --- a/src/Traits/AdvancedFormatOutputTrait.php +++ b/src/Traits/AdvancedFormatOutputTrait.php @@ -10,9 +10,10 @@ /** * Trait AdvancedFormatOutputTrait + * * @package Inhere\Console\Traits */ trait AdvancedFormatOutputTrait { -} \ No newline at end of file +} diff --git a/src/Traits/ApplicationHelpTrait.php b/src/Traits/ApplicationHelpTrait.php index fb7bfad3..87f42778 100644 --- a/src/Traits/ApplicationHelpTrait.php +++ b/src/Traits/ApplicationHelpTrait.php @@ -8,16 +8,37 @@ namespace Inhere\Console\Traits; +use Inhere\Console\AbstractHandler; use Inhere\Console\Component\Style\Style; use Inhere\Console\Console; use Inhere\Console\Contract\CommandInterface; +use Inhere\Console\IO\Input; +use Inhere\Console\IO\Output; use Inhere\Console\Router; use Inhere\Console\Util\FormatUtil; use Inhere\Console\Util\Show; use Toolkit\Cli\ColorTag; +use function array_merge; +use function date; +use function dirname; +use function file_get_contents; +use function file_put_contents; +use function get_class; +use function implode; +use function is_object; +use function is_string; +use function is_subclass_of; +use function ksort; +use function sprintf; +use function str_replace; +use function strtr; +use const PHP_EOL; +use const PHP_OS; +use const PHP_VERSION; /** * Trait ApplicationHelpTrait + * * @package Inhere\Console\Traits */ trait ApplicationHelpTrait @@ -31,20 +52,20 @@ trait ApplicationHelpTrait */ public function showVersionInfo(): void { - $os = \PHP_OS; - $date = \date('Y.m.d'); + $os = PHP_OS; + $date = date('Y.m.d'); $logo = ''; $name = $this->getParam('name', 'Console Application'); $version = $this->getParam('version', 'Unknown'); $publishAt = $this->getParam('publishAt', 'Unknown'); $updateAt = $this->getParam('updateAt', 'Unknown'); - $phpVersion = \PHP_VERSION; + $phpVersion = PHP_VERSION; if ($logoTxt = $this->getLogoText()) { $logo = ColorTag::wrap($logoTxt, $this->getLogoStyle()); } - /** @var \Inhere\Console\IO\Output $out */ + /** @var Output $out */ $out = $this->output; $out->aList([ "$logo\n {$name}, Version $version\n", @@ -58,11 +79,12 @@ public function showVersionInfo(): void /** * Display the application help information + * * @param string $command */ public function showHelpInfo(string $command = ''): void { - /** @var \Inhere\Console\IO\Input $in */ + /** @var Input $in */ $in = $this->input; // display help for a special command @@ -77,7 +99,7 @@ public function showHelpInfo(string $command = ''): void $sep = $this->delimiter; $script = $in->getScript(); - /** @var \Inhere\Console\IO\Output $out */ + /** @var Output $out */ $out = $this->output; $out->helpPanel([ 'usage' => "$script {command} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", @@ -96,7 +118,7 @@ public function showHelpInfo(string $command = ''): void */ public function showCommandList(): void { - /** @var \Inhere\Console\IO\Input $input */ + /** @var Input $input */ $input = $this->input; // has option: --auto-completion $autoComp = $input->getBoolOpt('auto-completion'); @@ -109,7 +131,7 @@ public function showCommandList(): void return; } - /** @var \Inhere\Console\IO\Output $output */ + /** @var Output $output */ $output = $this->output; /** @var Router $router */ $router = $this->getRouter(); @@ -122,31 +144,28 @@ public function showCommandList(): void // all console groups/controllers if ($groups = $router->getControllers()) { $hasGroup = true; - \ksort($groups); + ksort($groups); } // all independent commands, Independent, Single, Alone if ($commands = $router->getCommands()) { $hasCommand = true; - \ksort($commands); + ksort($commands); } // add split title on both exists. if (!$autoComp && $hasCommand && $hasGroup) { - $groupArr[] = \PHP_EOL . '- Group Commands'; - $commandArr[] = \PHP_EOL . '- Alone Commands'; + $groupArr[] = PHP_EOL . '- Group Commands'; + $commandArr[] = PHP_EOL . '- Alone Commands'; } foreach ($groups as $name => $info) { $options = $info['options']; $controller = $info['handler']; - /** @var \Inhere\Console\AbstractHandler $controller */ + /** @var AbstractHandler $controller */ $desc = $controller::getDescription() ?: $placeholder; $aliases = $options['aliases']; - $extra = $aliases ? ColorTag::wrap( - ' [alias: ' . \implode(',', $aliases) . ']', - 'info' - ) : ''; + $extra = $aliases ? ColorTag::wrap(' [alias: ' . implode(',', $aliases) . ']', 'info') : ''; // collect $groupArr[$name] = $desc . $extra; @@ -161,19 +180,19 @@ public function showCommandList(): void $options = $info['options']; $command = $info['handler']; - /** @var \Inhere\Console\AbstractHandler $command */ - if (\is_subclass_of($command, CommandInterface::class)) { + /** @var AbstractHandler $command */ + if (is_subclass_of($command, CommandInterface::class)) { $desc = $command::getDescription() ?: $placeholder; } elseif ($msg = $options['description'] ?? '') { $desc = $msg; - } elseif (\is_string($command)) { + } elseif (is_string($command)) { $desc = 'A handler : ' . $command; - } elseif (\is_object($command)) { - $desc = 'A handler by ' . \get_class($command); + } elseif (is_object($command)) { + $desc = 'A handler by ' . get_class($command); } $aliases = $options['aliases']; - $extra = $aliases ? ColorTag::wrap(' [alias: ' . \implode(',', $aliases) . ']', 'info') : ''; + $extra = $aliases ? ColorTag::wrap(' [alias: ' . implode(',', $aliases) . ']', 'info') : ''; $commandArr[$name] = $desc . $extra; } @@ -186,17 +205,17 @@ public function showCommandList(): void $internalCommands = static::$internalCommands; if ($autoComp && $shellEnv === 'zsh') { - $map = \array_merge($internalCommands, $groupArr, $commandArr); + $map = array_merge($internalCommands, $groupArr, $commandArr); $this->dumpAutoCompletion('zsh', $map); return; } - \ksort($internalCommands); + ksort($internalCommands); Console::startBuffer(); if ($appDesc = $this->getParam('description', '')) { $appVer = $this->getParam('version', ''); - Console::writeln(\sprintf('%s%s' . \PHP_EOL, $appDesc, $appVer ? " (Version: $appVer)" : '')); + Console::writeln(sprintf('%s%s' . PHP_EOL, $appDesc, $appVer ? " (Version: $appVer)" : '')); } // built in options @@ -206,7 +225,7 @@ public function showCommandList(): void 'Usage:' => "$script {command} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", 'Options:' => $internalOptions, 'Internal Commands:' => $internalCommands, - 'Available Commands:' => \array_merge($groupArr, $commandArr), + 'Available Commands:' => array_merge($groupArr, $commandArr), ], [ 'sepChar' => ' ', ]); @@ -225,14 +244,15 @@ public function showCommandList(): void * php examples/app --auto-completion --shell-env bash * php examples/app --auto-completion --shell-env bash --gen-file * php examples/app --auto-completion --shell-env bash --gen-file stdout + * * @param string $shellEnv * @param array $data */ protected function dumpAutoCompletion(string $shellEnv, array $data): void { - /** @var \Inhere\Console\IO\Input $input */ + /** @var Input $input */ $input = $this->input; - /** @var \Inhere\Console\IO\Output $output */ + /** @var Output $output */ $output = $this->output; /** @var Router $router */ $router = $this->getRouter(); @@ -241,25 +261,22 @@ protected function dumpAutoCompletion(string $shellEnv, array $data): void $glue = ' '; $genFile = (string)$input->getLongOpt('gen-file'); $filename = 'auto-completion.' . $shellEnv; - $tplDir = \dirname(__DIR__, 2) . '/resource/templates'; + $tplDir = dirname(__DIR__, 2) . '/resource/templates'; if ($shellEnv === 'bash') { $tplFile = $tplDir . '/bash-completion.tpl'; - $list = \array_merge( - $router->getCommandNames(), - $router->getControllerNames(), - $this->getInternalCommands() - ); + $list = array_merge($router->getCommandNames(), $router->getControllerNames(), + $this->getInternalCommands()); } else { - $glue = \PHP_EOL; + $glue = PHP_EOL; $list = []; $tplFile = $tplDir . '/zsh-completion.tpl'; foreach ($data as $name => $desc) { - $list[] = $name . ':' . \str_replace(':', '\:', $desc); + $list[] = $name . ':' . str_replace(':', '\:', $desc); } } - $commands = \implode($glue, $list); + $commands = implode($glue, $list); // dump to stdout. if (!$genFile) { @@ -268,32 +285,32 @@ protected function dumpAutoCompletion(string $shellEnv, array $data): void } if ($shellEnv === 'zsh') { - $commands = "'" . \implode("'\n'", $list) . "'"; + $commands = "'" . implode("'\n'", $list) . "'"; $commands = Style::stripColor($commands); } // dump at script file $binName = $input->getBinName(); - $tplText = \file_get_contents($tplFile); - $content = \strtr($tplText, [ + $tplText = file_get_contents($tplFile); + $content = strtr($tplText, [ '{{version}}' => $this->getVersion(), '{{filename}}' => $filename, '{{commands}}' => $commands, '{{binName}}' => $binName, - '{{datetime}}' => \date('Y-m-d H:i:s'), - '{{fmtBinName}}' => \str_replace('/', '_', $binName), + '{{datetime}}' => date('Y-m-d H:i:s'), + '{{fmtBinName}}' => str_replace('/', '_', $binName), ]); // dump to stdout if ($genFile === 'stdout') { - \file_put_contents('php://stdout', $content); + file_put_contents('php://stdout', $content); return; } $targetFile = $input->getPwd() . '/' . $filename; $output->write(['Target File:', $targetFile, '']); - if (\file_put_contents($targetFile, $content) > 10) { + if (file_put_contents($targetFile, $content) > 10) { $output->success("O_O! Generate $filename successful!"); } else { $output->error("O^O! Generate $filename failure!"); diff --git a/src/Traits/ControllerHelpTrait.php b/src/Traits/ControllerHelpTrait.php index 920d5424..2ee8bdd6 100644 --- a/src/Traits/ControllerHelpTrait.php +++ b/src/Traits/ControllerHelpTrait.php @@ -4,6 +4,7 @@ /** * Trait ControllerHelpTrait + * * @package Inhere\Console\Traits */ trait ControllerHelpTrait diff --git a/src/Traits/FormatOutputAwareTrait.php b/src/Traits/FormatOutputAwareTrait.php index 39b522a6..9293e78c 100644 --- a/src/Traits/FormatOutputAwareTrait.php +++ b/src/Traits/FormatOutputAwareTrait.php @@ -8,13 +8,24 @@ namespace Inhere\Console\Traits; +use Closure; +use Generator; use Inhere\Console\Component\Style\Style; use Inhere\Console\Console; +use Inhere\Console\Util\Interact; use Inhere\Console\Util\Show; +use LogicException; use Toolkit\PhpUtil\Php; +use function array_merge; +use function json_encode; +use function method_exists; +use function sprintf; +use function strpos; +use function substr; /** * Class FormatOutputAwareTrait + * * @package Inhere\Console\Traits * * @method int info($messages, $quit = false) @@ -43,7 +54,13 @@ * @method pending($msg = 'Pending ', $ended = false) * @method pointing($msg = 'handling ', $ended = false) * - * @method \Generator counterTxt($msg = 'Pending ', $ended = false) + * @method Generator counterTxt($msg = 'Pending ', $ended = false) + * + * @method confirm(string $question, bool $default = true, bool $nl = true): bool + * @method select(string $description, $options, $default = null, bool $allowExit = true): string + * @method checkbox(string $description, $options, $default = null, bool $allowExit = true): array + * @method ask(string $question, string $default = '', Closure $validator = null): string + * @method askPassword(string $prompt = 'Enter Password:'): string */ trait FormatOutputAwareTrait { @@ -53,7 +70,7 @@ trait FormatOutputAwareTrait */ public function write($messages, $nl = true, $quit = false, array $opts = []): int { - return Console::write($messages, $nl, $quit, \array_merge([ + return Console::write($messages, $nl, $quit, array_merge([ 'flush' => true, 'stream' => $this->outputStream, ], $opts)); @@ -80,11 +97,12 @@ public function writeRaw($text, bool $nl = true, $quit = false, array $opts = [] /** * @param string $text * @param string $tag + * * @return int */ public function colored(string $text, string $tag = 'info'): int { - return $this->writeln(\sprintf('<%s>%s', $tag, $text, $tag)); + return $this->writeln(sprintf('<%s>%s', $tag, $text, $tag)); } /** @@ -152,6 +170,7 @@ public function mList(array $data, array $opts = []): void /** * helpPanel + * * @inheritdoc * @see Show::helpPanel() */ @@ -182,7 +201,7 @@ public function table(array $data, $title = 'Data Table', $showBorder = true): v * @inheritdoc * @see Show::progressBar() */ - public function progressTxt($total, $msg, $doneMsg = ''): \Generator + public function progressTxt($total, $msg, $doneMsg = ''): Generator { return Show::progressTxt($total, $msg, $doneMsg); } @@ -191,7 +210,7 @@ public function progressTxt($total, $msg, $doneMsg = ''): \Generator * @inheritdoc * @see Show::progressBar() */ - public function progressBar($total, array $opts = []): \Generator + public function progressBar($total, array $opts = []): Generator { return Show::progressBar($total, $opts); } @@ -199,37 +218,43 @@ public function progressBar($total, array $opts = []): \Generator /** * @param string $method * @param array $args + * * @return int - * @throws \LogicException + * @throws LogicException */ public function __call($method, array $args = []) { $map = Show::getBlockMethods(false); if (isset($map[$method])) { - $msg = $args[0]; - $quit = $args[1] ?? false; + $msg = $args[0]; + $quit = $args[1] ?? false; $style = $map[$method]; - if (0 === \strpos($method, 'lite')) { - $type = \substr($method, 4); + if (0 === strpos($method, 'lite')) { + $type = substr($method, 4); return Show::liteBlock($msg, $type === 'Primary' ? 'IMPORTANT' : $type, $style, $quit); } return Show::block($msg, $style === 'primary' ? 'IMPORTANT' : $style, $style, $quit); } - if (\method_exists(Show::class, $method)) { + if (method_exists(Show::class, $method)) { return Show::$method(...$args); } - throw new \LogicException("Call a not exists method: $method of the " . static::class); + if (method_exists(Interact::class, $method)) { + return Interact::$method(...$args); + } + + throw new LogicException("Call a not exists method: $method of the " . static::class); } /** * @param mixed $data * @param bool $echo * @param int $flags + * * @return int|string */ public function json( @@ -237,7 +262,7 @@ public function json( bool $echo = true, int $flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ) { - $string = \json_encode($data, $flags); + $string = json_encode($data, $flags); if ($echo) { return Console::write($string); diff --git a/src/Traits/InputOutputAwareTrait.php b/src/Traits/InputOutputAwareTrait.php index 4ceb40b3..9f876d11 100644 --- a/src/Traits/InputOutputAwareTrait.php +++ b/src/Traits/InputOutputAwareTrait.php @@ -13,9 +13,11 @@ use Inhere\Console\IO\InputInterface; use Inhere\Console\IO\Output; use Inhere\Console\IO\OutputInterface; +use InvalidArgumentException; /** * Class InputOutputAwareTrait + * * @package Inhere\Console\Traits */ trait InputOutputAwareTrait @@ -62,8 +64,8 @@ public function getFirstArg(string $default = ''): string /** * {@inheritdoc} + * @throws InvalidArgumentException * @see Input::getRequiredArg() - * @throws \InvalidArgumentException */ public function getRequiredArg($name) { @@ -99,8 +101,8 @@ public function getSameOpt(array $names, $default = null) /** * {@inheritdoc} + * @throws InvalidArgumentException * @see Input::getRequiredOpt() - * @throws \InvalidArgumentException */ public function getRequiredOpt(string $name) { @@ -110,6 +112,7 @@ public function getRequiredOpt(string $name) /** * @param string $question * @param bool $nl + * * @return string */ public function read(string $question = '', bool $nl = false): string @@ -121,6 +124,7 @@ public function read(string $question = '', bool $nl = false): string * @param mixed $message * @param bool $nl * @param bool|int $quit + * * @return int */ public function write($message, $nl = true, $quit = false): int @@ -131,6 +135,7 @@ public function write($message, $nl = true, $quit = false): int /** * @param mixed $message * @param bool|int $quit + * * @return int */ public function writeln($message, $quit = false): int @@ -172,6 +177,7 @@ public function setOutput(OutputInterface $output): void /** * get debug level value + * * @return int */ public function getVerbLevel(): int @@ -181,7 +187,9 @@ public function getVerbLevel(): int /** * check is given verbose level + * * @param int $level + * * @return bool */ public function isDebug(int $level = Console::VERB_DEBUG): bool diff --git a/src/Traits/NameAliasTrait.php b/src/Traits/NameAliasTrait.php index 26fb72ec..35ced718 100644 --- a/src/Traits/NameAliasTrait.php +++ b/src/Traits/NameAliasTrait.php @@ -10,6 +10,7 @@ /** * Class NameAliasTrait + * * @package Inhere\Console\Traits */ trait NameAliasTrait @@ -21,6 +22,7 @@ trait NameAliasTrait /** * set name alias(es) + * * @param string $name * @param string|array $alias */ @@ -35,7 +37,9 @@ public function setAlias(string $name, $alias): void /** * get real name by alias + * * @param string $alias + * * @return mixed */ public function resolveAlias(string $alias): string @@ -45,6 +49,7 @@ public function resolveAlias(string $alias): string /** * @param string $alias + * * @return bool */ public function hasAlias(string $alias): bool diff --git a/src/Traits/RuntimeProfileTrait.php b/src/Traits/RuntimeProfileTrait.php index ef5e4259..6e66759a 100644 --- a/src/Traits/RuntimeProfileTrait.php +++ b/src/Traits/RuntimeProfileTrait.php @@ -8,16 +8,24 @@ namespace Inhere\Console\Traits; +use InvalidArgumentException; use Toolkit\PhpUtil\PhpHelper; +use function array_pop; +use function explode; +use function in_array; +use function memory_get_usage; +use function microtime; /** * Trait RuntimeProfileTrait + * * @package Inhere\Library\Traits */ trait RuntimeProfileTrait { /** * profile data + * * @var array */ private static $profiles = []; @@ -35,17 +43,19 @@ trait RuntimeProfileTrait /** * mark data analysis start + * * @param $name * @param array $context * @param string $category - * @throws \InvalidArgumentException + * + * @throws InvalidArgumentException */ public static function profile($name, array $context = [], $category = 'application'): void { $data = [ '_profile_stats' => [ - 'startTime' => \microtime(true), - 'startMem' => \memory_get_usage(), + 'startTime' => microtime(true), + 'startMem' => memory_get_usage(), ], '_profile_start' => $context, '_profile_end' => null, @@ -54,35 +64,37 @@ public static function profile($name, array $context = [], $category = 'applicat $profileKey = $category . '|' . $name; - if (\in_array($profileKey, self::$keyQueue, 1)) { - throw new \InvalidArgumentException("Your added profile name [$name] have been exists!"); + if (in_array($profileKey, self::$keyQueue, 1)) { + throw new InvalidArgumentException("Your added profile name [$name] have been exists!"); } - self::$keyQueue[] = $profileKey; + self::$keyQueue[] = $profileKey; self::$profiles[$category][$name] = $data; } /** * mark data analysis end + * * @param string|null $msg * @param array $context + * * @return bool|array */ public static function profileEnd(string $msg = null, array $context = []) { - if (!$latestKey = \array_pop(self::$keyQueue)) { + if (!$latestKey = array_pop(self::$keyQueue)) { return false; } - [$category, $name] = \explode('|', $latestKey); + [$category, $name] = explode('|', $latestKey); if (isset(self::$profiles[$category][$name])) { $data = self::$profiles[$category][$name]; - $old = $data['_profile_stats']; + $old = $data['_profile_stats']; $data['_profile_stats'] = PhpHelper::runtime($old['startTime'], $old['startMem']); - $data['_profile_end'] = $context; - $data['_profile_msg'] = $msg; + $data['_profile_end'] = $context; + $data['_profile_msg'] = $msg; // $title = $category . ' - ' . ($title ?: $name); @@ -98,6 +110,7 @@ public static function profileEnd(string $msg = null, array $context = []) /** * @param null|string $name * @param string $category + * * @return array */ public static function getProfileData(string $name = null, string $category = 'application'): array diff --git a/src/Traits/SimpleEventTrait.php b/src/Traits/SimpleEventTrait.php index 51973a07..125a72b8 100644 --- a/src/Traits/SimpleEventTrait.php +++ b/src/Traits/SimpleEventTrait.php @@ -8,8 +8,12 @@ namespace Inhere\Console\Traits; +use function count; +use function in_array; + /** * Class SimpleEventStaticTrait + * * @package Inhere\Console\Traits */ trait SimpleEventTrait @@ -17,12 +21,14 @@ trait SimpleEventTrait /** * set the supported events, if you need. * if it is empty, will allow register any event. + * * @var array */ protected static $supportedEvents = []; /** * registered Events + * * @var array * [ * 'event' => bool, // is once event @@ -32,6 +38,7 @@ trait SimpleEventTrait /** * events and handlers + * * @var array * [ * 'event' => callable, // event handler @@ -41,6 +48,7 @@ trait SimpleEventTrait /** * register a event handler + * * @param $event * @param callable $handler * @param bool $once @@ -58,6 +66,7 @@ public function on(string $event, callable $handler, $once = false): void /** * register a once event handler + * * @param $event * @param callable $handler */ @@ -68,8 +77,10 @@ public function once(string $event, callable $handler): void /** * trigger event + * * @param string $event * @param array ...$args + * * @return bool */ public function fire(string $event, ...$args): bool @@ -96,7 +107,9 @@ public function fire(string $event, ...$args): bool /** * remove event and it's handlers + * * @param $event + * * @return bool */ public function off(string $event): bool @@ -112,6 +125,7 @@ public function off(string $event): bool /** * @param $event + * * @return bool */ public function hasEvent(string $event): bool @@ -121,6 +135,7 @@ public function hasEvent(string $event): bool /** * @param $event + * * @return bool */ public function isOnce(string $event): bool @@ -134,7 +149,9 @@ public function isOnce(string $event): bool /** * check $name is a supported event name + * * @param string $event + * * @return bool */ public static function isSupportedEvent(string $event): bool @@ -144,7 +161,7 @@ public static function isSupportedEvent(string $event): bool } if ($ets = self::$supportedEvents) { - return \in_array($event, $ets, true); + return in_array($event, $ets, true); } return true; @@ -179,6 +196,6 @@ public static function getEvents(): array */ public static function countEvents(): int { - return \count(self::$events); + return count(self::$events); } } diff --git a/src/Traits/UserInteractAwareTrait.php b/src/Traits/UserInteractAwareTrait.php index 6d38e120..caf406b8 100644 --- a/src/Traits/UserInteractAwareTrait.php +++ b/src/Traits/UserInteractAwareTrait.php @@ -8,12 +8,16 @@ namespace Inhere\Console\Traits; +use Closure; use Inhere\Console\Util\Interact; +use LogicException; +use function method_exists; /** * Class UserInteractAwareTrait + * * @package Inhere\Console\Traits - * @see Interact + * @see Interact * * @method string readRow($message = null, $nl = false) * @method string readln($message = null, $nl = false, array $opts = []) @@ -58,12 +62,12 @@ public function confirm(string $question, bool $default = true): bool * @inheritdoc * @see Interact::question() */ - public function ask(string $question, string $default = '', \Closure $validator = null): ?string + public function ask(string $question, string $default = '', Closure $validator = null): ?string { return $this->question($question, $default, $validator); } - public function question(string $question, string $default = '', \Closure $validator = null): ?string + public function question(string $question, string $default = '', Closure $validator = null): ?string { return Interact::question($question, $default, $validator); } @@ -72,7 +76,7 @@ public function question(string $question, string $default = '', \Closure $valid * @inheritdoc * @see Interact::limitedAsk() */ - public function limitedAsk(string $question, string $default = '', \Closure $validator = null, $times = 3): ?string + public function limitedAsk(string $question, string $default = '', Closure $validator = null, $times = 3): ?string { return Interact::limitedAsk($question, $default, $validator, $times); } @@ -80,15 +84,16 @@ public function limitedAsk(string $question, string $default = '', \Closure $val /** * @param string $method * @param array $args + * * @return int - * @throws \LogicException + * @throws LogicException */ public function __call($method, array $args = []) { - if (\method_exists(Interact::class, $method)) { + if (method_exists(Interact::class, $method)) { return Interact::$method(...$args); } - throw new \LogicException("Call a not exists method: $method of the " . static::class); + throw new LogicException("Call a not exists method: $method of the " . static::class); } } diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index 238e6e64..5bbd27be 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -8,8 +8,30 @@ namespace Inhere\Console\Util; +use function array_keys; +use function array_merge; +use function count; +use function date; +use function explode; +use function floor; +use function gettype; +use function implode; +use function is_array; +use function is_bool; +use function is_int; +use function is_numeric; +use function is_scalar; +use function rtrim; +use function sprintf; +use function str_pad; +use function str_repeat; +use function str_replace; +use function strpos; use Toolkit\Cli\ColorTag; use Toolkit\Sys\Sys; +use function trim; +use function ucfirst; +use function wordwrap; /** * Class FormatUtil @@ -27,7 +49,7 @@ public static function typeToString($val): string return '(Null)'; } - if (\is_bool($val)) { + if (is_bool($val)) { return $val ? '(True)' : '(False)'; } @@ -47,8 +69,8 @@ public static function applyIndent(string $string, int $indent = 2, string $inde } $new = ''; - $list = \explode("\n", $string); - $indentStr = \str_repeat($indentChar ?: ' ', $indent); + $list = explode("\n", $string); + $indentStr = str_repeat($indentChar ?: ' ', $indent); foreach ($list as $value) { $new .= $indentStr . trim($value) . "\n"; @@ -93,8 +115,8 @@ public static function wrapText($text, $indent = 0, $width = 0): string $width = $size[0]; } - $pad = \str_repeat(' ', $indent); - $lines = \explode("\n", \wordwrap($text, $width - $indent, "\n", true)); + $pad = str_repeat(' ', $indent); + $lines = explode("\n", wordwrap($text, $width - $indent, "\n", true)); $first = true; foreach ($lines as $i => $line) { @@ -105,7 +127,7 @@ public static function wrapText($text, $indent = 0, $width = 0): string $lines[$i] = $pad . $line; } - return $pad . ' ' . \implode("\n", $lines); + return $pad . ' ' . implode("\n", $lines); } /** @@ -115,7 +137,7 @@ public static function wrapText($text, $indent = 0, $width = 0): string public static function alignOptions(array $options): array { // e.g '-h, --help' - $hasShort = (bool)\strpos(\implode(\array_keys($options), ''), ','); + $hasShort = (bool)strpos(implode('', array_keys($options)), ','); if (!$hasShort) { return $options; @@ -123,15 +145,15 @@ public static function alignOptions(array $options): array $formatted = []; foreach ($options as $name => $des) { - if (!$name = \trim($name, ', ')) { + if (!$name = trim($name, ', ')) { continue; } - if (!\strpos($name, ',')) { + if (!strpos($name, ',')) { // padding length equals to '-h, ' $name = ' ' . $name; } else { - $name = \str_replace([' ', ','], ['', ', '], $name); + $name = str_replace([' ', ','], ['', ', '], $name); } $formatted[$name] = $des; @@ -150,18 +172,18 @@ public static function alignOptions(array $options): array public static function memoryUsage($memory): string { if ($memory >= 1024 * 1024 * 1024) { - return \sprintf('%.2f Gb', $memory / 1024 / 1024 / 1024); + return sprintf('%.2f Gb', $memory / 1024 / 1024 / 1024); } if ($memory >= 1024 * 1024) { - return \sprintf('%.2f Mb', $memory / 1024 / 1024); + return sprintf('%.2f Mb', $memory / 1024 / 1024); } if ($memory >= 1024) { - return \sprintf('%.2f Kb', $memory / 1024); + return sprintf('%.2f Kb', $memory / 1024); } - return \sprintf('%d B', $memory); + return sprintf('%d B', $memory); } /** @@ -187,17 +209,17 @@ public static function howLongAgo(int $secs): string if ($secs >= $format[0]) { $next = $timeFormats[$index + 1] ?? false; - if (($next && $secs < $next[0]) || $index === \count($timeFormats) - 1) { - if (2 === \count($format)) { + if (($next && $secs < $next[0]) || $index === count($timeFormats) - 1) { + if (2 === count($format)) { return $format[1]; } - return \floor($secs / $format[2]) . ' ' . $format[1]; + return floor($secs / $format[2]) . ' ' . $format[1]; } } } - return \date('Y-m-d H:i:s', $secs); + return date('Y-m-d H:i:s', $secs); } /** @@ -213,7 +235,7 @@ public static function howLongAgo(int $secs): string public static function spliceKeyValue(array $data, array $opts = []): string { $text = ''; - $opts = \array_merge([ + $opts = array_merge([ 'leftChar' => '', // e.g ' ', ' * ' 'sepChar' => ' ', // e.g ' | ' OUT: key | value 'keyStyle' => '', // e.g 'info','comment' @@ -223,7 +245,7 @@ public static function spliceKeyValue(array $data, array $opts = []): string 'ucFirst' => true, // upper first char ], $opts); - if (!\is_numeric($opts['keyMaxWidth'])) { + if (!is_numeric($opts['keyMaxWidth'])) { $opts['keyMaxWidth'] = Helper::getKeyMaxWidth($data); } @@ -232,40 +254,40 @@ public static function spliceKeyValue(array $data, array $opts = []): string $opts['keyMaxWidth'] = $opts['keyMinWidth']; } - $keyStyle = \trim($opts['keyStyle']); + $keyStyle = trim($opts['keyStyle']); foreach ($data as $key => $value) { - $hasKey = !\is_int($key); + $hasKey = !is_int($key); $text .= $opts['leftChar']; if ($hasKey && $opts['keyMaxWidth']) { - $key = \str_pad($key, $opts['keyMaxWidth'], ' '); + $key = str_pad($key, $opts['keyMaxWidth'], ' '); $text .= ColorTag::wrap($key, $keyStyle) . $opts['sepChar']; } // if value is array, translate array to string - if (\is_array($value)) { + if (is_array($value)) { $temp = ''; /** @var array $value */ foreach ($value as $k => $val) { - if (\is_bool($val)) { + if (is_bool($val)) { $val = $val ? '(True)' : '(False)'; } else { - $val = \is_scalar($val) ? (string)$val : \gettype($val); + $val = is_scalar($val) ? (string)$val : gettype($val); } - $temp .= (!\is_numeric($k) ? "$k: " : '') . "$val, "; + $temp .= (!is_numeric($k) ? "$k: " : '') . "$val, "; } - $value = \rtrim($temp, ' ,'); - } elseif (\is_bool($value)) { + $value = rtrim($temp, ' ,'); + } elseif (is_bool($value)) { $value = $value ? '(True)' : '(False)'; } else { $value = (string)$value; } - $value = $hasKey && $opts['ucFirst'] ? \ucfirst($value) : $value; + $value = $hasKey && $opts['ucFirst'] ? ucfirst($value) : $value; $text .= ColorTag::wrap($value, $opts['valStyle']) . "\n"; } diff --git a/src/Util/Helper.php b/src/Util/Helper.php index a7ae6d7b..ba585d6e 100644 --- a/src/Util/Helper.php +++ b/src/Util/Helper.php @@ -10,7 +10,24 @@ namespace Inhere\Console\Util; +use function class_exists; +use function file_exists; +use FilesystemIterator; use Inhere\Console\Traits\RuntimeProfileTrait; +use InvalidArgumentException; +use function is_dir; +use function is_numeric; +use Iterator; +use function mb_strlen; +use function mkdir; +use function preg_match; +use RecursiveCallbackFilterIterator; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use RuntimeException; +use function similar_text; +use function sprintf; +use function strpos; use Swoole\Coroutine; /** @@ -26,7 +43,7 @@ class Helper */ public static function isSupportCoroutine(): bool { - return \class_exists(Coroutine::class, false); + return class_exists(Coroutine::class, false); } /** @@ -47,18 +64,18 @@ public static function inCoroutine(): bool */ public static function isAbsPath(string $path): bool { - return \strpos($path, '/') === 0 || 1 === \preg_match('#^[a-z]:[\/|\\\]{1}.+#i', $path); + return strpos($path, '/') === 0 || 1 === preg_match('#^[a-z]:[\/|\\\]{1}.+#i', $path); } /** * @param string $dir * @param int $mode - * @throws \RuntimeException + * @throws RuntimeException */ public static function mkdir(string $dir, int $mode = 0775): void { - if (!\file_exists($dir) && !\mkdir($dir, $mode, true) && !\is_dir($dir)) { - throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir)); + if (!file_exists($dir) && !mkdir($dir, $mode, true) && !is_dir($dir)) { + throw new RuntimeException(sprintf('Directory "%s" was not created', $dir)); } } @@ -66,22 +83,22 @@ public static function mkdir(string $dir, int $mode = 0775): void * @param string $srcDir * @param callable $filter * @param int $flags - * @return \RecursiveIteratorIterator - * @throws \InvalidArgumentException + * @return RecursiveIteratorIterator + * @throws InvalidArgumentException */ public static function directoryIterator( string $srcDir, callable $filter, - $flags = \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO - ): \RecursiveIteratorIterator { + $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO + ): RecursiveIteratorIterator { if (!$srcDir || !file_exists($srcDir)) { - throw new \InvalidArgumentException('Please provide a exists source directory.'); + throw new InvalidArgumentException('Please provide a exists source directory.'); } - $directory = new \RecursiveDirectoryIterator($srcDir, $flags); - $filterIterator = new \RecursiveCallbackFilterIterator($directory, $filter); + $directory = new RecursiveDirectoryIterator($srcDir, $flags); + $filterIterator = new RecursiveCallbackFilterIterator($directory, $filter); - return new \RecursiveIteratorIterator($filterIterator); + return new RecursiveIteratorIterator($filterIterator); } /** @@ -96,7 +113,7 @@ public static function commandSearch(string $command, array $map): void /** * find similar text from an array|Iterator * @param string $need - * @param \Iterator|array $iterator + * @param Iterator|array $iterator * @param int $similarPercent * @return array */ @@ -110,7 +127,7 @@ public static function findSimilar(string $need, $iterator, int $similarPercent $similar = []; foreach ($iterator as $name) { - \similar_text($need, $name, $percent); + similar_text($need, $name, $percent); if ($similarPercent <= (int)$percent) { $similar[] = $name; @@ -136,8 +153,8 @@ public static function getKeyMaxWidth(array $data, bool $expectInt = false): int foreach ($data as $key => $value) { // key is not a integer - if (!$expectInt || !\is_numeric($key)) { - $width = \mb_strlen($key, 'UTF-8'); + if (!$expectInt || !is_numeric($key)) { + $width = mb_strlen($key, 'UTF-8'); $keyMaxWidth = $width > $keyMaxWidth ? $width : $keyMaxWidth; } } @@ -151,7 +168,7 @@ public static function getKeyMaxWidth(array $data, bool $expectInt = false): int */ public static function throwInvalidArgument(string $format, ...$args): void { - throw new \InvalidArgumentException(\sprintf($format, ...$args)); + throw new InvalidArgumentException(sprintf($format, ...$args)); } /** diff --git a/src/Util/Interact.php b/src/Util/Interact.php index da090717..6762f340 100644 --- a/src/Util/Interact.php +++ b/src/Util/Interact.php @@ -8,6 +8,7 @@ namespace Inhere\Console\Util; +use Closure; use Inhere\Console\Component\Interact\Checkbox; use Inhere\Console\Component\Interact\Choose; use Inhere\Console\Component\Interact\Confirm; @@ -15,6 +16,11 @@ use Inhere\Console\Component\Interact\Password; use Inhere\Console\Component\Interact\Question; use Inhere\Console\Console; +use RuntimeException; +use function sprintf; +use const STDIN; +use function strtolower; +use function trim; /** * Class Interact @@ -38,9 +44,9 @@ public static function readln($message = null, $nl = false, array $opts = []): s Console::write($message, $nl); } - $stream = $opts['stream'] ?? \STDIN; + $stream = $opts['stream'] ?? STDIN; - return \trim(fgets($stream)); + return trim(fgets($stream)); } /** @@ -161,11 +167,11 @@ public static function answerIsYes(bool $default = null): bool if ($default !== null) { $defMsg = $default ? 'yes' : 'no'; - $mark = \sprintf(' [yes|no](default %s): ', $defMsg); + $mark = sprintf(' [yes|no](default %s): ', $defMsg); } if ($answer = Console::readFirst($mark)) { - $answer = \strtolower($answer); + $answer = strtolower($answer); if ($answer === 'y') { return true; @@ -186,10 +192,10 @@ public static function answerIsYes(bool $default = null): bool * alias of the `question()` * @param string $question question message * @param string $default default value - * @param \Closure $validator The validate callback. It must return bool. + * @param Closure $validator The validate callback. It must return bool. * @return string|null */ - public static function ask(string $question, string $default = '', \Closure $validator = null): ?string + public static function ask(string $question, string $default = '', Closure $validator = null): ?string { return self::question($question, $default, $validator); } @@ -199,10 +205,10 @@ public static function ask(string $question, string $default = '', \Closure $val * @see Question::ask() * @param string $question * @param string $default - * @param \Closure|null $validator Validator, must return bool. + * @param Closure|null $validator Validator, must return bool. * @return string */ - public static function question(string $question, string $default = '', \Closure $validator = null): string + public static function question(string $question, string $default = '', Closure $validator = null): string { return Question::ask($question, $default, $validator); } @@ -212,14 +218,14 @@ public static function question(string $question, string $default = '', \Closure * @see LimitedAsk::ask() * @param string $question 问题 * @param string $default 默认值 - * @param \Closure $validator (默认验证输入是否为空)自定义回调验证输入是否符合要求; 验证成功返回true 否则 可返回错误消息 + * @param Closure $validator (默认验证输入是否为空)自定义回调验证输入是否符合要求; 验证成功返回true 否则 可返回错误消息 * @param int $times Allow input times * @return string */ public static function limitedAsk( string $question, string $default = '', - \Closure $validator = null, + Closure $validator = null, int $times = 3 ): string { return LimitedAsk::ask($question, $default, $validator, $times); @@ -237,7 +243,7 @@ public static function limitedAsk( * @return string * @link https://stackoverflow.com/questions/187736/command-line-password-prompt-in-php * @link http://www.sitepoint.com/blogs/2009/05/01/interactive-cli-password-prompt-in-php - * @throws \RuntimeException + * @throws RuntimeException */ public static function promptSilent(string $prompt = 'Enter Password:'): string { @@ -248,7 +254,7 @@ public static function promptSilent(string $prompt = 'Enter Password:'): string * alias of the method `promptSilent()` * @param string $prompt * @return string - * @throws \RuntimeException + * @throws RuntimeException */ public static function askHiddenInput(string $prompt = 'Enter Password:'): string { @@ -259,7 +265,7 @@ public static function askHiddenInput(string $prompt = 'Enter Password:'): strin * alias of the method `promptSilent()` * @param string $prompt * @return string - * @throws \RuntimeException + * @throws RuntimeException */ public static function askPassword(string $prompt = 'Enter Password:'): string { diff --git a/src/Util/ProgressBar.php b/src/Util/ProgressBar.php index 9e4d020c..9c05cd6e 100644 --- a/src/Util/ProgressBar.php +++ b/src/Util/ProgressBar.php @@ -8,8 +8,12 @@ namespace Inhere\Console\Util; +use Closure; use Inhere\Console\IO\Output; use Inhere\Console\IO\OutputInterface; +use LogicException; +use function max; +use RuntimeException; use Toolkit\StrUtil\Str; /** @@ -81,7 +85,7 @@ class ProgressBar /** * section parsers - * @var \Closure[] + * @var Closure[] */ private static $parsers = [ // 'precent' => function () { ... }, @@ -114,12 +118,12 @@ public function __construct(OutputInterface $output = null, int $maxSteps = 0) /** * 开始 * @param null $maxSteps - * @throws \LogicException + * @throws LogicException */ public function start($maxSteps = null): void { if ($this->started) { - throw new \LogicException('Progress bar already started.'); + throw new LogicException('Progress bar already started.'); } $this->startTime = time(); @@ -137,12 +141,12 @@ public function start($maxSteps = null): void /** * 前进,按步长前进几步 * @param int $step 前进几步 - * @throws \LogicException + * @throws LogicException */ public function advance(int $step = 1): void { if (!$this->started) { - throw new \LogicException('Progress indicator has not yet been started.'); + throw new LogicException('Progress indicator has not yet been started.'); } $this->advanceTo($this->step + $step); @@ -172,12 +176,12 @@ public function advanceTo(int $step): void /** * Finishes the progress output. - * @throws \LogicException + * @throws LogicException */ public function finish(): void { if (!$this->started) { - throw new \LogicException('Progress bar has not yet been started.'); + throw new LogicException('Progress bar has not yet been started.'); } if (!$this->maxSteps) { @@ -246,7 +250,7 @@ public function render(string $text): void /** * @return mixed - * @throws \RuntimeException + * @throws RuntimeException */ protected function buildLine() { @@ -283,7 +287,7 @@ public function setParser(string $section, callable $handler): void * @param string $section * @param bool|boolean $throwException * @return mixed - * @throws \RuntimeException + * @throws RuntimeException */ public function getParser(string $section, bool $throwException = false) { @@ -296,7 +300,7 @@ public function getParser(string $section, bool $throwException = false) } if ($throwException) { - throw new \RuntimeException("The section($section) formatter is not registered!", -500); + throw new RuntimeException("The section($section) formatter is not registered!", -500); } return null; @@ -373,7 +377,7 @@ public function setOverwrite(bool $overwrite): void */ private function setMaxSteps(int $maxSteps): void { - $this->maxSteps = \max(0, $maxSteps); + $this->maxSteps = max(0, $maxSteps); $this->stepWidth = $this->maxSteps ? Str::len($this->maxSteps) : 2; } @@ -512,7 +516,7 @@ public function setFormat(string $format): void /** * @return array - * @throws \LogicException + * @throws LogicException */ private static function loadDefaultParsers(): array { @@ -533,7 +537,7 @@ private static function loadDefaultParsers(): array }, 'remaining' => function (self $bar) { if (!$bar->getMaxSteps()) { - throw new \LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); + throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); } if (!$bar->getProgress()) { diff --git a/src/Util/Show.php b/src/Util/Show.php index 7674c00e..b1d46cbf 100644 --- a/src/Util/Show.php +++ b/src/Util/Show.php @@ -2,6 +2,12 @@ namespace Inhere\Console\Util; +use function array_keys; +use function array_values; +use function ceil; +use function count; +use Generator; +use function implode; use Inhere\Console\Component\Formatter\HelpPanel; use Inhere\Console\Component\Formatter\MultiList; use Inhere\Console\Component\Formatter\Padding; @@ -17,10 +23,26 @@ use Inhere\Console\Component\Progress\SimpleTextBar; use Inhere\Console\Component\Style\Style; use Inhere\Console\Console; +use function is_array; +use function is_string; +use function json_encode; +use const JSON_PRETTY_PRINT; +use const JSON_UNESCAPED_SLASHES; +use const JSON_UNESCAPED_UNICODE; +use LogicException; +use function microtime; +use const PHP_EOL; +use function sprintf; +use function str_repeat; +use function strlen; +use function strpos; +use function strtoupper; +use function substr; use Toolkit\Cli\Cli; use Toolkit\Cli\ColorTag; use Toolkit\StrUtil\Str; use Toolkit\Sys\Sys; +use function ucwords; /** * Class Show - render and display formatted message text @@ -75,18 +97,18 @@ class Show */ public static function block($messages, string $type = 'MESSAGE', string $style = Style::NORMAL, $quit = false): int { - $messages = \is_array($messages) ? \array_values($messages) : [$messages]; + $messages = is_array($messages) ? array_values($messages) : [$messages]; // add type if ($type) { - $messages[0] = \sprintf('[%s] %s', \strtoupper($type), $messages[0]); + $messages[0] = sprintf('[%s] %s', strtoupper($type), $messages[0]); } - $text = \implode(\PHP_EOL, $messages); + $text = implode(PHP_EOL, $messages); $color = static::getStyle(); - if (\is_string($style) && $color->hasStyle($style)) { - $text = \sprintf('<%s>%s', $style, $text, $style); + if (is_string($style) && $color->hasStyle($style)) { + $text = sprintf('<%s>%s', $style, $text, $style); } return self::write($text, true, $quit); @@ -106,18 +128,18 @@ public static function liteBlock( $quit = false ): int { $fmtType = ''; - $messages = \is_array($messages) ? \array_values($messages) : [$messages]; + $messages = is_array($messages) ? array_values($messages) : [$messages]; - $text = \implode(\PHP_EOL, $messages); + $text = implode(PHP_EOL, $messages); $color = static::getStyle(); // format type if ($type) { // add style if ($style && $color->hasStyle($style)) { - $fmtType = \sprintf('<%s>[%s] ', $style, $upType, $style); + $fmtType = sprintf('<%s>[%s] ', $style, $upType, $style); } else { - $fmtType = \sprintf('[%s]', $upType = \strtoupper($type)); + $fmtType = sprintf('[%s]', $upType = strtoupper($type)); } } @@ -153,7 +175,7 @@ public static function liteBlock( * @param string $method * @param array $args * @return int - * @throws \LogicException + * @throws LogicException */ public static function __callStatic($method, array $args = []) { @@ -162,8 +184,8 @@ public static function __callStatic($method, array $args = []) $quit = $args[1] ?? false; $style = self::$blockMethods[$method]; - if (0 === \strpos($method, 'lite')) { - $type = \substr($method, 4); + if (0 === strpos($method, 'lite')) { + $type = substr($method, 4); return self::liteBlock($msg, $type === 'primary' ? 'IMPORTANT' : $type, $style, $quit); } @@ -171,7 +193,7 @@ public static function __callStatic($method, array $args = []) return self::block($msg, $style === 'primary' ? 'IMPORTANT' : $style, $style, $quit); } - throw new \LogicException("Call a not exists method: $method"); + throw new LogicException("Call a not exists method: $method"); } /************************************************************************************************** @@ -186,9 +208,9 @@ public static function __callStatic($method, array $args = []) */ public static function prettyJSON( $data, - int $flags = \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES + int $flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ): int { - $string = (string)\json_encode($data, $flags); + $string = (string)json_encode($data, $flags); return Console::write($string); } @@ -207,13 +229,13 @@ public static function splitLine(string $title, string $char = '-', int $width = } if (!$title) { - return self::write(\str_repeat($char, $width)); + return self::write(str_repeat($char, $width)); } - $strLen = \ceil(($width - Str::len($title) - 2) / 2); - $padStr = $strLen > 0 ? \str_repeat($char, $strLen) : ''; + $strLen = ceil(($width - Str::len($title) - 2) / 2); + $padStr = $strLen > 0 ? str_repeat($char, $strLen) : ''; - return self::write($padStr . ' ' . \ucwords($title) . ' ' . $padStr); + return self::write($padStr . ' ' . ucwords($title) . ' ' . $padStr); } /** @@ -395,7 +417,7 @@ public static function spinner(string $msg = '', $ended = false): void return; } - $now = \microtime(true); + $now = microtime(true); if (null === $lastTime || ($lastTime < $now - 0.1)) { $lastTime = $now; @@ -403,7 +425,7 @@ public static function spinner(string $msg = '', $ended = false): void printf($tpl, $chars[$counter] . $msg); $counter++; - if ($counter > \strlen($chars) - 1) { + if ($counter > strlen($chars) - 1) { $counter = 0; } } @@ -445,14 +467,14 @@ public static function pending(string $msg = 'Pending ', $ended = false): void return; } - $now = \microtime(true); + $now = microtime(true); if (null === $lastTime || ($lastTime < $now - 0.8)) { $lastTime = $now; printf($tpl, $msg . $chars[$counter]); $counter++; - if ($counter > \count($chars) - 1) { + if ($counter > count($chars) - 1) { $counter = 0; } } @@ -494,9 +516,9 @@ public static function pointing(string $msg = 'handling ', $ended = false) * * @param string $msg * @param string|null $doneMsg - * @return \Generator + * @return Generator */ - public static function counterTxt(string $msg, $doneMsg = ''): \Generator + public static function counterTxt(string $msg, $doneMsg = ''): Generator { return CounterText::gen($msg, $doneMsg); } @@ -504,9 +526,9 @@ public static function counterTxt(string $msg, $doneMsg = ''): \Generator /** * @param string $doneMsg * @param string|null $fixMsg - * @return \Generator + * @return Generator */ - public static function dynamicTxt(string $doneMsg, string $fixMsg = null): \Generator + public static function dynamicTxt(string $doneMsg, string $fixMsg = null): Generator { return self::dynamicText($doneMsg, $fixMsg); } @@ -514,9 +536,9 @@ public static function dynamicTxt(string $doneMsg, string $fixMsg = null): \Gene /** * @param string $doneMsg * @param string|null $fixedMsg - * @return \Generator + * @return Generator */ - public static function dynamicText(string $doneMsg, string $fixedMsg = null): \Generator + public static function dynamicText(string $doneMsg, string $fixedMsg = null): Generator { return DynamicText::gen($doneMsg, $fixedMsg); } @@ -526,9 +548,9 @@ public static function dynamicText(string $doneMsg, string $fixedMsg = null): \G * @param int $total * @param string $msg * @param string $doneMsg - * @return \Generator + * @return Generator */ - public static function progressTxt(int $total, string $msg, string $doneMsg = ''): \Generator + public static function progressTxt(int $total, string $msg, string $doneMsg = ''): Generator { return SimpleTextBar::gen($total, $msg, $doneMsg); } @@ -538,9 +560,9 @@ public static function progressTxt(int $total, string $msg, string $doneMsg = '' * @param int $total * @param array $opts * @internal int $current - * @return \Generator + * @return Generator */ - public static function progressBar(int $total, array $opts = []): ?\Generator + public static function progressBar(int $total, array $opts = []): ?Generator { return SimpleBar::gen($total, $opts); } @@ -560,7 +582,7 @@ public static function progressBar(int $total, array $opts = []): ?\Generator * @param int $max * @param bool $start * @return ProgressBar - * @throws \LogicException + * @throws LogicException */ public static function createProgressBar($max = 0, $start = true): ProgressBar { @@ -675,7 +697,7 @@ public static function flushBuffer($nl = false, $quit = false, array $opts = []) */ public static function writef(string $format, ...$args): int { - return self::write(\sprintf($format, ...$args)); + return self::write(sprintf($format, ...$args)); } /** @@ -734,8 +756,8 @@ public static function colored($message, string $style = 'info', $nl = true, arr { $quit = isset($opts['quit']) ? (bool)$opts['quit'] : false; - if (\is_array($message)) { - $message = \implode($nl ? \PHP_EOL : '', $message); + if (is_array($message)) { + $message = implode($nl ? PHP_EOL : '', $message); } return self::write(ColorTag::wrap($message, $style), $nl, $quit, $opts); @@ -747,7 +769,7 @@ public static function colored($message, string $style = 'info', $nl = true, arr */ public static function getBlockMethods($onlyKey = true): array { - return $onlyKey ? \array_keys(self::$blockMethods) : self::$blockMethods; + return $onlyKey ? array_keys(self::$blockMethods) : self::$blockMethods; } /** diff --git a/test/ApplicationTest.php b/test/ApplicationTest.php index 86d0fe2f..2d7606a2 100644 --- a/test/ApplicationTest.php +++ b/test/ApplicationTest.php @@ -7,6 +7,7 @@ use Inhere\Console\IO\Input; use Inhere\Console\IO\InputInterface; use Inhere\Console\Router; +use InvalidArgumentException; use PHPUnit\Framework\TestCase; class ApplicationTest extends TestCase @@ -55,11 +56,11 @@ public function testAddCommandError(): void { $app = $this->newApp(); - $this->expectException(\InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessageRegExp("/'name' and 'handler' cannot be empty/"); $app->addCommand(''); - $this->expectException(\InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessageRegExp('/"name" and "controller" cannot be empty/'); $app->addCommand('test', 'invalid'); } @@ -100,11 +101,11 @@ public function testAddControllerError(): void { $app = $this->newApp(); - $this->expectException(\InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessageRegExp('/"name" and "controller" cannot be empty/'); $app->addController(''); - $this->expectException(\InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessageRegExp('/"name" and "controller" cannot be empty/'); $app->controller('test', 'invalid'); } From dd651ef89ebeb937ed830a2bf40c55f89b329a21 Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 7 Aug 2019 00:27:57 +0800 Subject: [PATCH 002/258] update some logic for app. add more test for run command --- src/AbstractApplication.php | 25 ++++++++--- src/Application.php | 37 +++++++++++----- src/Contract/RouterInterface.php | 16 ++++--- src/IO/Input.php | 76 +++++++++++++++++++++++++------- src/Router.php | 3 +- test/ApplicationTest.php | 17 +++++++ test/TestCommand.php | 7 ++- test/TestController.php | 2 + 8 files changed, 140 insertions(+), 43 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 375802fb..6803d4bd 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -39,6 +39,7 @@ /** * Class AbstractApplication + * * @package Inhere\Console */ abstract class AbstractApplication implements ApplicationInterface @@ -107,9 +108,11 @@ abstract class AbstractApplication implements ApplicationInterface /** * Class constructor. + * * @param array $config * @param Input $input * @param Output $output + * * @throws InvalidArgumentException */ public function __construct(array $config = [], Input $input = null, Output $output = null) @@ -181,7 +184,9 @@ protected function beforeRun(): void /** * run application + * * @param bool $exit + * * @return int|mixed * @throws InvalidArgumentException */ @@ -251,6 +256,7 @@ public function stop(int $code = 0) * @param string $command * @param InputInterface $input * @param OutputInterface $output + * * @return int|mixed */ public function subRun(string $command, InputInterface $input, OutputInterface $output) @@ -274,14 +280,13 @@ protected function runtimeCheck(): void // check env if (!in_array(PHP_SAPI, ['cli', 'phpdbg', 'cli-server'], true)) { header('HTTP/1.1 403 Forbidden'); - exit(" 403 Forbidden \n\n" - . " current environment is CLI. \n" - . " :( Sorry! Run this script is only allowed in the terminal environment!\n,You are not allowed to access this file.\n"); + exit(" 403 Forbidden \n\n" . " current environment is CLI. \n" . " :( Sorry! Run this script is only allowed in the terminal environment!\n,You are not allowed to access this file.\n"); } } /** * register error handle + * * @throws InvalidArgumentException */ protected function registerErrorHandle(): void @@ -296,11 +301,13 @@ protected function registerErrorHandle(): void } /** - * 运行异常处理 + * Running error handling + * * @param int $num * @param string $str * @param string $file * @param int $line + * * @throws InvalidArgumentException */ public function handleError(int $num, string $str, string $file, int $line): void @@ -311,18 +318,20 @@ public function handleError(int $num, string $str, string $file, int $line): voi /** * Running exception handling + * * @param Throwable $e + * * @throws InvalidArgumentException */ public function handleException($e): void { // you can log error on sub class ... - $this->errorHandler->handle($e, $this); } /** * @param string $command + * * @return bool True will stop run, False will goon run give command. */ protected function filterSpecialCommand(string $command): bool @@ -365,6 +374,7 @@ protected function filterSpecialCommand(string $command): bool /** * @param string $name * @param string|array $aliases + * * @return $this */ public function addAliases(string $name, $aliases): self @@ -449,6 +459,7 @@ public function getInternalCommands(): array /** * @param $name + * * @return bool */ public function isInternalCommand(string $name): bool @@ -500,8 +511,10 @@ public function getConfig(): array /** * Get config param value + * * @param null|string $name * @param null|string $default + * * @return array|string */ public function getParam(string $name, $default = null) @@ -519,6 +532,7 @@ public function isStrictMode(): bool /** * get current debug level value + * * @return int */ public function getVerbLevel(): int @@ -528,6 +542,7 @@ public function getVerbLevel(): int /** * is profile + * * @return boolean */ public function isProfile(): bool diff --git a/src/Application.php b/src/Application.php index afa0b44c..378cd376 100644 --- a/src/Application.php +++ b/src/Application.php @@ -25,6 +25,7 @@ /** * Class App + * * @package Inhere\Console */ class Application extends AbstractApplication @@ -50,7 +51,8 @@ public function controller(string $name, $class = null, $option = null) } /** - * add group/controller + * Add group/controller + * * @inheritdoc * @see controller() */ @@ -61,8 +63,8 @@ public function addGroup(string $name, $controller = null, $option = null) /** * {@inheritdoc} - * @see controller() * @throws InvalidArgumentException + * @see controller() */ public function addController(string $name, $class = null, $option = null) { @@ -71,6 +73,7 @@ public function addController(string $name, $class = null, $option = null) /** * @param array $controllers + * * @throws InvalidArgumentException */ public function controllers(array $controllers): void @@ -80,6 +83,7 @@ public function controllers(array $controllers): void /** * @param array $controllers + * * @throws InvalidArgumentException * @deprecated please use addControllers() instead it. */ @@ -90,6 +94,7 @@ public function setControllers(array $controllers): void /** * @param array $controllers + * * @throws InvalidArgumentException */ public function addControllers(array $controllers): void @@ -115,6 +120,7 @@ public function command(string $name, $handler = null, $option = null) /** * add command + * * @inheritdoc * @see command() */ @@ -125,6 +131,7 @@ public function addCommand(string $name, $handler = null, $option = null): self /** * @param array $commands + * * @throws InvalidArgumentException */ public function addCommands(array $commands): void @@ -134,6 +141,7 @@ public function addCommands(array $commands): void /** * @param array $commands + * * @throws InvalidArgumentException */ public function commands(array $commands): void @@ -150,6 +158,7 @@ public function commands(array $commands): void * * @param string $namespace * @param string $basePath + * * @return $this * @throws InvalidArgumentException */ @@ -168,8 +177,10 @@ public function registerCommands(string $namespace, string $basePath): self /** * auto register controllers from a dir. + * * @param string $namespace * @param string $basePath + * * @return $this * @throws InvalidArgumentException */ @@ -241,6 +252,9 @@ public function dispatch(string $name, bool $standAlone = false) return 2; } + // save command ID + $this->input->setCommandId($info['cmdId']); + // is command if ($info['type'] === Router::TYPE_SINGLE) { return $this->runCommand($info['name'], $info['handler'], $info['options']); @@ -252,9 +266,11 @@ public function dispatch(string $name, bool $standAlone = false) /** * run a independent command - * @param string $name Command name + * + * @param string $name Command name * @param Closure|string $handler Command class - * @param array $options + * @param array $options + * * @return mixed * @throws InvalidArgumentException */ @@ -290,11 +306,13 @@ protected function runCommand(string $name, $handler, array $options) /** * Execute an action in a group command(controller) - * @param string $group The group name - * @param string $action Command method, no suffix + * + * @param string $group The group name + * @param string $action Command method, no suffix * @param mixed $handler The controller class or object * @param array $options * @param bool $standAlone + * * @return mixed * @throws ReflectionException */ @@ -312,11 +330,8 @@ protected function runAction(string $group, string $action, $handler, array $opt } if (!($handler instanceof Controller)) { - Helper::throwInvalidArgument( - 'The console controller class [%s] must instanceof the %s', - $handler, - Controller::class - ); + Helper::throwInvalidArgument('The console controller class [%s] must instanceof the %s', $handler, + Controller::class); } $handler::setName($group); diff --git a/src/Contract/RouterInterface.php b/src/Contract/RouterInterface.php index 33aae5b4..4fe82e5e 100644 --- a/src/Contract/RouterInterface.php +++ b/src/Contract/RouterInterface.php @@ -26,12 +26,13 @@ interface RouterInterface /** * Register a app group command(by controller) - * @param string $name The controller name - * @param string|ControllerInterface $class The controller class - * @param array $options - * array: - * - aliases The command aliases - * - description The description message + * + * @param string $name The controller name + * @param string|ControllerInterface $class The controller class + * @param array $options array: + * - aliases The command aliases + * - description The description message + * * @return static * @throws InvalidArgumentException */ @@ -43,7 +44,7 @@ public function addGroup(string $name, $class = null, array $options = []): self * @param string|CommandInterface $name * @param string|Closure|CommandInterface $handler * @param array $options - * array: + * array: * - aliases The command aliases * - description The description message * @@ -54,6 +55,7 @@ public function addCommand(string $name, $handler = null, array $options = []): /** * @param string $name The input command name + * * @return array return route info array. If not found, will return empty array. * [ * type => 1, // 1 group 2 command diff --git a/src/IO/Input.php b/src/IO/Input.php index 778463d1..54abf75d 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -8,23 +8,32 @@ namespace Inhere\Console\IO; +use Toolkit\Cli\Flags; use function array_map; use function array_shift; use function fgets; use function fwrite; use function implode; use function preg_match; +use function trim; use const STDIN; use const STDOUT; -use Toolkit\Cli\Flags; -use function trim; /** * Class Input - The input information. by parse global var $argv. + * * @package Inhere\Console\IO */ class Input extends AbstractInput { + /** + * The real command ID(group:command) + * e.g `http:start` + * + * @var string + */ + protected $commandId = ''; + /** * @var resource */ @@ -32,6 +41,7 @@ class Input extends AbstractInput /** * Input constructor. + * * @param null|array $args * @param bool $parsing */ @@ -41,9 +51,9 @@ public function __construct(array $args = null, bool $parsing = true) $args = (array)$_SERVER['argv']; } - $this->pwd = $this->getPwd(); - $this->tokens = $args; - $this->script = array_shift($args); + $this->pwd = $this->getPwd(); + $this->tokens = $args; + $this->script = array_shift($args); $this->fullScript = implode(' ', $args); if ($parsing) { @@ -60,25 +70,35 @@ public function __construct(array $args = null, bool $parsing = true) */ public function toString(): string { - $tokens = array_map(function ($token) { - if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { - return $match[1] . Flags::escapeToken($match[2]); - } + $tokens = array_map([$this, 'tokenEscape'], $this->tokens); - if ($token && $token[0] !== '-') { - return Flags::escapeToken($token); - } + return implode(' ', $tokens); + } - return $token; - }, $this->tokens); + /** + * @param string $token + * + * @return string + */ + protected function tokenEscape(string $token): string + { + if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { + return $match[1] . Flags::escapeToken($match[2]); + } - return implode(' ', $tokens); + if ($token && $token[0] !== '-') { + return Flags::escapeToken($token); + } + + return $token; } /** * Read input information - * @param string $question 若不为空,则先输出文本消息 - * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 + * + * @param string $question 若不为空,则先输出文本消息 + * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 + * * @return string */ public function read(string $question = '', bool $nl = false): string @@ -125,4 +145,26 @@ public function getInputStream() { return $this->inputStream; } + + /** + * Get command ID e.g `http:start` + * + * @return string + */ + public function getCommandId(): string + { + return $this->commandId; + } + + /** + * Set command ID e.g `http:start` + * + * @param string $commandId e.g `http:start` + * + * @return void + */ + public function setCommandId(string $commandId): void + { + $this->commandId = $commandId; + } } diff --git a/src/Router.php b/src/Router.php index c30a0a34..a70c241e 100644 --- a/src/Router.php +++ b/src/Router.php @@ -271,7 +271,7 @@ public function match(string $name): array // is a command name if ($route = $this->commands[$realName] ?? []) { - $route['name'] = $realName; + $route['name'] = $route['cmdId'] = $realName; return $route; } @@ -293,6 +293,7 @@ public function match(string $name): array $route['name'] = $realName; $route['group'] = $group; $route['action'] = $action; + $route['cmdId'] = $group . $sep . $action; return $route; } diff --git a/test/ApplicationTest.php b/test/ApplicationTest.php index 2d7606a2..34931b26 100644 --- a/test/ApplicationTest.php +++ b/test/ApplicationTest.php @@ -122,4 +122,21 @@ public function testRunController(): void $ret = $app->run(false); $this->assertSame('Inhere\ConsoleTest\TestController::demoCommand', $ret); } + + public function testTriggerEvent(): void + { + $app = $this->newApp([ + './app', + 'test1' + ]); + + $app->on(Application::ON_BEFORE_RUN, function (Application $app) { + $this->assertEquals('Tests', $app->getName()); + }); + + $app->addCommand('test1', TestCommand::class); + + $ret = $app->run(false); + $this->assertSame('Inhere\ConsoleTest\TestCommand::execute', $ret); + } } diff --git a/test/TestCommand.php b/test/TestCommand.php index 95e864de..63f1d7eb 100644 --- a/test/TestCommand.php +++ b/test/TestCommand.php @@ -14,6 +14,7 @@ /** * Class TestCommand + * * @package Inhere\ConsoleTest */ class TestCommand extends Command @@ -23,8 +24,10 @@ class TestCommand extends Command /** * do execute command - * @param Input $input - * @param Output $output + * + * @param Input $input + * @param Output $output + * * @return int|mixed */ protected function execute($input, $output) diff --git a/test/TestController.php b/test/TestController.php index c8688284..998b3a38 100644 --- a/test/TestController.php +++ b/test/TestController.php @@ -12,6 +12,7 @@ /** * Class TestController + * * @package Inhere\ConsoleTest */ class TestController extends Controller @@ -21,6 +22,7 @@ class TestController extends Controller /** * this is an demo command in test + * * @return mixed */ public function demoCommand() From 51c05ec060705249df9d529aba9c8f0340d15b67 Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 7 Aug 2019 00:44:27 +0800 Subject: [PATCH 003/258] update: limit phpunit version to 7.5 --- .travis.yml | 6 +++--- composer.json | 3 +++ test/boot.php | 2 ++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 367cdaf6..c5a8ee0d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: php php: - - 7.1 - - 7.2 - - 7.3 + - '7.1' + - '7.2' + - '7.3' #matrix: # include: diff --git a/composer.json b/composer.json index 9a665313..c99af64a 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,9 @@ "toolkit/php-utils": "~1.0", "toolkit/sys-utils": "~1.0" }, + "require-dev": { + "phpunit/phpunit": "^7.5" + }, "autoload": { "psr-4": { "Inhere\\Console\\": "src/" diff --git a/test/boot.php b/test/boot.php index 222d44c1..b5e02ea0 100644 --- a/test/boot.php +++ b/test/boot.php @@ -29,4 +29,6 @@ if (is_file(dirname(__DIR__, 3) . '/autoload.php')) { require dirname(__DIR__, 3) . '/autoload.php'; +} elseif (is_file(dirname(__DIR__) . '/vendor/autoload.php')) { + require dirname(__DIR__) . '/vendor/autoload.php'; } From 6be600906dc1b7b728a90a5cf4482e90a8b7dc41 Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 7 Aug 2019 10:07:43 +0800 Subject: [PATCH 004/258] formar some codes. use phpunit 7.5 for run ci test --- .travis.yml | 3 ++- src/Component/ConsoleRenderer.php | 3 ++- src/Component/Interact/Checkbox.php | 4 ++-- src/Component/Interact/Choose.php | 4 ++-- src/Component/Interact/Password.php | 6 +++--- src/Component/Progress/DynamicText.php | 2 +- src/Component/Progress/SimpleBar.php | 4 ++-- src/Component/Style/Color.php | 4 ++-- src/Component/Style/Style.php | 6 +++--- src/Component/Symbol/ArtFont.php | 6 ++---- src/IO/Output.php | 2 +- 11 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.travis.yml b/.travis.yml index c5a8ee0d..e4ec624b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,8 @@ before_script: - composer require php-coveralls/php-coveralls:^2.1.0 script: - - phpunit --coverage-clover clover.xml +# - phpunit --coverage-clover clover.xml + - php vendor/bin/phpunit --coverage-clover clover.xml after_success: - vendor/bin/php-coveralls --coverage_clover=clover.xml --json_path=coveralls-upload.json -v diff --git a/src/Component/ConsoleRenderer.php b/src/Component/ConsoleRenderer.php index 4220740b..fbcd2478 100644 --- a/src/Component/ConsoleRenderer.php +++ b/src/Component/ConsoleRenderer.php @@ -8,10 +8,11 @@ namespace Inhere\Console\Component; -use Inhere\Console\Component\Formatter\FormatterInterface; +use Inhere\Console\Contract\FormatterInterface; /** * Class ConsoleRenderer + * * @package Inhere\Console\Component */ class ConsoleRenderer diff --git a/src/Component/Interact/Checkbox.php b/src/Component/Interact/Checkbox.php index af5af6a1..314d594b 100644 --- a/src/Component/Interact/Checkbox.php +++ b/src/Component/Interact/Checkbox.php @@ -2,11 +2,11 @@ namespace Inhere\Console\Component\Interact; -use function array_filter; -use function explode; use Inhere\Console\Component\InteractMessage; use Inhere\Console\Console; use Inhere\Console\Util\Show; +use function array_filter; +use function explode; use function is_array; use function str_replace; use function strpos; diff --git a/src/Component/Interact/Choose.php b/src/Component/Interact/Choose.php index 22cc0458..d29a0677 100644 --- a/src/Component/Interact/Choose.php +++ b/src/Component/Interact/Choose.php @@ -2,11 +2,11 @@ namespace Inhere\Console\Component\Interact; -use function array_key_exists; -use function explode; use Inhere\Console\Component\InteractMessage; use Inhere\Console\Console; use Inhere\Console\Util\Show; +use function array_key_exists; +use function explode; use function is_array; use function trim; diff --git a/src/Component/Interact/Password.php b/src/Component/Interact/Password.php index ebc47d5c..5d9778dc 100644 --- a/src/Component/Interact/Password.php +++ b/src/Component/Interact/Password.php @@ -2,15 +2,15 @@ namespace Inhere\Console\Component\Interact; +use Inhere\Console\Component\InteractMessage; +use RuntimeException; +use Toolkit\Sys\Sys; use function addslashes; use function escapeshellarg; use function file_put_contents; -use Inhere\Console\Component\InteractMessage; use function rtrim; -use RuntimeException; use function shell_exec; use function sprintf; -use Toolkit\Sys\Sys; use function unlink; /** diff --git a/src/Component/Progress/DynamicText.php b/src/Component/Progress/DynamicText.php index e3e07cbb..9b2088d2 100644 --- a/src/Component/Progress/DynamicText.php +++ b/src/Component/Progress/DynamicText.php @@ -5,8 +5,8 @@ use Generator; use Inhere\Console\Component\NotifyMessage; use Inhere\Console\Console; -use function printf; use Toolkit\Cli\Cli; +use function printf; /** * Class DynamicText diff --git a/src/Component/Progress/SimpleBar.php b/src/Component/Progress/SimpleBar.php index 59a174e4..c7498282 100644 --- a/src/Component/Progress/SimpleBar.php +++ b/src/Component/Progress/SimpleBar.php @@ -2,12 +2,12 @@ namespace Inhere\Console\Component\Progress; -use function array_merge; -use function ceil; use Generator; use Inhere\Console\Component\NotifyMessage; use Inhere\Console\Console; use Toolkit\Cli\Cli; +use function array_merge; +use function ceil; /** * Class SimpleBar diff --git a/src/Component/Style/Color.php b/src/Component/Style/Color.php index 99f4bbc0..f21ca471 100644 --- a/src/Component/Style/Color.php +++ b/src/Component/Style/Color.php @@ -5,13 +5,13 @@ namespace Inhere\Console\Component\Style; +use InvalidArgumentException; +use RuntimeException; use function array_key_exists; use function array_keys; use function count; use function explode; use function implode; -use InvalidArgumentException; -use RuntimeException; use function str_replace; /** diff --git a/src/Component/Style/Style.php b/src/Component/Style/Style.php index b53c2074..ec307188 100644 --- a/src/Component/Style/Style.php +++ b/src/Component/Style/Style.php @@ -10,18 +10,18 @@ namespace Inhere\Console\Component\Style; +use InvalidArgumentException; +use Toolkit\Cli\Cli; +use Toolkit\Cli\ColorTag; use function array_key_exists; use function array_keys; use function array_merge; use function array_values; -use InvalidArgumentException; use function is_array; use function is_object; use function sprintf; use function str_replace; use function strpos; -use Toolkit\Cli\Cli; -use Toolkit\Cli\ColorTag; /** * Class Style diff --git a/src/Component/Symbol/ArtFont.php b/src/Component/Symbol/ArtFont.php index 511d03d6..21fc784a 100644 --- a/src/Component/Symbol/ArtFont.php +++ b/src/Component/Symbol/ArtFont.php @@ -8,14 +8,12 @@ namespace Inhere\Console\Component\Symbol; +use Inhere\Console\Console; +use Toolkit\Cli\ColorTag; use function dirname; use function file_get_contents; use function in_array; -use Inhere\Console\Console; -use Inhere\Console\Util\Helper; -use Inhere\Console\Util\Show; use function is_file; -use Toolkit\Cli\ColorTag; /** * Class ArtFont art fonts Manager diff --git a/src/IO/Output.php b/src/IO/Output.php index fbfe8a35..5ba86ba5 100644 --- a/src/IO/Output.php +++ b/src/IO/Output.php @@ -12,9 +12,9 @@ use Inhere\Console\Console; use Inhere\Console\Traits\FormatOutputAwareTrait; use Inhere\Console\Util\Show; +use Toolkit\Cli\Cli; use const STDERR; use const STDOUT; -use Toolkit\Cli\Cli; /** * Class Output From 9a7458fa141ae3edc6d40486e27affad89f80801 Mon Sep 17 00:00:00 2001 From: Luffy <52o@qq52o.cn> Date: Fri, 18 Oct 2019 18:37:00 +0800 Subject: [PATCH 005/258] Update FormatOutputAwareTrait.php --- src/Traits/FormatOutputAwareTrait.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Traits/FormatOutputAwareTrait.php b/src/Traits/FormatOutputAwareTrait.php index 9293e78c..45095f57 100644 --- a/src/Traits/FormatOutputAwareTrait.php +++ b/src/Traits/FormatOutputAwareTrait.php @@ -192,9 +192,9 @@ public function panel(array $data, $title = 'Information panel', array $opts = [ * @inheritdoc * @see Show::table() */ - public function table(array $data, $title = 'Data Table', $showBorder = true): void + public function table(array $data, $title = 'Data Table', array $opts = []): void { - Show::table($data, $title, ['showBorder' => $showBorder]); + Show::table($data, $title, $opts); } /** From edd0445e45f6d341633528d2a3955d62dec2c877 Mon Sep 17 00:00:00 2001 From: Wang Ningkai Date: Thu, 31 Oct 2019 09:03:46 +0800 Subject: [PATCH 006/258] Update Show.php fix Undefined variable: upType --- src/Util/Show.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Util/Show.php b/src/Util/Show.php index b1d46cbf..2544e6e8 100644 --- a/src/Util/Show.php +++ b/src/Util/Show.php @@ -135,11 +135,12 @@ public static function liteBlock( // format type if ($type) { + $upType = strtoupper($type); // add style if ($style && $color->hasStyle($style)) { $fmtType = sprintf('<%s>[%s] ', $style, $upType, $style); } else { - $fmtType = sprintf('[%s]', $upType = strtoupper($type)); + $fmtType = sprintf('[%s]', $upType); } } From dace994f44a15831fd9a44c6eed13d3c9f707ba9 Mon Sep 17 00:00:00 2001 From: Wang Ningkai Date: Sat, 9 Nov 2019 22:40:02 +0800 Subject: [PATCH 007/258] fix get version error fix error detail : ``` [ERROR] An error occurred! MESSAGE: Argument 2 passed to Inhere\Console\Util\Show::aList() must be of the type string, null given, called in E:\Dev\Code\New\php-redis-cli\vendor\inhere\console\src\Traits\FormatOutputAwareTrait.php on line 150 ``` --- src/Traits/ApplicationHelpTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Traits/ApplicationHelpTrait.php b/src/Traits/ApplicationHelpTrait.php index 87f42778..23543f89 100644 --- a/src/Traits/ApplicationHelpTrait.php +++ b/src/Traits/ApplicationHelpTrait.php @@ -71,7 +71,7 @@ public function showVersionInfo(): void "$logo\n {$name}, Version $version\n", 'System Info' => "PHP version $phpVersion, on $os system", 'Application Info' => "Update at $updateAt, publish at $publishAt(current $date)", - ], null, [ + ], '', [ 'leftChar' => '', 'sepChar' => ' : ' ]); From e2548fbd17f9276e342df4ebd32bde7279e6a978 Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 29 Apr 2020 10:23:52 +0800 Subject: [PATCH 008/258] update some --- README_cn.md | 8 ++++---- docs/screenshots/cli-text-progress.gif | Bin 0 -> 1585397 bytes src/Application.php | 24 ++++++++++++++++++------ 3 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 docs/screenshots/cli-text-progress.gif diff --git a/README_cn.md b/README_cn.md index db392291..de92bdc9 100644 --- a/README_cn.md +++ b/README_cn.md @@ -59,10 +59,10 @@ composer require inhere/console - **[错误/异常捕获](https://github.com/inhere/php-console/wiki/error-handle)** - **[输入对象](https://github.com/inhere/php-console/wiki/input-instance)** - **[输出对象](https://github.com/inhere/php-console/wiki/output-instance)** -- **[格式化输出](https://github.com/inhere/php-console/wiki/format-output)** -- **[进度动态输出](https://github.com/inhere/php-console/wiki/process-output)** -- **[用户交互](https://github.com/inhere/php-console/wiki/user-interactive)** -- **[扩展工具](https://github.com/inhere/php-console/wiki/extra-tools)** +- **[格式化输出](https://github.com/inhere/php-console/wiki/format-output)** `section`, `panel`, `padding`, `helpPanel`, `table`, `tree`, `title`, `list`, `multiList` 等风格信息输出 +- **[进度动态输出](https://github.com/inhere/php-console/wiki/process-output)** 动态信息显示(`pending/loading`, `pointing`, `spinner`, `counterTxt`, `dynamicText`, `progressTxt`, `progressBar`) +- **[用户交互](https://github.com/inhere/php-console/wiki/user-interactive)** 用户信息交互支持(`select`, `multiSelect`, `confirm`, `ask/question`, `askPassword/askHiddenInput`) +- **[扩展工具](https://github.com/inhere/php-console/wiki/extra-tools)** 打包phar, 命令行下的文件下载, 命令行的php代码高亮 ## 项目地址 diff --git a/docs/screenshots/cli-text-progress.gif b/docs/screenshots/cli-text-progress.gif new file mode 100644 index 0000000000000000000000000000000000000000..89292202d529c5dd02aba9f858e5d9b02ab2ab93 GIT binary patch literal 1585397 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≧j|TWd4x^n-|50FaK_`PE zca1346gSeXkUeHM6;}phAwSSl(6C9rn;4Ke1%TGn8wIt{{}E*QD>OMF^AO`mfnzFI zT!nkVzFt7~l_Dhuy~2?J*ia8W50IV^pD|F<85%QDj74foP+@ME@!OfLLC55C$?1N=)fGG`jEN7x0WlZ6#gk=HA~ES{(;oXYtXxBG5%T9a4^DQnJGO{5X|Jk(g2ogae+~g z8)TDV$UpCq0qMm&DiuX3F5<96Hqu-Qa>>wg&??Jbi4B)_o)#qYxMJ{jP80yq0({QO z9SXTrmGtt;t;YH-jFWG2(qP{@I}*Ssh^d5 z8H{h3(5h=eI)zHi@S+NAw(k0;k5S?u*w*-m@tFg_rzrYKyn}B&`fg`G9FEkBCW|nKZhb(JY7P*V+ zw@i4M4W%*sSVw>|QCn4^ zU=K#M7%ZrHL08Eu^3!l|F1YmADFNoA;<#Ze`)>Y3UJkl&#r!gYYv2=699)wKNoXl| z_M{6f#xu=4kor&DJBH{`;5M7t@)rF`nt%B8qRzrr^jQ|wu}&d*02TXqAJ0c|ga%R< zVqA2n^!2~UuAb;X#0?@E5>=I@fJf4WOcn7IhOTJ zbNHlxVr`t*Sx>2V!Z`w_Mz*-

EszC~4pY4w)h^n|Bg1f=lG(j)MTs04iPAl|T>` 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 z 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%0D1Yr!|qDbV_-Qct&$o3%;V&)0|0omdZR-OT10p| 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

K_=uh(0ptq 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�I|TrS-;p!dZX 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%0AZ8xj-aL(2TU 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�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 HoEQEdnhxZj literal 0 HcmV?d00001 diff --git a/src/Application.php b/src/Application.php index 378cd376..c88414e2 100644 --- a/src/Application.php +++ b/src/Application.php @@ -9,6 +9,7 @@ namespace Inhere\Console; use Closure; +use Inhere\Console\Contract\ControllerInterface; use Inhere\Console\Util\Helper; use InvalidArgumentException; use ReflectionException; @@ -53,17 +54,24 @@ public function controller(string $name, $class = null, $option = null) /** * Add group/controller * - * @inheritdoc + * @param string $name + * @param string|ControllerInterface $class The controller class + * @param null|array|string $option + * + * @return Application|Contract\ApplicationInterface * @see controller() */ - public function addGroup(string $name, $controller = null, $option = null) + public function addGroup(string $name, $class = null, $option = null) { - return $this->controller($name, $controller, $option); + return $this->controller($name, $class, $option); } /** - * {@inheritdoc} - * @throws InvalidArgumentException + * @param string $name + * @param string|ControllerInterface $class The controller class + * @param null|array|string $option + * + * @return Application|Contract\ApplicationInterface * @see controller() */ public function addController(string $name, $class = null, $option = null) @@ -121,7 +129,11 @@ public function command(string $name, $handler = null, $option = null) /** * add command * - * @inheritdoc + * @param string $name + * @param null $handler + * @param null $option + * + * @return Application * @see command() */ public function addCommand(string $name, $handler = null, $option = null): self From fdede619749c1e7575575ae8855e232e1fb99d85 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 1 May 2020 13:35:53 +0800 Subject: [PATCH 009/258] format codes --- .php_cs | 25 ++++++ src/AbstractHandler.php | 51 ++++++----- src/Application.php | 4 +- src/BuiltIn/DevServerCommand.php | 18 ++-- src/BuiltIn/PharController.php | 41 +++++---- src/BuiltIn/SelfUpdateCommand.php | 90 ++++++------------- src/Command.php | 2 + src/Component/Animation/Animation.php | 1 + src/Component/AutoCompDumper.php | 1 + src/Component/ErrorHandler.php | 20 ++--- src/Component/Formatter/Alert.php | 1 + src/Component/Formatter/Block.php | 1 + src/Component/Formatter/Body.php | 1 + src/Component/Formatter/SingleList.php | 6 +- src/Component/Formatter/Table.php | 18 ++-- src/Component/Formatter/Title.php | 3 +- src/Component/Formatter/Tree.php | 6 +- src/Component/Interact/Checkbox.php | 2 + src/Component/Interact/Choose.php | 15 ++-- src/Component/Interact/Confirm.php | 5 +- src/Component/Interact/LimitedAsk.php | 13 +-- src/Component/Interact/Password.php | 11 ++- src/Component/Interact/Question.php | 19 ++-- src/Component/Interact/Terminal.php | 1 + src/Component/InteractMessage.php | 1 + src/Component/MessageFormatter.php | 3 + src/Component/NotifyMessage.php | 2 +- src/Component/PharCompiler.php | 79 ++++++++++------- src/Component/Progress/Bar.php | 1 + src/Component/Progress/CounterText.php | 2 + src/Component/Progress/DynamicText.php | 2 + src/Component/Progress/Pending.php | 1 + src/Component/Progress/SimpleBar.php | 13 ++- src/Component/Progress/SimpleTextBar.php | 3 + src/Component/Progress/Spinner.php | 1 + src/Component/Progress/TextBar.php | 1 + src/Component/Style/Color.php | 42 ++++----- src/Component/Style/Style.php | 100 ++++++++++++--------- src/Component/Symbol/ArtFont.php | 24 +++-- src/Console.php | 71 +++++++++------ src/Contract/ApplicationInterface.php | 22 +++-- src/Contract/CommandHandlerInterface.php | 5 +- src/Contract/CommandInterface.php | 1 + src/Contract/ControllerInterface.php | 1 + src/Contract/ErrorHandlerInterface.php | 3 +- src/Contract/FormatterInterface.php | 1 + src/Controller.php | 37 ++++---- src/IO/AbstractInput.php | 49 +++++++++- src/IO/Input.php | 2 +- src/IO/Input/ArrayInput.php | 10 ++- src/IO/Input/InputArgument.php | 1 + src/IO/Input/InputArguments.php | 1 + src/IO/Input/InputItem.php | 9 +- src/IO/Input/InputOption.php | 2 + src/IO/Input/InputOptions.php | 3 +- src/IO/InputDefinition.php | 62 +++++++++---- src/IO/InputInterface.php | 10 ++- src/IO/Output.php | 16 +++- src/IO/OutputInterface.php | 9 +- src/IO/StrictInput.php | 2 + src/Router.php | 36 +++++--- src/Traits/SimpleEventTrait.php | 2 +- src/Util/FormatUtil.php | 38 +++++--- src/Util/Helper.php | 40 +++++---- src/Util/Interact.php | 82 +++++++++++------ src/Util/ProgressBar.php | 56 ++++++++---- src/Util/Show.php | 108 ++++++++++++++++------- 67 files changed, 837 insertions(+), 472 deletions(-) create mode 100644 .php_cs diff --git a/.php_cs b/.php_cs new file mode 100644 index 00000000..e960b7fc --- /dev/null +++ b/.php_cs @@ -0,0 +1,25 @@ +setRiskyAllowed(true)->setRules([ + '@PSR2' => true, + // 'header_comment' => [ + // 'comment_type' => 'PHPDoc', + // 'header' => $header, + // 'separate' => 'none' + // ], + 'array_syntax' => [ + 'syntax' => 'short' + ], + 'single_quote' => true, + 'class_attributes_separation' => true, + 'no_unused_imports' => true, + 'standardize_not_equals' => true, + 'declare_strict_types' => true, + ])->setFinder(PhpCsFixer\Finder::create() + // ->exclude('test') + ->exclude('docs')->exclude('vendor')->in(__DIR__))->setUsingCache(false); diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index d93af976..ca61dae2 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -51,6 +51,7 @@ /** * Class AbstractHandler + * * @package Inhere\Console */ abstract class AbstractHandler implements CommandHandlerInterface @@ -59,6 +60,7 @@ abstract class AbstractHandler implements CommandHandlerInterface /** * command name e.g 'test' 'test:one' + * * @var string */ protected static $name = ''; @@ -66,6 +68,7 @@ abstract class AbstractHandler implements CommandHandlerInterface /** * command/controller description message * please use the property setting current controller/command description + * * @var string */ protected static $description = ''; @@ -77,6 +80,7 @@ abstract class AbstractHandler implements CommandHandlerInterface /** * Allow display message tags in the command annotation + * * @var array */ protected static $annotationTags = [ @@ -103,6 +107,7 @@ abstract class AbstractHandler implements CommandHandlerInterface /** * Whether enabled + * * @return bool */ public static function isEnabled(): bool @@ -112,6 +117,7 @@ public static function isEnabled(): bool /** * Setting current command/group name aliases + * * @return string[] */ public static function aliases(): array @@ -122,6 +128,7 @@ public static function aliases(): array /** * Command constructor. + * * @param Input $input * @param Output $output * @param InputDefinition|null $definition @@ -180,6 +187,7 @@ protected function createDefinition(): InputDefinition * ]); * } * ``` + * * @return array */ protected function annotationVars(): array @@ -202,7 +210,9 @@ protected function annotationVars(): array /** * run command + * * @param string $command + * * @return int|mixed * @throws RuntimeException * @throws InvalidArgumentException @@ -241,6 +251,7 @@ public function run(string $command = '') /** * coroutine run by swoole go() + * * @return bool */ public function coroutineRun(): bool @@ -271,6 +282,7 @@ public function coroutineRun(): bool /** * Before command execute + * * @return boolean It MUST return TRUE to continue execute. */ protected function beforeExecute(): bool @@ -280,8 +292,10 @@ protected function beforeExecute(): bool /** * Do execute command - * @param Input $input - * @param Output $output + * + * @param Input $input + * @param Output $output + * * @return int|mixed */ abstract protected function execute($input, $output); @@ -295,6 +309,7 @@ protected function afterExecute(): void /** * prepare run + * * @throws InvalidArgumentException * @throws RuntimeException */ @@ -317,6 +332,7 @@ protected function prepare(): bool /** * validate input arguments and options + * * @return bool * @throws InvalidArgumentException */ @@ -373,10 +389,8 @@ public function validateInput(): bool $names = array_keys($unknown); $first = array_shift($names); - throw new InvalidArgumentException(sprintf( - 'Input option is not exists (unknown: "%s").', - (isset($first[1]) ? '--' : '-') . $first - )); + throw new InvalidArgumentException(sprintf('Input option is not exists (unknown: "%s").', + (isset($first[1]) ? '--' : '-') . $first)); } foreach ($defOpts as $name => $conf) { @@ -390,10 +404,7 @@ public function validateInput(): bool } if (count($missingOpts) > 0) { - $out->liteError( - sprintf('Not enough options parameters (missing: "%s").', - implode(', ', $missingOpts)) - ); + $out->liteError(sprintf('Not enough options parameters (missing: "%s").', implode(', ', $missingOpts))); return false; } @@ -440,7 +451,9 @@ protected function setCommentsVar(string $name, $value): void /** * 替换注解中的变量为对应的值 + * * @param string $str + * * @return string */ protected function parseCommentsVars(string $str): string @@ -477,6 +490,7 @@ public function isAlone(): bool /** * Display help information + * * @return bool */ protected function showHelp(): bool @@ -488,12 +502,7 @@ protected function showHelp(): bool // if has InputDefinition object. (The comment of the command will not be parsed and used at this time.) $help = $definition->getSynopsis(); // build usage - $help['usage:'] = sprintf( - '%s %s %s', - $this->getScriptName(), - $this->getCommandName(), - $help['usage:'] - ); + $help['usage:'] = sprintf('%s %s %s', $this->getScriptName(), $this->getCommandName(), $help['usage:']); // align global options $help['global options:'] = FormatUtil::alignOptions(Application::getGlobalOptions()); @@ -515,9 +524,11 @@ protected function showHelp(): bool /** * Display command/action help by parse method annotations + * * @param string $method * @param string $action * @param array $aliases + * * @return int * @throws ReflectionException */ @@ -526,11 +537,8 @@ protected function showHelpByMethodAnnotations(string $method, string $action = $ref = new ReflectionClass($this); $name = $this->input->getCommand(); - $this->logf( - Console::VERB_CRAZY, - 'display help info for the method=%s, action=%s, class=%s', - $method, $action, static::class - ); + $this->logf(Console::VERB_CRAZY, 'display help info for the method=%s, action=%s, class=%s', $method, $action, + static::class); if (!$ref->hasMethod($method)) { $this->write("The command [$name] don't exist in the group: " . static::getName()); @@ -686,6 +694,7 @@ public static function setAnnotationTags(array $annotationTags, $replace = false /** * get current debug level value + * * @return int */ public function getVerbLevel(): int diff --git a/src/Application.php b/src/Application.php index c88414e2..0ae3f7ee 100644 --- a/src/Application.php +++ b/src/Application.php @@ -54,7 +54,7 @@ public function controller(string $name, $class = null, $option = null) /** * Add group/controller * - * @param string $name + * @param string $name * @param string|ControllerInterface $class The controller class * @param null|array|string $option * @@ -67,7 +67,7 @@ public function addGroup(string $name, $class = null, $option = null) } /** - * @param string $name + * @param string $name * @param string|ControllerInterface $class The controller class * @param null|array|string $option * diff --git a/src/BuiltIn/DevServerCommand.php b/src/BuiltIn/DevServerCommand.php index 6bdab3b8..8f1b2238 100644 --- a/src/BuiltIn/DevServerCommand.php +++ b/src/BuiltIn/DevServerCommand.php @@ -11,12 +11,13 @@ use Inhere\Console\Command; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; -use const PHP_VERSION; -use function strpos; use Toolkit\Sys\Sys; +use function strpos; +use const PHP_VERSION; /** * Class DevServerCommand + * * @package Inhere\Console\BuiltIn */ class DevServerCommand extends Command @@ -31,6 +32,7 @@ public static function aliases(): array /** * start a php built-in http server for development + * * @usage * {command} [-S HOST:PORT] * {command} [-H HOST] [-p PORT] @@ -43,11 +45,13 @@ public static function aliases(): array * -b,--php-bin STRING The php binary file(php) * @arguments * file=STRING The entry file for server. e.g web/index.php + * + * @param Input $in + * @param Output $out + * + * @return int|mixed|void * @example * {command} -S 127.0.0.1:8552 web/index.php - * @param Input $in - * @param Output $out - * @return int|mixed|void */ public function execute($in, $out) { @@ -56,13 +60,13 @@ public function execute($in, $out) } if (!strpos($server, ':')) { - $port = $this->getSameOpt(['p', 'port'], 8552); + $port = $this->getSameOpt(['p', 'port'], 8552); $server .= ':' . $port; } $version = PHP_VERSION; $workDir = $this->input->getPwd(); - $docDir = $this->getOpt('t'); + $docDir = $this->getOpt('t'); $docRoot = $docDir ? $workDir . '/' . $docDir : $workDir; $this->write([ diff --git a/src/BuiltIn/PharController.php b/src/BuiltIn/PharController.php index f6da71d4..24a12943 100644 --- a/src/BuiltIn/PharController.php +++ b/src/BuiltIn/PharController.php @@ -9,25 +9,26 @@ namespace Inhere\Console\BuiltIn; use BadMethodCallException; -use function basename; use Closure; use Exception; -use function file_exists; use Inhere\Console\Component\PharCompiler; use Inhere\Console\Controller; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; use Inhere\Console\Util\Helper; use Inhere\Console\Util\Show; +use RuntimeException; +use UnexpectedValueException; +use function basename; +use function file_exists; use function is_dir; use function is_file; use function microtime; use function realpath; -use RuntimeException; -use UnexpectedValueException; /** * Class PharController + * * @package Inhere\Console\BuiltIn */ class PharController extends Controller @@ -50,6 +51,7 @@ protected function init(): void /** * pack project to a phar package * @usage {fullCommand} [--dir DIR] [--output FILE] [...] + * * @options * -d, --dir STRING Setting the project directory for packing. * default is current work-dir.({workDir}) @@ -57,8 +59,10 @@ protected function init(): void * -o, --output STRING Setting the output file name({defaultPkgName}.phar) * --fast BOOL Fast build. only add modified files by git status -s * --refresh BOOL Whether build vendor folder files on phar file exists(False) - * @param Input $in - * @param Output $out + * + * @param Input $in + * @param Output $out + * * @return int * @throws UnexpectedValueException * @throws RuntimeException @@ -67,14 +71,14 @@ protected function init(): void */ public function packCommand($in, $out): int { - $time = microtime(1); + $time = microtime(1); $workDir = $in->getPwd(); $dir = $in->getOpt('dir') ?: $workDir; $cpr = $this->configCompiler($dir); - $counter = null; - $refresh = $in->boolOpt('refresh'); + $counter = null; + $refresh = $in->boolOpt('refresh'); $pharFile = $workDir . '/' . $in->sameOpt(['o', 'output'], basename($workDir) . '.phar'); // use fast build @@ -83,10 +87,7 @@ public function packCommand($in, $out): int $this->output->liteNote('Use fast build, will only pack changed or new files(from git status)'); } - $out->liteInfo( - "Now, will begin building phar package.\n from path: $workDir\n" . - " phar file: $pharFile" - ); + $out->liteInfo("Now, will begin building phar package.\n from path: $workDir\n" . " phar file: $pharFile"); $out->info('Pack file to Phar: '); $cpr->onError(function ($error) { @@ -126,6 +127,7 @@ public function packCommand($in, $out): int /** * @param string $dir + * * @return PharCompiler */ protected function configCompiler(string $dir): PharCompiler @@ -163,15 +165,18 @@ public function setCompilerConfiger(Closure $compilerConfiger): void /** * unpack a phar package to a directory * @usage {fullCommand} -f FILE [-d DIR] + * * @options * -f, --file STRING The packed phar file path * -d, --dir STRING The output dir on extract phar package. * -y, --yes BOOL Whether display goon tips message. * --overwrite BOOL Whether overwrite exists files on extract phar - * @example {fullCommand} -f myapp.phar -d var/www/app - * @param Input $in - * @param Output $out + * + * @param Input $in + * @param Output $out + * * @return int + * @example {fullCommand} -f myapp.phar -d var/www/app */ public function unpackCommand($in, $out): int { @@ -180,13 +185,13 @@ public function unpackCommand($in, $out): int } $basePath = $in->getPwd(); - $file = realpath($basePath . '/' . $path); + $file = realpath($basePath . '/' . $path); if (!file_exists($file)) { return $out->error("The phar file not exists. File: $file"); } - $dir = $in->getSameOpt(['d', 'dir']) ?: $basePath; + $dir = $in->getSameOpt(['d', 'dir']) ?: $basePath; $overwrite = $in->getBoolOpt('overwrite'); if (!is_dir($dir)) { diff --git a/src/BuiltIn/SelfUpdateCommand.php b/src/BuiltIn/SelfUpdateCommand.php index 9a97a9ec..c1dcc3b6 100644 --- a/src/BuiltIn/SelfUpdateCommand.php +++ b/src/BuiltIn/SelfUpdateCommand.php @@ -20,6 +20,7 @@ /** * Class SelfUpdateCommand + * * @package Inhere\Console\BuiltIn */ class SelfUpdateCommand extends Command @@ -42,13 +43,14 @@ class SelfUpdateCommand extends Command /** * Execute the command. + * * @param Input $input * @param Output $output */ protected function execute($input, $output) { $this->version = $this->getApp()->getVersion(); - $parser = new VersionParser; + $parser = new VersionParser; /** * Check for ancilliary options @@ -192,20 +194,14 @@ protected function update(Updater $updater): void if ($result) { $this->output->writeln('Humbug has been updated.'); - $this->output->writeln(sprintf( - 'Current version is: %s.', - $newVersion - )); - $this->output->writeln(sprintf( - 'Previous version was: %s.', - $oldVersion - )); + $this->output->writeln(sprintf('Current version is: %s.', + $newVersion)); + $this->output->writeln(sprintf('Previous version was: %s.', + $oldVersion)); } else { $this->output->writeln('Humbug is currently up to date.'); - $this->output->writeln(sprintf( - 'Current version is: %s.', - $oldVersion - )); + $this->output->writeln(sprintf('Current version is: %s.', + $oldVersion)); } } catch (Exception $e) { $this->output->writeln(sprintf('Error: %s', $e->getMessage())); @@ -240,10 +236,8 @@ protected function printAvailableUpdates(): void protected function printCurrentLocalVersion(): void { - $this->output->writeln(sprintf( - 'Your current local build version is: %s', - $this->version - )); + $this->output->writeln(sprintf('Your current local build version is: %s', + $this->version)); } protected function printCurrentStableVersion(): void @@ -266,18 +260,16 @@ protected function printVersion(Updater $updater): void $stability = 'stable'; if ($updater->getStrategy() instanceof ShaStrategy) { $stability = 'development'; - } elseif ($updater->getStrategy() instanceof GithubStrategy - && $updater->getStrategy()->getStability() === GithubStrategy::UNSTABLE) { + } elseif ($updater->getStrategy() instanceof GithubStrategy && $updater->getStrategy() + ->getStability() === GithubStrategy::UNSTABLE + ) { $stability = 'pre-release'; } try { if ($updater->hasUpdate()) { - $this->output->writeln(sprintf( - 'The current %s build available remotely is: %s', - $stability, - $updater->getNewVersion() - )); + $this->output->writeln(sprintf('The current %s build available remotely is: %s', + $stability, $updater->getNewVersion())); } elseif (false === $updater->getNewVersion()) { $this->output->writeln(sprintf('There are no %s builds available.', $stability)); } else { @@ -290,45 +282,19 @@ protected function printVersion(Updater $updater): void protected function configure(): void { - $this - ->createDefinition() + $this->createDefinition() // ->setName('self-update') // ->setDescription(self::$description) - ->addOption( - 'dev', - 'd', - Input::OPT_BOOLEAN, - 'Update to most recent development build of package.' - ) - ->addOption( - 'non-dev', - 'N', - Input::OPT_BOOLEAN, - 'Update to most recent non-development (alpha/beta/stable) build of package tagged on Github.' - ) - ->addOption( - 'pre', - 'p', - Input::OPT_BOOLEAN, - 'Update to most recent pre-release version of package (alpha/beta/rc) tagged on Github.' - ) - ->addOption( - 'stable', - 's', - Input::OPT_BOOLEAN, - 'Update to most recent stable version tagged on Github.' - ) - ->addOption( - 'rollback', - 'r', - Input::OPT_BOOLEAN, - 'Rollback to previous version of package if available on filesystem.' - ) - ->addOption( - 'check', - 'c', - Input::OPT_BOOLEAN, - 'Checks what updates are available across all possible stability tracks.' - ); + ->addOption('dev', 'd', Input::OPT_BOOLEAN, + 'Update to most recent development build of package.') + ->addOption('non-dev', 'N', Input::OPT_BOOLEAN, + 'Update to most recent non-development (alpha/beta/stable) build of package tagged on Github.') + ->addOption('pre', 'p', Input::OPT_BOOLEAN, + 'Update to most recent pre-release version of package (alpha/beta/rc) tagged on Github.') + ->addOption('stable', 's', Input::OPT_BOOLEAN, 'Update to most recent stable version tagged on Github.') + ->addOption('rollback', 'r', Input::OPT_BOOLEAN, + 'Rollback to previous version of package if available on filesystem.') + ->addOption('check', 'c', Input::OPT_BOOLEAN, + 'Checks what updates are available across all possible stability tracks.'); } } diff --git a/src/Command.php b/src/Command.php index 49e4cf4f..d627d4b9 100644 --- a/src/Command.php +++ b/src/Command.php @@ -13,6 +13,7 @@ /** * Class Command + * * @package Inhere\Console * * ```php @@ -48,6 +49,7 @@ abstract class Command extends AbstractHandler implements CommandInterface /** * Show help information + * * @return bool * @throws ReflectionException */ diff --git a/src/Component/Animation/Animation.php b/src/Component/Animation/Animation.php index edd09dc7..ebd7a58d 100644 --- a/src/Component/Animation/Animation.php +++ b/src/Component/Animation/Animation.php @@ -10,6 +10,7 @@ /** * Class Animation + * * @package Inhere\Console\Component\Animation */ class Animation diff --git a/src/Component/AutoCompDumper.php b/src/Component/AutoCompDumper.php index 5352fb79..c3f01319 100644 --- a/src/Component/AutoCompDumper.php +++ b/src/Component/AutoCompDumper.php @@ -4,6 +4,7 @@ /** * Class AutoCompDumper Auto complete dumper for zsh/sh shell + * * @package Inhere\Console\Component */ class AutoCompDumper diff --git a/src/Component/ErrorHandler.php b/src/Component/ErrorHandler.php index 6f7d372a..bec7ca03 100644 --- a/src/Component/ErrorHandler.php +++ b/src/Component/ErrorHandler.php @@ -19,6 +19,7 @@ /** * Class ErrorHandler + * * @package Inhere\Console\Component */ class ErrorHandler implements ErrorHandlerInterface @@ -32,7 +33,7 @@ public function handle(Throwable $e, AbstractApplication $app): void // open debug, throw exception if ($app->isDebug()) { - $tpl = << Error %s At File %s line %d @@ -40,19 +41,12 @@ public function handle(Throwable $e, AbstractApplication $app): void Code View:\n\n%s Code Trace:\n\n%s\n ERR; - $line = $e->getLine(); - $file = $e->getFile(); + $line = $e->getLine(); + $file = $e->getFile(); $snippet = Highlighter::create()->highlightSnippet(file_get_contents($file), $line, 3, 3); - $message = sprintf( - $tpl, - // $e->getCode(), - $e->getMessage(), - $file, - $line, - // __METHOD__, - $snippet, - $e->getTraceAsString() - // \str_replace('):', '): -', $e->getTraceAsString()) + $message = sprintf($tpl, // $e->getCode(), + $e->getMessage(), $file, $line, // __METHOD__, + $snippet, $e->getTraceAsString()// \str_replace('):', '): -', $e->getTraceAsString()) ); if ($app->getParam('hideRootPath') && ($rootPath = $app->getParam('rootPath'))) { diff --git a/src/Component/Formatter/Alert.php b/src/Component/Formatter/Alert.php index 4d820931..bd8c8f5f 100644 --- a/src/Component/Formatter/Alert.php +++ b/src/Component/Formatter/Alert.php @@ -12,6 +12,7 @@ /** * Class Alert + * * @package Inhere\Console\Component\Formatter */ class Alert extends MessageFormatter diff --git a/src/Component/Formatter/Block.php b/src/Component/Formatter/Block.php index eafd69ab..c2654d07 100644 --- a/src/Component/Formatter/Block.php +++ b/src/Component/Formatter/Block.php @@ -12,6 +12,7 @@ /** * Class Block + * * @package Inhere\Console\Component\Formatter */ class Block extends MessageFormatter diff --git a/src/Component/Formatter/Body.php b/src/Component/Formatter/Body.php index 886df2e1..d4e8366d 100644 --- a/src/Component/Formatter/Body.php +++ b/src/Component/Formatter/Body.php @@ -10,6 +10,7 @@ /** * Class Body + * * @package Inhere\Console\Component\Formatter */ class Body diff --git a/src/Component/Formatter/SingleList.php b/src/Component/Formatter/SingleList.php index 7ae366f9..7ffb9ed9 100644 --- a/src/Component/Formatter/SingleList.php +++ b/src/Component/Formatter/SingleList.php @@ -2,17 +2,18 @@ namespace Inhere\Console\Component\Formatter; -use function array_merge; use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; use Inhere\Console\Util\FormatUtil; -use const PHP_EOL; use Toolkit\Cli\ColorTag; +use function array_merge; use function trim; use function ucwords; +use const PHP_EOL; /** * Class SingleList - Format and render a single list + * * @package Inhere\Console\Component\Formatter */ class SingleList extends MessageFormatter @@ -31,6 +32,7 @@ class SingleList extends MessageFormatter * @param array $data * @param string $title * @param array $opts More {@see FormatUtil::spliceKeyValue()} + * * @return int|string */ public static function show($data, string $title = '', array $opts = []) diff --git a/src/Component/Formatter/Table.php b/src/Component/Formatter/Table.php index 2cef3e7f..8efc8b26 100644 --- a/src/Component/Formatter/Table.php +++ b/src/Component/Formatter/Table.php @@ -8,22 +8,23 @@ namespace Inhere\Console\Component\Formatter; +use Inhere\Console\Component\MessageFormatter; +use Inhere\Console\Console; +use Toolkit\Cli\ColorTag; +use Toolkit\StrUtil\StrBuffer; use function array_keys; use function array_merge; use function array_sum; use function ceil; use function count; -use Inhere\Console\Component\MessageFormatter; -use Inhere\Console\Console; use function is_string; use function mb_strlen; use function str_pad; -use Toolkit\Cli\ColorTag; -use Toolkit\StrUtil\StrBuffer; use function ucwords; /** * Class Table - Tabular data display + * * @package Inhere\Console\Component\Formatter */ class Table extends MessageFormatter @@ -52,9 +53,11 @@ class Table extends MessageFormatter /** * Tabular data display * - * @param array $data - * @param string $title - * @param array $opts + * @param array $data + * @param string $title + * @param array $opts + * + * @return int * @example * * ```php @@ -78,7 +81,6 @@ class Table extends MessageFormatter * ]; * Show::table($data, 'a table', $opts); * ``` - * @return int */ public static function show(array $data, string $title = 'Data Table', array $opts = []): int { diff --git a/src/Component/Formatter/Title.php b/src/Component/Formatter/Title.php index 31948b4a..65315ded 100644 --- a/src/Component/Formatter/Title.php +++ b/src/Component/Formatter/Title.php @@ -5,13 +5,14 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; use Toolkit\StrUtil\Str; +use Toolkit\Sys\Sys; use function array_merge; use function ceil; use function str_pad; -use Toolkit\Sys\Sys; /** * Class Title + * * @package Inhere\Console\Component\Formatter */ class Title extends MessageFormatter diff --git a/src/Component/Formatter/Tree.php b/src/Component/Formatter/Tree.php index d091ecff..219114c9 100644 --- a/src/Component/Formatter/Tree.php +++ b/src/Component/Formatter/Tree.php @@ -8,17 +8,18 @@ namespace Inhere\Console\Component\Formatter; -use function array_merge; use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; use Inhere\Console\Util\FormatUtil; +use Toolkit\Cli\Cli; +use function array_merge; use function is_array; use function is_scalar; use function str_pad; -use Toolkit\Cli\Cli; /** * Class Tree + * * @package Inhere\Console\Component\Formatter */ class Tree extends MessageFormatter @@ -33,6 +34,7 @@ class Tree extends MessageFormatter * Render data like tree * ├ ─ ─ * └ ─ + * * @param array $data * @param array $opts */ diff --git a/src/Component/Interact/Checkbox.php b/src/Component/Interact/Checkbox.php index 314d594b..12d46d25 100644 --- a/src/Component/Interact/Checkbox.php +++ b/src/Component/Interact/Checkbox.php @@ -14,6 +14,7 @@ /** * Class Checkbox + * * @package Inhere\Console\Component\Interact */ class Checkbox extends InteractMessage @@ -26,6 +27,7 @@ class Checkbox extends InteractMessage * @param string|array $options * @param null|mixed $default * @param bool $allowExit + * * @return array */ public static function select(string $description, $options, $default = null, $allowExit = true): array diff --git a/src/Component/Interact/Choose.php b/src/Component/Interact/Choose.php index d29a0677..a7cfa85d 100644 --- a/src/Component/Interact/Choose.php +++ b/src/Component/Interact/Choose.php @@ -12,22 +12,25 @@ /** * Class Choose + * * @package Inhere\Console\Component\Interact */ class Choose extends InteractMessage { /** * Choose one of several options + * * @param string $description * @param string|array $options Option data - * e.g - * [ - * // option => value - * '1' => 'chengdu', - * '2' => 'beijing' - * ] + * e.g + * [ + * // option => value + * '1' => 'chengdu', + * '2' => 'beijing' + * ] * @param string|int $default Default option * @param bool $allowExit + * * @return string */ public static function one(string $description, $options, $default = null, bool $allowExit = true): string diff --git a/src/Component/Interact/Confirm.php b/src/Component/Interact/Confirm.php index 6d3afb73..09ae8d0f 100644 --- a/src/Component/Interact/Confirm.php +++ b/src/Component/Interact/Confirm.php @@ -11,14 +11,17 @@ /** * Class Confirm + * * @package Inhere\Console\Component\Interact */ class Confirm extends InteractMessage { /** * Send a message request confirmation + * * @param string $question The question message - * @param bool $default Default value + * @param bool $default Default value + * * @return bool */ public static function ask(string $question, bool $default = true): bool diff --git a/src/Component/Interact/LimitedAsk.php b/src/Component/Interact/LimitedAsk.php index b1332ba7..19b29021 100644 --- a/src/Component/Interact/LimitedAsk.php +++ b/src/Component/Interact/LimitedAsk.php @@ -12,6 +12,7 @@ /** * Class LimitedAsk + * * @package Inhere\Console\Component\Interact */ class LimitedAsk extends InteractMessage @@ -21,9 +22,13 @@ class LimitedAsk extends InteractMessage * Ask a question, ask for a limited number of times * 若输入了值且验证成功则返回 输入的结果 * 否则,会连续询问 $times 次, 若仍然错误,退出 - * @param string $question 问题 - * @param string $default 默认值 + * + * @param string $question 问题 + * @param string $default 默认值 * @param Closure $validator (默认验证输入是否为空)自定义回调验证输入是否符合要求; 验证成功返回true 否则 可返回错误消息 + * @param int $times Allow input times + * + * @return string * @example This is an example * * ```php @@ -48,8 +53,6 @@ class LimitedAsk extends InteractMessage * } ); * ``` * - * @param int $times Allow input times - * @return string */ public static function ask( string $question, @@ -88,7 +91,7 @@ public static function ask( } // If setting verify callback - if ($validator && true === $validator($answer) ) { + if ($validator && true === $validator($answer)) { break; } diff --git a/src/Component/Interact/Password.php b/src/Component/Interact/Password.php index 5d9778dc..8206c73b 100644 --- a/src/Component/Interact/Password.php +++ b/src/Component/Interact/Password.php @@ -15,6 +15,7 @@ /** * Class Password + * * @package Inhere\Console\Component\Interact */ class Password extends InteractMessage @@ -23,11 +24,13 @@ class Password extends InteractMessage * Interactively prompts for input without echoing to the terminal. * Requires a bash shell or Windows and won't work with * safe_mode settings (Uses `shell_exec`) + * * @param string $prompt + * * @return string - * @link https://stackoverflow.com/questions/187736/command-line-password-prompt-in-php - * @link http://www.sitepoint.com/blogs/2009/05/01/interactive-cli-password-prompt-in-php * @throws RuntimeException + * @link http://www.sitepoint.com/blogs/2009/05/01/interactive-cli-password-prompt-in-php + * @link https://stackoverflow.com/questions/187736/command-line-password-prompt-in-php */ public static function ask(string $prompt = 'Enter Password:'): string { @@ -39,7 +42,7 @@ public static function ask(string $prompt = 'Enter Password:'): string // linux, unix, git-bash if (Sys::shIsAvailable()) { // COMMAND: sh -c 'read -p "Enter Password:" -s user_input && echo $user_input' - $command = sprintf('sh -c "read -p \'%s\' -s user_input && echo $user_input"', $prompt); + $command = sprintf('sh -c "read -p \'%s\' -s user_input && echo $user_input"', $prompt); $password = Sys::execute($command, false); print "\n"; @@ -52,7 +55,7 @@ public static function ask(string $prompt = 'Enter Password:'): string file_put_contents($vbFile, sprintf('wscript.echo(InputBox("%s", "", "password here"))', $prompt)); - $command = 'cscript //nologo ' . escapeshellarg($vbFile); + $command = 'cscript //nologo ' . escapeshellarg($vbFile); $password = rtrim(shell_exec($command)); unlink($vbFile); diff --git a/src/Component/Interact/Question.php b/src/Component/Interact/Question.php index 694cea8b..cd642933 100644 --- a/src/Component/Interact/Question.php +++ b/src/Component/Interact/Question.php @@ -11,20 +11,27 @@ /** * Class Question + * * @package Inhere\Console\Component\Interact */ class Question extends InteractMessage { /** * Ask a question, ask for results; return the result of the input + * + * @param string $question + * @param string $default + * @param Closure|null $validator Validator, must return bool. + * + * @return string * @example This is an example - * ```php - * $answer = Interact::ask('Please input your name?', null, function ($answer) { - * if (!preg_match('/\w{2,}/', $answer)) { + * ```php + * $answer = Interact::ask('Please input your name?', null, function ($answer) { + * if (!preg_match('/\w{2,}/', $answer)) { * // output error tips. * Interact::error('The name must match "/\w{2,}/"'); * return false; - * } + * } * * return true; * }); @@ -47,10 +54,6 @@ class Question extends InteractMessage * * echo "Your input: $answer"; * ``` - * @param string $question - * @param string $default - * @param Closure|null $validator Validator, must return bool. - * @return string */ public static function ask(string $question, string $default = '', Closure $validator = null): string { diff --git a/src/Component/Interact/Terminal.php b/src/Component/Interact/Terminal.php index f1c97edb..567e9dd2 100644 --- a/src/Component/Interact/Terminal.php +++ b/src/Component/Interact/Terminal.php @@ -6,6 +6,7 @@ /** * Class Terminal + * * @package Inhere\Console\Component\Interact */ class Terminal extends InteractMessage diff --git a/src/Component/InteractMessage.php b/src/Component/InteractMessage.php index a3ccdf22..583e86c1 100644 --- a/src/Component/InteractMessage.php +++ b/src/Component/InteractMessage.php @@ -4,6 +4,7 @@ /** * Class InteractMessage + * * @package Inhere\Console\Component */ class InteractMessage diff --git a/src/Component/MessageFormatter.php b/src/Component/MessageFormatter.php index 6812c6c9..bc5f1371 100644 --- a/src/Component/MessageFormatter.php +++ b/src/Component/MessageFormatter.php @@ -15,6 +15,7 @@ /** * Class Formatter - message formatter + * * @package Inhere\Console\Component */ abstract class MessageFormatter implements FormatterInterface @@ -31,6 +32,7 @@ abstract class MessageFormatter implements FormatterInterface /** * @param array $config + * * @return MessageFormatter */ public static function create(array $config = []): self @@ -40,6 +42,7 @@ public static function create(array $config = []): self /** * Formatter constructor. + * * @param array $config */ public function __construct(array $config = []) diff --git a/src/Component/NotifyMessage.php b/src/Component/NotifyMessage.php index 9580d650..de88bf5f 100644 --- a/src/Component/NotifyMessage.php +++ b/src/Component/NotifyMessage.php @@ -14,7 +14,7 @@ * Class NotifyMessage - like progress, spinner .... * * @package Inhere\Console\Component - * @link https://github.com/wp-cli/php-cli-tools/tree/master/lib/cli + * @link https://github.com/wp-cli/php-cli-tools/tree/master/lib/cli */ class NotifyMessage { diff --git a/src/Component/PharCompiler.php b/src/Component/PharCompiler.php index 810e8ba8..1db1b181 100644 --- a/src/Component/PharCompiler.php +++ b/src/Component/PharCompiler.php @@ -59,6 +59,7 @@ /** * Class PharCompiler + * * @package Inhere\Console\Component */ class PharCompiler @@ -104,6 +105,7 @@ class PharCompiler * 'tag' => '{@package_branch_alias_version}', * 'releaseDate' => '{@release_date}', * ] + * * @var string */ private $versionFile; @@ -206,6 +208,7 @@ class PharCompiler * @param string $extractTo * @param string|array|null $files Only fetch the listed files * @param bool $overwrite + * * @return bool * @throws UnexpectedValueException * @throws BadMethodCallException @@ -231,15 +234,15 @@ private static function checkEnv(): void } if (ini_get('phar.readonly')) { - throw new RuntimeException( - "The 'phar.readonly' is 'On', build phar must setting it 'Off' or exec with 'php -d phar.readonly=0'" - ); + throw new RuntimeException("The 'phar.readonly' is 'On', build phar must setting it 'Off' or exec with 'php -d phar.readonly=0'"); } } /** * PharCompiler constructor. + * * @param string $basePath + * * @throws RuntimeException */ public function __construct(string $basePath) @@ -255,6 +258,7 @@ public function __construct(string $basePath) /** * @param string|array $suffixes + * * @return $this */ public function addSuffix($suffixes): self @@ -266,6 +270,7 @@ public function addSuffix($suffixes): self /** * @param string|array $filename + * * @return $this */ public function notName($filename): self @@ -277,6 +282,7 @@ public function notName($filename): self /** * @param string|array $dirs + * * @return $this */ public function addExclude($dirs): self @@ -288,6 +294,7 @@ public function addExclude($dirs): self /** * @param string|array $files + * * @return $this */ public function addFile($files): self @@ -299,6 +306,7 @@ public function addFile($files): self /** * @param bool $value + * * @return PharCompiler */ public function stripComments($value): self @@ -310,6 +318,7 @@ public function stripComments($value): self /** * @param bool $value + * * @return PharCompiler */ public function collectVersion($value): self @@ -321,6 +330,7 @@ public function collectVersion($value): self /** * @param Closure $stripFilter + * * @return PharCompiler */ public function setStripFilter(Closure $stripFilter): PharCompiler @@ -332,6 +342,7 @@ public function setStripFilter(Closure $stripFilter): PharCompiler /** * @param bool|string $shebang + * * @return PharCompiler */ public function setShebang($shebang): PharCompiler @@ -343,6 +354,7 @@ public function setShebang($shebang): PharCompiler /** * @param string|array $dirs + * * @return PharCompiler */ public function in($dirs): self @@ -366,8 +378,10 @@ public function setModifies($modifies): self /** * Compiles composer into a single phar file - * @param string $pharFile The full path to the file to create - * @param bool $refresh + * + * @param string $pharFile The full path to the file to create + * @param bool $refresh + * * @return string * @throws UnexpectedValueException * @throws BadMethodCallException @@ -456,6 +470,7 @@ public function pack(string $pharFile, $refresh = true): string /** * find changed or new created files by git status. + * * @return Generator */ public function findChangedByGit(): ?Generator @@ -492,15 +507,13 @@ public function findChangedByGit(): ?Generator */ protected function findFiles(string $directory) { - return Helper::directoryIterator( - $directory, - $this->createIteratorFilter(), - FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS - ); + return Helper::directoryIterator($directory, $this->createIteratorFilter(), + FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS); } /** * Add a file to the Phar. + * * @param Phar $phar * @param SplFileInfo $file */ @@ -513,8 +526,8 @@ private function packFile(Phar $phar, SplFileInfo $file): void } $this->counter++; - $path = $this->getRelativeFilePath($file); - $strip = $this->stripComments; + $path = $this->getRelativeFilePath($file); + $strip = $this->stripComments; $content = file_get_contents($file); // clear php file comments @@ -553,7 +566,7 @@ private function packIndexFile(Phar $phar): void { if ($this->cliIndex) { $this->counter++; - $path = $this->basePath . '/' . $this->cliIndex; + $path = $this->basePath . '/' . $this->cliIndex; $content = preg_replace('{^#!/usr/bin/env php\s*}', '', file_get_contents($path)); if ($cb = $this->events['add']) { @@ -565,7 +578,7 @@ private function packIndexFile(Phar $phar): void if ($this->webIndex) { $this->counter++; - $path = $this->basePath . '/' . $this->webIndex; + $path = $this->basePath . '/' . $this->webIndex; $content = file_get_contents($path); if ($cb = $this->events['add']) { @@ -591,9 +604,9 @@ public function getCounter(): int private function createStub(): string { // var_dump($this);die; - $date = date('Y-m-d H:i'); + $date = date('Y-m-d H:i'); $pharName = $this->pharName; - $stub = <<shebang) { $shebang = is_string($shebang) ? $shebang : '#!/usr/bin/env php'; - $stub = "$shebang\n$stub"; + $stub = "$shebang\n$stub"; } if ($this->cliIndex && $this->webIndex) { $stub .= <<cliIndex}'; } else { @@ -672,7 +685,9 @@ private function createIteratorFilter(): Closure /** * Removes whitespace from a PHP source string while preserving line numbers. - * @param string $source A PHP string + * + * @param string $source A PHP string + * * @return string The PHP string with the whitespace removed */ private function stripWhitespace(string $source): string @@ -694,7 +709,7 @@ private function stripWhitespace(string $source): string $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace); // trim leading spaces $whitespace = preg_replace('{\n +}', "\n", $whitespace); - $output .= $whitespace; + $output .= $whitespace; } else { $output .= $token[1]; } @@ -705,6 +720,7 @@ private function stripWhitespace(string $source): string /** * auto collect project information by git log + * * @throws RuntimeException * @throws Exception */ @@ -718,9 +734,7 @@ private function collectInformation(): void [$code, $ret,] = Sys::run('git log --pretty="%H" -n1 HEAD', $basePath); if ($code !== 0) { - throw new RuntimeException( - 'Can\'t run git log. You must ensure to run compile from git repository clone and that git binary is available.' - ); + throw new RuntimeException('Can\'t run git log. You must ensure to run compile from git repository clone and that git binary is available.'); } $this->version = trim($ret); @@ -728,9 +742,7 @@ private function collectInformation(): void [$code, $ret,] = Sys::run('git log -n1 --pretty=%ci HEAD', $basePath); if ($code !== 0) { - throw new RuntimeException( - 'Can\'t run git log. You must ensure to run compile from git repository clone and that git binary is available.' - ); + throw new RuntimeException('Can\'t run git log. You must ensure to run compile from git repository clone and that git binary is available.'); } $this->versionDate = new DateTime(trim($ret)); @@ -750,15 +762,16 @@ private function collectInformation(): void } /** - * @param SplFileInfo $file + * @param SplFileInfo $file + * * @return string */ private function getRelativeFilePath($file): string { - $realPath = $file->getRealPath(); + $realPath = $file->getRealPath(); $pathPrefix = $this->basePath . DIRECTORY_SEPARATOR; - $pos = strpos($realPath, $pathPrefix); + $pos = strpos($realPath, $pathPrefix); $relativePath = $pos !== false ? substr_replace($realPath, '', $pos, strlen($pathPrefix)) : $realPath; return str_replace('\\', '/', $relativePath); @@ -776,7 +789,8 @@ private function reportError($error): void /** * add event handler - * @param string $event + * + * @param string $event * @param Closure $closure */ public function on(string $event, Closure $closure): void @@ -842,6 +856,7 @@ public function getCliIndex(): ?string /** * @param string $cliIndex + * * @return $this */ public function setCliIndex(string $cliIndex): self @@ -861,6 +876,7 @@ public function getWebIndex(): ?string /** * @param null|string $webIndex + * * @return PharCompiler */ public function setWebIndex(string $webIndex): self @@ -887,6 +903,7 @@ public function getVersionFile(): ?string /** * @param string $versionFile + * * @return PharCompiler */ public function setVersionFile(string $versionFile): PharCompiler @@ -906,6 +923,7 @@ public function getVersion(): ?string /** * @param null|string $version + * * @return PharCompiler */ public function setVersion(string $version): PharCompiler @@ -925,6 +943,7 @@ public function getBranchAliasVersion(): ?string /** * @param null|string $branchAliasVersion + * * @return PharCompiler */ public function setBranchAliasVersion(string $branchAliasVersion): PharCompiler diff --git a/src/Component/Progress/Bar.php b/src/Component/Progress/Bar.php index ee28642b..e616efd6 100644 --- a/src/Component/Progress/Bar.php +++ b/src/Component/Progress/Bar.php @@ -10,6 +10,7 @@ /** * Class Bar + * * @package Inhere\Console\Component\Progress */ class Bar extends TextBar diff --git a/src/Component/Progress/CounterText.php b/src/Component/Progress/CounterText.php index 2f998995..f6fcfe18 100644 --- a/src/Component/Progress/CounterText.php +++ b/src/Component/Progress/CounterText.php @@ -9,6 +9,7 @@ /** * Class CounterText + * * @package Inhere\Console\Component\Progress */ class CounterText extends NotifyMessage @@ -31,6 +32,7 @@ class CounterText extends NotifyMessage * * @param string $msg * @param string $doneMsg + * * @return Generator */ public static function gen(string $msg, $doneMsg = ''): Generator diff --git a/src/Component/Progress/DynamicText.php b/src/Component/Progress/DynamicText.php index 9b2088d2..9ab48726 100644 --- a/src/Component/Progress/DynamicText.php +++ b/src/Component/Progress/DynamicText.php @@ -10,6 +10,7 @@ /** * Class DynamicText + * * @package Inhere\Console\Component\Progress */ class DynamicText extends NotifyMessage @@ -17,6 +18,7 @@ class DynamicText extends NotifyMessage /** * @param string $doneMsg * @param string $fixedMsg + * * @return Generator */ public static function gen(string $doneMsg, string $fixedMsg = ''): Generator diff --git a/src/Component/Progress/Pending.php b/src/Component/Progress/Pending.php index ff3d7203..50a3c459 100644 --- a/src/Component/Progress/Pending.php +++ b/src/Component/Progress/Pending.php @@ -12,6 +12,7 @@ /** * Class Pending + * * @package Inhere\Console\Component\Progress */ class Pending extends NotifyMessage diff --git a/src/Component/Progress/SimpleBar.php b/src/Component/Progress/SimpleBar.php index c7498282..cafc5ef0 100644 --- a/src/Component/Progress/SimpleBar.php +++ b/src/Component/Progress/SimpleBar.php @@ -11,6 +11,7 @@ /** * Class SimpleBar + * * @package Inhere\Console\Component\Progress */ class SimpleBar extends NotifyMessage @@ -35,8 +36,9 @@ class SimpleBar extends NotifyMessage * * @param int $total * @param array $opts - * @internal int $current + * * @return Generator + * @internal int $current */ public static function gen(int $total, array $opts = []): Generator { @@ -80,14 +82,11 @@ public static function gen(int $total, array $opts = []): Generator * \r, \x0D 回车,到行首 * \x1B ESC * 2K 清除本行 - */ - // printf("\r[%'--100s] %d%% %s", + */ // printf("\r[%'--100s] %d%% %s", // printf("\x0D\x1B[2K[%'{$waitChar}-100s] %d%% %s", printf("{$tplPrefix}[%'{$waitChar}-100s] %' 3d%% %s", - str_repeat($opts['doneChar'], $percent) . ($finished ? '' : $opts['signChar']), - $percent, - $msg - );// ♥ ■ ☺ ☻ = # + str_repeat($opts['doneChar'], $percent) . ($finished ? '' : $opts['signChar']), $percent, + $msg);// ♥ ■ ☺ ☻ = # if ($finished) { echo "\n"; diff --git a/src/Component/Progress/SimpleTextBar.php b/src/Component/Progress/SimpleTextBar.php index 0946dcb3..fde85a79 100644 --- a/src/Component/Progress/SimpleTextBar.php +++ b/src/Component/Progress/SimpleTextBar.php @@ -9,15 +9,18 @@ /** * Class SimpleTextBar + * * @package Inhere\Console\Component\Progress */ class SimpleTextBar extends NotifyMessage { /** * Render a simple text progress bar by 'yield' + * * @param int $total * @param string $msg * @param string $doneMsg + * * @return Generator */ public static function gen(int $total, string $msg, string $doneMsg = ''): Generator diff --git a/src/Component/Progress/Spinner.php b/src/Component/Progress/Spinner.php index 63881d65..0076f3ac 100644 --- a/src/Component/Progress/Spinner.php +++ b/src/Component/Progress/Spinner.php @@ -12,6 +12,7 @@ /** * Class Spinner + * * @package Inhere\Console\Component\Progress */ class Spinner extends NotifyMessage diff --git a/src/Component/Progress/TextBar.php b/src/Component/Progress/TextBar.php index 7bdcb3e2..8f49b5b0 100644 --- a/src/Component/Progress/TextBar.php +++ b/src/Component/Progress/TextBar.php @@ -10,6 +10,7 @@ /** * Class Text + * * @package Inhere\Console\Component\Progress */ class TextBar diff --git a/src/Component/Style/Color.php b/src/Component/Style/Color.php index f21ca471..fd3f826e 100644 --- a/src/Component/Style/Color.php +++ b/src/Component/Style/Color.php @@ -93,6 +93,7 @@ class Color * @param string $bg * @param array $options * @param bool $extra + * * @return Color * @throws InvalidArgumentException */ @@ -105,16 +106,17 @@ public static function make($fg = '', $bg = '', array $options = [], bool $extra * Create a color style from a parameter string. * * @param string $string e.g 'fg=white;bg=black;options=bold,underscore;extra=1' + * * @return static * @throws InvalidArgumentException * @throws RuntimeException */ public static function makeByString($string) { - $fg = $bg = ''; - $extra = false; + $fg = $bg = ''; + $extra = false; $options = []; - $parts = explode(';', str_replace(' ', '', $string)); + $parts = explode(';', str_replace(' ', '', $string)); foreach ($parts as $part) { $subParts = explode('=', $part); @@ -147,22 +149,20 @@ public static function makeByString($string) /** * Constructor - * @param string $fg Foreground color. e.g 'white' - * @param string $bg Background color. e.g 'black' + * + * @param string $fg Foreground color. e.g 'white' + * @param string $bg Background color. e.g 'black' * @param array $options Style options. e.g ['bold', 'underscore'] * @param bool $extra + * * @throws InvalidArgumentException */ public function __construct($fg = '', $bg = '', array $options = [], bool $extra = false) { if ($fg) { if (false === array_key_exists($fg, static::$knownColors)) { - throw new InvalidArgumentException( - sprintf('Invalid foreground color "%1$s" [%2$s]', - $fg, - implode(', ', $this->getKnownColors()) - ) - ); + throw new InvalidArgumentException(sprintf('Invalid foreground color "%1$s" [%2$s]', $fg, + implode(', ', $this->getKnownColors()))); } $this->fgColor = ($extra ? self::FG_EXTRA : self::FG_BASE) + static::$knownColors[$fg]; @@ -170,12 +170,8 @@ public function __construct($fg = '', $bg = '', array $options = [], bool $extra if ($bg) { if (false === array_key_exists($bg, static::$knownColors)) { - throw new InvalidArgumentException( - sprintf('Invalid background color "%1$s" [%2$s]', - $bg, - implode(', ', $this->getKnownColors()) - ) - ); + throw new InvalidArgumentException(sprintf('Invalid background color "%1$s" [%2$s]', $bg, + implode(', ', $this->getKnownColors()))); } $this->bgColor = ($extra ? self::BG_EXTRA : self::BG_BASE) + static::$knownColors[$bg]; @@ -183,12 +179,8 @@ public function __construct($fg = '', $bg = '', array $options = [], bool $extra foreach ($options as $option) { if (false === array_key_exists($option, static::$knownOptions)) { - throw new InvalidArgumentException( - sprintf('Invalid option "%1$s" [%2$s]', - $option, - implode(', ', $this->getKnownOptions()) - ) - ); + throw new InvalidArgumentException(sprintf('Invalid option "%1$s" [%2$s]', $option, + implode(', ', $this->getKnownOptions()))); } $this->options[] = $option; @@ -227,7 +219,9 @@ public function toStyle(): string /** * Get the known colors. + * * @param bool $onlyName + * * @return array */ public function getKnownColors(bool $onlyName = true): array @@ -237,7 +231,9 @@ public function getKnownColors(bool $onlyName = true): array /** * Get the known options. + * * @param bool $onlyName + * * @return array */ public function getKnownOptions(bool $onlyName = true): array diff --git a/src/Component/Style/Style.php b/src/Component/Style/Style.php index ec307188..783c7095 100644 --- a/src/Component/Style/Style.php +++ b/src/Component/Style/Style.php @@ -25,8 +25,9 @@ /** * Class Style + * * @package Inhere\Console\Component\Style - * @link https://github.com/ventoviro/windwalker-IO + * @link https://github.com/ventoviro/windwalker-IO * * @method string info(string $message) * @method string comment(string $message) @@ -56,6 +57,7 @@ class Style /** * Regex to match tags + * * @var string */ public const COLOR_TAG = '/<([a-zA-Z=;]+)>(.*?)<\/\\1>/s'; @@ -67,12 +69,14 @@ class Style /** * Flag to remove color codes from the output + * * @var bool */ protected static $noColor = false; /** * Array of Color objects + * * @var Color[] */ private $styles = []; @@ -91,9 +95,10 @@ public static function instance(): Style /** * Constructor - * @param string $fg 前景色(字体颜色) - * @param string $bg 背景色 - * @param array $options 其它选项 + * + * @param string $fg 前景色(字体颜色) + * @param string $bg 背景色 + * @param array $options 其它选项 */ public function __construct($fg = '', $bg = '', array $options = []) { @@ -107,6 +112,7 @@ public function __construct($fg = '', $bg = '', array $options = []) /** * @param string $method * @param array $args + * * @return mixed|string * @throws InvalidArgumentException */ @@ -125,35 +131,31 @@ public function __call($method, array $args) */ protected function loadDefaultStyles(): void { - $this - ->addByArray(self::NORMAL, ['fg' => 'normal']) + $this->addByArray(self::NORMAL, ['fg' => 'normal']) // 不明显的 浅灰色的 - ->addByArray(self::FAINTLY, ['fg' => 'normal', 'options' => ['italic']]) - ->addByArray(self::BOLD, ['options' => ['bold']]) - ->addByArray(self::INFO, ['fg' => 'green',])//'options' => ['bold'] - ->addByArray(self::NOTE, ['fg' => 'cyan', 'options' => ['bold']])//'options' => ['bold'] - ->addByArray(self::PRIMARY, ['fg' => 'yellow', 'options' => ['bold']])// - ->addByArray(self::SUCCESS, ['fg' => 'green', 'options' => ['bold']]) - ->addByArray(self::NOTICE, ['options' => ['bold', 'underscore'],]) - ->addByArray(self::WARNING, ['fg' => 'black', 'bg' => 'yellow',])//'options' => ['bold'] - ->addByArray(self::COMMENT, ['fg' => 'yellow',])//'options' => ['bold'] - ->addByArray(self::QUESTION, ['fg' => 'black', 'bg' => 'cyan']) - ->addByArray(self::DANGER, ['fg' => 'red',])// 'bg' => 'magenta', 'options' => ['bold'] - ->add(self::ERROR, 'white', 'red', [], true) - ->add('underline', 'normal', '', ['underscore']) - ->add('blue', 'blue') - ->add('cyan', 'cyan') - ->add('magenta', 'magenta') - ->add('mga', 'magenta') - ->add('red', 'red') - ->addByArray('yellow', ['fg' => 'yellow']) - ->addByArray('darkGray', ['fg' => 'black', 'extra' => true]); + ->addByArray(self::FAINTLY, ['fg' => 'normal', 'options' => ['italic']]) + ->addByArray(self::BOLD, ['options' => ['bold']]) + ->addByArray(self::INFO, ['fg' => 'green',])//'options' => ['bold'] + ->addByArray(self::NOTE, ['fg' => 'cyan', 'options' => ['bold']])//'options' => ['bold'] + ->addByArray(self::PRIMARY, ['fg' => 'yellow', 'options' => ['bold']])// + ->addByArray(self::SUCCESS, ['fg' => 'green', 'options' => ['bold']]) + ->addByArray(self::NOTICE, ['options' => ['bold', 'underscore'],]) + ->addByArray(self::WARNING, ['fg' => 'black', 'bg' => 'yellow',])//'options' => ['bold'] + ->addByArray(self::COMMENT, ['fg' => 'yellow',])//'options' => ['bold'] + ->addByArray(self::QUESTION, ['fg' => 'black', 'bg' => 'cyan']) + ->addByArray(self::DANGER, ['fg' => 'red',])// 'bg' => 'magenta', 'options' => ['bold'] + ->add(self::ERROR, 'white', 'red', [], true)->add('underline', 'normal', '', ['underscore']) + ->add('blue', 'blue')->add('cyan', 'cyan')->add('magenta', 'magenta')->add('mga', 'magenta') + ->add('red', 'red')->addByArray('yellow', ['fg' => 'yellow']) + ->addByArray('darkGray', ['fg' => 'black', 'extra' => true]); } /** * Process a string use style + * * @param string $style * @param string $text + * * @return string */ public function apply(string $style, string $text): string @@ -163,7 +165,9 @@ public function apply(string $style, string $text): string /** * Process a string. + * * @param string $text + * * @return mixed */ public function t(string $text) @@ -173,7 +177,9 @@ public function t(string $text) /** * Process a string. + * * @param string $text + * * @return mixed */ public function render(string $text) @@ -183,6 +189,7 @@ public function render(string $text) /** * @param string $text + * * @return mixed|string */ public function format(string $text) @@ -217,10 +224,12 @@ public function format(string $text) /** * Replace color tags in a string. - * @param string $text - * @param string $tag The matched tag. - * @param string $match The match. - * @param string $style The color style to apply. + * + * @param string $text + * @param string $tag The matched tag. + * @param string $match The match. + * @param string $style The color style to apply. + * * @return string */ protected function replaceColor($text, $tag, $match, $style): string @@ -233,7 +242,9 @@ protected function replaceColor($text, $tag, $match, $style): string /** * Strip color tags from a string. + * * @param string $string + * * @return mixed */ public static function stripColor(string $string) @@ -247,12 +258,14 @@ public static function stripColor(string $string) /** * Add a style. + * * @param string $name - * @param string|Color|array $fg 前景色|Color对象|也可以是style配置数组(@see self::addByArray()) - * 当它为Color对象或配置数组时,后面两个参数无效 - * @param string $bg 背景色 + * @param string|Color|array $fg 前景色|Color对象|也可以是style配置数组(@see self::addByArray()) + * 当它为Color对象或配置数组时,后面两个参数无效 + * @param string $bg 背景色 * @param array $options 其它选项 * @param bool $extra + * * @return $this */ public function add(string $name, $fg = '', $bg = '', array $options = [], bool $extra = false): self @@ -272,15 +285,17 @@ public function add(string $name, $fg = '', $bg = '', array $options = [], bool /** * Add a style by an array config + * * @param string $name * @param array $styleConfig 样式设置信息 - * e.g - * [ - * 'fg' => 'white', - * 'bg' => 'black', - * 'extra' => true, - * 'options' => ['bold', 'underscore'] - * ] + * e.g + * [ + * 'fg' => 'white', + * 'bg' => 'black', + * 'extra' => true, + * 'options' => ['bold', 'underscore'] + * ] + * * @return $this */ public function addByArray(string $name, array $styleConfig): self @@ -326,6 +341,7 @@ public function getStyles(): array /** * @param $name + * * @return Color|null */ public function getStyle($name): ?Color @@ -339,6 +355,7 @@ public function getStyle($name): ?Color /** * @param $name + * * @return bool */ public function hasStyle($name): bool @@ -348,8 +365,10 @@ public function hasStyle($name): bool /** * wrap a color style tag + * * @param string $text * @param string $tag + * * @return string */ public static function wrap(string $text, string $tag): string @@ -371,6 +390,7 @@ public static function isNoColor(): bool /** * Method to set property noColor + * * @param $noColor */ public static function setNoColor($noColor = true): void diff --git a/src/Component/Symbol/ArtFont.php b/src/Component/Symbol/ArtFont.php index 21fc784a..c38478a7 100644 --- a/src/Component/Symbol/ArtFont.php +++ b/src/Component/Symbol/ArtFont.php @@ -17,6 +17,7 @@ /** * Class ArtFont art fonts Manager + * * @package Inhere\Console\Component\Symbol */ class ArtFont @@ -76,7 +77,7 @@ public function __construct() */ protected function loadInternalFonts(): void { - $path = dirname(__DIR__) . '/BuiltIn/Resources/art-fonts/'; + $path = dirname(__DIR__) . '/BuiltIn/Resources/art-fonts/'; $group = self::INTERNAL_GROUP; foreach (self::$internalFonts as $font) { @@ -88,8 +89,10 @@ protected function loadInternalFonts(): void /** * display the internal art font + * * @param string $name * @param array $opts + * * @return int */ public function showInternal(string $name, array $opts = []): int @@ -101,6 +104,7 @@ public function showInternal(string $name, array $opts = []): int * @param string $name * @param string $group * @param array $opts + * * @return int */ public function showItalic(string $name, string $group = null, array $opts = []): int @@ -112,6 +116,7 @@ public function showItalic(string $name, string $group = null, array $opts = []) /** * display the art font + * * @param string $name * @param string $group * @param array $opts @@ -119,6 +124,7 @@ public function showItalic(string $name, string $group = null, array $opts = []) * - type => '', // 'italic' * - indent => 2, * - style => '', // 'info' 'error' + * * @return int */ public function show(string $name, string $group = null, array $opts = []): int @@ -129,12 +135,12 @@ public function show(string $name, string $group = null, array $opts = []): int 'style' => '', ], $opts); - $type = $opts['type']; + $type = $opts['type']; $pfxType = $type ? '_' . $type : ''; - $txt = ''; - $group = trim($group); - $group = $group ?: self::DEFAULT_GROUP; + $txt = ''; + $group = trim($group); + $group = $group ?: self::DEFAULT_GROUP; $longKey = $group . '.' . $name . $pfxType; if (isset($this->fontContents[$longKey])) { @@ -164,6 +170,7 @@ public function show(string $name, string $group = null, array $opts = []): int /** * @param string $name * @param string $group + * * @return string */ public function font(string $name, string $group = null): string @@ -174,6 +181,7 @@ public function font(string $name, string $group = null): string /** * @param string $group * @param string $path + * * @return $this */ public function addGroup(string $group, string $path): self @@ -194,6 +202,7 @@ public function addGroup(string $group, string $path): self /** * @param string $group * @param string $path + * * @return $this */ public function setGroup(string $group, string $path): self @@ -213,6 +222,7 @@ public function setGroup(string $group, string $path): self * @param string $name * @param string $file font file path * @param string|null $group + * * @return $this */ public function addFont(string $name, string $file, string $group = null): self @@ -221,7 +231,7 @@ public function addFont(string $name, string $file, string $group = null): self if (is_file($file)) { $info = pathinfo($file); - $ext = !empty($info['extension']) ? $info['extension'] : 'txt'; + $ext = !empty($info['extension']) ? $info['extension'] : 'txt'; $this->fonts[$group][$name] = $info['dirname'] . '/' . $info['filename'] . '.' . $ext; } @@ -232,6 +242,7 @@ public function addFont(string $name, string $file, string $group = null): self /** * @param string $name * @param string $content + * * @return $this */ public function addFontContent(string $name, string $content): self @@ -245,6 +256,7 @@ public function addFontContent(string $name, string $content): self /** * @param string $name + * * @return bool */ public static function isInternalFont($name): bool diff --git a/src/Console.php b/src/Console.php index 38cc29a6..29940f70 100644 --- a/src/Console.php +++ b/src/Console.php @@ -29,6 +29,7 @@ /** * Class Console + * * @package Inhere\Console */ class Console @@ -91,6 +92,7 @@ public static function setApp(Application $app): void * @param array $config * @param Input|null $input * @param Output|null $output + * * @return Application */ public static function newApp( @@ -115,8 +117,10 @@ public static function style(): Style /** * Format and write message to terminal. like printf() + * * @param string $format * @param mixed ...$args + * * @return int */ public static function writef(string $format, ...$args): int @@ -126,8 +130,10 @@ public static function writef(string $format, ...$args): int /** * Format and write message to terminal. like printf() + * * @param string $format * @param mixed ...$args + * * @return int */ public static function printf(string $format, ...$args): int @@ -137,10 +143,12 @@ public static function printf(string $format, ...$args): int /** * Write raw data to stdout, will disable color render. + * * @param string|array $message * @param bool $nl * @param bool|int $quit * @param array $opts + * * @return int */ public static function writeRaw($message, $nl = true, $quit = false, array $opts = []): int @@ -151,9 +159,11 @@ public static function writeRaw($message, $nl = true, $quit = false, array $opts /** * Write data to stdout with newline. + * * @param string|array $message * @param array $opts * @param bool|int $quit + * * @return int */ public static function writeln($message, $quit = false, array $opts = []): int @@ -166,14 +176,15 @@ public static function writeln($message, $quit = false, array $opts = []): int * * @param string|array $messages Output message * @param boolean $nl True 会添加换行符, False 原样输出,不添加换行符 - * @param int|boolean $quit If is int, setting it is exit code. + * @param int|boolean $quit If is int, setting it is exit code. * 'True' translate as code 0 and exit, 'False' will not exit. * @param array $opts Some options for write - * [ - * 'color' => bool, // whether render color, default is: True. - * 'stream' => resource, // the stream resource, default is: STDOUT - * 'flush' => bool, // flush the stream data, default is: True - * ] + * [ + * 'color' => bool, // whether render color, default is: True. + * 'stream' => resource, // the stream resource, default is: STDOUT + * 'flush' => bool, // flush the stream data, default is: True + * ] + * * @return int */ public static function write($messages, $nl = true, $quit = false, array $opts = []): int @@ -222,6 +233,7 @@ public static function write($messages, $nl = true, $quit = false, array $opts = /** * Logs data to stdout + * * @param string|array $text * @param bool $nl * @param bool|int $quit @@ -233,6 +245,7 @@ public static function stdout($text, $nl = true, $quit = false): void /** * Logs data to stderr + * * @param string|array $text * @param bool $nl * @param bool|int $quit @@ -262,16 +275,17 @@ public static function logf(int $level, string $format, ...$args): void /** * Print log message to console + * * @param string $msg * @param array $data * @param int $level * @param array $opts - * [ + * [ * '_category' => 'application', * 'process' => 'work', * 'pid' => 234, * 'coId' => 12, - * ] + * ] */ public static function log(string $msg, array $data = [], int $level = self::VERB_DEBUG, array $opts = []): void { @@ -290,14 +304,8 @@ public static function log(string $msg, array $data = [], int $level = self::VER $optString = $userOpts ? ' ' . implode(' ', $userOpts) : ''; - self::write(sprintf( - '%s [%s]%s %s %s', - date('Y/m/d H:i:s'), - $taggedName, - $optString, - trim($msg), - $data ? PHP_EOL . json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) : '' - )); + self::write(sprintf('%s [%s]%s %s %s', date('Y/m/d H:i:s'), $taggedName, $optString, trim($msg), + $data ? PHP_EOL . json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) : '')); } /*********************************************************************************** @@ -310,6 +318,7 @@ public static function log(string $msg, array $data = [], int $level = self::VER * @param mixed $message * @param bool $nl * @param array $opts + * * @return string */ public static function read($message = null, bool $nl = false, array $opts = []): string @@ -332,9 +341,10 @@ public static function read($message = null, bool $nl = false, array $opts = []) * @param mixed $message * @param bool $nl * @param array $opts - * [ + * [ * 'stream' => \STDIN - * ] + * ] + * * @return string */ public static function readln($message = null, $nl = false, array $opts = []): string @@ -353,8 +363,10 @@ public static function readln($message = null, $nl = false, array $opts = []): s /** * Read input information - * @param mixed $message 若不为空,则先输出文本 - * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 + * + * @param mixed $message 若不为空,则先输出文本 + * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 + * * @return string */ public static function readRow($message = null, $nl = false): string @@ -368,6 +380,7 @@ public static function readRow($message = null, $nl = false): string * @param mixed $message * @param bool $nl * @param array $opts + * * @return string */ public static function readSafe($message = null, bool $nl = false, array $opts = []): string @@ -377,8 +390,8 @@ public static function readSafe($message = null, bool $nl = false, array $opts = } $opts = array_merge([ - 'length' => 1024, - 'stream' => STDIN, + 'length' => 1024, + 'stream' => STDIN, 'allowTags' => null, ], $opts); @@ -387,8 +400,10 @@ public static function readSafe($message = null, bool $nl = false, array $opts = /** * Gets first character from file pointer + * * @param string $message * @param bool $nl + * * @return string */ public static function readChar(string $message = '', bool $nl = false): string @@ -400,8 +415,10 @@ public static function readChar(string $message = '', bool $nl = false): string /** * Read input first char + * * @param string $message * @param bool $nl + * * @return string */ public static function readFirst(string $message = '', bool $nl = false): string @@ -455,12 +472,14 @@ public static function clearBuffer(): void /** * Stop buffering - * @see write() + * * @param bool $flush Whether flush buffer to output stream - * @param bool $nl Default is False, because the last write() have been added "\n" + * @param bool $nl Default is False, because the last write() have been added "\n" * @param bool $quit * @param array $opts + * * @return string If flush = False, will return all buffer text. + * @see write() */ public static function stopBuffer($flush = true, $nl = false, $quit = false, array $opts = []): string { @@ -482,10 +501,12 @@ public static function stopBuffer($flush = true, $nl = false, $quit = false, arr /** * Stop buffering and flush buffer text - * @see write() + * * @param bool $nl * @param bool $quit * @param array $opts + * + * @see write() */ public static function flushBuffer($nl = false, $quit = false, array $opts = []): void { diff --git a/src/Contract/ApplicationInterface.php b/src/Contract/ApplicationInterface.php index d705159c..074bf56b 100644 --- a/src/Contract/ApplicationInterface.php +++ b/src/Contract/ApplicationInterface.php @@ -27,33 +27,39 @@ interface ApplicationInterface /** * @param bool $exit + * * @return int|mixed */ public function run(bool $exit = true); /** * Dispatch input command, exec found command handler. - * @param string $name Inputted command name + * + * @param string $name Inputted command name * @param bool $standAlone Use for an group commands execution alone + * * @return int|mixed */ public function dispatch(string $name, bool $standAlone = false); /** * @param int $code + * * @return mixed */ public function stop(int $code = 0); /** * Register a app group command(by controller) - * @param string $name The controller name + * + * @param string $name The controller name * @param string|ControllerInterface $class The controller class * @param null|array|string $option - * string: define the description message. - * array: - * - aliases The command aliases - * - description The description message + * string: define the description message. + * array: + * - aliases The command aliases + * - description The description message + * * @return static * @throws InvalidArgumentException */ @@ -65,8 +71,8 @@ public function controller(string $name, $class = null, $option = null); * @param string|CommandInterface $name * @param string|Closure|CommandInterface $handler * @param null|array|string $option - * string: define the description message. - * array: + * string: define the description message. + * array: * - aliases The command aliases * - description The description message * diff --git a/src/Contract/CommandHandlerInterface.php b/src/Contract/CommandHandlerInterface.php index 4ef95b26..79705983 100644 --- a/src/Contract/CommandHandlerInterface.php +++ b/src/Contract/CommandHandlerInterface.php @@ -9,12 +9,11 @@ namespace Inhere\Console\Contract; use Inhere\Console\AbstractApplication; -use Inhere\Console\IO\Input; use Inhere\Console\IO\InputDefinition; -use Inhere\Console\IO\Output; /** * Interface CommandHandlerInterface + * * @package Inhere\Console\Contract */ interface CommandHandlerInterface @@ -31,7 +30,9 @@ interface CommandHandlerInterface /** * Run command + * * @param string $command + * * @return int|mixed return int is exit code. other is command exec result. */ public function run(string $command = ''); diff --git a/src/Contract/CommandInterface.php b/src/Contract/CommandInterface.php index feabe6f0..1aaf9ff1 100644 --- a/src/Contract/CommandInterface.php +++ b/src/Contract/CommandInterface.php @@ -10,6 +10,7 @@ /** * Interface CommandInterface + * * @package Inhere\Console\Contract */ interface CommandInterface extends CommandHandlerInterface diff --git a/src/Contract/ControllerInterface.php b/src/Contract/ControllerInterface.php index da9288b4..bc13579b 100644 --- a/src/Contract/ControllerInterface.php +++ b/src/Contract/ControllerInterface.php @@ -10,6 +10,7 @@ /** * interface ControllerInterface + * * @package Inhere\Console\Contract */ interface ControllerInterface diff --git a/src/Contract/ErrorHandlerInterface.php b/src/Contract/ErrorHandlerInterface.php index f186cdf8..aa29b760 100644 --- a/src/Contract/ErrorHandlerInterface.php +++ b/src/Contract/ErrorHandlerInterface.php @@ -14,12 +14,13 @@ /** * Interface ErrorHandlerInterface + * * @package Inhere\Console\Contract */ interface ErrorHandlerInterface { /** - * @param Throwable $e + * @param Throwable $e * @param Application|AbstractApplication $app */ public function handle(Throwable $e, AbstractApplication $app): void; diff --git a/src/Contract/FormatterInterface.php b/src/Contract/FormatterInterface.php index 542e29b9..49f25d97 100644 --- a/src/Contract/FormatterInterface.php +++ b/src/Contract/FormatterInterface.php @@ -10,6 +10,7 @@ /** * Interface FormatterInterface + * * @package Inhere\Console\Contract */ interface FormatterInterface diff --git a/src/Controller.php b/src/Controller.php index 95dfde55..faff2e57 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -37,6 +37,7 @@ /** * Class Controller + * * @package Inhere\Console */ abstract class Controller extends AbstractHandler implements ControllerInterface @@ -75,6 +76,7 @@ abstract class Controller extends AbstractHandler implements ControllerInterface /** * define command alias map + * * @return array */ protected static function commandAliases(): array @@ -91,7 +93,7 @@ protected function init(): void // save to property $this->disabledCommands = $list ? array_flip($list) : []; self::$commandAliases = static::commandAliases(); - $this->groupOptions = $this->groupOptions(); + $this->groupOptions = $this->groupOptions(); if (!$this->actionSuffix) { $this->actionSuffix = 'Command'; @@ -106,24 +108,24 @@ protected function init(): void */ protected function groupOptions(): array { - return [ - // '--skip-invalid' => 'Whether ignore invalid arguments and options, when use input definition', + return [// '--skip-invalid' => 'Whether ignore invalid arguments and options, when use input definition', ]; } /** * define disabled command list. + * * @return array */ protected function disabledCommands(): array { - return [ - // 'command1', 'command2' + return [// 'command1', 'command2' ]; } /** * @param string $command + * * @return int|mixed * @throws ReflectionException */ @@ -158,8 +160,9 @@ protected function configure(): void /** * Run command action in the group * - * @param Input $input - * @param Output $output + * @param Input $input + * @param Output $output + * * @return mixed * @throws ReflectionException */ @@ -243,9 +246,12 @@ public function getGroupOptions(): array /** * Show help of the controller command group or specified command action * @usage {name}:[command] -h OR {command} [command] OR {name} [command] -h + * * @options * -s, --search Search command by input keywords * --format Set the help information dump format(raw, xml, json, markdown) + * @return int + * @throws ReflectionException * @example * {script} {name} -h * {script} {name}:help @@ -253,8 +259,6 @@ public function getGroupOptions(): array * {script} {name}:index -h * {script} {name} index * - * @return int - * @throws ReflectionException */ final public function helpCommand(): int { @@ -281,6 +285,7 @@ protected function beforeShowCommandList() /** * Display all sub-commands list of the controller class + * * @throws ReflectionException */ final public function showCommandList(): void @@ -356,17 +361,15 @@ final public function showCommandList(): void 'sepChar' => ' ', ]); - $this->write(sprintf( - 'More information about a command, please use: %s %s{command} -h', - $script, - $this->executionAlone ? '' : $name - )); + $this->write(sprintf('More information about a command, please use: %s %s{command} -h', $script, + $this->executionAlone ? '' : $name)); $this->output->flush(); } /** * @param ReflectionClass|null $ref - * @param bool $onlyName + * @param bool $onlyName + * * @return Generator */ protected function getAllCommandMethods(ReflectionClass $ref = null, bool $onlyName = false): ?Generator @@ -394,6 +397,7 @@ protected function getAllCommandMethods(ReflectionClass $ref = null, bool $onlyN /** * @param string $name + * * @return mixed|string */ protected function getRealCommandName(string $name) @@ -409,6 +413,7 @@ protected function getRealCommandName(string $name) /** * @param string $name + * * @return bool */ public function isDisabled(string $name): bool @@ -430,6 +435,7 @@ public function getDisabledCommands(): array /** * @param string|null $name + * * @return array */ public function getCommandAliases(string $name = ''): array @@ -451,6 +457,7 @@ public function getAction(): string /** * @param string $action + * * @return $this */ public function setAction(string $action): self diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index 2537b9ee..ed881a9f 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -8,9 +8,9 @@ namespace Inhere\Console\IO; +use InvalidArgumentException; use function array_merge; use function getcwd; -use InvalidArgumentException; use function is_bool; use function is_int; use function trim; @@ -30,6 +30,7 @@ abstract class AbstractInput implements InputInterface /** * the script name * e.g `./bin/app` OR `bin/cli.php` + * * @var string */ protected $script; @@ -37,36 +38,42 @@ abstract class AbstractInput implements InputInterface /** * the command name(Is first argument) * e.g `start` OR `start` + * * @var string */ protected $command = ''; /** * eg `./examples/app home:useArg status=2 name=john arg0 -s=test --page=23` + * * @var string */ protected $fullScript; /** * raw argv data. + * * @var array */ protected $tokens; /** * Input args data + * * @var array */ protected $args = []; /** * Input short-opts data + * * @var array */ protected $sOpts = []; /** * Input long-opts data + * * @var array */ protected $lOpts = []; @@ -139,6 +146,7 @@ public function setArgs(array $args, $replace = false): void /** * @param string|int $name + * * @return bool */ public function hasArg($name): bool @@ -148,8 +156,10 @@ public function hasArg($name): bool /** * get Argument + * * @param null|int|string $name * @param mixed $default + * * @return mixed */ public function getArgument($name, $default = null) @@ -159,8 +169,10 @@ public function getArgument($name, $default = null) /** * get Argument + * * @param null|int|string $name * @param mixed $default + * * @return mixed */ public function getArg($name, $default = null) @@ -170,8 +182,10 @@ public function getArg($name, $default = null) /** * get Argument + * * @param null|int|string $name * @param mixed $default + * * @return mixed */ public function get($name, $default = null) @@ -181,7 +195,9 @@ public function get($name, $default = null) /** * get a required argument + * * @param int|string $name argument index + * * @return mixed * @throws InvalidArgumentException */ @@ -196,7 +212,9 @@ public function getRequiredArg($name) /** * get first argument + * * @param string $default + * * @return string */ public function getFirstArg(string $default = ''): string @@ -206,7 +224,9 @@ public function getFirstArg(string $default = ''): string /** * get second argument + * * @param string $default + * * @return string */ public function getSecondArg(string $default = ''): string @@ -217,6 +237,7 @@ public function getSecondArg(string $default = ''): string /** * @param string|int $key * @param int $default + * * @return int */ public function getInt($key, $default = 0): int @@ -236,6 +257,7 @@ public function getInt($key, $default = 0): int * * @param array $names * @param mixed $default + * * @return bool|mixed|null */ public function getSameArg(array $names, $default = null) @@ -246,6 +268,7 @@ public function getSameArg(array $names, $default = null) /** * @param array $names * @param mixed $default + * * @return mixed */ public function sameArg(array $names, $default = null) @@ -274,8 +297,10 @@ public function clearArgs(): void /** * get (long/short)opt value * eg: -e dev --name sam + * * @param string $name * @param null $default + * * @return bool|mixed|null */ public function getOpt(string $name, $default = null) @@ -290,8 +315,10 @@ public function getOpt(string $name, $default = null) /** * alias of the getOpt() + * * @param string $name * @param mixed $default + * * @return mixed */ public function getOption(string $name, $default = null) @@ -301,7 +328,9 @@ public function getOption(string $name, $default = null) /** * get a required argument + * * @param string $name + * * @return mixed * @throws InvalidArgumentException */ @@ -317,8 +346,10 @@ public function getRequiredOpt(string $name) /** * get (long/short)opt value(bool) * eg: -h --help + * * @param string $name * @param bool $default + * * @return bool */ public function getBoolOpt(string $name, bool $default = false): bool @@ -333,7 +364,9 @@ public function boolOpt(string $name, bool $default = false): bool /** * check option exists + * * @param $name + * * @return bool */ public function hasOpt(string $name): bool @@ -351,6 +384,7 @@ public function hasOpt(string $name): bool * * @param array $names * @param mixed $default + * * @return bool|mixed|null */ public function getSameOpt(array $names, $default = null) @@ -397,8 +431,10 @@ public function clearOpts(): void /** * get short-opt value + * * @param string $name * @param null $default + * * @return mixed|null */ public function sOpt(string $name, $default = null) @@ -413,7 +449,9 @@ public function getShortOpt(string $name, $default = null) /** * check short-opt exists + * * @param $name + * * @return bool */ public function hasSOpt(string $name): bool @@ -423,8 +461,10 @@ public function hasSOpt(string $name): bool /** * get short-opt value(bool) + * * @param string $name * @param bool $default + * * @return bool */ public function sBoolOpt(string $name, $default = false): bool @@ -480,8 +520,10 @@ public function clearSOpts(): void /** * get long-opt value + * * @param string $name * @param null $default + * * @return mixed|null */ public function lOpt(string $name, $default = null) @@ -492,6 +534,7 @@ public function lOpt(string $name, $default = null) /** * @param string $name * @param null $default + * * @return mixed|null */ public function getLongOpt(string $name, $default = null) @@ -501,7 +544,9 @@ public function getLongOpt(string $name, $default = null) /** * check long-opt exists + * * @param $name + * * @return bool */ public function hasLOpt(string $name): bool @@ -511,8 +556,10 @@ public function hasLOpt(string $name): bool /** * get long-opt value(bool) + * * @param string $name * @param bool $default + * * @return bool */ public function lBoolOpt(string $name, $default = false): bool diff --git a/src/IO/Input.php b/src/IO/Input.php index 54abf75d..23ea8a72 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -159,7 +159,7 @@ public function getCommandId(): string /** * Set command ID e.g `http:start` * - * @param string $commandId e.g `http:start` + * @param string $commandId e.g `http:start` * * @return void */ diff --git a/src/IO/Input/ArrayInput.php b/src/IO/Input/ArrayInput.php index 03ce1b95..3b191dba 100644 --- a/src/IO/Input/ArrayInput.php +++ b/src/IO/Input/ArrayInput.php @@ -8,19 +8,21 @@ namespace Inhere\Console\IO\Input; -use function array_shift; -use function implode; use Inhere\Console\IO\Input; use Toolkit\Cli\Flags; +use function array_shift; +use function implode; /** * Class ArrayInput + * * @package Inhere\Console\IO\Input */ class ArrayInput extends Input { /** * Input constructor. + * * @param null|array $args * @param bool $parsing */ @@ -28,8 +30,8 @@ public function __construct(array $args = null, bool $parsing = true) { parent::__construct([], false); - $this->tokens = $args; - $this->script = array_shift($args); + $this->tokens = $args; + $this->script = array_shift($args); $this->fullScript = implode(' ', $args); if ($parsing && $args) { diff --git a/src/IO/Input/InputArgument.php b/src/IO/Input/InputArgument.php index ac357823..68f25d52 100644 --- a/src/IO/Input/InputArgument.php +++ b/src/IO/Input/InputArgument.php @@ -11,6 +11,7 @@ /** * Class InputArgument * - definition a input argument + * * @package Inhere\Console\IO\Input */ class InputArgument extends InputItem diff --git a/src/IO/Input/InputArguments.php b/src/IO/Input/InputArguments.php index bd5b9a4e..52050e71 100644 --- a/src/IO/Input/InputArguments.php +++ b/src/IO/Input/InputArguments.php @@ -11,6 +11,7 @@ /** * Class InputArguments * - input arguments builder + * * @package Inhere\Console\IO\Input */ class InputArguments diff --git a/src/IO/Input/InputItem.php b/src/IO/Input/InputItem.php index 14d5d68d..6a704c28 100644 --- a/src/IO/Input/InputItem.php +++ b/src/IO/Input/InputItem.php @@ -13,6 +13,7 @@ /** * Class InputItem * - definition a input item(option|argument) + * * @package Inhere\Console\IO\Input */ class InputItem @@ -34,12 +35,14 @@ class InputItem /** * The argument data type. (eg: 'string', 'array', 'mixed') + * * @var string */ public $type; /** * default value + * * @var mixed */ public $default; @@ -49,6 +52,7 @@ class InputItem * @param int|null $mode * @param string $description * @param null $default + * * @return static */ public static function make(string $name, int $mode = null, string $description = '', $default = null) @@ -58,12 +62,13 @@ public static function make(string $name, int $mode = null, string $description /** * class constructor. + * * @param string $name * @param int|null $mode * @param string $description * @param mixed $default The default value - * - for InputArgument::OPTIONAL mode only - * - must be null for InputOption::OPT_BOOL + * - for InputArgument::OPTIONAL mode only + * - must be null for InputOption::OPT_BOOL */ public function __construct(string $name, int $mode = null, string $description = '', $default = null) { diff --git a/src/IO/Input/InputOption.php b/src/IO/Input/InputOption.php index 15fa04fe..447050e0 100644 --- a/src/IO/Input/InputOption.php +++ b/src/IO/Input/InputOption.php @@ -11,12 +11,14 @@ /** * Class InputOption * - definition a input option + * * @package Inhere\Console\IO\Input */ class InputOption extends InputItem { /** * alias name + * * @var string */ public $alias; diff --git a/src/IO/Input/InputOptions.php b/src/IO/Input/InputOptions.php index 09d19580..47d30c67 100644 --- a/src/IO/Input/InputOptions.php +++ b/src/IO/Input/InputOptions.php @@ -11,9 +11,10 @@ /** * Class InputOptions * - input options builder + * * @package Inhere\Console\IO\Input */ class InputOptions { -} \ No newline at end of file +} diff --git a/src/IO/InputDefinition.php b/src/IO/InputDefinition.php index bb6137ef..a3413b91 100644 --- a/src/IO/InputDefinition.php +++ b/src/IO/InputDefinition.php @@ -8,15 +8,15 @@ namespace Inhere\Console\IO; +use InvalidArgumentException; +use LogicException; use function array_filter; use function array_merge; use function array_values; use function count; use function implode; -use InvalidArgumentException; use function is_array; use function is_int; -use LogicException; use function preg_split; use function sprintf; use function strpos; @@ -59,6 +59,7 @@ class InputDefinition /** * @param array $arguments * @param array $options + * * @return InputDefinition */ public static function make(array $arguments = [], array $options = []): InputDefinition @@ -71,6 +72,7 @@ public static function make(array $arguments = [], array $options = []): InputDe * * @param array $arguments * @param array $options + * * @throws LogicException * @throws InvalidArgumentException */ @@ -86,6 +88,7 @@ public function __construct(array $arguments = [], array $options = []) /** * @param array $arguments + * * @return InputDefinition * @throws LogicException */ @@ -98,6 +101,7 @@ public function setArguments(array $arguments): InputDefinition /** * @param array $arguments + * * @return $this * @throws LogicException */ @@ -113,10 +117,12 @@ public function addArguments(array $arguments): self /** * alias of the addArgument + * * @param string $name * @param int|null $mode * @param string $description * @param null $default + * * @return InputDefinition */ public function addArg(string $name, int $mode = null, string $description = '', $default = null): self @@ -127,10 +133,11 @@ public function addArg(string $name, int $mode = null, string $description = '', /** * Adds an argument. * - * @param string $name The argument name - * @param int $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL + * @param string $name The argument name + * @param int $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL * @param string $description A description text - * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) + * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) + * * @return $this * @throws LogicException */ @@ -192,6 +199,7 @@ public function addArgument(string $name, int $mode = null, string $description /** * @param int|string $name * @param null $default + * * @return string|int|null */ public function getArgument($name, $default = null) @@ -208,6 +216,7 @@ public function getArgument($name, $default = null) /** * @param string|int $name The argument name or position + * * @return bool true if the InputArgument object exists, false otherwise */ public function hasArgument($name): bool @@ -227,6 +236,7 @@ public function getArguments(): array /** * Returns the number of arguments. + * * @return int */ public function getArgumentCount(): int @@ -236,6 +246,7 @@ public function getArgumentCount(): int /** * get count of required arguments + * * @return int */ public function getArgumentRequiredCount(): int @@ -251,6 +262,7 @@ public function getArgumentRequiredCount(): int * Sets the options * * @param array[] $options An array of InputOption objects + * * @throws LogicException * @throws InvalidArgumentException */ @@ -264,6 +276,7 @@ public function setOptions(array $options = []): void * Adds an array of option * * @param array + * * @throws LogicException * @throws InvalidArgumentException */ @@ -278,6 +291,7 @@ public function addOptions(array $options = []): void /** * alias of the addOption * {@inheritdoc} + * * @return InputDefinition */ public function addOpt( @@ -293,11 +307,11 @@ public function addOpt( /** * Adds an option. * - * @param string|bool $name The option name, must is a string - * @param string|array|null $shortcut The shortcut (can be null) - * @param int $mode The option mode: One of the Input::OPT_* constants + * @param string|bool $name The option name, must is a string + * @param string|array|null $shortcut The shortcut (can be null) + * @param int $mode The option mode: One of the Input::OPT_* constants * @param string $description A description text - * @param mixed $default The default value (must be null for InputOption::OPT_BOOL) + * @param mixed $default The default value (must be null for InputOption::OPT_BOOL) * * @return $this * @throws InvalidArgumentException @@ -360,7 +374,7 @@ public function addOption( $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); $shortcuts = array_filter($shortcuts); - $shortcut = implode('|', $shortcuts); + $shortcut = implode('|', $shortcuts); foreach ($shortcuts as $srt) { if (isset($this->shortcuts[$srt])) { @@ -385,6 +399,7 @@ public function addOption( /** * @param string $name + * * @return array * @throws InvalidArgumentException */ @@ -399,6 +414,7 @@ public function getOption(string $name): array /** * @param string $name + * * @return bool */ public function hasOption(string $name): bool @@ -418,6 +434,7 @@ public function getOptions(): array /** * @param string $name The InputOption shortcut + * * @return bool */ public function hasShortcut(string $name): bool @@ -427,7 +444,9 @@ public function hasShortcut(string $name): bool /** * Gets an option info array + * * @param string $shortcut the Shortcut name + * * @return array * @throws InvalidArgumentException */ @@ -438,6 +457,7 @@ public function getOptionByShortcut(string $shortcut): array /** * @param string $shortcut + * * @return mixed * @throws InvalidArgumentException */ @@ -452,6 +472,7 @@ private function shortcutToName(string $shortcut) /** * @param array $map + * * @return array */ private function mergeArgOptConfig(array $map): array @@ -461,7 +482,9 @@ private function mergeArgOptConfig(array $map): array /** * Gets the synopsis. + * * @param bool $short 简化版显示 + * * @return array */ public function getSynopsis(bool $short = false): array @@ -475,18 +498,14 @@ public function getSynopsis(bool $short = false): array $value = ''; if ($this->optionIsAcceptValue($option['mode'])) { - $value = sprintf( - ' %s%s%s', - $option['optional'] ? '[' : '', - strtoupper($name), - $option['optional'] ? ']' : '' - ); + $value = sprintf(' %s%s%s', $option['optional'] ? '[' : '', strtoupper($name), + $option['optional'] ? ']' : ''); } - $shortcut = $option['shortcut'] ? sprintf('-%s, ', $option['shortcut']) : ' '; + $shortcut = $option['shortcut'] ? sprintf('-%s, ', $option['shortcut']) : ' '; $elements[] = sprintf('[%s--%s%s]', $shortcut, $name, $value); - $key = "{$shortcut}--{$name}"; + $key = "{$shortcut}--{$name}"; $opts[$key] = ($option['required'] ? '*' : '') . $option['description']; } } @@ -509,7 +528,7 @@ public function getSynopsis(bool $short = false): array $element .= '...'; } - $elements[] = $element; + $elements[] = $element; $args[$name] = $des; } @@ -525,6 +544,7 @@ public function getSynopsis(bool $short = false): array /** * @param string $name + * * @return bool */ public function argumentIsRequired($name): bool @@ -538,6 +558,7 @@ public function argumentIsRequired($name): bool /** * @param int $mode + * * @return bool */ protected function argumentIsAcceptValue(int $mode): bool @@ -547,6 +568,7 @@ protected function argumentIsAcceptValue(int $mode): bool /** * @param int $mode + * * @return bool */ protected function optionIsAcceptValue(int $mode): bool @@ -564,6 +586,7 @@ public function getExample() /** * @param string|array $example + * * @return $this */ public function setExample($example): self @@ -582,6 +605,7 @@ public function getDescription(): ?string /** * @param string $description + * * @return $this */ public function setDescription(string $description): self diff --git a/src/IO/InputInterface.php b/src/IO/InputInterface.php index 81b1b2ac..9336f3bb 100644 --- a/src/IO/InputInterface.php +++ b/src/IO/InputInterface.php @@ -10,6 +10,7 @@ /** * Class Input + * * @package Inhere\Console\IO */ interface InputInterface @@ -26,8 +27,10 @@ interface InputInterface /** * 读取输入信息 - * @param string $question 若不为空,则先输出文本消息 - * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 + * + * @param string $question 若不为空,则先输出文本消息 + * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 + * * @return string */ public function read(string $question = '', bool $nl = false): string; @@ -49,8 +52,10 @@ public function getArgs(): array; /** * get Argument + * * @param null|int|string $name * @param mixed $default + * * @return mixed */ public function getArg($name, $default = null); @@ -63,6 +68,7 @@ public function getOpts(): array; /** * @param string $name * @param null $default + * * @return bool|mixed|null */ public function getOpt(string $name, $default = null); diff --git a/src/IO/Output.php b/src/IO/Output.php index 5ba86ba5..c495c8c6 100644 --- a/src/IO/Output.php +++ b/src/IO/Output.php @@ -18,6 +18,7 @@ /** * Class Output + * * @package Inhere\Console\IO */ class Output implements OutputInterface @@ -41,12 +42,14 @@ class Output implements OutputInterface /** * 控制台窗口(字体/背景)颜色添加处理 * window colors + * * @var Style */ protected $style; /** * Output constructor. + * * @param null|resource $outputStream */ public function __construct($outputStream = null) @@ -81,6 +84,7 @@ public function clearBuffer(): void /** * stop buffering and flush buffer text * {@inheritdoc} + * * @see Console::stopBuffer() */ public function stopBuffer(bool $flush = true, $nl = false, $quit = false, array $opts = []): void @@ -103,8 +107,10 @@ public function flush(bool $nl = false, $quit = false, array $opts = []): void /** * Read input information - * @param string $question 若不为空,则先输出文本 - * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 + * + * @param string $question 若不为空,则先输出文本 + * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 + * * @return string */ public function read(string $question = '', bool $nl = false): string @@ -114,8 +120,10 @@ public function read(string $question = '', bool $nl = false): string /** * Write a message to standard error output stream. + * * @param string $text * @param boolean $nl True (default) to append a new line at the end of the output string. + * * @return int */ public function stderr(string $text = '', $nl = true): int @@ -159,7 +167,9 @@ public function getOutputStream() /** * setOutStream + * * @param $outStream + * * @return $this */ public function setOutputStream($outStream): self @@ -179,7 +189,9 @@ public function getErrorStream() /** * Method to set property errorStream + * * @param $errorStream + * * @return $this */ public function setErrorStream($errorStream): self diff --git a/src/IO/OutputInterface.php b/src/IO/OutputInterface.php index 103546f8..ce9f218e 100644 --- a/src/IO/OutputInterface.php +++ b/src/IO/OutputInterface.php @@ -10,15 +10,18 @@ /** * Class OutputInterface + * * @package Inhere\Console\IO */ interface OutputInterface { /** * Write a message to standard output stream. - * @param mixed $messages Output message - * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 - * @param int|boolean $quit If is int, setting it is exit code. + * + * @param mixed $messages Output message + * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 + * @param int|boolean $quit If is int, setting it is exit code. + * * @return int */ public function write($messages, $nl = true, $quit = false): int; diff --git a/src/IO/StrictInput.php b/src/IO/StrictInput.php index 60be62b2..7e130cb3 100644 --- a/src/IO/StrictInput.php +++ b/src/IO/StrictInput.php @@ -24,6 +24,7 @@ class StrictInput extends Input { /** * the prepare parsed options. + * * @see AbstractApplication::$globalOptions * @var array */ @@ -42,6 +43,7 @@ class StrictInput extends Input /** * FixedInput constructor. + * * @param null|array $args */ public function __construct(array $args = null) diff --git a/src/Router.php b/src/Router.php index a70c241e..fdfafdcd 100644 --- a/src/Router.php +++ b/src/Router.php @@ -29,6 +29,7 @@ /** * Class Router - match input command find command handler + * * @package Inhere\Console */ class Router implements RouterInterface @@ -42,12 +43,14 @@ class Router implements RouterInterface /** * Command delimiter char. e.g dev:serve + * * @var string */ private $delimiter = ':'; // '/' ':' /** * The independent commands + * * @var array * [ * 'name' => [ @@ -60,6 +63,7 @@ class Router implements RouterInterface /** * The group commands(controller) + * * @var array * [ * 'name' => [ @@ -76,12 +80,14 @@ class Router implements RouterInterface /** * Register a app group command(by controller) - * @param string $name The controller name + * + * @param string $name The controller name * @param string|ControllerInterface $class The controller class * @param array $options - * array: - * - aliases The command aliases - * - description The description message + * array: + * - aliases The command aliases + * - description The description message + * * @return Router * @throws InvalidArgumentException */ @@ -96,11 +102,8 @@ public function addGroup(string $name, $class = null, array $options = []): Rout } if (!$name || !$class) { - Helper::throwInvalidArgument( - 'Group-command "name" and "controller" cannot be empty! name: %s, controller: %s', - $name, - $class - ); + Helper::throwInvalidArgument('Group-command "name" and "controller" cannot be empty! name: %s, controller: %s', + $name, $class); } $this->validateName($name); @@ -145,7 +148,7 @@ public function addGroup(string $name, $class = null, array $options = []): Rout * @param string|CommandInterface $name * @param string|Closure|CommandInterface $handler * @param array $options - * array: + * array: * - aliases The command aliases * - description The description message * @@ -194,10 +197,8 @@ public function addCommand(string $name, $handler = null, array $options = []): $options['aliases'] = array_merge($options['aliases'], $aliases); } } elseif (!is_object($handler) || !method_exists($handler, '__invoke')) { - Helper::throwInvalidArgument( - 'The console command handler must is an subclass of %s OR a Closure OR a object have method __invoke()', - Command::class - ); + Helper::throwInvalidArgument('The console command handler must is an subclass of %s OR a Closure OR a object have method __invoke()', + Command::class); } // is an class name string @@ -217,6 +218,7 @@ public function addCommand(string $name, $handler = null, array $options = []): /** * @param array $commands + * * @throws InvalidArgumentException */ public function addCommands(array $commands): void @@ -232,6 +234,7 @@ public function addCommands(array $commands): void /** * @param array $controllers + * * @throws InvalidArgumentException */ public function addControllers(array $controllers): void @@ -251,6 +254,7 @@ public function addControllers(array $controllers): void /** * @param string $name The input command name + * * @return array return route info array. If not found, will return empty array. * [ * type => 1, // 1 group 2 command @@ -307,6 +311,7 @@ public function match(string $name): array /** * @param $name + * * @throws InvalidArgumentException */ protected function validateName(string $name): void @@ -371,6 +376,7 @@ public function getControllers(): array /** * @param $name + * * @return bool */ public function isController(string $name): bool @@ -388,6 +394,7 @@ public function getCommands(): array /** * @param $name + * * @return bool */ public function isCommand(string $name): bool @@ -397,6 +404,7 @@ public function isCommand(string $name): bool /** * @param string $name + * * @return bool */ public function isBlocked(string $name): bool diff --git a/src/Traits/SimpleEventTrait.php b/src/Traits/SimpleEventTrait.php index 125a72b8..bec5b1ad 100644 --- a/src/Traits/SimpleEventTrait.php +++ b/src/Traits/SimpleEventTrait.php @@ -79,7 +79,7 @@ public function once(string $event, callable $handler): void * trigger event * * @param string $event - * @param array ...$args + * @param mixed ...$args * * @return bool */ diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index 5bbd27be..553e593a 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -8,6 +8,8 @@ namespace Inhere\Console\Util; +use Toolkit\Cli\ColorTag; +use Toolkit\Sys\Sys; use function array_keys; use function array_merge; use function count; @@ -27,20 +29,20 @@ use function str_repeat; use function str_replace; use function strpos; -use Toolkit\Cli\ColorTag; -use Toolkit\Sys\Sys; use function trim; use function ucfirst; use function wordwrap; /** * Class FormatUtil + * * @package Inhere\Console\Util */ final class FormatUtil { /** * @param mixed $val + * * @return string */ public static function typeToString($val): string @@ -60,6 +62,7 @@ public static function typeToString($val): string * @param string $string * @param int $indent * @param string $indentChar + * * @return string */ public static function applyIndent(string $string, int $indent = 2, string $indentChar = ' '): string @@ -68,8 +71,8 @@ public static function applyIndent(string $string, int $indent = 2, string $inde return $string; } - $new = ''; - $list = explode("\n", $string); + $new = ''; + $list = explode("\n", $string); $indentStr = str_repeat($indentChar ?: ' ', $indent); foreach ($list as $value) { @@ -93,9 +96,10 @@ public static function applyIndent(string $string, int $indent = 2, string $inde * amet. * ``` * - * @param string $text the text to be wrapped + * @param string $text the text to be wrapped * @param integer $indent number of spaces to use for indentation. * @param integer $width + * * @return string the wrapped text. * @from yii2 */ @@ -115,7 +119,7 @@ public static function wrapText($text, $indent = 0, $width = 0): string $width = $size[0]; } - $pad = str_repeat(' ', $indent); + $pad = str_repeat(' ', $indent); $lines = explode("\n", wordwrap($text, $width - $indent, "\n", true)); $first = true; @@ -132,6 +136,7 @@ public static function wrapText($text, $indent = 0, $width = 0): string /** * @param array $options + * * @return array */ public static function alignOptions(array $options): array @@ -164,6 +169,7 @@ public static function alignOptions(array $options): array /** * @param float $memory + * * @return string * ``` * FormatUtil::memoryUsage(memory_get_usage(true)); @@ -188,7 +194,9 @@ public static function memoryUsage($memory): string /** * format timestamp to how long ago - * @param int $secs + * + * @param int $secs + * * @return string */ public static function howLongAgo(int $secs): string @@ -224,12 +232,14 @@ public static function howLongAgo(int $secs): string /** * splice Array - * @param array $data - * e.g [ + * + * @param array $data + * e.g [ * 'system' => 'Linux', * 'version' => '4.4.5', - * ] - * @param array $opts + * ] + * @param array $opts + * * @return string */ public static function spliceKeyValue(array $data, array $opts = []): string @@ -258,10 +268,10 @@ public static function spliceKeyValue(array $data, array $opts = []): string foreach ($data as $key => $value) { $hasKey = !is_int($key); - $text .= $opts['leftChar']; + $text .= $opts['leftChar']; if ($hasKey && $opts['keyMaxWidth']) { - $key = str_pad($key, $opts['keyMaxWidth'], ' '); + $key = str_pad($key, $opts['keyMaxWidth'], ' '); $text .= ColorTag::wrap($key, $keyStyle) . $opts['sepChar']; } @@ -288,7 +298,7 @@ public static function spliceKeyValue(array $data, array $opts = []): string } $value = $hasKey && $opts['ucFirst'] ? ucfirst($value) : $value; - $text .= ColorTag::wrap($value, $opts['valStyle']) . "\n"; + $text .= ColorTag::wrap($value, $opts['valStyle']) . "\n"; } return $text; diff --git a/src/Util/Helper.php b/src/Util/Helper.php index ba585d6e..3da5ff0d 100644 --- a/src/Util/Helper.php +++ b/src/Util/Helper.php @@ -10,28 +10,29 @@ namespace Inhere\Console\Util; -use function class_exists; -use function file_exists; use FilesystemIterator; use Inhere\Console\Traits\RuntimeProfileTrait; use InvalidArgumentException; -use function is_dir; -use function is_numeric; use Iterator; -use function mb_strlen; -use function mkdir; -use function preg_match; use RecursiveCallbackFilterIterator; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use RuntimeException; +use Swoole\Coroutine; +use function class_exists; +use function file_exists; +use function is_dir; +use function is_numeric; +use function mb_strlen; +use function mkdir; +use function preg_match; use function similar_text; use function sprintf; use function strpos; -use Swoole\Coroutine; /** * Class Helper + * * @package Inhere\Console\Util */ class Helper @@ -60,6 +61,7 @@ public static function inCoroutine(): bool /** * @param string $path + * * @return bool */ public static function isAbsPath(string $path): bool @@ -70,6 +72,7 @@ public static function isAbsPath(string $path): bool /** * @param string $dir * @param int $mode + * * @throws RuntimeException */ public static function mkdir(string $dir, int $mode = 0775): void @@ -83,6 +86,7 @@ public static function mkdir(string $dir, int $mode = 0775): void * @param string $srcDir * @param callable $filter * @param int $flags + * * @return RecursiveIteratorIterator * @throws InvalidArgumentException */ @@ -95,7 +99,7 @@ public static function directoryIterator( throw new InvalidArgumentException('Please provide a exists source directory.'); } - $directory = new RecursiveDirectoryIterator($srcDir, $flags); + $directory = new RecursiveDirectoryIterator($srcDir, $flags); $filterIterator = new RecursiveCallbackFilterIterator($directory, $filter); return new RecursiveIteratorIterator($filterIterator); @@ -112,9 +116,11 @@ public static function commandSearch(string $command, array $map): void /** * find similar text from an array|Iterator - * @param string $need + * + * @param string $need * @param Iterator|array $iterator - * @param int $similarPercent + * @param int $similarPercent + * * @return array */ public static function findSimilar(string $need, $iterator, int $similarPercent = 45): array @@ -139,12 +145,14 @@ public static function findSimilar(string $need, $iterator, int $similarPercent /** * get key Max Width - * @param array $data - * [ + * + * @param array $data + * [ * 'key1' => 'value1', * 'key2-test' => 'value2', - * ] - * @param bool $expectInt + * ] + * @param bool $expectInt + * * @return int */ public static function getKeyMaxWidth(array $data, bool $expectInt = false): int @@ -154,7 +162,7 @@ public static function getKeyMaxWidth(array $data, bool $expectInt = false): int foreach ($data as $key => $value) { // key is not a integer if (!$expectInt || !is_numeric($key)) { - $width = mb_strlen($key, 'UTF-8'); + $width = mb_strlen($key, 'UTF-8'); $keyMaxWidth = $width > $keyMaxWidth ? $width : $keyMaxWidth; } } diff --git a/src/Util/Interact.php b/src/Util/Interact.php index 6762f340..055d59c2 100644 --- a/src/Util/Interact.php +++ b/src/Util/Interact.php @@ -18,24 +18,27 @@ use Inhere\Console\Console; use RuntimeException; use function sprintf; -use const STDIN; use function strtolower; use function trim; +use const STDIN; /** * Class Interact + * * @package Inhere\Console\Util */ class Interact extends Show { /** * read line from CLI input + * * @param mixed $message * @param bool $nl * @param array $opts - * [ + * [ * 'stream' => \STDIN - * ] + * ] + * * @return string */ public static function readln($message = null, $nl = false, array $opts = []): string @@ -51,8 +54,10 @@ public static function readln($message = null, $nl = false, array $opts = []): s /** * 读取输入信息 - * @param mixed $message 若不为空,则先输出文本 - * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 + * + * @param mixed $message 若不为空,则先输出文本 + * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 + * * @return string */ public static function readRow($message = null, $nl = false): string @@ -63,6 +68,7 @@ public static function readRow($message = null, $nl = false): string /** * @param null|mixed $message * @param bool $nl + * * @return string */ public static function readFirst($message = null, $nl = false): string @@ -82,10 +88,12 @@ public static function readFirst($message = null, $nl = false): string /** * alias of the `select()` - * @param string $description 说明 - * @param string|array $options 选项数据 - * @param int|string $default 默认选项 - * @param bool $allowExit 有退出选项 默认 true + * + * @param string $description 说明 + * @param string|array $options 选项数据 + * @param int|string $default 默认选项 + * @param bool $allowExit 有退出选项 默认 true + * * @return string */ public static function select(string $description, $options, $default = null, bool $allowExit = true): string @@ -95,16 +103,18 @@ public static function select(string $description, $options, $default = null, bo /** * Choose one of several options + * * @param string $description * @param string|array $options Option data - * e.g - * [ - * // option => value - * '1' => 'chengdu', - * '2' => 'beijing' - * ] + * e.g + * [ + * // option => value + * '1' => 'chengdu', + * '2' => 'beijing' + * ] * @param string|int $default Default option * @param bool $allowExit + * * @return string */ public static function choice(string $description, $options, $default = null, bool $allowExit = true): string @@ -114,10 +124,12 @@ public static function choice(string $description, $options, $default = null, bo /** * alias of the `multiSelect()` + * * @param string $description * @param string|array $options * @param null|mixed $default * @param bool $allowExit + * * @return array */ public static function checkbox(string $description, $options, $default = null, bool $allowExit = true): array @@ -132,6 +144,7 @@ public static function checkbox(string $description, $options, $default = null, * @param string|array $options * @param null|mixed $default * @param bool $allowExit + * * @return array */ public static function multiSelect(string $description, $options, $default = null, bool $allowExit = true): array @@ -141,8 +154,10 @@ public static function multiSelect(string $description, $options, $default = nul /** * Send a message request confirmation + * * @param string $question The question message - * @param bool $default Default value + * @param bool $default Default value + * * @return bool */ public static function confirm(string $question, bool $default = true): bool @@ -159,6 +174,7 @@ public static function confirm(string $question, bool $default = true): bool * ``` * * @param bool|null $default + * * @return bool */ public static function answerIsYes(bool $default = null): bool @@ -190,9 +206,11 @@ public static function answerIsYes(bool $default = null): bool /** * alias of the `question()` - * @param string $question question message - * @param string $default default value + * + * @param string $question question message + * @param string $default default value * @param Closure $validator The validate callback. It must return bool. + * * @return string|null */ public static function ask(string $question, string $default = '', Closure $validator = null): ?string @@ -202,11 +220,13 @@ public static function ask(string $question, string $default = '', Closure $vali /** * Ask a question, ask for results; return the result of the input - * @see Question::ask() - * @param string $question - * @param string $default + * + * @param string $question + * @param string $default * @param Closure|null $validator Validator, must return bool. + * * @return string + * @see Question::ask() */ public static function question(string $question, string $default = '', Closure $validator = null): string { @@ -215,12 +235,14 @@ public static function question(string $question, string $default = '', Closure /** * Ask a question, ask for a limited number of times - * @see LimitedAsk::ask() - * @param string $question 问题 - * @param string $default 默认值 + * + * @param string $question 问题 + * @param string $default 默认值 * @param Closure $validator (默认验证输入是否为空)自定义回调验证输入是否符合要求; 验证成功返回true 否则 可返回错误消息 - * @param int $times Allow input times + * @param int $times Allow input times + * * @return string + * @see LimitedAsk::ask() */ public static function limitedAsk( string $question, @@ -239,11 +261,13 @@ public static function limitedAsk( * Interactively prompts for input without echoing to the terminal. * Requires a bash shell or Windows and won't work with * safe_mode settings (Uses `shell_exec`) + * * @param string $prompt + * * @return string - * @link https://stackoverflow.com/questions/187736/command-line-password-prompt-in-php - * @link http://www.sitepoint.com/blogs/2009/05/01/interactive-cli-password-prompt-in-php * @throws RuntimeException + * @link http://www.sitepoint.com/blogs/2009/05/01/interactive-cli-password-prompt-in-php + * @link https://stackoverflow.com/questions/187736/command-line-password-prompt-in-php */ public static function promptSilent(string $prompt = 'Enter Password:'): string { @@ -252,7 +276,9 @@ public static function promptSilent(string $prompt = 'Enter Password:'): string /** * alias of the method `promptSilent()` + * * @param string $prompt + * * @return string * @throws RuntimeException */ @@ -263,7 +289,9 @@ public static function askHiddenInput(string $prompt = 'Enter Password:'): strin /** * alias of the method `promptSilent()` + * * @param string $prompt + * * @return string * @throws RuntimeException */ diff --git a/src/Util/ProgressBar.php b/src/Util/ProgressBar.php index 9c05cd6e..91dd1ee7 100644 --- a/src/Util/ProgressBar.php +++ b/src/Util/ProgressBar.php @@ -12,12 +12,13 @@ use Inhere\Console\IO\Output; use Inhere\Console\IO\OutputInterface; use LogicException; -use function max; use RuntimeException; use Toolkit\StrUtil\Str; +use function max; /** * Class ProgressBar + * * @package Inhere\Console\Util * @form \Symfony\Component\Console\Helper\ProgressBar * @@ -40,6 +41,7 @@ class ProgressBar /** * 已完成百分比 + * * @var float */ private $percent = 0.0; @@ -47,6 +49,7 @@ class ProgressBar /** * maximal steps. * 当前在多少步 + * * @var int */ private $step = 0; @@ -54,6 +57,7 @@ class ProgressBar /** * maximal steps. * 设置多少步就会走完进度条, + * * @var int */ private $maxSteps; @@ -61,6 +65,7 @@ class ProgressBar /** * step Width * 设置步长 + * * @var int */ private $stepWidth; @@ -79,16 +84,17 @@ class ProgressBar /** * messages + * * @var array */ private $messages = []; /** * section parsers + * * @var Closure[] */ - private static $parsers = [ - // 'precent' => function () { ... }, + private static $parsers = [// 'precent' => function () { ... }, ]; public const DEFAULT_FORMAT = '[{@bar}] {@percent:3s}%({@current}/{@max}) {@elapsed:6s}/{@estimated:-6s} {@memory:6s}'; @@ -96,6 +102,7 @@ class ProgressBar /** * @param OutputInterface $output * @param int $maxSteps + * * @return ProgressBar */ public static function create(OutputInterface $output = null, int $maxSteps = 0): ProgressBar @@ -117,7 +124,9 @@ public function __construct(OutputInterface $output = null, int $maxSteps = 0) /** * 开始 + * * @param null $maxSteps + * * @throws LogicException */ public function start($maxSteps = null): void @@ -127,9 +136,9 @@ public function start($maxSteps = null): void } $this->startTime = time(); - $this->step = 0; - $this->percent = 0.0; - $this->started = true; + $this->step = 0; + $this->percent = 0.0; + $this->started = true; if (null !== $maxSteps) { $this->setMaxSteps($maxSteps); @@ -140,7 +149,9 @@ public function start($maxSteps = null): void /** * 前进,按步长前进几步 + * * @param int $step 前进几步 + * * @throws LogicException */ public function advance(int $step = 1): void @@ -154,6 +165,7 @@ public function advance(int $step = 1): void /** * 直接前进到第几步 + * * @param int $step 第几步 */ public function advanceTo(int $step): void @@ -164,9 +176,9 @@ public function advanceTo(int $step): void $step = 0; } - $prevPeriod = (int)($this->step / $this->redrawFreq); - $currPeriod = (int)($step / $this->redrawFreq); - $this->step = $step; + $prevPeriod = (int)($this->step / $this->redrawFreq); + $currPeriod = (int)($step / $this->redrawFreq); + $this->step = $step; $this->percent = $this->maxSteps ? (float)$this->step / $this->maxSteps : 0; if ($prevPeriod !== $currPeriod || $this->maxSteps === $step) { @@ -176,6 +188,7 @@ public function advanceTo(int $step): void /** * Finishes the progress output. + * * @throws LogicException */ public function finish(): void @@ -229,7 +242,8 @@ public function clear(): void /** * render - * @param string $text + * + * @param string $text */ public function render(string $text): void { @@ -274,6 +288,7 @@ protected function buildLine() /** * set section Parser + * * @param string $section * @param callable $handler */ @@ -284,8 +299,10 @@ public function setParser(string $section, callable $handler): void /** * get section Parser - * @param string $section - * @param bool|boolean $throwException + * + * @param string $section + * @param bool|boolean $throwException + * * @return mixed * @throws RuntimeException */ @@ -324,8 +341,9 @@ public function setMessages(array $messages): void /** * set a named Message + * * @param string $message The text to associate with the placeholder - * @param string $name The name of the placeholder + * @param string $name The name of the placeholder */ public function setMessage($message, string $name = 'message'): void { @@ -334,6 +352,7 @@ public function setMessage($message, string $name = 'message'): void /** * @param string $name + * * @return string */ public function getMessage(string $name = 'message'): string @@ -373,11 +392,12 @@ public function setOverwrite(bool $overwrite): void /** * Sets the progress bar maximal steps. + * * @param int $maxSteps The progress bar max steps */ private function setMaxSteps(int $maxSteps): void { - $this->maxSteps = max(0, $maxSteps); + $this->maxSteps = max(0, $maxSteps); $this->stepWidth = $this->maxSteps ? Str::len($this->maxSteps) : 2; } @@ -431,6 +451,7 @@ public function setCompleteChar(string $completeChar): void /** * Gets the complete bar character. + * * @return string A character */ public function getCompleteChar(): string @@ -522,12 +543,13 @@ private static function loadDefaultParsers(): array { return [ 'bar' => function (self $bar) { - $completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth()); - $display = str_repeat($bar->getCompleteChar(), $completeBars); + $completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getPercent() * $bar->getBarWidth() : + $bar->getProgress() % $bar->getBarWidth()); + $display = str_repeat($bar->getCompleteChar(), $completeBars); if ($completeBars < $bar->getBarWidth()) { $emptyBars = $bar->getBarWidth() - $completeBars; - $display .= $bar->getProgressChar() . str_repeat($bar->getRemainingChar(), $emptyBars); + $display .= $bar->getProgressChar() . str_repeat($bar->getRemainingChar(), $emptyBars); } return $display; diff --git a/src/Util/Show.php b/src/Util/Show.php index 2544e6e8..85c23ffc 100644 --- a/src/Util/Show.php +++ b/src/Util/Show.php @@ -2,12 +2,7 @@ namespace Inhere\Console\Util; -use function array_keys; -use function array_values; -use function ceil; -use function count; use Generator; -use function implode; use Inhere\Console\Component\Formatter\HelpPanel; use Inhere\Console\Component\Formatter\MultiList; use Inhere\Console\Component\Formatter\Padding; @@ -23,29 +18,35 @@ use Inhere\Console\Component\Progress\SimpleTextBar; use Inhere\Console\Component\Style\Style; use Inhere\Console\Console; +use LogicException; +use Toolkit\Cli\Cli; +use Toolkit\Cli\ColorTag; +use Toolkit\StrUtil\Str; +use Toolkit\Sys\Sys; +use function array_keys; +use function array_values; +use function ceil; +use function count; +use function implode; use function is_array; use function is_string; use function json_encode; -use const JSON_PRETTY_PRINT; -use const JSON_UNESCAPED_SLASHES; -use const JSON_UNESCAPED_UNICODE; -use LogicException; use function microtime; -use const PHP_EOL; use function sprintf; use function str_repeat; use function strlen; use function strpos; use function strtoupper; use function substr; -use Toolkit\Cli\Cli; -use Toolkit\Cli\ColorTag; -use Toolkit\StrUtil\Str; -use Toolkit\Sys\Sys; use function ucwords; +use const JSON_PRETTY_PRINT; +use const JSON_UNESCAPED_SLASHES; +use const JSON_UNESCAPED_UNICODE; +use const PHP_EOL; /** * Class Show - render and display formatted message text + * * @package Inhere\Console\Util * @method static int info($messages, $quit = false) * @method static int note($messages, $quit = false) @@ -93,6 +94,7 @@ class Show * @param string $type * @param string $style * @param int|boolean $quit If is int, setting it is exit code. + * * @return int */ public static function block($messages, string $type = 'MESSAGE', string $style = Style::NORMAL, $quit = false): int @@ -119,6 +121,7 @@ public static function block($messages, string $type = 'MESSAGE', string $style * @param string $type * @param string $style * @param int|boolean $quit If is int, setting it is exit code. + * * @return int */ public static function liteBlock( @@ -175,6 +178,7 @@ public static function liteBlock( /** * @param string $method * @param array $args + * * @return int * @throws LogicException */ @@ -203,8 +207,10 @@ public static function __callStatic($method, array $args = []) /** * Print JSON + * * @param mixed $data * @param int $flags + * * @return int */ public static function prettyJSON( @@ -220,6 +226,7 @@ public static function prettyJSON( * @param string $title * @param string $char * @param int $width + * * @return int */ public static function splitLine(string $title, string $char = '-', int $width = 0): int @@ -266,6 +273,7 @@ public static function section(string $title, $body, array $opts = []): void * 'Bacon' => '$2.99', * ]; * ``` + * * @param array $data * @param string $title * @param array $opts @@ -289,6 +297,7 @@ public static function padding(array $data, string $title = '', array $opts = [] * @param array $data * @param string $title * @param array $opts More {@see FormatUtil::spliceKeyValue()} + * * @return int|string */ public static function aList($data, string $title = '', array $opts = []) @@ -321,6 +330,7 @@ public static function sList($data, string $title = '', array $opts = []) * ... ... * ] * ``` + * * @param array $data * @param array $opts */ @@ -331,6 +341,7 @@ public static function mList(array $data, array $opts = []): void /** * alias of the `mList()` + * * @param array $data * @param array $opts */ @@ -342,7 +353,9 @@ public static function multiList(array $data, array $opts = []): void /** * Render console help message - * @param array $config The config data + * + * @param array $config The config data + * * @see HelpPanel::show() */ public static function helpPanel(array $config): void @@ -352,9 +365,11 @@ public static function helpPanel(array $config): void /** * Show information data panel - * @param mixed $data - * @param string $title - * @param array $opts + * + * @param mixed $data + * @param string $title + * @param array $opts + * * @return int */ public static function panel($data, string $title = 'Information Panel', array $opts = []): int @@ -366,6 +381,7 @@ public static function panel($data, string $title = 'Information Panel', array $ * Render data like tree * ├ ─ ─ * └ ─ + * * @param array $data * @param array $opts */ @@ -377,9 +393,10 @@ public static function tree(array $data, array $opts = []): void /** * Tabular data display * - * @param array $data - * @param string $title - * @param array $opts + * @param array $data + * @param string $title + * @param array $opts + * * @return int * @see Table::show() */ @@ -402,6 +419,7 @@ public static function table(array $data, string $title = 'Data Table', array $o * } * Show::spinner('Done', true); * ``` + * * @param string $msg * @param bool $ended */ @@ -434,6 +452,7 @@ public static function spinner(string $msg = '', $ended = false): void /** * alias of the pending() + * * @param string $msg * @param bool $ended */ @@ -452,6 +471,7 @@ public static function loading(string $msg = 'Loading ', $ended = false): void * } * Show::pending('Done', true); * ``` + * * @param string $msg * @param bool $ended */ @@ -491,8 +511,10 @@ public static function pending(string $msg = 'Pending ', $ended = false): void * } * Show::pointing('Total', true); * ``` + * * @param string $msg * @param bool $ended + * * @return int|mixed */ public static function pointing(string $msg = 'handling ', $ended = false) @@ -517,6 +539,7 @@ public static function pointing(string $msg = 'handling ', $ended = false) * * @param string $msg * @param string|null $doneMsg + * * @return Generator */ public static function counterTxt(string $msg, $doneMsg = ''): Generator @@ -527,6 +550,7 @@ public static function counterTxt(string $msg, $doneMsg = ''): Generator /** * @param string $doneMsg * @param string|null $fixMsg + * * @return Generator */ public static function dynamicTxt(string $doneMsg, string $fixMsg = null): Generator @@ -537,6 +561,7 @@ public static function dynamicTxt(string $doneMsg, string $fixMsg = null): Gener /** * @param string $doneMsg * @param string|null $fixedMsg + * * @return Generator */ public static function dynamicText(string $doneMsg, string $fixedMsg = null): Generator @@ -546,9 +571,11 @@ public static function dynamicText(string $doneMsg, string $fixedMsg = null): Ge /** * Render a simple text progress bar by 'yield' + * * @param int $total * @param string $msg * @param string $doneMsg + * * @return Generator */ public static function progressTxt(int $total, string $msg, string $doneMsg = ''): Generator @@ -558,10 +585,12 @@ public static function progressTxt(int $total, string $msg, string $doneMsg = '' /** * Render a simple progress bar by 'yield' + * * @param int $total * @param array $opts - * @internal int $current + * * @return Generator + * @internal int $current */ public static function progressBar(int $total, array $opts = []): ?Generator { @@ -580,8 +609,10 @@ public static function progressBar(int $total, array $opts = []): ?Generator * } * $bar->finish(); * ``` + * * @param int $max * @param bool $start + * * @return ProgressBar * @throws LogicException */ @@ -620,6 +651,7 @@ public static function getBuffer(): string /** * @param string $buffer + * * @deprecated Please use \Inhere\Console\Console method instead it. */ public static function setBuffer(string $buffer): void @@ -629,6 +661,7 @@ public static function setBuffer(string $buffer): void /** * start buffering + * * @deprecated Please use \Inhere\Console\Console method instead it. */ public static function startBuffer(): void @@ -638,6 +671,7 @@ public static function startBuffer(): void /** * start buffering + * * @deprecated Please use \Inhere\Console\Console method instead it. */ public static function clearBuffer(): void @@ -647,12 +681,14 @@ public static function clearBuffer(): void /** * stop buffering - * @see Show::write() + * * @param bool $flush Whether flush buffer to output stream * @param bool $nl Default is False, because the last write() have been added "\n" * @param bool $quit * @param array $opts + * * @return null|string If flush = False, will return all buffer text. + * @see Show::write() * @deprecated Please use \Inhere\Console\Console method instead it. */ public static function stopBuffer($flush = true, $nl = false, $quit = false, array $opts = []): ?string @@ -675,10 +711,12 @@ public static function stopBuffer($flush = true, $nl = false, $quit = false, arr /** * stop buffering and flush buffer text - * @see Show::write() + * * @param bool $nl * @param bool $quit * @param array $opts + * + * @see Show::write() * @deprecated Please use \Inhere\Console\Console method instead it. */ public static function flushBuffer($nl = false, $quit = false, array $opts = []): void @@ -692,8 +730,10 @@ public static function flushBuffer($nl = false, $quit = false, array $opts = []) /** * Format and write message to terminal + * * @param string $format * @param mixed ...$args + * * @return int */ public static function writef(string $format, ...$args): int @@ -703,16 +743,18 @@ public static function writef(string $format, ...$args): int /** * Write a message to standard output stream. + * * @param string|array $messages Output message * @param boolean $nl True 会添加换行符, False 原样输出,不添加换行符 * @param int|boolean $quit If is int, setting it is exit code. 'True' translate as code 0 and exit, 'False' will not exit. * @param array $opts Some options for write - * refer: - * [ - * 'color' => bool, // whether render color, default is: True. - * 'stream' => resource, // the stream resource, default is: STDOUT - * 'flush' => bool, // flush the stream data, default is: True - * ] + * refer: + * [ + * 'color' => bool, // whether render color, default is: True. + * 'stream' => resource, // the stream resource, default is: STDOUT + * 'flush' => bool, // flush the stream data, default is: True + * ] + * * @return int */ public static function write($messages, $nl = true, $quit = false, array $opts = []): int @@ -722,10 +764,12 @@ public static function write($messages, $nl = true, $quit = false, array $opts = /** * Write raw data to stdout, will disable color render. + * * @param string|array $message * @param bool $nl * @param bool|int $quit * @param array $opts + * * @return int */ public static function writeRaw($message, bool $nl = true, $quit = false, array $opts = []): int @@ -736,9 +780,11 @@ public static function writeRaw($message, bool $nl = true, $quit = false, array /** * Write data to stdout with newline. + * * @param string|array $message * @param array $opts * @param bool|int $quit + * * @return int */ public static function writeln($message, $quit = false, array $opts = []): int @@ -751,6 +797,7 @@ public static function writeln($message, $quit = false, array $opts = []): int * @param string $style * @param bool $nl * @param array $opts + * * @return int */ public static function colored($message, string $style = 'info', $nl = true, array $opts = []): int @@ -766,6 +813,7 @@ public static function colored($message, string $style = 'info', $nl = true, arr /** * @param bool $onlyKey + * * @return array */ public static function getBlockMethods($onlyKey = true): array From e2b47dec0d3fba86b077cad8f2e26872682a082e Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 1 May 2020 13:46:22 +0800 Subject: [PATCH 010/258] run php cs fixer for format codes --- .php_cs | 2 +- composer.json | 4 ++ examples/Command/CorCommand.php | 4 +- examples/Command/DemoCommand.php | 8 ++- examples/Command/TestCommand.php | 3 +- examples/Controller/HomeController.php | 21 +++--- examples/Controller/InteractController.php | 10 +-- examples/Controller/ProcessController.php | 12 ++-- examples/Controller/ShowController.php | 12 ++-- examples/commands.php | 2 +- examples/demo/cli-spinner.php | 6 +- examples/demo/color.php | 7 +- examples/demo/constants.php | 2 +- examples/demo/progress_bar.php | 2 +- examples/demo/progress_bar1.php | 2 +- examples/demo/progress_bar3.php | 10 +-- examples/demo/sf2_color.php | 82 +++++++++++++--------- examples/demo/spinner.php | 6 +- src/AbstractApplication.php | 4 +- src/AbstractHandler.php | 17 +++-- src/Application.php | 9 ++- src/BuiltIn/DevServerCommand.php | 3 +- src/BuiltIn/PharController.php | 3 +- src/BuiltIn/SelfUpdateCommand.php | 74 +++++++++++++------ src/Command.php | 2 +- src/Component/Animation/Animation.php | 3 +- src/Component/AutoCompDumper.php | 3 +- src/Component/ConsoleRenderer.php | 3 +- src/Component/ErrorHandler.php | 12 ++-- src/Component/Formatter/Alert.php | 2 +- src/Component/Formatter/Block.php | 3 +- src/Component/Formatter/Body.php | 2 +- src/Component/Formatter/HelpPanel.php | 10 ++- src/Component/Formatter/MultiList.php | 2 +- src/Component/Formatter/Padding.php | 2 +- src/Component/Formatter/Panel.php | 2 +- src/Component/Formatter/Section.php | 2 +- src/Component/Formatter/SingleList.php | 2 +- src/Component/Formatter/Table.php | 9 ++- src/Component/Formatter/Title.php | 2 +- src/Component/Formatter/Tree.php | 2 +- src/Component/Interact/Checkbox.php | 4 +- src/Component/Interact/Choose.php | 2 +- src/Component/Interact/Confirm.php | 2 +- src/Component/Interact/LimitedAsk.php | 3 +- src/Component/Interact/Password.php | 3 +- src/Component/Interact/Question.php | 2 +- src/Component/Interact/Terminal.php | 3 +- src/Component/InteractMessage.php | 3 +- src/Component/MessageFormatter.php | 4 +- src/Component/NotifyMessage.php | 2 +- src/Component/PharCompiler.php | 11 +-- src/Component/Progress/Bar.php | 3 +- src/Component/Progress/CounterText.php | 2 +- src/Component/Progress/DynamicText.php | 3 +- src/Component/Progress/Pending.php | 3 +- src/Component/Progress/SimpleBar.php | 12 ++-- src/Component/Progress/SimpleTextBar.php | 2 +- src/Component/Progress/Spinner.php | 3 +- src/Component/Progress/TextBar.php | 3 +- src/Component/Style/Alert.php | 5 +- src/Component/Style/Color.php | 37 ++++++++-- src/Component/Style/Style.php | 16 ++++- src/Component/Symbol/ArtFont.php | 3 +- src/Component/Symbol/Char.php | 14 +++- src/Component/Symbol/Emoji.php | 42 ++++++++++- src/Console.php | 17 ++++- src/Contract/ApplicationInterface.php | 6 +- src/Contract/CommandHandlerInterface.php | 4 +- src/Contract/CommandInterface.php | 2 +- src/Contract/ControllerInterface.php | 2 +- src/Contract/ErrorHandlerInterface.php | 2 +- src/Contract/FormatterInterface.php | 9 ++- src/Contract/RouterInterface.php | 4 +- src/Controller.php | 9 ++- src/Exception/ConsoleException.php | 3 +- src/Exception/PromptException.php | 3 +- src/IO/AbstractInput.php | 68 +++++++++++++++--- src/IO/Input.php | 2 +- src/IO/Input/ArrayInput.php | 2 +- src/IO/Input/InputArgument.php | 2 +- src/IO/Input/InputArguments.php | 3 +- src/IO/Input/InputItem.php | 2 +- src/IO/Input/InputOption.php | 3 +- src/IO/Input/InputOptions.php | 3 +- src/IO/InputDefinition.php | 22 ++++-- src/IO/InputInterface.php | 7 +- src/IO/Output.php | 2 +- src/IO/OutputInterface.php | 2 +- src/IO/StrictInput.php | 3 +- src/Router.php | 15 ++-- src/Traits/AdvancedFormatOutputTrait.php | 3 +- src/Traits/ApplicationHelpTrait.php | 9 ++- src/Traits/ControllerHelpTrait.php | 3 +- src/Traits/FormatOutputAwareTrait.php | 6 +- src/Traits/InputOutputAwareTrait.php | 2 +- src/Traits/NameAliasTrait.php | 2 +- src/Traits/RuntimeProfileTrait.php | 2 +- src/Traits/SimpleEventTrait.php | 2 +- src/Traits/UserInteractAwareTrait.php | 2 +- src/Util/FormatUtil.php | 2 +- src/Util/Helper.php | 4 +- src/Util/Interact.php | 2 +- src/Util/ProgressBar.php | 9 ++- src/Util/Show.php | 3 +- test/ApplicationTest.php | 2 +- test/CommandTest.php | 2 +- test/ControllerTest.php | 2 +- test/TestCommand.php | 3 +- test/TestController.php | 3 +- test/boot.php | 12 ++-- 111 files changed, 545 insertions(+), 282 deletions(-) diff --git a/.php_cs b/.php_cs index e960b7fc..7266c585 100644 --- a/.php_cs +++ b/.php_cs @@ -2,7 +2,7 @@ $header = <<<'EOF' -@license https://github.com/inhere/php-validate/blob/master/LICENSE +@license https://github.com/inhere/php-/blob/master/LICENSE EOF; return PhpCsFixer\Config::create()->setRiskyAllowed(true)->setRules([ diff --git a/composer.json b/composer.json index c99af64a..6ec36a79 100644 --- a/composer.json +++ b/composer.json @@ -45,5 +45,9 @@ "symfony/process": "php process operation", "padraic/phar-updater": "A thing to make PHAR self-updating easy and secure.", "text/template": "Single-Class string template engine for PHP5/7 supporting if/loops/filters" + }, + "scripts": { + "check-cs": "./php-cs-fixer fix --dry-run --diff --diff-format=udiff", + "cs-fix": "./php-cs-fixer fix" } } diff --git a/examples/Command/CorCommand.php b/examples/Command/CorCommand.php index 32dd316a..dd24130f 100644 --- a/examples/Command/CorCommand.php +++ b/examples/Command/CorCommand.php @@ -1,4 +1,4 @@ -write('hello, this in ' . __METHOD__); // $name = $input->getArg('name'); - $output->write(<<write( + <<send('Start'); foreach (['Request', 'Downloading', 'Save'] as $txt) { - \sleep(2); + sleep(2); $dt->send($txt); } - \sleep(2); + sleep(2); $dt->send(false); } @@ -324,7 +329,7 @@ public function progressCommand($input): int /** * a progress bar example show, by class ProgressBar - * @throws \LogicException + * @throws LogicException */ public function progressBarCommand(): void { diff --git a/examples/Controller/InteractController.php b/examples/Controller/InteractController.php index 63808e8c..f6340090 100644 --- a/examples/Controller/InteractController.php +++ b/examples/Controller/InteractController.php @@ -1,4 +1,4 @@ -input->getBoolOpt('nv')) { $a = Interact::limitedAsk('you name is: ', '', null, $times); } else { - $a = Interact::limitedAsk('you name is: ', '', function ($val) { - if (!\preg_match('/^\w{2,}$/', $val)) { + $a = Interact::limitedAsk('you name is: ', '', static function ($val) { + if (!preg_match('/^\w{2,}$/', $val)) { Show::error('Your input must match /^\w{2,}$/'); return false; } diff --git a/examples/Controller/ProcessController.php b/examples/Controller/ProcessController.php index eea4fe23..a52e749e 100644 --- a/examples/Controller/ProcessController.php +++ b/examples/Controller/ProcessController.php @@ -1,4 +1,4 @@ - 可以向子进程标准输入写入的句柄 // 1 => 可以从子进程标准输出读取的句柄 @@ -73,7 +75,6 @@ public function runScriptCommand(): void echo "command returned $retVal\n"; } - } /** @@ -94,11 +95,11 @@ public function childProcessCommand(): void /** * simple process example for daemon run - * @throws \RuntimeException + * @throws RuntimeException */ public function daemonRunCommand(): void { - $ret = ProcessUtil::daemonRun(function ($pid){ + $ret = ProcessUtil::daemonRun(function ($pid) { $this->output->info("will running background by new process: $pid"); }); @@ -127,6 +128,5 @@ public function runInBackgroundCommand(): void */ public function multiProcessCommand(): void { - } } diff --git a/examples/Controller/ShowController.php b/examples/Controller/ShowController.php index e84b36f5..4bafc984 100644 --- a/examples/Controller/ShowController.php +++ b/examples/Controller/ShowController.php @@ -1,4 +1,4 @@ -app->getRootPath() . '/examples/routes.php'; $file = $this->app->getRootPath() . '/src/Utils/Show.php'; - $src = \file_get_contents($file); + $src = file_get_contents($file); $code = Highlighter::create()->highlight($src, $in->getBoolOpt('ln')); @@ -309,5 +312,4 @@ public function jsonCommand(): void $this->output->write('use json:'); $this->output->json($data); } - } diff --git a/examples/commands.php b/examples/commands.php index ab8e7118..91db377b 100644 --- a/examples/commands.php +++ b/examples/commands.php @@ -1,4 +1,4 @@ -'), $progress, $msg @@ -49,4 +51,4 @@ function progress_bar($total, $msg, $char = '=') { usleep(50000); $i++; } -//var_dump($bar->valid()); \ No newline at end of file +//var_dump($bar->valid()); diff --git a/examples/demo/sf2_color.php b/examples/demo/sf2_color.php index 71de58b1..74944fb2 100644 --- a/examples/demo/sf2_color.php +++ b/examples/demo/sf2_color.php @@ -1,4 +1,4 @@ - array('set' => 30, 'unset' => 39), - 'red' => array('set' => 31, 'unset' => 39), - 'green' => array('set' => 32, 'unset' => 39), - 'yellow' => array('set' => 33, 'unset' => 39), - 'blue' => array('set' => 34, 'unset' => 39), - 'magenta' => array('set' => 35, 'unset' => 39), - 'cyan' => array('set' => 36, 'unset' => 39), - 'white' => array('set' => 37, 'unset' => 39), - 'default' => array('set' => 39, 'unset' => 39), - ); - private static $availableBackgroundColors = array( - 'black' => array('set' => 40, 'unset' => 49), - 'red' => array('set' => 41, 'unset' => 49), - 'green' => array('set' => 42, 'unset' => 49), - 'yellow' => array('set' => 43, 'unset' => 49), - 'blue' => array('set' => 44, 'unset' => 49), - 'magenta' => array('set' => 45, 'unset' => 49), - 'cyan' => array('set' => 46, 'unset' => 49), - 'white' => array('set' => 47, 'unset' => 49), - 'default' => array('set' => 49, 'unset' => 49), - ); - private static $availableOptions = array( - 'bold' => array('set' => 1, 'unset' => 22), - 'underscore' => array('set' => 4, 'unset' => 24), - 'blink' => array('set' => 5, 'unset' => 25), - 'reverse' => array('set' => 7, 'unset' => 27), - 'conceal' => array('set' => 8, 'unset' => 28), - ); + private static $availableForegroundColors = [ + 'black' => ['set' => 30, 'unset' => 39], + 'red' => ['set' => 31, 'unset' => 39], + 'green' => ['set' => 32, 'unset' => 39], + 'yellow' => ['set' => 33, 'unset' => 39], + 'blue' => ['set' => 34, 'unset' => 39], + 'magenta' => ['set' => 35, 'unset' => 39], + 'cyan' => ['set' => 36, 'unset' => 39], + 'white' => ['set' => 37, 'unset' => 39], + 'default' => ['set' => 39, 'unset' => 39], + ]; + + private static $availableBackgroundColors = [ + 'black' => ['set' => 40, 'unset' => 49], + 'red' => ['set' => 41, 'unset' => 49], + 'green' => ['set' => 42, 'unset' => 49], + 'yellow' => ['set' => 43, 'unset' => 49], + 'blue' => ['set' => 44, 'unset' => 49], + 'magenta' => ['set' => 45, 'unset' => 49], + 'cyan' => ['set' => 46, 'unset' => 49], + 'white' => ['set' => 47, 'unset' => 49], + 'default' => ['set' => 49, 'unset' => 49], + ]; + + private static $availableOptions = [ + 'bold' => ['set' => 1, 'unset' => 22], + 'underscore' => ['set' => 4, 'unset' => 24], + 'blink' => ['set' => 5, 'unset' => 25], + 'reverse' => ['set' => 7, 'unset' => 27], + 'conceal' => ['set' => 8, 'unset' => 28], + ]; + private $foreground; + private $background; - private $options = array(); + + private $options = []; + /** * Initializes output formatter style. * @@ -55,7 +61,7 @@ class OutputFormatterStyle * @param string|null $background The style background color name * @param array $options The style options */ - public function __construct($foreground = null, $background = null, array $options = array()) + public function __construct($foreground = null, $background = null, array $options = []) { if (null !== $foreground) { $this->setForeground($foreground); @@ -67,6 +73,7 @@ public function __construct($foreground = null, $background = null, array $optio $this->setOptions($options); } } + /** * Sets style foreground color. * @@ -89,6 +96,7 @@ public function setForeground($color = null) } $this->foreground = static::$availableForegroundColors[$color]; } + /** * Sets style background color. * @@ -111,6 +119,7 @@ public function setBackground($color = null) } $this->background = static::$availableBackgroundColors[$color]; } + /** * Sets some specific style option. * @@ -131,6 +140,7 @@ public function setOption($option) $this->options[] = static::$availableOptions[$option]; } } + /** * Unsets some specific style option. * @@ -152,6 +162,7 @@ public function unsetOption($option) unset($this->options[$pos]); } } + /** * Sets multiple style options at once. * @@ -159,11 +170,12 @@ public function unsetOption($option) */ public function setOptions(array $options) { - $this->options = array(); + $this->options = []; foreach ($options as $option) { $this->setOption($option); } } + /** * Applies the style to a given text. * @@ -173,8 +185,8 @@ public function setOptions(array $options) */ public function apply($text) { - $setCodes = array(); - $unsetCodes = array(); + $setCodes = []; + $unsetCodes = []; if (null !== $this->foreground) { $setCodes[] = $this->foreground['set']; $unsetCodes[] = $this->foreground['unset']; diff --git a/examples/demo/spinner.php b/examples/demo/spinner.php index 1116ff1a..05f96e79 100644 --- a/examples/demo/spinner.php +++ b/examples/demo/spinner.php @@ -1,4 +1,4 @@ -defaultCommand ? 'list'; $command = 'list'; - // is user command + // is user command } elseif (!$this->isInternalCommand($command)) { return false; } diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index ca61dae2..0935f608 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -1,4 +1,4 @@ - $conf) { @@ -537,8 +539,13 @@ protected function showHelpByMethodAnnotations(string $method, string $action = $ref = new ReflectionClass($this); $name = $this->input->getCommand(); - $this->logf(Console::VERB_CRAZY, 'display help info for the method=%s, action=%s, class=%s', $method, $action, - static::class); + $this->logf( + Console::VERB_CRAZY, + 'display help info for the method=%s, action=%s, class=%s', + $method, + $action, + static::class + ); if (!$ref->hasMethod($method)) { $this->write("The command [$name] don't exist in the group: " . static::getName()); diff --git a/src/Application.php b/src/Application.php index 0ae3f7ee..6c26e6f5 100644 --- a/src/Application.php +++ b/src/Application.php @@ -1,4 +1,4 @@ -output->writeln('Humbug has been updated.'); - $this->output->writeln(sprintf('Current version is: %s.', - $newVersion)); - $this->output->writeln(sprintf('Previous version was: %s.', - $oldVersion)); + $this->output->writeln(sprintf( + 'Current version is: %s.', + $newVersion + )); + $this->output->writeln(sprintf( + 'Previous version was: %s.', + $oldVersion + )); } else { $this->output->writeln('Humbug is currently up to date.'); - $this->output->writeln(sprintf('Current version is: %s.', - $oldVersion)); + $this->output->writeln(sprintf( + 'Current version is: %s.', + $oldVersion + )); } } catch (Exception $e) { $this->output->writeln(sprintf('Error: %s', $e->getMessage())); @@ -236,8 +243,10 @@ protected function printAvailableUpdates(): void protected function printCurrentLocalVersion(): void { - $this->output->writeln(sprintf('Your current local build version is: %s', - $this->version)); + $this->output->writeln(sprintf( + 'Your current local build version is: %s', + $this->version + )); } protected function printCurrentStableVersion(): void @@ -268,8 +277,11 @@ protected function printVersion(Updater $updater): void try { if ($updater->hasUpdate()) { - $this->output->writeln(sprintf('The current %s build available remotely is: %s', - $stability, $updater->getNewVersion())); + $this->output->writeln(sprintf( + 'The current %s build available remotely is: %s', + $stability, + $updater->getNewVersion() + )); } elseif (false === $updater->getNewVersion()) { $this->output->writeln(sprintf('There are no %s builds available.', $stability)); } else { @@ -285,16 +297,36 @@ protected function configure(): void $this->createDefinition() // ->setName('self-update') // ->setDescription(self::$description) - ->addOption('dev', 'd', Input::OPT_BOOLEAN, - 'Update to most recent development build of package.') - ->addOption('non-dev', 'N', Input::OPT_BOOLEAN, - 'Update to most recent non-development (alpha/beta/stable) build of package tagged on Github.') - ->addOption('pre', 'p', Input::OPT_BOOLEAN, - 'Update to most recent pre-release version of package (alpha/beta/rc) tagged on Github.') + ->addOption( + 'dev', + 'd', + Input::OPT_BOOLEAN, + 'Update to most recent development build of package.' + ) + ->addOption( + 'non-dev', + 'N', + Input::OPT_BOOLEAN, + 'Update to most recent non-development (alpha/beta/stable) build of package tagged on Github.' + ) + ->addOption( + 'pre', + 'p', + Input::OPT_BOOLEAN, + 'Update to most recent pre-release version of package (alpha/beta/rc) tagged on Github.' + ) ->addOption('stable', 's', Input::OPT_BOOLEAN, 'Update to most recent stable version tagged on Github.') - ->addOption('rollback', 'r', Input::OPT_BOOLEAN, - 'Rollback to previous version of package if available on filesystem.') - ->addOption('check', 'c', Input::OPT_BOOLEAN, - 'Checks what updates are available across all possible stability tracks.'); + ->addOption( + 'rollback', + 'r', + Input::OPT_BOOLEAN, + 'Rollback to previous version of package if available on filesystem.' + ) + ->addOption( + 'check', + 'c', + Input::OPT_BOOLEAN, + 'Checks what updates are available across all possible stability tracks.' + ); } } diff --git a/src/Command.php b/src/Command.php index d627d4b9..a82b3ecf 100644 --- a/src/Command.php +++ b/src/Command.php @@ -1,4 +1,4 @@ -getLine(); $file = $e->getFile(); $snippet = Highlighter::create()->highlightSnippet(file_get_contents($file), $line, 3, 3); - $message = sprintf($tpl, // $e->getCode(), - $e->getMessage(), $file, $line, // __METHOD__, - $snippet, $e->getTraceAsString()// \str_replace('):', '): -', $e->getTraceAsString()) + $message = sprintf( + $tpl, // $e->getCode(), + $e->getMessage(), + $file, + $line, // __METHOD__, + $snippet, + $e->getTraceAsString()// \str_replace('):', '): -', $e->getTraceAsString()) ); if ($app->getParam('hideRootPath') && ($rootPath = $app->getParam('rootPath'))) { diff --git a/src/Component/Formatter/Alert.php b/src/Component/Formatter/Alert.php index bd8c8f5f..108b2fb1 100644 --- a/src/Component/Formatter/Alert.php +++ b/src/Component/Formatter/Alert.php @@ -1,4 +1,4 @@ - 'text1', 'key2' => 'text2'] + // is key-value [ 'key1' => 'text1', 'key2' => 'text2'] } else { $value = FormatUtil::spliceKeyValue($value, [ 'leftChar' => ' ', diff --git a/src/Component/Formatter/MultiList.php b/src/Component/Formatter/MultiList.php index df9b5e5c..7cc30eef 100644 --- a/src/Component/Formatter/MultiList.php +++ b/src/Component/Formatter/MultiList.php @@ -1,4 +1,4 @@ -write($headBorder . "\n"); } } diff --git a/src/Component/Formatter/Title.php b/src/Component/Formatter/Title.php index 65315ded..69161b7f 100644 --- a/src/Component/Formatter/Title.php +++ b/src/Component/Formatter/Title.php @@ -1,4 +1,4 @@ -createIteratorFilter(), - FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS); + return Helper::directoryIterator( + $directory, + $this->createIteratorFilter(), + FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS + ); } /** diff --git a/src/Component/Progress/Bar.php b/src/Component/Progress/Bar.php index e616efd6..f41b9b49 100644 --- a/src/Component/Progress/Bar.php +++ b/src/Component/Progress/Bar.php @@ -1,4 +1,4 @@ - 'default', 'template' => '' ], $opts); - } public static function lite(string $message, string $style = 'info'): void { - } } diff --git a/src/Component/Style/Color.php b/src/Component/Style/Color.php index fd3f826e..22dd314f 100644 --- a/src/Component/Style/Color.php +++ b/src/Component/Style/Color.php @@ -1,4 +1,4 @@ -getKnownColors()))); + throw new InvalidArgumentException(sprintf( + 'Invalid foreground color "%1$s" [%2$s]', + $fg, + implode(', ', $this->getKnownColors()) + )); } $this->fgColor = ($extra ? self::FG_EXTRA : self::FG_BASE) + static::$knownColors[$fg]; @@ -170,8 +187,11 @@ public function __construct($fg = '', $bg = '', array $options = [], bool $extra if ($bg) { if (false === array_key_exists($bg, static::$knownColors)) { - throw new InvalidArgumentException(sprintf('Invalid background color "%1$s" [%2$s]', $bg, - implode(', ', $this->getKnownColors()))); + throw new InvalidArgumentException(sprintf( + 'Invalid background color "%1$s" [%2$s]', + $bg, + implode(', ', $this->getKnownColors()) + )); } $this->bgColor = ($extra ? self::BG_EXTRA : self::BG_BASE) + static::$knownColors[$bg]; @@ -179,8 +199,11 @@ public function __construct($fg = '', $bg = '', array $options = [], bool $extra foreach ($options as $option) { if (false === array_key_exists($option, static::$knownOptions)) { - throw new InvalidArgumentException(sprintf('Invalid option "%1$s" [%2$s]', $option, - implode(', ', $this->getKnownOptions()))); + throw new InvalidArgumentException(sprintf( + 'Invalid option "%1$s" [%2$s]', + $option, + implode(', ', $this->getKnownOptions()) + )); } $this->options[] = $option; diff --git a/src/Component/Style/Style.php b/src/Component/Style/Style.php index 783c7095..97620ad1 100644 --- a/src/Component/Style/Style.php +++ b/src/Component/Style/Style.php @@ -1,4 +1,4 @@ -styles)) { $text = $this->replaceColor($text, $key, $matches[2][$i], (string)$this->styles[$key]); - /** Custom style format @see Color::makeByString() */ + /** Custom style format @see Color::makeByString() */ } elseif (strpos($key, '=')) { $text = $this->replaceColor($text, $key, $matches[2][$i], (string)Color::makeByString($key)); } diff --git a/src/Component/Symbol/ArtFont.php b/src/Component/Symbol/ArtFont.php index c38478a7..10f8bb41 100644 --- a/src/Component/Symbol/ArtFont.php +++ b/src/Component/Symbol/ArtFont.php @@ -1,4 +1,4 @@ - name @@ -304,8 +309,14 @@ public static function log(string $msg, array $data = [], int $level = self::VER $optString = $userOpts ? ' ' . implode(' ', $userOpts) : ''; - self::write(sprintf('%s [%s]%s %s %s', date('Y/m/d H:i:s'), $taggedName, $optString, trim($msg), - $data ? PHP_EOL . json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) : '')); + self::write(sprintf( + '%s [%s]%s %s %s', + date('Y/m/d H:i:s'), + $taggedName, + $optString, + trim($msg), + $data ? PHP_EOL . json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) : '' + )); } /*********************************************************************************** diff --git a/src/Contract/ApplicationInterface.php b/src/Contract/ApplicationInterface.php index 074bf56b..35110a50 100644 --- a/src/Contract/ApplicationInterface.php +++ b/src/Contract/ApplicationInterface.php @@ -1,4 +1,4 @@ - {name} @@ -26,6 +27,7 @@ interface CommandHandlerInterface // {$%s} name -> {name} public const HELP_VAR_LEFT = '{'; + public const HELP_VAR_RIGHT = '}'; /** diff --git a/src/Contract/CommandInterface.php b/src/Contract/CommandInterface.php index 1aaf9ff1..1455fb31 100644 --- a/src/Contract/CommandInterface.php +++ b/src/Contract/CommandInterface.php @@ -1,4 +1,4 @@ - ' ', ]); - $this->write(sprintf('More information about a command, please use: %s %s{command} -h', $script, - $this->executionAlone ? '' : $name)); + $this->write(sprintf( + 'More information about a command, please use: %s %s{command} -h', + $script, + $this->executionAlone ? '' : $name + )); $this->output->flush(); } diff --git a/src/Exception/ConsoleException.php b/src/Exception/ConsoleException.php index 2ffc54e7..52741829 100644 --- a/src/Exception/ConsoleException.php +++ b/src/Exception/ConsoleException.php @@ -1,4 +1,4 @@ -lOpt($name, $default); } @@ -357,6 +357,14 @@ public function getBoolOpt(string $name, bool $default = false): bool return (bool)$this->getOpt($name, $default); } + /** + * Alias of the getBoolOpt() + * + * @param string $name + * @param bool $default + * + * @return bool + */ public function boolOpt(string $name, bool $default = false): bool { return (bool)$this->getOpt($name, $default); @@ -375,7 +383,7 @@ public function hasOpt(string $name): bool } /** - * get same opts value + * Get same opts value * eg: -h --help * * ```php @@ -392,6 +400,14 @@ public function getSameOpt(array $names, $default = null) return $this->sameOpt($names, $default); } + /** + * Alias of the getSameOpt() + * + * @param array $names + * @param null $default + * + * @return bool|mixed|null + */ public function sameOpt(array $names, $default = null) { foreach ($names as $name) { @@ -430,7 +446,7 @@ public function clearOpts(): void /************************** short-opts **********************/ /** - * get short-opt value + * Get short-opt value * * @param string $name * @param null $default @@ -442,6 +458,27 @@ public function sOpt(string $name, $default = null) return $this->sOpts[$name] ?? $default; } + /** + * Alias of the sOpt() + * + * @param string $name + * @param null $default + * + * @return mixed|null + */ + public function shortOpt(string $name, $default = null) + { + return $this->sOpts[$name] ?? $default; + } + + /** + * Alias of the sOpt() + * + * @param string $name + * @param null $default + * + * @return mixed|null + */ public function getShortOpt(string $name, $default = null) { return $this->sOpts[$name] ?? $default; @@ -484,7 +521,7 @@ public function getShortOpts(): array /** * @param string $name - * @param $value + * @param mixed $value */ public function setSOpt(string $name, $value): void { @@ -519,7 +556,7 @@ public function clearSOpts(): void /************************** long-opts **********************/ /** - * get long-opt value + * Alias of the getLongOpt() * * @param string $name * @param null $default @@ -532,6 +569,21 @@ public function lOpt(string $name, $default = null) } /** + * Alias of the getLongOpt() + * + * @param string $name + * @param null $default + * + * @return mixed|null + */ + public function longOpt(string $name, $default = null) + { + return $this->lOpts[$name] ?? $default; + } + + /** + * Get long-opt value + * * @param string $name * @param null $default * @@ -545,7 +597,7 @@ public function getLongOpt(string $name, $default = null) /** * check long-opt exists * - * @param $name + * @param string $name * * @return bool */ diff --git a/src/IO/Input.php b/src/IO/Input.php index 23ea8a72..7f828c4c 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -1,4 +1,4 @@ -optionIsAcceptValue($option['mode'])) { - $value = sprintf(' %s%s%s', $option['optional'] ? '[' : '', strtoupper($name), - $option['optional'] ? ']' : ''); + $value = sprintf( + ' %s%s%s', + $option['optional'] ? '[' : '', + strtoupper($name), + $option['optional'] ? ']' : '' + ); } $shortcut = $option['shortcut'] ? sprintf('-%s, ', $option['shortcut']) : ' '; @@ -521,7 +533,7 @@ public function getSynopsis(bool $short = false): array if (!$argument['required']) { $element = '[' . $element . ']'; } elseif ($argument['isArray']) { - $element = $element . ' (' . $element . ')'; + $element .= ' (' . $element . ')'; } if ($argument['isArray']) { diff --git a/src/IO/InputInterface.php b/src/IO/InputInterface.php index 9336f3bb..dc424400 100644 --- a/src/IO/InputInterface.php +++ b/src/IO/InputInterface.php @@ -1,4 +1,4 @@ -cleanedTokens; } - } diff --git a/src/Router.php b/src/Router.php index fdfafdcd..e0f09b33 100644 --- a/src/Router.php +++ b/src/Router.php @@ -1,4 +1,4 @@ -validateName($name); @@ -197,8 +200,10 @@ public function addCommand(string $name, $handler = null, array $options = []): $options['aliases'] = array_merge($options['aliases'], $aliases); } } elseif (!is_object($handler) || !method_exists($handler, '__invoke')) { - Helper::throwInvalidArgument('The console command handler must is an subclass of %s OR a Closure OR a object have method __invoke()', - Command::class); + Helper::throwInvalidArgument( + 'The console command handler must is an subclass of %s OR a Closure OR a object have method __invoke()', + Command::class + ); } // is an class name string diff --git a/src/Traits/AdvancedFormatOutputTrait.php b/src/Traits/AdvancedFormatOutputTrait.php index b9951948..44d76bba 100644 --- a/src/Traits/AdvancedFormatOutputTrait.php +++ b/src/Traits/AdvancedFormatOutputTrait.php @@ -1,4 +1,4 @@ -getCommandNames(), $router->getControllerNames(), - $this->getInternalCommands()); + $list = array_merge( + $router->getCommandNames(), + $router->getControllerNames(), + $this->getInternalCommands() + ); } else { $glue = PHP_EOL; $list = []; diff --git a/src/Traits/ControllerHelpTrait.php b/src/Traits/ControllerHelpTrait.php index 2ee8bdd6..b2edd23a 100644 --- a/src/Traits/ControllerHelpTrait.php +++ b/src/Traits/ControllerHelpTrait.php @@ -1,4 +1,4 @@ -'; // 当前进度的显示字符 + private $remainingChar = '-'; // 剩下的的显示字符 + private $redrawFreq = 1; + private $format; /** @@ -71,10 +75,13 @@ class ProgressBar private $stepWidth; private $startTime; + private $finishTime; private $overwrite = true; + private $started = false; + private $firstRun = true; /** diff --git a/src/Util/Show.php b/src/Util/Show.php index 85c23ffc..cf5dd3b1 100644 --- a/src/Util/Show.php +++ b/src/Util/Show.php @@ -1,4 +1,4 @@ - Date: Sun, 3 May 2020 12:10:52 +0800 Subject: [PATCH 011/258] fix some ci error --- .php_cs | 2 +- src/AbstractApplication.php | 4 ++-- src/IO/AbstractInput.php | 2 +- test/ApplicationTest.php | 48 +++++++++++++++++++++++++------------ 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/.php_cs b/.php_cs index 7266c585..ad247932 100644 --- a/.php_cs +++ b/.php_cs @@ -2,7 +2,7 @@ $header = <<<'EOF' -@license https://github.com/inhere/php-/blob/master/LICENSE +@license https://github.com/inhere/php-console/blob/master/LICENSE EOF; return PhpCsFixer\Config::create()->setRiskyAllowed(true)->setRules([ diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 1698852e..42ea4038 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -133,7 +133,7 @@ public function __construct(array $config = [], Input $input = null, Output $out protected function init(): void { $this->stats = [ - 'startTime' => microtime(1), + 'startTime' => microtime(true), 'endTime' => 0, 'startMemory' => memory_get_usage(), 'endMemory' => 0, @@ -214,7 +214,7 @@ public function run(bool $exit = true) $this->handleException($e); } - $this->stats['endTime'] = microtime(1); + $this->stats['endTime'] = microtime(true); // call 'onAfterRun' service, if it is registered. $this->fire(self::ON_AFTER_RUN, $this); diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index aada8fe0..c9985dab 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -673,7 +673,7 @@ public function clearLOpts(): void public function getPwd(): string { if (!$this->pwd) { - $this->pwd = getcwd(); + $this->pwd = (string)getcwd(); } return $this->pwd; diff --git a/test/ApplicationTest.php b/test/ApplicationTest.php index 6b495c79..27397702 100644 --- a/test/ApplicationTest.php +++ b/test/ApplicationTest.php @@ -9,9 +9,17 @@ use Inhere\Console\Router; use InvalidArgumentException; use PHPUnit\Framework\TestCase; +use Throwable; +use function get_class; +use function strpos; class ApplicationTest extends TestCase { + protected function assertStringContains(string $string, string $contains): void + { + $this->assertNotSame(false, strpos($string, $contains), "string \"$string\" not contains: $contains"); + } + private function newApp(array $args = null): Application { $input = new Input($args); @@ -55,14 +63,18 @@ public function testAddCommand(): void public function testAddCommandError(): void { $app = $this->newApp(); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageRegExp("/'name' and 'handler' cannot be empty/"); - $app->addCommand(''); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageRegExp('/"name" and "controller" cannot be empty/'); - $app->addCommand('test', 'invalid'); + try { + $app->addCommand(''); + } catch (Throwable $e) { + $this->assertSame(get_class($e), InvalidArgumentException::class); + } + + try { + $app->addCommand('test', 'invalid'); + } catch (Throwable $e) { + $this->assertSame(get_class($e), InvalidArgumentException::class); + $this->assertSame($e->getMessage(), 'The console command class [invalid] not exists!'); + } } public function testRunCommand(): void @@ -101,13 +113,19 @@ public function testAddControllerError(): void { $app = $this->newApp(); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageRegExp('/"name" and "controller" cannot be empty/'); - $app->addController(''); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageRegExp('/"name" and "controller" cannot be empty/'); - $app->controller('test', 'invalid'); + try { + $app->addController(''); + } catch (Throwable $e) { + $this->assertSame(get_class($e), InvalidArgumentException::class); + $this->assertStringContains($e->getMessage(), '"name" and "controller" cannot be empty'); + } + + try { + $app->controller('test', 'invalid'); + } catch (Throwable $e) { + $this->assertSame(get_class($e), InvalidArgumentException::class); + $this->assertSame($e->getMessage(), 'The console controller class [invalid] not exists!'); + } } public function testRunController(): void From 77ac0a9fcbb5ccc8eef708f67f1ad140a1aea323 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 22 May 2020 20:33:59 +0800 Subject: [PATCH 012/258] fix type error --- src/Component/Formatter/MultiList.php | 2 +- src/Component/Formatter/Padding.php | 2 +- src/Util/Helper.php | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Component/Formatter/MultiList.php b/src/Component/Formatter/MultiList.php index 7cc30eef..c120e461 100644 --- a/src/Component/Formatter/MultiList.php +++ b/src/Component/Formatter/MultiList.php @@ -50,7 +50,7 @@ public static function show(array $data, array $opts = []): void continue; } - $stringList[] = SingleList::show($list, $title, $opts); + $stringList[] = SingleList::show($list, (string)$title, $opts); } Console::write(implode("\n", $stringList), $lastNewline); diff --git a/src/Component/Formatter/Padding.php b/src/Component/Formatter/Padding.php index a09ee677..eefa363f 100644 --- a/src/Component/Formatter/Padding.php +++ b/src/Component/Formatter/Padding.php @@ -50,7 +50,7 @@ public static function show(array $data, string $title = '', array $opts = []): foreach ($data as $label => $value) { $value = ColorTag::wrap((string)$value, $opts['valueStyle']); - $string .= $opts['indent'] . str_pad($label, $paddingLen, $opts['char']) . " $value\n"; + $string .= $opts['indent'] . str_pad((string)$label, $paddingLen, $opts['char']) . " $value\n"; } Console::write(trim($string)); diff --git a/src/Util/Helper.php b/src/Util/Helper.php index e55799a5..7afb4f45 100644 --- a/src/Util/Helper.php +++ b/src/Util/Helper.php @@ -161,7 +161,8 @@ public static function getKeyMaxWidth(array $data, bool $expectInt = false): int foreach ($data as $key => $value) { // key is not a integer if (!$expectInt || !is_numeric($key)) { - $width = mb_strlen($key, 'UTF-8'); + $width = mb_strlen((string)$key, 'UTF-8'); + $keyMaxWidth = $width > $keyMaxWidth ? $width : $keyMaxWidth; } } From b05a80a1d87becd30906ce9786ef3d09f8df56d8 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 22 May 2020 20:37:48 +0800 Subject: [PATCH 013/258] fix type error on load class --- src/AbstractApplication.php | 20 ++++++++++---------- src/Application.php | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 42ea4038..8f9adbae 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -194,19 +194,19 @@ public function run(bool $exit = true) { $command = trim($this->input->getCommand(), $this->delimiter); - $this->prepareRun(); + try { + $this->prepareRun(); - // like: help, version, list - if ($this->filterSpecialCommand($command)) { - return 0; - } + // like: help, version, list + if ($this->filterSpecialCommand($command)) { + return 0; + } - // call 'onBeforeRun' service, if it is registered. - $this->fire(self::ON_BEFORE_RUN, $this); - $this->beforeRun(); + // call 'onBeforeRun' service, if it is registered. + $this->fire(self::ON_BEFORE_RUN, $this); + $this->beforeRun(); - // do run ... - try { + // do run ... $result = $this->dispatch($command); } catch (Throwable $e) { $this->fire(self::ON_RUN_ERROR, $e, $this); diff --git a/src/Application.php b/src/Application.php index 6c26e6f5..8ee0301a 100644 --- a/src/Application.php +++ b/src/Application.php @@ -180,7 +180,7 @@ public function registerCommands(string $namespace, string $basePath): self $iterator = Helper::directoryIterator($basePath, $this->getFileFilter()); foreach ($iterator as $file) { - $class = $namespace . '\\' . substr($file, $length, -4); + $class = $namespace . '\\' . substr($file->getPathName(), $length, -4); $this->addCommand($class); } @@ -202,7 +202,7 @@ public function registerGroups(string $namespace, string $basePath): self $iterator = Helper::directoryIterator($basePath, $this->getFileFilter()); foreach ($iterator as $file) { - $class = $namespace . '\\' . substr($file, $length, -4); + $class = $namespace . '\\' . substr($file->getPathName(), $length, -4); $this->addController($class); } @@ -214,7 +214,7 @@ public function registerGroups(string $namespace, string $basePath): self */ protected function getFileFilter(): callable { - return function (SplFileInfo $f) { + return static function (SplFileInfo $f) { $name = $f->getFilename(); // Skip hidden files and directories. From fc894fe61341ea35426d8867c82030cbc937a621 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 22 May 2020 21:07:31 +0800 Subject: [PATCH 014/258] fix return type error --- src/Controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller.php b/src/Controller.php index 0ebcb48a..c641b05b 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -227,7 +227,7 @@ protected function showHelp(): bool return true; } - return $this->helpCommand(); + return $this->helpCommand() === 0; } protected function beforeRenderCommandHelp(array &$help): void From 2e9ad2b2f1d4cf59c626273d5d6b525035ac9a33 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 24 May 2020 14:37:39 +0800 Subject: [PATCH 015/258] fix an error --- src/Command.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Command.php b/src/Command.php index a82b3ecf..0ec5028c 100644 --- a/src/Command.php +++ b/src/Command.php @@ -60,6 +60,9 @@ protected function showHelp(): bool return true; } - return $this->showHelpByMethodAnnotations('execute', '', static::aliases()); + $execMethod = 'execute'; + $cmdAliases = static::aliases(); + + return $this->showHelpByMethodAnnotations($execMethod, '', $cmdAliases) !== 0; } } From bb33a6bdb46d82543b3ff2a130e194079971c565 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 25 May 2020 10:36:20 +0800 Subject: [PATCH 016/258] update some for code --- examples/Command/DemoCommand.php | 3 +- examples/alone | 9 +- examples/alone-app | 6 +- examples/app | 4 +- examples/commands.php | 6 +- phar.build.inc | 2 +- src/AbstractApplication.php | 27 ++++-- src/Application.php | 16 ++++ src/Component/Formatter/Title.php | 3 +- src/Console.php | 28 ++++++ src/IO/Output.php | 11 ++- src/Traits/FormatOutputAwareTrait.php | 126 +++++++++++++++++--------- src/Traits/InputOutputAwareTrait.php | 29 ++++-- src/Util/FormatUtil.php | 8 +- src/Util/Show.php | 7 +- 15 files changed, 206 insertions(+), 79 deletions(-) diff --git a/examples/Command/DemoCommand.php b/examples/Command/DemoCommand.php index 13807b31..f1240921 100644 --- a/examples/Command/DemoCommand.php +++ b/examples/Command/DemoCommand.php @@ -11,6 +11,7 @@ use Inhere\Console\Command; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; +use LogicException; /** * Class DemoCommand @@ -24,7 +25,7 @@ class DemoCommand extends Command /** * {@inheritDoc} - * @throws \LogicException + * @throws LogicException */ protected function configure(): void { diff --git a/examples/alone b/examples/alone index 5144841c..a6026e70 100644 --- a/examples/alone +++ b/examples/alone @@ -5,19 +5,22 @@ */ use Inhere\Console\Examples\Controller\HomeController; +use Inhere\Console\IO\Input; +use Inhere\Console\IO\Output; +use Toolkit\Cli\Color; define('BASE_PATH', dirname(__DIR__)); require dirname(__DIR__) . '/test/boot.php'; -$input = new \Inhere\Console\IO\Input(); -$ctrl = new HomeController($input, new \Inhere\Console\IO\Output()); +$input = new Input(); +$ctrl = new HomeController($input, new Output()); $ctrl->setExecutionAlone(); try { exit($ctrl->run($input->getCommand())); } catch (\Exception $e) { - $message = \Toolkit\Cli\Color::apply('error', $e->getMessage()); + $message = Color::apply('error', $e->getMessage()); echo \sprintf("%s\nFile %s:%d\nTrace:\n%s\n", $message, $e->getFile(), $e->getLine(), $e->getTraceAsString() diff --git a/examples/alone-app b/examples/alone-app index 8f82dde3..6a89f1c5 100644 --- a/examples/alone-app +++ b/examples/alone-app @@ -4,7 +4,9 @@ * only run a controller. */ +use Inhere\Console\Application; use Inhere\Console\Examples\Controller\HomeController; +use Inhere\Console\IO\Input; use Toolkit\Cli\Color; define('BASE_PATH', dirname(__DIR__)); @@ -12,8 +14,8 @@ define('BASE_PATH', dirname(__DIR__)); require dirname(__DIR__) . '/test/boot.php'; try { - $input = new \Inhere\Console\IO\Input(); - $app = new \Inhere\Console\Application([ + $input = new Input(); + $app = new Application([ 'debug' => true, 'rootPath' => BASE_PATH, ], $input); diff --git a/examples/app b/examples/app index 7cd9da94..e8bec0a1 100644 --- a/examples/app +++ b/examples/app @@ -6,12 +6,14 @@ * @release-date {@release_date} */ +use Inhere\Console\Application; + define('BASE_PATH', dirname(__DIR__)); require dirname(__DIR__) . '/test/boot.php'; // create app instance -$app = new \Inhere\Console\Application([ +$app = new Application([ 'debug' => true, 'rootPath' => dirname(__DIR__), ]); diff --git a/examples/commands.php b/examples/commands.php index 91db377b..5e2d2c69 100644 --- a/examples/commands.php +++ b/examples/commands.php @@ -13,7 +13,9 @@ use Inhere\Console\Examples\Command\DemoCommand; use Inhere\Console\Examples\Command\TestCommand; use Inhere\Console\Examples\Controller\HomeController; +use Inhere\Console\Examples\Controller\InteractController; use Inhere\Console\Examples\Controller\ProcessController; +use Inhere\Console\Examples\Controller\ShowController; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; @@ -42,8 +44,8 @@ 'aliases' => 'prc' ]); $app->controller(PharController::class); -$app->controller(\Inhere\Console\Examples\Controller\ShowController::class); -$app->controller(\Inhere\Console\Examples\Controller\InteractController::class); +$app->controller(ShowController::class); +$app->controller(InteractController::class); // add alias for a group command. $app->addAliases('home:test', 'h-test'); diff --git a/phar.build.inc b/phar.build.inc index 2373ebce..5b8191f9 100644 --- a/phar.build.inc +++ b/phar.build.inc @@ -29,7 +29,7 @@ $compiler ; // Console 下的 Command Controller 命令类不去除注释,注释上是命令帮助信息 -$compiler->setStripFilter(function ($file) { +$compiler->setStripFilter(static function ($file) { /** @var \SplFileInfo $file */ $name = $file->getFilename(); diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 8f9adbae..b0fd1ed1 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -55,11 +55,12 @@ abstract class AbstractApplication implements ApplicationInterface /** @var array */ protected static $globalOptions = [ - '--debug' => 'Setting the application runtime debug level(0 - 4)', - '--profile' => 'Display timing and memory usage information', - '--no-color' => 'Disable color/ANSI for message output', - '-h, --help' => 'Display this help message', - '-V, --version' => 'Show application version information', + '--debug' => 'Setting the application runtime debug level(0 - 4)', + '--profile' => 'Display timing and memory usage information', + '--no-color' => 'Disable color/ANSI for message output', + '-h, --help' => 'Display this help message', + '-V, --version' => 'Show application version information', + '--no-interactive' => 'Run commands in a non-interactive environment', ]; /** @var array Application runtime stats */ @@ -350,7 +351,7 @@ protected function filterSpecialCommand(string $command): bool // default run list command // $command = $this->defaultCommand ? 'list'; $command = 'list'; - // is user command + // is user command } elseif (!$this->isInternalCommand($command)) { return false; } @@ -547,7 +548,19 @@ public function getVerbLevel(): int */ public function isProfile(): bool { - return (bool)$this->input->getOpt('profile', $this->getParam('profile')); + return (bool)$this->input->getOpt('profile', $this->getParam('profile', false)); + } + + /** + * is interactive env + * + * @return bool + */ + public function isInteractive(): bool + { + $key = 'no-interactive'; + + return (bool)$this->input->getOpt($key, $this->getParam($key, true)); } /** diff --git a/src/Application.php b/src/Application.php index 8ee0301a..fc136bad 100644 --- a/src/Application.php +++ b/src/Application.php @@ -10,6 +10,8 @@ use Closure; use Inhere\Console\Contract\ControllerInterface; +use Inhere\Console\IO\Input; +use Inhere\Console\IO\Output; use Inhere\Console\Util\Helper; use InvalidArgumentException; use ReflectionException; @@ -31,6 +33,20 @@ */ class Application extends AbstractApplication { + /** + * Class constructor. + * + * @param array $config + * @param Input|null $input + * @param Output|null $output + */ + public function __construct(array $config = [], Input $input = null, Output $output = null) + { + Console::setApp($this); + + parent::__construct($config, $input, $output); + } + /**************************************************************************** * register console controller/command ****************************************************************************/ diff --git a/src/Component/Formatter/Title.php b/src/Component/Formatter/Title.php index 69161b7f..c641fe8c 100644 --- a/src/Component/Formatter/Title.php +++ b/src/Component/Formatter/Title.php @@ -28,6 +28,7 @@ public static function show(string $title, array $opts = []): void 'char' => self::CHAR_EQUAL, 'titlePos' => self::POS_LEFT, 'indent' => 2, + 'ucWords' => true, 'showBorder' => true, ], $opts); @@ -37,7 +38,7 @@ public static function show(string $title, array $opts = []): void $indent = (int)$opts['indent'] >= 0 ? $opts['indent'] : 2; $indentStr = Str::pad(self::CHAR_SPACE, $indent, self::CHAR_SPACE); - $title = ucwords(trim($title)); + $title = $opts['ucWords'] ? ucwords(trim($title)) : trim($title); $tLength = Str::len($title); $width = $width > 10 ? $width : 80; diff --git a/src/Console.php b/src/Console.php index fc1ac6f1..fb63ad2f 100644 --- a/src/Console.php +++ b/src/Console.php @@ -176,6 +176,34 @@ public static function writeln($message, $quit = false, array $opts = []): int return self::write($message, true, $quit, $opts); } + /** + * Write data to stdout with newline. + * + * @param string|array $message + * @param array $opts + * @param bool|int $quit + * + * @return int + */ + public static function println($message, $quit = false, array $opts = []): int + { + return self::write($message, true, $quit, $opts); + } + + /** + * Write message to stdout. + * + * @param string|array $message + * @param array $opts + * @param bool|int $quit + * + * @return int + */ + public static function print($message, $quit = false, array $opts = []): int + { + return self::write($message, false, $quit, $opts); + } + /** * Write a message to standard output stream. * diff --git a/src/IO/Output.php b/src/IO/Output.php index 2d78f6a6..a7d23204 100644 --- a/src/IO/Output.php +++ b/src/IO/Output.php @@ -83,7 +83,11 @@ public function clearBuffer(): void /** * stop buffering and flush buffer text - * {@inheritdoc} + * + * @param bool $flush + * @param bool $nl + * @param bool $quit + * @param array $opts * * @see Console::stopBuffer() */ @@ -94,7 +98,10 @@ public function stopBuffer(bool $flush = true, $nl = false, $quit = false, array /** * stop buffering and flush buffer text - * {@inheritdoc} + * + * @param bool $nl + * @param bool $quit + * @param array $opts */ public function flush(bool $nl = false, $quit = false, array $opts = []): void { diff --git a/src/Traits/FormatOutputAwareTrait.php b/src/Traits/FormatOutputAwareTrait.php index b617fea3..6db9a0fc 100644 --- a/src/Traits/FormatOutputAwareTrait.php +++ b/src/Traits/FormatOutputAwareTrait.php @@ -10,6 +10,13 @@ use Closure; use Generator; +use Inhere\Console\Component\Formatter\HelpPanel; +use Inhere\Console\Component\Formatter\MultiList; +use Inhere\Console\Component\Formatter\Panel; +use Inhere\Console\Component\Formatter\Section; +use Inhere\Console\Component\Formatter\SingleList; +use Inhere\Console\Component\Formatter\Table; +use Inhere\Console\Component\Formatter\Title; use Inhere\Console\Component\Style\Style; use Inhere\Console\Console; use Inhere\Console\Util\Interact; @@ -77,8 +84,11 @@ public function write($messages, $nl = true, $quit = false, array $opts = []): i } /** - * @inheritdoc - * @see Console::writeln() + * @param string|mixed $text + * @param bool $quit + * @param array $opts + * + * @return int */ public function writeln($text, $quit = false, array $opts = []): int { @@ -86,8 +96,24 @@ public function writeln($text, $quit = false, array $opts = []): int } /** - * @inheritdoc - * @see Console::writeRaw() + * @param string|mixed $text + * @param bool $quit + * @param array $opts + * + * @return int + */ + public function println($text, $quit = false, array $opts = []): int + { + return Console::writeln($text, $quit, $opts); + } + + /** + * @param string|mixed $text + * @param bool $nl + * @param bool $quit + * @param array $opts + * + * @return int */ public function writeRaw($text, bool $nl = true, $quit = false, array $opts = []): int { @@ -106,102 +132,114 @@ public function colored(string $text, string $tag = 'info'): int } /** - * @inheritdoc - * @see Show::block() + * @param array|mixed $messages + * @param string $type + * @param string $style + * @param bool $quit + * + * @return int */ - public function block($messages, $type = 'MESSAGE', $style = Style::NORMAL, $quit = false): int + public function block($messages, string $type = 'MESSAGE', string $style = Style::NORMAL, $quit = false): int { return Show::block($messages, $type, $style, $quit); } /** - * @inheritdoc - * @see Show::liteBlock() + * @param array|mixed $messages + * @param string $type + * @param string $style + * @param bool $quit + * + * @return int */ - public function liteBlock($messages, $type = 'MESSAGE', $style = Style::NORMAL, $quit = false): int + public function liteBlock($messages, string $type = 'MESSAGE', string $style = Style::NORMAL, $quit = false): int { return Show::liteBlock($messages, $type, $style, $quit); } /** - * @inheritdoc - * @see Show::title() + * @param string $title + * @param array $opts */ - public function title($title, array $opts = []): void + public function title(string $title, array $opts = []): void { - Show::title($title, $opts); + Title::show($title, $opts); } /** - * @inheritdoc - * @see Show::section() + * @param string $title + * @param string|array $body The section body message + * @param array $opts */ - public function section($title, $body, array $opts = []): void + public function section(string $title, $body, array $opts = []): void { - Show::section($title, $body, $opts); + Section::show($title, $body, $opts); } /** - * @inheritdoc - * @see Show::aList() + * @param array|mixed $data + * @param string $title + * @param array $opts */ - public function aList($data, $title = null, array $opts = []): void + public function aList($data, string $title = '', array $opts = []): void { - Show::aList($data, $title, $opts); + SingleList::show($data, $title, $opts); } /** - * @inheritdoc - * @see Show::mList() + * @param array $data + * @param array $opts */ public function multiList(array $data, array $opts = []): void { - Show::mList($data, $opts); + MultiList::show($data, $opts); } /** - * @inheritdoc - * @see Show::mList() + * @param array $data + * @param array $opts */ public function mList(array $data, array $opts = []): void { - Show::mList($data, $opts); + MultiList::show($data, $opts); } /** - * helpPanel - * - * @inheritdoc - * @see Show::helpPanel() + * @param array $config */ public function helpPanel(array $config): void { - Show::helpPanel($config); + HelpPanel::show($config); } /** - * @inheritdoc - * @see Show::panel() + * @param array $data + * @param string $title + * @param array $opts */ - public function panel(array $data, $title = 'Information panel', array $opts = []): void + public function panel(array $data, string $title = 'Information panel', array $opts = []): void { - Show::panel($data, $title, $opts); + Panel::show($data, $title, $opts); } /** - * @inheritdoc - * @see Show::table() + * @param array $data + * @param string $title + * @param array $opts */ - public function table(array $data, $title = 'Data Table', array $opts = []): void + public function table(array $data, string $title = 'Data Table', array $opts = []): void { - Show::table($data, $title, $opts); + Table::show($data, $title, $opts); } /** - * @inheritdoc - * @see Show::progressBar() + * @param int $total + * @param string $msg + * @param string $doneMsg + * + * @return Generator */ - public function progressTxt($total, $msg, $doneMsg = ''): Generator + public function progressTxt(int $total, string $msg, string $doneMsg = ''): Generator { return Show::progressTxt($total, $msg, $doneMsg); } diff --git a/src/Traits/InputOutputAwareTrait.php b/src/Traits/InputOutputAwareTrait.php index e89786c6..764a991e 100644 --- a/src/Traits/InputOutputAwareTrait.php +++ b/src/Traits/InputOutputAwareTrait.php @@ -13,7 +13,6 @@ use Inhere\Console\IO\InputInterface; use Inhere\Console\IO\Output; use Inhere\Console\IO\OutputInterface; -use InvalidArgumentException; /** * Class InputOutputAwareTrait @@ -22,10 +21,14 @@ */ trait InputOutputAwareTrait { - /** @var Input|InputInterface */ + /** + * @var Input|InputInterface + */ protected $input; - /** @var Output|OutputInterface */ + /** + * @var Output|OutputInterface + */ protected $output; /** @@ -45,8 +48,11 @@ public function getCommandName(): string } /** + * @param int|string $name + * @param mixed $default + * + * @return mixed|null * @see Input::getArg() - * {@inheritdoc} */ public function getArg($name, $default = null) { @@ -54,8 +60,10 @@ public function getArg($name, $default = null) } /** + * @param string $default + * + * @return string * @see Input::getFirstArg() - * {@inheritdoc} */ public function getFirstArg(string $default = ''): string { @@ -63,8 +71,9 @@ public function getFirstArg(string $default = ''): string } /** - * {@inheritdoc} - * @throws InvalidArgumentException + * @param int|string $name + * + * @return mixed * @see Input::getRequiredArg() */ public function getRequiredArg($name) @@ -73,7 +82,10 @@ public function getRequiredArg($name) } /** - * {@inheritdoc} + * @param array $names + * @param mixed $default + * + * @return bool|mixed|null * @see Input::getSameArg() */ public function getSameArg(array $names, $default = null) @@ -101,7 +113,6 @@ public function getSameOpt(array $names, $default = null) /** * {@inheritdoc} - * @throws InvalidArgumentException * @see Input::getRequiredOpt() */ public function getRequiredOpt(string $name) diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index d7ee5cc3..1e28e647 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -252,7 +252,7 @@ public static function spliceKeyValue(array $data, array $opts = []): string 'valStyle' => '', // e.g 'info','comment' 'keyMinWidth' => 8, 'keyMaxWidth' => null, // if not set, will automatic calculation - 'ucFirst' => true, // upper first char + 'ucFirst' => true, // upper first char for value ], $opts); if (!is_numeric($opts['keyMaxWidth'])) { @@ -271,13 +271,13 @@ public static function spliceKeyValue(array $data, array $opts = []): string $text .= $opts['leftChar']; if ($hasKey && $opts['keyMaxWidth']) { - $key = str_pad($key, $opts['keyMaxWidth'], ' '); + $key = str_pad((string)$key, $opts['keyMaxWidth'], ' '); $text .= ColorTag::wrap($key, $keyStyle) . $opts['sepChar']; } // if value is array, translate array to string if (is_array($value)) { - $temp = ''; + $temp = '['; /** @var array $value */ foreach ($value as $k => $val) { @@ -290,7 +290,7 @@ public static function spliceKeyValue(array $data, array $opts = []): string $temp .= (!is_numeric($k) ? "$k: " : '') . "$val, "; } - $value = rtrim($temp, ' ,'); + $value = rtrim($temp, ' ,') . ']'; } elseif (is_bool($value)) { $value = $value ? '(True)' : '(False)'; } else { diff --git a/src/Util/Show.php b/src/Util/Show.php index cf5dd3b1..2fd28dcb 100644 --- a/src/Util/Show.php +++ b/src/Util/Show.php @@ -306,8 +306,11 @@ public static function aList($data, string $title = '', array $opts = []) } /** - * @see Show::aList() - * {@inheritdoc} + * @param mixed $data + * @param string $title + * @param array $opts + * + * @return int|string */ public static function sList($data, string $title = '', array $opts = []) { From 6d0e3bab3a1d164fb8deed16863504efeb57ac7c Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 25 May 2020 21:48:28 +0800 Subject: [PATCH 017/258] update some for error --- src/Component/ErrorHandler.php | 2 +- src/Router.php | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Component/ErrorHandler.php b/src/Component/ErrorHandler.php index 342749b7..98fa3017 100644 --- a/src/Component/ErrorHandler.php +++ b/src/Component/ErrorHandler.php @@ -61,7 +61,7 @@ public function handle(Throwable $e, AbstractApplication $app): void } else { // simple output $app->getOutput()->error('An error occurred! MESSAGE: ' . $e->getMessage()); - $app->write("\nYou can use '--debug' to see error details."); + $app->write("\nYou can use '--debug 4' to see error details."); } } } diff --git a/src/Router.php b/src/Router.php index e0f09b33..84c47a6d 100644 --- a/src/Router.php +++ b/src/Router.php @@ -9,13 +9,9 @@ use Inhere\Console\Traits\NameAliasTrait; use Inhere\Console\Util\Helper; use InvalidArgumentException; -use function array_filter; use function array_keys; use function array_merge; -use function array_splice; -use function array_values; use function class_exists; -use function count; use function explode; use function in_array; use function is_int; @@ -290,9 +286,9 @@ public function match(string $name): array // like 'home:index' if (strpos($realName, $sep) > 0) { - $input = array_values(array_filter(explode($sep, $realName))); + [$group, $action] = explode($sep, $realName, 2); - [$group, $action] = count($input) > 2 ? array_splice($input, 2) : $input; + $action = trim($action, ': '); // resolve alias $group = $this->resolveAlias($group); } From edc28248c055ba005b4ffeeeb777b97531b8a62c Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 26 May 2020 15:24:37 +0800 Subject: [PATCH 018/258] update some logic for input --- src/AbstractHandler.php | 32 ++++---- src/IO/AbstractInput.php | 43 +++++++++- src/IO/Input.php | 62 ++++++++------ src/IO/Input/AloneInput.php | 26 ++++++ src/IO/Input/ArrayInput.php | 24 ++++-- src/IO/Input/InputArgument.php | 2 + src/IO/Input/InputItem.php | 122 +++++++++++++++++++++++----- src/IO/Input/InputOption.php | 36 +++++++- src/IO/{ => Input}/StrictInput.php | 3 +- src/IO/InputDefinition.php | 4 +- src/IO/Output/BufferOutput.php | 13 +++ src/Traits/ApplicationHelpTrait.php | 2 +- 12 files changed, 291 insertions(+), 78 deletions(-) create mode 100644 src/IO/Input/AloneInput.php rename src/IO/{ => Input}/StrictInput.php (97%) create mode 100644 src/IO/Output/BufferOutput.php diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 0935f608..fe03be53 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -192,15 +192,20 @@ protected function createDefinition(): InputDefinition */ protected function annotationVars(): array { + $fullCmd = $this->input->getFullCommand(); + $binName = $this->input->getScriptName(); + // e.g: `more info see {name}:index` return [ 'name' => self::getName(), 'group' => self::getName(), 'workDir' => $this->input->getPwd(), 'script' => $this->input->getScript(), // bin/app - 'binName' => $this->input->getScript(), // bin/app + 'binName' => $binName, // app + 'scriptName' => $binName, // app 'command' => $this->input->getCommand(), // demo OR home:test - 'fullCommand' => $this->input->getFullCommand(), + 'fullCmd' => $fullCmd, + 'fullCommand' => $fullCmd, ]; } @@ -342,10 +347,10 @@ public function validateInput(): bool return true; } - $in = $this->input; - $out = $this->output; - $givenArgs = $errArgs = []; + $in = $this->input; + $out = $this->output; + $givenArgs = $errArgs = []; foreach ($in->getArgs() as $key => $value) { if (is_int($key)) { $givenArgs[$key] = $value; @@ -360,7 +365,7 @@ public function validateInput(): bool } $defArgs = $def->getArguments(); - $missingArgs = array_filter(array_keys($defArgs), function ($name, $key) use ($def, $givenArgs) { + $missingArgs = array_filter(array_keys($defArgs), static function ($name, $key) use ($def, $givenArgs) { return !array_key_exists($key, $givenArgs) && $def->argumentIsRequired($name); }, ARRAY_FILTER_USE_BOTH); @@ -389,10 +394,8 @@ public function validateInput(): bool $names = array_keys($unknown); $first = array_shift($names); - throw new InvalidArgumentException(sprintf( - 'Input option is not exists (unknown: "%s").', - (isset($first[1]) ? '--' : '-') . $first - )); + throw new InvalidArgumentException(sprintf('Input option is not exists (unknown: "%s").', + (isset($first[1]) ? '--' : '-') . $first)); } foreach ($defOpts as $name => $conf) { @@ -539,13 +542,8 @@ protected function showHelpByMethodAnnotations(string $method, string $action = $ref = new ReflectionClass($this); $name = $this->input->getCommand(); - $this->logf( - Console::VERB_CRAZY, - 'display help info for the method=%s, action=%s, class=%s', - $method, - $action, - static::class - ); + $this->logf(Console::VERB_CRAZY, 'display help info for the method=%s, action=%s, class=%s', $method, $action, + static::class); if (!$ref->hasMethod($method)) { $this->write("The command [$name] don't exist in the group: " . static::getName()); diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index c9985dab..2dd503de 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -28,13 +28,21 @@ abstract class AbstractInput implements InputInterface protected $pwd; /** - * the script name + * The script path * e.g `./bin/app` OR `bin/cli.php` * * @var string */ protected $script; + /** + * The script name + * e.g `app` OR `cli.php` + * + * @var string + */ + protected $scriptName; + /** * the command name(Is first argument) * e.g `start` OR `start` @@ -51,12 +59,19 @@ abstract class AbstractInput implements InputInterface protected $fullScript; /** - * raw argv data. + * Raw argv data. * * @var array */ protected $tokens; + /** + * Same the $tokens but no $script + * + * @var array + */ + protected $flags = []; + /** * Input args data * @@ -679,6 +694,14 @@ public function getPwd(): string return $this->pwd; } + /** + * @return string + */ + public function getWorkDir(): string + { + return $this->getPwd(); + } + /** * @param string $pwd */ @@ -703,6 +726,22 @@ public function setScript(string $script): void $this->script = $script; } + /** + * @return string + */ + public function getScriptName(): string + { + return $this->scriptName; + } + + /** + * @return string + */ + public function getBinName(): string + { + return $this->scriptName; + } + /** * @return string */ diff --git a/src/IO/Input.php b/src/IO/Input.php index 7f828c4c..75689664 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -11,6 +11,7 @@ use Toolkit\Cli\Flags; use function array_map; use function array_shift; +use function basename; use function fgets; use function fwrite; use function implode; @@ -48,23 +49,48 @@ class Input extends AbstractInput public function __construct(array $args = null, bool $parsing = true) { if (null === $args) { - $args = (array)$_SERVER['argv']; + $args = $_SERVER['argv']; } - $this->pwd = $this->getPwd(); - $this->tokens = $args; - $this->script = array_shift($args); - $this->fullScript = implode(' ', $args); + $this->collectInfo($args); if ($parsing) { - // list($this->args, $this->sOpts, $this->lOpts) = InputParser::fromArgv($args); - [$this->args, $this->sOpts, $this->lOpts] = Flags::parseArgv($args); - - // find command name - $this->findCommand(); + $this->doParse($this->flags); } } + /** + * @param array $args + */ + protected function collectInfo(array $args): void + { + $this->getPwd(); + + $this->tokens = $args; + $this->script = array_shift($args); + $this->flags = $args; // no script + // bin name + $this->scriptName = basename($this->script); + // full script + $this->fullScript = implode(' ', $args); + + } + + /** + * @param array $args + */ + protected function doParse(array $args): void + { + [ + $this->args, + $this->sOpts, + $this->lOpts + ] = Flags::parseArgv($args); + + // find command name + $this->findCommand(); + } + /** * @return string */ @@ -122,22 +148,6 @@ public function getFullCommand(): string return $this->script . ' ' . $this->command; } - /** - * @return string - */ - public function getScriptName(): string - { - return $this->script; - } - - /** - * @return string - */ - public function getBinName(): string - { - return $this->script; - } - /** * @return resource */ diff --git a/src/IO/Input/AloneInput.php b/src/IO/Input/AloneInput.php new file mode 100644 index 00000000..1b07ea42 --- /dev/null +++ b/src/IO/Input/AloneInput.php @@ -0,0 +1,26 @@ +args, + $this->sOpts, + $this->lOpts + ] = Flags::parseArgv($args); + } +} diff --git a/src/IO/Input/ArrayInput.php b/src/IO/Input/ArrayInput.php index 8181a310..b856ba7f 100644 --- a/src/IO/Input/ArrayInput.php +++ b/src/IO/Input/ArrayInput.php @@ -30,15 +30,23 @@ public function __construct(array $args = null, bool $parsing = true) { parent::__construct([], false); - $this->tokens = $args; - $this->script = array_shift($args); - $this->fullScript = implode(' ', $args); - if ($parsing && $args) { - [$this->args, $this->sOpts, $this->lOpts] = Flags::parseArray($args); - - // find command name - $this->findCommand(); + $this->doParse($this->flags); } } + + /** + * @param array $args + */ + protected function doParse(array $args): void + { + [ + $this->args, + $this->sOpts, + $this->lOpts + ] = Flags::parseArray($args); + + // find command name + $this->findCommand(); + } } diff --git a/src/IO/Input/InputArgument.php b/src/IO/Input/InputArgument.php index c0015722..4ff86f55 100644 --- a/src/IO/Input/InputArgument.php +++ b/src/IO/Input/InputArgument.php @@ -17,6 +17,8 @@ class InputArgument extends InputItem { /** + * The argument position + * * @var int */ private $index = 0; diff --git a/src/IO/Input/InputItem.php b/src/IO/Input/InputItem.php index 2face242..cb7e67fa 100644 --- a/src/IO/Input/InputItem.php +++ b/src/IO/Input/InputItem.php @@ -21,58 +21,62 @@ class InputItem /** * @var string */ - public $name; + private $name; /** * @var string */ - public $description; + private $description; /** * @var int */ - public $mode; + private $mode; /** * The argument data type. (eg: 'string', 'array', 'mixed') * * @var string */ - public $type; + private $type; /** - * default value + * The default value * * @var mixed */ - public $default; + private $default; /** - * @param string $name - * @param int|null $mode - * @param string $description - * @param null $default + * @param string $name + * @param int $mode see Input::ARG_* or Input::OPT_* + * @param string $description + * @param null $default * * @return static */ - public static function make(string $name, int $mode = null, string $description = '', $default = null) + public static function make(string $name, int $mode = 0, string $description = '', $default = null) { return new static($name, $mode, $description, $default); } /** - * class constructor. + * Class constructor. * - * @param string $name - * @param int|null $mode - * @param string $description - * @param mixed $default The default value - * - for InputArgument::OPTIONAL mode only + * @param string $name + * @param int $mode see Input::ARG_* or Input::OPT_* + * @param string $description + * @param mixed $default The default value + * - for Input::ARG_OPTIONAL mode only * - must be null for InputOption::OPT_BOOL */ - public function __construct(string $name, int $mode = null, string $description = '', $default = null) + public function __construct(string $name, int $mode = 0, string $description = '', $default = null) { - $this->mode = Input::ARG_IS_ARRAY; + $this->name = $name; + $this->mode = $mode; + + $this->default = $default; + $this->setDescription($description); } /** @@ -82,4 +86,84 @@ public function isArray(): bool { return $this->mode === Input::ARG_IS_ARRAY; } + + /** + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * @param string $type + */ + public function setType(string $type): void + { + $this->type = $type; + } + + /** + * @return int + */ + public function getMode(): int + { + return $this->mode; + } + + /** + * @param int $mode + */ + public function setMode(int $mode): void + { + $this->mode = $mode; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * @param mixed $default + */ + public function setDefault($default): void + { + $this->default = $default; + } + + /** + * @return string + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * @param string $description + */ + public function setDescription(string $description): void + { + $this->description = $description; + } } diff --git a/src/IO/Input/InputOption.php b/src/IO/Input/InputOption.php index 70f8e6bc..109345c4 100644 --- a/src/IO/Input/InputOption.php +++ b/src/IO/Input/InputOption.php @@ -21,10 +21,42 @@ class InputOption extends InputItem * * @var string */ - public $alias; + private $alias; /** * @var string|array */ - public $shortcut; + private $shortcut; + + /** + * @return string + */ + public function getAlias(): string + { + return $this->alias; + } + + /** + * @param string $alias + */ + public function setAlias(string $alias): void + { + $this->alias = $alias; + } + + /** + * @return array + */ + public function getShortcut() + { + return $this->shortcut; + } + + /** + * @param array|string $shortcut + */ + public function setShortcut($shortcut): void + { + $this->shortcut = (array)$shortcut; + } } diff --git a/src/IO/StrictInput.php b/src/IO/Input/StrictInput.php similarity index 97% rename from src/IO/StrictInput.php rename to src/IO/Input/StrictInput.php index 6e1997c3..f606b886 100644 --- a/src/IO/StrictInput.php +++ b/src/IO/Input/StrictInput.php @@ -6,8 +6,9 @@ * Time: 18:39 */ -namespace Inhere\Console\IO; +namespace Inhere\Console\IO\Input; +use Inhere\Console\IO\Input; use function array_shift; use function strpos; diff --git a/src/IO/InputDefinition.php b/src/IO/InputDefinition.php index b8080675..91432719 100644 --- a/src/IO/InputDefinition.php +++ b/src/IO/InputDefinition.php @@ -137,9 +137,9 @@ public function addArg(string $name, int $mode = null, string $description = '', * Adds an argument. * * @param string $name The argument name - * @param int $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL + * @param int $mode The argument mode: Input::ARG_REQUIRED or Input::ARG_OPTIONAL * @param string $description A description text - * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) + * @param mixed $default The default value (for Input::ARG_OPTIONAL mode only) * * @return $this * @throws LogicException diff --git a/src/IO/Output/BufferOutput.php b/src/IO/Output/BufferOutput.php new file mode 100644 index 00000000..ccb14d78 --- /dev/null +++ b/src/IO/Output/BufferOutput.php @@ -0,0 +1,13 @@ +output; + // $output = $this->output; /** @var Router $router */ $router = $this->getRouter(); $script = $this->getScriptName(); From 128721bb2476064a9ac757e397444280b483e7c4 Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 26 May 2020 17:32:22 +0800 Subject: [PATCH 019/258] fix some bug for definition --- src/AbstractHandler.php | 13 ++++++++----- src/IO/InputDefinition.php | 31 +++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index fe03be53..7e1482c1 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -385,21 +385,24 @@ public function validateInput(): bool $in->setArgs($args); // check options - $opts = $missingOpts = []; + $opts = $missingOpts = []; + $givenOpts = $in->getOptions(); - $defOpts = $def->getOptions(); + $allDefOpts = $def->getAllOptionNames(); // check unknown options - if ($unknown = array_diff_key($givenOpts, $defOpts)) { + if ($unknown = array_diff_key($givenOpts, $allDefOpts)) { $names = array_keys($unknown); $first = array_shift($names); - throw new InvalidArgumentException(sprintf('Input option is not exists (unknown: "%s").', - (isset($first[1]) ? '--' : '-') . $first)); + $errMsg = sprintf('Input option is not exists (unknown: "%s").', (isset($first[1]) ? '--' : '-') . $first); + throw new InvalidArgumentException($errMsg); } + $defOpts = $def->getOptions(); foreach ($defOpts as $name => $conf) { if (!$in->hasLOpt($name)) { + // TODO multi short: 'a|b|c' if (($srt = $conf['shortcut']) && $in->hasSOpt($srt)) { $opts[$name] = $in->sOpt($srt); } elseif ($conf['required']) { diff --git a/src/IO/InputDefinition.php b/src/IO/InputDefinition.php index 91432719..35a33e50 100644 --- a/src/IO/InputDefinition.php +++ b/src/IO/InputDefinition.php @@ -11,6 +11,7 @@ use InvalidArgumentException; use LogicException; use function array_filter; +use function array_keys; use function array_merge; use function array_values; use function count; @@ -317,6 +318,8 @@ public function addOpt( * * @param string|bool $name The option name, must is a string * @param string|array|null $shortcut The shortcut (can be null) + * - array: [a, b] + * - string: 'a|b' * @param int $mode The option mode: One of the Input::OPT_* constants * @param string $description A description text * @param mixed $default The default value (must be null for InputOption::OPT_BOOL) @@ -327,8 +330,8 @@ public function addOpt( */ public function addOption( string $name, - string $shortcut = null, - int $mode = null, + $shortcut = '', + int $mode = 0, string $description = '', $default = null ): self { @@ -340,18 +343,13 @@ public function addOption( throw new InvalidArgumentException('An option name cannot be empty.'); } - if (empty($shortcut)) { - $shortcut = null; - } - - if (null === $mode) { + if ($mode <= 0) { $mode = Input::OPT_BOOLEAN; } elseif (!is_int($mode) || $mode > 15 || $mode < 1) { throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); } $isArray = $mode === Input::OPT_IS_ARRAY; - if ($isArray && !$this->optionIsAcceptValue($mode)) { throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); } @@ -440,6 +438,23 @@ public function getOptions(): array return $this->options; } + /** + * Gets the name of options, contains short-name + * + * @return array[] key is name + */ + public function getAllOptionNames(): array + { + $allNames = $this->shortcuts; + $longNames = array_keys($this->options); + + foreach ($longNames as $name) { + $allNames[$name] = 1; + } + + return $allNames; + } + /** * @param string $name The InputOption shortcut * From d86e8a7e3549d3f2479cfbe3ce77bf4cf8df78c0 Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 26 May 2020 20:49:36 +0800 Subject: [PATCH 020/258] fix error for vlaidate input --- src/AbstractApplication.php | 24 +++++++++++++++++++++ src/AbstractHandler.php | 42 ++++++++++++++++++++++++++----------- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index b0fd1ed1..b92b3fdf 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -53,6 +53,20 @@ abstract class AbstractApplication implements ApplicationInterface 'list' => 'List all group and alone commands', ]; + /** + * @var int[] + */ + protected static $globalOptionNames = [ + 'debug' => 1, + 'profile' => 1, + 'no-color' => 1, + 'h' => 1, + 'help' => 1, + 'V' => 1, + 'version' => 1, + 'no-interactive' => 1, + ]; + /** @var array */ protected static $globalOptions = [ '--debug' => 'Setting the application runtime debug level(0 - 4)', @@ -147,6 +161,16 @@ protected function init(): void $this->registerErrorHandle(); } + /** + * @param string $name + * + * @return bool + */ + public static function isGlobalOption(string $name): bool + { + return isset(self::$globalOptionNames[$name]); + } + /** * @return array */ diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 7e1482c1..4724a651 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -384,21 +384,11 @@ public function validateInput(): bool $in->setArgs($args); + $this->checkNotExistsOptions($def); + // check options $opts = $missingOpts = []; - $givenOpts = $in->getOptions(); - $allDefOpts = $def->getAllOptionNames(); - - // check unknown options - if ($unknown = array_diff_key($givenOpts, $allDefOpts)) { - $names = array_keys($unknown); - $first = array_shift($names); - - $errMsg = sprintf('Input option is not exists (unknown: "%s").', (isset($first[1]) ? '--' : '-') . $first); - throw new InvalidArgumentException($errMsg); - } - $defOpts = $def->getOptions(); foreach ($defOpts as $name => $conf) { if (!$in->hasLOpt($name)) { @@ -423,6 +413,34 @@ public function validateInput(): bool return true; } + private function checkNotExistsOptions(InputDefinition $def): void + { + $givenOpts = $this->input->getOptions(); + $allDefOpts = $def->getAllOptionNames(); + + // check unknown options + if ($unknown = array_diff_key($givenOpts, $allDefOpts)) { + $names = array_keys($unknown); + + // $first = array_shift($names); + $first = ''; + foreach ($names as $name) { + if (!Application::isGlobalOption($name)) { + $first = $name; + break; + } + } + + if (!$first) { + return; + } + + $errMsg = sprintf('Input option is not exists (unknown: "%s").', (isset($first[1]) ? '--' : '-') . $first); + throw new InvalidArgumentException($errMsg); + } + + } + /************************************************************************** * helper methods **************************************************************************/ From adc63453244bba9ede7e7cab8e981ebb0a390ffe Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 27 May 2020 14:16:32 +0800 Subject: [PATCH 021/258] update some --- src/AbstractHandler.php | 6 ++-- src/Controller.php | 4 +-- src/IO/AbstractInput.php | 74 +++++++++++++++++++++++++++++++++++--- src/IO/InputDefinition.php | 70 +++++++++++++++++++++--------------- 4 files changed, 118 insertions(+), 36 deletions(-) diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 4724a651..adaf7ca2 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -34,6 +34,7 @@ use function cli_set_process_title; use function count; use function error_get_last; +use function explode; use function function_exists; use function implode; use function is_array; @@ -392,8 +393,9 @@ public function validateInput(): bool $defOpts = $def->getOptions(); foreach ($defOpts as $name => $conf) { if (!$in->hasLOpt($name)) { - // TODO multi short: 'a|b|c' - if (($srt = $conf['shortcut']) && $in->hasSOpt($srt)) { + // support multi short: 'a|b|c' + $shortNames = $conf['shortcut'] ? explode('|', $conf['shortcut']) : []; + if ($srt = $in->findOneShortOpts($shortNames)) { $opts[$name] = $in->sOpt($srt); } elseif ($conf['required']) { $missingOpts[] = "--{$name}" . ($srt ? "|-{$srt}" : ''); diff --git a/src/Controller.php b/src/Controller.php index c641b05b..a480299c 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -108,8 +108,8 @@ protected function init(): void */ protected function groupOptions(): array { - return [// '--skip-invalid' => 'Whether ignore invalid arguments and options, when use input definition', - ]; + // ['--skip-invalid' => 'Whether ignore invalid arguments and options, when use input definition',] + return []; } /** diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index 2dd503de..1871dbb3 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -11,6 +11,7 @@ use InvalidArgumentException; use function array_merge; use function getcwd; +use function is_array; use function is_bool; use function is_int; use function trim; @@ -250,16 +251,63 @@ public function getSecondArg(string $default = ''): string } /** + * Get an string argument value + * + * @param string|int $key + * @param string $default + * + * @return string + */ + public function getStringArg($key, string $default = ''): string + { + return (string)$this->get($key, $default); + } + + /** + * Get an int argument value + * + * @param string|int $key + * @param int $default + * + * @return int + */ + public function getInt($key, int $default = 0): int + { + return $this->getIntArg($key, $default); + } + + /** + * Get an int argument value + * * @param string|int $key * @param int $default * * @return int */ - public function getInt($key, $default = 0): int + public function getIntArg($key, int $default = 0): int + { + $value = $this->get($key); + + return $value === null ? $default : (int)$value; + } + + /** + * Get an array argument value + * + * @param string|int $key + * @param array $default + * + * @return array + */ + public function getArrayArg($key, array $default = []): array { $value = $this->get($key); - return $value === null ? (int)$default : (int)$value; + if (is_array($value)) { + return $value; + } + + return $value ? [$value] : $default; } /** @@ -500,9 +548,9 @@ public function getShortOpt(string $name, $default = null) } /** - * check short-opt exists + * Check short-opt exists * - * @param $name + * @param string $name * * @return bool */ @@ -511,6 +559,24 @@ public function hasSOpt(string $name): bool return isset($this->sOpts[$name]); } + /** + * Check multi short-opt exists + * + * @param string[] $names + * + * @return string + */ + public function findOneShortOpts(array $names): string + { + foreach ($names as $name) { + if (isset($this->sOpts[$name])) { + return $name; + } + } + + return ''; + } + /** * get short-opt value(bool) * diff --git a/src/IO/InputDefinition.php b/src/IO/InputDefinition.php index 35a33e50..1dc5e35e 100644 --- a/src/IO/InputDefinition.php +++ b/src/IO/InputDefinition.php @@ -20,7 +20,6 @@ use function is_int; use function preg_split; use function sprintf; -use function strpos; use function strtoupper; use function trim; @@ -39,25 +38,44 @@ class InputDefinition 'description' => '', ]; - /** @var string|array */ + /** + * @var string|array + */ private $example; - /** @var string */ + /** + * @var string + */ private $description; - /** @var array[] */ + /** + * @var array[] + */ private $arguments; + /** + * @var int + */ private $requiredCount = 0; - private $hasOptional = false; + /** + * @var bool + */ + private $hasOptionalArgument = false; + /** + * @var bool + */ private $hasAnArrayArgument = false; - /** @var array[] */ + /** + * @var array[] + */ private $options; - /** @var array */ + /** + * @var array + */ private $shortcuts; /** @@ -138,16 +156,17 @@ public function addArg(string $name, int $mode = null, string $description = '', * Adds an argument. * * @param string $name The argument name - * @param int $mode The argument mode: Input::ARG_REQUIRED or Input::ARG_OPTIONAL + * @param int $mode The argument mode flags. eg: Input::ARG_REQUIRED, Input::ARG_OPTIONAL + * allow more flags, eg: Input::ARG_REQUIRED|Input::ARG_IS_ARRAY * @param string $description A description text * @param mixed $default The default value (for Input::ARG_OPTIONAL mode only) * * @return $this * @throws LogicException */ - public function addArgument(string $name, int $mode = null, string $description = '', $default = null): self + public function addArgument(string $name, int $mode = 0, string $description = '', $default = null): self { - if (null === $mode) { + if (0 === $mode) { $mode = Input::ARG_OPTIONAL; } elseif (!is_int($mode) || $mode > 7 || $mode < 1) { throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); @@ -161,14 +180,16 @@ public function addArgument(string $name, int $mode = null, string $description throw new LogicException('Cannot add an argument after an array argument.'); } - if (($required = $mode === Input::ARG_REQUIRED) && $this->hasOptional) { + $required = ($mode & Input::ARG_REQUIRED) > 0; + if ($required && $this->hasOptionalArgument) { throw new LogicException('Cannot add a required argument after an optional one.'); } - if ($isArray = ($mode === Input::ARG_IS_ARRAY)) { - if (!$this->argumentIsAcceptValue($mode)) { - throw new InvalidArgumentException('Impossible to have an option mode ARG_IS_ARRAY if the option does not accept a value.'); - } + $isArray = ($mode & Input::ARG_IS_ARRAY) > 0; + if ($isArray) { + // if (false === $this->argumentIsAcceptValue($mode)) { + // throw new InvalidArgumentException('Impossible to have an option mode ARG_IS_ARRAY if the option does not accept a value.'); + // } $this->hasAnArrayArgument = true; @@ -186,7 +207,7 @@ public function addArgument(string $name, int $mode = null, string $description ++$this->requiredCount; } else { - $this->hasOptional = true; + $this->hasOptionalArgument = true; } $this->arguments[$name] = [ @@ -335,10 +356,7 @@ public function addOption( string $description = '', $default = null ): self { - if (0 === strpos($name, '-')) { - $name = trim($name, '-'); - } - + $name = trim($name, '-'); if (empty($name)) { throw new InvalidArgumentException('An option name cannot be empty.'); } @@ -351,7 +369,7 @@ public function addOption( $isArray = $mode === Input::OPT_IS_ARRAY; if ($isArray && !$this->optionIsAcceptValue($mode)) { - throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); + throw new InvalidArgumentException('Impossible to have an option mode OPT_IS_ARRAY if the option does not accept a value.'); } if (isset($this->options[$name])) { @@ -445,7 +463,7 @@ public function getOptions(): array */ public function getAllOptionNames(): array { - $allNames = $this->shortcuts; + $allNames = $this->shortcuts; $longNames = array_keys($this->options); foreach ($longNames as $name) { @@ -521,12 +539,8 @@ public function getSynopsis(bool $short = false): array $value = ''; if ($this->optionIsAcceptValue($option['mode'])) { - $value = sprintf( - ' %s%s%s', - $option['optional'] ? '[' : '', - strtoupper($name), - $option['optional'] ? ']' : '' - ); + $value = sprintf(' %s%s%s', $option['optional'] ? '[' : '', strtoupper($name), + $option['optional'] ? ']' : ''); } $shortcut = $option['shortcut'] ? sprintf('-%s, ', $option['shortcut']) : ' '; From ad63db18adab3ea01973f72cc259dc60a7c6eebe Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 28 May 2020 17:08:05 +0800 Subject: [PATCH 022/258] add some new inupt method --- src/AbstractHandler.php | 21 +++++++++++++++++++-- src/Controller.php | 6 +++++- src/IO/AbstractInput.php | 28 +++++++++++++++++++++++++++- src/IO/InputDefinition.php | 7 ++++++- 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index adaf7ca2..f295e454 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -195,6 +195,7 @@ protected function annotationVars(): array { $fullCmd = $this->input->getFullCommand(); $binName = $this->input->getScriptName(); + $command = $this->input->getCommand(); // e.g: `more info see {name}:index` return [ @@ -204,9 +205,10 @@ protected function annotationVars(): array 'script' => $this->input->getScript(), // bin/app 'binName' => $binName, // app 'scriptName' => $binName, // app - 'command' => $this->input->getCommand(), // demo OR home:test - 'fullCmd' => $fullCmd, + 'command' => $command, // demo OR home:test + 'fullCmd' => $fullCmd, // bin/app demo OR bin/app home:test 'fullCommand' => $fullCmd, + 'binWithCmd' => $binName . ' ' . $command, ]; } @@ -529,6 +531,21 @@ protected function showHelp(): bool // if has InputDefinition object. (The comment of the command will not be parsed and used at this time.) $help = $definition->getSynopsis(); + // parse example + $example = $help['example:']; + if (!empty($example)) { + if (is_string($example)) { + $help['example:'] = $this->parseCommentsVars($example); + } elseif (is_array($example)) { + foreach ($example as &$item) { + $item = $this->parseCommentsVars($item); + } + unset($item); + } else { + $help['example:'] = ''; + } + } + // build usage $help['usage:'] = sprintf('%s %s %s', $this->getScriptName(), $this->getCommandName(), $help['usage:']); // align global options diff --git a/src/Controller.php b/src/Controller.php index a480299c..8298bedf 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -50,7 +50,11 @@ abstract class Controller extends AbstractHandler implements ControllerInterface '--show-disabled' => 'Whether display disabled commands', ]; - /** @var string Action name, no suffix. */ + /** + * Action name, no suffix. + * + * @var string + */ private $action; /** @var string */ diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index 1871dbb3..a10fd221 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -407,7 +407,33 @@ public function getRequiredOpt(string $name) } /** - * get (long/short)opt value(bool) + * Get an string option(long/short) value + * + * @param string $name + * @param string $default + * + * @return string + */ + public function getStringOpt(string $name, string $default = ''): string + { + return (string)$this->getOpt($name, $default); + } + + /** + * Get an int option(long/short) value + * + * @param string $name + * @param int $default + * + * @return int + */ + public function getIntOpt(string $name, int $default = 0): int + { + return (int)$this->getOpt($name, $default); + } + + /** + * Get (long/short)option value(bool) * eg: -h --help * * @param string $name diff --git a/src/IO/InputDefinition.php b/src/IO/InputDefinition.php index 1dc5e35e..bdb288fe 100644 --- a/src/IO/InputDefinition.php +++ b/src/IO/InputDefinition.php @@ -573,12 +573,17 @@ public function getSynopsis(bool $short = false): array $args[$name] = $des; } + $example = $this->example; + if ($this->example) { + + } + return [ $this->description, 'usage:' => implode(' ', $elements), 'options:' => $opts, 'arguments:' => $args, - 'example:' => $this->example, + 'example:' => $example, 'global options:' => '', ]; } From 1c3a332520bd035e80cce0086b7f738eb03485ae Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 28 May 2020 19:17:44 +0800 Subject: [PATCH 023/258] update some for command help --- src/AbstractHandler.php | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index f295e454..14473988 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -30,7 +30,6 @@ use function array_key_exists; use function array_keys; use function array_merge; -use function array_shift; use function cli_set_process_title; use function count; use function error_get_last; @@ -208,7 +207,7 @@ protected function annotationVars(): array 'command' => $command, // demo OR home:test 'fullCmd' => $fullCmd, // bin/app demo OR bin/app home:test 'fullCommand' => $fullCmd, - 'binWithCmd' => $binName . ' ' . $command, + 'binWithCmd' => $binName . ' ' . $command, ]; } @@ -419,7 +418,7 @@ public function validateInput(): bool private function checkNotExistsOptions(InputDefinition $def): void { - $givenOpts = $this->input->getOptions(); + $givenOpts = $this->input->getOptions(); $allDefOpts = $def->getAllOptionNames(); // check unknown options @@ -596,43 +595,45 @@ protected function showHelpByMethodAnnotations(string $method, string $action = return 0; } - $doc = $ref->getMethod($method)->getDocComment(); - $tags = PhpDoc::getTags($this->parseCommentsVars($doc)); - $isAlone = $ref->isSubclassOf(CommandInterface::class); - $help = []; + $help = []; + $doc = $ref->getMethod($method)->getDocComment(); + $tags = PhpDoc::getTags($this->parseCommentsVars($doc)); if ($aliases) { - $realName = $action ?: self::getName(); + $realName = $action ?: static::getName(); + // command name $help['Command:'] = sprintf('%s(alias: %s)', $realName, implode(',', $aliases)); } + // is an command object + $isCommand = $ref->isSubclassOf(CommandInterface::class); foreach (array_keys(self::$annotationTags) as $tag) { if (empty($tags[$tag]) || !is_string($tags[$tag])) { // for alone command - if ($tag === 'description' && $isAlone) { - $help['Description:'] = self::getDescription(); + if ($tag === 'description' && $isCommand) { + $help['Description:'] = static::getDescription(); continue; } if ($tag === 'usage') { - $help['Usage:'] = $this->commentsVars['fullCommand'] . ' [--options ...] [arguments ...]'; + $help['Usage:'] = $this->commentsVars['binWithCmd'] . ' [--options ...] [arguments ...]'; } continue; } // $msg = trim($tags[$tag]); - $msg = $tags[$tag]; - $tag = ucfirst($tag); + $message = $tags[$tag]; + $labelName = ucfirst($tag) . ':'; // for alone command - if (!$msg && $tag === 'description' && $isAlone) { - $msg = self::getDescription(); + if ($tag === 'description' && $isCommand) { + $message = static::getDescription(); } else { - $msg = preg_replace('#(\n)#', '$1 ', $msg); + $message = preg_replace('#(\n)#', '$1 ', $message); } - $help[$tag . ':'] = $msg; + $help[$labelName] = $message; } if (isset($help['Description:'])) { From d8ac9e8814f9a8b16cc7eca925be3b960004e3fc Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 29 May 2020 13:40:15 +0800 Subject: [PATCH 024/258] upsome --- src/AbstractApplication.php | 1 + src/Traits/ApplicationHelpTrait.php | 33 +++++++++++------------ src/Traits/InputOutputAwareTrait.php | 10 ++++++- src/Traits/UserInteractAwareTrait.php | 39 +++++++++++++++++++++++---- 4 files changed, 59 insertions(+), 24 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index b92b3fdf..a87461c9 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -96,6 +96,7 @@ abstract class AbstractApplication implements ApplicationInterface 'updateAt' => '2019.01.01', 'rootPath' => '', 'strictMode' => false, + 'interactive' => true, 'hideRootPath' => true, // 'timeZone' => 'Asia/Shanghai', diff --git a/src/Traits/ApplicationHelpTrait.php b/src/Traits/ApplicationHelpTrait.php index 351801f6..9c113c3a 100644 --- a/src/Traits/ApplicationHelpTrait.php +++ b/src/Traits/ApplicationHelpTrait.php @@ -96,19 +96,19 @@ public function showHelpInfo(string $command = ''): void return; } - $sep = $this->delimiter; - $script = $in->getScript(); + $delimiter = $this->delimiter; + $binName = $in->getScriptName(); /** @var Output $out */ $out = $this->output; $out->helpPanel([ - 'usage' => "$script {command} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", + 'usage' => "$binName {command} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", 'example' => [ - "$script test (run a independent command)", - "$script home{$sep}index (run a command of the group)", - "$script help {command} (see a command help information)", - "$script home{$sep}index -h (see a command help of the group)", - "$script --auto-completion --shell-env [zsh|bash] [--gen-file stdout]", + "$binName test (run a independent command)", + "$binName home{$delimiter}index (run a command of the group)", + "$binName help {command} (see a command help information)", + "$binName home{$delimiter}index -h (see a command help of the group)", + "$binName --auto-completion --shell-env [zsh|bash] [--gen-file stdout]", ] ]); } @@ -131,11 +131,9 @@ public function showCommandList(): void return; } - /** @var Output $output */ - // $output = $this->output; + /** @var Output $output */ // $output = $this->output; /** @var Router $router */ $router = $this->getRouter(); - $script = $this->getScriptName(); $hasGroup = $hasCommand = false; $groupArr = $commandArr = []; @@ -218,11 +216,13 @@ public function showCommandList(): void Console::writeln(sprintf('%s%s' . PHP_EOL, $appDesc, $appVer ? " (Version: $appVer)" : '')); } + $scriptName = $this->getScriptName(); + // built in options $internalOptions = FormatUtil::alignOptions(self::$globalOptions); Show::mList([ - 'Usage:' => "$script {command} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", + 'Usage:' => "$scriptName {COMMAND} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", 'Options:' => $internalOptions, 'Internal Commands:' => $internalCommands, 'Available Commands:' => array_merge($groupArr, $commandArr), @@ -231,7 +231,7 @@ public function showCommandList(): void ]); unset($groupArr, $commandArr, $internalCommands); - Console::write("More command information, please use: $script {command} -h"); + Console::write("More command information, please use: $scriptName {command} -h"); Console::flushBuffer(); } @@ -265,11 +265,8 @@ protected function dumpAutoCompletion(string $shellEnv, array $data): void if ($shellEnv === 'bash') { $tplFile = $tplDir . '/bash-completion.tpl'; - $list = array_merge( - $router->getCommandNames(), - $router->getControllerNames(), - $this->getInternalCommands() - ); + $list = array_merge($router->getCommandNames(), $router->getControllerNames(), + $this->getInternalCommands()); } else { $glue = PHP_EOL; $list = []; diff --git a/src/Traits/InputOutputAwareTrait.php b/src/Traits/InputOutputAwareTrait.php index 764a991e..b7540333 100644 --- a/src/Traits/InputOutputAwareTrait.php +++ b/src/Traits/InputOutputAwareTrait.php @@ -34,11 +34,19 @@ trait InputOutputAwareTrait /** * @return string */ - public function getScriptName(): string + public function getScript(): string { return $this->input->getScript(); } + /** + * @return string + */ + public function getScriptName(): string + { + return $this->input->getScriptName(); + } + /** * @return string */ diff --git a/src/Traits/UserInteractAwareTrait.php b/src/Traits/UserInteractAwareTrait.php index 03484115..0d332664 100644 --- a/src/Traits/UserInteractAwareTrait.php +++ b/src/Traits/UserInteractAwareTrait.php @@ -32,7 +32,12 @@ trait UserInteractAwareTrait { /** - * @inheritdoc + * @param string $description + * @param string|array $options Option data + * @param string|int $default Default option + * @param bool $allowExit + * + * @return string * @see Interact::choice() */ public function select(string $description, $options, $default = null, $allowExit = true): string @@ -41,7 +46,12 @@ public function select(string $description, $options, $default = null, $allowExi } /** - * @inheritdoc + * @param string $description + * @param string|array $options Option data + * @param string|int $default Default option + * @param bool $allowExit + * + * @return string * @see Interact::choice() */ public function choice(string $description, $options, $default = null, $allowExit = true): string @@ -50,7 +60,10 @@ public function choice(string $description, $options, $default = null, $allowExi } /** - * @inheritdoc + * @param string $question + * @param bool $default + * + * @return bool * @see Interact::confirm() */ public function confirm(string $question, bool $default = true): bool @@ -59,7 +72,11 @@ public function confirm(string $question, bool $default = true): bool } /** - * @inheritdoc + * @param string $question + * @param string $default + * @param Closure|null $validator + * + * @return string|null * @see Interact::question() */ public function ask(string $question, string $default = '', Closure $validator = null): ?string @@ -67,13 +84,25 @@ public function ask(string $question, string $default = '', Closure $validator = return $this->question($question, $default, $validator); } + /** + * @param string $question + * @param string $default + * @param Closure|null $validator + * + * @return string|null + */ public function question(string $question, string $default = '', Closure $validator = null): ?string { return Interact::question($question, $default, $validator); } /** - * @inheritdoc + * @param string $question + * @param string $default + * @param Closure|null $validator + * @param int $times + * + * @return string|null * @see Interact::limitedAsk() */ public function limitedAsk(string $question, string $default = '', Closure $validator = null, $times = 3): ?string From 2a83ad418ccbd3ac6f0072f254c97d1194fcd194 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 29 May 2020 15:35:49 +0800 Subject: [PATCH 025/258] update some for color render --- .../Style/{Color.php => ColorCode.php} | 6 +- src/Component/Style/Style.php | 82 ++++++++----------- src/Traits/FormatOutputAwareTrait.php | 9 ++ 3 files changed, 45 insertions(+), 52 deletions(-) rename src/Component/Style/{Color.php => ColorCode.php} (98%) diff --git a/src/Component/Style/Color.php b/src/Component/Style/ColorCode.php similarity index 98% rename from src/Component/Style/Color.php rename to src/Component/Style/ColorCode.php index 22dd314f..10e53e68 100644 --- a/src/Component/Style/Color.php +++ b/src/Component/Style/ColorCode.php @@ -21,7 +21,7 @@ * * @package Inhere\Console\Component\Style */ -class Color +class ColorCode { /** Foreground base value */ public const FG_BASE = 30; @@ -108,10 +108,10 @@ class Color * @param array $options * @param bool $extra * - * @return Color + * @return ColorCode * @throws InvalidArgumentException */ - public static function make($fg = '', $bg = '', array $options = [], bool $extra = false): Color + public static function make($fg = '', $bg = '', array $options = [], bool $extra = false): ColorCode { return new self($fg, $bg, $options, $extra); } diff --git a/src/Component/Style/Style.php b/src/Component/Style/Style.php index 97620ad1..69dc587b 100644 --- a/src/Component/Style/Style.php +++ b/src/Component/Style/Style.php @@ -12,6 +12,7 @@ use InvalidArgumentException; use Toolkit\Cli\Cli; +use Toolkit\Cli\Color; use Toolkit\Cli\ColorTag; use function array_key_exists; use function array_keys; @@ -20,7 +21,6 @@ use function is_array; use function is_object; use function sprintf; -use function str_replace; use function strpos; /** @@ -41,31 +41,31 @@ class Style /** * there are some default style tags */ - public const NORMAL = 'normal'; + public const NORMAL = 'normal'; - public const FAINTLY = 'faintly'; + public const FAINTLY = 'faintly'; - public const BOLD = 'bold'; + public const BOLD = 'bold'; - public const NOTICE = 'notice'; + public const NOTICE = 'notice'; - public const PRIMARY = 'primary'; + public const PRIMARY = 'primary'; - public const SUCCESS = 'success'; + public const SUCCESS = 'success'; - public const INFO = 'info'; + public const INFO = 'info'; - public const NOTE = 'note'; + public const NOTE = 'note'; - public const WARNING = 'warning'; + public const WARNING = 'warning'; - public const COMMENT = 'comment'; + public const COMMENT = 'comment'; public const QUESTION = 'question'; - public const DANGER = 'danger'; + public const DANGER = 'danger'; - public const ERROR = 'error'; + public const ERROR = 'error'; /** * Regex to match tags @@ -89,7 +89,7 @@ class Style /** * Array of Color objects * - * @var Color[] + * @var ColorCode[] */ private $styles = []; @@ -223,35 +223,18 @@ public function format(string $text) $key = $matches[1][$i]; if (array_key_exists($key, $this->styles)) { - $text = $this->replaceColor($text, $key, $matches[2][$i], (string)$this->styles[$key]); - - /** Custom style format @see Color::makeByString() */ + $text = ColorTag::replaceColor($text, $key, $matches[2][$i], (string)$this->styles[$key]); + } elseif (isset(Color::STYLES[$key])) { + $text = ColorTag::replaceColor($text, $key, $matches[2][$i], Color::STYLES[$key]); + /** Custom style format @see ColorCode::fromString() */ } elseif (strpos($key, '=')) { - $text = $this->replaceColor($text, $key, $matches[2][$i], (string)Color::makeByString($key)); + $text = ColorTag::replaceColor($text, $key, $matches[2][$i], (string)ColorCode::makeByString($key)); } } return $text; } - /** - * Replace color tags in a string. - * - * @param string $text - * @param string $tag The matched tag. - * @param string $match The match. - * @param string $style The color style to apply. - * - * @return string - */ - protected function replaceColor($text, $tag, $match, $style): string - { - $replace = self::$noColor ? $match : sprintf("\033[%sm%s\033[0m", $style, $match); - - return str_replace("<$tag>$match", $replace, $text); - // return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); - } - /** * Strip color tags from a string. * @@ -271,12 +254,12 @@ public static function stripColor(string $string) /** * Add a style. * - * @param string $name - * @param string|Color|array $fg 前景色|Color对象|也可以是style配置数组(@see self::addByArray()) - * 当它为Color对象或配置数组时,后面两个参数无效 - * @param string $bg 背景色 - * @param array $options 其它选项 - * @param bool $extra + * @param string $name + * @param string|ColorCode|array $fg 前景色|Color对象|也可以是style配置数组(@see self::addByArray()) + * 当它为Color对象或配置数组时,后面两个参数无效 + * @param string $bg 背景色 + * @param array $options 其它选项 + * @param bool $extra * * @return $this */ @@ -286,10 +269,10 @@ public function add(string $name, $fg = '', $bg = '', array $options = [], bool return $this->addByArray($name, $fg); } - if (is_object($fg) && $fg instanceof Color) { + if (is_object($fg) && $fg instanceof ColorCode) { $this->styles[$name] = $fg; } else { - $this->styles[$name] = Color::make($fg, $bg, $options, $extra); + $this->styles[$name] = ColorCode::make($fg, $bg, $options, $extra); } return $this; @@ -320,9 +303,10 @@ public function addByArray(string $name, array $styleConfig): self ]; $config = array_merge($style, $styleConfig); + // expand [$fg, $bg, $extra, $options] = array_values($config); - $this->styles[$name] = Color::make($fg, $bg, $options, (bool)$extra); + $this->styles[$name] = ColorCode::make($fg, $bg, $options, (bool)$extra); return $this; } @@ -354,9 +338,9 @@ public function getStyles(): array /** * @param $name * - * @return Color|null + * @return ColorCode|null */ - public function getStyle($name): ?Color + public function getStyle($name): ?ColorCode { if (!isset($this->styles[$name])) { return null; @@ -397,7 +381,7 @@ public static function wrap(string $text, string $tag): string */ public static function isNoColor(): bool { - return (bool)self::$noColor; + return Color::isNoColor(); } /** @@ -407,6 +391,6 @@ public static function isNoColor(): bool */ public static function setNoColor($noColor = true): void { - self::$noColor = (bool)$noColor; + Color::setNoColor($noColor); } } diff --git a/src/Traits/FormatOutputAwareTrait.php b/src/Traits/FormatOutputAwareTrait.php index 6db9a0fc..de0a2f72 100644 --- a/src/Traits/FormatOutputAwareTrait.php +++ b/src/Traits/FormatOutputAwareTrait.php @@ -95,6 +95,15 @@ public function writeln($text, $quit = false, array $opts = []): int return Console::writeln($text, $quit, $opts); } + /** + * @param string $format + * @param mixed ...$args + */ + public function printf(string $format, ...$args): void + { + Console::printf($format, ...$args); + } + /** * @param string|mixed $text * @param bool $quit From 1533e8da5db7d39b15207b61cc593a8389b6e939 Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 30 May 2020 10:05:48 +0800 Subject: [PATCH 026/258] update some for event --- src/Application.php | 13 +++++++++---- src/ConsoleEvent.php | 14 ++++++++++++++ src/Traits/FormatOutputAwareTrait.php | 23 ++++++++++++++++------- src/Traits/InputOutputAwareTrait.php | 2 +- src/Traits/SimpleEventTrait.php | 7 +++++-- 5 files changed, 45 insertions(+), 14 deletions(-) create mode 100644 src/ConsoleEvent.php diff --git a/src/Application.php b/src/Application.php index fc136bad..33d3818b 100644 --- a/src/Application.php +++ b/src/Application.php @@ -259,20 +259,25 @@ protected function getFileFilter(): callable */ public function dispatch(string $name, bool $standAlone = false) { - $this->logf(Console::VERB_DEBUG, 'begin dispatch command - %s', $name); + $this->logf(Console::VERB_DEBUG, 'begin dispatch command: %s', $name); // match handler by input name $info = $this->getRouter()->match($name); // command not found - if (!$info && true !== $this->fire(self::ON_NOT_FOUND, $this)) { - $this->output->liteError("The command '{$name}' is not exists in the console application!"); + if (!$info) { + if (true === $this->fire(self::ON_NOT_FOUND, $name, $this)) { + $this->logf(Console::VERB_DEBUG, 'not found handle by user, command: %s', $name); + return 0; + } + + $this->output->error("The command '{$name}' is not exists!"); $commands = $this->getRouter()->getAllNames(); // find similar command names by similar_text() if ($similar = Helper::findSimilar($name, $commands)) { - $this->write(sprintf("\nMaybe what you mean is:\n %s", implode(', ', $similar))); + $this->output->printf("\nMaybe what you mean is:\n %s", implode(', ', $similar)); } else { $this->showCommandList(); } diff --git a/src/ConsoleEvent.php b/src/ConsoleEvent.php new file mode 100644 index 00000000..546ec1db --- /dev/null +++ b/src/ConsoleEvent.php @@ -0,0 +1,14 @@ +off($event); } - return true; + return (bool)$return; } /** From e4cee963c66784550160fddc5d522aac35418491 Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 30 May 2020 10:20:21 +0800 Subject: [PATCH 027/258] update some for char --- src/Component/Symbol/Char.php | 56 ++++++++++++++++++++++++++-------- src/Component/Symbol/Emoji.php | 1 - 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/src/Component/Symbol/Char.php b/src/Component/Symbol/Char.php index 1f2dc4c8..68c30a60 100644 --- a/src/Component/Symbol/Char.php +++ b/src/Component/Symbol/Char.php @@ -9,7 +9,6 @@ namespace Inhere\Console\Component\Symbol; use ReflectionClass; -use ReflectionException; /** * Class FontSymbol @@ -20,41 +19,73 @@ final class Char { public const OK = '✔'; + public const OK1 = '✓'; - public const NO = '✘'; + public const NO = '✘︎'; + + public const NO1 = '︎✕'; + + public const NO2 = '︎✖︎'; + + public const NO3 = '︎✗'; public const PEN = '✎'; + // ☑︎☐☒ + + public const FLAG = '⚑'; + public const FLAG1 = '⚐'; + public const HEART = '❤'; - public const SMILE = '☺'; + // ☺︎☹︎☻ + public const SMILE = '☺'; + public const SMILE1 = '☹︎'; + public const SMILE2 = '☻︎'; public const FLOWER = '✿'; - public const MUSIC = '♬'; + public const MUSIC = '♬'; - public const UP = ''; + public const UP = ''; - public const DOWN = ''; + public const DOWN = ''; - public const LEFT = ''; + public const LEFT = ''; - public const RIGHT = ''; + public const RIGHT = ''; public const SEARCH = ''; - public const MALE = '♂'; + public const MALE = '♂'; public const FEMALE = '♀'; - public const SUN = '☀'; + public const SUN = '☀'; + + // ✪☆✯★✩ + public const STAR = '★'; + + public const STAR1 = '✪'; - public const STAR = '★'; + public const STAR2 = '✩'; - public const SNOW = '❈'; + public const SNOW = '❈'; public const CLOUD = '☁'; + public const POINT = '●'; + + public const POINT1 = '•'; + + public const POINT2 = '○'; + + public const POINT3 = '◉'; + + public const POINT4 = '◎'; + + public const POINT5 = '⦿'; + /** * @var array * [ @@ -66,7 +97,6 @@ final class Char /** * @return array - * @throws ReflectionException */ public static function getConstants(): array { diff --git a/src/Component/Symbol/Emoji.php b/src/Component/Symbol/Emoji.php index a89e3827..fc67f277 100644 --- a/src/Component/Symbol/Emoji.php +++ b/src/Component/Symbol/Emoji.php @@ -135,7 +135,6 @@ final class Emoji /** * @return array - * @throws ReflectionException */ public static function getConstants(): array { From a9ee7ae390bb555ed2a0e804ace682c0693f8fab Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 30 May 2020 10:58:12 +0800 Subject: [PATCH 028/258] add some new char --- src/Application.php | 4 +++- src/Component/Symbol/Emoji.php | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Application.php b/src/Application.php index 33d3818b..de098b17 100644 --- a/src/Application.php +++ b/src/Application.php @@ -279,7 +279,9 @@ public function dispatch(string $name, bool $standAlone = false) if ($similar = Helper::findSimilar($name, $commands)) { $this->output->printf("\nMaybe what you mean is:\n %s", implode(', ', $similar)); } else { - $this->showCommandList(); + // $this->showCommandList(); + $scriptName = $this->getScriptName(); + $this->output->colored("\nPlease use '$scriptName --help' for see all available commands"); } return 2; diff --git a/src/Component/Symbol/Emoji.php b/src/Component/Symbol/Emoji.php index fc67f277..daa82dd6 100644 --- a/src/Component/Symbol/Emoji.php +++ b/src/Component/Symbol/Emoji.php @@ -9,7 +9,6 @@ namespace Inhere\Console\Component\Symbol; use ReflectionClass; -use ReflectionException; /** * Class Emoji From 34aaa6400488b0c22297b6c4df9d14b429a5a07e Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 30 May 2020 17:28:58 +0800 Subject: [PATCH 029/258] upsome for tips error --- src/Component/ErrorHandler.php | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Component/ErrorHandler.php b/src/Component/ErrorHandler.php index 98fa3017..7c62c4c5 100644 --- a/src/Component/ErrorHandler.php +++ b/src/Component/ErrorHandler.php @@ -10,6 +10,7 @@ use Inhere\Console\AbstractApplication; use Inhere\Console\Contract\ErrorHandlerInterface; +use Inhere\Console\Exception\PromptException; use Throwable; use Toolkit\Cli\Highlighter; use function file_get_contents; @@ -29,11 +30,16 @@ class ErrorHandler implements ErrorHandlerInterface */ public function handle(Throwable $e, AbstractApplication $app): void { + if ($e instanceof PromptException) { + $app->getOutput()->error($e->getMessage()); + return; + } + $class = get_class($e); // open debug, throw exception if ($app->isDebug()) { - $tpl = << Error %s At File %s line %d @@ -41,8 +47,9 @@ public function handle(Throwable $e, AbstractApplication $app): void Code View:\n\n%s Code Trace:\n\n%s\n ERR; - $line = $e->getLine(); - $file = $e->getFile(); + $line = $e->getLine(); + $file = $e->getFile(); + $snippet = Highlighter::create()->highlightSnippet(file_get_contents($file), $line, 3, 3); $message = sprintf( $tpl, // $e->getCode(), @@ -58,10 +65,11 @@ public function handle(Throwable $e, AbstractApplication $app): void } $app->write($message, false); - } else { - // simple output - $app->getOutput()->error('An error occurred! MESSAGE: ' . $e->getMessage()); - $app->write("\nYou can use '--debug 4' to see error details."); + return; } + + // simple output + $app->getOutput()->error('An error occurred! MESSAGE: ' . $e->getMessage()); + $app->write("\nYou can use '--debug 4' to see error details."); } } From 977a44649834cd2de66f7d85d38320cef0b53a89 Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 30 May 2020 19:44:00 +0800 Subject: [PATCH 030/258] update some logic --- src/AbstractApplication.php | 3 ++- src/AbstractHandler.php | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index a87461c9..0fe7a895 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -584,8 +584,9 @@ public function isProfile(): bool public function isInteractive(): bool { $key = 'no-interactive'; + $val = (bool)$this->input->getOpt($key, $this->getParam($key, true)); - return (bool)$this->input->getOpt($key, $this->getParam($key, true)); + return $val === false; } /** diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 14473988..405d6730 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -739,7 +739,21 @@ public static function setAnnotationTags(array $annotationTags, $replace = false } /** - * get current debug level value + * @return bool + */ + public function isInteractive(): bool + { + if ($this->app) { + return $this->app->isInteractive(); + } + + $value = (bool)$this->input->getLongOpt('no-interactive', false); + + return $value === false; + } + + /** + * Get current debug level value * * @return int */ From 2e406bed23f35e25a99eff8d778f77688807ff6b Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 31 May 2020 20:00:50 +0800 Subject: [PATCH 031/258] update some for color --- src/Component/Style/ColorCode.php | 266 ------------------------------ src/Component/Style/Style.php | 6 +- src/ConsoleEvent.php | 12 +- 3 files changed, 13 insertions(+), 271 deletions(-) delete mode 100644 src/Component/Style/ColorCode.php diff --git a/src/Component/Style/ColorCode.php b/src/Component/Style/ColorCode.php deleted file mode 100644 index 10e53e68..00000000 --- a/src/Component/Style/ColorCode.php +++ /dev/null @@ -1,266 +0,0 @@ - 0, - 'red' => 1, - 'green' => 2, - 'yellow' => 3, - 'blue' => 4, - 'magenta' => 5, // 洋红色 洋红 品红色 - 'cyan' => 6, // 青色 青绿色 蓝绿色 - 'white' => 7, - 'normal' => 9, - ]; - - /** @var array Known style option */ - private static $knownOptions = [ - 'bold' => 1, // 22 加粗 - 'fuzzy' => 2, // 模糊(不是所有的终端仿真器都支持) - 'italic' => 3, // 斜体(不是所有的终端仿真器都支持) - 'underscore' => 4, // 24 下划线 - 'blink' => 5, // 25 闪烁 - 'reverse' => 7, // 27 颠倒的 交换背景色与前景色 - 'concealed' => 8, // 28 隐匿的 - ]; - - /** @var int Foreground color */ - private $fgColor = 0; - - /** @var int Background color */ - private $bgColor = 0; - - /** @var array Array of style options */ - private $options = []; - - /** - * @param string $fg - * @param string $bg - * @param array $options - * @param bool $extra - * - * @return ColorCode - * @throws InvalidArgumentException - */ - public static function make($fg = '', $bg = '', array $options = [], bool $extra = false): ColorCode - { - return new self($fg, $bg, $options, $extra); - } - - /** - * Create a color style from a parameter string. - * - * @param string $string e.g 'fg=white;bg=black;options=bold,underscore;extra=1' - * - * @return static - * @throws InvalidArgumentException - * @throws RuntimeException - */ - public static function makeByString($string) - { - $fg = $bg = ''; - $extra = false; - $options = []; - $parts = explode(';', str_replace(' ', '', $string)); - - foreach ($parts as $part) { - $subParts = explode('=', $part); - - if (count($subParts) < 2) { - continue; - } - - switch ($subParts[0]) { - case 'fg': - $fg = $subParts[1]; - break; - case 'bg': - $bg = $subParts[1]; - break; - case 'extra': - $extra = (bool)$subParts[1]; - break; - case 'options': - $options = explode(',', $subParts[1]); - break; - default: - throw new RuntimeException('Invalid option'); - break; - } - } - - return new self($fg, $bg, $options, $extra); - } - - /** - * Constructor - * - * @param string $fg Foreground color. e.g 'white' - * @param string $bg Background color. e.g 'black' - * @param array $options Style options. e.g ['bold', 'underscore'] - * @param bool $extra - * - * @throws InvalidArgumentException - */ - public function __construct($fg = '', $bg = '', array $options = [], bool $extra = false) - { - if ($fg) { - if (false === array_key_exists($fg, static::$knownColors)) { - throw new InvalidArgumentException(sprintf( - 'Invalid foreground color "%1$s" [%2$s]', - $fg, - implode(', ', $this->getKnownColors()) - )); - } - - $this->fgColor = ($extra ? self::FG_EXTRA : self::FG_BASE) + static::$knownColors[$fg]; - } - - if ($bg) { - if (false === array_key_exists($bg, static::$knownColors)) { - throw new InvalidArgumentException(sprintf( - 'Invalid background color "%1$s" [%2$s]', - $bg, - implode(', ', $this->getKnownColors()) - )); - } - - $this->bgColor = ($extra ? self::BG_EXTRA : self::BG_BASE) + static::$knownColors[$bg]; - } - - foreach ($options as $option) { - if (false === array_key_exists($option, static::$knownOptions)) { - throw new InvalidArgumentException(sprintf( - 'Invalid option "%1$s" [%2$s]', - $option, - implode(', ', $this->getKnownOptions()) - )); - } - - $this->options[] = $option; - } - } - - /** - * Convert to a string. - */ - public function __toString() - { - return $this->toStyle(); - } - - /** - * Get the translated color code. - */ - public function toStyle(): string - { - $values = []; - - if ($this->fgColor) { - $values[] = $this->fgColor; - } - - if ($this->bgColor) { - $values[] = $this->bgColor; - } - - foreach ($this->options as $option) { - $values[] = static::$knownOptions[$option]; - } - - return implode(';', $values); - } - - /** - * Get the known colors. - * - * @param bool $onlyName - * - * @return array - */ - public function getKnownColors(bool $onlyName = true): array - { - return $onlyName ? array_keys(static::$knownColors) : static::$knownColors; - } - - /** - * Get the known options. - * - * @param bool $onlyName - * - * @return array - */ - public function getKnownOptions(bool $onlyName = true): array - { - return $onlyName ? array_keys(static::$knownOptions) : static::$knownOptions; - } -} diff --git a/src/Component/Style/Style.php b/src/Component/Style/Style.php index 69dc587b..9cc441a5 100644 --- a/src/Component/Style/Style.php +++ b/src/Component/Style/Style.php @@ -11,8 +11,8 @@ namespace Inhere\Console\Component\Style; use InvalidArgumentException; -use Toolkit\Cli\Cli; use Toolkit\Cli\Color; +use Toolkit\Cli\ColorCode; use Toolkit\Cli\ColorTag; use function array_key_exists; use function array_keys; @@ -211,7 +211,7 @@ public function format(string $text) } // if don't support output color text, clear color tag. - if (!Cli::isSupportColor() || self::isNoColor()) { + if (!Color::isShouldRenderColor()) { return self::stripColor($text); } @@ -228,7 +228,7 @@ public function format(string $text) $text = ColorTag::replaceColor($text, $key, $matches[2][$i], Color::STYLES[$key]); /** Custom style format @see ColorCode::fromString() */ } elseif (strpos($key, '=')) { - $text = ColorTag::replaceColor($text, $key, $matches[2][$i], (string)ColorCode::makeByString($key)); + $text = ColorTag::replaceColor($text, $key, $matches[2][$i], (string)ColorCode::fromString($key)); } } diff --git a/src/ConsoleEvent.php b/src/ConsoleEvent.php index 546ec1db..137706dd 100644 --- a/src/ConsoleEvent.php +++ b/src/ConsoleEvent.php @@ -9,6 +9,14 @@ */ final class ConsoleEvent { - public const STOP = 1; - public const GOON = 1; + // event name list + public const ON_BEFORE_RUN = 'app.beforeRun'; + + public const ON_AFTER_RUN = 'app.afterRun'; + + public const ON_RUN_ERROR = 'app.runError'; + + public const ON_STOP_RUN = 'app.stopRun'; + + public const ON_NOT_FOUND = 'app.notFound'; } From 4294553a4633951b3af6eb6b8ce48c563e425e6f Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 1 Jun 2020 14:11:33 +0800 Subject: [PATCH 032/258] update some logic --- src/AbstractApplication.php | 33 ++++++++++---------------- src/GlobalOption.php | 34 +++++++++++++++++++++++++++ src/Traits/FormatOutputAwareTrait.php | 4 +++- src/Traits/UserInteractAwareTrait.php | 5 ++-- src/Util/Interact.php | 13 ++++++++++ 5 files changed, 65 insertions(+), 24 deletions(-) create mode 100644 src/GlobalOption.php diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 0fe7a895..bbb5e433 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -53,20 +53,6 @@ abstract class AbstractApplication implements ApplicationInterface 'list' => 'List all group and alone commands', ]; - /** - * @var int[] - */ - protected static $globalOptionNames = [ - 'debug' => 1, - 'profile' => 1, - 'no-color' => 1, - 'h' => 1, - 'help' => 1, - 'V' => 1, - 'version' => 1, - 'no-interactive' => 1, - ]; - /** @var array */ protected static $globalOptions = [ '--debug' => 'Setting the application runtime debug level(0 - 4)', @@ -96,8 +82,9 @@ abstract class AbstractApplication implements ApplicationInterface 'updateAt' => '2019.01.01', 'rootPath' => '', 'strictMode' => false, - 'interactive' => true, 'hideRootPath' => true, + // global options + 'no-interactive' => true, // 'timeZone' => 'Asia/Shanghai', // 'env' => 'prod', // dev test prod @@ -169,7 +156,7 @@ protected function init(): void */ public static function isGlobalOption(string $name): bool { - return isset(self::$globalOptionNames[$name]); + return isset(GlobalOption::KEY_MAP[$name]); } /** @@ -563,7 +550,9 @@ public function isStrictMode(): bool */ public function getVerbLevel(): int { - return (int)$this->input->getLongOpt('debug', (int)$this->config['debug']); + $key = GlobalOption::DEBUG; + + return (int)$this->input->getLongOpt($key, (int)$this->config[$key]); } /** @@ -573,7 +562,10 @@ public function getVerbLevel(): int */ public function isProfile(): bool { - return (bool)$this->input->getOpt('profile', $this->getParam('profile', false)); + $key = GlobalOption::PROFILE; + $def = (bool)$this->getParam($key, false); + + return $this->input->getBoolOpt($key, $def); } /** @@ -583,8 +575,9 @@ public function isProfile(): bool */ public function isInteractive(): bool { - $key = 'no-interactive'; - $val = (bool)$this->input->getOpt($key, $this->getParam($key, true)); + $key = GlobalOption::NO_INTERACTIVE; + $def = (bool)$this->getParam($key, true); + $val = $this->input->getBoolOpt($key, $def); return $val === false; } diff --git a/src/GlobalOption.php b/src/GlobalOption.php new file mode 100644 index 00000000..218c8dfe --- /dev/null +++ b/src/GlobalOption.php @@ -0,0 +1,34 @@ + 1, + 'profile' => 1, + 'no-color' => 1, + 'h' => 1, + 'help' => 1, + 'V' => 1, + 'version' => 1, + 'no-interactive' => 1, + ]; +} diff --git a/src/Traits/FormatOutputAwareTrait.php b/src/Traits/FormatOutputAwareTrait.php index f77c3ed4..a755c1f4 100644 --- a/src/Traits/FormatOutputAwareTrait.php +++ b/src/Traits/FormatOutputAwareTrait.php @@ -63,7 +63,8 @@ * * @method Generator counterTxt($msg = 'Pending ', $ended = false) * - * @method confirm(string $question, bool $default = true, bool $nl = true): bool + * @method confirm(string $question, bool $default = true): bool + * @method unConfirm(string $question, bool $default = true): bool * @method select(string $description, $options, $default = null, bool $allowExit = true): string * @method checkbox(string $description, $options, $default = null, bool $allowExit = true): array * @method ask(string $question, string $default = '', Closure $validator = null): string @@ -299,6 +300,7 @@ public function __call($method, array $args = []) return Show::$method(...$args); } + // interact methods if (method_exists(Interact::class, $method)) { return Interact::$method(...$args); } diff --git a/src/Traits/UserInteractAwareTrait.php b/src/Traits/UserInteractAwareTrait.php index 0d332664..3530eeff 100644 --- a/src/Traits/UserInteractAwareTrait.php +++ b/src/Traits/UserInteractAwareTrait.php @@ -25,6 +25,8 @@ * @method array checkbox(string $description, $options, $default = null, $allowExit = true) * @method array multiSelect(string $description, $options, $default = null, $allowExit = true) * + * @method unConfirm(string $question, bool $default = true): bool + * * @method string askHiddenInput(string $prompt = 'Enter Password:') * @method string promptSilent(string $prompt = 'Enter Password:') * @method string askPassword(string $prompt = 'Enter Password:') @@ -52,7 +54,6 @@ public function select(string $description, $options, $default = null, $allowExi * @param bool $allowExit * * @return string - * @see Interact::choice() */ public function choice(string $description, $options, $default = null, $allowExit = true): string { @@ -64,7 +65,6 @@ public function choice(string $description, $options, $default = null, $allowExi * @param bool $default * * @return bool - * @see Interact::confirm() */ public function confirm(string $question, bool $default = true): bool { @@ -77,7 +77,6 @@ public function confirm(string $question, bool $default = true): bool * @param Closure|null $validator * * @return string|null - * @see Interact::question() */ public function ask(string $question, string $default = '', Closure $validator = null): ?string { diff --git a/src/Util/Interact.php b/src/Util/Interact.php index 2689a9b7..6102f0bb 100644 --- a/src/Util/Interact.php +++ b/src/Util/Interact.php @@ -165,6 +165,19 @@ public static function confirm(string $question, bool $default = true): bool return Confirm::ask($question, $default); } + /** + * Send a message request confirmation + * + * @param string $question The question message + * @param bool $default Default value + * + * @return bool + */ + public static function unConfirm(string $question, bool $default = true): bool + { + return false === Confirm::ask($question, $default); + } + /** * Usage: * From db4a1ca6eca5812613aae0b95653559f61235ddc Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 2 Jun 2020 10:12:50 +0800 Subject: [PATCH 033/258] up --- src/AbstractHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 405d6730..ad92a369 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -747,7 +747,7 @@ public function isInteractive(): bool return $this->app->isInteractive(); } - $value = (bool)$this->input->getLongOpt('no-interactive', false); + $value = $this->input->getBoolOpt(GlobalOption::NO_INTERACTIVE); return $value === false; } From cd2b998eb06b799055df7d9cf6480e1262fec340 Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 3 Jun 2020 21:07:37 +0800 Subject: [PATCH 034/258] upsome for alone run command --- src/AbstractHandler.php | 48 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index ad92a369..e2fdb492 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -93,18 +93,33 @@ abstract class AbstractHandler implements CommandHandlerInterface 'help' => true, ]; - /** @var Application */ + /** + * @var Application + */ protected $app; - /** @var InputDefinition|null */ + /** + * @var InputDefinition|null + */ private $definition; - /** @var string */ + /** + * @var string + */ private $processTitle = ''; - /** @var array */ + /** + * @var array + */ private $commentsVars; + /** + * Mark the command/controller is attached in application. + * + * @var bool + */ + private $attached = true; + /** * Whether enabled * @@ -545,8 +560,15 @@ protected function showHelp(): bool } } + $binName = $this->getScriptName(); + // build usage - $help['usage:'] = sprintf('%s %s %s', $this->getScriptName(), $this->getCommandName(), $help['usage:']); + if ($this->attached) { + $help['usage:'] = sprintf('%s %s %s', $binName, $this->getCommandName(), $help['usage:']); + } else { + $help['usage:'] = $binName . ' ' . $help['usage:']; + } + // align global options $help['global options:'] = FormatUtil::alignOptions(Application::getGlobalOptions()); @@ -820,6 +842,22 @@ public function setApp(AbstractApplication $app): void $this->app = $app; } + /** + * @return bool + */ + public function isAttached(): bool + { + return $this->attached; + } + + /** + * @param bool $attached + */ + public function setAttached(bool $attached): void + { + $this->attached = $attached; + } + /** * @return string */ From 75646b21279faba61087045bcee173bed0d836a3 Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 4 Jun 2020 18:03:42 +0800 Subject: [PATCH 035/258] update some lgoic --- .php_cs | 36 +++++--- src/AbstractApplication.php | 4 +- src/AbstractHandler.php | 3 +- src/Contract/InputFlagInterface.php | 33 ++++++++ src/Contract/InputInterface.php | 80 ++++++++++++++++++ src/Contract/OutputInterface.php | 28 +++++++ src/IO/AbstractInput.php | 7 +- src/IO/Input/InputArgument.php | 28 ++++++- src/IO/Input/{InputItem.php => InputFlag.php} | 41 +++++++-- src/IO/Input/InputOption.php | 83 +++++++++++++++++-- src/IO/InputDefinition.php | 36 ++++---- src/IO/InputInterface.php | 65 +-------------- src/IO/Output.php | 5 +- src/IO/Output/BufferOutput.php | 22 ++++- src/IO/OutputInterface.php | 13 +-- src/Traits/InputOutputAwareTrait.php | 4 +- src/Util/Helper.php | 11 +++ src/Util/ProgressBar.php | 2 +- test/ApplicationTest.php | 2 +- test/IO/InputDefinitionTest.php | 24 ++++++ 20 files changed, 400 insertions(+), 127 deletions(-) create mode 100644 src/Contract/InputFlagInterface.php create mode 100644 src/Contract/InputInterface.php create mode 100644 src/Contract/OutputInterface.php rename src/IO/Input/{InputItem.php => InputFlag.php} (71%) create mode 100644 test/IO/InputDefinitionTest.php diff --git a/.php_cs b/.php_cs index ad247932..62707c2f 100644 --- a/.php_cs +++ b/.php_cs @@ -1,25 +1,39 @@ setRiskyAllowed(true)->setRules([ +$rules = [ '@PSR2' => true, - // 'header_comment' => [ - // 'comment_type' => 'PHPDoc', - // 'header' => $header, - // 'separate' => 'none' - // ], 'array_syntax' => [ - 'syntax' => 'short' + 'syntax' => 'short' ], - 'single_quote' => true, 'class_attributes_separation' => true, + 'declare_strict_types' => true, + 'global_namespace_import' => true, + 'header_comment' => [ + 'comment_type' => 'PHPDoc', + 'header' => $header, + 'separate' => 'bottom' + ], 'no_unused_imports' => true, + 'single_quote' => true, 'standardize_not_equals' => true, - 'declare_strict_types' => true, - ])->setFinder(PhpCsFixer\Finder::create() +]; + +$finder = PhpCsFixer\Finder::create() // ->exclude('test') - ->exclude('docs')->exclude('vendor')->in(__DIR__))->setUsingCache(false); + ->exclude('docs') + ->exclude('vendor') + ->in(__DIR__); + +return PhpCsFixer\Config::create() + ->setRiskyAllowed(true) + ->setRules($rules) + ->setFinder($finder) + ->setUsingCache(false); diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index bbb5e433..b47f57c0 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -13,10 +13,10 @@ use Inhere\Console\Component\Style\Style; use Inhere\Console\Contract\ApplicationInterface; use Inhere\Console\Contract\ErrorHandlerInterface; +use Inhere\Console\Contract\InputInterface; use Inhere\Console\IO\Input; -use Inhere\Console\IO\InputInterface; use Inhere\Console\IO\Output; -use Inhere\Console\IO\OutputInterface; +use Inhere\Console\Contract\OutputInterface; use Inhere\Console\Traits\ApplicationHelpTrait; use Inhere\Console\Traits\InputOutputAwareTrait; use Inhere\Console\Traits\SimpleEventTrait; diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index e2fdb492..ecb36a7b 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -400,7 +400,6 @@ public function validateInput(): bool } $in->setArgs($args); - $this->checkNotExistsOptions($def); // check options @@ -413,6 +412,8 @@ public function validateInput(): bool $shortNames = $conf['shortcut'] ? explode('|', $conf['shortcut']) : []; if ($srt = $in->findOneShortOpts($shortNames)) { $opts[$name] = $in->sOpt($srt); + } elseif ($conf['default']) { + } elseif ($conf['required']) { $missingOpts[] = "--{$name}" . ($srt ? "|-{$srt}" : ''); } diff --git a/src/Contract/InputFlagInterface.php b/src/Contract/InputFlagInterface.php new file mode 100644 index 00000000..8c8e49d3 --- /dev/null +++ b/src/Contract/InputFlagInterface.php @@ -0,0 +1,33 @@ +args = $newArgs; } + public function hasMode(): bool + { + + } + /*********************************************************************************** * arguments (eg: arg0 name=john city=chengdu) ***********************************************************************************/ diff --git a/src/IO/Input/InputArgument.php b/src/IO/Input/InputArgument.php index 4ff86f55..00316ae7 100644 --- a/src/IO/Input/InputArgument.php +++ b/src/IO/Input/InputArgument.php @@ -8,13 +8,15 @@ namespace Inhere\Console\IO\Input; +use Inhere\Console\IO\Input; + /** * Class InputArgument * - definition a input argument * * @package Inhere\Console\IO\Input */ -class InputArgument extends InputItem +class InputArgument extends InputFlag { /** * The argument position @@ -23,6 +25,30 @@ class InputArgument extends InputItem */ private $index = 0; + /** + * @return bool + */ + public function isArray(): bool + { + return $this->hasMode(Input::ARG_IS_ARRAY); + } + + /** + * @return bool + */ + public function isOptional(): bool + { + return $this->hasMode(Input::ARG_OPTIONAL); + } + + /** + * @return bool + */ + public function isRequired(): bool + { + return $this->hasMode(Input::ARG_REQUIRED); + } + /** * @return int */ diff --git a/src/IO/Input/InputItem.php b/src/IO/Input/InputFlag.php similarity index 71% rename from src/IO/Input/InputItem.php rename to src/IO/Input/InputFlag.php index cb7e67fa..0d322fb7 100644 --- a/src/IO/Input/InputItem.php +++ b/src/IO/Input/InputFlag.php @@ -8,15 +8,16 @@ namespace Inhere\Console\IO\Input; +use Inhere\Console\Contract\InputFlagInterface; use Inhere\Console\IO\Input; /** - * Class InputItem + * Class InputFlag * - definition a input item(option|argument) * * @package Inhere\Console\IO\Input */ -class InputItem +abstract class InputFlag implements InputFlagInterface { /** * @var string @@ -34,11 +35,11 @@ class InputItem private $mode; /** - * The argument data type. (eg: 'string', 'array', 'mixed') + * The argument data type. (eg: 'int', 'bool', 'string', 'array', 'mixed') * * @var string */ - private $type; + private $type = ''; /** * The default value @@ -79,14 +80,25 @@ public function __construct(string $name, int $mode = 0, string $description = ' $this->setDescription($description); } + /****************************************************************** + * mode value + *****************************************************************/ + /** + * @param int $mode + * * @return bool */ - public function isArray(): bool + public function hasMode(int $mode): bool { - return $this->mode === Input::ARG_IS_ARRAY; + return ($this->mode & $mode) > 0; } + + /****************************************************************** + * + *****************************************************************/ + /** * @return string */ @@ -166,4 +178,21 @@ public function setDescription(string $description): void { $this->description = $description; } + + /** + * @return array + */ + public function toArray(): array + { + return [ + 'name' => $this->name, + 'mode' => $this->mode, + 'type' => $this->type, + 'default' => $this->default, + 'isArray' => $this->isArray(), + 'isOptional' => $this->isOptional(), + 'isRequired' => $this->isRequired(), + 'description' => $this->description, + ]; + } } diff --git a/src/IO/Input/InputOption.php b/src/IO/Input/InputOption.php index 109345c4..236d7a65 100644 --- a/src/IO/Input/InputOption.php +++ b/src/IO/Input/InputOption.php @@ -8,25 +8,69 @@ namespace Inhere\Console\IO\Input; +use Inhere\Console\IO\Input; +use function implode; + /** * Class InputOption * - definition a input option * * @package Inhere\Console\IO\Input */ -class InputOption extends InputItem +class InputOption extends InputFlag { /** * alias name * * @var string */ - private $alias; + private $alias = ''; + + /** + * Shortcuts of the option. eg: ['a', 'b'] + * + * @var array + */ + private $shortcuts = []; + + /** + * eg: 'a|b' + * + * @var string + */ + private $shortcut = ''; + + /** + * @return bool + */ + public function isArray(): bool + { + return $this->hasMode(Input::OPT_IS_ARRAY); + } + + /** + * @return bool + */ + public function isOptional(): bool + { + return $this->hasMode(Input::OPT_OPTIONAL); + } + + /** + * @return bool + */ + public function isRequired(): bool + { + return $this->hasMode(Input::OPT_REQUIRED); + } /** - * @var string|array + * @return bool */ - private $shortcut; + public function isBoolean(): bool + { + return $this->hasMode(Input::OPT_BOOLEAN); + } /** * @return string @@ -45,18 +89,39 @@ public function setAlias(string $alias): void } /** - * @return array + * @return string */ - public function getShortcut() + public function getShortcut(): string { return $this->shortcut; } /** - * @param array|string $shortcut + * @param string $shortcut */ - public function setShortcut($shortcut): void + public function setShortcutsByString(string $shortcut): void { - $this->shortcut = (array)$shortcut; + $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); + $shortcuts = array_filter($shortcuts); + + $this->setShortcuts($shortcuts); } + + /** + * @return array + */ + public function getShortcuts(): array + { + return $this->shortcuts; + } + + /** + * @param array $shortcuts + */ + public function setShortcuts(array $shortcuts): void + { + $this->shortcuts = $shortcuts; + $this->shortcut = implode('|', $shortcuts); + } + } diff --git a/src/IO/InputDefinition.php b/src/IO/InputDefinition.php index bdb288fe..a71b520a 100644 --- a/src/IO/InputDefinition.php +++ b/src/IO/InputDefinition.php @@ -8,6 +8,7 @@ namespace Inhere\Console\IO; +use Inhere\Console\Util\Helper; use InvalidArgumentException; use LogicException; use function array_filter; @@ -51,7 +52,7 @@ class InputDefinition /** * @var array[] */ - private $arguments; + private $arguments = []; /** * @var int @@ -210,8 +211,11 @@ public function addArgument(string $name, int $mode = 0, string $description = ' $this->hasOptionalArgument = true; } + $index = count($this->arguments); + $this->arguments[$name] = [ 'mode' => $mode, + 'index' => $index, 'required' => $required, 'isArray' => $isArray, 'description' => $description, @@ -361,27 +365,30 @@ public function addOption( throw new InvalidArgumentException('An option name cannot be empty.'); } + if (isset($this->options[$name])) { + throw new LogicException(sprintf('An option named "%s" already exists.', $name)); + } + if ($mode <= 0) { $mode = Input::OPT_BOOLEAN; } elseif (!is_int($mode) || $mode > 15 || $mode < 1) { throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); } - $isArray = $mode === Input::OPT_IS_ARRAY; + $isArray = ($mode & Input::OPT_IS_ARRAY) > 0; if ($isArray && !$this->optionIsAcceptValue($mode)) { throw new InvalidArgumentException('Impossible to have an option mode OPT_IS_ARRAY if the option does not accept a value.'); } - if (isset($this->options[$name])) { - throw new LogicException(sprintf('An option named "%s" already exists.', $name)); - } - // set default value - if (Input::OPT_BOOLEAN === (Input::OPT_BOOLEAN & $mode) && null !== $default) { - throw new LogicException('Cannot set a default value when using OPT_BOOLEAN mode.'); - } + $isBoolean = Input::OPT_BOOLEAN === (Input::OPT_BOOLEAN & $mode); + if ($isBoolean) { + if (null !== $default) { + throw new LogicException('Cannot set a default value when using OPT_BOOLEAN mode.'); + } - if ($isArray) { + $default = false; + } elseif ($isArray) { if (null === $default) { $default = []; } elseif (!is_array($default)) { @@ -411,9 +418,10 @@ public function addOption( $this->options[$name] = [ 'mode' => $mode, + 'isArray' => $isArray, 'shortcut' => $shortcut, // 允许数组 - 'required' => $mode === Input::OPT_REQUIRED, - 'optional' => $mode === Input::OPT_OPTIONAL, + 'required' => Helper::hasMode($mode, Input::OPT_REQUIRED), + 'optional' => Helper::hasMode($mode, Input::OPT_OPTIONAL), 'description' => $description, 'default' => $default, ]; @@ -609,7 +617,7 @@ public function argumentIsRequired($name): bool */ protected function argumentIsAcceptValue(int $mode): bool { - return $mode === Input::ARG_REQUIRED || $mode === Input::ARG_OPTIONAL; + return $mode & Input::ARG_REQUIRED || $mode & Input::ARG_OPTIONAL; } /** @@ -619,7 +627,7 @@ protected function argumentIsAcceptValue(int $mode): bool */ protected function optionIsAcceptValue(int $mode): bool { - return $mode === Input::OPT_REQUIRED || $mode === Input::OPT_OPTIONAL; + return $mode & Input::OPT_REQUIRED || $mode & Input::OPT_OPTIONAL; } /** diff --git a/src/IO/InputInterface.php b/src/IO/InputInterface.php index dc424400..75c8ee5a 100644 --- a/src/IO/InputInterface.php +++ b/src/IO/InputInterface.php @@ -12,69 +12,8 @@ * Class Input * * @package Inhere\Console\IO + * @deprecated please use \Inhere\Console\Contract\InputInterface */ -interface InputInterface +interface InputInterface extends \Inhere\Console\Contract\InputInterface { - // fixed args and opts for a command/controller-command - public const ARG_REQUIRED = 1; - - public const ARG_OPTIONAL = 2; - - public const ARG_IS_ARRAY = 4; - - public const OPT_BOOLEAN = 1; // eq symfony InputOption::VALUE_NONE - - public const OPT_REQUIRED = 2; - - public const OPT_OPTIONAL = 4; - - public const OPT_IS_ARRAY = 8; - - /** - * 读取输入信息 - * - * @param string $question 若不为空,则先输出文本消息 - * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 - * - * @return string - */ - public function read(string $question = '', bool $nl = false): string; - - /** - * @return string - */ - public function getScript(): string; - - /** - * @return string - */ - public function getCommand(): string; - - /** - * @return array - */ - public function getArgs(): array; - - /** - * get Argument - * - * @param null|int|string $name - * @param mixed $default - * - * @return mixed - */ - public function getArg($name, $default = null); - - /** - * @return array - */ - public function getOpts(): array; - - /** - * @param string $name - * @param null $default - * - * @return bool|mixed|null - */ - public function getOpt(string $name, $default = null); } diff --git a/src/IO/Output.php b/src/IO/Output.php index a7d23204..5ae5e178 100644 --- a/src/IO/Output.php +++ b/src/IO/Output.php @@ -11,7 +11,6 @@ use Inhere\Console\Component\Style\Style; use Inhere\Console\Console; use Inhere\Console\Traits\FormatOutputAwareTrait; -use Inhere\Console\Util\Show; use Toolkit\Cli\Cli; use const STDERR; use const STDOUT; @@ -21,7 +20,7 @@ * * @package Inhere\Console\IO */ -class Output implements OutputInterface +class Output implements \Inhere\Console\Contract\OutputInterface { use FormatOutputAwareTrait; @@ -150,7 +149,7 @@ public function stderr(string $text = '', $nl = true): int public function getStyle(): Style { if (!$this->style) { - $this->style = Show::getStyle(); + $this->style = Style::instance(); } return $this->style; diff --git a/src/IO/Output/BufferOutput.php b/src/IO/Output/BufferOutput.php index ccb14d78..5ddf534d 100644 --- a/src/IO/Output/BufferOutput.php +++ b/src/IO/Output/BufferOutput.php @@ -2,12 +2,32 @@ namespace Inhere\Console\IO\Output; +use Inhere\Console\IO\Output; + /** * Class BufferOutput * * @package Inhere\Console\IO\Output */ -class BufferOutput +class BufferOutput extends Output { + /** + * @var array + */ + private $buffer = []; + + /** + * @param $messages + * @param bool $nl + * @param bool $quit + * @param array $opts + * + * @return int + */ + public function write($messages, $nl = true, $quit = false, array $opts = []): int + { + $this->buffer[] = $messages; + return 0; + } } diff --git a/src/IO/OutputInterface.php b/src/IO/OutputInterface.php index a36d264d..65b8f841 100644 --- a/src/IO/OutputInterface.php +++ b/src/IO/OutputInterface.php @@ -12,17 +12,8 @@ * Class OutputInterface * * @package Inhere\Console\IO + * @deprecated please use \Inhere\Console\Contract\OutputInterface */ -interface OutputInterface +interface OutputInterface extends \Inhere\Console\Contract\OutputInterface { - /** - * Write a message to standard output stream. - * - * @param mixed $messages Output message - * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 - * @param int|boolean $quit If is int, setting it is exit code. - * - * @return int - */ - public function write($messages, $nl = true, $quit = false): int; } diff --git a/src/Traits/InputOutputAwareTrait.php b/src/Traits/InputOutputAwareTrait.php index 35ae6175..74a44458 100644 --- a/src/Traits/InputOutputAwareTrait.php +++ b/src/Traits/InputOutputAwareTrait.php @@ -10,9 +10,9 @@ use Inhere\Console\Console; use Inhere\Console\IO\Input; -use Inhere\Console\IO\InputInterface; +use Inhere\Console\Contract\InputInterface; use Inhere\Console\IO\Output; -use Inhere\Console\IO\OutputInterface; +use Inhere\Console\Contract\OutputInterface; /** * Class InputOutputAwareTrait diff --git a/src/Util/Helper.php b/src/Util/Helper.php index 7afb4f45..4550b801 100644 --- a/src/Util/Helper.php +++ b/src/Util/Helper.php @@ -59,6 +59,17 @@ public static function inCoroutine(): bool return false; } + /** + * @param int $haystack + * @param int $value + * + * @return bool + */ + public static function hasMode(int $haystack, int $value): bool + { + return ($haystack & $value) > 0; + } + /** * @param string $path * diff --git a/src/Util/ProgressBar.php b/src/Util/ProgressBar.php index 9390435a..ffb1eba3 100644 --- a/src/Util/ProgressBar.php +++ b/src/Util/ProgressBar.php @@ -10,7 +10,7 @@ use Closure; use Inhere\Console\IO\Output; -use Inhere\Console\IO\OutputInterface; +use Inhere\Console\Contract\OutputInterface; use LogicException; use RuntimeException; use Toolkit\StrUtil\Str; diff --git a/test/ApplicationTest.php b/test/ApplicationTest.php index 27397702..da85a699 100644 --- a/test/ApplicationTest.php +++ b/test/ApplicationTest.php @@ -5,7 +5,7 @@ use Inhere\Console\Application; use Inhere\Console\Console; use Inhere\Console\IO\Input; -use Inhere\Console\IO\InputInterface; +use Inhere\Console\Contract\InputInterface; use Inhere\Console\Router; use InvalidArgumentException; use PHPUnit\Framework\TestCase; diff --git a/test/IO/InputDefinitionTest.php b/test/IO/InputDefinitionTest.php new file mode 100644 index 00000000..50a7022e --- /dev/null +++ b/test/IO/InputDefinitionTest.php @@ -0,0 +1,24 @@ +addArg('arg0', Input::OPT_OPTIONAL, 'this is arg0'); + + $this->assertNotEmpty($def->getArgument('arg0')); + } +} From 37b71cf73cf0d20785c85e55dfb64ea42cce415f Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 6 Jun 2020 15:05:57 +0800 Subject: [PATCH 036/258] update some arg bind logic --- examples/alone | 2 +- src/AbstractHandler.php | 16 + src/Application.php | 21 +- src/Concern/InputArgumentsTrait.php | 293 +++++++++++ src/Concern/InputOptionsTrait.php | 449 +++++++++++++++++ src/Contract/ApplicationInterface.php | 6 +- src/Contract/ControllerInterface.php | 6 + src/Controller.php | 76 +-- src/Exception/PromptException.php | 4 +- src/IO/AbstractInput.php | 673 +------------------------- src/Traits/InputOutputAwareTrait.php | 22 +- 11 files changed, 846 insertions(+), 722 deletions(-) create mode 100644 src/Concern/InputArgumentsTrait.php create mode 100644 src/Concern/InputOptionsTrait.php diff --git a/examples/alone b/examples/alone index a6026e70..186bcd0e 100644 --- a/examples/alone +++ b/examples/alone @@ -15,7 +15,7 @@ require dirname(__DIR__) . '/test/boot.php'; $input = new Input(); $ctrl = new HomeController($input, new Output()); -$ctrl->setExecutionAlone(); +$ctrl->setDetached(); try { exit($ctrl->run($input->getCommand())); diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index ecb36a7b..0db5d55d 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -851,6 +851,14 @@ public function isAttached(): bool return $this->attached; } + /** + * @return bool + */ + public function isDetached(): bool + { + return $this->attached === false; + } + /** * @param bool $attached */ @@ -859,6 +867,14 @@ public function setAttached(bool $attached): void $this->attached = $attached; } + /** + * Detached running + */ + public function setDetached(): void + { + $this->attached = false; + } + /** * @return string */ diff --git a/src/Application.php b/src/Application.php index de098b17..bf20824c 100644 --- a/src/Application.php +++ b/src/Application.php @@ -257,7 +257,7 @@ protected function getFileFilter(): callable * @throws ReflectionException * @throws InvalidArgumentException */ - public function dispatch(string $name, bool $standAlone = false) + public function dispatch(string $name, bool $detachedRun = false) { $this->logf(Console::VERB_DEBUG, 'begin dispatch command: %s', $name); @@ -287,23 +287,25 @@ public function dispatch(string $name, bool $standAlone = false) return 2; } + $cmdOptions = $info['options']; + // save command ID $this->input->setCommandId($info['cmdId']); // is command if ($info['type'] === Router::TYPE_SINGLE) { - return $this->runCommand($info['name'], $info['handler'], $info['options']); + return $this->runCommand($info['name'], $info['handler'], $cmdOptions); } // is controller/group - return $this->runAction($info['group'], $info['action'], $info['handler'], $info['options'], $standAlone); + return $this->runAction($info['group'], $info['action'], $info['handler'], $cmdOptions, $detachedRun); } /** * run a independent command * * @param string $name Command name - * @param Closure|string $handler Command class + * @param Closure|string $handler Command class or handler func * @param array $options * * @return mixed @@ -346,12 +348,12 @@ protected function runCommand(string $name, $handler, array $options) * @param string $action Command method, no suffix * @param mixed $handler The controller class or object * @param array $options - * @param bool $standAlone + * @param bool $detachedRun * * @return mixed * @throws ReflectionException */ - protected function runAction(string $group, string $action, $handler, array $options, bool $standAlone = false) + protected function runAction(string $group, string $action, $handler, array $options, bool $detachedRun = false) { /** @var Controller $handler */ if (is_string($handler)) { @@ -372,15 +374,18 @@ protected function runAction(string $group, string $action, $handler, array $opt ); } + // force set name and description $handler::setName($group); - if ($desc = $options['description'] ?? '') { $handler::setDescription($desc); } $handler->setApp($this); $handler->setDelimiter($this->delimiter); - $handler->setExecutionAlone($standAlone); + + if ($detachedRun) { + $handler->setDetached(); + } return $handler->run($action); } diff --git a/src/Concern/InputArgumentsTrait.php b/src/Concern/InputArgumentsTrait.php new file mode 100644 index 00000000..9d08e54e --- /dev/null +++ b/src/Concern/InputArgumentsTrait.php @@ -0,0 +1,293 @@ + 0, + * 'name2' => 1, + * ] + * + * @var array + */ + protected $binds = []; + + /** + * @param string $name + * @param int $index + */ + public function bindArgument(string $name, int $index): void + { + $this->binds[$name] = $index; + } + + /** + * @param array $map + * @param bool $replace + */ + public function bindArguments(array $map, bool $replace = false): void + { + if ($replace) { + $this->binds = []; + } + + foreach ($map as $name => $index) { + $this->bindArgument($name, (int)$index); + } + } + + /*********************************************************************************** + * arguments (eg: arg0 name=john city=chengdu) + ***********************************************************************************/ + + /** + * @return array + */ + public function getArgs(): array + { + return $this->args; + } + + /** + * @return array + */ + public function getArguments(): array + { + return $this->getArgs(); + } + + /** + * @param array $args + * @param bool $replace + */ + public function setArgs(array $args, bool $replace = false): void + { + $this->args = $replace ? $args : array_merge($this->args, $args); + } + + /** + * @param string|int $name + * + * @return bool + */ + public function hasArg($name): bool + { + // get real index key + $key = $this->binds[$name] ?? $name; + + return isset($this->args[$key]); + } + + /** + * get Argument + * + * @param null|int|string $name + * @param mixed $default + * + * @return mixed + */ + public function getArgument($name, $default = null) + { + return $this->get($name, $default); + } + + /** + * get Argument + * + * @param null|int|string $name + * @param mixed $default + * + * @return mixed + */ + public function getArg($name, $default = null) + { + return $this->get($name, $default); + } + + /** + * get Argument + * + * @param null|int|string $name + * @param mixed $default + * + * @return mixed + */ + public function get($name, $default = null) + { + // get real index key + $key = $this->binds[$name] ?? $name; + + return $this->args[$key] ?? $default; + } + + /** + * Get a required argument + * + * @param int|string $name argument index or name + * @param string $errMsg + * + * @return mixed + */ + public function getRequiredArg($name, string $errMsg = '') + { + // get real index key + $key = $this->binds[$name] ?? $name; + if (isset($this->args[$key])) { + return $this->args[$key]; + } + + if (!$errMsg) { + $errName = is_int($key) ? "'{$name}'(position#{$key})" : "'{$name}'"; + $errMsg = "The argument {$errName} is required"; + } + + throw new PromptException($errMsg); + } + + /** + * Get first argument + * + * @param string $default + * + * @return string + */ + public function getFirstArg(string $default = ''): string + { + return $this->get(0, $default); + } + + /** + * Get second argument + * + * @param string $default + * + * @return string + */ + public function getSecondArg(string $default = ''): string + { + return $this->get(1, $default); + } + + /** + * Get an string argument value + * + * @param string|int $key + * @param string $default + * + * @return string + */ + public function getStringArg($key, string $default = ''): string + { + return (string)$this->get($key, $default); + } + + /** + * Get an int argument value + * + * @param string|int $key + * @param int $default + * + * @return int + */ + public function getInt($key, int $default = 0): int + { + return $this->getIntArg($key, $default); + } + + /** + * Get an int argument value + * + * @param string|int $key + * @param int $default + * + * @return int + */ + public function getIntArg($key, int $default = 0): int + { + $value = $this->get($key); + + return $value === null ? $default : (int)$value; + } + + /** + * Get an array argument value + * + * @param string|int $key + * @param array $default + * + * @return array + */ + public function getArrayArg($key, array $default = []): array + { + $value = $this->get($key); + if (is_array($value)) { + return $value; + } + + return $value ? [$value] : $default; + } + + /** + * Get same args value + * eg: des description + * + * ```php + * $input->sameArg(['des', 'description']); + * ``` + * + * @param array $names + * @param mixed $default + * + * @return bool|mixed|null + */ + public function getSameArg(array $names, $default = null) + { + return $this->sameArg($names, $default); + } + + /** + * @param array $names + * @param mixed $default + * + * @return mixed + */ + public function sameArg(array $names, $default = null) + { + foreach ($names as $name) { + if ($this->hasArg($name)) { + return $this->get($name); + } + } + + return $default; + } + + /** + * clear args + */ + public function clearArgs(): void + { + $this->args = []; + } +} diff --git a/src/Concern/InputOptionsTrait.php b/src/Concern/InputOptionsTrait.php new file mode 100644 index 00000000..75c1df8a --- /dev/null +++ b/src/Concern/InputOptionsTrait.php @@ -0,0 +1,449 @@ +lOpt($name, $default); + } + + return $this->sOpt($name, $default); + } + + /** + * Alias of the getOpt() + * + * @param string $name + * @param mixed $default + * + * @return mixed + */ + public function getOption(string $name, $default = null) + { + return $this->getOpt($name, $default); + } + + /** + * Get a required option value + * + * @param string $name + * + * @param string $errMsg + * + * @return mixed + */ + public function getRequiredOpt(string $name, string $errMsg = '') + { + if (null !== ($val = $this->getOpt($name))) { + return $val; + } + + $errMsg = $errMsg ?: "The option '{$name}' is required"; + throw new PromptException($errMsg); + } + + /** + * Get an string option(long/short) value + * + * @param string $name + * @param string $default + * + * @return string + */ + public function getStringOpt(string $name, string $default = ''): string + { + return (string)$this->getOpt($name, $default); + } + + /** + * Get an int option(long/short) value + * + * @param string $name + * @param int $default + * + * @return int + */ + public function getIntOpt(string $name, int $default = 0): int + { + return (int)$this->getOpt($name, $default); + } + + /** + * Get (long/short)option value(bool) + * eg: -h --help + * + * @param string $name + * @param bool $default + * + * @return bool + */ + public function getBoolOpt(string $name, bool $default = false): bool + { + return (bool)$this->getOpt($name, $default); + } + + /** + * Alias of the getBoolOpt() + * + * @param string $name + * @param bool $default + * + * @return bool + */ + public function boolOpt(string $name, bool $default = false): bool + { + return (bool)$this->getOpt($name, $default); + } + + /** + * check option exists + * + * @param $name + * + * @return bool + */ + public function hasOpt(string $name): bool + { + return isset($this->sOpts[$name]) || isset($this->lOpts[$name]); + } + + /** + * Get same opts value + * eg: -h --help + * + * ```php + * $input->sameOpt(['h','help']); + * ``` + * + * @param array $names + * @param mixed $default + * + * @return bool|mixed|null + */ + public function getSameOpt(array $names, $default = null) + { + return $this->sameOpt($names, $default); + } + + /** + * Alias of the getSameOpt() + * + * @param array $names + * @param null $default + * + * @return bool|mixed|null + */ + public function sameOpt(array $names, $default = null) + { + foreach ($names as $name) { + if ($this->hasOpt($name)) { + return $this->getOpt($name); + } + } + + return $default; + } + + /** + * @return array + */ + public function getOpts(): array + { + return array_merge($this->sOpts, $this->lOpts); + } + + /** + * @return array + */ + public function getOptions(): array + { + return $this->getOpts(); + } + + /** + * clear (l/s)opts + */ + public function clearOpts(): void + { + $this->sOpts = $this->lOpts = []; + } + + /************************** short-opts **********************/ + + /** + * Get short-opt value + * + * @param string $name + * @param null $default + * + * @return mixed|null + */ + public function sOpt(string $name, $default = null) + { + return $this->sOpts[$name] ?? $default; + } + + /** + * Alias of the sOpt() + * + * @param string $name + * @param null $default + * + * @return mixed|null + */ + public function shortOpt(string $name, $default = null) + { + return $this->sOpts[$name] ?? $default; + } + + /** + * Alias of the sOpt() + * + * @param string $name + * @param null $default + * + * @return mixed|null + */ + public function getShortOpt(string $name, $default = null) + { + return $this->sOpts[$name] ?? $default; + } + + /** + * Check short-opt exists + * + * @param string $name + * + * @return bool + */ + public function hasSOpt(string $name): bool + { + return isset($this->sOpts[$name]); + } + + /** + * Check multi short-opt exists + * + * @param string[] $names + * + * @return string + */ + public function findOneShortOpts(array $names): string + { + foreach ($names as $name) { + if (isset($this->sOpts[$name])) { + return $name; + } + } + + return ''; + } + + /** + * get short-opt value(bool) + * + * @param string $name + * @param bool $default + * + * @return bool + */ + public function sBoolOpt(string $name, $default = false): bool + { + $val = $this->sOpt($name); + + return is_bool($val) ? $val : (bool)$default; + } + + /** + * @return array + */ + public function getShortOpts(): array + { + return $this->sOpts; + } + + /** + * @param string $name + * @param mixed $value + */ + public function setSOpt(string $name, $value): void + { + $this->sOpts[$name] = $value; + } + + /** + * @return array + */ + public function getSOpts(): array + { + return $this->sOpts; + } + + /** + * @param array $sOpts + * @param bool $replace + */ + public function setSOpts(array $sOpts, bool $replace = false): void + { + $this->sOpts = $replace ? $sOpts : array_merge($this->sOpts, $sOpts); + } + + /** + * clear s-opts + */ + public function clearSOpts(): void + { + $this->sOpts = []; + } + + /************************** long-opts **********************/ + + /** + * Alias of the getLongOpt() + * + * @param string $name + * @param null $default + * + * @return mixed|null + */ + public function lOpt(string $name, $default = null) + { + return $this->lOpts[$name] ?? $default; + } + + /** + * Alias of the getLongOpt() + * + * @param string $name + * @param null $default + * + * @return mixed|null + */ + public function longOpt(string $name, $default = null) + { + return $this->lOpts[$name] ?? $default; + } + + /** + * Get long-opt value + * + * @param string $name + * @param null $default + * + * @return mixed|null + */ + public function getLongOpt(string $name, $default = null) + { + return $this->lOpts[$name] ?? $default; + } + + /** + * check long-opt exists + * + * @param string $name + * + * @return bool + */ + public function hasLOpt(string $name): bool + { + return isset($this->lOpts[$name]); + } + + /** + * get long-opt value(bool) + * + * @param string $name + * @param bool $default + * + * @return bool + */ + public function lBoolOpt(string $name, $default = false): bool + { + $val = $this->lOpt($name); + + return is_bool($val) ? $val : (bool)$default; + } + + /** + * @return array + */ + public function getLongOpts(): array + { + return $this->lOpts; + } + + /** + * @param string $name + * @param $value + */ + public function setLOpt(string $name, $value): void + { + $this->lOpts[$name] = $value; + } + + /** + * @return array + */ + public function getLOpts(): array + { + return $this->lOpts; + } + + /** + * @param array $lOpts + * @param bool $replace + */ + public function setLOpts(array $lOpts, bool $replace = false): void + { + $this->lOpts = $replace ? $lOpts : array_merge($this->lOpts, $lOpts); + } + + /** + * clear lang opts + */ + public function clearLOpts(): void + { + $this->lOpts = []; + } +} diff --git a/src/Contract/ApplicationInterface.php b/src/Contract/ApplicationInterface.php index 35110a50..0d08bd07 100644 --- a/src/Contract/ApplicationInterface.php +++ b/src/Contract/ApplicationInterface.php @@ -39,12 +39,12 @@ public function run(bool $exit = true); /** * Dispatch input command, exec found command handler. * - * @param string $name Inputted command name - * @param bool $standAlone Use for an group commands execution alone + * @param string $name Inputted command name + * @param bool $detachedRun Use for an group commands execution alone * * @return int|mixed */ - public function dispatch(string $name, bool $standAlone = false); + public function dispatch(string $name, bool $detachedRun = false); /** * @param int $code diff --git a/src/Contract/ControllerInterface.php b/src/Contract/ControllerInterface.php index d0cdf460..db12f5f0 100644 --- a/src/Contract/ControllerInterface.php +++ b/src/Contract/ControllerInterface.php @@ -15,6 +15,12 @@ */ interface ControllerInterface { + // eg sampleCommand() + public const COMMAND_SUFFIX = 'Command'; + + // eg sampleConfigure() + public const CONFIGURE_SUFFIX = 'Configure'; + /** * @return int */ diff --git a/src/Controller.php b/src/Controller.php index 8298bedf..5c30b47f 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -42,10 +42,14 @@ */ abstract class Controller extends AbstractHandler implements ControllerInterface { - /** @var array sub-command aliases */ + /** + * @var array sub-command aliases + */ private static $commandAliases = []; - /** @var array global options for the group command */ + /** + * @var array global options for the group command + */ protected static $globalOptions = [ '--show-disabled' => 'Whether display disabled commands', ]; @@ -57,25 +61,36 @@ abstract class Controller extends AbstractHandler implements ControllerInterface */ private $action; - /** @var string */ - private $delimiter = ':'; // '/' ':' - - /** @var bool Execution alone */ - private $executionAlone = false; + /** + * eg: '/' ':' + * + * @var string + */ + private $delimiter = ':'; - /** @var string */ + /** + * @var string + */ private $defaultAction = ''; - /** @var string */ - private $actionSuffix = 'Command'; + /** + * @var string + */ + private $actionSuffix = self::COMMAND_SUFFIX; - /** @var string */ + /** + * @var string + */ protected $notFoundCallback = 'notFound'; - /** @var array Common options for all sub-commands in the group */ + /** + * @var array Common options for all sub-commands in the group + */ private $groupOptions = []; - /** @var array From disabledCommands() */ + /** + * @var array From disabledCommands() + */ private $disabledCommands = []; /** @@ -100,7 +115,7 @@ protected function init(): void $this->groupOptions = $this->groupOptions(); if (!$this->actionSuffix) { - $this->actionSuffix = 'Command'; + $this->actionSuffix = self::COMMAND_SUFFIX; } } @@ -123,8 +138,8 @@ protected function groupOptions(): array */ protected function disabledCommands(): array { - return [// 'command1', 'command2' - ]; + // ['command1', 'command2']; + return []; } /** @@ -153,11 +168,11 @@ public function run(string $command = '') */ protected function configure(): void { - // eg. IndexConfigure() for indexCommand() - $method = $this->action . 'Configure'; + // eg. indexConfigure() for indexCommand() + $method = $this->action . self::CONFIGURE_SUFFIX; if (method_exists($this, $method)) { - $this->$method(); + $this->$method($this->input); } } @@ -176,7 +191,7 @@ final public function execute($input, $output) $group = static::getName(); if ($this->isDisabled($action)) { - $output->liteError(sprintf("Sorry, The command '%s' is invalid in the group '%s'!", $action, $group)); + $output->error(sprintf("Sorry, The command '%s' is invalid in the group '%s'!", $action, $group)); return -1; } @@ -282,7 +297,7 @@ final public function helpCommand(): int return $this->showHelpByMethodAnnotations($method, $action, $aliases); } - protected function beforeShowCommandList() + protected function beforeShowCommandList(): void { // do something ... } @@ -344,7 +359,7 @@ final public function showCommandList(): void $script = $this->getScriptName(); - if ($this->executionAlone) { + if ($detached = $this->isDetached()) { $name = $sName . ' '; $usage = "$script {command} [--options ...] [arguments ...]"; } else { @@ -365,11 +380,8 @@ final public function showCommandList(): void 'sepChar' => ' ', ]); - $this->write(sprintf( - 'More information about a command, please use: %s %s{command} -h', - $script, - $this->executionAlone ? '' : $name - )); + $msgTpl = 'More information about a command, please use: %s %s{command} -h'; + $this->output->write(sprintf($msgTpl, $script, $detached ? '' : $name)); $this->output->flush(); } @@ -519,7 +531,7 @@ public function getNotFoundCallback(): ?string /** * @param string $notFoundCallback */ - public function setNotFoundCallback(string $notFoundCallback) + public function setNotFoundCallback(string $notFoundCallback): void { $this->notFoundCallback = $notFoundCallback; } @@ -529,15 +541,17 @@ public function setNotFoundCallback(string $notFoundCallback) */ public function isExecutionAlone(): bool { - return $this->executionAlone; + throw new \RuntimeException('please call isAttached() instead'); } /** * @param bool $executionAlone + * + * @deprecated */ - public function setExecutionAlone($executionAlone = true) + public function setExecutionAlone($executionAlone = true): void { - $this->executionAlone = (bool)$executionAlone; + throw new \RuntimeException('please call setAttached() instead'); } /** diff --git a/src/Exception/PromptException.php b/src/Exception/PromptException.php index 54f903d9..956ab917 100644 --- a/src/Exception/PromptException.php +++ b/src/Exception/PromptException.php @@ -8,13 +8,13 @@ namespace Inhere\Console\Exception; -use RuntimeException; +use InvalidArgumentException; /** * Class PromptException * * @package Inhere\Console\Exception */ -class PromptException extends RuntimeException +class PromptException extends InvalidArgumentException { } diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index 60aad535..32d8db20 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -8,11 +8,9 @@ namespace Inhere\Console\IO; -use InvalidArgumentException; -use function array_merge; +use Inhere\Console\Concern\InputArgumentsTrait; +use Inhere\Console\Concern\InputOptionsTrait; use function getcwd; -use function is_array; -use function is_bool; use function is_int; use function trim; @@ -23,6 +21,8 @@ */ abstract class AbstractInput implements \Inhere\Console\Contract\InputInterface { + use InputArgumentsTrait, InputOptionsTrait; + /** * @var string */ @@ -73,27 +73,6 @@ abstract class AbstractInput implements \Inhere\Console\Contract\InputInterface */ protected $flags = []; - /** - * Input args data - * - * @var array - */ - protected $args = []; - - /** - * Input short-opts data - * - * @var array - */ - protected $sOpts = []; - - /** - * Input long-opts data - * - * @var array - */ - protected $lOpts = []; - /** * @return string */ @@ -131,650 +110,6 @@ protected function findCommand(): void $this->args = $newArgs; } - public function hasMode(): bool - { - - } - - /*********************************************************************************** - * arguments (eg: arg0 name=john city=chengdu) - ***********************************************************************************/ - - /** - * @return array - */ - public function getArgs(): array - { - return $this->args; - } - - /** - * @return array - */ - public function getArguments(): array - { - return $this->getArgs(); - } - - /** - * @param array $args - * @param bool $replace - */ - public function setArgs(array $args, $replace = false): void - { - $this->args = $replace ? $args : array_merge($this->args, $args); - } - - /** - * @param string|int $name - * - * @return bool - */ - public function hasArg($name): bool - { - return isset($this->args[$name]); - } - - /** - * get Argument - * - * @param null|int|string $name - * @param mixed $default - * - * @return mixed - */ - public function getArgument($name, $default = null) - { - return $this->get($name, $default); - } - - /** - * get Argument - * - * @param null|int|string $name - * @param mixed $default - * - * @return mixed - */ - public function getArg($name, $default = null) - { - return $this->get($name, $default); - } - - /** - * get Argument - * - * @param null|int|string $name - * @param mixed $default - * - * @return mixed - */ - public function get($name, $default = null) - { - return $this->args[$name] ?? $default; - } - - /** - * get a required argument - * - * @param int|string $name argument index - * - * @return mixed - * @throws InvalidArgumentException - */ - public function getRequiredArg($name) - { - if ('' !== $this->get($name, '')) { - return $this->args[$name]; - } - - throw new InvalidArgumentException("The argument '{$name}' is required"); - } - - /** - * get first argument - * - * @param string $default - * - * @return string - */ - public function getFirstArg(string $default = ''): string - { - return $this->get(0, $default); - } - - /** - * get second argument - * - * @param string $default - * - * @return string - */ - public function getSecondArg(string $default = ''): string - { - return $this->get(1, $default); - } - - /** - * Get an string argument value - * - * @param string|int $key - * @param string $default - * - * @return string - */ - public function getStringArg($key, string $default = ''): string - { - return (string)$this->get($key, $default); - } - - /** - * Get an int argument value - * - * @param string|int $key - * @param int $default - * - * @return int - */ - public function getInt($key, int $default = 0): int - { - return $this->getIntArg($key, $default); - } - - /** - * Get an int argument value - * - * @param string|int $key - * @param int $default - * - * @return int - */ - public function getIntArg($key, int $default = 0): int - { - $value = $this->get($key); - - return $value === null ? $default : (int)$value; - } - - /** - * Get an array argument value - * - * @param string|int $key - * @param array $default - * - * @return array - */ - public function getArrayArg($key, array $default = []): array - { - $value = $this->get($key); - - if (is_array($value)) { - return $value; - } - - return $value ? [$value] : $default; - } - - /** - * get same args value - * eg: des description - * - * ```php - * $input->sameArg(['des', 'description']); - * ``` - * - * @param array $names - * @param mixed $default - * - * @return bool|mixed|null - */ - public function getSameArg(array $names, $default = null) - { - return $this->sameArg($names, $default); - } - - /** - * @param array $names - * @param mixed $default - * - * @return mixed - */ - public function sameArg(array $names, $default = null) - { - foreach ($names as $name) { - if ($this->hasArg($name)) { - return $this->get($name); - } - } - - return $default; - } - - /** - * clear args - */ - public function clearArgs(): void - { - $this->args = []; - } - - /*********************************************************************************** - * long/short options (eg: -d --help) - ***********************************************************************************/ - - /** - * get (long/short)opt value - * eg: -e dev --name sam - * - * @param string $name - * @param null $default - * - * @return bool|mixed|null - */ - public function getOpt(string $name, $default = null) - { - // It's long-opt - if (isset($name[1])) { - return $this->lOpt($name, $default); - } - - return $this->sOpt($name, $default); - } - - /** - * alias of the getOpt() - * - * @param string $name - * @param mixed $default - * - * @return mixed - */ - public function getOption(string $name, $default = null) - { - return $this->getOpt($name, $default); - } - - /** - * get a required argument - * - * @param string $name - * - * @return mixed - * @throws InvalidArgumentException - */ - public function getRequiredOpt(string $name) - { - if (null === ($val = $this->getOpt($name))) { - throw new InvalidArgumentException("The option '{$name}' is required"); - } - - return $val; - } - - /** - * Get an string option(long/short) value - * - * @param string $name - * @param string $default - * - * @return string - */ - public function getStringOpt(string $name, string $default = ''): string - { - return (string)$this->getOpt($name, $default); - } - - /** - * Get an int option(long/short) value - * - * @param string $name - * @param int $default - * - * @return int - */ - public function getIntOpt(string $name, int $default = 0): int - { - return (int)$this->getOpt($name, $default); - } - - /** - * Get (long/short)option value(bool) - * eg: -h --help - * - * @param string $name - * @param bool $default - * - * @return bool - */ - public function getBoolOpt(string $name, bool $default = false): bool - { - return (bool)$this->getOpt($name, $default); - } - - /** - * Alias of the getBoolOpt() - * - * @param string $name - * @param bool $default - * - * @return bool - */ - public function boolOpt(string $name, bool $default = false): bool - { - return (bool)$this->getOpt($name, $default); - } - - /** - * check option exists - * - * @param $name - * - * @return bool - */ - public function hasOpt(string $name): bool - { - return isset($this->sOpts[$name]) || isset($this->lOpts[$name]); - } - - /** - * Get same opts value - * eg: -h --help - * - * ```php - * $input->sameOpt(['h','help']); - * ``` - * - * @param array $names - * @param mixed $default - * - * @return bool|mixed|null - */ - public function getSameOpt(array $names, $default = null) - { - return $this->sameOpt($names, $default); - } - - /** - * Alias of the getSameOpt() - * - * @param array $names - * @param null $default - * - * @return bool|mixed|null - */ - public function sameOpt(array $names, $default = null) - { - foreach ($names as $name) { - if ($this->hasOpt($name)) { - return $this->getOpt($name); - } - } - - return $default; - } - - /** - * @return array - */ - public function getOpts(): array - { - return array_merge($this->sOpts, $this->lOpts); - } - - /** - * @return array - */ - public function getOptions(): array - { - return $this->getOpts(); - } - - /** - * clear (l/s)opts - */ - public function clearOpts(): void - { - $this->sOpts = $this->lOpts = []; - } - - /************************** short-opts **********************/ - - /** - * Get short-opt value - * - * @param string $name - * @param null $default - * - * @return mixed|null - */ - public function sOpt(string $name, $default = null) - { - return $this->sOpts[$name] ?? $default; - } - - /** - * Alias of the sOpt() - * - * @param string $name - * @param null $default - * - * @return mixed|null - */ - public function shortOpt(string $name, $default = null) - { - return $this->sOpts[$name] ?? $default; - } - - /** - * Alias of the sOpt() - * - * @param string $name - * @param null $default - * - * @return mixed|null - */ - public function getShortOpt(string $name, $default = null) - { - return $this->sOpts[$name] ?? $default; - } - - /** - * Check short-opt exists - * - * @param string $name - * - * @return bool - */ - public function hasSOpt(string $name): bool - { - return isset($this->sOpts[$name]); - } - - /** - * Check multi short-opt exists - * - * @param string[] $names - * - * @return string - */ - public function findOneShortOpts(array $names): string - { - foreach ($names as $name) { - if (isset($this->sOpts[$name])) { - return $name; - } - } - - return ''; - } - - /** - * get short-opt value(bool) - * - * @param string $name - * @param bool $default - * - * @return bool - */ - public function sBoolOpt(string $name, $default = false): bool - { - $val = $this->sOpt($name); - - return is_bool($val) ? $val : (bool)$default; - } - - /** - * @return array - */ - public function getShortOpts(): array - { - return $this->sOpts; - } - - /** - * @param string $name - * @param mixed $value - */ - public function setSOpt(string $name, $value): void - { - $this->sOpts[$name] = $value; - } - - /** - * @return array - */ - public function getSOpts(): array - { - return $this->sOpts; - } - - /** - * @param array $sOpts - * @param bool $replace - */ - public function setSOpts(array $sOpts, bool $replace = false): void - { - $this->sOpts = $replace ? $sOpts : array_merge($this->sOpts, $sOpts); - } - - /** - * clear s-opts - */ - public function clearSOpts(): void - { - $this->sOpts = []; - } - - /************************** long-opts **********************/ - - /** - * Alias of the getLongOpt() - * - * @param string $name - * @param null $default - * - * @return mixed|null - */ - public function lOpt(string $name, $default = null) - { - return $this->lOpts[$name] ?? $default; - } - - /** - * Alias of the getLongOpt() - * - * @param string $name - * @param null $default - * - * @return mixed|null - */ - public function longOpt(string $name, $default = null) - { - return $this->lOpts[$name] ?? $default; - } - - /** - * Get long-opt value - * - * @param string $name - * @param null $default - * - * @return mixed|null - */ - public function getLongOpt(string $name, $default = null) - { - return $this->lOpts[$name] ?? $default; - } - - /** - * check long-opt exists - * - * @param string $name - * - * @return bool - */ - public function hasLOpt(string $name): bool - { - return isset($this->lOpts[$name]); - } - - /** - * get long-opt value(bool) - * - * @param string $name - * @param bool $default - * - * @return bool - */ - public function lBoolOpt(string $name, $default = false): bool - { - $val = $this->lOpt($name); - - return is_bool($val) ? $val : (bool)$default; - } - - /** - * @return array - */ - public function getLongOpts(): array - { - return $this->lOpts; - } - - /** - * @param string $name - * @param $value - */ - public function setLOpt(string $name, $value): void - { - $this->lOpts[$name] = $value; - } - - /** - * @return array - */ - public function getLOpts(): array - { - return $this->lOpts; - } - - /** - * @param array $lOpts - * @param bool $replace - */ - public function setLOpts(array $lOpts, bool $replace = false): void - { - $this->lOpts = $replace ? $lOpts : array_merge($this->lOpts, $lOpts); - } - - /** - * clear lang opts - */ - public function clearLOpts(): void - { - $this->lOpts = []; - } - /*********************************************************************************** * getter/setter ***********************************************************************************/ diff --git a/src/Traits/InputOutputAwareTrait.php b/src/Traits/InputOutputAwareTrait.php index 74a44458..e3987735 100644 --- a/src/Traits/InputOutputAwareTrait.php +++ b/src/Traits/InputOutputAwareTrait.php @@ -102,8 +102,10 @@ public function getSameArg(array $names, $default = null) } /** - * {@inheritdoc} - * @see Input::getOpt() + * @param int|string $name + * @param mixed $default + * + * @return mixed */ public function getOpt($name, $default = null) { @@ -111,8 +113,10 @@ public function getOpt($name, $default = null) } /** - * {@inheritdoc} - * @see Input::getSameOpt() + * @param array $names + * @param mixed $default + * + * @return mixed */ public function getSameOpt(array $names, $default = null) { @@ -120,12 +124,14 @@ public function getSameOpt(array $names, $default = null) } /** - * {@inheritdoc} - * @see Input::getRequiredOpt() + * @param string $name + * @param string $errMsg + * + * @return mixed */ - public function getRequiredOpt(string $name) + public function getRequiredOpt(string $name, string $errMsg = '') { - return $this->input->getRequiredOpt($name); + return $this->input->getRequiredOpt($name, $errMsg); } /** From c9523169e2d85b788c06fe7ec1ca2a9da659016e Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 6 Jun 2020 15:38:28 +0800 Subject: [PATCH 037/258] add some tests --- test/IO/InputTest.php | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 test/IO/InputTest.php diff --git a/test/IO/InputTest.php b/test/IO/InputTest.php new file mode 100644 index 00000000..a6fe4db8 --- /dev/null +++ b/test/IO/InputTest.php @@ -0,0 +1,47 @@ +assertSame('./bin/app', $in->getScript()); + $this->assertSame('app', $in->getScriptName()); + $this->assertSame('cmd', $in->getCommand()); + } + + public function testArguments(): void + { + $in = new Input(['./bin/app', 'cmd', 'val0', 'val1']); + + $this->assertTrue($in->hasArg(0)); + $this->assertSame('val0', $in->getArgument(0)); + $this->assertSame('val1', $in->getArgument(1)); + } + + public function testBindArgument(): void + { + $in = new Input(['./bin/app', 'cmd', 'val0', 'val1']); + + $this->assertTrue($in->hasArg(0)); + $this->assertFalse($in->hasArg('arg0')); + $this->assertFalse($in->hasArg('arg1')); + + $in->bindArgument('arg0', 0); + $this->assertTrue($in->hasArg('arg0')); + + $in->bindArguments(['arg1' => 1]); + $this->assertTrue($in->hasArg('arg1')); + } +} From 8e41446f7ea81c8b65893be0be236c2501d27080 Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 6 Jun 2020 16:22:01 +0800 Subject: [PATCH 038/258] upsome --- src/Component/Formatter/Title.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Component/Formatter/Title.php b/src/Component/Formatter/Title.php index c641fe8c..07dbd42a 100644 --- a/src/Component/Formatter/Title.php +++ b/src/Component/Formatter/Title.php @@ -36,7 +36,11 @@ public static function show(string $title, array $opts = []): void $width = (int)$opts['width']; $char = trim($opts['char']); $indent = (int)$opts['indent'] >= 0 ? $opts['indent'] : 2; - $indentStr = Str::pad(self::CHAR_SPACE, $indent, self::CHAR_SPACE); + + $indentStr = ''; + if ($indent > 0) { + $indentStr = Str::pad(self::CHAR_SPACE, $indent, self::CHAR_SPACE); + } $title = $opts['ucWords'] ? ucwords(trim($title)) : trim($title); $tLength = Str::len($title); From 68f959b52922d7aa33067a28678e411536f87436 Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 6 Jun 2020 17:37:23 +0800 Subject: [PATCH 039/258] upsome --- src/Concern/InputOptionsTrait.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Concern/InputOptionsTrait.php b/src/Concern/InputOptionsTrait.php index 75c1df8a..663a63e1 100644 --- a/src/Concern/InputOptionsTrait.php +++ b/src/Concern/InputOptionsTrait.php @@ -123,6 +123,20 @@ public function getBoolOpt(string $name, bool $default = false): bool return (bool)$this->getOpt($name, $default); } + /** + * Get (long/short)option value(bool) + * eg: -h --help + * + * @param string[] $names + * @param bool $default + * + * @return bool + */ + public function getSameBoolOpt(array $names, bool $default = false): bool + { + return (bool)$this->getSameOpt($names, $default); + } + /** * Alias of the getBoolOpt() * From f63f10f66ea349d3fff9315ac7128ea819cabd66 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 8 Jun 2020 11:52:18 +0800 Subject: [PATCH 040/258] update some logic for group sub-command alias --- src/AbstractApplication.php | 2 +- src/Component/PharCompiler.php | 466 ++++++++++++++++-------------- src/Concern/InputOptionsTrait.php | 13 + src/Controller.php | 70 ++++- src/Router.php | 4 +- src/Traits/NameAliasTrait.php | 8 +- 6 files changed, 324 insertions(+), 239 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index b47f57c0..f73681b5 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -393,7 +393,7 @@ protected function filterSpecialCommand(string $command): bool public function addAliases(string $name, $aliases): self { if ($name && $aliases) { - $this->router->setAlias($name, $aliases); + $this->router->setAlias($name, $aliases, true); } return $this; diff --git a/src/Component/PharCompiler.php b/src/Component/PharCompiler.php index 4f7a1ee1..b416c06d 100644 --- a/src/Component/PharCompiler.php +++ b/src/Component/PharCompiler.php @@ -1,12 +1,6 @@ 1, @@ -74,7 +72,7 @@ class PharCompiler /** @var resource */ private $key; - /** @var */ + /** @var int */ private $signatureType; /** @@ -82,48 +80,20 @@ class PharCompiler */ private $compressMode = 0; - /** - * @var string|null The latest commit id - */ - private $version; - - /** - * @var string|null The latest tag name - */ - private $branchAliasVersion = 'UNKNOWN'; - - /** - * @var DateTime - */ - private $versionDate; - - /** - * 记录上面三个信息的文件, 相对于basePath - * 当里面存在下面的占位符时会自动替换为获取到的信息 - * [ - * 'version' => '{@package_version}', - * 'tag' => '{@package_branch_alias_version}', - * 'releaseDate' => '{@release_date}', - * ] - * - * @var string - */ - private $versionFile; - /** * @var string The want to packaged project path */ private $basePath; /** - * @var string|null + * @var string */ - private $cliIndex; + private $cliIndex = ''; /** - * @var string|null + * @var string */ - private $webIndex; + private $webIndex = ''; /** * @var string|bool Set the shebang. eg '#!/usr/bin/env php' @@ -141,12 +111,10 @@ class PharCompiler private $suffixes = ['.php']; /** - * @var array Want to exclude file name list - */ - private $notNames = []; - - /** - * @var array Want to exclude directory name list + * @var array Want to exclude directory/file name list + * [ + * '/test/', // exclude all contains '/test/' path + * ] */ private $excludes = []; @@ -155,18 +123,10 @@ class PharCompiler */ private $directories = []; - /** - * @var array|Iterator The modifies files list. if not empty, will skip find dirs. - */ - private $modifies; - /** * @var Closure[] Some events. if you want to get some info on packing. */ - private $events = [ - 'add' => 0, - 'error' => 0, - ]; + private $events = []; /** * @var Closure Maybe you not want strip all files. @@ -183,7 +143,36 @@ class PharCompiler */ private $collectVersionInfo = true; - // -------------------- internal props -------------------- + // -------------------- project version(by git) -------------------- + + /** + * @var string The latest commit id + */ + private $lastCommit = ''; + + /** + * @var string The latest tag name + */ + private $lastVersion = ''; + + /** + * @var DateTime + */ + private $versionDate; + + /** + * 记录上面三个信息的文件, 相对于basePath + * 当里面存在下面的占位符时会自动替换为获取到的信息 + * [ + * 'lastCommit' => '{@package_last_commit}', + * 'lastVersion' => '{@package_last_version}', + * 'releaseDate' => '{@release_date}', + * ] + * @var string + */ + private $versionFile = ''; + + // -------------------- internal properties -------------------- /** @var int */ private $counter = 0; @@ -203,14 +192,17 @@ class PharCompiler */ private $fileFilter; + /** + * @var array|Iterator The modifies files list. if not empty, will skip find dirs. + */ + private $modifies; + /** * @param string $pharFile * @param string $extractTo * @param string|array|null $files Only fetch the listed files * @param bool $overwrite - * * @return bool - * @throws UnexpectedValueException * @throws BadMethodCallException * @throws RuntimeException */ @@ -224,7 +216,6 @@ public static function unpack(string $pharFile, string $extractTo, $files = null } /** - * * @throws RuntimeException */ private static function checkEnv(): void @@ -234,15 +225,15 @@ private static function checkEnv(): void } if (ini_get('phar.readonly')) { - throw new RuntimeException("The 'phar.readonly' is 'On', build phar must setting it 'Off' or exec with 'php -d phar.readonly=0'"); + throw new RuntimeException( + "The 'phar.readonly' is 'On', build phar must setting it 'Off' or exec with 'php -d phar.readonly=0'" + ); } } /** * PharCompiler constructor. - * * @param string $basePath - * * @throws RuntimeException */ public function __construct(string $basePath) @@ -252,115 +243,137 @@ public function __construct(string $basePath) $this->basePath = realpath($basePath); if (!is_dir($this->basePath)) { - throw new RuntimeException("The inputted project path is not exists. DIR: {$this->basePath}"); + throw new RuntimeException("The inputted path is not exists. PATH: {$this->basePath}"); } } + /** + * @param string|array $files + * @return $this + */ + public function addFile($files): self + { + $this->files = array_merge($this->files, (array)$files); + return $this; + } + + /** + * @param array $files + * @return PharCompiler + */ + public function setFiles(array $files): self + { + $this->files = $files; + return $this; + } + /** * @param string|array $suffixes - * * @return $this */ public function addSuffix($suffixes): self { $this->suffixes = array_merge($this->suffixes, (array)$suffixes); - return $this; } /** - * @param string|array $filename - * + * @param string|array $patterns * @return $this */ - public function notName($filename): self + public function addExclude($patterns): self { - $this->notNames = array_merge($this->notNames, (array)$filename); - + $this->excludes = array_merge($this->excludes, (array)$patterns); return $this; } /** - * @param string|array $dirs - * + * @param string|array $patterns * @return $this */ - public function addExclude($dirs): self + public function addExcludeDir($patterns): self { - $this->excludes = array_merge($this->excludes, (array)$dirs); + $list = []; + foreach ((array)$patterns as $pattern) { + $list[] = '/' . trim($pattern, '/') . '/'; + } + $this->excludes = array_merge($this->excludes, $list); return $this; } /** - * @param string|array $files - * + * @param string|array $patterns * @return $this */ - public function addFile($files): self + public function addExcludeFile($patterns): self { - $this->files = array_merge($this->files, (array)$files); + $list = []; + foreach ((array)$patterns as $pattern) { + $list[] = '/' . ltrim($pattern, '/') . '/'; + } + $this->excludes = array_merge($this->excludes, $list); + return $this; + } + + /** + * @param array $excludes + * @return PharCompiler + */ + public function setExcludes(array $excludes): self + { + $this->excludes = $excludes; return $this; } /** * @param bool $value - * * @return PharCompiler */ public function stripComments($value): self { $this->stripComments = (bool)$value; - return $this; } /** * @param bool $value - * * @return PharCompiler */ public function collectVersion($value): self { $this->collectVersionInfo = (bool)$value; - return $this; } /** * @param Closure $stripFilter - * * @return PharCompiler */ - public function setStripFilter(Closure $stripFilter): PharCompiler + public function setStripFilter(Closure $stripFilter): self { $this->stripFilter = $stripFilter; - return $this; } /** * @param bool|string $shebang - * * @return PharCompiler */ - public function setShebang($shebang): PharCompiler + public function setShebang($shebang): self { $this->shebang = $shebang; - return $this; } /** * @param string|array $dirs - * * @return PharCompiler */ public function in($dirs): self { $this->directories = array_merge($this->directories, (array)$dirs); - return $this; } @@ -372,20 +385,18 @@ public function in($dirs): self public function setModifies($modifies): self { $this->modifies = $modifies; - return $this; } /** * Compiles composer into a single phar file - * - * @param string $pharFile The full path to the file to create - * @param bool $refresh - * + * @param string $pharFile The full path to the file to create + * @param bool $refresh * @return string * @throws UnexpectedValueException * @throws BadMethodCallException * @throws RuntimeException + * @throws InvalidArgumentException * @throws Exception */ public function pack(string $pharFile, $refresh = true): string @@ -395,23 +406,24 @@ public function pack(string $pharFile, $refresh = true): string } $exists = file_exists($pharFile); - if ($refresh && $exists) { unlink($pharFile); } $this->pharFile = $pharFile; $this->pharName = $pharName = basename($this->pharFile); - $this->excludes = array_flip($this->excludes); + // $this->excludes = \array_flip($this->excludes); $this->collectInformation(); $phar = new Phar($pharFile, 0, $pharName); - if ($this->key !== null) { + if ($this->key) { $privateKey = ''; + /** @noinspection PhpComposerExtensionStubsInspection */ openssl_pkey_export($this->key, $privateKey); $phar->setSignatureAlgorithm(Phar::OPENSSL, $privateKey); + /** @noinspection PhpComposerExtensionStubsInspection */ $keyDetails = openssl_pkey_get_details($this->key); file_put_contents($pharFile . '.pubkey', $keyDetails['key']); } else { @@ -421,7 +433,7 @@ public function pack(string $pharFile, $refresh = true): string $basePath = $this->basePath; $phar->startBuffering(); - // only build modifies + // Only build modifies if (!$refresh && $exists && $this->modifies) { foreach ($this->modifies as $file) { if ('/' === $file[0] || is_file($file = $basePath . '/' . $file)) { @@ -429,7 +441,7 @@ public function pack(string $pharFile, $refresh = true): string } } } else { - // collect files in there are dirs. + // Collect files in there are dirs. foreach ($this->directories as $directory) { foreach ($this->findFiles($directory) as $file) { $this->packFile($phar, $file); @@ -437,14 +449,14 @@ public function pack(string $pharFile, $refresh = true): string } } - // add special files + // Add special files foreach ($this->files as $filename) { if ('/' === $filename[0] || is_file($filename = $basePath . '/' . $filename)) { $this->packFile($phar, new SplFileInfo($filename)); } } - // add index files + // Add index files $this->packIndexFile($phar); // Stubs @@ -455,6 +467,16 @@ public function pack(string $pharFile, $refresh = true): string $phar->compressFiles($this->compressMode); } + // Default meta information + // $metaData = array( + // 'Author' => 'Inhere ', + // 'Description' => 'PHP Class for working with Matrix numbers', + // 'Copyright' => 'Mark Baker (c) 2013-' . date('Y'), + // 'Timestamp' => time(), + // 'Version' => '0.1.0', + // 'Date' => date('Y-m-d') + // ); + // $phar->setMetadata($metaData); $phar->stopBuffering(); unset($phar); @@ -470,10 +492,9 @@ public function pack(string $pharFile, $refresh = true): string /** * find changed or new created files by git status. - * - * @return Generator + * @throws RuntimeException */ - public function findChangedByGit(): ?Generator + public function findChangedByGit() { // -u expand dir's files [, $output,] = Sys::run('git status -s -u', $this->basePath); @@ -493,7 +514,7 @@ public function findChangedByGit(): ?Generator if (strpos($file, 'M ') === 0) { yield substr($file, 2); - // new files + // new files } elseif (strpos($file, '?? ') === 0) { yield substr($file, 3); } @@ -504,62 +525,77 @@ public function findChangedByGit(): ?Generator * @param string $directory * * @return Iterator|SplFileInfo[] + * @throws InvalidArgumentException */ protected function findFiles(string $directory) { - return Helper::directoryIterator( + return Dir::filterIterator( $directory, - $this->createIteratorFilter(), + $this->getIteratorFilter(), FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS ); } /** * Add a file to the Phar. - * * @param Phar $phar * @param SplFileInfo $file */ private function packFile(Phar $phar, SplFileInfo $file): void { + $filePath = $file->getPathname(); + // skip error - if (!file_exists($file)) { - $this->reportError("File $file is not exists!"); + if (!file_exists($filePath)) { + $this->fire(self::ON_ERROR, "File $filePath is not exists!"); return; } + $strip = $this->stripComments; + $path = $this->getRelativeFilePath($file); + $this->counter++; - $path = $this->getRelativeFilePath($file); - $strip = $this->stripComments; - $content = file_get_contents($file); + $this->fire(self::ON_ADD, $path, $this->counter); // clear php file comments if ($strip && strpos($path, '.php')) { $filter = $this->stripFilter; - if (!$filter || ($filter && $filter($file))) { - $content = $this->stripWhitespace($content); + if (!$filter || $filter($file)) { + $content = $this->stripWhitespace(file_get_contents($filePath)); + + // add content to phar + $phar->addFromString( + $path, + $this->addVersionInfo($content) . "\n// added by phar pack" + ); + return; } } // have versionFile if ($path === $this->versionFile) { - $content = str_replace([ - '{@package_version}', - '{@package_branch_alias_version}', - '{@release_date}', - ], [ - $this->version, - $this->branchAliasVersion, - $this->versionDate->format('Y-m-d H:i:s') - ], $content); - } + $content = file_get_contents($filePath); - if ($cb = $this->events['add']) { - $cb($path, $this->counter); + $phar->addFromString($path, $this->addVersionInfo($content)); + return; } - $phar->addFromString($path, $content); + // add file to phar + $phar->addFile($filePath, $path); + } + + private function addVersionInfo(string $content): string + { + return str_replace([ + '{@package_last_commit}', + '{@package_last_version}', + '{@release_date}', + ], [ + $this->lastCommit, + $this->lastVersion, + $this->versionDate->format('Y-m-d H:i:s') + ], $content); } /** @@ -572,23 +608,18 @@ private function packIndexFile(Phar $phar): void $path = $this->basePath . '/' . $this->cliIndex; $content = preg_replace('{^#!/usr/bin/env php\s*}', '', file_get_contents($path)); - if ($cb = $this->events['add']) { - $cb($this->cliIndex, $this->counter); - } - - $phar->addFromString($this->cliIndex, $content); + $this->fire(self::ON_ADD, $this->cliIndex, $this->counter); + $phar->addFromString($this->cliIndex, trim($content) . PHP_EOL); } if ($this->webIndex) { $this->counter++; - $path = $this->basePath . '/' . $this->webIndex; - $content = file_get_contents($path); + $path = $this->basePath . '/' . $this->webIndex; - if ($cb = $this->events['add']) { - $cb($this->webIndex, $this->counter); - } + $this->fire(self::ON_ADD, $this->webIndex, $this->counter); - $phar->addFromString($this->webIndex, $content); + $content = file_get_contents($path); + $phar->addFromString($this->webIndex, trim($content) . PHP_EOL); } } @@ -606,11 +637,10 @@ public function getCounter(): int */ private function createStub(): string { - // var_dump($this);die; $date = date('Y-m-d H:i'); $pharName = $this->pharName; $stub = << @@ -628,7 +658,7 @@ private function createStub(): string if ($this->cliIndex && $this->webIndex) { $stub .= <<cliIndex}'; } else { @@ -649,33 +679,38 @@ private function createStub(): string /** * @return Closure */ - private function createIteratorFilter(): Closure + private function getIteratorFilter(): Closure { if (!$this->fileFilter) { $this->fileFilter = function (SplFileInfo $file) { $name = $file->getFilename(); + $path = FSHelper::formatPath($file->getPathname()); // Skip hidden files and directories. if (strpos($name, '.') === 0) { return false; } - // skip exclude directories. + // Skip exclude directories. if ($file->isDir()) { - return !isset($this->excludes[$name]); - } - - // skip exclude files. - if ($this->notNames && in_array($name, $this->notNames, true)) { - return false; + foreach ($this->excludes as $exclude) { + if (strpos($path . '/', $exclude) > 0) { + $this->fire(self::ON_SKIP, $path, false); + return false; + } + } + return true; } + // File ext check if ($this->suffixes) { foreach ($this->suffixes as $suffix) { - if (stripos($name, $suffix)) { + if (stripos($name, $suffix) !== false) { return true; } } + + $this->fire(self::ON_SKIP, $path, true); return false; } @@ -688,9 +723,7 @@ private function createIteratorFilter(): Closure /** * Removes whitespace from a PHP source string while preserving line numbers. - * - * @param string $source A PHP string - * + * @param string $source A PHP string * @return string The PHP string with the whitespace removed */ private function stripWhitespace(string $source): string @@ -712,7 +745,8 @@ private function stripWhitespace(string $source): string $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace); // trim leading spaces $whitespace = preg_replace('{\n +}', "\n", $whitespace); - $output .= $whitespace; + // append + $output .= $whitespace; } else { $output .= $token[1]; } @@ -722,8 +756,7 @@ private function stripWhitespace(string $source): string } /** - * auto collect project information by git log - * + * Auto collect project information by git log * @throws RuntimeException * @throws Exception */ @@ -737,63 +770,64 @@ private function collectInformation(): void [$code, $ret,] = Sys::run('git log --pretty="%H" -n1 HEAD', $basePath); if ($code !== 0) { - throw new RuntimeException('Can\'t run git log. You must ensure to run compile from git repository clone and that git binary is available.'); + throw new RuntimeException( + 'Can\'t run git log. You must ensure to run compile from git repository clone and that git binary is available.' + ); } - $this->version = trim($ret); + $this->lastCommit = trim($ret); [$code, $ret,] = Sys::run('git log -n1 --pretty=%ci HEAD', $basePath); if ($code !== 0) { - throw new RuntimeException('Can\'t run git log. You must ensure to run compile from git repository clone and that git binary is available.'); + throw new RuntimeException( + 'Can\'t run git log. You must ensure to run compile from git repository clone and that git binary is available.' + ); } $this->versionDate = new DateTime(trim($ret)); $this->versionDate->setTimezone(new DateTimeZone('UTC')); - // 获取到最新的 tag + // Get the latest tag [$code, $ret,] = Sys::run('git describe --tags --exact-match HEAD', $basePath); if ($code === 0) { - $this->version = trim($ret); + $this->lastCommit = trim($ret); } else { - [$code1, $ret,] = Sys::run('git branch', $basePath); - - if ($code1 === 0) { - $this->branchAliasVersion = explode("\n", trim($ret, "* \n"), 2)[0]; - } + [$code, $ret,] = Sys::run('git branch', $basePath); + $this->lastVersion = $code === 0 ? trim($ret, '* ') : 'UNKNOWN'; } } /** - * @param SplFileInfo $file - * + * @param SplFileInfo $file * @return string */ - private function getRelativeFilePath($file): string + private function getRelativeFilePath(SplFileInfo $file): string { $realPath = $file->getRealPath(); $pathPrefix = $this->basePath . DIRECTORY_SEPARATOR; - $pos = strpos($realPath, $pathPrefix); - $relativePath = $pos !== false ? substr_replace($realPath, '', $pos, strlen($pathPrefix)) : $realPath; + $pos = strpos($realPath, $pathPrefix); + $path = $pos !== false ? substr_replace($realPath, '', $pos, strlen($pathPrefix)) : $realPath; - return str_replace('\\', '/', $relativePath); + return str_replace('\\', '/', $path); } /** - * @param string $error + * @param string $event + * @param array $args */ - private function reportError($error): void + private function fire(string $event, ...$args): void { - if ($cb = $this->events['error']) { - $cb($error); + if (isset($this->events[$event])) { + $cb = $this->events[$event]; + $cb(...$args); } } /** * add event handler - * - * @param string $event + * @param string $event * @param Closure $closure */ public function on(string $event, Closure $closure): void @@ -806,7 +840,7 @@ public function on(string $event, Closure $closure): void */ public function onAdd(Closure $onAdd): void { - $this->events['add'] = $onAdd; + $this->on(self::ON_ADD, $onAdd); } /** @@ -814,7 +848,7 @@ public function onAdd(Closure $onAdd): void */ public function onError(Closure $onError): void { - $this->events['error'] = $onError; + $this->on(self::ON_ERROR, $onError); } /** @@ -850,36 +884,33 @@ public function getExcludes(): array } /** - * @return string|null + * @return string */ - public function getCliIndex(): ?string + public function getCliIndex(): string { return $this->cliIndex; } /** * @param string $cliIndex - * * @return $this */ public function setCliIndex(string $cliIndex): self { $this->cliIndex = $cliIndex; - return $this; } /** - * @return null|string + * @return string */ - public function getWebIndex(): ?string + public function getWebIndex(): string { return $this->webIndex; } /** * @param null|string $webIndex - * * @return PharCompiler */ public function setWebIndex(string $webIndex): self @@ -897,61 +928,56 @@ public function getEvents(): array } /** - * @return string|null + * @return string */ - public function getVersionFile(): ?string + public function getVersionFile(): string { return $this->versionFile; } /** * @param string $versionFile - * * @return PharCompiler */ public function setVersionFile(string $versionFile): PharCompiler { $this->versionFile = $versionFile; - return $this; } /** - * @return string|null + * @return string */ - public function getVersion(): ?string + public function getLastCommit(): string { - return $this->version; + return $this->lastCommit; } /** - * @param null|string $version - * + * @param null|string $lastCommit * @return PharCompiler */ - public function setVersion(string $version): PharCompiler + public function setLastCommit(string $lastCommit): PharCompiler { - $this->version = $version; - + $this->lastCommit = $lastCommit; return $this; } /** - * @return null|string + * @return string */ - public function getBranchAliasVersion(): ?string + public function getLastVersion(): string { - return $this->branchAliasVersion; + return $this->lastVersion; } /** - * @param null|string $branchAliasVersion - * + * @param null|string $lastVersion * @return PharCompiler */ - public function setBranchAliasVersion(string $branchAliasVersion): PharCompiler + public function setLastVersion(string $lastVersion): PharCompiler { - $this->branchAliasVersion = $branchAliasVersion; + $this->lastVersion = $lastVersion; return $this; } @@ -982,12 +1008,4 @@ public function getPharFile(): string { return $this->pharFile; } - - /** - * @return bool - */ - public function hasDirectory(): bool - { - return !empty($this->directories); - } } diff --git a/src/Concern/InputOptionsTrait.php b/src/Concern/InputOptionsTrait.php index 663a63e1..fcd85100 100644 --- a/src/Concern/InputOptionsTrait.php +++ b/src/Concern/InputOptionsTrait.php @@ -96,6 +96,19 @@ public function getStringOpt(string $name, string $default = ''): string return (string)$this->getOpt($name, $default); } + /** + * Get an string option(long/short) value + * + * @param string[] $names eg ['n', 'name'] + * @param string $default + * + * @return string + */ + public function getSameStringOpt(array $names, string $default = ''): string + { + return (string)$this->getSameOpt($names, $default); + } + /** * Get an int option(long/short) value * diff --git a/src/Controller.php b/src/Controller.php index 5c30b47f..6c3980b6 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -18,6 +18,7 @@ use ReflectionException; use ReflectionMethod; use ReflectionObject; +use RuntimeException; use Toolkit\Cli\ColorTag; use Toolkit\PhpUtil\PhpDoc; use Toolkit\StrUtil\Str; @@ -25,6 +26,8 @@ use function array_keys; use function array_merge; use function implode; +use function is_array; +use function is_string; use function ksort; use function lcfirst; use function method_exists; @@ -43,7 +46,15 @@ abstract class Controller extends AbstractHandler implements ControllerInterface { /** - * @var array sub-command aliases + * The sub-command aliases mapping + * + * eg: [ + * alias => command, + * alias1 => command1, + * alias2 => command1, + * ] + * + * @var array */ private static $commandAliases = []; @@ -94,24 +105,35 @@ abstract class Controller extends AbstractHandler implements ControllerInterface private $disabledCommands = []; /** - * define command alias map + * Define command alias mapping. please rewrite it on sub-class. * * @return array */ protected static function commandAliases(): array { - return [ - // alias => command - // 'i' => 'install', - ]; + // Usage: + // - method 1: + // alias => command + // [ + // 'i' => 'install', + // 'ins' => 'install', + // ] + // + // - method 2: + // command => alias[] + // [ + // 'install' => ['i', 'ins'], + // ] + return []; } protected function init(): void { + self::loadCommandAliases(); + $list = $this->disabledCommands(); // save to property $this->disabledCommands = $list ? array_flip($list) : []; - self::$commandAliases = static::commandAliases(); $this->groupOptions = $this->groupOptions(); if (!$this->actionSuffix) { @@ -440,6 +462,32 @@ public function isDisabled(string $name): bool return isset($this->disabledCommands[$name]); } + /** + * load sub-commands aliases from sub-class::commandAliases() + */ + public static function loadCommandAliases(): void + { + $cmdAliases = static::commandAliases(); + if (!$cmdAliases) { + return; + } + + $fmtAliases = []; + foreach ($cmdAliases as $name => $item) { + // $name is command, $item is alias list + // eg: ['command1' => ['alias1', 'alias2']] + if (is_array($item)) { + foreach ($item as $alias) { + $fmtAliases[$alias] = $name; + } + } elseif (is_string($item)) { // $item is command, $name is alias name + $fmtAliases[$name] = $item; + } + } + + self::$commandAliases = $fmtAliases; + } + /************************************************************************** * getter/setter methods **************************************************************************/ @@ -499,7 +547,7 @@ public function getDefaultAction(): string /** * @param string $defaultAction */ - public function setDefaultAction(string $defaultAction) + public function setDefaultAction(string $defaultAction): void { $this->defaultAction = trim($defaultAction, $this->delimiter); } @@ -515,7 +563,7 @@ public function getActionSuffix(): string /** * @param string $actionSuffix */ - public function setActionSuffix(string $actionSuffix) + public function setActionSuffix(string $actionSuffix): void { $this->actionSuffix = $actionSuffix; } @@ -541,7 +589,7 @@ public function setNotFoundCallback(string $notFoundCallback): void */ public function isExecutionAlone(): bool { - throw new \RuntimeException('please call isAttached() instead'); + throw new RuntimeException('please call isAttached() instead'); } /** @@ -551,7 +599,7 @@ public function isExecutionAlone(): bool */ public function setExecutionAlone($executionAlone = true): void { - throw new \RuntimeException('please call setAttached() instead'); + throw new RuntimeException('please call setAttached() instead'); } /** diff --git a/src/Router.php b/src/Router.php index 84c47a6d..3d57cb90 100644 --- a/src/Router.php +++ b/src/Router.php @@ -135,7 +135,7 @@ public function addGroup(string $name, $class = null, array $options = []): Rout // has alias option if (isset($options['aliases'])) { - $this->setAlias($name, $options['aliases'] ?? []); + $this->setAlias($name, $options['aliases'], true); } return $this; @@ -211,7 +211,7 @@ public function addCommand(string $name, $handler = null, array $options = []): // has alias option if (isset($options['aliases'])) { - $this->setAlias($name, $options['aliases'] ?? []); + $this->setAlias($name, $options['aliases'], true); } return $this; diff --git a/src/Traits/NameAliasTrait.php b/src/Traits/NameAliasTrait.php index 75e23d14..894dfa26 100644 --- a/src/Traits/NameAliasTrait.php +++ b/src/Traits/NameAliasTrait.php @@ -8,6 +8,8 @@ namespace Inhere\Console\Traits; +use InvalidArgumentException; + /** * Class NameAliasTrait * @@ -25,12 +27,16 @@ trait NameAliasTrait * * @param string $name * @param string|array $alias + * @param bool $validate */ - public function setAlias(string $name, $alias): void + public function setAlias(string $name, $alias, bool $validate = false): void { foreach ((array)$alias as $aliasName) { if (!isset($this->aliases[$aliasName])) { $this->aliases[$aliasName] = $name; + } elseif ($validate) { + $oldName = $this->aliases[$aliasName]; + throw new InvalidArgumentException("Alias '{$aliasName}' has been registered by '{$oldName}', cannot assign to the '{$name}'"); } } } From 643580b233789bc9a63cc927ccb84f0bea048561 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 8 Jun 2020 12:00:39 +0800 Subject: [PATCH 041/258] fix error if not doc on controller action --- src/Controller.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Controller.php b/src/Controller.php index 6c3980b6..faa8fec3 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -349,7 +349,10 @@ final public function showCommandList(): void continue; } - $desc = PhpDoc::firstLine($m->getDocComment()) ?: $defaultDes; + $desc = $defaultDes; + if ($phpDoc = $m->getDocComment()) { + $desc = PhpDoc::firstLine($phpDoc); + } // is a annotation tag if (strpos($desc, '@') === 0) { From 0b65cccf27b6b79be96246fd145c585a255ef7e3 Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 11 Jun 2020 21:13:40 +0800 Subject: [PATCH 042/258] update dep packages. update some class imports --- .php_cs | 30 ++-- composer.json | 3 +- examples/Controller/HomeController.php | 2 +- src/AbstractApplication.php | 2 +- src/AbstractHandler.php | 2 +- src/BuiltIn/PharController.php | 125 ++++++++++------ src/Component/Formatter/Panel.php | 2 +- src/Component/Formatter/Section.php | 2 +- src/Component/Formatter/Table.php | 2 +- src/Component/Formatter/Title.php | 2 +- src/Component/MessageFormatter.php | 4 +- src/Component/PharCompiler.php | 192 ++++++++++++++----------- src/Concern/InputArgumentsTrait.php | 6 +- src/Controller.php | 4 +- src/Traits/FormatOutputAwareTrait.php | 2 +- src/Traits/RuntimeProfileTrait.php | 2 +- src/Util/ProgressBar.php | 2 +- src/Util/Show.php | 2 +- 18 files changed, 235 insertions(+), 151 deletions(-) diff --git a/.php_cs b/.php_cs index 62707c2f..3bed08d5 100644 --- a/.php_cs +++ b/.php_cs @@ -9,21 +9,31 @@ The file is part of inhere/console EOF; $rules = [ - '@PSR2' => true, - 'array_syntax' => [ + '@PSR2' => true, + 'array_syntax' => [ + 'syntax' => 'short' + ], + 'list_syntax' => [ 'syntax' => 'short' ], 'class_attributes_separation' => true, - 'declare_strict_types' => true, - 'global_namespace_import' => true, - 'header_comment' => [ + 'declare_strict_types' => true, + 'global_namespace_import' => [ + 'import_constants' => true, + 'import_functions' => true, + ], + 'header_comment' => [ 'comment_type' => 'PHPDoc', - 'header' => $header, - 'separate' => 'bottom' + 'header' => $header, + 'separate' => 'bottom' + ], + 'no_unused_imports' => true, + 'return_type_declaration' => [ + 'space_before' => 'none', ], - 'no_unused_imports' => true, - 'single_quote' => true, - 'standardize_not_equals' => true, + 'single_quote' => true, + 'standardize_not_equals' => true, + 'void_return' => true, // add :void for method ]; $finder = PhpCsFixer\Finder::create() diff --git a/composer.json b/composer.json index 6ec36a79..dc12d2d2 100644 --- a/composer.json +++ b/composer.json @@ -24,8 +24,7 @@ "require": { "php": ">7.1.0", "toolkit/cli-utils": "~1.0", - "toolkit/str-utils": "~1.0", - "toolkit/php-utils": "~1.0", + "toolkit/stdlib": "~1.0", "toolkit/sys-utils": "~1.0" }, "require-dev": { diff --git a/examples/Controller/HomeController.php b/examples/Controller/HomeController.php index cf2eab50..29762e5d 100644 --- a/examples/Controller/HomeController.php +++ b/examples/Controller/HomeController.php @@ -12,7 +12,7 @@ use RuntimeException; use Toolkit\Cli\Cli; use Toolkit\Cli\Download; -use Toolkit\PhpUtil\Php; +use Toolkit\Stdlib\Php; use function sleep; use function trigger_error; diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index f73681b5..514eddb5 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -22,7 +22,7 @@ use Inhere\Console\Traits\SimpleEventTrait; use InvalidArgumentException; use Throwable; -use Toolkit\PhpUtil\PhpHelper; +use Toolkit\Stdlib\Helper\PhpHelper; use function array_keys; use function array_merge; use function error_get_last; diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 0db5d55d..0bc3154b 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -24,7 +24,7 @@ use RuntimeException; use Swoole\Coroutine; use Swoole\Event; -use Toolkit\PhpUtil\PhpDoc; +use Toolkit\Stdlib\Util\PhpDoc; use function array_diff_key; use function array_filter; use function array_key_exists; diff --git a/src/BuiltIn/PharController.php b/src/BuiltIn/PharController.php index 3e76e8d8..c015c663 100644 --- a/src/BuiltIn/PharController.php +++ b/src/BuiltIn/PharController.php @@ -8,7 +8,6 @@ namespace Inhere\Console\BuiltIn; -use BadMethodCallException; use Closure; use Exception; use Inhere\Console\Component\PharCompiler; @@ -18,7 +17,7 @@ use Inhere\Console\Util\Helper; use Inhere\Console\Util\Show; use RuntimeException; -use UnexpectedValueException; +use Toolkit\Stdlib\Str; use function basename; use function file_exists; use function is_dir; @@ -58,29 +57,43 @@ protected function init(): void * default is current work-dir.({workDir}) * -c, --config STRING Use the custom config file for build phar(./phar.build.inc) * -o, --output STRING Setting the output file name({defaultPkgName}.phar) - * --fast BOOL Fast build. only add modified files by git status -s - * --refresh BOOL Whether build vendor folder files on phar file exists(False) + * --fast Fast build. only add modified files by git status -s + * --refresh Whether build vendor folder files on phar file exists(False) + * --files STRING Only pack the list files to the exist phar, multi use ',' split + * --no-progress Disable output progress on the runtime * - * @param Input $in - * @param Output $out + * @param Input $input + * @param Output $output * * @return int - * @throws UnexpectedValueException - * @throws RuntimeException - * @throws BadMethodCallException * @throws Exception + * @example + * {fullCommand} Pack current dir to a phar file. + * {fullCommand} --dir vendor/swoft/devtool Pack the specified dir to a phar file. + * + * custom output phar file name + * php -d phar.readonly=0 {binFile} phar:pack -o=mycli.phar + * + * only update the input files: + * php -d phar.readonly=0 {binFile} phar:pack -o=mycli.phar --debug --files app/Command/ServeCommand.php */ - public function packCommand($in, $out): int + public function packCommand($input, $output): int { - $time = microtime(1); - $workDir = $in->getPwd(); + $startAt = microtime(1); + $workDir = $input->getPwd(); - $dir = $in->getOpt('dir') ?: $workDir; + $dir = $input->getOpt('dir') ?: $workDir; $cpr = $this->configCompiler($dir); - $counter = null; - $refresh = $in->boolOpt('refresh'); - $pharFile = $workDir . '/' . $in->sameOpt(['o', 'output'], basename($workDir) . '.phar'); + $refresh = $input->boolOpt('refresh'); + $outFile = $input->sameOpt(['o', 'output'], basename($workDir) . '.phar'); + $pharFile = $workDir . '/' . $outFile; + + Show::aList([ + 'work dir' => $workDir, + 'project' => $dir, + 'phar file' => $pharFile, + ], 'Building Information'); // use fast build if ($this->input->boolOpt('fast')) { @@ -88,40 +101,34 @@ public function packCommand($in, $out): int $this->output->liteNote('Use fast build, will only pack changed or new files(from git status)'); } - $out->liteInfo("Now, will begin building phar package.\n from path: $workDir\n" . " phar file: $pharFile"); + // Manual append some files + if ($files = $input->getStringOpt('files')) { + $cpr->setModifies(Str::explode($files)); + $output->liteInfo("will only pack input files to the exists phar: $outFile"); + } - $out->info('Pack file to Phar: '); $cpr->onError(function ($error) { - $this->output->warning($error); + $this->writeln("$error"); + }); + $cpr->on(PharCompiler::ON_MESSAGE, function ($msg) { + $this->output->colored('> ' . $msg); }); - if ($in->getOpt('debug')) { - $cpr->onAdd(function ($path) { - $this->output->write(" + $path"); - }); - } else { - $counter = Show::counterTxt('Handling ...', 'Done.'); - $cpr->onAdd(function () use ($counter) { - $counter->send(1); - }); - } + $output->colored('Collect Pack files', 'comment'); + $this->outputProgress($cpr, $input); // packing ... $cpr->pack($pharFile, $refresh); - // end - if ($counter) { - $counter->send(-1); - } - - $out->write([ - PHP_EOL . 'Phar build completed!', - " - Phar file: $pharFile", - ' - Phar size: ' . round(filesize($pharFile) / 1024 / 1024, 2) . ' Mb', - ' - Pack Time: ' . round(microtime(1) - $time, 3) . ' s', + $info = [ + PHP_EOL . 'Phar Build Completed!', ' - Pack File: ' . $cpr->getCounter(), - ' - Commit ID: ' . $cpr->getVersion(), - ]); + ' - Pack Time: ' . round(microtime(true) - $startAt, 3) . ' s', + ' - Phar Size: ' . round(filesize($pharFile) / 1024 / 1024, 2) . ' Mb', + " - Phar File: $pharFile", + ' - Commit ID: ' . $cpr->getLastCommit(), + ]; + $output->writeln($info); return 0; } @@ -147,14 +154,48 @@ protected function configCompiler(string $dir): PharCompiler $configFile = $this->input->getSameOpt(['c', 'config']) ?: $dir . '/phar.build.inc'; if ($configFile && is_file($configFile)) { + /** @noinspection PhpIncludeInspection */ require $configFile; - return $compiler->in($dir); } throw new RuntimeException("The phar build config file not exists! File: $configFile"); } + /** + * @param PharCompiler $cpr + * @param Input $input + * + * @return void + */ + private function outputProgress(PharCompiler $cpr,Input $input): void + { + if ($input->getBoolOpt('no-progress')) { + return; + } + + if ($input->getOpt('debug')) { + // $output->info('Pack file to Phar ... ...'); + $cpr->onAdd(function (string $path) { + $this->writeln(" + $path"); + }); + + $cpr->on('skip', function (string $path, bool $isFile) { + $mark = $isFile ? '[F]' : '[D]'; + $this->writeln(" - $path $mark"); + }); + } else { + $counter = Show::counterTxt('Collecting ...', 'Done.'); + $cpr->onAdd(function () use ($counter) { + $counter->send(1); + }); + $cpr->on(PharCompiler::ON_COLLECTED, function () use ($counter) { + $counter->send(-1); + $this->writeln(''); + }); + } + } + /** * @param Closure $compilerConfiger */ diff --git a/src/Component/Formatter/Panel.php b/src/Component/Formatter/Panel.php index affc361b..3086e066 100644 --- a/src/Component/Formatter/Panel.php +++ b/src/Component/Formatter/Panel.php @@ -11,7 +11,7 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; use Inhere\Console\Util\FormatUtil; -use Toolkit\StrUtil\StrBuffer; +use Toolkit\Stdlib\Str\StrBuffer; use function array_filter; use function array_merge; use function ceil; diff --git a/src/Component/Formatter/Section.php b/src/Component/Formatter/Section.php index cdcd6c22..4ee1d77e 100644 --- a/src/Component/Formatter/Section.php +++ b/src/Component/Formatter/Section.php @@ -5,7 +5,7 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; use Inhere\Console\Util\FormatUtil; -use Toolkit\StrUtil\Str; +use Toolkit\Stdlib\Str; use function array_merge; use function ceil; use function implode; diff --git a/src/Component/Formatter/Table.php b/src/Component/Formatter/Table.php index 8b3428eb..549cc9fb 100644 --- a/src/Component/Formatter/Table.php +++ b/src/Component/Formatter/Table.php @@ -11,7 +11,7 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; use Toolkit\Cli\ColorTag; -use Toolkit\StrUtil\StrBuffer; +use Toolkit\Stdlib\Str\StrBuffer; use function array_keys; use function array_merge; use function array_sum; diff --git a/src/Component/Formatter/Title.php b/src/Component/Formatter/Title.php index 07dbd42a..5f478b83 100644 --- a/src/Component/Formatter/Title.php +++ b/src/Component/Formatter/Title.php @@ -4,7 +4,7 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; -use Toolkit\StrUtil\Str; +use Toolkit\Stdlib\Str; use Toolkit\Sys\Sys; use function array_merge; use function ceil; diff --git a/src/Component/MessageFormatter.php b/src/Component/MessageFormatter.php index a2f8f89f..392ed5ff 100644 --- a/src/Component/MessageFormatter.php +++ b/src/Component/MessageFormatter.php @@ -11,7 +11,7 @@ use Inhere\Console\Console; use Inhere\Console\Contract\FormatterInterface; use RuntimeException; -use Toolkit\PhpUtil\PhpHelper; +use Toolkit\Stdlib\Obj\ObjectHelper; /** * Class Formatter - message formatter @@ -49,7 +49,7 @@ public static function create(array $config = []): self */ public function __construct(array $config = []) { - PhpHelper::initObject($this, $config); + ObjectHelper::init($this, $config); $this->config = $config; } diff --git a/src/Component/PharCompiler.php b/src/Component/PharCompiler.php index b416c06d..9c6c016b 100644 --- a/src/Component/PharCompiler.php +++ b/src/Component/PharCompiler.php @@ -1,6 +1,6 @@ 1, @@ -172,6 +176,8 @@ class PharCompiler */ private $versionFile = ''; + private $versionFileContent = ''; + // -------------------- internal properties -------------------- /** @var int */ @@ -197,6 +203,11 @@ class PharCompiler */ private $modifies; + /** + * @var SplQueue + */ + private $fileQueue; + /** * @param string $pharFile * @param string $extractTo @@ -240,7 +251,8 @@ public function __construct(string $basePath) { self::checkEnv(); - $this->basePath = realpath($basePath); + $this->basePath = realpath($basePath); + $this->fileQueue = new SplQueue(); if (!is_dir($this->basePath)) { throw new RuntimeException("The inputted path is not exists. PATH: {$this->basePath}"); @@ -399,7 +411,7 @@ public function setModifies($modifies): self * @throws InvalidArgumentException * @throws Exception */ - public function pack(string $pharFile, $refresh = true): string + public function pack(string $pharFile, bool $refresh = true): string { if (!$this->directories) { throw new RuntimeException("Please setting the 'directories' want building directories by 'in()'"); @@ -430,36 +442,25 @@ public function pack(string $pharFile, $refresh = true): string $phar->setSignatureAlgorithm($this->selectSignatureType()); } - $basePath = $this->basePath; + // Collect files + $this->collectFiles($exists, $refresh); + + // Begin packing + $this->fire(self::ON_COLLECTED, $this->counter); $phar->startBuffering(); - // Only build modifies - if (!$refresh && $exists && $this->modifies) { - foreach ($this->modifies as $file) { - if ('/' === $file[0] || is_file($file = $basePath . '/' . $file)) { - $this->packFile($phar, new SplFileInfo($file)); - } - } - } else { - // Collect files in there are dirs. - foreach ($this->directories as $directory) { - foreach ($this->findFiles($directory) as $file) { - $this->packFile($phar, $file); - } - } - } - - // Add special files - foreach ($this->files as $filename) { - if ('/' === $filename[0] || is_file($filename = $basePath . '/' . $filename)) { - $this->packFile($phar, new SplFileInfo($filename)); - } - } + $this->fire(self::ON_MESSAGE, 'Files collect complete, begin add file to Phar'); + $phar->buildFromIterator($this->fileQueue, $this->basePath); // Add index files $this->packIndexFile($phar); - // Stubs + // Add version file + if ($content = $this->versionFileContent) { + $phar->addFromString($this->versionFile, $content); + } + + // Add Stubs // $phar->setDefaultStub($this->cliIndex, $this->webIndex)); $phar->setStub($this->createStub()); @@ -477,6 +478,7 @@ public function pack(string $pharFile, $refresh = true): string // 'Date' => date('Y-m-d') // ); // $phar->setMetadata($metaData); + $this->fire(self::ON_MESSAGE, 'Write requests to the Phar archive, save changes to disk'); $phar->stopBuffering(); unset($phar); @@ -490,6 +492,68 @@ public function pack(string $pharFile, $refresh = true): string return $pharFile; } + /** + * @param bool $exists + * @param bool $refresh + */ + protected function collectFiles(bool $exists, bool $refresh): void + { + $basePath = $this->basePath; + + // Only build modifies + if (!$refresh && $exists && $this->modifies) { + foreach ($this->modifies as $file) { + if ('/' === $file[0] || is_file($file = $basePath . '/' . $file)) { + $this->collectFileInfo(new SplFileInfo($file)); + } + } + } else { + // Collect files in there are dirs. + foreach ($this->directories as $directory) { + foreach ($this->findFiles($directory) as $fileInfo) { + $this->collectFileInfo($fileInfo); + } + } + } + + // Add special files + foreach ($this->files as $filename) { + if ('/' === $filename[0] || is_file($filename = $basePath . '/' . $filename)) { + // $this->packFile($phar, new SplFileInfo($filename)); + $this->collectFileInfo(new SplFileInfo($filename)); + } + } + } + + /** + * @param SplFileInfo $file + */ + protected function collectFileInfo(SplFileInfo $file): void + { + $filePath = $file->getPathname(); + + // Skip not exist file + if (!file_exists($filePath)) { + $this->fire(self::ON_ERROR, "File $filePath is not exists!"); + return; + } + + $this->counter++; + $relativePath = $this->getRelativeFilePath($file); + + $this->fire(self::ON_ADD, $relativePath, $this->counter); + + // have version file + if ($relativePath === $this->versionFile) { + $content = file_get_contents($filePath); + // save content + $this->versionFileContent = $this->addVersionInfo($content); + return; + } + + $this->fileQueue->push($file); + } + /** * find changed or new created files by git status. * @throws RuntimeException @@ -529,7 +593,7 @@ public function findChangedByGit() */ protected function findFiles(string $directory) { - return Dir::filterIterator( + return Helper::directoryIterator( $directory, $this->getIteratorFilter(), FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS @@ -537,56 +601,16 @@ protected function findFiles(string $directory) } /** - * Add a file to the Phar. - * @param Phar $phar - * @param SplFileInfo $file + * @param string $content + * + * @return string */ - private function packFile(Phar $phar, SplFileInfo $file): void + private function addVersionInfo(string $content): string { - $filePath = $file->getPathname(); - - // skip error - if (!file_exists($filePath)) { - $this->fire(self::ON_ERROR, "File $filePath is not exists!"); - return; - } - - $strip = $this->stripComments; - $path = $this->getRelativeFilePath($file); - - $this->counter++; - $this->fire(self::ON_ADD, $path, $this->counter); - - // clear php file comments - if ($strip && strpos($path, '.php')) { - $filter = $this->stripFilter; - - if (!$filter || $filter($file)) { - $content = $this->stripWhitespace(file_get_contents($filePath)); - - // add content to phar - $phar->addFromString( - $path, - $this->addVersionInfo($content) . "\n// added by phar pack" - ); - return; - } - } - - // have versionFile - if ($path === $this->versionFile) { - $content = file_get_contents($filePath); - - $phar->addFromString($path, $this->addVersionInfo($content)); - return; + if (!$this->collectVersionInfo) { + return $content; } - // add file to phar - $phar->addFile($filePath, $path); - } - - private function addVersionInfo(string $content): string - { return str_replace([ '{@package_last_commit}', '{@package_last_version}', @@ -684,7 +708,7 @@ private function getIteratorFilter(): Closure if (!$this->fileFilter) { $this->fileFilter = function (SplFileInfo $file) { $name = $file->getFilename(); - $path = FSHelper::formatPath($file->getPathname()); + $path = $file->getPathname(); // Skip hidden files and directories. if (strpos($name, '.') === 0) { @@ -1008,4 +1032,12 @@ public function getPharFile(): string { return $this->pharFile; } + + /** + * @return int + */ + public function getFileCount(): int + { + return $this->fileQueue->count(); + } } diff --git a/src/Concern/InputArgumentsTrait.php b/src/Concern/InputArgumentsTrait.php index 9d08e54e..2d8889e0 100644 --- a/src/Concern/InputArgumentsTrait.php +++ b/src/Concern/InputArgumentsTrait.php @@ -36,14 +36,16 @@ trait InputArgumentsTrait /** * @param string $name * @param int $index + * @return self|mixed */ - public function bindArgument(string $name, int $index): void + public function bindArgument(string $name, int $index) { $this->binds[$name] = $index; + return $this; } /** - * @param array $map + * @param array $map [ argName => index, ] * @param bool $replace */ public function bindArguments(array $map, bool $replace = false): void diff --git a/src/Controller.php b/src/Controller.php index faa8fec3..166e1d9a 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -20,8 +20,8 @@ use ReflectionObject; use RuntimeException; use Toolkit\Cli\ColorTag; -use Toolkit\PhpUtil\PhpDoc; -use Toolkit\StrUtil\Str; +use Toolkit\Stdlib\Util\PhpDoc; +use Toolkit\Stdlib\Str; use function array_flip; use function array_keys; use function array_merge; diff --git a/src/Traits/FormatOutputAwareTrait.php b/src/Traits/FormatOutputAwareTrait.php index a755c1f4..f6ebf6ec 100644 --- a/src/Traits/FormatOutputAwareTrait.php +++ b/src/Traits/FormatOutputAwareTrait.php @@ -22,7 +22,7 @@ use Inhere\Console\Util\Interact; use Inhere\Console\Util\Show; use LogicException; -use Toolkit\PhpUtil\Php; +use Toolkit\Stdlib\Php; use function array_merge; use function json_encode; use function method_exists; diff --git a/src/Traits/RuntimeProfileTrait.php b/src/Traits/RuntimeProfileTrait.php index e3a717e7..68d8e563 100644 --- a/src/Traits/RuntimeProfileTrait.php +++ b/src/Traits/RuntimeProfileTrait.php @@ -9,7 +9,7 @@ namespace Inhere\Console\Traits; use InvalidArgumentException; -use Toolkit\PhpUtil\PhpHelper; +use Toolkit\Stdlib\Helper\PhpHelper; use function array_pop; use function explode; use function in_array; diff --git a/src/Util/ProgressBar.php b/src/Util/ProgressBar.php index ffb1eba3..fdb63ce3 100644 --- a/src/Util/ProgressBar.php +++ b/src/Util/ProgressBar.php @@ -13,7 +13,7 @@ use Inhere\Console\Contract\OutputInterface; use LogicException; use RuntimeException; -use Toolkit\StrUtil\Str; +use Toolkit\Stdlib\Str; use function max; /** diff --git a/src/Util/Show.php b/src/Util/Show.php index 2fd28dcb..7acc9f46 100644 --- a/src/Util/Show.php +++ b/src/Util/Show.php @@ -21,7 +21,7 @@ use LogicException; use Toolkit\Cli\Cli; use Toolkit\Cli\ColorTag; -use Toolkit\StrUtil\Str; +use Toolkit\Stdlib\Str; use Toolkit\Sys\Sys; use function array_keys; use function array_values; From a47bc1d82411006cbd4924b7480fbd4d2e4c466c Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 13 Jun 2020 11:38:01 +0800 Subject: [PATCH 043/258] update some for phar controller --- src/AbstractHandler.php | 4 ++- src/BuiltIn/PharController.php | 41 ++++++++++++++++++--------- src/Component/PharCompiler.php | 2 ++ src/Traits/UserInteractAwareTrait.php | 13 +++++++-- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 0bc3154b..7fe89cfe 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -208,6 +208,7 @@ protected function createDefinition(): InputDefinition protected function annotationVars(): array { $fullCmd = $this->input->getFullCommand(); + $binFile = $this->input->getScript(); // bin/app $binName = $this->input->getScriptName(); $command = $this->input->getCommand(); @@ -216,7 +217,8 @@ protected function annotationVars(): array 'name' => self::getName(), 'group' => self::getName(), 'workDir' => $this->input->getPwd(), - 'script' => $this->input->getScript(), // bin/app + 'script' => $binFile, // bin/app + 'binFile' => $binFile, // bin/app 'binName' => $binName, // app 'scriptName' => $binName, // app 'command' => $command, // demo OR home:test diff --git a/src/BuiltIn/PharController.php b/src/BuiltIn/PharController.php index c015c663..349f87dc 100644 --- a/src/BuiltIn/PharController.php +++ b/src/BuiltIn/PharController.php @@ -41,11 +41,26 @@ class PharController extends Controller */ private $compilerConfiger; - protected function init(): void + /** + * @var string + */ + private $defPkgName; + + protected static function commandAliases(): array + { + return [ + 'pack' => ['build'] + ]; + } + + /** + * @param Input $input + */ + protected function packConfigure(Input $input): void { - parent::init(); + $this->defPkgName = trim(basename($input->getPwd()), '.') . PharCompiler::FILE_EXT; - $this->addCommentsVar('defaultPkgName', basename($this->input->getPwd())); + $this->addCommentsVar('defaultPkgName', $this->defPkgName); } /** @@ -54,9 +69,9 @@ protected function init(): void * * @options * -d, --dir STRING Setting the project directory for packing. - * default is current work-dir.({workDir}) - * -c, --config STRING Use the custom config file for build phar(./phar.build.inc) - * -o, --output STRING Setting the output file name({defaultPkgName}.phar) + * default is current work-dir(default: {workDir}) + * -c, --config STRING Use the custom config file for build phar(default: ./phar.build.inc) + * -o, --output STRING Setting the output file name({defaultPkgName}) * --fast Fast build. only add modified files by git status -s * --refresh Whether build vendor folder files on phar file exists(False) * --files STRING Only pack the list files to the exist phar, multi use ',' split @@ -68,25 +83,25 @@ protected function init(): void * @return int * @throws Exception * @example - * {fullCommand} Pack current dir to a phar file. - * {fullCommand} --dir vendor/swoft/devtool Pack the specified dir to a phar file. + * {fullCommand} Pack current dir to a phar file. + * {fullCommand} --dir vendor/swoft/devtool Pack the specified dir to a phar file. * - * custom output phar file name + * custom output phar file name * php -d phar.readonly=0 {binFile} phar:pack -o=mycli.phar * - * only update the input files: + * only update the input files: * php -d phar.readonly=0 {binFile} phar:pack -o=mycli.phar --debug --files app/Command/ServeCommand.php */ public function packCommand($input, $output): int { - $startAt = microtime(1); + $startAt = microtime(true); $workDir = $input->getPwd(); $dir = $input->getOpt('dir') ?: $workDir; $cpr = $this->configCompiler($dir); $refresh = $input->boolOpt('refresh'); - $outFile = $input->sameOpt(['o', 'output'], basename($workDir) . '.phar'); + $outFile = $input->getSameStringOpt(['o', 'output'], $this->defPkgName); $pharFile = $workDir . '/' . $outFile; Show::aList([ @@ -186,7 +201,7 @@ private function outputProgress(PharCompiler $cpr,Input $input): void }); } else { $counter = Show::counterTxt('Collecting ...', 'Done.'); - $cpr->onAdd(function () use ($counter) { + $cpr->onAdd(static function () use ($counter) { $counter->send(1); }); $cpr->on(PharCompiler::ON_COLLECTED, function () use ($counter) { diff --git a/src/Component/PharCompiler.php b/src/Component/PharCompiler.php index 9c6c016b..02549385 100644 --- a/src/Component/PharCompiler.php +++ b/src/Component/PharCompiler.php @@ -66,6 +66,8 @@ class PharCompiler public const ON_COLLECTED = 'collected'; + public const FILE_EXT = '.phar'; + /** @var array */ private static $supportedSignatureTypes = [ Phar::SHA512 => 1, diff --git a/src/Traits/UserInteractAwareTrait.php b/src/Traits/UserInteractAwareTrait.php index 3530eeff..4f6c9eb8 100644 --- a/src/Traits/UserInteractAwareTrait.php +++ b/src/Traits/UserInteractAwareTrait.php @@ -25,8 +25,6 @@ * @method array checkbox(string $description, $options, $default = null, $allowExit = true) * @method array multiSelect(string $description, $options, $default = null, $allowExit = true) * - * @method unConfirm(string $question, bool $default = true): bool - * * @method string askHiddenInput(string $prompt = 'Enter Password:') * @method string promptSilent(string $prompt = 'Enter Password:') * @method string askPassword(string $prompt = 'Enter Password:') @@ -71,6 +69,17 @@ public function confirm(string $question, bool $default = true): bool return Interact::confirm($question, $default); } + /** + * @param string $question + * @param bool $default + * + * @return bool + */ + public function unConfirm(string $question, bool $default = true): bool + { + return Interact::unConfirm($question, $default); + } + /** * @param string $question * @param string $default From 158755abb9dba8b435ce5c50f3849945cf12a309 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 15 Jun 2020 10:47:50 +0800 Subject: [PATCH 044/258] move some base cli tool to toolkit/cli --- src/AbstractApplication.php | 2 +- src/Component/ErrorHandler.php | 2 +- src/Component/Style/Style.php | 1 + src/Console.php | 423 +------------------------- src/IO/Output.php | 2 +- src/Traits/ApplicationHelpTrait.php | 2 +- src/Traits/FormatOutputAwareTrait.php | 2 +- src/Util/Show.php | 2 +- 8 files changed, 14 insertions(+), 422 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 514eddb5..da258655 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -10,7 +10,6 @@ use ErrorException; use Inhere\Console\Component\ErrorHandler; -use Inhere\Console\Component\Style\Style; use Inhere\Console\Contract\ApplicationInterface; use Inhere\Console\Contract\ErrorHandlerInterface; use Inhere\Console\Contract\InputInterface; @@ -22,6 +21,7 @@ use Inhere\Console\Traits\SimpleEventTrait; use InvalidArgumentException; use Throwable; +use Toolkit\Cli\Style; use Toolkit\Stdlib\Helper\PhpHelper; use function array_keys; use function array_merge; diff --git a/src/Component/ErrorHandler.php b/src/Component/ErrorHandler.php index 7c62c4c5..282ffe29 100644 --- a/src/Component/ErrorHandler.php +++ b/src/Component/ErrorHandler.php @@ -12,7 +12,7 @@ use Inhere\Console\Contract\ErrorHandlerInterface; use Inhere\Console\Exception\PromptException; use Throwable; -use Toolkit\Cli\Highlighter; +use Toolkit\Cli\Util\Highlighter; use function file_get_contents; use function get_class; use function sprintf; diff --git a/src/Component/Style/Style.php b/src/Component/Style/Style.php index 9cc441a5..318efc41 100644 --- a/src/Component/Style/Style.php +++ b/src/Component/Style/Style.php @@ -28,6 +28,7 @@ * * @package Inhere\Console\Component\Style * @link https://github.com/ventoviro/windwalker-IO + * @deprecated please use Toolkit\Cli\Style instead. * * @method string info(string $message) * @method string comment(string $message) diff --git a/src/Console.php b/src/Console.php index fb63ad2f..4c07d279 100644 --- a/src/Console.php +++ b/src/Console.php @@ -2,19 +2,12 @@ namespace Inhere\Console; -use Inhere\Console\Component\Style\Style; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; +use Toolkit\Cli\Cli; use Toolkit\Cli\ColorTag; -use function array_merge; use function date; -use function fflush; -use function fgets; -use function fgetss; -use function file_get_contents; -use function fwrite; use function implode; -use function is_array; use function is_numeric; use function json_encode; use function sprintf; @@ -23,25 +16,22 @@ use const JSON_PRETTY_PRINT; use const JSON_UNESCAPED_SLASHES; use const PHP_EOL; -use const STDERR; -use const STDIN; -use const STDOUT; /** * Class Console * * @package Inhere\Console */ -class Console +class Console extends Cli { // constants for error level 0 - 4. you can setting by '--debug LEVEL' public const VERB_QUIET = 0; public const VERB_ERROR = 1; // default reporting on error - public const VERB_WARN = 2; + public const VERB_WARN = 2; - public const VERB_INFO = 3; + public const VERB_INFO = 3; public const VERB_DEBUG = 4; @@ -71,12 +61,6 @@ class Console */ private static $app; - /** @var string */ - private static $buffer = ''; - - /** @var bool */ - private static $buffering = false; - /** * @return Application */ @@ -108,188 +92,6 @@ public static function newApp( return new Application($config, $input, $output); } - /** - * @return Style - */ - public static function style(): Style - { - return Style::instance(); - } - - /*********************************************************************************** - * Output message - ***********************************************************************************/ - - /** - * Format and write message to terminal. like printf() - * - * @param string $format - * @param mixed ...$args - * - * @return int - */ - public static function writef(string $format, ...$args): int - { - return self::write(sprintf($format, ...$args)); - } - - /** - * Format and write message to terminal. like printf() - * - * @param string $format - * @param mixed ...$args - * - * @return int - */ - public static function printf(string $format, ...$args): int - { - return self::write(sprintf($format, ...$args)); - } - - /** - * Write raw data to stdout, will disable color render. - * - * @param string|array $message - * @param bool $nl - * @param bool|int $quit - * @param array $opts - * - * @return int - */ - public static function writeRaw($message, $nl = true, $quit = false, array $opts = []): int - { - $opts['color'] = false; - return self::write($message, $nl, $quit, $opts); - } - - /** - * Write data to stdout with newline. - * - * @param string|array $message - * @param array $opts - * @param bool|int $quit - * - * @return int - */ - public static function writeln($message, $quit = false, array $opts = []): int - { - return self::write($message, true, $quit, $opts); - } - - /** - * Write data to stdout with newline. - * - * @param string|array $message - * @param array $opts - * @param bool|int $quit - * - * @return int - */ - public static function println($message, $quit = false, array $opts = []): int - { - return self::write($message, true, $quit, $opts); - } - - /** - * Write message to stdout. - * - * @param string|array $message - * @param array $opts - * @param bool|int $quit - * - * @return int - */ - public static function print($message, $quit = false, array $opts = []): int - { - return self::write($message, false, $quit, $opts); - } - - /** - * Write a message to standard output stream. - * - * @param string|array $messages Output message - * @param boolean $nl True 会添加换行符, False 原样输出,不添加换行符 - * @param int|boolean $quit If is int, setting it is exit code. - * 'True' translate as code 0 and exit, 'False' will not exit. - * @param array $opts Some options for write - * [ - * 'color' => bool, // whether render color, default is: True. - * 'stream' => resource, // the stream resource, default is: STDOUT - * 'flush' => bool, // flush the stream data, default is: True - * ] - * - * @return int - */ - public static function write($messages, $nl = true, $quit = false, array $opts = []): int - { - if (is_array($messages)) { - $messages = implode($nl ? PHP_EOL : '', $messages); - } - - $messages = (string)$messages; - - if (!isset($opts['color']) || $opts['color']) { - $messages = Style::instance()->render($messages); - } else { - $messages = Style::stripColor($messages); - } - - // if open buffering - if (self::isBuffering()) { - self::$buffer .= $messages . ($nl ? PHP_EOL : ''); - - if (!$quit) { - return 0; - } - - $messages = self::$buffer; - // clear buffer - self::$buffer = ''; - } else { - $messages .= $nl ? PHP_EOL : ''; - } - - fwrite($stream = $opts['stream'] ?? STDOUT, $messages); - - if (!isset($opts['flush']) || $opts['flush']) { - fflush($stream); - } - - // if will quit. - if ($quit !== false) { - $code = true === $quit ? 0 : (int)$quit; - exit($code); - } - - return 0; - } - - /** - * Logs data to stdout - * - * @param string|array $text - * @param bool $nl - * @param bool|int $quit - */ - public static function stdout($text, $nl = true, $quit = false): void - { - self::write($text, $nl, $quit); - } - - /** - * Logs data to stderr - * - * @param string|array $text - * @param bool $nl - * @param bool|int $quit - */ - public static function stderr($text, $nl = true, $quit = -200): void - { - self::write($text, $nl, $quit, [ - 'stream' => STDERR, - ]); - } - /** * @param int $level * @param string $format @@ -335,220 +137,9 @@ public static function log(string $msg, array $data = [], int $level = self::VER } } - $optString = $userOpts ? ' ' . implode(' ', $userOpts) : ''; - - self::write(sprintf( - '%s [%s]%s %s %s', - date('Y/m/d H:i:s'), - $taggedName, - $optString, - trim($msg), - $data ? PHP_EOL . json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) : '' - )); - } - - /*********************************************************************************** - * Read message - ***********************************************************************************/ - - /** - * Read message from STDIN - * - * @param mixed $message - * @param bool $nl - * @param array $opts - * - * @return string - */ - public static function read($message = null, bool $nl = false, array $opts = []): string - { - if ($message) { - self::write($message, $nl); - } - - $opts = array_merge([ - 'length' => 1024, - 'stream' => STDIN, - ], $opts); - - return file_get_contents($opts['stream'], $opts['length']); - } - - /** - * Gets line from file pointer - * - * @param mixed $message - * @param bool $nl - * @param array $opts - * [ - * 'stream' => \STDIN - * ] - * - * @return string - */ - public static function readln($message = null, $nl = false, array $opts = []): string - { - if ($message) { - self::write($message, $nl); - } - - $opts = array_merge([ - 'length' => 1024, - 'stream' => STDIN, - ], $opts); - - return trim(fgets($opts['stream'], $opts['length'])); - } + $optString = $userOpts ? ' ' . implode(' ', $userOpts) : ''; + $dataString = $data ? PHP_EOL . json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) : ''; - /** - * Read input information - * - * @param mixed $message 若不为空,则先输出文本 - * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 - * - * @return string - */ - public static function readRow($message = null, $nl = false): string - { - return self::readln($message, $nl); - } - - /** - * Gets line from file pointer and strip HTML tags - * - * @param mixed $message - * @param bool $nl - * @param array $opts - * - * @return string - */ - public static function readSafe($message = null, bool $nl = false, array $opts = []): string - { - if ($message) { - self::write($message, $nl); - } - - $opts = array_merge([ - 'length' => 1024, - 'stream' => STDIN, - 'allowTags' => null, - ], $opts); - - return trim(fgetss($opts['stream'], $opts['length'], $opts['allowTags'])); - } - - /** - * Gets first character from file pointer - * - * @param string $message - * @param bool $nl - * - * @return string - */ - public static function readChar(string $message = '', bool $nl = false): string - { - $line = self::readln($message, $nl); - - return $line !== '' ? $line[0] : ''; - } - - /** - * Read input first char - * - * @param string $message - * @param bool $nl - * - * @return string - */ - public static function readFirst(string $message = '', bool $nl = false): string - { - return self::readChar($message, $nl); - } - - /*********************************************************************************** - * Output buffer - ***********************************************************************************/ - - /** - * @return bool - */ - public static function isBuffering(): bool - { - return self::$buffering; - } - - /** - * @return string - */ - public static function getBuffer(): string - { - return self::$buffer; - } - - /** - * @param string $buffer - */ - public static function setBuffer(string $buffer): void - { - self::$buffer = $buffer; - } - - /** - * Start buffering - */ - public static function startBuffer(): void - { - self::$buffering = true; - } - - /** - * Clear buffering - */ - public static function clearBuffer(): void - { - self::$buffer = ''; - } - - /** - * Stop buffering - * - * @param bool $flush Whether flush buffer to output stream - * @param bool $nl Default is False, because the last write() have been added "\n" - * @param bool $quit - * @param array $opts - * - * @return string If flush = False, will return all buffer text. - * @see write() - */ - public static function stopBuffer($flush = true, $nl = false, $quit = false, array $opts = []): string - { - self::$buffering = false; - - if ($flush && self::$buffer) { - // all text have been rendered by Style::render() in every write(); - $opts['color'] = false; - - // flush to stream - self::write(self::$buffer, $nl, $quit, $opts); - - // clear buffer - self::$buffer = ''; - } - - return self::$buffer; - } - - /** - * Stop buffering and flush buffer text - * - * @param bool $nl - * @param bool $quit - * @param array $opts - * - * @see write() - */ - public static function flushBuffer($nl = false, $quit = false, array $opts = []): void - { - self::stopBuffer(true, $nl, $quit, $opts); + self::writef('%s [%s]%s %s %s', date('Y/m/d H:i:s'), $taggedName, $optString, trim($msg), $dataString); } } diff --git a/src/IO/Output.php b/src/IO/Output.php index 5ae5e178..b7e7fc9a 100644 --- a/src/IO/Output.php +++ b/src/IO/Output.php @@ -8,7 +8,7 @@ namespace Inhere\Console\IO; -use Inhere\Console\Component\Style\Style; +use Toolkit\Cli\Style; use Inhere\Console\Console; use Inhere\Console\Traits\FormatOutputAwareTrait; use Toolkit\Cli\Cli; diff --git a/src/Traits/ApplicationHelpTrait.php b/src/Traits/ApplicationHelpTrait.php index 9c113c3a..0280b3f2 100644 --- a/src/Traits/ApplicationHelpTrait.php +++ b/src/Traits/ApplicationHelpTrait.php @@ -9,7 +9,7 @@ namespace Inhere\Console\Traits; use Inhere\Console\AbstractHandler; -use Inhere\Console\Component\Style\Style; +use Toolkit\Cli\Style; use Inhere\Console\Console; use Inhere\Console\Contract\CommandInterface; use Inhere\Console\IO\Input; diff --git a/src/Traits/FormatOutputAwareTrait.php b/src/Traits/FormatOutputAwareTrait.php index f6ebf6ec..b17518ea 100644 --- a/src/Traits/FormatOutputAwareTrait.php +++ b/src/Traits/FormatOutputAwareTrait.php @@ -17,7 +17,7 @@ use Inhere\Console\Component\Formatter\SingleList; use Inhere\Console\Component\Formatter\Table; use Inhere\Console\Component\Formatter\Title; -use Inhere\Console\Component\Style\Style; +use Toolkit\Cli\Style; use Inhere\Console\Console; use Inhere\Console\Util\Interact; use Inhere\Console\Util\Show; diff --git a/src/Util/Show.php b/src/Util/Show.php index 7acc9f46..e49ce98b 100644 --- a/src/Util/Show.php +++ b/src/Util/Show.php @@ -16,7 +16,7 @@ use Inhere\Console\Component\Progress\DynamicText; use Inhere\Console\Component\Progress\SimpleBar; use Inhere\Console\Component\Progress\SimpleTextBar; -use Inhere\Console\Component\Style\Style; +use Toolkit\Cli\Style; use Inhere\Console\Console; use LogicException; use Toolkit\Cli\Cli; From f9f00b3a5050e64a1f997515d804ec63b3f58929 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 19 Jun 2020 01:04:58 +0800 Subject: [PATCH 045/258] fix some strict error for formatter --- src/Component/Formatter/Padding.php | 4 +- src/Component/Formatter/Panel.php | 21 ++++--- src/Component/Formatter/Section.php | 7 +-- src/Component/Formatter/Table.php | 12 ++-- src/Component/Formatter/Title.php | 3 +- src/IO/Input.php | 15 +++-- src/IO/Input/ArrayInput.php | 4 +- src/IO/Output.php | 15 +++-- src/IO/Output/BufferOutput.php | 23 +++----- src/Util/ProgressBar.php | 44 +++++++------- test/TempStream.php | 91 +++++++++++++++++++++++++++++ 11 files changed, 168 insertions(+), 71 deletions(-) create mode 100644 test/TempStream.php diff --git a/src/Component/Formatter/Padding.php b/src/Component/Formatter/Padding.php index eefa363f..57d5f869 100644 --- a/src/Component/Formatter/Padding.php +++ b/src/Component/Formatter/Padding.php @@ -6,8 +6,8 @@ use Inhere\Console\Console; use Inhere\Console\Util\Helper; use Toolkit\Cli\ColorTag; +use Toolkit\Stdlib\Str; use function array_merge; -use function str_pad; use function trim; use function ucfirst; @@ -50,7 +50,7 @@ public static function show(array $data, string $title = '', array $opts = []): foreach ($data as $label => $value) { $value = ColorTag::wrap((string)$value, $opts['valueStyle']); - $string .= $opts['indent'] . str_pad((string)$label, $paddingLen, $opts['char']) . " $value\n"; + $string .= $opts['indent'] . Str::pad($label, $paddingLen, $opts['char']) . " $value\n"; } Console::write(trim($string)); diff --git a/src/Component/Formatter/Panel.php b/src/Component/Formatter/Panel.php index 3086e066..01eeee3c 100644 --- a/src/Component/Formatter/Panel.php +++ b/src/Component/Formatter/Panel.php @@ -11,6 +11,7 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; use Inhere\Console\Util\FormatUtil; +use Toolkit\Stdlib\Str; use Toolkit\Stdlib\Str\StrBuffer; use function array_filter; use function array_merge; @@ -20,7 +21,6 @@ use function is_numeric; use function mb_strlen; use function rtrim; -use function str_pad; use function strip_tags; use function trim; use function ucwords; @@ -117,7 +117,8 @@ public static function show($data, string $title = 'Information Panel', array $o foreach ($data as $label => $value) { // label exists if (!is_numeric($label)) { - $width = mb_strlen($label, 'UTF-8'); + $width = Str::len2($label, 'UTF-8'); + $labelMaxWidth = $width > $labelMaxWidth ? $width : $labelMaxWidth; } @@ -158,16 +159,17 @@ public static function show($data, string $title = 'Information Panel', array $o // output title if ($title) { - $title = ucwords($title); + $title = ucwords($title); + $titleLength = mb_strlen($title, 'UTF-8'); $panelWidth = $panelWidth > $titleLength ? $panelWidth : $titleLength; - $indentSpace = str_pad(' ', ceil($panelWidth / 2) - ceil($titleLength / 2) + 2 * 2, ' '); + $indentSpace = Str::pad(' ', ceil($panelWidth / 2) - ceil($titleLength / 2) + 2 * 2, ' '); Console::write(" {$indentSpace}{$title}"); } // output panel top border if ($borderChar) { - $border = str_pad($borderChar, $panelWidth + (3 * 3), $borderChar); + $border = Str::pad($borderChar, $panelWidth + (3 * 3), $borderChar); Console::write(' ' . $border); } @@ -215,7 +217,8 @@ public function format(): string foreach ($data as $label => $value) { // label exists if (!is_numeric($label)) { - $width = mb_strlen($label, 'UTF-8'); + $width = Str::len2($label, 'UTF-8'); + $labelMaxWidth = $width > $labelMaxWidth ? $width : $labelMaxWidth; } @@ -257,13 +260,13 @@ public function format(): string $title = ucwords($title); $titleLength = mb_strlen($title, 'UTF-8'); $panelWidth = $panelWidth > $titleLength ? $panelWidth : $titleLength; - $indentSpace = str_pad(' ', ceil($panelWidth / 2) - ceil($titleLength / 2) + 2 * 2, ' '); + $indentSpace = Str::pad(' ', ceil($panelWidth / 2) - ceil($titleLength / 2) + 2 * 2, ' '); $buffer->write(" {$indentSpace}{$title}\n"); } // output panel top border if ($topBorder = $this->titleBorder) { - $border = str_pad($topBorder, $panelWidth + (3 * 3), $topBorder); + $border = Str::pad($topBorder, $panelWidth + (3 * 3), $topBorder); $buffer->write(' ' . $border . PHP_EOL); } @@ -280,7 +283,7 @@ public function format(): string // output panel bottom border if ($footBorder = $this->footerBorder) { - $border = str_pad($footBorder, $panelWidth + (3 * 3), $footBorder); + $border = Str::pad($footBorder, $panelWidth + (3 * 3), $footBorder); $buffer->write(' ' . $border . PHP_EOL); } diff --git a/src/Component/Formatter/Section.php b/src/Component/Formatter/Section.php index 4ee1d77e..69d91eeb 100644 --- a/src/Component/Formatter/Section.php +++ b/src/Component/Formatter/Section.php @@ -10,7 +10,6 @@ use function ceil; use function implode; use function is_array; -use function str_pad; use function trim; use function ucwords; use const PHP_EOL; @@ -52,9 +51,9 @@ public static function show(string $title, $body, array $opts = []): void if ($tLength >= $width) { $titleIndent = Str::pad(self::CHAR_SPACE, $indent, self::CHAR_SPACE); } elseif ($opts['titlePos'] === self::POS_RIGHT) { - $titleIndent = str_pad(self::CHAR_SPACE, ceil($width - $tLength) + $indent, self::CHAR_SPACE); + $titleIndent = Str::pad(self::CHAR_SPACE, ceil($width - $tLength) + $indent, self::CHAR_SPACE); } elseif ($opts['titlePos'] === self::POS_MIDDLE) { - $titleIndent = str_pad(self::CHAR_SPACE, ceil(($width - $tLength) / 2) + $indent, self::CHAR_SPACE); + $titleIndent = Str::pad(self::CHAR_SPACE, ceil(($width - $tLength) / 2) + $indent, self::CHAR_SPACE); } else { $titleIndent = Str::pad(self::CHAR_SPACE, $indent, self::CHAR_SPACE); } @@ -67,7 +66,7 @@ public static function show(string $title, $body, array $opts = []): void $showBBorder = (bool)$opts['bottomBorder']; if ($showTBorder || $showBBorder) { - $border = str_pad($char, $width, $char); + $border = Str::pad($char, $width, $char); if ($showTBorder) { $topBorder = "{$indentStr}$border\n"; diff --git a/src/Component/Formatter/Table.php b/src/Component/Formatter/Table.php index 549cc9fb..b344e129 100644 --- a/src/Component/Formatter/Table.php +++ b/src/Component/Formatter/Table.php @@ -11,6 +11,7 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; use Toolkit\Cli\ColorTag; +use Toolkit\Stdlib\Str; use Toolkit\Stdlib\Str\StrBuffer; use function array_keys; use function array_merge; @@ -19,7 +20,6 @@ use function count; use function is_string; use function mb_strlen; -use function str_pad; use function ucwords; /** @@ -164,11 +164,11 @@ public static function show(array $data, string $title = 'Data Table', array $op $tStyle = $opts['titleStyle'] ?: 'bold'; $title = ucwords(trim($title)); $titleLength = mb_strlen($title, 'UTF-8'); - $indentSpace = str_pad(' ', ceil($tableWidth / 2) - ceil($titleLength / 2) + ($columnCount * 2), ' '); + $indentSpace = Str::pad(' ', ceil($tableWidth / 2) - ceil($titleLength / 2) + ($columnCount * 2), ' '); $buf->write(" {$indentSpace}<$tStyle>{$title}\n"); } - $border = $leftIndent . str_pad($rowBorderChar, $tableWidth + ($columnCount * 3) + 2, $rowBorderChar); + $border = $leftIndent . Str::pad($rowBorderChar, $tableWidth + ($columnCount * 3) + 2, $rowBorderChar); // output table top border if ($showBorder) { @@ -184,7 +184,7 @@ public static function show(array $data, string $title = 'Data Table', array $op foreach ($head as $index => $name) { $colMaxWidth = $info['columnMaxWidth'][$index]; // format - $name = str_pad($name, $colMaxWidth, ' '); + $name = Str::pad($name, $colMaxWidth, ' '); $name = ColorTag::wrap($name, $opts['headStyle']); $headStr .= " {$name} {$colBorderChar}"; } @@ -193,7 +193,7 @@ public static function show(array $data, string $title = 'Data Table', array $op // head border: split head and body if ($headBorderChar = $opts['headBorderChar']) { - $headBorder = $leftIndent . str_pad( + $headBorder = $leftIndent . Str::pad( $headBorderChar, $tableWidth + ($columnCount * 3) + 2, $headBorderChar @@ -212,7 +212,7 @@ public static function show(array $data, string $title = 'Data Table', array $op foreach ((array)$row as $value) { $colMaxWidth = $info['columnMaxWidth'][$colIndex]; // format - $value = str_pad($value, $colMaxWidth, ' '); + $value = Str::pad($value, $colMaxWidth, ' '); $value = ColorTag::wrap($value, $opts['bodyStyle']); $rowStr .= " {$value} {$colBorderChar}"; $colIndex++; diff --git a/src/Component/Formatter/Title.php b/src/Component/Formatter/Title.php index 5f478b83..f8ae0c30 100644 --- a/src/Component/Formatter/Title.php +++ b/src/Component/Formatter/Title.php @@ -8,7 +8,6 @@ use Toolkit\Sys\Sys; use function array_merge; use function ceil; -use function str_pad; /** * Class Title @@ -63,7 +62,7 @@ public static function show(string $title, array $opts = []): void } $titleLine = "$titleIndent$title\n"; - $border = $indentStr . str_pad($char, $width, $char); + $border = $indentStr . Str::pad($char, $width, $char); Console::write($titleLine . $border); } diff --git a/src/IO/Input.php b/src/IO/Input.php index 75689664..08246bbe 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -8,6 +8,7 @@ namespace Inhere\Console\IO; +use Toolkit\Cli\Cli; use Toolkit\Cli\Flags; use function array_map; use function array_shift; @@ -17,8 +18,6 @@ use function implode; use function preg_match; use function trim; -use const STDIN; -use const STDOUT; /** * Class Input - The input information. by parse global var $argv. @@ -36,9 +35,11 @@ class Input extends AbstractInput protected $commandId = ''; /** + * Default is STDIN + * * @var resource */ - protected $inputStream = STDIN; + protected $inputStream; /** * Input constructor. @@ -52,6 +53,7 @@ public function __construct(array $args = null, bool $parsing = true) $args = $_SERVER['argv']; } + $this->inputStream = Cli::getInputStream(); $this->collectInfo($args); if ($parsing) { @@ -91,6 +93,11 @@ protected function doParse(array $args): void $this->findCommand(); } + public function resetInputStream(): void + { + $this->inputStream = Cli::getInputStream(); + } + /** * @return string */ @@ -130,7 +137,7 @@ protected function tokenEscape(string $token): string public function read(string $question = '', bool $nl = false): string { if ($question) { - fwrite(STDOUT, $question . ($nl ? "\n" : '')); + fwrite(Cli::getOutputStream(), $question . ($nl ? "\n" : '')); } return trim(fgets($this->inputStream)); diff --git a/src/IO/Input/ArrayInput.php b/src/IO/Input/ArrayInput.php index b856ba7f..1ec9d0d1 100644 --- a/src/IO/Input/ArrayInput.php +++ b/src/IO/Input/ArrayInput.php @@ -10,8 +10,6 @@ use Inhere\Console\IO\Input; use Toolkit\Cli\Flags; -use function array_shift; -use function implode; /** * Class ArrayInput @@ -31,7 +29,7 @@ public function __construct(array $args = null, bool $parsing = true) parent::__construct([], false); if ($parsing && $args) { - $this->doParse($this->flags); + $this->doParse($this->flags); } } diff --git a/src/IO/Output.php b/src/IO/Output.php index b7e7fc9a..37c3bd81 100644 --- a/src/IO/Output.php +++ b/src/IO/Output.php @@ -25,18 +25,18 @@ class Output implements \Inhere\Console\Contract\OutputInterface use FormatOutputAwareTrait; /** - * Normal output stream + * Normal output stream. Default is STDOUT * * @var resource */ - protected $outputStream = STDOUT; + protected $outputStream; /** - * Error output stream + * Error output stream. Default is STDERR * * @var resource */ - protected $errorStream = STDERR; + protected $errorStream; /** * 控制台窗口(字体/背景)颜色添加处理 @@ -55,11 +55,18 @@ public function __construct($outputStream = null) { if ($outputStream) { $this->outputStream = $outputStream; + } else { + $this->outputStream = Cli::getOutputStream(); } $this->getStyle(); } + public function resetOutputStream(): void + { + $this->outputStream = Cli::getOutputStream(); + } + /*************************************************************************** * Output buffer ***************************************************************************/ diff --git a/src/IO/Output/BufferOutput.php b/src/IO/Output/BufferOutput.php index 5ddf534d..42b1269b 100644 --- a/src/IO/Output/BufferOutput.php +++ b/src/IO/Output/BufferOutput.php @@ -3,6 +3,7 @@ namespace Inhere\Console\IO\Output; use Inhere\Console\IO\Output; +use function fopen; /** * Class BufferOutput @@ -11,23 +12,13 @@ */ class BufferOutput extends Output { - /** - * @var array - */ - private $buffer = []; - - /** - * @param $messages - * @param bool $nl - * @param bool $quit - * @param array $opts - * - * @return int - */ - public function write($messages, $nl = true, $quit = false, array $opts = []): int + public function __construct() { - $this->buffer[] = $messages; + parent::__construct(fopen('php://memory', 'rwb')); + } - return 0; + public function getBuffer(): string + { + return ''; } } diff --git a/src/Util/ProgressBar.php b/src/Util/ProgressBar.php index fdb63ce3..7defeaa8 100644 --- a/src/Util/ProgressBar.php +++ b/src/Util/ProgressBar.php @@ -275,7 +275,7 @@ public function render(string $text): void */ protected function buildLine() { - // $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; + // $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; return preg_replace_callback('/{@([\w]+)(?:\:([\w-]+))?}/i', function ($matches) { if ($formatter = $this->getParser($matches[1])) { $text = $formatter($this, $this->output); @@ -305,10 +305,10 @@ public function setParser(string $section, callable $handler): void } /** - * get section Parser + * Get section Parser * - * @param string $section - * @param bool|boolean $throwException + * @param string $section + * @param bool $throwException * * @return mixed * @throws RuntimeException @@ -549,35 +549,37 @@ public function setFormat(string $format): void private static function loadDefaultParsers(): array { return [ - 'bar' => function (self $bar) { - $completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getPercent() * $bar->getBarWidth() : - $bar->getProgress() % $bar->getBarWidth()); + 'bar' => static function (self $bar) { + $barWidth = $bar->getBarWidth(); + $completeBars = (int)floor($bar->getMaxSteps() > 0 ? $bar->getPercent() * $barWidth : + $bar->getProgress() % $barWidth); $display = str_repeat($bar->getCompleteChar(), $completeBars); - if ($completeBars < $bar->getBarWidth()) { - $emptyBars = $bar->getBarWidth() - $completeBars; + if ($completeBars < $barWidth) { + $emptyBars = $barWidth - $completeBars; $display .= $bar->getProgressChar() . str_repeat($bar->getRemainingChar(), $emptyBars); } return $display; }, - 'elapsed' => function (self $bar) { + 'elapsed' => static function (self $bar) { return FormatUtil::howLongAgo(time() - $bar->getStartTime()); }, - 'remaining' => function (self $bar) { + 'remaining' => static function (self $bar) { if (!$bar->getMaxSteps()) { throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); } - if (!$bar->getProgress()) { + $progress = $bar->getProgress(); + if (!$progress) { $remaining = 0; } else { - $remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress())); + $remaining = (int)round((time() - $bar->getStartTime()) / $progress * ($bar->getMaxSteps() - $progress)); } return FormatUtil::howLongAgo($remaining); }, - 'estimated' => function (self $bar) { + 'estimated' => static function (self $bar) { if (!$bar->getMaxSteps()) { return 0; // throw new \LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); @@ -586,22 +588,22 @@ private static function loadDefaultParsers(): array if (!$bar->getProgress()) { $estimated = 0; } else { - $estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps()); + $estimated = (int)round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps()); } return FormatUtil::howLongAgo($estimated); }, - 'memory' => function () { + 'memory' => static function () { return FormatUtil::memoryUsage(memory_get_usage(true)); }, - 'current' => function (self $bar) { - return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT); + 'current' => static function (self $bar) { + return Str::pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT); }, - 'max' => function (self $bar) { + 'max' => static function (self $bar) { return $bar->getMaxSteps(); }, - 'percent' => function (self $bar) { - return floor($bar->getPercent() * 100); + 'percent' => static function (self $bar) { + return (float)floor($bar->getPercent() * 100); }, ]; } diff --git a/test/TempStream.php b/test/TempStream.php new file mode 100644 index 00000000..bae3c2df --- /dev/null +++ b/test/TempStream.php @@ -0,0 +1,91 @@ + Date: Fri, 19 Jun 2020 01:06:58 +0800 Subject: [PATCH 046/258] upsome --- src/IO/Output.php | 2 -- src/Util/Interact.php | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/IO/Output.php b/src/IO/Output.php index 37c3bd81..6102cb2d 100644 --- a/src/IO/Output.php +++ b/src/IO/Output.php @@ -12,8 +12,6 @@ use Inhere\Console\Console; use Inhere\Console\Traits\FormatOutputAwareTrait; use Toolkit\Cli\Cli; -use const STDERR; -use const STDOUT; /** * Class Output diff --git a/src/Util/Interact.php b/src/Util/Interact.php index 6102f0bb..c20e4336 100644 --- a/src/Util/Interact.php +++ b/src/Util/Interact.php @@ -17,10 +17,10 @@ use Inhere\Console\Component\Interact\Question; use Inhere\Console\Console; use RuntimeException; +use Toolkit\Cli\Cli; use function sprintf; use function strtolower; use function trim; -use const STDIN; /** * Class Interact @@ -47,7 +47,7 @@ public static function readln($message = null, $nl = false, array $opts = []): s Console::write($message, $nl); } - $stream = $opts['stream'] ?? STDIN; + $stream = $opts['stream'] ?? Cli::getInputStream(); return trim(fgets($stream)); } From fb3db61f3d53796a036374a2ed849019c78c3191 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 5 Jul 2020 10:12:32 +0800 Subject: [PATCH 047/258] add more event on phar build --- src/Component/PharCompiler.php | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/Component/PharCompiler.php b/src/Component/PharCompiler.php index 02549385..c8db34e0 100644 --- a/src/Component/PharCompiler.php +++ b/src/Component/PharCompiler.php @@ -58,6 +58,9 @@ class PharCompiler { public const ON_ADD = 'add'; + public const ADD_CLI_INDEX = 'add.index.cli'; + public const ADD_WEB_INDEX = 'add.index.web'; + public const ON_SKIP = 'skip'; public const ON_ERROR = 'error'; @@ -631,11 +634,17 @@ private function packIndexFile(Phar $phar): void { if ($this->cliIndex) { $this->counter++; - $path = $this->basePath . '/' . $this->cliIndex; - $content = preg_replace('{^#!/usr/bin/env php\s*}', '', file_get_contents($path)); + $path = $this->basePath . '/' . $this->cliIndex; $this->fire(self::ON_ADD, $this->cliIndex, $this->counter); - $phar->addFromString($this->cliIndex, trim($content) . PHP_EOL); + + $rawContent = preg_replace('{^#!/usr/bin/env php\s*}', '', file_get_contents($path)); + $fmtContent = $this->fire(self::ADD_CLI_INDEX, $this->cliIndex, $rawContent); + if ($fmtContent) { + $rawContent = $fmtContent; + } + + $phar->addFromString($this->cliIndex, trim($rawContent) . PHP_EOL); } if ($this->webIndex) { @@ -644,8 +653,13 @@ private function packIndexFile(Phar $phar): void $this->fire(self::ON_ADD, $this->webIndex, $this->counter); - $content = file_get_contents($path); - $phar->addFromString($this->webIndex, trim($content) . PHP_EOL); + $rawContent = file_get_contents($path); + $fmtContent = $this->fire(self::ADD_WEB_INDEX, $this->cliIndex, $rawContent); + if ($fmtContent) { + $rawContent = $fmtContent; + } + + $phar->addFromString($this->webIndex, trim($rawContent) . PHP_EOL); } } @@ -842,13 +856,17 @@ private function getRelativeFilePath(SplFileInfo $file): string /** * @param string $event * @param array $args + * + * @return mixed|null */ - private function fire(string $event, ...$args): void + private function fire(string $event, ...$args) { if (isset($this->events[$event])) { $cb = $this->events[$event]; - $cb(...$args); + return $cb(...$args); } + + return null; } /** From 26e0928f1628d02f29ffa5e47a85bf2f68a5c3d3 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 6 Jul 2020 11:31:26 +0800 Subject: [PATCH 048/258] update some for display help --- src/AbstractApplication.php | 10 ++++++++++ src/AbstractHandler.php | 26 +++++++++++++++++++++----- src/BuiltIn/SelfUpdateCommand.php | 8 ++++---- src/Controller.php | 10 ++++++++++ src/Traits/NameAliasTrait.php | 17 +++++++++++++++-- 5 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index da258655..d90c7080 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -399,6 +399,16 @@ public function addAliases(string $name, $aliases): self return $this; } + /** + * @param string $name + * + * @return array + */ + public function getAliases(string $name = ''): array + { + return $this->router->getAliases($name); + } + /** * @param int $level * @param string $format diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 7fe89cfe..4ceb477a 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -414,8 +414,8 @@ public function validateInput(): bool $shortNames = $conf['shortcut'] ? explode('|', $conf['shortcut']) : []; if ($srt = $in->findOneShortOpts($shortNames)) { $opts[$name] = $in->sOpt($srt); - } elseif ($conf['default']) { - + } elseif ($conf['default'] !== null) { + $opts[$name] = $conf['default']; } elseif ($conf['required']) { $missingOpts[] = "--{$name}" . ($srt ? "|-{$srt}" : ''); } @@ -606,8 +606,10 @@ protected function showHelpByMethodAnnotations(string $method, string $action = $ref = new ReflectionClass($this); $name = $this->input->getCommand(); - $this->logf(Console::VERB_CRAZY, 'display help info for the method=%s, action=%s, class=%s', $method, $action, - static::class); + $this->log(Console::VERB_CRAZY, "display help info for the method=$method", [ + 'class' => static::class, + 'action' => $action, + ]); if (!$ref->hasMethod($method)) { $this->write("The command [$name] don't exist in the group: " . static::getName()); @@ -663,7 +665,7 @@ protected function showHelpByMethodAnnotations(string $method, string $action = if (isset($help['Description:'])) { $description = $help['Description:'] ?: 'No description message for the command'; - $this->write(ucfirst($description) . PHP_EOL); + $this->write(ucfirst($this->parseCommentsVars($description)) . PHP_EOL); unset($help['Description:']); } @@ -805,6 +807,20 @@ public function logf(int $level, string $format, ...$args): void Console::logf($level, $format, ...$args); } + /** + * @param int $level + * @param string $message + * @param array $extra + */ + public function log(int $level, string $message, array $extra = []): void + { + if ($this->getVerbLevel() < $level) { + return; + } + + Console::log($message, $extra, $level); + } + /** * @return InputDefinition|null */ diff --git a/src/BuiltIn/SelfUpdateCommand.php b/src/BuiltIn/SelfUpdateCommand.php index 3d675f99..2ea2eb3a 100644 --- a/src/BuiltIn/SelfUpdateCommand.php +++ b/src/BuiltIn/SelfUpdateCommand.php @@ -25,13 +25,13 @@ */ class SelfUpdateCommand extends Command { - public const VERSION_URL = 'https://padraic.github.io/humbug/downloads/humbug.version'; + public const VERSION_URL = 'https://inhere.github.io/humbug/downloads/humbug.version'; - public const PHAR_URL = 'https://padraic.github.io/humbug/downloads/humbug.phar'; + public const PHAR_URL = 'https://inhere.github.io/humbug/downloads/humbug.phar'; - public const PACKAGE_NAME = 'humbug/humbug'; + public const PACKAGE_NAME = 'inhere/console'; - public const FILE_NAME = 'humbug.phar'; + public const FILE_NAME = 'console.phar'; protected static $name = 'self-update'; diff --git a/src/Controller.php b/src/Controller.php index 166e1d9a..9c816a0e 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -315,6 +315,16 @@ final public function helpCommand(): int $method = $this->actionSuffix ? $action . ucfirst($this->actionSuffix) : $action; $aliases = $this->getCommandAliases($action); + // up: find global aliases from app + if ($this->app) { + $commandId = $this->input->getCommandId(); + $gAliases = $this->app->getAliases($commandId); + + if ($gAliases) { + $aliases = array_merge($aliases, $gAliases); + } + } + // For a specified sub-command. return $this->showHelpByMethodAnnotations($method, $action, $aliases); } diff --git a/src/Traits/NameAliasTrait.php b/src/Traits/NameAliasTrait.php index 894dfa26..e9abc2ba 100644 --- a/src/Traits/NameAliasTrait.php +++ b/src/Traits/NameAliasTrait.php @@ -42,7 +42,7 @@ public function setAlias(string $name, $alias, bool $validate = false): void } /** - * get real name by alias + * Get real name by alias * * @param string $alias * @@ -64,10 +64,23 @@ public function hasAlias(string $alias): bool } /** + * @param string $name + * * @return array */ - public function getAliases(): array + public function getAliases(string $name = ''): array { + if ($name) { + $aliases = []; + foreach ($this->aliases as $alias => $n) { + if ($name === $n) { + $aliases[] = $alias; + } + } + + return $aliases; + } + return $this->aliases; } } From 6b5096b0cbe22038d8e92fde29fb632b384482e0 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 21 Aug 2020 15:37:58 +0800 Subject: [PATCH 049/258] upsome comments --- src/Router.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Router.php b/src/Router.php index 3d57cb90..1ed7b9c9 100644 --- a/src/Router.php +++ b/src/Router.php @@ -78,7 +78,7 @@ class Router implements RouterInterface * Register a app group command(by controller) * * @param string $name The controller name - * @param string|ControllerInterface $class The controller class + * @param string|ControllerInterface|null $class The controller class * @param array $options * array: * - aliases The command aliases @@ -145,7 +145,7 @@ public function addGroup(string $name, $class = null, array $options = []): Rout * Register a app independent console command * * @param string|CommandInterface $name - * @param string|Closure|CommandInterface $handler + * @param string|Closure|CommandInterface|null $handler * @param array $options * array: * - aliases The command aliases From df9164cf926e8a09a7e07f396be00595d7c6bfb7 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 21 Aug 2020 17:48:21 +0800 Subject: [PATCH 050/258] refact: refacting some logic, begin v4 develop --- .travis.yml | 3 +- CHANGELOG.md | 5 + README.md | 2 +- composer.json | 4 +- src/AbstractApplication.php | 6 +- src/AbstractHandler.php | 4 +- src/Component/Formatter/Alert.php | 25 ++ src/Component/Style/Alert.php | 45 -- src/Component/Style/Style.php | 397 ------------------ .../AdvancedFormatOutputTrait.php | 4 +- .../ApplicationHelpTrait.php | 4 +- .../ControllerHelpTrait.php | 4 +- .../FormatOutputAwareTrait.php | 2 +- .../InputOutputAwareTrait.php | 4 +- src/{Traits => Concern}/NameAliasTrait.php | 4 +- .../RuntimeProfileTrait.php | 4 +- src/{Traits => Concern}/SimpleEventTrait.php | 4 +- .../UserInteractAwareTrait.php | 20 +- src/Flag/Parser.php | 13 + src/IO/InputInterface.php | 19 - src/IO/Output.php | 5 +- src/IO/OutputInterface.php | 19 - src/Router.php | 2 +- src/Util/Helper.php | 2 +- 24 files changed, 83 insertions(+), 518 deletions(-) delete mode 100644 src/Component/Style/Alert.php delete mode 100644 src/Component/Style/Style.php rename src/{Traits => Concern}/AdvancedFormatOutputTrait.php (74%) rename src/{Traits => Concern}/ApplicationHelpTrait.php (99%) rename src/{Traits => Concern}/ControllerHelpTrait.php (59%) rename src/{Traits => Concern}/FormatOutputAwareTrait.php (99%) rename src/{Traits => Concern}/InputOutputAwareTrait.php (98%) rename src/{Traits => Concern}/NameAliasTrait.php (96%) rename src/{Traits => Concern}/RuntimeProfileTrait.php (97%) rename src/{Traits => Concern}/SimpleEventTrait.php (98%) rename src/{Traits => Concern}/UserInteractAwareTrait.php (88%) create mode 100644 src/Flag/Parser.php delete mode 100644 src/IO/InputInterface.php delete mode 100644 src/IO/OutputInterface.php diff --git a/.travis.yml b/.travis.yml index e4ec624b..d2cd7079 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,10 @@ language: php php: - - '7.1' +# - '7.1' - '7.2' - '7.3' + - '7.4' #matrix: # include: diff --git a/CHANGELOG.md b/CHANGELOG.md index 60f94203..32cd189b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG +## v4.0.x + +> begin at: 2020.08.21 + + ## v3.0.x > publish at: 2019.01.03 diff --git a/README.md b/README.md index e1c8f7e1..49f05ea6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # PHP console [![License](https://img.shields.io/packagist/l/inhere/console.svg?style=flat-square)](LICENSE) -[![Php Version](https://img.shields.io/badge/php-%3E=7.1.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) +[![Php Version](https://img.shields.io/badge/php-%3E=7.3.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) [![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console) A simple, full-featured php command line application library. diff --git a/composer.json b/composer.json index dc12d2d2..90a5968c 100644 --- a/composer.json +++ b/composer.json @@ -22,13 +22,13 @@ } ], "require": { - "php": ">7.1.0", + "php": ">7.3.0", "toolkit/cli-utils": "~1.0", "toolkit/stdlib": "~1.0", "toolkit/sys-utils": "~1.0" }, "require-dev": { - "phpunit/phpunit": "^7.5" + "phpunit/phpunit": "~9.1" }, "autoload": { "psr-4": { diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index d90c7080..a2df162d 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -16,9 +16,9 @@ use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; use Inhere\Console\Contract\OutputInterface; -use Inhere\Console\Traits\ApplicationHelpTrait; -use Inhere\Console\Traits\InputOutputAwareTrait; -use Inhere\Console\Traits\SimpleEventTrait; +use Inhere\Console\Concern\ApplicationHelpTrait; +use Inhere\Console\Concern\InputOutputAwareTrait; +use Inhere\Console\Concern\SimpleEventTrait; use InvalidArgumentException; use Throwable; use Toolkit\Cli\Style; diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 4ceb477a..b1802663 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -13,8 +13,8 @@ use Inhere\Console\IO\Input; use Inhere\Console\IO\InputDefinition; use Inhere\Console\IO\Output; -use Inhere\Console\Traits\InputOutputAwareTrait; -use Inhere\Console\Traits\UserInteractAwareTrait; +use Inhere\Console\Concern\InputOutputAwareTrait; +use Inhere\Console\Concern\UserInteractAwareTrait; use Inhere\Console\Util\FormatUtil; use Inhere\Console\Util\Helper; use InvalidArgumentException; diff --git a/src/Component/Formatter/Alert.php b/src/Component/Formatter/Alert.php index 108b2fb1..9c6a3ae6 100644 --- a/src/Component/Formatter/Alert.php +++ b/src/Component/Formatter/Alert.php @@ -17,4 +17,29 @@ */ class Alert extends MessageFormatter { + public const THEMES = [ + 'default' => '<{@style}>{@message}', + 'theme1' => '<{@style}>[{@type}] {@message}', + 'lite' => '[<{@style}>{@type}] {@message}', + ]; + + public static function simple(string $message, string $style = 'info', array $opts = []): void + { + } + + public static function block(string $message, string $style = 'info', array $opts = []): void + { + $opts = array_merge([ + 'paddingX' => 1, // line + 'paddingY' => 1, // space + + 'icon' => '', + 'theme' => 'default', + 'template' => '' + ], $opts); + } + + public static function lite(string $message, string $style = 'info'): void + { + } } diff --git a/src/Component/Style/Alert.php b/src/Component/Style/Alert.php deleted file mode 100644 index 49c94d01..00000000 --- a/src/Component/Style/Alert.php +++ /dev/null @@ -1,45 +0,0 @@ - '<{@style}>{@message}', - 'theme1' => '<{@style}>[{@type}] {@message}', - 'lite' => '[<{@style}>{@type}] {@message}', - ]; - - public static function create(string $message, string $style = 'info', array $opts = []): void - { - } - - public static function block(string $message, string $style = 'info', array $opts = []): void - { - $opts = array_merge([ - 'paddingX' => 1, // line - 'paddingY' => 1, // space - - 'icon' => '', - 'theme' => 'default', - 'template' => '' - ], $opts); - } - - public static function lite(string $message, string $style = 'info'): void - { - } -} diff --git a/src/Component/Style/Style.php b/src/Component/Style/Style.php deleted file mode 100644 index 318efc41..00000000 --- a/src/Component/Style/Style.php +++ /dev/null @@ -1,397 +0,0 @@ -(.*?)<\/\\1>/s'; - - /** - * @var self - */ - private static $instance; - - /** - * Flag to remove color codes from the output - * - * @var bool - */ - protected static $noColor = false; - - /** - * Array of Color objects - * - * @var ColorCode[] - */ - private $styles = []; - - /** - * @return Style - */ - public static function instance(): Style - { - if (!self::$instance) { - self::$instance = new self(); - } - - return self::$instance; - } - - /** - * Constructor - * - * @param string $fg 前景色(字体颜色) - * @param string $bg 背景色 - * @param array $options 其它选项 - */ - public function __construct($fg = '', $bg = '', array $options = []) - { - if ($fg || $bg || $options) { - $this->add('base', $fg, $bg, $options); - } - - $this->loadDefaultStyles(); - } - - /** - * @param string $method - * @param array $args - * - * @return mixed|string - * @throws InvalidArgumentException - */ - public function __call($method, array $args) - { - if (isset($args[0]) && $this->hasStyle($method)) { - return $this->format(sprintf('<%s>%s', $method, $args[0], $method)); - } - - throw new InvalidArgumentException("You called method is not exists: $method"); - } - - /** - * Adds predefined color styles to the Color styles - * default primary success info warning danger - */ - protected function loadDefaultStyles(): void - { - $this->addByArray(self::NORMAL, ['fg' => 'normal']) - // 不明显的 浅灰色的 - ->addByArray(self::FAINTLY, ['fg' => 'normal', 'options' => ['italic']]) - ->addByArray(self::BOLD, ['options' => ['bold']]) - ->addByArray(self::INFO, ['fg' => 'green',])//'options' => ['bold'] - ->addByArray(self::NOTE, ['fg' => 'cyan', 'options' => ['bold']])//'options' => ['bold'] - ->addByArray(self::PRIMARY, ['fg' => 'yellow', 'options' => ['bold']])// - ->addByArray(self::SUCCESS, ['fg' => 'green', 'options' => ['bold']]) - ->addByArray(self::NOTICE, ['options' => ['bold', 'underscore'],]) - ->addByArray(self::WARNING, ['fg' => 'black', 'bg' => 'yellow',])//'options' => ['bold'] - ->addByArray(self::COMMENT, ['fg' => 'yellow',])//'options' => ['bold'] - ->addByArray(self::QUESTION, ['fg' => 'black', 'bg' => 'cyan']) - ->addByArray(self::DANGER, ['fg' => 'red',])// 'bg' => 'magenta', 'options' => ['bold'] - ->add(self::ERROR, 'white', 'red', [], true)->add('underline', 'normal', '', ['underscore']) - ->add('blue', 'blue')->add('cyan', 'cyan')->add('magenta', 'magenta')->add('mga', 'magenta') - ->add('red', 'red')->addByArray('yellow', ['fg' => 'yellow']) - ->addByArray('darkGray', ['fg' => 'black', 'extra' => true]); - } - - /** - * Process a string use style - * - * @param string $style - * @param string $text - * - * @return string - */ - public function apply(string $style, string $text): string - { - return $this->format(self::wrap($text, $style)); - } - - /** - * Process a string. - * - * @param string $text - * - * @return mixed - */ - public function t(string $text) - { - return $this->format($text); - } - - /** - * Process a string. - * - * @param string $text - * - * @return mixed - */ - public function render(string $text) - { - return $this->format($text); - } - - /** - * @param string $text - * - * @return mixed|string - */ - public function format(string $text) - { - if (!$text || false === strpos($text, ' $m) { - $key = $matches[1][$i]; - - if (array_key_exists($key, $this->styles)) { - $text = ColorTag::replaceColor($text, $key, $matches[2][$i], (string)$this->styles[$key]); - } elseif (isset(Color::STYLES[$key])) { - $text = ColorTag::replaceColor($text, $key, $matches[2][$i], Color::STYLES[$key]); - /** Custom style format @see ColorCode::fromString() */ - } elseif (strpos($key, '=')) { - $text = ColorTag::replaceColor($text, $key, $matches[2][$i], (string)ColorCode::fromString($key)); - } - } - - return $text; - } - - /** - * Strip color tags from a string. - * - * @param string $string - * - * @return mixed - */ - public static function stripColor(string $string) - { - return ColorTag::strip($string); - } - - /**************************************************************************** - * Attr Color Style - ****************************************************************************/ - - /** - * Add a style. - * - * @param string $name - * @param string|ColorCode|array $fg 前景色|Color对象|也可以是style配置数组(@see self::addByArray()) - * 当它为Color对象或配置数组时,后面两个参数无效 - * @param string $bg 背景色 - * @param array $options 其它选项 - * @param bool $extra - * - * @return $this - */ - public function add(string $name, $fg = '', $bg = '', array $options = [], bool $extra = false): self - { - if (is_array($fg)) { - return $this->addByArray($name, $fg); - } - - if (is_object($fg) && $fg instanceof ColorCode) { - $this->styles[$name] = $fg; - } else { - $this->styles[$name] = ColorCode::make($fg, $bg, $options, $extra); - } - - return $this; - } - - /** - * Add a style by an array config - * - * @param string $name - * @param array $styleConfig 样式设置信息 - * e.g - * [ - * 'fg' => 'white', - * 'bg' => 'black', - * 'extra' => true, - * 'options' => ['bold', 'underscore'] - * ] - * - * @return $this - */ - public function addByArray(string $name, array $styleConfig): self - { - $style = [ - 'fg' => '', - 'bg' => '', - 'extra' => false, - 'options' => [] - ]; - - $config = array_merge($style, $styleConfig); - // expand - [$fg, $bg, $extra, $options] = array_values($config); - - $this->styles[$name] = ColorCode::make($fg, $bg, $options, (bool)$extra); - - return $this; - } - - /** - * @return array - */ - public function getStyleNames(): array - { - return array_keys($this->styles); - } - - /** - * @return array - */ - public function getNames(): array - { - return array_keys($this->styles); - } - - /** - * @return array - */ - public function getStyles(): array - { - return $this->styles; - } - - /** - * @param $name - * - * @return ColorCode|null - */ - public function getStyle($name): ?ColorCode - { - if (!isset($this->styles[$name])) { - return null; - } - - return $this->styles[$name]; - } - - /** - * @param $name - * - * @return bool - */ - public function hasStyle($name): bool - { - return isset($this->styles[$name]); - } - - /** - * wrap a color style tag - * - * @param string $text - * @param string $tag - * - * @return string - */ - public static function wrap(string $text, string $tag): string - { - if (!$text || !$tag) { - return $text; - } - - return "<$tag>$text"; - } - - /** - * Method to get property NoColor - */ - public static function isNoColor(): bool - { - return Color::isNoColor(); - } - - /** - * Method to set property noColor - * - * @param $noColor - */ - public static function setNoColor($noColor = true): void - { - Color::setNoColor($noColor); - } -} diff --git a/src/Traits/AdvancedFormatOutputTrait.php b/src/Concern/AdvancedFormatOutputTrait.php similarity index 74% rename from src/Traits/AdvancedFormatOutputTrait.php rename to src/Concern/AdvancedFormatOutputTrait.php index 44d76bba..43b8b9e6 100644 --- a/src/Traits/AdvancedFormatOutputTrait.php +++ b/src/Concern/AdvancedFormatOutputTrait.php @@ -6,12 +6,12 @@ * Time: 9:44 */ -namespace Inhere\Console\Traits; +namespace Inhere\Console\Concern; /** * Trait AdvancedFormatOutputTrait * - * @package Inhere\Console\Traits + * @package Inhere\Console\Concern */ trait AdvancedFormatOutputTrait { diff --git a/src/Traits/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php similarity index 99% rename from src/Traits/ApplicationHelpTrait.php rename to src/Concern/ApplicationHelpTrait.php index 0280b3f2..0b5dc167 100644 --- a/src/Traits/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -6,7 +6,7 @@ * Time: 09:54 */ -namespace Inhere\Console\Traits; +namespace Inhere\Console\Concern; use Inhere\Console\AbstractHandler; use Toolkit\Cli\Style; @@ -39,7 +39,7 @@ /** * Trait ApplicationHelpTrait * - * @package Inhere\Console\Traits + * @package Inhere\Console\Concern */ trait ApplicationHelpTrait { diff --git a/src/Traits/ControllerHelpTrait.php b/src/Concern/ControllerHelpTrait.php similarity index 59% rename from src/Traits/ControllerHelpTrait.php rename to src/Concern/ControllerHelpTrait.php index b2edd23a..03ca6ab7 100644 --- a/src/Traits/ControllerHelpTrait.php +++ b/src/Concern/ControllerHelpTrait.php @@ -1,11 +1,11 @@ Date: Fri, 23 Oct 2020 11:26:15 +0800 Subject: [PATCH 051/258] add an new tool class --- src/Util/PhpDevServe.php | 215 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 src/Util/PhpDevServe.php diff --git a/src/Util/PhpDevServe.php b/src/Util/PhpDevServe.php new file mode 100644 index 00000000..daa72e97 --- /dev/null +++ b/src/Util/PhpDevServe.php @@ -0,0 +1,215 @@ +documentRoot = $documentRoot; + + $this->entryFile = $entryFile; + $this->serveAddr = $serveAddr ?: '127.0.0.1:8080'; + } + + /** + * @param bool $withColorTag + */ + public function showTipsMessage(bool $withColorTag = true): void + { + $msg = $this->getTipsMessage($withColorTag); + + if ($withColorTag) { + echo Color::parseTag($msg), PHP_EOL; + } else { + echo $msg, PHP_EOL; + } + } + + /** + * @param bool $withColorTag + * + * @return string + */ + public function getTipsMessage(bool $withColorTag = true): string + { + $version = PHP_VERSION; + + $addr = $this->serveAddr; + $root = $this->documentRoot; + $stop = 'CTRL + C'; + + if ($withColorTag) { + $addr = "{$this->serveAddr}"; + $root = "{$this->documentRoot}"; + $stop = "CTRL + C"; + } + + $nodes = [ + sprintf("PHP $version Development Server started\nServer listening on http://%s", $addr), + sprintf("Document root is %s\nYou can use %s to stop run.", $root, $stop), + ]; + + return implode("\n", $nodes); + } + + public function start(): void + { + // php -S {$serveAddr} -t public public/index.php + $phpBin = $this->phpBin ?: 'php'; + $command = "$phpBin -S {$this->serveAddr}"; + + if ($this->documentRoot) { + $command .= " -t {$this->documentRoot}"; + } + + if ($entryFile = $this->entryFile) { + $command .= " $entryFile"; + } + + echo Color::parseTag("> $command"), PHP_EOL; + Sys::execute($command); + } + + /** + * @return string + */ + public function findPhpBinFile(): string + { + $phpBin = 'php'; + + // TODO use `type php` check and find. return: 'php is /usr/local/bin/php' + [$ok, $ret,] = Sys::run('which php'); + if ($ok === 0) { + $phpBin = trim($ret); + } + + return $phpBin; + } + + /** + * @return string + */ + public function getPhpBin(): string + { + return $this->phpBin; + } + + /** + * @param string $phpBin + * + * @return PhpDevServe + */ + public function setPhpBin(string $phpBin): self + { + $this->phpBin = $phpBin; + return $this; + } + + /** + * @return string + */ + public function getServeAddr(): string + { + return $this->serveAddr; + } + + /** + * @param string $serveAddr + * + * @return PhpDevServe + */ + public function setServeAddr(string $serveAddr): self + { + $this->serveAddr = $serveAddr; + return $this; + } + + /** + * @return string + */ + public function getDocumentRoot(): string + { + return $this->documentRoot; + } + + /** + * @param string $documentRoot + * + * @return PhpDevServe + */ + public function setDocumentRoot(string $documentRoot): self + { + $this->documentRoot = $documentRoot; + return $this; + } + + /** + * @return string + */ + public function getEntryFile(): string + { + return $this->entryFile; + } + + /** + * @param string $entryFile + * + * @return PhpDevServe + */ + public function setEntryFile(string $entryFile): self + { + $this->entryFile = $entryFile; + return $this; + } +} From 6b8010daf61c5d34a2431605e1c37bb07f97c2e4 Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 18 Nov 2020 21:57:51 +0800 Subject: [PATCH 052/258] upsome for display list --- src/Component/Formatter/SingleList.php | 2 +- src/Concern/FormatOutputAwareTrait.php | 2 +- src/Console.php | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Component/Formatter/SingleList.php b/src/Component/Formatter/SingleList.php index c213f328..161555d5 100644 --- a/src/Component/Formatter/SingleList.php +++ b/src/Component/Formatter/SingleList.php @@ -35,7 +35,7 @@ class SingleList extends MessageFormatter * * @return int|string */ - public static function show($data, string $title = '', array $opts = []) + public static function show($data, string $title = 'Information', array $opts = []) { $string = ''; $opts = array_merge([ diff --git a/src/Concern/FormatOutputAwareTrait.php b/src/Concern/FormatOutputAwareTrait.php index db633a50..8c477289 100644 --- a/src/Concern/FormatOutputAwareTrait.php +++ b/src/Concern/FormatOutputAwareTrait.php @@ -200,7 +200,7 @@ public function section(string $title, $body, array $opts = []): void * @param string $title * @param array $opts */ - public function aList($data, string $title = '', array $opts = []): void + public function aList($data, string $title = 'Information', array $opts = []): void { SingleList::show($data, $title, $opts); } diff --git a/src/Console.php b/src/Console.php index 4c07d279..b5b07af8 100644 --- a/src/Console.php +++ b/src/Console.php @@ -99,13 +99,15 @@ public static function newApp( */ public static function logf(int $level, string $format, ...$args): void { - $levelName = self::LEVEL_NAMES[$level] ?? 'INFO'; - $colorName = self::LEVEL2TAG[$level] ?? 'info'; + $levelName = self::LEVEL_NAMES[$level] ?? 'INFO'; + $colorName = self::LEVEL2TAG[$level] ?? 'info'; + $taggedName = ColorTag::add($levelName, $colorName); - $message = strpos($format, '%') > 0 ? sprintf($format, ...$args) : $format; + $datetime = date('Y/m/d H:i:s'); + $message = strpos($format, '%') > 0 ? sprintf($format, ...$args) : $format; - self::writef('[%s] %s', $taggedName, $message); + self::writef('%s [%s] %s', $datetime, $taggedName, $message); } /** From cef2fc477fdba3dadf9d1b2c3d2a146952948f3b Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 20 Nov 2020 01:35:53 +0800 Subject: [PATCH 053/258] fix: #19 sub-command desc display error --- examples/Controller/HomeController.php | 12 ++-- src/AbstractHandler.php | 4 +- src/Controller.php | 84 ++++++++++++++++++++++++-- 3 files changed, 89 insertions(+), 11 deletions(-) diff --git a/examples/Controller/HomeController.php b/examples/Controller/HomeController.php index 29762e5d..9283127a 100644 --- a/examples/Controller/HomeController.php +++ b/examples/Controller/HomeController.php @@ -42,6 +42,7 @@ protected static function commandAliases(): array 'ml' => 'multiList', 'sl' => 'splitLine', 'dt' => 'dynamicText', + 'da' => 'defArg', ]; } @@ -50,6 +51,10 @@ protected function init(): void parent::init(); $this->addCommentsVar('internalFonts', implode(',', ArtFont::getInternalFonts())); + + $this->setCommandMeta('defArg', [ + 'desc' => 'the command args and opts config use defined configure, it like symfony console, please see defArgConfigure()', + ]); } /** @@ -75,6 +80,7 @@ protected function afterExecute(): void /** * this is a command's description message * the second line text + * * @usage {command} [arg ...] [--opt ...] * @arguments * arg1 argument description 1 @@ -107,15 +113,13 @@ public function disabledCommand(): void protected function defArgConfigure(): void { $this->createDefinition() - ->setDescription('the command arg/opt config use defined configure, it like symfony console: argument define by position') + // ->setDescription('the command args and opts config use defined configure, it like symfony console, please see defArgConfigure()') ->addArgument('name', Input::ARG_REQUIRED, "description for the argument 'name'") ->addOption('yes', 'y', Input::OPT_BOOLEAN, "description for the option 'yes'") ->addOption('opt1', null, Input::OPT_REQUIRED, "description for the option 'opt1'"); } - /** - * the command arg/opt config use defined configure, it like symfony console: argument define by position - */ + // desc set at $this->commandMetas. public function defArgCommand(): void { $this->output->dump($this->input->getArgs(), $this->input->getOpts(), $this->input->getBoolOpt('y')); diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index b1802663..f95aa459 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -101,12 +101,12 @@ abstract class AbstractHandler implements CommandHandlerInterface /** * @var InputDefinition|null */ - private $definition; + protected $definition; /** * @var string */ - private $processTitle = ''; + protected $processTitle = ''; /** * @var array diff --git a/src/Controller.php b/src/Controller.php index 9c816a0e..bd01a288 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -11,6 +11,7 @@ use Generator; use Inhere\Console\Contract\ControllerInterface; use Inhere\Console\IO\Input; +use Inhere\Console\IO\InputDefinition; use Inhere\Console\IO\Output; use Inhere\Console\Util\FormatUtil; use Inhere\Console\Util\Helper; @@ -20,8 +21,8 @@ use ReflectionObject; use RuntimeException; use Toolkit\Cli\ColorTag; -use Toolkit\Stdlib\Util\PhpDoc; use Toolkit\Stdlib\Str; +use Toolkit\Stdlib\Util\PhpDoc; use function array_flip; use function array_keys; use function array_merge; @@ -67,6 +68,7 @@ abstract class Controller extends AbstractHandler implements ControllerInterface /** * Action name, no suffix. + * eg: updateCommand() -> action: 'update' * * @var string */ @@ -104,6 +106,21 @@ abstract class Controller extends AbstractHandler implements ControllerInterface */ private $disabledCommands = []; + /** + * Metadata for sub-commands. such as: desc, alias + * Notice: you must add metadata on `init()` + * + * [ + * 'command real name' => [ + * 'desc' => 'sub command description', + * 'alias' => [], + * ], + * ], + * + * @var array + */ + protected $commandMetas = []; + /** * Define command alias mapping. please rewrite it on sub-class. * @@ -177,11 +194,11 @@ public function run(string $command = '') } $this->action = Str::camelCase($this->getRealCommandName($command)); - if (!$this->action) { return $this->showHelp(); } + // do running return parent::run($command); } @@ -190,7 +207,7 @@ public function run(string $command = '') */ protected function configure(): void { - // eg. indexConfigure() for indexCommand() + // eg. use `indexConfigure()` for `indexCommand()` $method = $this->action . self::CONFIGURE_SUFFIX; if (method_exists($this, $method)) { @@ -198,6 +215,24 @@ protected function configure(): void } } + /** + * @return InputDefinition + */ + protected function createDefinition(): InputDefinition + { + if (!$this->definition) { + $this->definition = new InputDefinition(); + + // if have been set desc for the sub-command + $cmdDesc = $this->commandMetas[$this->action]['desc'] ?? ''; + if ($cmdDesc) { + $this->definition->setDescription($cmdDesc); + } + } + + return $this->definition; + } + /** * Run command action in the group * @@ -271,6 +306,9 @@ protected function showHelp(): bool return $this->helpCommand() === 0; } + /** + * @param array $help + */ protected function beforeRenderCommandHelp(array &$help): void { $help['Group Options:'] = FormatUtil::alignOptions($this->groupOptions); @@ -305,7 +343,7 @@ final public function helpCommand(): int { $action = $this->action; - // For all sub-commands of the controller + // Not input action, for all sub-commands of the controller if (!$action && !($action = $this->getFirstArg())) { $this->showCommandList(); return 0; @@ -354,12 +392,15 @@ final public function showCommandList(): void $showDisabled = (bool)$this->getOpt('show-disabled', false); $defaultDes = 'No description message'; + /** + * @var $cmd string The command name. + */ foreach ($this->getAllCommandMethods($ref) as $cmd => $m) { if (!$cmd) { continue; } - $desc = $defaultDes; + $desc = $this->getCommandMeta('desc', $defaultDes, $cmd); if ($phpDoc = $m->getDocComment()) { $desc = PhpDoc::firstLine($phpDoc); } @@ -630,4 +671,37 @@ public function setDelimiter(string $delimiter): void { $this->delimiter = $delimiter; } + + /** + * @return array + */ + public function getCommandMetas(): array + { + return $this->commandMetas; + } + + /** + * @param string $command + * @param array $meta eg: ['desc' => '', 'alias' => []] + */ + public function setCommandMeta(string $command, array $meta): void + { + if ($command) { + $this->commandMetas[$command] = $meta; + } + } + + /** + * @param string $key + * @param null $default + * @param string $command if not set, will use $this->action + * + * @return mixed|null + */ + public function getCommandMeta(string $key, $default = null, string $command = '') + { + $action = $command ?: $this->action; + + return $this->commandMetas[$action][$key] ?? $default; + } } From 16b64202bb6bbf5bd058e83f950b4eb6796c8496 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 20 Nov 2020 11:26:35 +0800 Subject: [PATCH 054/258] up: add github action scripts --- .github/workflows/php.yml | 52 +++++++++++++++++++++++++++++ .github/workflows/release.yml | 62 +++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 .github/workflows/php.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml new file mode 100644 index 00000000..dd1b697c --- /dev/null +++ b/.github/workflows/php.yml @@ -0,0 +1,52 @@ +name: Unit-tests + +on: + push: + paths: + - '**.php' + - 'composer.json' + - '**.yml' + +jobs: + test: + name: Test on php ${{ matrix.php}} and ${{ matrix.os }} + runs-on: ${{ matrix.os }} + timeout-minutes: 10 + strategy: + fail-fast: true + matrix: + php: [7.3, 7.4] # + os: [ubuntu-latest, macOS-latest] # windows-latest, + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set ENV vars + # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable + run: | + echo "RELEASE_TAG=${GITHUB_REF:10}" >> $GITHUB_ENV + echo "RELEASE_NAME=$GITHUB_WORKFLOW" >> $GITHUB_ENV + + - name: Display Env + run: env + + # usage refer https://github.com/shivammathur/setup-php + - name: Setup PHP + timeout-minutes: 5 + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php}} + tools: pecl, php-cs-fixer, phpunit + extensions: mbstring, dom, fileinfo, mysql, openssl, igbinary, redis # , swoole-4.4.19 #optional, setup extensions + ini-values: post_max_size=56M, short_open_tag=On #optional, setup php.ini configuration + coverage: none #optional, setup coverage driver: xdebug, none + + - name: Install dependencies + run: composer install --no-progress --no-suggest + + # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" + # Docs: https://getcomposer.org/doc/articles/scripts.md + + - name: Run unit tests + run: php vendor/bin/phpunit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..2453628b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,62 @@ +name: Tag-release + +on: + push: + tags: + - v* + +jobs: + release: + name: Test on php ${{ matrix.php}} + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + fail-fast: true + matrix: + php: [7.3] + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set ENV for github-release + # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable + run: | + echo "RELEASE_TAG=${GITHUB_REF:10}" >> $GITHUB_ENV + echo "RELEASE_NAME=$GITHUB_WORKFLOW" >> $GITHUB_ENV + + # usage refer https://github.com/shivammathur/setup-php + - name: Setup PHP + timeout-minutes: 5 + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php}} + tools: pecl, php-cs-fixer, phpunit + extensions: mbstring, dom, fileinfo, mysql, openssl # , swoole-4.4.19 #optional, setup extensions + ini-values: post_max_size=56M, short_open_tag=On #optional, setup php.ini configuration + coverage: none #optional, setup coverage driver: xdebug, none + + - name: Install dependencies # eg: v1.0.3 + run: | + tag1=${GITHUB_REF#refs/*/} + echo "release tag: ${tag1}" + composer install --no-progress --no-suggest + + # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" + # Docs: https://getcomposer.org/doc/articles/scripts.md + + - name: Build phar and send to github assets + run: | + echo $RELEASE_TAG + echo $RELEASE_NAME + php -d phar.readonly=0 bin/kite phar:pack -o kite-${RELEASE_TAG}.phar --no-progress + php kite-${RELEASE_TAG}.phar -V + + # https://github.com/actions/create-release + - uses: meeDamian/github-release@2.0 + with: + gzip: false + token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ env.RELEASE_TAG }} + name: ${{ env.RELEASE_TAG }} + files: kite-${{ env.RELEASE_TAG }}.phar From 159f36388826c6bb8e4d4b19c89cdbe2dfd73076 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 20 Nov 2020 19:51:20 +0800 Subject: [PATCH 055/258] fix ci error --- .travis.yml | 2 +- src/AbstractHandler.php | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d2cd7079..0969ef51 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: php php: # - '7.1' - - '7.2' +# - '7.2' - '7.3' - '7.4' diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index f95aa459..72d7b0c9 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -160,12 +160,19 @@ public function __construct(Input $input, Output $output, InputDefinition $defin $this->commentsVars = $this->annotationVars(); $this->init(); + + $this->afterInit(); } protected function init(): void { } + protected function afterInit(): void + { + // do something... + } + /** * Configure input definition for command, like symfony console. */ From ecfb9dddc74f71d1ef50dc2fca36fcd24ea1b477 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 23 Nov 2020 09:52:27 +0800 Subject: [PATCH 056/258] fix release script error --- .github/workflows/release.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2453628b..a33279f9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,12 +45,12 @@ jobs: # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" # Docs: https://getcomposer.org/doc/articles/scripts.md - - name: Build phar and send to github assets - run: | - echo $RELEASE_TAG - echo $RELEASE_NAME - php -d phar.readonly=0 bin/kite phar:pack -o kite-${RELEASE_TAG}.phar --no-progress - php kite-${RELEASE_TAG}.phar -V +# - name: Build phar and send to github assets +# run: | +# echo $RELEASE_TAG +# echo $RELEASE_NAME +# php -d phar.readonly=0 bin/kite phar:pack -o kite-${RELEASE_TAG}.phar --no-progress +# php kite-${RELEASE_TAG}.phar -V # https://github.com/actions/create-release - uses: meeDamian/github-release@2.0 @@ -59,4 +59,4 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} tag: ${{ env.RELEASE_TAG }} name: ${{ env.RELEASE_TAG }} - files: kite-${{ env.RELEASE_TAG }}.phar +# files: kite-${{ env.RELEASE_TAG }}.phar From 92377607d7daddb4e73fb7c9dea606f3e8addfe3 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 28 Dec 2020 11:32:51 +0800 Subject: [PATCH 057/258] up: support string for get same opts and args --- src/Concern/InputArgumentsTrait.php | 35 ++++++++++++-------- src/Concern/InputOptionsTrait.php | 46 ++++++++++++++++----------- src/Concern/InputOutputAwareTrait.php | 8 ++--- 3 files changed, 53 insertions(+), 36 deletions(-) diff --git a/src/Concern/InputArgumentsTrait.php b/src/Concern/InputArgumentsTrait.php index 2d8889e0..f2ec7bf0 100644 --- a/src/Concern/InputArgumentsTrait.php +++ b/src/Concern/InputArgumentsTrait.php @@ -252,30 +252,26 @@ public function getArrayArg($key, array $default = []): array /** * Get same args value - * eg: des description + * eg: des = description * * ```php + * $input->sameArg('des, description'); * $input->sameArg(['des', 'description']); * ``` * - * @param array $names - * @param mixed $default - * - * @return bool|mixed|null - */ - public function getSameArg(array $names, $default = null) - { - return $this->sameArg($names, $default); - } - - /** - * @param array $names + * @param string|array $names * @param mixed $default * * @return mixed */ - public function sameArg(array $names, $default = null) + public function getSameArg($names, $default = null) { + if (is_string($names)) { + $names = array_map('trim', explode(',', $names)); + } elseif (!is_array($names)) { + $names = (array)$names; + } + foreach ($names as $name) { if ($this->hasArg($name)) { return $this->get($name); @@ -285,6 +281,17 @@ public function sameArg(array $names, $default = null) return $default; } + /** + * @param string|array $names + * @param mixed $default + * + * @return mixed + */ + public function sameArg($names, $default = null) + { + return $this->getSameArg($names, $default); + } + /** * clear args */ diff --git a/src/Concern/InputOptionsTrait.php b/src/Concern/InputOptionsTrait.php index fcd85100..7ce4efc2 100644 --- a/src/Concern/InputOptionsTrait.php +++ b/src/Concern/InputOptionsTrait.php @@ -3,8 +3,12 @@ namespace Inhere\Console\Concern; use Inhere\Console\Exception\PromptException; +use function array_map; use function array_merge; +use function explode; +use function is_array; use function is_bool; +use function is_string; /** * Trait InputOptionsTrait @@ -13,7 +17,6 @@ */ trait InputOptionsTrait { - /** * Input short-opts data * @@ -99,12 +102,12 @@ public function getStringOpt(string $name, string $default = ''): string /** * Get an string option(long/short) value * - * @param string[] $names eg ['n', 'name'] + * @param string|string[] $names eg 'n,name' OR ['n', 'name'] * @param string $default * * @return string */ - public function getSameStringOpt(array $names, string $default = ''): string + public function getSameStringOpt($names, string $default = ''): string { return (string)$this->getSameOpt($names, $default); } @@ -140,12 +143,12 @@ public function getBoolOpt(string $name, bool $default = false): bool * Get (long/short)option value(bool) * eg: -h --help * - * @param string[] $names + * @param string|string[] $names eg 'n,name' OR ['n', 'name'] * @param bool $default * * @return bool */ - public function getSameBoolOpt(array $names, bool $default = false): bool + public function getSameBoolOpt($names, bool $default = false): bool { return (bool)$this->getSameOpt($names, $default); } @@ -180,36 +183,43 @@ public function hasOpt(string $name): bool * eg: -h --help * * ```php + * $input->sameOpt('h,help'); * $input->sameOpt(['h','help']); * ``` * - * @param array $names + * @param string|string[] $names eg 'n,name' OR ['n', 'name'] * @param mixed $default * * @return bool|mixed|null */ - public function getSameOpt(array $names, $default = null) + public function getSameOpt($names, $default = null) { - return $this->sameOpt($names, $default); + if (is_string($names)) { + $names = array_map('trim', explode(',', $names)); + } elseif (!is_array($names)) { + $names = (array)$names; + } + + foreach ($names as $name) { + if ($this->hasOpt($name)) { + return $this->getOpt($name); + } + } + + return $default; } /** * Alias of the getSameOpt() * - * @param array $names - * @param null $default + * @param string|array $names + * @param mixed $default * * @return bool|mixed|null */ - public function sameOpt(array $names, $default = null) + public function sameOpt($names, $default = null) { - foreach ($names as $name) { - if ($this->hasOpt($name)) { - return $this->getOpt($name); - } - } - - return $default; + return $this->getSameOpt($names, $default); } /** diff --git a/src/Concern/InputOutputAwareTrait.php b/src/Concern/InputOutputAwareTrait.php index cc8918cb..e6fa4f0e 100644 --- a/src/Concern/InputOutputAwareTrait.php +++ b/src/Concern/InputOutputAwareTrait.php @@ -90,13 +90,13 @@ public function getRequiredArg($name) } /** - * @param array $names + * @param string|array $names * @param mixed $default * * @return bool|mixed|null * @see Input::getSameArg() */ - public function getSameArg(array $names, $default = null) + public function getSameArg($names, $default = null) { return $this->input->getSameArg($names, $default); } @@ -113,12 +113,12 @@ public function getOpt($name, $default = null) } /** - * @param array $names + * @param string|string[] $names eg 'n,name' OR ['n', 'name'] * @param mixed $default * * @return mixed */ - public function getSameOpt(array $names, $default = null) + public function getSameOpt($names, $default = null) { return $this->input->getSameOpt($names, $default); } From 000360a4a63e59e63f0f92116b78f042a16c63a7 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 8 Jan 2021 11:58:01 +0800 Subject: [PATCH 058/258] feat: support use GROUP COMMAND for run an group command --- src/AbstractApplication.php | 38 +++++++++++++-------------- src/AbstractHandler.php | 18 +++++++------ src/Application.php | 26 +++++++++---------- src/Command.php | 12 +++++++++ src/Console.php | 47 +++++++++++++++++++++++++++++----- src/Controller.php | 36 +++++++++++++++++++++----- src/IO/AbstractInput.php | 17 ++++++++----- src/IO/Input.php | 2 +- src/IO/Input/ArrayInput.php | 2 +- test/ApplicationTest.php | 51 ++++++++++++++++++++----------------- 10 files changed, 164 insertions(+), 85 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index a2df162d..3c51fb52 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -64,7 +64,7 @@ abstract class AbstractApplication implements ApplicationInterface ]; /** @var array Application runtime stats */ - private $stats = [ + protected $stats = [ 'startTime' => 0, 'endTime' => 0, 'startMemory' => 0, @@ -72,19 +72,19 @@ abstract class AbstractApplication implements ApplicationInterface ]; /** @var array Application config data */ - private $config = [ - 'name' => 'My Console Application', - 'description' => 'This is my console application', - 'debug' => Console::VERB_ERROR, - 'profile' => false, - 'version' => '0.5.1', - 'publishAt' => '2017.03.24', - 'updateAt' => '2019.01.01', - 'rootPath' => '', - 'strictMode' => false, - 'hideRootPath' => true, + protected $config = [ + 'name' => 'My Console Application', + 'description' => 'This is my console application', + 'debug' => Console::VERB_ERROR, + 'profile' => false, + 'version' => '0.5.1', + 'publishAt' => '2017.03.24', + 'updateAt' => '2019.01.01', + 'rootPath' => '', + 'strictMode' => false, + 'hideRootPath' => true, // global options - 'no-interactive' => true, + 'no-interactive' => true, // 'timeZone' => 'Asia/Shanghai', // 'env' => 'prod', // dev test prod @@ -102,21 +102,19 @@ abstract class AbstractApplication implements ApplicationInterface /** * @var Router */ - private $router; + protected $router; /** * @var ErrorHandlerInterface Can custom error handler */ - private $errorHandler; + protected $errorHandler; /** * Class constructor. * - * @param array $config - * @param Input $input - * @param Output $output - * - * @throws InvalidArgumentException + * @param array $config + * @param Input|null $input + * @param Output|null $output */ public function __construct(array $config = [], Input $input = null, Output $output = null) { diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 72d7b0c9..04310087 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -39,8 +39,8 @@ use function is_array; use function is_int; use function is_string; +use function json_encode; use function preg_replace; -use function setproctitle; use function sprintf; use function strpos; use function strtr; @@ -553,6 +553,8 @@ protected function showHelp(): bool return false; } + $this->log(Console::VERB_DEBUG, 'display help by definition'); + // if has InputDefinition object. (The comment of the command will not be parsed and used at this time.) $help = $definition->getSynopsis(); // parse example @@ -613,11 +615,6 @@ protected function showHelpByMethodAnnotations(string $method, string $action = $ref = new ReflectionClass($this); $name = $this->input->getCommand(); - $this->log(Console::VERB_CRAZY, "display help info for the method=$method", [ - 'class' => static::class, - 'action' => $action, - ]); - if (!$ref->hasMethod($method)) { $this->write("The command [$name] don't exist in the group: " . static::getName()); return 0; @@ -639,6 +636,11 @@ protected function showHelpByMethodAnnotations(string $method, string $action = $help['Command:'] = sprintf('%s(alias: %s)', $realName, implode(',', $aliases)); } + $path = $this->input->getBinName() . ' ' . $name; + if ($action) { + $path .= " $action"; + } + // is an command object $isCommand = $ref->isSubclassOf(CommandInterface::class); foreach (array_keys(self::$annotationTags) as $tag) { @@ -650,7 +652,7 @@ protected function showHelpByMethodAnnotations(string $method, string $action = } if ($tag === 'usage') { - $help['Usage:'] = $this->commentsVars['binWithCmd'] . ' [--options ...] [arguments ...]'; + $help['Usage:'] = "$path [--options ...] [arguments ...]"; } continue; @@ -825,7 +827,7 @@ public function log(int $level, string $message, array $extra = []): void return; } - Console::log($message, $extra, $level); + Console::log($level, $message, $extra); } /** diff --git a/src/Application.php b/src/Application.php index bf20824c..015d99d3 100644 --- a/src/Application.php +++ b/src/Application.php @@ -21,7 +21,6 @@ use function is_object; use function is_string; use function method_exists; -use function sprintf; use function strlen; use function strpos; use function substr; @@ -62,7 +61,8 @@ public function controller(string $name, $class = null, $option = null) ]; } - $this->getRouter()->addGroup($name, $class, (array)$option); + $this->logf(Console::VERB_CRAZY, 'load group controller: %s', $name); + $this->router->addGroup($name, $class, (array)$option); return $this; } @@ -70,9 +70,9 @@ public function controller(string $name, $class = null, $option = null) /** * Add group/controller * - * @param string $name - * @param string|ControllerInterface $class The controller class - * @param null|array|string $option + * @param string $name + * @param string|ControllerInterface|null $class The controller class + * @param null|array|string $option * * @return Application|Contract\ApplicationInterface * @see controller() @@ -83,9 +83,9 @@ public function addGroup(string $name, $class = null, $option = null) } /** - * @param string $name - * @param string|ControllerInterface $class The controller class - * @param null|array|string $option + * @param string $name + * @param string|ControllerInterface|null $class The controller class + * @param null|array|string $option * * @return Application|Contract\ApplicationInterface * @see controller() @@ -123,7 +123,7 @@ public function setControllers(array $controllers): void */ public function addControllers(array $controllers): void { - $this->getRouter()->addControllers($controllers); + $this->router->addControllers($controllers); } /** @@ -137,7 +137,7 @@ public function command(string $name, $handler = null, $option = null) ]; } - $this->getRouter()->addCommand($name, $handler, (array)$option); + $this->router->addCommand($name, $handler, (array)$option); return $this; } @@ -164,7 +164,7 @@ public function addCommand(string $name, $handler = null, $option = null): self */ public function addCommands(array $commands): void { - $this->getRouter()->addCommands($commands); + $this->router->addCommands($commands); } /** @@ -262,7 +262,7 @@ public function dispatch(string $name, bool $detachedRun = false) $this->logf(Console::VERB_DEBUG, 'begin dispatch command: %s', $name); // match handler by input name - $info = $this->getRouter()->match($name); + $info = $this->router->match($name); // command not found if (!$info) { @@ -273,7 +273,7 @@ public function dispatch(string $name, bool $detachedRun = false) $this->output->error("The command '{$name}' is not exists!"); - $commands = $this->getRouter()->getAllNames(); + $commands = $this->router->getAllNames(); // find similar command names by similar_text() if ($similar = Helper::findSimilar($name, $commands)) { diff --git a/src/Command.php b/src/Command.php index 0ec5028c..d4c3a7c1 100644 --- a/src/Command.php +++ b/src/Command.php @@ -28,6 +28,16 @@ */ abstract class Command extends AbstractHandler implements CommandInterface { + /** + * @var Command + */ + protected $parent; + + /** + * @var Command[] + */ + protected $commands = []; + /* * Do execute command */ @@ -63,6 +73,8 @@ protected function showHelp(): bool $execMethod = 'execute'; $cmdAliases = static::aliases(); + $this->logf(Console::VERB_CRAZY, "display help info for the command: %s", static::$name); + return $this->showHelpByMethodAnnotations($execMethod, '', $cmdAliases) !== 0; } } diff --git a/src/Console.php b/src/Console.php index b5b07af8..af2af55f 100644 --- a/src/Console.php +++ b/src/Console.php @@ -7,12 +7,14 @@ use Toolkit\Cli\Cli; use Toolkit\Cli\ColorTag; use function date; +use function debug_backtrace; use function implode; use function is_numeric; use function json_encode; use function sprintf; use function strpos; use function trim; +use const DEBUG_BACKTRACE_IGNORE_ARGS; use const JSON_PRETTY_PRINT; use const JSON_UNESCAPED_SLASHES; use const PHP_EOL; @@ -92,6 +94,11 @@ public static function newApp( return new Application($config, $input, $output); } + /** + * @var int + */ + public static $traceIndex = 1; + /** * @param int $level * @param string $format @@ -99,15 +106,17 @@ public static function newApp( */ public static function logf(int $level, string $format, ...$args): void { + $datetime = date('Y/m/d H:i:s'); $levelName = self::LEVEL_NAMES[$level] ?? 'INFO'; $colorName = self::LEVEL2TAG[$level] ?? 'info'; - $taggedName = ColorTag::add($levelName, $colorName); + $message = strpos($format, '%') > 0 ? sprintf($format, ...$args) : $format; + $tagName = ColorTag::add($levelName, $colorName); - $datetime = date('Y/m/d H:i:s'); - $message = strpos($format, '%') > 0 ? sprintf($format, ...$args) : $format; + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, self::$traceIndex + 2); + $position = self::formatBacktrace($backtrace, self::$traceIndex); - self::writef('%s [%s] %s', $datetime, $taggedName, $message); + self::writef('%s [%s] [%s] %s', $datetime, $tagName, $position, $message); } /** @@ -124,13 +133,14 @@ public static function logf(int $level, string $format, ...$args): void * 'coId' => 12, * ] */ - public static function log(string $msg, array $data = [], int $level = self::VERB_DEBUG, array $opts = []): void + public static function log(int $level, string $msg, array $data = [], array $opts = []): void { $levelName = self::LEVEL_NAMES[$level] ?? 'INFO'; $colorName = self::LEVEL2TAG[$level] ?? 'info'; $taggedName = ColorTag::add($levelName, $colorName); $userOpts = []; + $datetime = date('Y/m/d H:i:s'); foreach ($opts as $n => $v) { if (is_numeric($n) || strpos($n, '_') === 0) { $userOpts[] = "[$v]"; @@ -140,8 +150,31 @@ public static function log(string $msg, array $data = [], int $level = self::VER } $optString = $userOpts ? ' ' . implode(' ', $userOpts) : ''; - $dataString = $data ? PHP_EOL . json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) : ''; + $dataString = $data ? json_encode($data, JSON_UNESCAPED_SLASHES) : ''; + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, self::$traceIndex + 2); + $position = self::formatBacktrace($backtrace, self::$traceIndex); + + self::writef('%s [%s] [%s]%s %s %s', $datetime, $taggedName, $position, $optString, trim($msg), $dataString); + } + + /** + * @param array $traces + * @param int $index + * + * @return string + */ + private static function formatBacktrace(array $traces, int $index): string + { + $position = 'unknown'; + + if (isset($traces[$index+1])) { + $tInfo = $traces[$index]; + $prev = $traces[$index+1]; + + $position = sprintf('%s.%s:%d', $prev['class'], $prev['function'] ?? 'UNKNOWN', $tInfo['line']); + } - self::writef('%s [%s]%s %s %s', date('Y/m/d H:i:s'), $taggedName, $optString, trim($msg), $dataString); + return ColorTag::add($position, 'green'); } } diff --git a/src/Controller.php b/src/Controller.php index bd01a288..7a97e271 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -191,13 +191,25 @@ public function run(string $command = '') { if (!$command = trim($command, $this->delimiter)) { $command = $this->defaultAction; + + if (!$command) { + // use next arg as sub-command name. + $command = $this->input->findCommandName(); + } } - $this->action = Str::camelCase($this->getRealCommandName($command)); - if (!$this->action) { + // not input command + if (!$command) { + $this->logf(Console::VERB_DEBUG, 'group action is empty, display help for the group'); + return $this->showHelp(); } + $command = $this->getRealCommandName($command); + + $this->action = Str::camelCase($command); + $this->logf(Console::VERB_DEBUG, 'will run the group action: %s, command: %s', $this->action, $command); + // do running return parent::run($command); } @@ -344,7 +356,7 @@ final public function helpCommand(): int $action = $this->action; // Not input action, for all sub-commands of the controller - if (!$action && !($action = $this->getFirstArg())) { + if (!$action) { $this->showCommandList(); return 0; } @@ -363,6 +375,11 @@ final public function helpCommand(): int } } + $this->log(Console::VERB_DEBUG, "display help for the controller method: $method", [ + 'group' => static::$name, + 'action' => $action, + ]); + // For a specified sub-command. return $this->showHelpByMethodAnnotations($method, $action, $aliases); } @@ -379,6 +396,8 @@ protected function beforeShowCommandList(): void */ final public function showCommandList(): void { + $this->logf(Console::VERB_DEBUG, 'display all sub-commands list of the group: %s', static::$name); + $this->beforeShowCommandList(); $ref = new ReflectionClass($this); @@ -435,12 +454,17 @@ final public function showCommandList(): void $script = $this->getScriptName(); + // if is alone running. if ($detached = $this->isDetached()) { $name = $sName . ' '; $usage = "$script {command} [--options ...] [arguments ...]"; } else { $name = $sName . $this->delimiter; - $usage = "$script {$name}{command} [--options ...] [arguments ...]"; + // $usage = "$script {$name}{command} [--options ...] [arguments ...]"; + $usage = [ + "$script {$name}{command} [--options ...] [arguments ...]", + "$script {$sName} {command} [--options ...] [arguments ...]", + ]; } $globalOptions = array_merge(Application::getGlobalOptions(), static::$globalOptions); @@ -456,8 +480,8 @@ final public function showCommandList(): void 'sepChar' => ' ', ]); - $msgTpl = 'More information about a command, please use: %s %s{command} -h'; - $this->output->write(sprintf($msgTpl, $script, $detached ? '' : $name)); + $msgTpl = 'More information about a command, please see: %s %s {command} -h'; + $this->output->write(sprintf($msgTpl, $script, $detached ? '' : $sName)); $this->output->flush(); } diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index 32d8db20..13129d91 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -87,19 +87,20 @@ public function __toString(): string abstract public function toString(): string; /** - * find command name. it is first argument. + * find command name, it is first argument. + * TIP: will reset args data after founded. */ - protected function findCommand(): void + public function findCommandName(): string { if (!isset($this->args[0])) { - return; + return ''; } + $command = ''; $newArgs = []; - foreach ($this->args as $key => $value) { if ($key === 0) { - $this->command = trim($value); + $command = trim($value); } elseif (is_int($key)) { $newArgs[] = $value; } else { @@ -107,7 +108,11 @@ protected function findCommand(): void } } - $this->args = $newArgs; + if ($command) { + $this->args = $newArgs; + } + + return $command; } /*********************************************************************************** diff --git a/src/IO/Input.php b/src/IO/Input.php index 08246bbe..354c1f4a 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -90,7 +90,7 @@ protected function doParse(array $args): void ] = Flags::parseArgv($args); // find command name - $this->findCommand(); + $this->command = $this->findCommandName(); } public function resetInputStream(): void diff --git a/src/IO/Input/ArrayInput.php b/src/IO/Input/ArrayInput.php index 1ec9d0d1..c1dabbf2 100644 --- a/src/IO/Input/ArrayInput.php +++ b/src/IO/Input/ArrayInput.php @@ -45,6 +45,6 @@ protected function doParse(array $args): void ] = Flags::parseArray($args); // find command name - $this->findCommand(); + $this->command = $this->findCommandName(); } } diff --git a/test/ApplicationTest.php b/test/ApplicationTest.php index da85a699..3d580724 100644 --- a/test/ApplicationTest.php +++ b/test/ApplicationTest.php @@ -13,11 +13,16 @@ use function get_class; use function strpos; +/** + * Class ApplicationTest + * + * @package Inhere\ConsoleTest + */ class ApplicationTest extends TestCase { protected function assertStringContains(string $string, string $contains): void { - $this->assertNotSame(false, strpos($string, $contains), "string \"$string\" not contains: $contains"); + self::assertNotFalse(strpos($string, $contains), "string \"$string\" not contains: $contains"); } private function newApp(array $args = null): Application @@ -37,11 +42,11 @@ public function testApp(): void 'name' => 'Tests', ]); - $this->assertArrayHasKey('name', $app->getConfig()); - $this->assertEquals('Tests', $app->getName()); - $this->assertEquals('Tests', $app->getParam('name')); + self::assertArrayHasKey('name', $app->getConfig()); + self::assertEquals('Tests', $app->getName()); + self::assertEquals('Tests', $app->getParam('name')); - $this->assertInstanceOf(InputInterface::class, $app->getInput()); + self::assertInstanceOf(InputInterface::class, $app->getInput()); } public function testAddCommand(): void @@ -54,10 +59,10 @@ public function testAddCommand(): void $router = $app->getRouter(); - $this->assertTrue($router->isCommand('test')); - $this->assertFalse($router->isController('test')); - $this->assertArrayHasKey('test', $router->getCommands()); - $this->assertContains('test', $router->getCommandNames()); + self::assertTrue($router->isCommand('test')); + self::assertFalse($router->isController('test')); + self::assertArrayHasKey('test', $router->getCommands()); + self::assertContains('test', $router->getCommandNames()); } public function testAddCommandError(): void @@ -66,14 +71,14 @@ public function testAddCommandError(): void try { $app->addCommand(''); } catch (Throwable $e) { - $this->assertSame(get_class($e), InvalidArgumentException::class); + self::assertSame(get_class($e), InvalidArgumentException::class); } try { $app->addCommand('test', 'invalid'); } catch (Throwable $e) { - $this->assertSame(get_class($e), InvalidArgumentException::class); - $this->assertSame($e->getMessage(), 'The console command class [invalid] not exists!'); + self::assertSame(get_class($e), InvalidArgumentException::class); + self::assertSame($e->getMessage(), 'The console command class [invalid] not exists!'); } } @@ -89,7 +94,7 @@ public function testRunCommand(): void }); $ret = $app->run(false); - $this->assertSame('hello', $ret); + self::assertSame('hello', $ret); } public function testAddController(): void @@ -100,13 +105,13 @@ public function testAddController(): void $router = $app->getRouter(); - $this->assertTrue($app->getRouter()->isController('test')); - $this->assertFalse($app->getRouter()->isCommand('test')); - $this->assertArrayHasKey('test', $router->getControllers()); + self::assertTrue($app->getRouter()->isController('test')); + self::assertFalse($app->getRouter()->isCommand('test')); + self::assertArrayHasKey('test', $router->getControllers()); $group = $router->getControllers()['test']; - $this->assertSame(TestController::class, $group['handler']); - $this->assertSame(Router::TYPE_GROUP, $group['type']); + self::assertSame(TestController::class, $group['handler']); + self::assertSame(Router::TYPE_GROUP, $group['type']); } public function testAddControllerError(): void @@ -116,15 +121,15 @@ public function testAddControllerError(): void try { $app->addController(''); } catch (Throwable $e) { - $this->assertSame(get_class($e), InvalidArgumentException::class); + self::assertSame(get_class($e), InvalidArgumentException::class); $this->assertStringContains($e->getMessage(), '"name" and "controller" cannot be empty'); } try { $app->controller('test', 'invalid'); } catch (Throwable $e) { - $this->assertSame(get_class($e), InvalidArgumentException::class); - $this->assertSame($e->getMessage(), 'The console controller class [invalid] not exists!'); + self::assertSame(get_class($e), InvalidArgumentException::class); + self::assertSame($e->getMessage(), 'The console controller class [invalid] not exists!'); } } @@ -138,7 +143,7 @@ public function testRunController(): void $app->controller('test', TestController::class); $ret = $app->run(false); - $this->assertSame('Inhere\ConsoleTest\TestController::demoCommand', $ret); + self::assertSame('Inhere\ConsoleTest\TestController::demoCommand', $ret); } public function testTriggerEvent(): void @@ -155,6 +160,6 @@ public function testTriggerEvent(): void $app->addCommand('test1', TestCommand::class); $ret = $app->run(false); - $this->assertSame('Inhere\ConsoleTest\TestCommand::execute', $ret); + self::assertSame('Inhere\ConsoleTest\TestCommand::execute', $ret); } } From c1515b20b08b424e90d263f16f22b1fc32155cc6 Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 21 Jan 2021 22:36:35 +0800 Subject: [PATCH 059/258] update some for output info --- src/AbstractApplication.php | 6 +- src/Command.php | 2 + src/Concern/FormatOutputAwareTrait.php | 223 +---------------------- src/Concern/StyledOutputAwareTrait.php | 237 +++++++++++++++++++++++++ 4 files changed, 246 insertions(+), 222 deletions(-) create mode 100644 src/Concern/StyledOutputAwareTrait.php diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 3c51fb52..86e60f46 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -10,6 +10,7 @@ use ErrorException; use Inhere\Console\Component\ErrorHandler; +use Inhere\Console\Concern\StyledOutputAwareTrait; use Inhere\Console\Contract\ApplicationInterface; use Inhere\Console\Contract\ErrorHandlerInterface; use Inhere\Console\Contract\InputInterface; @@ -44,7 +45,10 @@ */ abstract class AbstractApplication implements ApplicationInterface { - use ApplicationHelpTrait, InputOutputAwareTrait, SimpleEventTrait; + use ApplicationHelpTrait; + use InputOutputAwareTrait; + use StyledOutputAwareTrait; + use SimpleEventTrait; /** @var array */ protected static $internalCommands = [ diff --git a/src/Command.php b/src/Command.php index d4c3a7c1..908eda9c 100644 --- a/src/Command.php +++ b/src/Command.php @@ -34,6 +34,8 @@ abstract class Command extends AbstractHandler implements CommandInterface protected $parent; /** + * sub-commands of the command + * * @var Command[] */ protected $commands = []; diff --git a/src/Concern/FormatOutputAwareTrait.php b/src/Concern/FormatOutputAwareTrait.php index 8c477289..ab42ff9c 100644 --- a/src/Concern/FormatOutputAwareTrait.php +++ b/src/Concern/FormatOutputAwareTrait.php @@ -8,70 +8,20 @@ namespace Inhere\Console\Concern; -use Closure; -use Generator; -use Inhere\Console\Component\Formatter\HelpPanel; -use Inhere\Console\Component\Formatter\MultiList; -use Inhere\Console\Component\Formatter\Panel; -use Inhere\Console\Component\Formatter\Section; -use Inhere\Console\Component\Formatter\SingleList; -use Inhere\Console\Component\Formatter\Table; -use Inhere\Console\Component\Formatter\Title; -use Toolkit\Cli\Style; use Inhere\Console\Console; -use Inhere\Console\Util\Interact; -use Inhere\Console\Util\Show; -use LogicException; use Toolkit\Stdlib\Php; use function array_merge; use function json_encode; -use function method_exists; -use function sprintf; -use function strpos; -use function substr; /** * Class FormatOutputAwareTrait * * @package Inhere\Console\Traits - * - * @method int info($messages, $quit = false) - * @method int note($messages, $quit = false) - * @method int notice($messages, $quit = false) - * @method int success($messages, $quit = false) - * @method int primary($messages, $quit = false) - * @method int warning($messages, $quit = false) - * @method int danger($messages, $quit = false) - * @method int error($messages, $quit = false) - * - * @method int liteInfo($messages, $quit = false) - * @method int liteNote($messages, $quit = false) - * @method int liteNotice($messages, $quit = false) - * @method int liteSuccess($messages, $quit = false) - * @method int litePrimary($messages, $quit = false) - * @method int liteWarning($messages, $quit = false) - * @method int liteDanger($messages, $quit = false) - * @method int liteError($messages, $quit = false) - * - * @method padding(array $data, string $title = null, array $opts = []) - * - * @method splitLine(string $title, string $char = '-', int $width = 0) - * @method spinner($msg = '', $ended = false) - * @method loading($msg = 'Loading ', $ended = false) - * @method pending($msg = 'Pending ', $ended = false) - * @method pointing($msg = 'handling ', $ended = false) - * - * @method Generator counterTxt($msg = 'Pending ', $ended = false) - * - * @method confirm(string $question, bool $default = true): bool - * @method unConfirm(string $question, bool $default = true): bool - * @method select(string $description, $options, $default = null, bool $allowExit = true): string - * @method checkbox(string $description, $options, $default = null, bool $allowExit = true): array - * @method ask(string $question, string $default = '', Closure $validator = null): string - * @method askPassword(string $prompt = 'Enter Password:'): string */ trait FormatOutputAwareTrait { + use StyledOutputAwareTrait; + /** * @inheritdoc * @see Console::write() @@ -139,175 +89,6 @@ public function writeRaw($text, bool $nl = true, $quit = false, array $opts = [] return Console::writeRaw($text, $nl, $quit, $opts); } - /** - * @param string $text - * @param string $tag - * - * @return int - */ - public function colored(string $text, string $tag = 'info'): int - { - return $this->writeln(sprintf('<%s>%s', $tag, $text, $tag)); - } - - /** - * @param array|mixed $messages - * @param string $type - * @param string $style - * @param bool $quit - * - * @return int - */ - public function block($messages, string $type = 'MESSAGE', string $style = Style::NORMAL, $quit = false): int - { - return Show::block($messages, $type, $style, $quit); - } - - /** - * @param array|mixed $messages - * @param string $type - * @param string $style - * @param bool $quit - * - * @return int - */ - public function liteBlock($messages, string $type = 'MESSAGE', string $style = Style::NORMAL, $quit = false): int - { - return Show::liteBlock($messages, $type, $style, $quit); - } - - /** - * @param string $title - * @param array $opts - */ - public function title(string $title, array $opts = []): void - { - Title::show($title, $opts); - } - - /** - * @param string $title - * @param string|array $body The section body message - * @param array $opts - */ - public function section(string $title, $body, array $opts = []): void - { - Section::show($title, $body, $opts); - } - - /** - * @param array|mixed $data - * @param string $title - * @param array $opts - */ - public function aList($data, string $title = 'Information', array $opts = []): void - { - SingleList::show($data, $title, $opts); - } - - /** - * @param array $data - * @param array $opts - */ - public function multiList(array $data, array $opts = []): void - { - MultiList::show($data, $opts); - } - - /** - * @param array $data - * @param array $opts - */ - public function mList(array $data, array $opts = []): void - { - MultiList::show($data, $opts); - } - - /** - * @param array $config - */ - public function helpPanel(array $config): void - { - HelpPanel::show($config); - } - - /** - * @param array $data - * @param string $title - * @param array $opts - */ - public function panel(array $data, string $title = 'Information panel', array $opts = []): void - { - Panel::show($data, $title, $opts); - } - - /** - * @param array $data - * @param string $title - * @param array $opts - */ - public function table(array $data, string $title = 'Data Table', array $opts = []): void - { - Table::show($data, $title, $opts); - } - - /** - * @param int $total - * @param string $msg - * @param string $doneMsg - * - * @return Generator - */ - public function progressTxt(int $total, string $msg, string $doneMsg = ''): Generator - { - return Show::progressTxt($total, $msg, $doneMsg); - } - - /** - * @inheritdoc - * @see Show::progressBar() - */ - public function progressBar($total, array $opts = []): Generator - { - return Show::progressBar($total, $opts); - } - - /** - * @param string $method - * @param array $args - * - * @return int - * @throws LogicException - */ - public function __call($method, array $args = []) - { - $map = Show::getBlockMethods(false); - - if (isset($map[$method])) { - $msg = $args[0]; - $quit = $args[1] ?? false; - $style = $map[$method]; - - if (0 === strpos($method, 'lite')) { - $type = substr($method, 4); - return Show::liteBlock($msg, $type === 'Primary' ? 'IMPORTANT' : $type, $style, $quit); - } - - return Show::block($msg, $style === 'primary' ? 'IMPORTANT' : $style, $style, $quit); - } - - if (method_exists(Show::class, $method)) { - return Show::$method(...$args); - } - - // interact methods - if (method_exists(Interact::class, $method)) { - return Interact::$method(...$args); - } - - throw new LogicException("Call a not exists method: $method of the " . static::class); - } - /** * @param mixed $data * @param bool $echo diff --git a/src/Concern/StyledOutputAwareTrait.php b/src/Concern/StyledOutputAwareTrait.php new file mode 100644 index 00000000..fc5cef41 --- /dev/null +++ b/src/Concern/StyledOutputAwareTrait.php @@ -0,0 +1,237 @@ +writeln(sprintf('<%s>%s', $tag, $text, $tag)); + } + + /** + * @param array|mixed $messages + * @param string $type + * @param string $style + * @param bool $quit + * + * @return int + */ + public function block($messages, string $type = 'MESSAGE', string $style = Style::NORMAL, $quit = false): int + { + return Show::block($messages, $type, $style, $quit); + } + + /** + * @param array|mixed $messages + * @param string $type + * @param string $style + * @param bool $quit + * + * @return int + */ + public function liteBlock($messages, string $type = 'MESSAGE', string $style = Style::NORMAL, $quit = false): int + { + return Show::liteBlock($messages, $type, $style, $quit); + } + + /** + * @param string $title + * @param array $opts + */ + public function title(string $title, array $opts = []): void + { + Title::show($title, $opts); + } + + /** + * @param string $title + * @param string|array $body The section body message + * @param array $opts + */ + public function section(string $title, $body, array $opts = []): void + { + Section::show($title, $body, $opts); + } + + /** + * @param array|mixed $data + * @param string $title + * @param array $opts + */ + public function aList($data, string $title = 'Information', array $opts = []): void + { + SingleList::show($data, $title, $opts); + } + + /** + * @param array $data + * @param array $opts + */ + public function multiList(array $data, array $opts = []): void + { + MultiList::show($data, $opts); + } + + /** + * @param array $data + * @param array $opts + */ + public function mList(array $data, array $opts = []): void + { + MultiList::show($data, $opts); + } + + /** + * @param array $config + */ + public function helpPanel(array $config): void + { + HelpPanel::show($config); + } + + /** + * @param array $data + * @param string $title + * @param array $opts + */ + public function panel(array $data, string $title = 'Information panel', array $opts = []): void + { + Panel::show($data, $title, $opts); + } + + /** + * @param array $data + * @param string $title + * @param array $opts + */ + public function table(array $data, string $title = 'Data Table', array $opts = []): void + { + Table::show($data, $title, $opts); + } + + /** + * @param int $total + * @param string $msg + * @param string $doneMsg + * + * @return Generator + */ + public function progressTxt(int $total, string $msg, string $doneMsg = ''): Generator + { + return Show::progressTxt($total, $msg, $doneMsg); + } + + /** + * @param integer $total + * @param array $opts + * + * @return Generator + * @see Show::progressBar() + */ + public function progressBar($total, array $opts = []): Generator + { + return Show::progressBar($total, $opts); + } + + /** + * @param string $method + * @param array $args + * + * @return int + * @throws LogicException + */ + public function __call($method, array $args = []) + { + $map = Show::getBlockMethods(false); + + if (isset($map[$method])) { + $msg = $args[0]; + $quit = $args[1] ?? false; + $style = $map[$method]; + + if (0 === strpos($method, 'lite')) { + $type = substr($method, 4); + return Show::liteBlock($msg, $type === 'Primary' ? 'IMPORTANT' : $type, $style, $quit); + } + + return Show::block($msg, $style === 'primary' ? 'IMPORTANT' : $style, $style, $quit); + } + + if (method_exists(Show::class, $method)) { + return Show::$method(...$args); + } + + // interact methods + if (method_exists(Interact::class, $method)) { + return Interact::$method(...$args); + } + + throw new LogicException("Call a not exists method: $method of the " . static::class); + } + +} From f837631632c16e075d2b9dfe4be9b72cf88dd053 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 22 Jan 2021 22:44:06 +0800 Subject: [PATCH 060/258] update some logic for dispatch. add more logs --- src/AbstractApplication.php | 6 +++++- src/Application.php | 25 +++++++++++++++++-------- src/Console.php | 3 ++- src/Contract/ApplicationInterface.php | 5 ++++- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 86e60f46..4e61a36f 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -149,6 +149,8 @@ protected function init(): void } $this->registerErrorHandle(); + + $this->logf(Console::VERB_DEBUG, 'console application init completed'); } /** @@ -209,6 +211,8 @@ public function run(bool $exit = true) { $command = trim($this->input->getCommand(), $this->delimiter); + $this->logf(Console::VERB_DEBUG, 'begin run the application, command is: %s', $command); + try { $this->prepareRun(); @@ -537,7 +541,7 @@ public function getConfig(): array /** * Get config param value * - * @param null|string $name + * @param string $name * @param null|string $default * * @return array|string diff --git a/src/Application.php b/src/Application.php index 015d99d3..619ef298 100644 --- a/src/Application.php +++ b/src/Application.php @@ -21,9 +21,11 @@ use function is_object; use function is_string; use function method_exists; +use function str_replace; use function strlen; use function strpos; use function substr; +use function trim; /** * Class App @@ -255,25 +257,33 @@ protected function getFileFilter(): callable /** * @inheritdoc * @throws ReflectionException - * @throws InvalidArgumentException */ public function dispatch(string $name, bool $detachedRun = false) { - $this->logf(Console::VERB_DEBUG, 'begin dispatch command: %s', $name); + if (!$name = trim($name)) { + throw new InvalidArgumentException('cannot dispatch an empty command'); + } + + $cmdId = $name; + $this->logf(Console::VERB_DEBUG, 'begin dispatch the input command: %s', $name); + + // format is: `group action` + if (strpos($name, ' ') > 0) { + $cmdId = str_replace(' ', $this->delimiter, $name); + } // match handler by input name - $info = $this->router->match($name); + $info = $this->router->match($cmdId); // command not found if (!$info) { - if (true === $this->fire(self::ON_NOT_FOUND, $name, $this)) { + if (true === $this->fire(self::ON_NOT_FOUND, $cmdId, $this)) { $this->logf(Console::VERB_DEBUG, 'not found handle by user, command: %s', $name); return 0; } - $this->output->error("The command '{$name}' is not exists!"); - $commands = $this->router->getAllNames(); + $this->output->error("The command '{$name}' is not exists!"); // find similar command names by similar_text() if ($similar = Helper::findSimilar($name, $commands)) { @@ -287,9 +297,8 @@ public function dispatch(string $name, bool $detachedRun = false) return 2; } - $cmdOptions = $info['options']; - // save command ID + $cmdOptions = $info['options']; $this->input->setCommandId($info['cmdId']); // is command diff --git a/src/Console.php b/src/Console.php index af2af55f..caa55a20 100644 --- a/src/Console.php +++ b/src/Console.php @@ -171,8 +171,9 @@ private static function formatBacktrace(array $traces, int $index): string if (isset($traces[$index+1])) { $tInfo = $traces[$index]; $prev = $traces[$index+1]; + $type = $prev['type']; - $position = sprintf('%s.%s:%d', $prev['class'], $prev['function'] ?? 'UNKNOWN', $tInfo['line']); + $position = sprintf('%s%s%s(),L%d', $prev['class'], $type, $prev['function'] ?? 'UNKNOWN', $tInfo['line']); } return ColorTag::add($position, 'green'); diff --git a/src/Contract/ApplicationInterface.php b/src/Contract/ApplicationInterface.php index 0d08bd07..208b2f80 100644 --- a/src/Contract/ApplicationInterface.php +++ b/src/Contract/ApplicationInterface.php @@ -39,7 +39,10 @@ public function run(bool $exit = true); /** * Dispatch input command, exec found command handler. * - * @param string $name Inputted command name + * @param string $name Inputted command name. allow: + * - 'command' + * - 'group:action' + * - 'group action' * @param bool $detachedRun Use for an group commands execution alone * * @return int|mixed From 81a36bf09e69131eb7636a00058b5e56176d13f6 Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 23 Jan 2021 12:11:12 +0800 Subject: [PATCH 061/258] up: update event handle and some help render logic --- TODO.md | 5 + src/AbstractApplication.php | 26 +- src/AbstractHandler.php | 228 ++---------------- src/Application.php | 2 +- src/Concern/ApplicationHelpTrait.php | 2 + src/Concern/AttachApplicationTrait.php | 154 ++++++++++++ src/Concern/CommandHelpTrait.php | 92 +++++++ src/Concern/InputOutputAwareTrait.php | 22 -- ...entTrait.php => SimpleEventAwareTrait.php} | 2 +- src/Console.php | 12 +- src/ConsoleEvent.php | 37 ++- src/Controller.php | 108 +++++---- 12 files changed, 397 insertions(+), 293 deletions(-) create mode 100644 TODO.md create mode 100644 src/Concern/AttachApplicationTrait.php create mode 100644 src/Concern/CommandHelpTrait.php rename src/Concern/{SimpleEventTrait.php => SimpleEventAwareTrait.php} (99%) diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..dd9fa863 --- /dev/null +++ b/TODO.md @@ -0,0 +1,5 @@ +# TODO + +- [x] Add more events supports +- [ ] Add nested command supports +- [ ] Simpler bash command completion support diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 4e61a36f..102f5400 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -19,7 +19,7 @@ use Inhere\Console\Contract\OutputInterface; use Inhere\Console\Concern\ApplicationHelpTrait; use Inhere\Console\Concern\InputOutputAwareTrait; -use Inhere\Console\Concern\SimpleEventTrait; +use Inhere\Console\Concern\SimpleEventAwareTrait; use InvalidArgumentException; use Throwable; use Toolkit\Cli\Style; @@ -48,7 +48,7 @@ abstract class AbstractApplication implements ApplicationInterface use ApplicationHelpTrait; use InputOutputAwareTrait; use StyledOutputAwareTrait; - use SimpleEventTrait; + use SimpleEventAwareTrait; /** @var array */ protected static $internalCommands = [ @@ -222,13 +222,13 @@ public function run(bool $exit = true) } // call 'onBeforeRun' service, if it is registered. - $this->fire(self::ON_BEFORE_RUN, $this); + $this->fire(ConsoleEvent::ON_BEFORE_RUN, $this); $this->beforeRun(); // do run ... $result = $this->dispatch($command); } catch (Throwable $e) { - $this->fire(self::ON_RUN_ERROR, $e, $this); + $this->fire(ConsoleEvent::ON_RUN_ERROR, $e, $this); $result = $e->getCode() === 0 ? $e->getLine() : $e->getCode(); $this->handleException($e); } @@ -236,7 +236,7 @@ public function run(bool $exit = true) $this->stats['endTime'] = microtime(true); // call 'onAfterRun' service, if it is registered. - $this->fire(self::ON_AFTER_RUN, $this); + $this->fire(ConsoleEvent::ON_AFTER_RUN, $this); $this->afterRun(); if ($exit) { @@ -559,6 +559,18 @@ public function isStrictMode(): bool return (bool)$this->config['strictMode']; } + /** + * check is given verbose level + * + * @param int $level + * + * @return bool + */ + public function isDebug(int $level = Console::VERB_DEBUG): bool + { + return $level <= $this->getVerbLevel(); + } + /** * get current debug level value * @@ -572,7 +584,7 @@ public function getVerbLevel(): int } /** - * is profile + * is open profile * * @return boolean */ @@ -585,7 +597,7 @@ public function isProfile(): bool } /** - * is interactive env + * is open interactive env * * @return bool */ diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 04310087..fa63e22e 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -8,6 +8,8 @@ namespace Inhere\Console; +use Inhere\Console\Concern\AttachApplicationTrait; +use Inhere\Console\Concern\CommandHelpTrait; use Inhere\Console\Contract\CommandHandlerInterface; use Inhere\Console\Contract\CommandInterface; use Inhere\Console\IO\Input; @@ -39,11 +41,8 @@ use function is_array; use function is_int; use function is_string; -use function json_encode; use function preg_replace; use function sprintf; -use function strpos; -use function strtr; use function ucfirst; use const ARRAY_FILTER_USE_BOTH; use const PHP_EOL; @@ -56,10 +55,13 @@ */ abstract class AbstractHandler implements CommandHandlerInterface { - use InputOutputAwareTrait, UserInteractAwareTrait; + use AttachApplicationTrait; + Use CommandHelpTrait; + use InputOutputAwareTrait; + use UserInteractAwareTrait; /** - * command name e.g 'test' 'test:one' + * group/command name e.g 'test' 'test:one' * * @var string */ @@ -93,11 +95,6 @@ abstract class AbstractHandler implements CommandHandlerInterface 'help' => true, ]; - /** - * @var Application - */ - protected $app; - /** * @var InputDefinition|null */ @@ -108,18 +105,6 @@ abstract class AbstractHandler implements CommandHandlerInterface */ protected $processTitle = ''; - /** - * @var array - */ - private $commentsVars; - - /** - * Mark the command/controller is attached in application. - * - * @var bool - */ - private $attached = true; - /** * Whether enabled * @@ -253,17 +238,19 @@ public function run(string $command = '') // load input definition configure $this->configure(); - if ($this->input->sameOpt(['h', 'help'])) { + // if with option: -h|--help + if ($this->input->getSameBoolOpt(['h', 'help'])) { $this->showHelp(); return 0; } // some prepare check + // - validate input arguments if (true !== $this->prepare()) { return -1; } - // return False to deny go on + // return False to deny goon run. if (false === $this->beforeExecute()) { return -1; } @@ -293,12 +280,10 @@ public function coroutineRun(): bool // $ch->push($result); }); - // create co fail: + // if create co fail if ((int)$ok === 0) { // if open debug, output a tips - if ($this->isDebug()) { - $this->output->warning('The coroutine create failed!'); - } + $this->logf(Console::VERB_DEBUG, 'ERROR: The coroutine create failed'); // exec by normal flow $result = $this->execute($this->input, $this->output); @@ -373,6 +358,8 @@ public function validateInput(): bool return true; } + $this->logf(Console::VERB_DEBUG, 'validate the input arguments and options by Definition'); + $in = $this->input; $out = $this->output; @@ -473,63 +460,6 @@ private function checkNotExistsOptions(InputDefinition $def): void * helper methods **************************************************************************/ - /** - * @param string $name - * @param string|array $value - */ - protected function addCommentsVar(string $name, $value): void - { - if (!isset($this->commentsVars[$name])) { - $this->setCommentsVar($name, $value); - } - } - - /** - * @param array $map - */ - protected function addCommentsVars(array $map): void - { - foreach ($map as $name => $value) { - $this->setCommentsVar($name, $value); - } - } - - /** - * @param string $name - * @param string|array $value - */ - protected function setCommentsVar(string $name, $value): void - { - $this->commentsVars[$name] = is_array($value) ? implode(',', $value) : (string)$value; - } - - /** - * 替换注解中的变量为对应的值 - * - * @param string $str - * - * @return string - */ - protected function parseCommentsVars(string $str): string - { - // not use vars - if (false === strpos($str, self::HELP_VAR_LEFT)) { - return $str; - } - - static $map; - - if ($map === null) { - foreach ($this->commentsVars as $key => $value) { - $key = self::HELP_VAR_LEFT . $key . self::HELP_VAR_RIGHT; - // save - $map[$key] = $value; - } - } - - return $map ? strtr($str, $map) : $str; - } - /** * @return bool */ @@ -553,7 +483,7 @@ protected function showHelp(): bool return false; } - $this->log(Console::VERB_DEBUG, 'display help by definition'); + $this->log(Console::VERB_DEBUG, 'display help information by definition'); // if has InputDefinition object. (The comment of the command will not be parsed and used at this time.) $help = $definition->getSynopsis(); @@ -575,7 +505,7 @@ protected function showHelp(): bool $binName = $this->getScriptName(); // build usage - if ($this->attached) { + if ($this->isAttached()) { $help['usage:'] = sprintf('%s %s %s', $binName, $this->getCommandName(), $help['usage:']); } else { $help['usage:'] = $binName . ' ' . $help['usage:']; @@ -604,7 +534,7 @@ protected function showHelp(): bool * Display command/action help by parse method annotations * * @param string $method - * @param string $action + * @param string $action action of an group * @param array $aliases * * @return int @@ -626,6 +556,8 @@ protected function showHelpByMethodAnnotations(string $method, string $action = return 0; } + $this->logf(Console::VERB_DEBUG, "render help for the command: %s", $this->input->getCommandId()); + $help = []; $doc = $ref->getMethod($method)->getDocComment(); $tags = PhpDoc::getTags($this->parseCommentsVars($doc)); @@ -636,9 +568,12 @@ protected function showHelpByMethodAnnotations(string $method, string $action = $help['Command:'] = sprintf('%s(alias: %s)', $realName, implode(',', $aliases)); } - $path = $this->input->getBinName() . ' ' . $name; + $binName = $this->input->getBinName(); + + $path = $binName . ' ' . $name; if ($action) { - $path .= " $action"; + $group = static::getName(); + $path = "$binName $group $action"; } // is an command object @@ -683,6 +618,7 @@ protected function showHelpByMethodAnnotations(string $method, string $action = $this->beforeRenderCommandHelp($help); + $this->output->mList($help, [ 'sepChar' => ' ', 'lastNewline' => 0, @@ -774,62 +710,6 @@ public static function setAnnotationTags(array $annotationTags, $replace = false self::$annotationTags = $replace ? $annotationTags : array_merge(self::$annotationTags, $annotationTags); } - /** - * @return bool - */ - public function isInteractive(): bool - { - if ($this->app) { - return $this->app->isInteractive(); - } - - $value = $this->input->getBoolOpt(GlobalOption::NO_INTERACTIVE); - - return $value === false; - } - - /** - * Get current debug level value - * - * @return int - */ - public function getVerbLevel(): int - { - if ($this->app) { - return $this->app->getVerbLevel(); - } - - return (int)$this->input->getLongOpt('debug', Console::VERB_ERROR); - } - - /** - * @param int $level - * @param string $format - * @param mixed ...$args - */ - public function logf(int $level, string $format, ...$args): void - { - if ($this->getVerbLevel() < $level) { - return; - } - - Console::logf($level, $format, ...$args); - } - - /** - * @param int $level - * @param string $message - * @param array $extra - */ - public function log(int $level, string $message, array $extra = []): void - { - if ($this->getVerbLevel() < $level) { - return; - } - - Console::log($level, $message, $extra); - } - /** * @return InputDefinition|null */ @@ -846,62 +726,6 @@ public function setDefinition(InputDefinition $definition): void $this->definition = $definition; } - /** - * @return array - */ - public function getCommentsVars(): array - { - return $this->commentsVars; - } - - /** - * @return AbstractApplication - */ - public function getApp(): AbstractApplication - { - return $this->app; - } - - /** - * @param AbstractApplication $app - */ - public function setApp(AbstractApplication $app): void - { - $this->app = $app; - } - - /** - * @return bool - */ - public function isAttached(): bool - { - return $this->attached; - } - - /** - * @return bool - */ - public function isDetached(): bool - { - return $this->attached === false; - } - - /** - * @param bool $attached - */ - public function setAttached(bool $attached): void - { - $this->attached = $attached; - } - - /** - * Detached running - */ - public function setDetached(): void - { - $this->attached = false; - } - /** * @return string */ diff --git a/src/Application.php b/src/Application.php index 619ef298..c80e7f77 100644 --- a/src/Application.php +++ b/src/Application.php @@ -277,7 +277,7 @@ public function dispatch(string $name, bool $detachedRun = false) // command not found if (!$info) { - if (true === $this->fire(self::ON_NOT_FOUND, $cmdId, $this)) { + if (true === $this->fire(ConsoleEvent::ON_NOT_FOUND, $cmdId, $this)) { $this->logf(Console::VERB_DEBUG, 'not found handle by user, command: %s', $name); return 0; } diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index 0b5dc167..441cd2a1 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -131,6 +131,8 @@ public function showCommandList(): void return; } + $this->logf(Console::VERB_DEBUG, 'Display the application commands list'); + /** @var Output $output */ // $output = $this->output; /** @var Router $router */ $router = $this->getRouter(); diff --git a/src/Concern/AttachApplicationTrait.php b/src/Concern/AttachApplicationTrait.php new file mode 100644 index 00000000..4084c8a8 --- /dev/null +++ b/src/Concern/AttachApplicationTrait.php @@ -0,0 +1,154 @@ +app; + } + + /** + * @param AbstractApplication $app + */ + public function setApp(AbstractApplication $app): void + { + $this->app = $app; + + // auto setting $attached + $this->attached = true; + } + + /** + * @return bool + */ + public function isAttached(): bool + { + return $this->attached; + } + + /** + * @return bool + */ + public function isDetached(): bool + { + return $this->attached === false; + } + + /** + * Detached running + */ + public function setDetached(): void + { + $this->attached = false; + } + + /** + * @return bool + */ + public function isInteractive(): bool + { + if ($this->app) { + return $this->app->isInteractive(); + } + + $value = $this->input->getBoolOpt(GlobalOption::NO_INTERACTIVE); + + return $value === false; + } + + /** + * Get current debug level value + * + * @return int + */ + public function getVerbLevel(): int + { + if ($this->app) { + return $this->app->getVerbLevel(); + } + + return (int)$this->input->getLongOpt('debug', Console::VERB_ERROR); + } + + /** + * @param int $level + * @param string $format + * @param mixed ...$args + */ + public function logf(int $level, string $format, ...$args): void + { + if ($this->getVerbLevel() < $level) { + return; + } + + Console::logf($level, $format, ...$args); + } + + /** + * @param int $level + * @param string $message + * @param array $extra + */ + public function log(int $level, string $message, array $extra = []): void + { + if ($this->getVerbLevel() < $level) { + return; + } + + Console::log($level, $message, $extra); + } + + /************************************************************************** + * wrap trigger events + **************************************************************************/ + + /** + * @param string $event + * @param mixed ...$args + * + * @return bool + */ + public function fire(string $event, ...$args): bool + { + // if has application instance + if ($this->attached) { + $stop = $this->app->fire($event, ...$args); + if ($stop === false) { + return false; + } + } + + return $this->parentFire($event, ...$args); + } +} diff --git a/src/Concern/CommandHelpTrait.php b/src/Concern/CommandHelpTrait.php new file mode 100644 index 00000000..59a22333 --- /dev/null +++ b/src/Concern/CommandHelpTrait.php @@ -0,0 +1,92 @@ + value] + */ + private $commentsVars; + + /** + * @return array + */ + public function getCommentsVars(): array + { + return $this->commentsVars; + } + + /** + * @param array $commentsVars + */ + public function setCommentsVars(array $commentsVars): void + { + $this->commentsVars = $commentsVars; + } + + /** + * @param string $name + * @param string|array $value + */ + protected function addCommentsVar(string $name, $value): void + { + if (!isset($this->commentsVars[$name])) { + $this->setCommentsVar($name, $value); + } + } + + /** + * @param array $map + */ + protected function addCommentsVars(array $map): void + { + foreach ($map as $name => $value) { + $this->setCommentsVar($name, $value); + } + } + + /** + * @param string $name + * @param string|array $value + */ + protected function setCommentsVar(string $name, $value): void + { + $this->commentsVars[$name] = is_array($value) ? implode(',', $value) : (string)$value; + } + + /** + * 替换注解中的变量为对应的值 + * + * @param string $str + * + * @return string + */ + protected function parseCommentsVars(string $str): string + { + // not use vars + if (false === strpos($str, self::HELP_VAR_LEFT)) { + return $str; + } + + static $map; + + if ($map === null) { + foreach ($this->commentsVars as $key => $value) { + $key = self::HELP_VAR_LEFT . $key . self::HELP_VAR_RIGHT; + // save + $map[$key] = $value; + } + } + + return $map ? strtr($str, $map) : $str; + } +} diff --git a/src/Concern/InputOutputAwareTrait.php b/src/Concern/InputOutputAwareTrait.php index e6fa4f0e..88f6c470 100644 --- a/src/Concern/InputOutputAwareTrait.php +++ b/src/Concern/InputOutputAwareTrait.php @@ -199,26 +199,4 @@ public function setOutput(OutputInterface $output): void { $this->output = $output; } - - /** - * get debug level value - * - * @return int - */ - public function getVerbLevel(): int - { - return (int)$this->input->getLongOpt('debug', Console::VERB_ERROR); - } - - /** - * check is given verbose level - * - * @param int $level - * - * @return bool - */ - public function isDebug(int $level = Console::VERB_DEBUG): bool - { - return $level <= $this->getVerbLevel(); - } } diff --git a/src/Concern/SimpleEventTrait.php b/src/Concern/SimpleEventAwareTrait.php similarity index 99% rename from src/Concern/SimpleEventTrait.php rename to src/Concern/SimpleEventAwareTrait.php index 86dfe02c..b42a58dc 100644 --- a/src/Concern/SimpleEventTrait.php +++ b/src/Concern/SimpleEventAwareTrait.php @@ -16,7 +16,7 @@ * * @package Inhere\Console\Concern */ -trait SimpleEventTrait +trait SimpleEventAwareTrait { /** * set the supported events, if you need. diff --git a/src/Console.php b/src/Console.php index caa55a20..1c7d7f94 100644 --- a/src/Console.php +++ b/src/Console.php @@ -15,9 +15,7 @@ use function strpos; use function trim; use const DEBUG_BACKTRACE_IGNORE_ARGS; -use const JSON_PRETTY_PRINT; use const JSON_UNESCAPED_SLASHES; -use const PHP_EOL; /** * Class Console @@ -106,7 +104,7 @@ public static function newApp( */ public static function logf(int $level, string $format, ...$args): void { - $datetime = date('Y/m/d H:i:s'); + $datetime = date('Y/m/d H:i:s'); $levelName = self::LEVEL_NAMES[$level] ?? 'INFO'; $colorName = self::LEVEL2TAG[$level] ?? 'info'; @@ -114,7 +112,7 @@ public static function logf(int $level, string $format, ...$args): void $tagName = ColorTag::add($levelName, $colorName); $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, self::$traceIndex + 2); - $position = self::formatBacktrace($backtrace, self::$traceIndex); + $position = self::formatBacktrace($backtrace, self::$traceIndex); self::writef('%s [%s] [%s] %s', $datetime, $tagName, $position, $message); } @@ -153,7 +151,7 @@ public static function log(int $level, string $msg, array $data = [], array $opt $dataString = $data ? json_encode($data, JSON_UNESCAPED_SLASHES) : ''; $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, self::$traceIndex + 2); - $position = self::formatBacktrace($backtrace, self::$traceIndex); + $position = self::formatBacktrace($backtrace, self::$traceIndex); self::writef('%s [%s] [%s]%s %s %s', $datetime, $taggedName, $position, $optString, trim($msg), $dataString); } @@ -168,9 +166,9 @@ private static function formatBacktrace(array $traces, int $index): string { $position = 'unknown'; - if (isset($traces[$index+1])) { + if (isset($traces[$index + 1])) { $tInfo = $traces[$index]; - $prev = $traces[$index+1]; + $prev = $traces[$index + 1]; $type = $prev['type']; $position = sprintf('%s%s%s(),L%d', $prev['class'], $type, $prev['function'] ?? 'UNKNOWN', $tInfo['line']); diff --git a/src/ConsoleEvent.php b/src/ConsoleEvent.php index 137706dd..f6f53c02 100644 --- a/src/ConsoleEvent.php +++ b/src/ConsoleEvent.php @@ -4,19 +4,46 @@ /** * Class ConsoleEvent + * - event name list * * @package Inhere\Console */ final class ConsoleEvent { - // event name list + // ----- application + public const ON_BEFORE_RUN = 'app.beforeRun'; - public const ON_AFTER_RUN = 'app.afterRun'; + public const ON_AFTER_RUN = 'app.afterRun'; + + public const ON_RUN_ERROR = 'app.runError'; + + public const ON_STOP_RUN = 'app.stopRun'; + + // on group OR command not found + public const ON_NOT_FOUND = 'app.notFound'; + + public const BEFORE_RENDER_APP_HELP = 'app.help.render.before'; + + public const BEFORE_RENDER_APP_VERSION = 'app.version.render.before'; + + public const BEFORE_RENDER_APP_COMMANDS_LIST = 'app.commands.list.render.before'; + + // ----- group/sub-command + + public const SUB_COMMAND_NOT_FOUND = 'group.command.notFound'; + + public const BEFORE_RENDER_GROUP_HELP = 'group.help.render.before'; + + public const AFTER_RENDER_GROUP_HELP = 'group.help.render.after'; + + public const BEFORE_RENDER_SUB_COMMAND_HELP = 'group.command.help.render.before'; + + public const AFTER_RENDER_SUB_COMMAND_HELP = 'group.command.help.render.after'; - public const ON_RUN_ERROR = 'app.runError'; + // ----- command - public const ON_STOP_RUN = 'app.stopRun'; + public const BEFORE_RENDER_COMMAND_HELP = 'command.help.render.before'; - public const ON_NOT_FOUND = 'app.notFound'; + public const AFTER_RENDER_COMMAND_HELP = 'command.help.render.after'; } diff --git a/src/Controller.php b/src/Controller.php index 7a97e271..1dd2916f 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -9,6 +9,7 @@ namespace Inhere\Console; use Generator; +use Inhere\Console\Concern\ControllerHelpTrait; use Inhere\Console\Contract\ControllerInterface; use Inhere\Console\IO\Input; use Inhere\Console\IO\InputDefinition; @@ -17,7 +18,6 @@ use Inhere\Console\Util\Helper; use ReflectionClass; use ReflectionException; -use ReflectionMethod; use ReflectionObject; use RuntimeException; use Toolkit\Cli\ColorTag; @@ -46,6 +46,8 @@ */ abstract class Controller extends AbstractHandler implements ControllerInterface { + use ControllerHelpTrait; + /** * The sub-command aliases mapping * @@ -91,11 +93,6 @@ abstract class Controller extends AbstractHandler implements ControllerInterface */ private $actionSuffix = self::COMMAND_SUFFIX; - /** - * @var string - */ - protected $notFoundCallback = 'notFound'; - /** * @var array Common options for all sub-commands in the group */ @@ -149,6 +146,7 @@ protected function init(): void self::loadCommandAliases(); $list = $this->disabledCommands(); + // save to property $this->disabledCommands = $list ? array_flip($list) : []; $this->groupOptions = $this->groupOptions(); @@ -181,6 +179,19 @@ protected function disabledCommands(): array return []; } + /** + * Will call it on action(sub-command) not found on the group. + * + * @param string $action + * + * @return bool if return True, will stop goon render group help. + */ + protected function onNotFound(string $action): bool + { + // you can add custom logic on sub-command not found. + return false; + } + /** * @param string $command * @@ -192,23 +203,29 @@ public function run(string $command = '') if (!$command = trim($command, $this->delimiter)) { $command = $this->defaultAction; + // try use next arg as sub-command name. if (!$command) { - // use next arg as sub-command name. $command = $this->input->findCommandName(); + + // update the command id. + if ($command) { + $group = $this->input->getCommand(); + $this->input->setCommandId("$group:$command"); + } } } - // not input command + // if not input sub-command, render group help. if (!$command) { - $this->logf(Console::VERB_DEBUG, 'group action is empty, display help for the group'); - + $this->logf(Console::VERB_DEBUG, 'sub-command is empty, display help for the group: %s', self::getName()); return $this->showHelp(); } $command = $this->getRealCommandName($command); + // convert 'boo-foo' to 'booFoo' $this->action = Str::camelCase($command); - $this->logf(Console::VERB_DEBUG, 'will run the group action: %s, command: %s', $this->action, $command); + $this->logf(Console::VERB_DEBUG, 'will run the group action: %s, sub-command: %s', $this->action, $command); // do running return parent::run($command); @@ -260,6 +277,7 @@ final public function execute($input, $output) $group = static::getName(); if ($this->isDisabled($action)) { + $this->logf(Console::VERB_DEBUG, 'command %s is disabled on the group %s', $action, $group); $output->error(sprintf("Sorry, The command '%s' is invalid in the group '%s'!", $action, $group)); return -1; } @@ -267,16 +285,21 @@ final public function execute($input, $output) $method = $this->actionSuffix ? $action . ucfirst($this->actionSuffix) : $action; // the action method exists and only allow access public method. - if (method_exists($this, $method) && (($rfm = new ReflectionMethod($this, $method)) && $rfm->isPublic())) { - // before - if (method_exists($this, $before = 'before' . ucfirst($action))) { - $this->$before($input, $output); + // if (method_exists($this, $method) && (($rfm = new ReflectionMethod($this, $method)) && $rfm->isPublic())) { + if (method_exists($this, $method)) { + // before run action + if (method_exists($this, $beforeFunc = 'before' . ucfirst($action))) { + $beforeOk = $this->$beforeFunc($input, $output); + if ($beforeOk === false) { + $this->logf(Console::VERB_DEBUG, '%s() returns FALSE, interrupt processing continues', $beforeFunc); + return 0; + } } // run action $result = $this->$method($input, $output); - // after + // after run action if (method_exists($this, $after = 'after' . ucfirst($action))) { $this->$after($input, $output); } @@ -284,24 +307,30 @@ final public function execute($input, $output) return $result; } + // if user custom handle not found logic. + if ($this->onNotFound($action)) { + $this->logf(Console::VERB_DEBUG, 'user custom handle the action:%s not found logic', $action); + return 0; + } + + $this->logf(Console::VERB_DEBUG, 'action:%s not found on the group controller', $action); + // if you defined the method '$this->notFoundCallback' , will call it - if (($notFoundCallback = $this->notFoundCallback) && method_exists($this, $notFoundCallback)) { - $result = $this->{$notFoundCallback}($action); - } else { - $result = -1; - $output->liteError("Sorry, The command '$action' not exist of the group '{$group}'!"); + // if (($notFoundCallback = $this->notFoundCallback) && method_exists($this, $notFoundCallback)) { + // $result = $this->{$notFoundCallback}($action); + // } else { + $output->liteError("Sorry, The command '$action' not exist of the group '{$group}'!"); - // find similar command names - $similar = Helper::findSimilar($action, $this->getAllCommandMethods(null, true)); + // find similar command names + $similar = Helper::findSimilar($action, $this->getAllCommandMethods(null, true)); - if ($similar) { - $output->write(sprintf("\nMaybe what you mean is:\n %s", implode(', ', $similar))); - } else { - $this->showCommandList(); - } + if ($similar) { + $output->write(sprintf("\nMaybe what you mean is:\n %s", implode(', ', $similar))); + } else { + $this->showCommandList(); } - return $result; + return -1; } /** @@ -517,16 +546,15 @@ protected function getAllCommandMethods(ReflectionClass $ref = null, bool $onlyN /** * @param string $name * - * @return mixed|string + * @return string */ - protected function getRealCommandName(string $name) + protected function getRealCommandName(string $name): string { if (!$name) { return ''; } $map = $this->getCommandAliases(); - return $map[$name] ?? $name; } @@ -646,22 +674,6 @@ public function setActionSuffix(string $actionSuffix): void $this->actionSuffix = $actionSuffix; } - /** - * @return string|null - */ - public function getNotFoundCallback(): ?string - { - return $this->notFoundCallback; - } - - /** - * @param string $notFoundCallback - */ - public function setNotFoundCallback(string $notFoundCallback): void - { - $this->notFoundCallback = $notFoundCallback; - } - /** * @return bool */ From 9d9aeca5d51c0a8d614ecd3ec6e7eaefc60efcef Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 23 Jan 2021 14:22:22 +0800 Subject: [PATCH 062/258] update: add debugf for print logs --- src/AbstractApplication.php | 13 +++++++++++++ src/Application.php | 8 +++++--- src/Concern/AttachApplicationTrait.php | 13 +++++++++++++ src/Controller.php | 12 ++++++------ 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 102f5400..ed86ace5 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -429,6 +429,19 @@ public function logf(int $level, string $format, ...$args): void Console::logf($level, $format, ...$args); } + /** + * @param string $format + * @param mixed ...$args + */ + public function debugf(string $format, ...$args): void + { + if ($this->getVerbLevel() < Console::VERB_DEBUG) { + return; + } + + Console::logf(Console::VERB_DEBUG, $format, ...$args); + } + /********************************************************** * getter/setter methods **********************************************************/ diff --git a/src/Application.php b/src/Application.php index c80e7f77..f12e4a7b 100644 --- a/src/Application.php +++ b/src/Application.php @@ -139,6 +139,7 @@ public function command(string $name, $handler = null, $option = null) ]; } + $this->logf(Console::VERB_CRAZY, 'load application command: %s', $name); $this->router->addCommand($name, $handler, (array)$option); return $this; @@ -265,7 +266,7 @@ public function dispatch(string $name, bool $detachedRun = false) } $cmdId = $name; - $this->logf(Console::VERB_DEBUG, 'begin dispatch the input command: %s', $name); + $this->debugf( 'begin dispatch the input command: %s', $name); // format is: `group action` if (strpos($name, ' ') > 0) { @@ -277,8 +278,9 @@ public function dispatch(string $name, bool $detachedRun = false) // command not found if (!$info) { - if (true === $this->fire(ConsoleEvent::ON_NOT_FOUND, $cmdId, $this)) { - $this->logf(Console::VERB_DEBUG, 'not found handle by user, command: %s', $name); + $evtName = ConsoleEvent::ON_NOT_FOUND; + if (true === $this->fire($evtName, $cmdId, $this)) { + $this->debugf('user custom handle the not found command: %s, event: %s', $name, $evtName); return 0; } diff --git a/src/Concern/AttachApplicationTrait.php b/src/Concern/AttachApplicationTrait.php index 4084c8a8..89e6cf15 100644 --- a/src/Concern/AttachApplicationTrait.php +++ b/src/Concern/AttachApplicationTrait.php @@ -101,6 +101,19 @@ public function getVerbLevel(): int return (int)$this->input->getLongOpt('debug', Console::VERB_ERROR); } + /** + * @param string $format + * @param mixed ...$args + */ + public function debugf(string $format, ...$args): void + { + if ($this->getVerbLevel() < Console::VERB_DEBUG) { + return; + } + + Console::logf(Console::VERB_DEBUG, $format, ...$args); + } + /** * @param int $level * @param string $format diff --git a/src/Controller.php b/src/Controller.php index 1dd2916f..eb11ab4e 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -217,7 +217,7 @@ public function run(string $command = '') // if not input sub-command, render group help. if (!$command) { - $this->logf(Console::VERB_DEBUG, 'sub-command is empty, display help for the group: %s', self::getName()); + $this->debugf('sub-command is empty, display help for the group: %s', self::getName()); return $this->showHelp(); } @@ -225,7 +225,7 @@ public function run(string $command = '') // convert 'boo-foo' to 'booFoo' $this->action = Str::camelCase($command); - $this->logf(Console::VERB_DEBUG, 'will run the group action: %s, sub-command: %s', $this->action, $command); + $this->debugf('will run the group action: %s, sub-command: %s', $this->action, $command); // do running return parent::run($command); @@ -277,7 +277,7 @@ final public function execute($input, $output) $group = static::getName(); if ($this->isDisabled($action)) { - $this->logf(Console::VERB_DEBUG, 'command %s is disabled on the group %s', $action, $group); + $this->debugf('command %s is disabled on the group %s', $action, $group); $output->error(sprintf("Sorry, The command '%s' is invalid in the group '%s'!", $action, $group)); return -1; } @@ -291,7 +291,7 @@ final public function execute($input, $output) if (method_exists($this, $beforeFunc = 'before' . ucfirst($action))) { $beforeOk = $this->$beforeFunc($input, $output); if ($beforeOk === false) { - $this->logf(Console::VERB_DEBUG, '%s() returns FALSE, interrupt processing continues', $beforeFunc); + $this->debugf('%s() returns FALSE, interrupt processing continues', $beforeFunc); return 0; } } @@ -309,11 +309,11 @@ final public function execute($input, $output) // if user custom handle not found logic. if ($this->onNotFound($action)) { - $this->logf(Console::VERB_DEBUG, 'user custom handle the action:%s not found logic', $action); + $this->debugf('user custom handle the action:%s not found logic', $action); return 0; } - $this->logf(Console::VERB_DEBUG, 'action:%s not found on the group controller', $action); + $this->debugf('action:%s not found on the group controller', $action); // if you defined the method '$this->notFoundCallback' , will call it // if (($notFoundCallback = $this->notFoundCallback) && method_exists($this, $notFoundCallback)) { From 41873768a18ace36bf76c969c18178a4b05b7359 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 24 Jan 2021 02:10:31 +0800 Subject: [PATCH 063/258] feat: support start an interactive shell for run application --- README.md | 3 +- src/AbstractApplication.php | 65 ++++++++++++++++++++++++++-- src/Concern/ApplicationHelpTrait.php | 30 +++++++++---- src/Controller.php | 4 +- src/GlobalOption.php | 7 +++ src/IO/Input.php | 13 +++++- src/IO/Input/StringInput.php | 53 +++++++++++++++++++++++ src/Util/Interact.php | 2 +- 8 files changed, 160 insertions(+), 17 deletions(-) create mode 100644 src/IO/Input/StringInput.php diff --git a/README.md b/README.md index 49f05ea6..57cc4fc8 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,10 @@ Provide console parameter parsing, command run, color style output, user informa - Commonly used special format information display (`section`, `panel`, `padding`, `helpPanel`, `table`, `tree`, `title`, `list`, `multiList`) - Rich dynamic information display (`pending/loading`, `pointing`, `spinner`, `counterTxt`, `dynamicText`, `progressTxt`, `progressBar`) - Common user information interaction support (`select`, `multiSelect`, `confirm`, `ask/question`, `askPassword/askHiddenInput`) -- Support for predefined parameter definitions like `symfony/console` (giving parameter values ​​by position, recommended when strict parameter restrictions are required) +- Support for predefined parameter definitions like `symfony/console` (giving parameter values by position, recommended when strict parameter restrictions are required) - The color output is `windows` `linux` `mac` compatible. Environments that do not support color will automatically remove the relevant CODE. - Quickly generate auto-completion scripts for the current application in the `bash/zsh` environment +- NEW: Support start an interactive shell for run application ### Built-in tools diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index ed86ace5..156013d8 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -20,10 +20,14 @@ use Inhere\Console\Concern\ApplicationHelpTrait; use Inhere\Console\Concern\InputOutputAwareTrait; use Inhere\Console\Concern\SimpleEventAwareTrait; +use Inhere\Console\Util\Interact; use InvalidArgumentException; use Throwable; use Toolkit\Cli\Style; +use Toolkit\Cli\Util\LineParser; use Toolkit\Stdlib\Helper\PhpHelper; +use Toolkit\Sys\Proc\ProcessUtil; +use Toolkit\Sys\Proc\Signal; use function array_keys; use function array_merge; use function error_get_last; @@ -59,7 +63,8 @@ abstract class AbstractApplication implements ApplicationInterface /** @var array */ protected static $globalOptions = [ - '--debug' => 'Setting the application runtime debug level(0 - 4)', + '--debug' => 'Setting the runtime log debug level(quiet 0 - 5 crazy)', + '--ishell' => 'Run application an interactive shell environment', '--profile' => 'Display timing and memory usage information', '--no-color' => 'Disable color/ANSI for message output', '-h, --help' => 'Display this help message', @@ -356,16 +361,21 @@ public function handleException($e): void protected function filterSpecialCommand(string $command): bool { if (!$command) { - if ($this->input->getSameOpt(['V', 'version'])) { + if ($this->input->getSameBoolOpt(GlobalOption::VERSION_OPTS)) { $this->showVersionInfo(); return true; } - if ($this->input->getSameOpt(['h', 'help'])) { + if ($this->input->getSameBoolOpt(GlobalOption::HELP_OPTS)) { $this->showHelpInfo(); return true; } + if ($this->input->getBoolOpt(GlobalOption::ISHELL)) { + $this->startInteractiveShell(); + return true; + } + // default run list command // $command = $this->defaultCommand ? 'list'; $command = 'list'; @@ -390,6 +400,55 @@ protected function filterSpecialCommand(string $command): bool return true; } + /********************************************************** + * start interactive shell + **********************************************************/ + + /** + * start an interactive shell run + */ + protected function startInteractiveShell(): void + { + $in = $this->input; + $out = $this->output; + + $out->colored("Will start interactive shell for run application"); + + if (!($hasPcntl = ProcessUtil::hasPcntl())) { + $this->debugf('php is not enable "pcntl" extension, cannot listen CTRL+C signal'); + } + + if ($hasPcntl) { + // register signal. + ProcessUtil::installSignal(Signal::INT, static function () use ($out) { + $out->colored("\nQuit by CTRL+C"); + exit(0); + }); + } + + while (true) { + $line = Interact::readln('CMD > '); + if ($line === 'exit' || $line === 'quit') { + break; + } + + if ($hasPcntl) { + // listen signal. + ProcessUtil::dispatchSignal(); + } + + $args = LineParser::parseIt($line); + + // reload and parse args + $in->parse($args); + + // \vdump($in); + $this->run(false); + } + + $out->colored("\nQuit. ByeBye!"); + } + /** * @param string $name * @param string|array $aliases diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index 441cd2a1..ab1ea4ae 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -99,17 +99,29 @@ public function showHelpInfo(string $command = ''): void $delimiter = $this->delimiter; $binName = $in->getScriptName(); + // built in options + $globalOptions = FormatUtil::alignOptions(self::$globalOptions); + /** @var Output $out */ $out = $this->output; $out->helpPanel([ - 'usage' => "$binName {command} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", - 'example' => [ - "$binName test (run a independent command)", - "$binName home{$delimiter}index (run a command of the group)", - "$binName help {command} (see a command help information)", - "$binName home{$delimiter}index -h (see a command help of the group)", - "$binName --auto-completion --shell-env [zsh|bash] [--gen-file stdout]", - ] + 'Usage' => "$binName {command} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", + 'Options' => $globalOptions, + 'Example' => [ + "$binName test run a independent command", + "$binName home index run a sub-command of the group", + sprintf("$binName home%sindex run a sub-command of the group", $delimiter), + "$binName help {command} see a command help information", + "$binName home index -h see a sub-command help of the group", + sprintf("$binName home%sindex -h see a sub-command help of the group", $delimiter), + ], + 'Help' => [ + 'Generate shell auto completion scripts:', + " $binName --auto-completion --shell-env [zsh|bash] [--gen-file stdout]", + ' eg:', + " $binName --auto-completion --shell-env bash --gen-file stdout", + " $binName --auto-completion --shell-env bash --gen-file myapp.sh", + ], ]); } @@ -261,7 +273,7 @@ protected function dumpAutoCompletion(string $shellEnv, array $data): void // info $glue = ' '; - $genFile = (string)$input->getLongOpt('gen-file'); + $genFile = $input->getStringOpt('gen-file'); $filename = 'auto-completion.' . $shellEnv; $tplDir = dirname(__DIR__, 2) . '/resource/templates'; diff --git a/src/Controller.php b/src/Controller.php index eb11ab4e..cc40baf7 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -309,11 +309,11 @@ final public function execute($input, $output) // if user custom handle not found logic. if ($this->onNotFound($action)) { - $this->debugf('user custom handle the action:%s not found logic', $action); + $this->debugf('user custom handle the action "%s" not found logic', $action); return 0; } - $this->debugf('action:%s not found on the group controller', $action); + $this->debugf('action "%s" not found on the group controller', $action); // if you defined the method '$this->notFoundCallback' , will call it // if (($notFoundCallback = $this->notFoundCallback) && method_exists($this, $notFoundCallback)) { diff --git a/src/GlobalOption.php b/src/GlobalOption.php index 218c8dfe..2d2e378e 100644 --- a/src/GlobalOption.php +++ b/src/GlobalOption.php @@ -13,6 +13,8 @@ class GlobalOption public const DEBUG = 'debug'; + public const ISHELL = 'ishell'; + public const VERSION = 'version'; public const PROFILE = 'profile'; @@ -21,8 +23,13 @@ class GlobalOption public const NO_INTERACTIVE = 'no-interactive'; + public const HELP_OPTS = ['h', 'help']; + + public const VERSION_OPTS = ['V', 'version']; + public const KEY_MAP = [ 'debug' => 1, + 'ishell' => 1, 'profile' => 1, 'no-color' => 1, 'h' => 1, diff --git a/src/IO/Input.php b/src/IO/Input.php index 354c1f4a..66951273 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -71,11 +71,22 @@ protected function collectInfo(array $args): void $this->tokens = $args; $this->script = array_shift($args); $this->flags = $args; // no script + // bin name $this->scriptName = basename($this->script); + // full script $this->fullScript = implode(' ', $args); + } + /** + * re-parse args/opts from given args + * + * @param array $args + */ + public function parse(array $args): void + { + $this->doParse($args); } /** @@ -140,7 +151,7 @@ public function read(string $question = '', bool $nl = false): string fwrite(Cli::getOutputStream(), $question . ($nl ? "\n" : '')); } - return trim(fgets($this->inputStream)); + return trim((string)fgets($this->inputStream)); } /*********************************************************************************** diff --git a/src/IO/Input/StringInput.php b/src/IO/Input/StringInput.php new file mode 100644 index 00000000..50e0b89c --- /dev/null +++ b/src/IO/Input/StringInput.php @@ -0,0 +1,53 @@ +doParse($flags); + } + } + + /** + * @param array $args + */ + protected function doParse(array $args): void + { + [ + $this->args, + $this->sOpts, + $this->lOpts + ] = Flags::parseArray($args); + + // find command name + $this->command = $this->findCommandName(); + } +} diff --git a/src/Util/Interact.php b/src/Util/Interact.php index c20e4336..9cd44c12 100644 --- a/src/Util/Interact.php +++ b/src/Util/Interact.php @@ -49,7 +49,7 @@ public static function readln($message = null, $nl = false, array $opts = []): s $stream = $opts['stream'] ?? Cli::getInputStream(); - return trim(fgets($stream)); + return trim((string)fgets($stream)); } /** From 96c9b35d2de43925fced109d76388ebe20e8456e Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 24 Jan 2021 02:13:22 +0800 Subject: [PATCH 064/258] rm .triavs.yml --- .travis.yml | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0969ef51..00000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: php - -php: -# - '7.1' -# - '7.2' - - '7.3' - - '7.4' - -#matrix: -# include: -# - php: 7.2 -# env: ANALYSIS='true' - -before_script: - - composer require php-coveralls/php-coveralls:^2.1.0 - -script: -# - phpunit --coverage-clover clover.xml - - php vendor/bin/phpunit --coverage-clover clover.xml - -after_success: - - vendor/bin/php-coveralls --coverage_clover=clover.xml --json_path=coveralls-upload.json -v From 2de1962cc2bd302d9f4cfa9cd557ec3e825edf38 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 24 Jan 2021 15:11:51 +0800 Subject: [PATCH 065/258] limit php 7.3 -> 7,2 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 90a5968c..cecc70cb 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ } ], "require": { - "php": ">7.3.0", + "php": ">7.2.0", "toolkit/cli-utils": "~1.0", "toolkit/stdlib": "~1.0", "toolkit/sys-utils": "~1.0" From 4470b6cf3c8d972718ef9264aa4b27f441d73c26 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 24 Jan 2021 16:09:31 +0800 Subject: [PATCH 066/258] add new usage image --- docs/screenshots/alone-command-help.jpg | Bin 0 -> 80794 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/screenshots/alone-command-help.jpg diff --git a/docs/screenshots/alone-command-help.jpg b/docs/screenshots/alone-command-help.jpg new file mode 100644 index 0000000000000000000000000000000000000000..24da8ec78a84d589b5ecd66c687eff9c3364f652 GIT binary patch literal 80794 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

urHcEpUT=#` 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 literal 0 HcmV?d00001 From 4800cf0cf68218f78ea7c8c4338718763cde9645 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 24 Jan 2021 17:10:47 +0800 Subject: [PATCH 067/258] update some for ishell env --- src/AbstractApplication.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 156013d8..38b97ae9 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -426,10 +426,23 @@ protected function startInteractiveShell(): void }); } + $exitKeys = [ + 'q' => 1, + 'quit' => 1, + 'exit' => 1, + ]; + while (true) { $line = Interact::readln('CMD > '); - if ($line === 'exit' || $line === 'quit') { - break; + if (strlen($line) < 5) { + if (isset($exitKeys[$line])) { + break; + } + + // "?" as show help + if ($line === '?') { + $line = 'help'; + } } if ($hasPcntl) { @@ -444,6 +457,7 @@ protected function startInteractiveShell(): void // \vdump($in); $this->run(false); + $out->println(''); } $out->colored("\nQuit. ByeBye!"); From bd4f38a17d9f086af6135c24c8bb6d7eb035b343 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 25 Jan 2021 17:32:09 +0800 Subject: [PATCH 068/258] update for titile display. support custom ishell name prefix --- src/AbstractApplication.php | 16 ++++++++++++++-- src/Component/Formatter/Title.php | 16 ++++++++++------ src/IO/AbstractInput.php | 3 ++- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 38b97ae9..8170c1fa 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -10,6 +10,7 @@ use ErrorException; use Inhere\Console\Component\ErrorHandler; +use Inhere\Console\Component\Formatter\Title; use Inhere\Console\Concern\StyledOutputAwareTrait; use Inhere\Console\Contract\ApplicationInterface; use Inhere\Console\Contract\ErrorHandlerInterface; @@ -34,6 +35,7 @@ use function header; use function in_array; use function is_int; +use function json_encode; use function memory_get_usage; use function microtime; use function register_shutdown_function; @@ -90,6 +92,7 @@ abstract class AbstractApplication implements ApplicationInterface 'publishAt' => '2017.03.24', 'updateAt' => '2019.01.01', 'rootPath' => '', + 'ishellName' => '', // name prefix on i-shell env. 'strictMode' => false, 'hideRootPath' => true, // global options @@ -412,7 +415,9 @@ protected function startInteractiveShell(): void $in = $this->input; $out = $this->output; - $out->colored("Will start interactive shell for run application"); + $out->title("Welcome interactive shell for run application", [ + 'titlePos' => Title::POS_MIDDLE, + ]); if (!($hasPcntl = ProcessUtil::hasPcntl())) { $this->debugf('php is not enable "pcntl" extension, cannot listen CTRL+C signal'); @@ -426,6 +431,11 @@ protected function startInteractiveShell(): void }); } + $prefix = $this->getParam('ishellName') ?: $this->getName(); + if (!$prefix) { + $prefix = 'CMD'; + } + $exitKeys = [ 'q' => 1, 'quit' => 1, @@ -433,7 +443,7 @@ protected function startInteractiveShell(): void ]; while (true) { - $line = Interact::readln('CMD > '); + $line = Interact::readln("$prefix > "); if (strlen($line) < 5) { if (isset($exitKeys[$line])) { break; @@ -451,9 +461,11 @@ protected function startInteractiveShell(): void } $args = LineParser::parseIt($line); + $this->debugf('input line: %s, parsed args: %s', $line, json_encode($args)); // reload and parse args $in->parse($args); + $in->setFullScript($line); // \vdump($in); $this->run(false); diff --git a/src/Component/Formatter/Title.php b/src/Component/Formatter/Title.php index f8ae0c30..0363ef76 100644 --- a/src/Component/Formatter/Title.php +++ b/src/Component/Formatter/Title.php @@ -4,6 +4,7 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; +use Toolkit\Cli\ColorTag; use Toolkit\Stdlib\Str; use Toolkit\Sys\Sys; use function array_merge; @@ -26,15 +27,16 @@ public static function show(string $title, array $opts = []): void 'width' => 80, 'char' => self::CHAR_EQUAL, 'titlePos' => self::POS_LEFT, - 'indent' => 2, + 'titleStyle' => 'bold', + 'indent' => 0, 'ucWords' => true, 'showBorder' => true, ], $opts); // list($sW, $sH) = Helper::getScreenSize(); - $width = (int)$opts['width']; - $char = trim($opts['char']); - $indent = (int)$opts['indent'] >= 0 ? $opts['indent'] : 2; + $width = (int)$opts['width']; + $char = trim($opts['char']); + $indent = (int)$opts['indent'] >= 0 ? $opts['indent'] : 2; $indentStr = ''; if ($indent > 0) { @@ -61,8 +63,10 @@ public static function show(string $title, array $opts = []): void $titleIndent = Str::pad(self::CHAR_SPACE, $indent, self::CHAR_SPACE); } - $titleLine = "$titleIndent$title\n"; - $border = $indentStr . Str::pad($char, $width, $char); + $titleText = ColorTag::wrap($title, $opts['titleStyle']); + $titleLine = "$titleIndent{$titleText}\n"; + + $border = $indentStr . Str::pad($char, $width, $char); Console::write($titleLine . $border); } diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index 13129d91..bd959f7c 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -10,6 +10,7 @@ use Inhere\Console\Concern\InputArgumentsTrait; use Inhere\Console\Concern\InputOptionsTrait; +use Inhere\Console\Contract\InputInterface; use function getcwd; use function is_int; use function trim; @@ -19,7 +20,7 @@ * * @package Inhere\Console\IO */ -abstract class AbstractInput implements \Inhere\Console\Contract\InputInterface +abstract class AbstractInput implements InputInterface { use InputArgumentsTrait, InputOptionsTrait; From 46ab96396234386ebafb44d888087d9ad1329525 Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 27 Jan 2021 21:39:38 +0800 Subject: [PATCH 069/258] up: fix command aliaes not display on help --- README.md | 3 +- README_cn.md => README.zh-CN.md | 1 + examples/Controller/HomeController.php | 35 +++++++++++++++-------- src/AbstractHandler.php | 39 ++++++++++++++++++++------ src/Command.php | 12 ++++---- src/Controller.php | 21 ++++++++++++-- src/IO/AbstractInput.php | 26 ++++++++++++++++- 7 files changed, 106 insertions(+), 31 deletions(-) rename README_cn.md => README.zh-CN.md (97%) diff --git a/README.md b/README.md index 57cc4fc8..23cc0c35 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,12 @@ [![License](https://img.shields.io/packagist/l/inhere/console.svg?style=flat-square)](LICENSE) [![Php Version](https://img.shields.io/badge/php-%3E=7.3.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) [![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console) +[![Github Actions Status](https://github.com/inhere/console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/console/actions) A simple, full-featured php command line application library. Provide console parameter parsing, command run, color style output, user information interaction, and special format information display. -> **[中文README](./README_cn.md)** +> **[中文README](./README.zh-CN.md)** ## Command line preview diff --git a/README_cn.md b/README.zh-CN.md similarity index 97% rename from README_cn.md rename to README.zh-CN.md index de92bdc9..ed2f0bc4 100644 --- a/README_cn.md +++ b/README.zh-CN.md @@ -3,6 +3,7 @@ [![License](https://img.shields.io/packagist/l/inhere/console.svg?style=flat-square)](LICENSE) [![Php Version](https://img.shields.io/badge/php-%3E=7.1.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) [![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console) +[![Github Actions Status](https://github.com/inhere/console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/console/actions) 简洁、功能全面的php命令行应用库。提供控制台参数解析, 命令运行,颜色风格输出, 用户信息交互, 特殊格式信息显示。 diff --git a/examples/Controller/HomeController.php b/examples/Controller/HomeController.php index 9283127a..ae4f002b 100644 --- a/examples/Controller/HomeController.php +++ b/examples/Controller/HomeController.php @@ -19,6 +19,7 @@ /** * default command controller. there are some command usage examples(1) * Class HomeController + * * @package Inhere\Console\Examples\Controller */ class HomeController extends Controller @@ -34,15 +35,15 @@ protected static function commandAliases(): array { return [ // now, 'home:i' is equals to 'home:index' - 'i' => 'index', - 'prg' => 'progress', - 'pgb' => 'progressBar', - 'l' => 'list', - 'af' => 'artFont', - 'ml' => 'multiList', - 'sl' => 'splitLine', - 'dt' => 'dynamicText', - 'da' => 'defArg', + 'i' => 'index', + 'prg' => 'progress', + 'pgb' => 'progressBar', + 'l' => 'list', + 'af' => 'artFont', + 'ml' => 'multiList', + 'sl' => 'splitLine', + 'dt' => 'dynamicText', + 'defArg' => ['da', 'defarg'], ]; } @@ -108,6 +109,7 @@ public function disabledCommand(): void /** * command `defArgCommand` config + * * @throws LogicException */ protected function defArgConfigure(): void @@ -127,6 +129,7 @@ public function defArgCommand(): void /** * a command for test throw exception + * * @throws RuntimeException */ public function exCommand(): void @@ -181,6 +184,7 @@ public function colorCheckCommand(): void /** * output art font text + * * @options * --font Set the art font name(allow: {internalFonts}). * --italic Set the art font type is italic. @@ -205,9 +209,10 @@ public function artFontCommand(): int /** * dynamic notice message show: counterTxt. It is like progress txt, but no max value. + * + * @return int * @example * {script} {command} - * @return int */ public function counterCommand(): int { @@ -293,16 +298,19 @@ public function dynamicTextCommand(): void /** * a progress bar example show, by Show::progressBar() + * * @options * --type the progress type, allow: bar,txt. txt * --done-char the done show char. = * --wait-char the waiting show char. - * --sign-char the sign char show. > + * + * @param Input $input + * + * @return int * @example * {script} {command} * {script} {command} --done-char '#' --wait-char ' ' - * @param Input $input - * @return int */ public function progressCommand($input): int { @@ -333,6 +341,7 @@ public function progressCommand($input): int /** * a progress bar example show, by class ProgressBar + * * @throws LogicException */ public function progressBarCommand(): void @@ -474,6 +483,7 @@ public function paddingCommand(): void /** * a example for use arguments on command + * * @usage home:useArg [arg1=val1 arg2=arg2] [options] * @example * home:useArg status=2 name=john arg0 -s=test --page=23 -d -rf --debug --test=false -a v1 --ab -c -g --cd val -h '' -i stat=online @@ -513,6 +523,7 @@ public function envCommand(): void /** * This is a demo for download a file to local * @usage {command} url=url saveTo=[saveAs] type=[bar|text] + * * @example {command} url=https://github.com/inhere/php-console/archive/master.zip type=bar */ public function downCommand(): int diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index fa63e22e..251807da 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -477,13 +477,15 @@ public function isAlone(): bool * * @return bool */ - protected function showHelp(): bool - { - if (!$definition = $this->getDefinition()) { - return false; - } + abstract protected function showHelp(): bool; - $this->log(Console::VERB_DEBUG, 'display help information by definition'); + /** + * @param InputDefinition $definition + * @param array $aliases + */ + protected function showHelpByDefinition(InputDefinition $definition, array $aliases = []): void + { + $this->log(Console::VERB_DEBUG, 'display help information by input definition'); // if has InputDefinition object. (The comment of the command will not be parsed and used at this time.) $help = $definition->getSynopsis(); @@ -514,7 +516,8 @@ protected function showHelp(): bool // align global options $help['global options:'] = FormatUtil::alignOptions(Application::getGlobalOptions()); - if (empty($help[0]) && $this->isAlone()) { + $isAlone = $this->isAlone(); + if ($isAlone && empty($help[0])) { $help[0] = self::getDescription(); } @@ -524,10 +527,14 @@ protected function showHelp(): bool // output description $this->write(ucfirst($help[0]) . PHP_EOL); + + if ($aliases) { + $this->output->writef('Alias: %s', implode(',', $aliases)); + } + unset($help[0]); $this->output->mList($help, ['sepChar' => ' ']); - return true; } /** @@ -546,7 +553,8 @@ protected function showHelpByMethodAnnotations(string $method, string $action = $name = $this->input->getCommand(); if (!$ref->hasMethod($method)) { - $this->write("The command [$name] don't exist in the group: " . static::getName()); + $subCmd = $this->input->getSubCommand(); + $this->write("The command '$subCmd' dont exist in the group: " . static::getName()); return 0; } @@ -635,6 +643,19 @@ protected function beforeRenderCommandHelp(array &$help): void * getter/setter methods **************************************************************************/ + /** + * @return array + */ + public function getAliases(): array + { + $aliases = []; + if ($this->app) { + $aliases = $this->app->getAliases(self::getName()); + } + + return $aliases; + } + /** * @param string $name */ diff --git a/src/Command.php b/src/Command.php index 908eda9c..ffde75ac 100644 --- a/src/Command.php +++ b/src/Command.php @@ -67,16 +67,18 @@ abstract class Command extends AbstractHandler implements CommandInterface */ protected function showHelp(): bool { - // help info has been build by input definition. - if (true === parent::showHelp()) { + $aliases = $this->getAliases(); + + // render help by input definition. + if ($definition = $this->getDefinition()) { + $this->showHelpByDefinition($definition, $aliases); return true; } $execMethod = 'execute'; - $cmdAliases = static::aliases(); - $this->logf(Console::VERB_CRAZY, "display help info for the command: %s", static::$name); + $this->logf(Console::VERB_CRAZY, "display help info for the command: %s", self::getName()); - return $this->showHelpByMethodAnnotations($execMethod, '', $cmdAliases) !== 0; + return $this->showHelpByMethodAnnotations($execMethod, '', $aliases) !== 0; } } diff --git a/src/Controller.php b/src/Controller.php index cc40baf7..ad3fbc68 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -193,7 +193,7 @@ protected function onNotFound(string $action): bool } /** - * @param string $command + * @param string $command command in the group * * @return int|mixed * @throws ReflectionException @@ -221,6 +221,9 @@ public function run(string $command = '') return $this->showHelp(); } + $this->input->setSubCommand($command); + + // get real sub-command name $command = $this->getRealCommandName($command); // convert 'boo-foo' to 'booFoo' @@ -339,8 +342,15 @@ final public function execute($input, $output) */ protected function showHelp(): bool { - // help info has been build by input definition. - if (true === parent::showHelp()) { + // render help by Definition + if ($definition = $this->getDefinition()) { + if ($action = $this->action) { + $aliases = $this->getCommandAliases($action); + } else { + $aliases = $this->getAliases(); + } + + $this->showHelpByDefinition($definition, $aliases); return true; } @@ -500,6 +510,11 @@ final public function showCommandList(): void $this->output->startBuffer(); $this->output->write(ucfirst($classDes) . PHP_EOL); + + if ($aliases = $this->getAliases()) { + $this->output->writef('Alias: %s', implode(',', $aliases)); + } + $this->output->mList([ 'Usage:' => $usage, //'Group Name:' => "$sName", diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index bd959f7c..502fa93e 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -47,12 +47,20 @@ abstract class AbstractInput implements InputInterface /** * the command name(Is first argument) - * e.g `start` OR `start` + * e.g `git` OR `start` * * @var string */ protected $command = ''; + /** + * the command name(Is first argument) + * e.g `subcmd` in the `./app group subcmd` + * + * @var string + */ + protected $subCommand = ''; + /** * eg `./examples/app home:useArg status=2 name=john arg0 -s=test --page=23` * @@ -227,4 +235,20 @@ public function setTokens(array $tokens): void { $this->tokens = $tokens; } + + /** + * @return string + */ + public function getSubCommand(): string + { + return $this->subCommand; + } + + /** + * @param string $subCommand + */ + public function setSubCommand(string $subCommand): void + { + $this->subCommand = $subCommand; + } } From 8635dabf312455d395792f73663ac8ebac7c6dba Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 27 Jan 2021 21:42:44 +0800 Subject: [PATCH 070/258] fix readme badge icon error --- README.md | 2 +- README.zh-CN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 23cc0c35..54fe5528 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![License](https://img.shields.io/packagist/l/inhere/console.svg?style=flat-square)](LICENSE) [![Php Version](https://img.shields.io/badge/php-%3E=7.3.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) [![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console) -[![Github Actions Status](https://github.com/inhere/console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/console/actions) +[![Github Actions Status](https://github.com/inhere/php-console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/php-console/actions) A simple, full-featured php command line application library. Provide console parameter parsing, command run, color style output, user information interaction, and special format information display. diff --git a/README.zh-CN.md b/README.zh-CN.md index ed2f0bc4..edc88f82 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -3,7 +3,7 @@ [![License](https://img.shields.io/packagist/l/inhere/console.svg?style=flat-square)](LICENSE) [![Php Version](https://img.shields.io/badge/php-%3E=7.1.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) [![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console) -[![Github Actions Status](https://github.com/inhere/console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/console/actions) +[![Github Actions Status](https://github.com/inhere/php-console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/php-console/actions) 简洁、功能全面的php命令行应用库。提供控制台参数解析, 命令运行,颜色风格输出, 用户信息交互, 特殊格式信息显示。 From 33dae3a081d84e9c05f67c95196eeafd7347164e Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 27 Jan 2021 21:48:07 +0800 Subject: [PATCH 071/258] update readme --- README.md | 8 +++++--- README.zh-CN.md | 5 +++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 54fe5528..286352f1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PHP console +# PHP Console [![License](https://img.shields.io/packagist/l/inhere/console.svg?style=flat-square)](LICENSE) [![Php Version](https://img.shields.io/badge/php-%3E=7.3.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) @@ -6,6 +6,7 @@ [![Github Actions Status](https://github.com/inhere/php-console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/php-console/actions) A simple, full-featured php command line application library. + Provide console parameter parsing, command run, color style output, user information interaction, and special format information display. > **[中文README](./README.zh-CN.md)** @@ -20,7 +21,8 @@ Provide console parameter parsing, command run, color style output, user informa - Command line application, `controller`, `command` parsing run on the command line - Support for setting aliases for commands. A command can have multiple aliases. Support command display/hide, enable/disable -- Full-featured command line option parameter parsing (named parameters, short options, long options...). `input`, `output` of the command line, management, use +- Full-featured command line option parameter parsing (named parameters, short options `-s`, long options `--long`). +- The `input`, `output` of the command line, management, use - Command method comments are automatically parsed as help information (by default, `@usage` `@arguments` `@options` `@example`) - Support for outputting message texts of multiple color styles (`info`, `comment`, `success`, `warning`, `danger`, `error` ... ) - Commonly used special format information display (`section`, `panel`, `padding`, `helpPanel`, `table`, `tree`, `title`, `list`, `multiList`) @@ -42,7 +44,7 @@ Provide console parameter parsing, command run, color style output, user informa > All features, effects; can be run in the example code `phps/app` in `examples/`. Basically covers all the features and can be tested directly -## Quick installation +## Installation ```bash composer require inhere/console diff --git a/README.zh-CN.md b/README.zh-CN.md index edc88f82..59e4bb44 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,4 +1,4 @@ -# php 命令行应用库 +# PHP Console [![License](https://img.shields.io/packagist/l/inhere/console.svg?style=flat-square)](LICENSE) [![Php Version](https://img.shields.io/badge/php-%3E=7.1.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) @@ -19,7 +19,8 @@ - 命令行应用, 命令行的 `controller`, `command` 解析运行 - 支持给命令设置别名,一个命令可以有多个别名。支持命令的显示/隐藏,启用/禁用 -- 功能全面的命令行的选项参数解析(命名参数,短选项,长选项 ...)。命令行的 `input`, `output` 管理、使用 +- 功能全面的命令行的选项参数解析(命名参数,短选项 `-s`,长选项 `--long`) +- 命令行下的 输入`input`, 输出 `output` 管理、使用 - 命令方法注释自动解析为帮助信息(默认提取 `@usage` `@arguments` `@options` `@example` 等信息) - 支持输出多种颜色风格的消息文本(`info`, `comment`, `success`, `warning`, `danger`, `error` ... ) - 常用的特殊格式信息显示(`section`, `panel`, `padding`, `helpPanel`, `table`, `tree`, `title`, `list`, `multiList`) From d8dbb5fc892c4e540914bea2884254cb4b8f8ef3 Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 27 Jan 2021 21:48:59 +0800 Subject: [PATCH 072/258] update readme for php version limit --- README.md | 2 +- README.zh-CN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 286352f1..c2f11720 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # PHP Console [![License](https://img.shields.io/packagist/l/inhere/console.svg?style=flat-square)](LICENSE) -[![Php Version](https://img.shields.io/badge/php-%3E=7.3.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) +[![Php Version](https://img.shields.io/badge/php-%3E=7.2.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) [![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console) [![Github Actions Status](https://github.com/inhere/php-console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/php-console/actions) diff --git a/README.zh-CN.md b/README.zh-CN.md index 59e4bb44..66151d18 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,7 +1,7 @@ # PHP Console [![License](https://img.shields.io/packagist/l/inhere/console.svg?style=flat-square)](LICENSE) -[![Php Version](https://img.shields.io/badge/php-%3E=7.1.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) +[![Php Version](https://img.shields.io/badge/php-%3E=7.2.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) [![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console) [![Github Actions Status](https://github.com/inhere/php-console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/php-console/actions) From ffa2ea1b28156dedb6b89b3e87635b9c23b57727 Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 28 Jan 2021 18:14:42 +0800 Subject: [PATCH 073/258] upsome for format code --- src/Util/FormatUtil.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index 1e28e647..53e0a32c 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -9,6 +9,7 @@ namespace Inhere\Console\Util; use Toolkit\Cli\ColorTag; +use Toolkit\Stdlib\Helper\JsonHelper; use Toolkit\Sys\Sys; use function array_keys; use function array_merge; @@ -279,12 +280,11 @@ public static function spliceKeyValue(array $data, array $opts = []): string if (is_array($value)) { $temp = '['; - /** @var array $value */ foreach ($value as $k => $val) { if (is_bool($val)) { $val = $val ? '(True)' : '(False)'; } else { - $val = is_scalar($val) ? (string)$val : gettype($val); + $val = is_scalar($val) ? (string)$val : JsonHelper::unescaped($val); } $temp .= (!is_numeric($k) ? "$k: " : '') . "$val, "; From d68c789d4eea66a90e496950bd84ea4447ff1279 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 29 Jan 2021 01:49:34 +0800 Subject: [PATCH 074/258] breaking: change command run() params type --- .github/workflows/php.yml | 2 +- examples/Command/TestCommand.php | 9 + examples/alone | 2 +- src/AbstractHandler.php | 11 +- src/Application.php | 4 +- src/CallableCommand.php | 54 ++++++ src/Command.php | 19 +- src/Concern/InputFlagsWareTrait.php | 13 ++ src/Concern/StyledOutputAwareTrait.php | 2 +- src/Concern/SubCommandsWareTrait.php | 220 +++++++++++++++++++++++ src/Console.php | 4 + src/Contract/CommandHandlerInterface.php | 4 +- src/Controller.php | 8 +- src/Flag/Flags.php | 26 +++ src/Util/Helper.php | 17 ++ test/ControllerTest.php | 2 +- 16 files changed, 374 insertions(+), 23 deletions(-) create mode 100644 src/CallableCommand.php create mode 100644 src/Concern/InputFlagsWareTrait.php create mode 100644 src/Concern/SubCommandsWareTrait.php create mode 100644 src/Flag/Flags.php diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index dd1b697c..6ac613db 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: true matrix: - php: [7.3, 7.4] # + php: [7.2, 7.3, 7.4] # os: [ubuntu-latest, macOS-latest] # windows-latest, steps: diff --git a/examples/Command/TestCommand.php b/examples/Command/TestCommand.php index 6cb813bb..896edcab 100644 --- a/examples/Command/TestCommand.php +++ b/examples/Command/TestCommand.php @@ -21,6 +21,15 @@ class TestCommand extends Command protected static $description = 'this is a test independent command'; + protected function commands(): array + { + return [ + 'sub' => static function($in, $out) { + $out->println('hello, this is an sub command of test.'); + }, + ]; + } + /** * test text * @usage {name} test message diff --git a/examples/alone b/examples/alone index 186bcd0e..20f9dd62 100644 --- a/examples/alone +++ b/examples/alone @@ -18,7 +18,7 @@ $ctrl = new HomeController($input, new Output()); $ctrl->setDetached(); try { - exit($ctrl->run($input->getCommand())); + exit($ctrl->run([$input->getCommand()])); } catch (\Exception $e) { $message = Color::apply('error', $e->getMessage()); diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 251807da..80e2e1a2 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -10,6 +10,7 @@ use Inhere\Console\Concern\AttachApplicationTrait; use Inhere\Console\Concern\CommandHelpTrait; +use Inhere\Console\Concern\SubCommandsWareTrait; use Inhere\Console\Contract\CommandHandlerInterface; use Inhere\Console\Contract\CommandInterface; use Inhere\Console\IO\Input; @@ -59,6 +60,7 @@ abstract class AbstractHandler implements CommandHandlerInterface Use CommandHelpTrait; use InputOutputAwareTrait; use UserInteractAwareTrait; + use SubCommandsWareTrait; /** * group/command name e.g 'test' 'test:one' @@ -151,6 +153,8 @@ public function __construct(Input $input, Output $output, InputDefinition $defin protected function init(): void { + $this->debugf('attach inner sub-commands to "%s"', self::getName()); + $this->addCommands($this->commands()); } protected function afterInit(): void @@ -227,14 +231,13 @@ protected function annotationVars(): array /** * run command * - * @param string $command + * @param array $args * * @return int|mixed - * @throws RuntimeException - * @throws InvalidArgumentException */ - public function run(string $command = '') + public function run(array $args) { + $this->debugf('begin run command. load input definition configure'); // load input definition configure $this->configure(); diff --git a/src/Application.php b/src/Application.php index f12e4a7b..5ec027d0 100644 --- a/src/Application.php +++ b/src/Application.php @@ -346,7 +346,7 @@ protected function runCommand(string $name, $handler, array $options) $object::setName($name); $object->setApp($this); - $result = $object->run(); + $result = $object->run([]); } return $result; @@ -398,6 +398,6 @@ protected function runAction(string $group, string $action, $handler, array $opt $handler->setDetached(); } - return $handler->run($action); + return $handler->run([$action]); } } diff --git a/src/CallableCommand.php b/src/CallableCommand.php new file mode 100644 index 00000000..0862cf25 --- /dev/null +++ b/src/CallableCommand.php @@ -0,0 +1,54 @@ +callable) { + throw new \BadMethodCallException('The callable property is empty'); + } + + // call custom callable + return $call($input, $output); + } + + /** + * @param callable $callable + */ + public function setCallable(callable $callable): void + { + $this->callable = $callable; + } +} diff --git a/src/Command.php b/src/Command.php index ffde75ac..1822e6ae 100644 --- a/src/Command.php +++ b/src/Command.php @@ -28,18 +28,13 @@ */ abstract class Command extends AbstractHandler implements CommandInterface { + public const METHOD = 'execute'; + /** * @var Command */ protected $parent; - /** - * sub-commands of the command - * - * @var Command[] - */ - protected $commands = []; - /* * Do execute command */ @@ -59,6 +54,14 @@ abstract class Command extends AbstractHandler implements CommandInterface // ->addOption('test'); // } + /** + * @param Command $parent + */ + public function setParent(Command $parent): void + { + $this->parent = $parent; + } + /** * Show help information * @@ -75,7 +78,7 @@ protected function showHelp(): bool return true; } - $execMethod = 'execute'; + $execMethod = self::METHOD; $this->logf(Console::VERB_CRAZY, "display help info for the command: %s", self::getName()); diff --git a/src/Concern/InputFlagsWareTrait.php b/src/Concern/InputFlagsWareTrait.php new file mode 100644 index 00000000..e9709a6d --- /dev/null +++ b/src/Concern/InputFlagsWareTrait.php @@ -0,0 +1,13 @@ + [ + * 'handler' => MyCommand::class, // allow: string|Closure|CommandInterface + * 'options' => [] + * ] + * ] + */ + private $commands = []; + + /** + * Can attach sub-commands + * + * @return array + */ + protected function commands(): array + { + // [ + // 'cmd1' => function(){}, + // MySubCommand::class, + // 'cmd2' => MySubCommand2::class, + // new FooCommand, + // 'cmd3' => new FooCommand2(), + // ] + return []; + } + + /** + * @param string $name + */ + protected function dispatchCommand(string $name): void + { + + } + + /** + * Register a app independent console command + * + * @param string|CommandInterface $name + * @param string|Closure|CommandInterface|null $handler + * @param array $options + * array: + * - aliases The command aliases + * - description The description message + * + * @throws InvalidArgumentException + */ + public function addCommand(string $name, $handler = null, array $options = []): void + { + /** + * @var Command $name name is an command class + */ + if (!$handler && class_exists($name)) { + $handler = $name; + $name = $name::getName(); + } + + if (!$name || !$handler) { + Helper::throwInvalidArgument("Command 'name' and 'handler' cannot be empty! name: $name"); + } + + $this->validateName($name); + + if (isset($this->commands[$name])) { + Helper::throwInvalidArgument("Command '$name' have been registered!"); + } + + $options['aliases'] = isset($options['aliases']) ? (array)$options['aliases'] : []; + + if (is_string($handler)) { + if (!class_exists($handler)) { + Helper::throwInvalidArgument("The console command class [$handler] not exists!"); + } + + if (!is_subclass_of($handler, Command::class)) { + Helper::throwInvalidArgument('The console command class must is subclass of the: ' . Command::class); + } + + // not enable + /** @var Command $handler */ + if (!$handler::isEnabled()) { + return; + } + + // allow define aliases in Command class by Command::aliases() + if ($aliases = $handler::aliases()) { + $options['aliases'] = array_merge($options['aliases'], $aliases); + } + } elseif (!is_object($handler) || !method_exists($handler, '__invoke')) { + Helper::throwInvalidArgument( + 'The console command handler must is an subclass of %s OR a Closure OR a object have method __invoke()', + Command::class + ); + } + + // is an class name string + $this->commands[$name] = [ + 'type' => Console::CMD_SINGLE, + 'handler' => $handler, + 'options' => $options, + ]; + + // has alias option + if (isset($options['aliases'])) { + $this->setAlias($name, $options['aliases'], true); + } + } + + /** + * @param array $commands + * + * @throws InvalidArgumentException + */ + public function addCommands(array $commands): void + { + foreach ($commands as $name => $handler) { + if (is_int($name)) { + $this->addCommand($handler); + } else { + $this->addCommand($name, $handler); + } + } + } + + /********************************************************** + * helper methods + **********************************************************/ + + /** + * @param $name + * + * @throws InvalidArgumentException + */ + protected function validateName(string $name): void + { + // '/^[a-z][\w-]*:?([a-z][\w-]+)?$/' + $pattern = '/^[a-z][\w:-]+$/'; + + if (1 !== preg_match($pattern, $name)) { + throw new InvalidArgumentException("The command name '$name' is must match: $pattern"); + } + + // cannot be override. like: help, version + if ($this->isBlocked($name)) { + throw new InvalidArgumentException("The command name '$name' is not allowed. It is a built in command."); + } + } + + /** + * @param string $name + * + * @return bool + */ + public function isBlocked(string $name): bool + { + return in_array($name, $this->blocked, true); + } + + /** + * @return array + */ + public function getBlocked(): array + { + return $this->blocked; + } + + /** + * @param array $blocked + */ + public function setBlocked(array $blocked): void + { + $this->blocked = $blocked; + } + + /** + * @return array + */ + public function getCommandNames(): array + { + return array_keys($this->commands); + } +} diff --git a/src/Console.php b/src/Console.php index 1c7d7f94..d91b41c4 100644 --- a/src/Console.php +++ b/src/Console.php @@ -56,6 +56,10 @@ class Console extends Cli self::VERB_CRAZY => 'magenta', ]; + public const CMD_GROUP = 1; + + public const CMD_SINGLE = 2; + /** * @var Application */ diff --git a/src/Contract/CommandHandlerInterface.php b/src/Contract/CommandHandlerInterface.php index 2b496671..21e04352 100644 --- a/src/Contract/CommandHandlerInterface.php +++ b/src/Contract/CommandHandlerInterface.php @@ -33,11 +33,11 @@ interface CommandHandlerInterface /** * Run command * - * @param string $command + * @param array $args * * @return int|mixed return int is exit code. other is command exec result. */ - public function run(string $command = ''); + public function run(array $args); /** * @return InputDefinition|null diff --git a/src/Controller.php b/src/Controller.php index ad3fbc68..0a2acdbe 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -193,13 +193,15 @@ protected function onNotFound(string $action): bool } /** - * @param string $command command in the group + * @param array $args * * @return int|mixed * @throws ReflectionException */ - public function run(string $command = '') + public function run(array $args) { + $command = $args[0]; + if (!$command = trim($command, $this->delimiter)) { $command = $this->defaultAction; @@ -231,7 +233,7 @@ public function run(string $command = '') $this->debugf('will run the group action: %s, sub-command: %s', $this->action, $command); // do running - return parent::run($command); + return parent::run([$command]); } /** diff --git a/src/Flag/Flags.php b/src/Flag/Flags.php new file mode 100644 index 00000000..b4b1bfd2 --- /dev/null +++ b/src/Flag/Flags.php @@ -0,0 +1,26 @@ + Date: Fri, 29 Jan 2021 13:00:31 +0800 Subject: [PATCH 075/258] refact: mv some class --- docs/v4-run-workflow.puml | 27 +++++++++++++++++++++++ src/AbstractHandler.php | 12 ++++++++++ src/Concern/SubCommandsWareTrait.php | 10 +++++++++ src/Console.php | 16 ++++++++++++++ src/Flag/Flags.php | 15 +++++++++++++ src/{IO/Input => Flag}/InputArgument.php | 4 +--- src/{IO/Input => Flag}/InputArguments.php | 2 +- src/{IO/Input => Flag}/InputFlag.php | 2 +- src/{IO/Input => Flag}/InputOption.php | 2 +- src/{IO/Input => Flag}/InputOptions.php | 2 +- {test => src/IO}/TempStream.php | 0 11 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 docs/v4-run-workflow.puml rename src/{IO/Input => Flag}/InputArgument.php (93%) rename src/{IO/Input => Flag}/InputArguments.php (96%) rename src/{IO/Input => Flag}/InputFlag.php (99%) rename src/{IO/Input => Flag}/InputOption.php (98%) rename src/{IO/Input => Flag}/InputOptions.php (87%) rename {test => src/IO}/TempStream.php (100%) diff --git a/docs/v4-run-workflow.puml b/docs/v4-run-workflow.puml new file mode 100644 index 00000000..1ff8647e --- /dev/null +++ b/docs/v4-run-workflow.puml @@ -0,0 +1,27 @@ +@startuml +start +:input command line - Entry; + +:create app: new Application(); + +:create Input/Output instance; + +partition AppInit { + :save app instance to Console::$app; + :load application config; + :register command/group commands; +} + +:run: app->run(); + +partition AppRun { + :match global flags: --debug; + :match global help flag: -h|--help; + if (TRUE) then(Yes) + :display commands and exit; + endif + :match global flags: -V|--version; +} + +stop +@enduml \ No newline at end of file diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 80e2e1a2..35f8d6c8 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -237,6 +237,16 @@ protected function annotationVars(): array */ public function run(array $args) { + if (isset($args[0])) { + $first = $args[0]; + $rName = $this->resolveAlias($first); + + if ($this->isSubCommand($rName)) { + + } + + } + $this->debugf('begin run command. load input definition configure'); // load input definition configure $this->configure(); @@ -253,6 +263,8 @@ public function run(array $args) return -1; } + // $this->dispatchCommand($name); + // return False to deny goon run. if (false === $this->beforeExecute()) { return -1; diff --git a/src/Concern/SubCommandsWareTrait.php b/src/Concern/SubCommandsWareTrait.php index 129aa64f..e51e0942 100644 --- a/src/Concern/SubCommandsWareTrait.php +++ b/src/Concern/SubCommandsWareTrait.php @@ -164,6 +164,16 @@ public function addCommands(array $commands): void * helper methods **********************************************************/ + /** + * @param string $name + * + * @return bool + */ + public function isSubCommand(string $name): bool + { + return isset($this->commands[$name]); + } + /** * @param $name * diff --git a/src/Console.php b/src/Console.php index d91b41c4..9bd5a758 100644 --- a/src/Console.php +++ b/src/Console.php @@ -81,6 +81,22 @@ public static function setApp(Application $app): void self::$app = $app; } + /** + * @return Input + */ + public static function getInput(): Input + { + return self::$app->getInput(); + } + + /** + * @return Output + */ + public static function getOutput(): Output + { + return self::$app->getOutput(); + } + /** * @param array $config * @param Input|null $input diff --git a/src/Flag/Flags.php b/src/Flag/Flags.php index b4b1bfd2..c99552e1 100644 --- a/src/Flag/Flags.php +++ b/src/Flag/Flags.php @@ -2,6 +2,9 @@ namespace Inhere\Console\Flag; +use Inhere\Console\Concern\InputArgumentsTrait; +use Inhere\Console\Concern\InputOptionsTrait; + /** * Class Flags * @@ -9,11 +12,23 @@ */ class Flags { + use InputArgumentsTrait, InputOptionsTrait; + public function new(): self { return new self(); } + /** + * @param array $args + * + * @return array + */ + public static function parseArgs(array $args): array + { + return (new self())->parse($args); + } + /** * @param array|null $args * diff --git a/src/IO/Input/InputArgument.php b/src/Flag/InputArgument.php similarity index 93% rename from src/IO/Input/InputArgument.php rename to src/Flag/InputArgument.php index 00316ae7..d2bb14c8 100644 --- a/src/IO/Input/InputArgument.php +++ b/src/Flag/InputArgument.php @@ -6,9 +6,7 @@ * Time: 10:33 */ -namespace Inhere\Console\IO\Input; - -use Inhere\Console\IO\Input; +namespace Inhere\Console\Flag; /** * Class InputArgument diff --git a/src/IO/Input/InputArguments.php b/src/Flag/InputArguments.php similarity index 96% rename from src/IO/Input/InputArguments.php rename to src/Flag/InputArguments.php index 67ad84c8..ac6a56b5 100644 --- a/src/IO/Input/InputArguments.php +++ b/src/Flag/InputArguments.php @@ -6,7 +6,7 @@ * Time: 10:11 */ -namespace Inhere\Console\IO\Input; +namespace Inhere\Console\Flag; /** * Class InputArguments diff --git a/src/IO/Input/InputFlag.php b/src/Flag/InputFlag.php similarity index 99% rename from src/IO/Input/InputFlag.php rename to src/Flag/InputFlag.php index 0d322fb7..7c4e4a37 100644 --- a/src/IO/Input/InputFlag.php +++ b/src/Flag/InputFlag.php @@ -6,7 +6,7 @@ * Time: 10:28 */ -namespace Inhere\Console\IO\Input; +namespace Inhere\Console\Flag; use Inhere\Console\Contract\InputFlagInterface; use Inhere\Console\IO\Input; diff --git a/src/IO/Input/InputOption.php b/src/Flag/InputOption.php similarity index 98% rename from src/IO/Input/InputOption.php rename to src/Flag/InputOption.php index 236d7a65..4364bc0a 100644 --- a/src/IO/Input/InputOption.php +++ b/src/Flag/InputOption.php @@ -6,7 +6,7 @@ * Time: 10:28 */ -namespace Inhere\Console\IO\Input; +namespace Inhere\Console\Flag; use Inhere\Console\IO\Input; use function implode; diff --git a/src/IO/Input/InputOptions.php b/src/Flag/InputOptions.php similarity index 87% rename from src/IO/Input/InputOptions.php rename to src/Flag/InputOptions.php index f509f26e..a2e8b6b4 100644 --- a/src/IO/Input/InputOptions.php +++ b/src/Flag/InputOptions.php @@ -6,7 +6,7 @@ * Time: 10:10 */ -namespace Inhere\Console\IO\Input; +namespace Inhere\Console\Flag; /** * Class InputOptions diff --git a/test/TempStream.php b/src/IO/TempStream.php similarity index 100% rename from test/TempStream.php rename to src/IO/TempStream.php From 1cb7ccea2225dafb6d4ead0a7f62344e74479b80 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 15 Feb 2021 22:45:52 +0800 Subject: [PATCH 076/258] up: update some flag logic --- src/Anotation/.keep | 0 src/Command.php | 20 ++ src/Contract/InputFlagInterface.php | 15 + src/Flag/{InputArgument.php => Argument.php} | 8 +- ...putArguments.php => CmdArgumentsTrait.php} | 17 +- src/Flag/{InputFlag.php => Flag.php} | 137 +++++++- src/Flag/{Parser.php => FlagException.php} | 4 +- src/Flag/FlagOptionsTrait.php | 83 +++++ src/Flag/Flags.php | 306 +++++++++++++++++- src/Flag/InputOptions.php | 19 -- src/Flag/{InputOption.php => Option.php} | 38 ++- src/IO/AbstractInput.php | 19 +- src/IO/Input.php | 8 +- test/BaseTestCase.php | 10 + test/Flag/FlagsTest.php | 24 ++ 15 files changed, 631 insertions(+), 77 deletions(-) create mode 100644 src/Anotation/.keep rename src/Flag/{InputArgument.php => Argument.php} (81%) rename src/Flag/{InputArguments.php => CmdArgumentsTrait.php} (78%) rename src/Flag/{InputFlag.php => Flag.php} (53%) rename src/Flag/{Parser.php => FlagException.php} (61%) create mode 100644 src/Flag/FlagOptionsTrait.php delete mode 100644 src/Flag/InputOptions.php rename src/Flag/{InputOption.php => Option.php} (65%) create mode 100644 test/BaseTestCase.php create mode 100644 test/Flag/FlagsTest.php diff --git a/src/Anotation/.keep b/src/Anotation/.keep new file mode 100644 index 00000000..e69de29b diff --git a/src/Command.php b/src/Command.php index 1822e6ae..af5714f0 100644 --- a/src/Command.php +++ b/src/Command.php @@ -62,6 +62,26 @@ public function setParent(Command $parent): void $this->parent = $parent; } + /** + * @return $this + */ + public function getRootCommand(): Command + { + if ($this->parent) { + return $this->parent->getRootCommand(); + } + + return $this; + } + + /** + * @return Command|null + */ + public function getParent(): ?Command + { + return $this->parent; + } + /** * Show help information * diff --git a/src/Contract/InputFlagInterface.php b/src/Contract/InputFlagInterface.php index 8c8e49d3..13a0cf74 100644 --- a/src/Contract/InputFlagInterface.php +++ b/src/Contract/InputFlagInterface.php @@ -9,6 +9,21 @@ */ interface InputFlagInterface { + // fixed args and opts for a command/controller-command + public const ARG_REQUIRED = 1; + + public const ARG_OPTIONAL = 2; + + public const ARG_IS_ARRAY = 4; + + public const OPT_BOOLEAN = 1; // eq symfony InputOption::VALUE_NONE + + public const OPT_REQUIRED = 2; + + public const OPT_OPTIONAL = 4; + + public const OPT_IS_ARRAY = 8; + /** * @param int $mode * diff --git a/src/Flag/InputArgument.php b/src/Flag/Argument.php similarity index 81% rename from src/Flag/InputArgument.php rename to src/Flag/Argument.php index d2bb14c8..97412e9f 100644 --- a/src/Flag/InputArgument.php +++ b/src/Flag/Argument.php @@ -14,7 +14,7 @@ * * @package Inhere\Console\IO\Input */ -class InputArgument extends InputFlag +class Argument extends Flag { /** * The argument position @@ -28,7 +28,7 @@ class InputArgument extends InputFlag */ public function isArray(): bool { - return $this->hasMode(Input::ARG_IS_ARRAY); + return $this->hasMode(Flag::ARG_IS_ARRAY); } /** @@ -36,7 +36,7 @@ public function isArray(): bool */ public function isOptional(): bool { - return $this->hasMode(Input::ARG_OPTIONAL); + return $this->hasMode(Flag::ARG_OPTIONAL); } /** @@ -44,7 +44,7 @@ public function isOptional(): bool */ public function isRequired(): bool { - return $this->hasMode(Input::ARG_REQUIRED); + return $this->hasMode(Flag::ARG_REQUIRED); } /** diff --git a/src/Flag/InputArguments.php b/src/Flag/CmdArgumentsTrait.php similarity index 78% rename from src/Flag/InputArguments.php rename to src/Flag/CmdArgumentsTrait.php index ac6a56b5..199dcb25 100644 --- a/src/Flag/InputArguments.php +++ b/src/Flag/CmdArgumentsTrait.php @@ -14,13 +14,26 @@ * * @package Inhere\Console\IO\Input */ -class InputArguments +trait CmdArgumentsTrait { /** - * @var array + * @var array [name => index] + */ + private $name2index = []; + + /** + * @var Argument[] */ private $arguments = []; + /** + * @param Argument $argument + */ + public function addArgument(Argument $argument): void + { + $this->arguments[] = $argument; + } + /** * @param string $name * @param int|null $mode diff --git a/src/Flag/InputFlag.php b/src/Flag/Flag.php similarity index 53% rename from src/Flag/InputFlag.php rename to src/Flag/Flag.php index 7c4e4a37..f6577109 100644 --- a/src/Flag/InputFlag.php +++ b/src/Flag/Flag.php @@ -13,21 +13,45 @@ /** * Class InputFlag - * - definition a input item(option|argument) + * - definition a input flag item(option|argument) * * @package Inhere\Console\IO\Input */ -abstract class InputFlag implements InputFlagInterface +abstract class Flag implements InputFlagInterface { + public const TYPE_INT = 'int'; + + public const TYPE_BOOL = 'bool'; + + public const TYPE_FLOAT = 'float'; + + public const TYPE_STRING = 'string'; + + public const TYPE_ARRAY = 'array'; + + // extend types + + public const TYPE_INTS = 'int[]'; + + public const TYPE_STRINGS = 'string[]'; + + public const TYPE_MIXED = 'mixed'; + + public const TYPE_CUSTOM = 'custom'; + + public const TYPE_UNKNOWN = ''; + /** * @var string */ private $name; /** + * The flag description + * * @var string */ - private $description; + private $desc; /** * @var int @@ -35,11 +59,11 @@ abstract class InputFlag implements InputFlagInterface private $mode; /** - * The argument data type. (eg: 'int', 'bool', 'string', 'array', 'mixed') + * The flag data type. (eg: 'int', 'bool', 'string', 'array', 'mixed') * * @var string */ - private $type = ''; + private $type = self::TYPE_UNKNOWN; /** * The default value @@ -48,6 +72,21 @@ abstract class InputFlag implements InputFlagInterface */ private $default; + /** + * The flag value + * + * @var mixed + */ + private $value; + + /** + * The flag value validator + * - if validate fail, please throw FlagException + * + * @var callable + */ + private $validator; + /** * @param string $name * @param int $mode see Input::ARG_* or Input::OPT_* @@ -56,7 +95,7 @@ abstract class InputFlag implements InputFlagInterface * * @return static */ - public static function make(string $name, int $mode = 0, string $description = '', $default = null) + public static function new(string $name, string $description = '', int $mode = 0, $default = null) { return new static($name, $mode, $description, $default); } @@ -64,20 +103,27 @@ public static function make(string $name, int $mode = 0, string $description = ' /** * Class constructor. * - * @param string $name + * @param string $name The flag name * @param int $mode see Input::ARG_* or Input::OPT_* - * @param string $description + * @param string $desc * @param mixed $default The default value * - for Input::ARG_OPTIONAL mode only * - must be null for InputOption::OPT_BOOL */ - public function __construct(string $name, int $mode = 0, string $description = '', $default = null) + public function __construct(string $name, int $mode = 0, string $desc = '', $default = null) { $this->name = $name; $this->mode = $mode; $this->default = $default; - $this->setDescription($description); + $this->setDesc($desc); + } + + public function init(): void + { + if ($this->isArray()) { + $this->type = self::TYPE_ARRAY; + } } /****************************************************************** @@ -94,7 +140,6 @@ public function hasMode(int $mode): bool return ($this->mode & $mode) > 0; } - /****************************************************************** * *****************************************************************/ @@ -166,17 +211,17 @@ public function setDefault($default): void /** * @return string */ - public function getDescription(): string + public function getDesc(): string { - return $this->description; + return $this->desc; } /** - * @param string $description + * @param string $desc */ - public function setDescription(string $description): void + public function setDesc(string $desc): void { - $this->description = $description; + $this->desc = $desc; } /** @@ -192,7 +237,65 @@ public function toArray(): array 'isArray' => $this->isArray(), 'isOptional' => $this->isOptional(), 'isRequired' => $this->isRequired(), - 'description' => $this->description, + 'description' => $this->desc, ]; } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * @param mixed $value + */ + public function setValue($value): void + { + // filter value by type + switch ($this->type) { + case self::TYPE_INT: + $value = (int)$value; + break; + case self::TYPE_BOOL: + $value = (bool)$value; + break; + case self::TYPE_FLOAT: + $value = (float)$value; + break; + case self::TYPE_STRING: + $value = (string)$value; + break; + // case self::TYPE_ARRAY: + // $value = (string)$value; + // break; + default: + // nothing + break; + } + + // has validator + if ($cb = $this->validator) { + $value = $cb($value); + // if (false === $ok) { + // throw new FlagException(''); + // } + } + + if ($this->isArray()) { + $this->value[] = $value; + } else { + $this->value = $value; + } + } + + /** + * @param callable $validator + */ + public function setValidator(callable $validator): void + { + $this->validator = $validator; + } } diff --git a/src/Flag/Parser.php b/src/Flag/FlagException.php similarity index 61% rename from src/Flag/Parser.php rename to src/Flag/FlagException.php index c241ab48..fe79d13c 100644 --- a/src/Flag/Parser.php +++ b/src/Flag/FlagException.php @@ -3,11 +3,11 @@ namespace Inhere\Console\Flag; /** - * Class Parser + * Class FlagException * * @package Inhere\Console\Flag */ -class Parser +class FlagException extends \RuntimeException { } diff --git a/src/Flag/FlagOptionsTrait.php b/src/Flag/FlagOptionsTrait.php new file mode 100644 index 00000000..03718bd6 --- /dev/null +++ b/src/Flag/FlagOptionsTrait.php @@ -0,0 +1,83 @@ +setShortcut($shorts); + + $this->addOption($opt); + } + + /** + * @param Option $option + */ + public function addOption(Option $option): void + { + $name = $option->getName(); + + $this->defined[$name] = $option; + } + + /** + * @param Option[] $options + */ + public function addOptions(array $options): void + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * @return Option[] + */ + public function getDefinedOptions(): array + { + return $this->defined; + } + + /** + * @return Option[] + */ + public function getMatchedOptions(): array + { + return $this->matched; + } +} diff --git a/src/Flag/Flags.php b/src/Flag/Flags.php index c99552e1..9cc3d2f6 100644 --- a/src/Flag/Flags.php +++ b/src/Flag/Flags.php @@ -2,8 +2,12 @@ namespace Inhere\Console\Flag; -use Inhere\Console\Concern\InputArgumentsTrait; -use Inhere\Console\Concern\InputOptionsTrait; +use Inhere\Console\Concern\NameAliasTrait; +use function array_shift; +use function count; +use function ltrim; +use function strlen; +use function substr; /** * Class Flags @@ -12,23 +16,91 @@ */ class Flags { - use InputArgumentsTrait, InputOptionsTrait; + // use InputArgumentsTrait, InputOptionsTrait; + use FlagOptionsTrait; + use CmdArgumentsTrait; + use NameAliasTrait; - public function new(): self + /** + * @var self + */ + private static $std; + + /** + * @var callable + */ + private $helpRenderer; + + /** + * @var bool + */ + private $parsed = false; + + /** + * @var bool + */ + private $autoBindArgs = false; + + /** + * The raw input args + * + * @var array + */ + private $rawArgs = []; + + /** + * The remaining args on parsed + * + * @var array + */ + private $args = []; + + /** + * @return $this + */ + public static function new(): self { return new self(); } /** - * @param array $args + * @return $this + */ + public static function std(): self + { + if (!self::$std) { + self::$std = new self(); + } + + return self::$std; + } + + /************************************************************************** + * parse command option flags + **************************************************************************/ + + /** + * @param array|null $args * * @return array */ - public static function parseArgs(array $args): array + public static function parseArgs(array $args = null): array { return (new self())->parse($args); } + /** + * @var string + */ + private $curOptKey = ''; + + private $parseStatus = self::STATUS_OK; + + public const STATUS_OK = 0; + public const STATUS_ERR = 1; + public const STATUS_END = 2; + public const STATUS_HELP = 3; // found `-h|--help` flag + /** * @param array|null $args * @@ -36,6 +108,228 @@ public static function parseArgs(array $args): array */ public function parse(array $args = null): array { + if ($args === null) { + $args = $_SERVER['argv']; + } + + $this->parsed = true; + $this->rawArgs = $this->args = $args; + + while (true) { + [$goon, $status] = $this->parseOne(); + if ($goon) { + continue; + } + + if (self::STATUS_OK === $status) { + break; + } + } + + // binding remaining args. + if ($this->args && $this->autoBindArgs) { + $this->bindingArguments(); + } + return []; } + + /** + * parse one flag. + * + * will stop on: + * - found `-h|--help` flag + * - found first arg(not an option) + * + * @return array [bool, status] + */ + protected function parseOne(): array + { + $count = count($this->args); + if ($count === 0) { + return [false, self::STATUS_OK]; + } + + $arg = array_shift($this->args); + + // empty, continue. + if ('' === $arg) { + return [true, self::STATUS_OK]; + } + + // is not an option flag. exit. + if ($arg[0] !== '-') { + return [false, self::STATUS_OK]; + } + + $name = ltrim($arg, '-'); + + // invalid arg. eg: '--' // ignore + if ('' === $name) { + return [true, self::STATUS_OK]; + } + + $value = ''; + $hasVal = false; + + $len = strlen($name); + for ($i = 0; $i < $len; $i++) { + if ($name[$i] === '=') { + $hasVal = true; + $name = substr($name, 0, $i); + + // fix: `--name=` no value string. + if ($i + 1 < $len) { + $value = substr($name, $i + 1); + } + } + } + + $rName = $this->resolveAlias($name); + if (!isset($this->defined[$rName])) { + throw new FlagException("flag option provided but not defined: $arg", 404); + } + + $opt = $this->defined[$rName]; + + // bool option always set TRUE. + if ($opt->isBoolean()) { + $boolVal = true; + if ($hasVal) { + $boolVal = self::filterBool($value); + } + $opt->setValue($boolVal); + } else { + if (!$hasVal && count($this->args) > 0) { + // value is next arg + $hasVal = true; + $ntArg = $this->args[0]; + + // is not an option value. + if ($ntArg[0] === '-') { + $hasVal = false; + } else { + $value = array_shift($this->args); + } + } + + if (!$hasVal) { + throw new FlagException("flag option '$arg' needs an value", 400); + } + + // set value + $opt->setValue($value); + } + + return [true, self::STATUS_OK]; + } + + // These words will be as a Boolean value + private const TRUE_WORDS = '|on|yes|true|'; + private const FALSE_WORDS = '|off|no|false|'; + + /** + * @param string $val + * + * @return bool|null + */ + public static function filterBool(string $val): ?bool + { + // check it is a bool value. + return false !== stripos(self::TRUE_WORDS, "|$val|"); + } + + /** + * @param string $val + * + * @return bool|null + */ + public static function filterBoolV2(string $val): ?bool + { + // check it is a bool value. + if (false !== stripos(self::TRUE_WORDS, "|$val|")) { + return true; + } + + if (false !== stripos(self::FALSE_WORDS, "|$val|")) { + return false; + } + + // return null; + return null; + } + + /************************************************************************** + * parse and binding command arguments + **************************************************************************/ + + /** + * parse and binding command arguments + * + * NOTICE: must call it on options parsed. + */ + public function bindingArguments(): void + { + if (!$this->args) { + return; + } + + // TODO ... + } + + /** + * @return callable + */ + public function getHelpRenderer(): callable + { + return $this->helpRenderer; + } + + /** + * @param callable $helpRenderer + */ + public function setHelpRenderer(callable $helpRenderer): void + { + $this->helpRenderer = $helpRenderer; + } + + /** + * @return array + */ + public function getRawArgs(): array + { + return $this->rawArgs; + } + + /** + * @return array + */ + public function getArgs(): array + { + return $this->args; + } + + /** + * @return bool + */ + public function isAutoBindArgs(): bool + { + return $this->autoBindArgs; + } + + /** + * @param bool $autoBindArgs + */ + public function setAutoBindArgs(bool $autoBindArgs): void + { + $this->autoBindArgs = $autoBindArgs; + } + + /** + * @return bool + */ + public function isParsed(): bool + { + return $this->parsed; + } } diff --git a/src/Flag/InputOptions.php b/src/Flag/InputOptions.php deleted file mode 100644 index a2e8b6b4..00000000 --- a/src/Flag/InputOptions.php +++ /dev/null @@ -1,19 +0,0 @@ -hasMode(Input::OPT_IS_ARRAY); + return $this->hasMode(Flag::OPT_IS_ARRAY); } /** @@ -53,7 +52,7 @@ public function isArray(): bool */ public function isOptional(): bool { - return $this->hasMode(Input::OPT_OPTIONAL); + return $this->hasMode(Flag::OPT_OPTIONAL); } /** @@ -61,7 +60,7 @@ public function isOptional(): bool */ public function isRequired(): bool { - return $this->hasMode(Input::OPT_REQUIRED); + return $this->hasMode(Flag::OPT_REQUIRED); } /** @@ -69,7 +68,7 @@ public function isRequired(): bool */ public function isBoolean(): bool { - return $this->hasMode(Input::OPT_BOOLEAN); + return $this->hasMode(Flag::OPT_BOOLEAN); } /** @@ -97,31 +96,30 @@ public function getShortcut(): string } /** - * @param string $shortcut + * @param string $shortcut eg: 'a|b' */ - public function setShortcutsByString(string $shortcut): void + public function setShortcut(string $shortcut): void { $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); $shortcuts = array_filter($shortcuts); - $this->setShortcuts($shortcuts); + $this->setShorts($shortcuts); } /** * @return array */ - public function getShortcuts(): array + public function getShorts(): array { - return $this->shortcuts; + return $this->shorts; } /** - * @param array $shortcuts + * @param array $shorts */ - public function setShortcuts(array $shortcuts): void + public function setShorts(array $shorts): void { - $this->shortcuts = $shortcuts; - $this->shortcut = implode('|', $shortcuts); + $this->shorts = $shorts; + $this->shortcut = implode('|', $shorts); } - } diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index 502fa93e..031184dc 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -11,6 +11,7 @@ use Inhere\Console\Concern\InputArgumentsTrait; use Inhere\Console\Concern\InputOptionsTrait; use Inhere\Console\Contract\InputInterface; +use function basename; use function getcwd; use function is_int; use function trim; @@ -30,7 +31,7 @@ abstract class AbstractInput implements InputInterface protected $pwd; /** - * The script path + * The bin script path * e.g `./bin/app` OR `bin/cli.php` * * @var string @@ -38,7 +39,7 @@ abstract class AbstractInput implements InputInterface protected $script; /** - * The script name + * The bin script name * e.g `app` OR `cli.php` * * @var string @@ -164,12 +165,24 @@ public function getScript(): string return $this->script; } + /** + * @return string + */ + public function getScriptPath(): string + { + return $this->script; + } + /** * @param string $script */ public function setScript(string $script): void { - $this->script = $script; + if ($script) { + $this->script = $script; + // update scriptName + $this->scriptName = basename($script); + } } /** diff --git a/src/IO/Input.php b/src/IO/Input.php index 66951273..3daa433f 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -69,11 +69,11 @@ protected function collectInfo(array $args): void $this->getPwd(); $this->tokens = $args; - $this->script = array_shift($args); - $this->flags = $args; // no script - // bin name - $this->scriptName = basename($this->script); + $script = array_shift($args); + $this->setScript($script); + + $this->flags = $args; // no script // full script $this->fullScript = implode(' ', $args); diff --git a/test/BaseTestCase.php b/test/BaseTestCase.php new file mode 100644 index 00000000..8dedeb75 --- /dev/null +++ b/test/BaseTestCase.php @@ -0,0 +1,10 @@ +addOption(Option::new('name')); + + $args = ['--name', 'inhere', '-s', 'sv', '-f']; + } +} From eb1d44922ebb0da811decaa12766814bc24cae3e Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 19 Apr 2021 21:51:30 +0800 Subject: [PATCH 077/258] up: use Str::padByWidth support zh-CN words on render table --- src/Component/Formatter/Table.php | 39 ++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/Component/Formatter/Table.php b/src/Component/Formatter/Table.php index b344e129..2be79f66 100644 --- a/src/Component/Formatter/Table.php +++ b/src/Component/Formatter/Table.php @@ -18,8 +18,8 @@ use function array_sum; use function ceil; use function count; +use function is_bool; use function is_string; -use function mb_strlen; use function ucwords; /** @@ -131,7 +131,7 @@ public static function show(array $data, string $title = 'Data Table', array $op $hasHead = true; } - $info['columnMaxWidth'][$index] = mb_strlen($name, 'UTF-8'); + $info['columnMaxWidth'][$index] = Str::utf8Len($name, 'UTF-8'); } } @@ -140,14 +140,18 @@ public static function show(array $data, string $title = 'Data Table', array $op foreach ((array)$row as $value) { // collection column max width if (isset($info['columnMaxWidth'][$colIndex])) { - $colWidth = mb_strlen($value, 'UTF-8'); + if (is_bool($value)) { + $colWidth = $value ? 4 : 5; + } else { + $colWidth = Str::utf8Len($value, 'UTF-8'); + } // If current column width gt old column width. override old width. if ($colWidth > $info['columnMaxWidth'][$colIndex]) { $info['columnMaxWidth'][$colIndex] = $colWidth; } } else { - $info['columnMaxWidth'][$colIndex] = mb_strlen($value, 'UTF-8'); + $info['columnMaxWidth'][$colIndex] = Str::utf8Len($value, 'UTF-8'); } $colIndex++; @@ -163,7 +167,7 @@ public static function show(array $data, string $title = 'Data Table', array $op if ($title) { $tStyle = $opts['titleStyle'] ?: 'bold'; $title = ucwords(trim($title)); - $titleLength = mb_strlen($title, 'UTF-8'); + $titleLength = Str::utf8Len($title, 'UTF-8'); $indentSpace = Str::pad(' ', ceil($tableWidth / 2) - ceil($titleLength / 2) + ($columnCount * 2), ' '); $buf->write(" {$indentSpace}<$tStyle>{$title}\n"); } @@ -183,9 +187,12 @@ public static function show(array $data, string $title = 'Data Table', array $op foreach ($head as $index => $name) { $colMaxWidth = $info['columnMaxWidth'][$index]; - // format - $name = Str::pad($name, $colMaxWidth, ' '); - $name = ColorTag::wrap($name, $opts['headStyle']); + // format head title + // $name = Str::pad($name, $colMaxWidth, ' '); + // use Str::padByWidth support zh-CN words + $name = Str::padByWidth($name, $colMaxWidth, ' '); + $name = ColorTag::wrap($name, $opts['headStyle']); + // join string $headStr .= " {$name} {$colBorderChar}"; } @@ -194,10 +201,10 @@ public static function show(array $data, string $title = 'Data Table', array $op // head border: split head and body if ($headBorderChar = $opts['headBorderChar']) { $headBorder = $leftIndent . Str::pad( - $headBorderChar, - $tableWidth + ($columnCount * 3) + 2, - $headBorderChar - ); + $headBorderChar, + $tableWidth + ($columnCount * 3) + 2, + $headBorderChar + ); $buf->write($headBorder . "\n"); } } @@ -212,7 +219,13 @@ public static function show(array $data, string $title = 'Data Table', array $op foreach ((array)$row as $value) { $colMaxWidth = $info['columnMaxWidth'][$colIndex]; // format - $value = Str::pad($value, $colMaxWidth, ' '); + if (is_bool($value)) { + $value = $value ? 'TRUE' : 'FALSE'; + } + + // $value = Str::pad($value, $colMaxWidth, ' '); + // use Str::padByWidth support zh-CN words + $value = Str::padByWidth($value, $colMaxWidth, ' '); $value = ColorTag::wrap($value, $opts['bodyStyle']); $rowStr .= " {$value} {$colBorderChar}"; $colIndex++; From c334161304f7734ebc6a87c6634b3166f49419e9 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 19 Apr 2021 21:59:09 +0800 Subject: [PATCH 078/258] up: use Str::ucwords inteadof the ucwords() --- src/Component/Formatter/Panel.php | 6 +++--- src/Component/Formatter/Section.php | 3 +-- src/Component/Formatter/SingleList.php | 4 ++-- src/Component/Formatter/Table.php | 3 +-- src/Component/Formatter/Title.php | 2 +- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Component/Formatter/Panel.php b/src/Component/Formatter/Panel.php index 01eeee3c..c8225a63 100644 --- a/src/Component/Formatter/Panel.php +++ b/src/Component/Formatter/Panel.php @@ -23,7 +23,6 @@ use function rtrim; use function strip_tags; use function trim; -use function ucwords; use const PHP_EOL; /** @@ -159,7 +158,7 @@ public static function show($data, string $title = 'Information Panel', array $o // output title if ($title) { - $title = ucwords($title); + $title = Str::ucwords($title); $titleLength = mb_strlen($title, 'UTF-8'); $panelWidth = $panelWidth > $titleLength ? $panelWidth : $titleLength; @@ -257,7 +256,8 @@ public function format(): string // output title if ($title) { - $title = ucwords($title); + $title = Str::ucwords($title); + $titleLength = mb_strlen($title, 'UTF-8'); $panelWidth = $panelWidth > $titleLength ? $panelWidth : $titleLength; $indentSpace = Str::pad(' ', ceil($panelWidth / 2) - ceil($titleLength / 2) + 2 * 2, ' '); diff --git a/src/Component/Formatter/Section.php b/src/Component/Formatter/Section.php index 69d91eeb..8c166879 100644 --- a/src/Component/Formatter/Section.php +++ b/src/Component/Formatter/Section.php @@ -11,7 +11,6 @@ use function implode; use function is_array; use function trim; -use function ucwords; use const PHP_EOL; /** @@ -43,7 +42,7 @@ public static function show(string $title, $body, array $opts = []): void $indent = (int)$opts['indent'] >= 0 ? $opts['indent'] : 2; $indentStr = Str::pad(self::CHAR_SPACE, $indent, self::CHAR_SPACE); - $title = ucwords(trim($title)); + $title = Str::ucwords(trim($title)); $tLength = Str::len($title); $width = $width > 10 ? $width : 80; diff --git a/src/Component/Formatter/SingleList.php b/src/Component/Formatter/SingleList.php index 161555d5..90c40726 100644 --- a/src/Component/Formatter/SingleList.php +++ b/src/Component/Formatter/SingleList.php @@ -6,9 +6,9 @@ use Inhere\Console\Console; use Inhere\Console\Util\FormatUtil; use Toolkit\Cli\ColorTag; +use Toolkit\Stdlib\Str; use function array_merge; use function trim; -use function ucwords; use const PHP_EOL; /** @@ -50,7 +50,7 @@ public static function show($data, string $title = 'Information', array $opts = // title if ($title) { - $title = ucwords(trim($title)); + $title = Str::ucwords(trim($title)); $string .= ColorTag::wrap($title, $opts['titleStyle']) . PHP_EOL; } diff --git a/src/Component/Formatter/Table.php b/src/Component/Formatter/Table.php index 2be79f66..063d73ef 100644 --- a/src/Component/Formatter/Table.php +++ b/src/Component/Formatter/Table.php @@ -20,7 +20,6 @@ use function count; use function is_bool; use function is_string; -use function ucwords; /** * Class Table - Tabular data display @@ -166,7 +165,7 @@ public static function show(array $data, string $title = 'Data Table', array $op // output title if ($title) { $tStyle = $opts['titleStyle'] ?: 'bold'; - $title = ucwords(trim($title)); + $title = Str::ucwords(trim($title)); $titleLength = Str::utf8Len($title, 'UTF-8'); $indentSpace = Str::pad(' ', ceil($tableWidth / 2) - ceil($titleLength / 2) + ($columnCount * 2), ' '); $buf->write(" {$indentSpace}<$tStyle>{$title}\n"); diff --git a/src/Component/Formatter/Title.php b/src/Component/Formatter/Title.php index 0363ef76..552850a4 100644 --- a/src/Component/Formatter/Title.php +++ b/src/Component/Formatter/Title.php @@ -43,7 +43,7 @@ public static function show(string $title, array $opts = []): void $indentStr = Str::pad(self::CHAR_SPACE, $indent, self::CHAR_SPACE); } - $title = $opts['ucWords'] ? ucwords(trim($title)) : trim($title); + $title = $opts['ucWords'] ? Str::ucwords(trim($title)) : trim($title); $tLength = Str::len($title); $width = $width > 10 ? $width : 80; From 9968747a04e5532b3e3b828b780bb7d23dfcdefd Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 22 Apr 2021 21:56:52 +0800 Subject: [PATCH 079/258] update some built in php serve --- src/BuiltIn/DevServerCommand.php | 57 +++---- src/Util/PhpDevServe.php | 277 ++++++++++++++++++++++--------- 2 files changed, 219 insertions(+), 115 deletions(-) diff --git a/src/BuiltIn/DevServerCommand.php b/src/BuiltIn/DevServerCommand.php index 85137b2f..53ab767b 100644 --- a/src/BuiltIn/DevServerCommand.php +++ b/src/BuiltIn/DevServerCommand.php @@ -8,12 +8,12 @@ namespace Inhere\Console\BuiltIn; +use Exception; use Inhere\Console\Command; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; -use Toolkit\Sys\Sys; +use Inhere\Console\Util\PhpDevServe; use function strpos; -use const PHP_VERSION; /** * Class DevServerCommand @@ -39,56 +39,49 @@ public static function aliases(): array * {command} [-H HOST] [-p PORT] * {command} [-S HOST:PORT] [file=]web/index.php * @options - * -S STRING The http server address. e.g 127.0.0.1:8552 - * -t STRING The document root dir for server(web) + * -s, -S, --addr STRING The http server address. e.g 127.0.0.1:8552 + * -t, --doc-root STRING The document root dir for server(public) * -H,--host STRING The server host address(127.0.0.1) * -p,--port INTEGER The server port number(8552) * -b,--php-bin STRING The php binary file(php) * @arguments * file=STRING The entry file for server. e.g web/index.php * - * @param Input $in - * @param Output $out + * @param Input $input + * @param Output $output * * @return int|mixed|void + * @throws Exception * @example * {command} -S 127.0.0.1:8552 web/index.php */ - public function execute($in, $out) + public function execute($input, $output) { - if (!$server = $this->getOpt('S')) { - $server = $this->getSameOpt(['H', 'host'], '127.0.0.1'); + $serveAddr = $input->getSameStringOpt('s,S,addr'); + if (!$serveAddr) { + $serveAddr = $this->getSameOpt(['H', 'host'], '127.0.0.1'); } - if (!strpos($server, ':')) { - $port = $this->getSameOpt(['p', 'port'], 8552); - $server .= ':' . $port; + $port = $input->getSameStringOpt(['p', 'port']); + if ($port && strpos($serveAddr, ':') === false) { + $serveAddr .= ':' . $port; } - $version = PHP_VERSION; - $workDir = $this->input->getPwd(); - $docDir = $this->getOpt('t'); - $docRoot = $docDir ? $workDir . '/' . $docDir : $workDir; + $hceFile = $input->getStringOpt('hce-file'); + $hceEnv = $input->getStringOpt('hce-env'); + $docRoot = $input->getSameStringOpt('t,doc-root'); - $this->write([ - "PHP $version Development Server started\nServer listening on http://$server", - "Document root is $docRoot", - 'You can use CTRL + C to stop run.', - ]); + $input->bindArgument('file', 0); + $entryFile = $input->getStringArg('file'); - // $command = "php -S {$server} -t web web/index.php"; - $command = "php -S {$server}"; + $pds = PhpDevServe::new($serveAddr, $docRoot); + $pds->setEntryFile($entryFile); - if ($docDir) { - $command .= " -t $docDir"; + if ($hceEnv && $hceFile) { + $pds->loadHceFile($hceFile); + $pds->useHceEnv($hceEnv); } - if ($entryFile = $this->getSameArg(['file', 0])) { - $command .= " $entryFile"; - } - - $this->write("> $command"); - - Sys::execute($command); + $pds->listen(); } } diff --git a/src/Util/PhpDevServe.php b/src/Util/PhpDevServe.php index daa72e97..566097c1 100644 --- a/src/Util/PhpDevServe.php +++ b/src/Util/PhpDevServe.php @@ -2,11 +2,17 @@ namespace Inhere\Console\Util; -use Toolkit\Cli\Color; +use Closure; +use Exception; +use RuntimeException; +use Toolkit\Cli\Cli; +use Toolkit\Stdlib\Json; use Toolkit\Sys\Sys; -use function implode; -use function sprintf; -use const PHP_EOL; +use function explode; +use function file_exists; +use function file_get_contents; +use function random_int; +use function strpos; /** * Class PhpDevServe @@ -15,36 +21,86 @@ */ class PhpDevServe { + public const PHP_BIN = 'php'; + public const IDX_FILE = 'index.php'; + public const SVR_ADDR = '127.0.0.1:8080'; + + /** + * The php binary file + * + * @var string + */ + public $phpBin = self::PHP_BIN; + /** + * The document root dir for server + * * @var string */ - private $phpBin; + public $docRoot = './'; /** + * The entry file for server. e.g web/index.php + * * @var string */ - private $serveAddr; + protected $entryFile = ''; /** + * The http server address. e.g 127.0.0.1:8552 + * * @var string */ - private $entryFile; + public $serveAddr = ''; + + /** + * Can custom message for print before start server. + * It must return an message string. + * + * @var callable + */ + protected $beforeStart; + + /** + * The IDEA http-client env json file data. + * + * @var array + */ + private $hceData = []; /** * @var string */ - private $documentRoot; + private $hceEnv = ''; /** * @param string $serveAddr - * @param string $documentRoot + * @param string $docRoot * @param string $entryFile * - * @return static + * @return self + */ + public static function new(string $serveAddr, string $docRoot = '', string $entryFile = ''): self + { + return new self($serveAddr, $docRoot, $entryFile); + } + + /** + * @return string */ - public static function new(string $serveAddr, string $documentRoot, string $entryFile = ''): self + public static function findPhpBin(): string { - return new self($serveAddr, $documentRoot, $entryFile); + // `which php` output: "/usr/local/bin/php" + // `type php` output: "php is /usr/local/bin/php" + [$ok, $ret,] = Sys::run('type php'); + + $phpBin = ''; + if ($ok === 0) { + $nodes = explode('/', $ret, 2); + $phpBin = $nodes[1] ?? ''; + } + + return $phpBin; } /** @@ -56,150 +112,205 @@ public static function new(string $serveAddr, string $documentRoot, string $entr */ public function __construct(string $serveAddr, string $documentRoot, string $entryFile = '') { - $this->documentRoot = $documentRoot; - + $this->docRoot = $documentRoot; $this->entryFile = $entryFile; - $this->serveAddr = $serveAddr ?: '127.0.0.1:8080'; + $this->serveAddr = $serveAddr ?: self::SVR_ADDR; } /** - * @param bool $withColorTag + * @param string $hceFile */ - public function showTipsMessage(bool $withColorTag = true): void + public function loadHceFile(string $hceFile): void { - $msg = $this->getTipsMessage($withColorTag); - - if ($withColorTag) { - echo Color::parseTag($msg), PHP_EOL; - } else { - echo $msg, PHP_EOL; + if (!file_exists($hceFile)) { + throw new RuntimeException('the IDEA http-client env json file not exists. file: ' . $hceFile); } + + $jsonString = file_get_contents($hceFile); + + // load data + $this->hceData = Json::decode($jsonString, true); } /** - * @param bool $withColorTag + * @param string $envName * - * @return string + * @return $this */ - public function getTipsMessage(bool $withColorTag = true): string + public function useHceEnv(string $envName): self { - $version = PHP_VERSION; + if (!isset($this->hceData[$envName])) { + throw new RuntimeException('the env name is not exist in hceData'); + } - $addr = $this->serveAddr; - $root = $this->documentRoot; - $stop = 'CTRL + C'; + $info = $this->hceData[$envName]; - if ($withColorTag) { - $addr = "{$this->serveAddr}"; - $root = "{$this->documentRoot}"; - $stop = "CTRL + C"; + $this->hceEnv = $envName; + if ($host = $info['host']) { + $this->serveAddr = $host; } - $nodes = [ - sprintf("PHP $version Development Server started\nServer listening on http://%s", $addr), - sprintf("Document root is %s\nYou can use %s to stop run.", $root, $stop), - ]; + // TODO load more from $info + // phpBin + + return $this; + } - return implode("\n", $nodes); + /** + * @param Closure $fn + * + * @return $this + */ + public function config(Closure $fn): self + { + $fn($this); + return $this; } - public function start(): void + /** + * start and listen serve + * + * @throws Exception + */ + public function listen(): void { - // php -S {$serveAddr} -t public public/index.php - $phpBin = $this->phpBin ?: 'php'; - $command = "$phpBin -S {$this->serveAddr}"; + $phpBin = $this->getPhpBin(); + $svrAddr = $this->getServerAddr(); + // command eg: "php -S 127.0.0.1:8080 -t web web/index.php"; + $command = "$phpBin -S {$svrAddr}"; - if ($this->documentRoot) { - $command .= " -t {$this->documentRoot}"; + if ($this->docRoot) { + $command .= " -t {$this->docRoot}"; } - if ($entryFile = $this->entryFile) { + if ($entryFile = $this->getEntryFile()) { $command .= " $entryFile"; } - echo Color::parseTag("> $command"), PHP_EOL; + if ($fn = $this->beforeStart) { + $fn($this); + } else { + $this->printDefaultMessage(); + } + + Cli::write("> $command"); Sys::execute($command); } /** - * @return string + * @return array + * @throws Exception */ - public function findPhpBinFile(): string + public function getInfo(): array { - $phpBin = 'php'; - - // TODO use `type php` check and find. return: 'php is /usr/local/bin/php' - [$ok, $ret,] = Sys::run('which php'); - if ($ok === 0) { - $phpBin = trim($ret); - } - - return $phpBin; + return [ + 'phpBinFile' => $this->getPhpBin(), + 'serverAddr' => $this->getServerAddr(), + 'documentRoot' => $this->docRoot, + 'entryFile' => $this->getEntryFile(), + ]; } /** - * @return string + * @throws Exception */ - public function getPhpBin(): string + protected function printDefaultMessage(): void { - return $this->phpBin; + $version = PHP_VERSION; + $workDir = (string)getcwd(); + $svrAddr = $this->getServerAddr(); + $docRoot = $this->docRoot ? $workDir . '/' . $this->docRoot : $workDir; + + Cli::writeln([ + "PHP $version Development Server started\nServer listening on http://$svrAddr", + "Document root is $docRoot", + 'You can use CTRL + C to stop run.', + ]); } /** - * @param string $phpBin + * @param callable $beforeStart * * @return PhpDevServe */ - public function setPhpBin(string $phpBin): self + public function setBeforeStart(callable $beforeStart): self { - $this->phpBin = $phpBin; + $this->beforeStart = $beforeStart; return $this; } /** - * @return string + * @return array */ - public function getServeAddr(): string + public function getCurrentHceInfo(): array { - return $this->serveAddr; + if ($this->hceEnv) { + return $this->hceData[$this->hceEnv] ?? []; + } + + return []; } /** - * @param string $serveAddr - * - * @return PhpDevServe + * @return string */ - public function setServeAddr(string $serveAddr): self + public function getPhpBin(): string { - $this->serveAddr = $serveAddr; - return $this; + if (!$this->phpBin) { + $this->phpBin = self::PHP_BIN; + } + + return $this->phpBin; } /** * @return string */ - public function getDocumentRoot(): string + public function getEntryFile(): string { - return $this->documentRoot; + if (!$this->entryFile) { + $this->entryFile = self::IDX_FILE; + } + + return $this->entryFile; } /** - * @param string $documentRoot - * - * @return PhpDevServe + * @return int + * @throws Exception */ - public function setDocumentRoot(string $documentRoot): self + public function getRandomPort(): int { - $this->documentRoot = $documentRoot; - return $this; + return random_int(10001, 59999); } /** * @return string + * @throws Exception */ - public function getEntryFile(): string + public function getServerAddr(): string { - return $this->entryFile; + if (!$this->serveAddr) { + $this->serveAddr = self::SVR_ADDR; + } else { + $svrAddr = $this->serveAddr; + $charPos = strpos($svrAddr, ':'); + if ($charPos === false) { + $this->serveAddr .= ':' . $this->getRandomPort(); + // } elseif ($charPos === 0) { + // $this->svrAddr = '' . $svrAddr; + } + } + + return $this->serveAddr; + } + + /** + * @return array + */ + public function getHceData(): array + { + return $this->hceData; } /** From 3054689c8ea4c5c9452ace7df7ae32b1b73da040 Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 22 Apr 2021 21:58:35 +0800 Subject: [PATCH 080/258] update some --- src/BuiltIn/DevServerCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BuiltIn/DevServerCommand.php b/src/BuiltIn/DevServerCommand.php index 53ab767b..109ef4f6 100644 --- a/src/BuiltIn/DevServerCommand.php +++ b/src/BuiltIn/DevServerCommand.php @@ -59,7 +59,7 @@ public function execute($input, $output) { $serveAddr = $input->getSameStringOpt('s,S,addr'); if (!$serveAddr) { - $serveAddr = $this->getSameOpt(['H', 'host'], '127.0.0.1'); + $serveAddr = $input->getSameStringOpt(['H', 'host']); } $port = $input->getSameStringOpt(['p', 'port']); From f9703fbb6b4977a3240bce895de4330711b6ce04 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 23 Apr 2021 00:46:09 +0800 Subject: [PATCH 081/258] update some --- src/BuiltIn/DevServerCommand.php | 7 +++--- src/Util/PhpDevServe.php | 40 +++++++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/BuiltIn/DevServerCommand.php b/src/BuiltIn/DevServerCommand.php index 109ef4f6..cad6617a 100644 --- a/src/BuiltIn/DevServerCommand.php +++ b/src/BuiltIn/DevServerCommand.php @@ -67,15 +67,16 @@ public function execute($input, $output) $serveAddr .= ':' . $port; } + $docRoot = $input->getSameStringOpt('t,doc-root'); $hceFile = $input->getStringOpt('hce-file'); $hceEnv = $input->getStringOpt('hce-env'); - $docRoot = $input->getSameStringOpt('t,doc-root'); + $phpBin = $input->getStringOpt('php-bin'); $input->bindArgument('file', 0); $entryFile = $input->getStringArg('file'); - $pds = PhpDevServe::new($serveAddr, $docRoot); - $pds->setEntryFile($entryFile); + $pds = PhpDevServe::new($serveAddr, $docRoot, $entryFile); + $pds->setPhpBin($phpBin); if ($hceEnv && $hceFile) { $pds->loadHceFile($hceFile); diff --git a/src/Util/PhpDevServe.php b/src/Util/PhpDevServe.php index 566097c1..a47ef913 100644 --- a/src/Util/PhpDevServe.php +++ b/src/Util/PhpDevServe.php @@ -30,14 +30,14 @@ class PhpDevServe * * @var string */ - public $phpBin = self::PHP_BIN; + protected $phpBin = self::PHP_BIN; /** * The document root dir for server * * @var string */ - public $docRoot = './'; + protected $docRoot = './'; /** * The entry file for server. e.g web/index.php @@ -107,14 +107,14 @@ public static function findPhpBin(): string * Class constructor. * * @param string $serveAddr - * @param string $documentRoot + * @param string $docRoot * @param string $entryFile */ - public function __construct(string $serveAddr, string $documentRoot, string $entryFile = '') + public function __construct(string $serveAddr, string $docRoot, string $entryFile = '') { - $this->docRoot = $documentRoot; - $this->entryFile = $entryFile; $this->serveAddr = $serveAddr ?: self::SVR_ADDR; + $this->setDocRoot($docRoot); + $this->setEntryFile($entryFile); } /** @@ -323,4 +323,32 @@ public function setEntryFile(string $entryFile): self $this->entryFile = $entryFile; return $this; } + + /** + * @param string $phpBin + * + * @return PhpDevServe + */ + public function setPhpBin(string $phpBin): self + { + if ($phpBin) { + $this->phpBin = $phpBin; + } + + return $this; + } + + /** + * @param string $docRoot + * + * @return PhpDevServe + */ + public function setDocRoot(string $docRoot): self + { + if ($docRoot) { + $this->docRoot = $docRoot; + } + + return $this; + } } From be33564c1fe0bfaa3604461ef96764d3b1c1da7d Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 23 Apr 2021 01:06:21 +0800 Subject: [PATCH 082/258] add new option for show list --- src/Component/Formatter/SingleList.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Component/Formatter/SingleList.php b/src/Component/Formatter/SingleList.php index 90c40726..8cd7f57c 100644 --- a/src/Component/Formatter/SingleList.php +++ b/src/Component/Formatter/SingleList.php @@ -39,18 +39,19 @@ public static function show($data, string $title = 'Information', array $opts = { $string = ''; $opts = array_merge([ - 'leftChar' => ' ', + 'leftChar' => ' ', // 'sepChar' => ' ', - 'keyStyle' => 'info', - 'keyMinWidth' => 8, - 'titleStyle' => 'comment', - 'returned' => false, - 'lastNewline' => true, + 'keyStyle' => 'info', + 'keyMinWidth' => 8, + 'titleStyle' => 'comment', + 'returned' => false, + 'ucTitleWords' => true, + 'lastNewline' => true, ], $opts); // title if ($title) { - $title = Str::ucwords(trim($title)); + $title = $opts['ucTitleWords'] ? Str::ucwords(trim($title)) : $title; $string .= ColorTag::wrap($title, $opts['titleStyle']) . PHP_EOL; } From a77e97a6c896695f2391ffb0fce05ca1b9096a72 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 23 Apr 2021 10:49:56 +0800 Subject: [PATCH 083/258] up: support check env before start php serve listen --- src/Util/PhpDevServe.php | 45 +++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/src/Util/PhpDevServe.php b/src/Util/PhpDevServe.php index a47ef913..7aa34b0a 100644 --- a/src/Util/PhpDevServe.php +++ b/src/Util/PhpDevServe.php @@ -11,6 +11,8 @@ use function explode; use function file_exists; use function file_get_contents; +use function is_dir; +use function is_file; use function random_int; use function strpos; @@ -173,28 +175,51 @@ public function config(Closure $fn): self * @throws Exception */ public function listen(): void + { + if ($fn = $this->beforeStart) { + $fn($this); + } else { + $this->printDefaultMessage(); + } + + $command = $this->getCommand(); + + Cli::write("> $command"); + Sys::execute($command); + } + + /** + * build full command line string + * + * @param bool $checkEnv + * + * @return string + * @throws Exception + */ + public function getCommand(bool $checkEnv = true): string { $phpBin = $this->getPhpBin(); $svrAddr = $this->getServerAddr(); // command eg: "php -S 127.0.0.1:8080 -t web web/index.php"; $command = "$phpBin -S {$svrAddr}"; - if ($this->docRoot) { - $command .= " -t {$this->docRoot}"; + if ($docRoot = $this->docRoot) { + if ($checkEnv && !is_dir($docRoot)) { + throw new RuntimeException("the document root is not exists. path: $docRoot"); + } + + $command .= " -t {$docRoot}"; } if ($entryFile = $this->getEntryFile()) { - $command .= " $entryFile"; - } + if ($checkEnv && !is_file($entryFile)) { + throw new RuntimeException("the entry file is not exists. path: $entryFile"); + } - if ($fn = $this->beforeStart) { - $fn($this); - } else { - $this->printDefaultMessage(); + $command .= " $entryFile"; } - Cli::write("> $command"); - Sys::execute($command); + return $command; } /** From abf16992f4419bbce209560fa0c1e9b954d7d2c9 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 23 Apr 2021 10:59:01 +0800 Subject: [PATCH 084/258] updat esome --- src/Util/PhpDevServe.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Util/PhpDevServe.php b/src/Util/PhpDevServe.php index 7aa34b0a..77bcba50 100644 --- a/src/Util/PhpDevServe.php +++ b/src/Util/PhpDevServe.php @@ -121,17 +121,25 @@ public function __construct(string $serveAddr, string $docRoot, string $entryFil /** * @param string $hceFile + * @param bool $mustLoad + * + * @return bool */ - public function loadHceFile(string $hceFile): void + public function loadHceFile(string $hceFile, bool $mustLoad = false): bool { if (!file_exists($hceFile)) { - throw new RuntimeException('the IDEA http-client env json file not exists. file: ' . $hceFile); + if ($mustLoad) { + throw new RuntimeException('the http-client env json file not exists. file: ' . $hceFile); + } + + return false; } $jsonString = file_get_contents($hceFile); // load data $this->hceData = Json::decode($jsonString, true); + return true; } /** @@ -233,6 +241,7 @@ public function getInfo(): array 'serverAddr' => $this->getServerAddr(), 'documentRoot' => $this->docRoot, 'entryFile' => $this->getEntryFile(), + 'commandLine' => $this->getCommand(false), ]; } From c24919a9b8f83f372c1dda757b0eb0379ef5cc9f Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 23 Apr 2021 22:00:07 +0800 Subject: [PATCH 085/258] up: support controler object cache on repeat fetach --- src/AbstractApplication.php | 5 +++ src/Application.php | 70 ++++++++++++++++++++++++++++--------- src/Controller.php | 2 +- src/Router.php | 10 ++++++ 4 files changed, 70 insertions(+), 17 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 8170c1fa..e37c1016 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -121,6 +121,11 @@ abstract class AbstractApplication implements ApplicationInterface */ protected $errorHandler; + /** + * @var Controller[] + */ + protected $groupObjects = []; + /** * Class constructor. * diff --git a/src/Application.php b/src/Application.php index f12e4a7b..b36d9251 100644 --- a/src/Application.php +++ b/src/Application.php @@ -15,6 +15,7 @@ use Inhere\Console\Util\Helper; use InvalidArgumentException; use ReflectionException; +use RuntimeException; use SplFileInfo; use function class_exists; use function implode; @@ -266,7 +267,7 @@ public function dispatch(string $name, bool $detachedRun = false) } $cmdId = $name; - $this->debugf( 'begin dispatch the input command: %s', $name); + $this->debugf('begin dispatch the input command: %s', $name); // format is: `group action` if (strpos($name, ' ') > 0) { @@ -309,7 +310,7 @@ public function dispatch(string $name, bool $detachedRun = false) } // is controller/group - return $this->runAction($info['group'], $info['action'], $info['handler'], $cmdOptions, $detachedRun); + return $this->runAction($info, $cmdOptions, $detachedRun); } /** @@ -355,21 +356,64 @@ protected function runCommand(string $name, $handler, array $options) /** * Execute an action in a group command(controller) * - * @param string $group The group name - * @param string $action Command method, no suffix - * @param mixed $handler The controller class or object + * @param array $info Matched route info * @param array $options * @param bool $detachedRun * * @return mixed * @throws ReflectionException */ - protected function runAction(string $group, string $action, $handler, array $options, bool $detachedRun = false) + protected function runAction(array $info, array $options, bool $detachedRun = false) { + $controller = $this->createController($info); + + if ($desc = $options['description'] ?? '') { + $controller::setDescription($desc); + } + + if ($detachedRun) { + $controller->setDetached(); + } + + // Command method, no suffix + return $controller->run($info['action']); + } + + /** + * @param string $name + * + * @return Controller + */ + public function getController(string $name): Controller + { + $info = $this->router->getControllerInfo($name); + if (!$info) { + throw new RuntimeException('the group controller not exist. name: ' . $name); + } + + $info['group'] = $name; + return $this->createController($info); + } + + /** + * @param array $info + * + * @return Controller + */ + protected function createController(array $info): Controller + { + $group = $info['group']; // The group name + if (isset($this->groupObjects[$group])) { + $this->debugf('load the "%s" controller object form cache', $group); + return $this->groupObjects[$group]; + } + + $this->debugf('create the "%s" controller object and cache it', $group); + /** @var Controller $handler */ + $handler = $info['handler']; // The controller class or object if (is_string($handler)) { $class = $handler; - if (!class_exists($class)) { Helper::throwInvalidArgument('The console controller class [%s] not exists!', $class); } @@ -387,17 +431,11 @@ protected function runAction(string $group, string $action, $handler, array $opt // force set name and description $handler::setName($group); - if ($desc = $options['description'] ?? '') { - $handler::setDescription($desc); - } - $handler->setApp($this); $handler->setDelimiter($this->delimiter); - if ($detachedRun) { - $handler->setDetached(); - } - - return $handler->run($action); + // cache object + $this->groupObjects[$group] = $handler; + return $handler; } } diff --git a/src/Controller.php b/src/Controller.php index ad3fbc68..6297d6e7 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -563,7 +563,7 @@ protected function getAllCommandMethods(ReflectionClass $ref = null, bool $onlyN * * @return string */ - protected function getRealCommandName(string $name): string + public function getRealCommandName(string $name): string { if (!$name) { return ''; diff --git a/src/Router.php b/src/Router.php index 4a10fb2c..aeb598fa 100644 --- a/src/Router.php +++ b/src/Router.php @@ -375,6 +375,16 @@ public function getControllers(): array return $this->controllers; } + /** + * @param $name + * + * @return array + */ + public function getControllerInfo(string $name): array + { + return $this->controllers[$name] ?? []; + } + /** * @param $name * From f1c2925962c28694bfd7fd41c48e4a188cb0f6ad Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 23 Apr 2021 22:11:05 +0800 Subject: [PATCH 086/258] up: add before run action methods --- src/Controller.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/Controller.php b/src/Controller.php index 6297d6e7..4d8cd0ae 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -265,6 +265,23 @@ protected function createDefinition(): InputDefinition return $this->definition; } + /** + * Before controller method execute + * + * @return boolean It MUST return TRUE to continue execute. if return False, will stop run. + */ + protected function beforeAction(): bool + { + return true; + } + + /** + * After controller method execute + */ + protected function afterAction(): void + { + } + /** * Run command action in the group * @@ -291,6 +308,11 @@ final public function execute($input, $output) // if (method_exists($this, $method) && (($rfm = new ReflectionMethod($this, $method)) && $rfm->isPublic())) { if (method_exists($this, $method)) { // before run action + if (!$this->beforeAction()) { + $this->debugf('beforeAction() returns FALSE, interrupt processing continues'); + return 0; + } + if (method_exists($this, $beforeFunc = 'before' . ucfirst($action))) { $beforeOk = $this->$beforeFunc($input, $output); if ($beforeOk === false) { @@ -307,6 +329,7 @@ final public function execute($input, $output) $this->$after($input, $output); } + $this->afterAction(); return $result; } From c9cb02df730d1e857c9b0869d2f1b28cc3a2b734 Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 24 Apr 2021 00:15:30 +0800 Subject: [PATCH 087/258] update some for controler logic --- CHANGELOG.md | 2 ++ src/Controller.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32cd189b..b47d58c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ > begin at: 2020.08.21 +## v3.0.1 + ## v3.0.x > publish at: 2019.01.03 diff --git a/src/Controller.php b/src/Controller.php index 4d8cd0ae..d5617c20 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -339,7 +339,7 @@ final public function execute($input, $output) return 0; } - $this->debugf('action "%s" not found on the group controller', $action); + $this->debugf('action "%s" not found on the group controller "%s"', $action, $group); // if you defined the method '$this->notFoundCallback' , will call it // if (($notFoundCallback = $this->notFoundCallback) && method_exists($this, $notFoundCallback)) { From 730902058cba221cfa39b86d275d441f9ad3e433 Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 24 Apr 2021 00:29:10 +0800 Subject: [PATCH 088/258] update change log --- CHANGELOG.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b47d58c7..942cd251 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,21 @@ ## v4.0.x -> begin at: 2020.08.21 +> begin at: 2020.08.21, branch `master` +## v3.1.20 -## v3.0.1 +**update** + +- add before/after methods on run controller method. +- support controller object cache on repeat fetch +- enhance built in php serve util logic + +## v3.1.19 + +> publish at 2021.04.20 + +- up: use `Str::padByWidth` support zh-CN words on render table/list/panel ## v3.0.x From 1a17784d6eabba258a67af767a27e61b15e5618f Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 27 Apr 2021 00:12:53 +0800 Subject: [PATCH 089/258] update some for error tips and sigle list --- src/Application.php | 2 -- src/Component/Formatter/SingleList.php | 1 + src/Controller.php | 5 ----- src/Exception/PromptException.php | 10 ++++++++++ 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Application.php b/src/Application.php index b36d9251..a0f76029 100644 --- a/src/Application.php +++ b/src/Application.php @@ -258,7 +258,6 @@ protected function getFileFilter(): callable /** * @inheritdoc - * @throws ReflectionException */ public function dispatch(string $name, bool $detachedRun = false) { @@ -361,7 +360,6 @@ protected function runCommand(string $name, $handler, array $options) * @param bool $detachedRun * * @return mixed - * @throws ReflectionException */ protected function runAction(array $info, array $options, bool $detachedRun = false) { diff --git a/src/Component/Formatter/SingleList.php b/src/Component/Formatter/SingleList.php index 8cd7f57c..0d24f998 100644 --- a/src/Component/Formatter/SingleList.php +++ b/src/Component/Formatter/SingleList.php @@ -44,6 +44,7 @@ public static function show($data, string $title = 'Information', array $opts = 'keyStyle' => 'info', 'keyMinWidth' => 8, 'titleStyle' => 'comment', + 'ucFirst' => false, 'returned' => false, 'ucTitleWords' => true, 'lastNewline' => true, diff --git a/src/Controller.php b/src/Controller.php index d5617c20..3ba4e74d 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -196,7 +196,6 @@ protected function onNotFound(string $action): bool * @param string $command command in the group * * @return int|mixed - * @throws ReflectionException */ public function run(string $command = '') { @@ -361,7 +360,6 @@ final public function execute($input, $output) /** * @return bool - * @throws ReflectionException */ protected function showHelp(): bool { @@ -404,7 +402,6 @@ public function getGroupOptions(): array * -s, --search Search command by input keywords * --format Set the help information dump format(raw, xml, json, markdown) * @return int - * @throws ReflectionException * @example * {script} {name} -h * {script} {name}:help @@ -453,8 +450,6 @@ protected function beforeShowCommandList(): void /** * Display all sub-commands list of the controller class - * - * @throws ReflectionException */ final public function showCommandList(): void { diff --git a/src/Exception/PromptException.php b/src/Exception/PromptException.php index 956ab917..9708663f 100644 --- a/src/Exception/PromptException.php +++ b/src/Exception/PromptException.php @@ -17,4 +17,14 @@ */ class PromptException extends InvalidArgumentException { + /** + * @param string $msg + * @param int $code + * + * @return static + */ + public static function new(string $msg, int $code = 0): self + { + return new self($msg, $code); + } } From 34cdac96fabb97dcde5ec84912eb30c858852761 Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 27 Apr 2021 00:14:50 +0800 Subject: [PATCH 090/258] update change log --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 942cd251..e79a8e7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,13 @@ > begin at: 2020.08.21, branch `master` +## v3.1.21 + ## v3.1.20 **update** +- update some for error tips and sigle list - add before/after methods on run controller method. - support controller object cache on repeat fetch - enhance built in php serve util logic From 26870f2de20585578ba55f3a6affdc23aa9a735a Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 27 Apr 2021 10:00:12 +0800 Subject: [PATCH 091/258] add tests on php 8 --- .github/workflows/php.yml | 2 +- CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index dd1b697c..a7565782 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: true matrix: - php: [7.3, 7.4] # + php: [7.3, 7.4, 8.0] # os: [ubuntu-latest, macOS-latest] # windows-latest, steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index e79a8e7a..df2165ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ **update** -- update some for error tips and sigle list +- update some for error tips and single list - add before/after methods on run controller method. - support controller object cache on repeat fetch - enhance built in php serve util logic From cd8a8fd9db11c3c8daa7bf17a013637f488226b7 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 10 May 2021 00:38:02 +0800 Subject: [PATCH 092/258] up: update default prop value for PhpDevServe --- src/Util/PhpDevServe.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Util/PhpDevServe.php b/src/Util/PhpDevServe.php index 77bcba50..0f820e0b 100644 --- a/src/Util/PhpDevServe.php +++ b/src/Util/PhpDevServe.php @@ -39,7 +39,7 @@ class PhpDevServe * * @var string */ - protected $docRoot = './'; + protected $docRoot = ''; /** * The entry file for server. e.g web/index.php From f278bcf65a7ad4e66c6fb8eaf5198520f93e4a2c Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 10 May 2021 10:20:49 +0800 Subject: [PATCH 093/258] up: action limit the phpunit version on php 7.2 --- .github/workflows/php.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index a7565782..a94630f7 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -17,6 +17,10 @@ jobs: matrix: php: [7.3, 7.4, 8.0] # os: [ubuntu-latest, macOS-latest] # windows-latest, + include: + - os: 'ubuntu-latest' + php: '7.2' + phpunit: '8.5.13' steps: - name: Checkout @@ -37,7 +41,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php}} - tools: pecl, php-cs-fixer, phpunit + tools: pecl, php-cs-fixer, phpunit:${{ matrix.phpunit }} extensions: mbstring, dom, fileinfo, mysql, openssl, igbinary, redis # , swoole-4.4.19 #optional, setup extensions ini-values: post_max_size=56M, short_open_tag=On #optional, setup php.ini configuration coverage: none #optional, setup coverage driver: xdebug, none From 9c923161299bbef672928cf841156b5b2c9b47ef Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 10 May 2021 20:50:36 +0800 Subject: [PATCH 094/258] up: modify the group action not found logic --- src/AbstractHandler.php | 4 +- src/Controller.php | 119 +++++++++++++++++++++++++++------------- 2 files changed, 83 insertions(+), 40 deletions(-) diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 251807da..3f92f7b7 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -257,7 +257,7 @@ public function run(string $command = '') // if enable swoole coroutine if (static::isCoroutine() && Helper::isSupportCoroutine()) { - $result = $this->coroutineRun(); + $result = $this->coExecute(); } else { // when not enable coroutine $result = $this->execute($this->input, $this->output); } @@ -272,7 +272,7 @@ public function run(string $command = '') * * @return bool */ - public function coroutineRun(): bool + public function coExecute(): bool { // $ch = new Coroutine\Channel(1); $ok = Coroutine::create(function () { diff --git a/src/Controller.php b/src/Controller.php index 3ba4e74d..79848d7c 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -193,11 +193,11 @@ protected function onNotFound(string $action): bool } /** - * @param string $command command in the group + * @param string $command * - * @return int|mixed + * @return string */ - public function run(string $command = '') + protected function findCommandName(string $command): string { if (!$command = trim($command, $this->delimiter)) { $command = $this->defaultAction; @@ -214,6 +214,19 @@ public function run(string $command = '') } } + return $command; + } + + /** + * @param string $command command in the group + * + * @return int|mixed + * @throws ReflectionException + */ + public function run(string $command = '') + { + $command = $this->findCommandName($command); + // if not input sub-command, render group help. if (!$command) { $this->debugf('sub-command is empty, display help for the group: %s', self::getName()); @@ -226,13 +239,27 @@ public function run(string $command = '') $command = $this->getRealCommandName($command); // convert 'boo-foo' to 'booFoo' - $this->action = Str::camelCase($command); - $this->debugf('will run the group action: %s, sub-command: %s', $this->action, $command); + $this->action = $action = Str::camelCase($command); + $this->debugf("will run the '%s' group action: %s, sub-command: %s", static::getName(), $this->action, $command); + + $this->beforeRun(); + + // check method not exist + $method = $this->getMethodName($action); + + // if command method not exists. + if (!method_exists($this, $method)) { + return $this->handleNotFound(static::getName(), $action); + } // do running return parent::run($command); } + protected function beforeRun(): void + { + } + /** * Load command configure */ @@ -288,7 +315,6 @@ protected function afterAction(): void * @param Output $output * * @return mixed - * @throws ReflectionException */ final public function execute($input, $output) { @@ -301,40 +327,47 @@ final public function execute($input, $output) return -1; } - $method = $this->actionSuffix ? $action . ucfirst($this->actionSuffix) : $action; + $method = $this->getMethodName($action); // the action method exists and only allow access public method. - // if (method_exists($this, $method) && (($rfm = new ReflectionMethod($this, $method)) && $rfm->isPublic())) { - if (method_exists($this, $method)) { - // before run action - if (!$this->beforeAction()) { - $this->debugf('beforeAction() returns FALSE, interrupt processing continues'); - return 0; - } + // if (method_exists($this, $method)) { + // before run action + if (!$this->beforeAction()) { + $this->debugf('beforeAction() returns FALSE, interrupt processing continues'); + return 0; + } - if (method_exists($this, $beforeFunc = 'before' . ucfirst($action))) { - $beforeOk = $this->$beforeFunc($input, $output); - if ($beforeOk === false) { - $this->debugf('%s() returns FALSE, interrupt processing continues', $beforeFunc); - return 0; - } + if (method_exists($this, $beforeFunc = 'before' . ucfirst($action))) { + $beforeOk = $this->$beforeFunc($input, $output); + if ($beforeOk === false) { + $this->debugf('%s() returns FALSE, interrupt processing continues', $beforeFunc); + return 0; } + } - // run action - $result = $this->$method($input, $output); - - // after run action - if (method_exists($this, $after = 'after' . ucfirst($action))) { - $this->$after($input, $output); - } + // run action + $result = $this->$method($input, $output); - $this->afterAction(); - return $result; + // after run action + if (method_exists($this, $after = 'after' . ucfirst($action))) { + $this->$after($input, $output); } + $this->afterAction(); + return $result; + } + + /** + * @param string $group + * @param string $action + * + * @return int + */ + protected function handleNotFound(string $group, string $action): int + { // if user custom handle not found logic. if ($this->onNotFound($action)) { - $this->debugf('user custom handle the action "%s" not found logic', $action); + $this->debugf('user custom handle the "%s" action "%s" not found', $group, $action); return 0; } @@ -344,13 +377,13 @@ final public function execute($input, $output) // if (($notFoundCallback = $this->notFoundCallback) && method_exists($this, $notFoundCallback)) { // $result = $this->{$notFoundCallback}($action); // } else { - $output->liteError("Sorry, The command '$action' not exist of the group '{$group}'!"); + $this->output->liteError("Sorry, The command '$action' not exist of the group '$group'!"); // find similar command names $similar = Helper::findSimilar($action, $this->getAllCommandMethods(null, true)); if ($similar) { - $output->write(sprintf("\nMaybe what you mean is:\n %s", implode(', ', $similar))); + $this->output->writef("\nMaybe what you mean is:\n %s", implode(', ', $similar)); } else { $this->showCommandList(); } @@ -358,8 +391,19 @@ final public function execute($input, $output) return -1; } + /** + * @param string $action + * + * @return string + */ + protected function getMethodName(string $action): string + { + return $this->actionSuffix ? $action . ucfirst($this->actionSuffix) : $action; + } + /** * @return bool + * @throws ReflectionException */ protected function showHelp(): bool { @@ -402,13 +446,13 @@ public function getGroupOptions(): array * -s, --search Search command by input keywords * --format Set the help information dump format(raw, xml, json, markdown) * @return int + * @throws ReflectionException * @example * {script} {name} -h * {script} {name}:help * {script} {name}:help index * {script} {name}:index -h * {script} {name} index - * */ final public function helpCommand(): int { @@ -519,8 +563,8 @@ final public function showCommandList(): void $name = $sName . $this->delimiter; // $usage = "$script {$name}{command} [--options ...] [arguments ...]"; $usage = [ - "$script {$name}{command} [--options ...] [arguments ...]", - "$script {$sName} {command} [--options ...] [arguments ...]", + "$script $name{command} [--options ...] [arguments ...]", + "$script $sName {command} [--options ...] [arguments ...]", ]; } @@ -709,6 +753,7 @@ public function setActionSuffix(string $actionSuffix): void /** * @return bool + * @deprecated */ public function isExecutionAlone(): bool { @@ -716,11 +761,9 @@ public function isExecutionAlone(): bool } /** - * @param bool $executionAlone - * * @deprecated */ - public function setExecutionAlone($executionAlone = true): void + public function setExecutionAlone(): void { throw new RuntimeException('please call setAttached() instead'); } From d0fe63895cb411339450e5bb4ec097d6c9ea79f6 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 10 May 2021 21:15:22 +0800 Subject: [PATCH 095/258] fix: command comment var error on render subcommand help --- src/AbstractHandler.php | 3 --- src/Concern/CommandHelpTrait.php | 2 ++ src/Controller.php | 15 +++++++++++---- src/IO/AbstractInput.php | 13 +++++++++++++ src/IO/Input.php | 10 +++++++++- 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 3f92f7b7..503f002a 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -533,7 +533,6 @@ protected function showHelpByDefinition(InputDefinition $definition, array $alia } unset($help[0]); - $this->output->mList($help, ['sepChar' => ' ']); } @@ -545,7 +544,6 @@ protected function showHelpByDefinition(InputDefinition $definition, array $alia * @param array $aliases * * @return int - * @throws ReflectionException */ protected function showHelpByMethodAnnotations(string $method, string $action = '', array $aliases = []): int { @@ -626,7 +624,6 @@ protected function showHelpByMethodAnnotations(string $method, string $action = $this->beforeRenderCommandHelp($help); - $this->output->mList($help, [ 'sepChar' => ' ', 'lastNewline' => 0, diff --git a/src/Concern/CommandHelpTrait.php b/src/Concern/CommandHelpTrait.php index 59a22333..032bdb9b 100644 --- a/src/Concern/CommandHelpTrait.php +++ b/src/Concern/CommandHelpTrait.php @@ -2,6 +2,7 @@ namespace Inhere\Console\Concern; +use Inhere\Console\AbstractHandler; use function strpos; use function strtr; @@ -14,6 +15,7 @@ trait CommandHelpTrait { /** * @var array [name => value] + * @see AbstractHandler::annotationVars() */ private $commentsVars; diff --git a/src/Controller.php b/src/Controller.php index 79848d7c..b0e818a8 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -217,6 +217,10 @@ protected function findCommandName(string $command): string return $command; } + protected function beforeRun(): void + { + } + /** * @param string $command command in the group * @@ -233,8 +237,15 @@ public function run(string $command = '') return $this->showHelp(); } + // update subcommand $this->input->setSubCommand($command); + // update some comment vars + $fullCmd = $this->input->getFullCommand(); + $this->setCommentsVar('fullCmd', $fullCmd); + $this->setCommentsVar('fullCommand', $fullCmd); + $this->setCommentsVar('binWithCmd', $this->input->getBinWithCommand()); + // get real sub-command name $command = $this->getRealCommandName($command); @@ -256,10 +267,6 @@ public function run(string $command = '') return parent::run($command); } - protected function beforeRun(): void - { - } - /** * Load command configure */ diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index 502fa93e..31261729 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -124,6 +124,19 @@ public function findCommandName(): string return $command; } + /** + * @return string + */ + public function getCommandPath(): string + { + $path = $this->command; + if ($this->subCommand) { + $path .= ' ' . $this->subCommand; + } + + return $path; + } + /*********************************************************************************** * getter/setter ***********************************************************************************/ diff --git a/src/IO/Input.php b/src/IO/Input.php index 66951273..7f422378 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -158,12 +158,20 @@ public function read(string $question = '', bool $nl = false): string * getter/setter ***********************************************************************************/ + /** + * @return string + */ + public function getBinWithCommand(): string + { + return $this->scriptName . ' ' . $this->getCommandPath(); + } + /** * @return string */ public function getFullCommand(): string { - return $this->script . ' ' . $this->command; + return $this->script . ' ' . $this->getCommandPath(); } /** From b2a904fee0ee940e295f0b57333236d0b1090b96 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 10 May 2021 21:27:02 +0800 Subject: [PATCH 096/258] update the phpunit version limit --- .github/workflows/php.yml | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index a94630f7..85f5505a 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -53,4 +53,4 @@ jobs: # Docs: https://getcomposer.org/doc/articles/scripts.md - name: Run unit tests - run: php vendor/bin/phpunit + run: phpunit -vv diff --git a/composer.json b/composer.json index cecc70cb..b78a50e5 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "toolkit/sys-utils": "~1.0" }, "require-dev": { - "phpunit/phpunit": "~9.1" + "phpunit/phpunit": "^8.5 || ^9.1" }, "autoload": { "psr-4": { From 75b8817ffc0df4388dd1e51df2feaceec4ea5895 Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 11 May 2021 13:09:21 +0800 Subject: [PATCH 097/258] update message show for php dev server --- src/Util/PhpDevServe.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Util/PhpDevServe.php b/src/Util/PhpDevServe.php index 0f820e0b..949a8c44 100644 --- a/src/Util/PhpDevServe.php +++ b/src/Util/PhpDevServe.php @@ -209,14 +209,14 @@ public function getCommand(bool $checkEnv = true): string $phpBin = $this->getPhpBin(); $svrAddr = $this->getServerAddr(); // command eg: "php -S 127.0.0.1:8080 -t web web/index.php"; - $command = "$phpBin -S {$svrAddr}"; + $command = "$phpBin -S $svrAddr"; if ($docRoot = $this->docRoot) { if ($checkEnv && !is_dir($docRoot)) { throw new RuntimeException("the document root is not exists. path: $docRoot"); } - $command .= " -t {$docRoot}"; + $command .= " -t $docRoot"; } if ($entryFile = $this->getEntryFile()) { @@ -250,13 +250,13 @@ public function getInfo(): array */ protected function printDefaultMessage(): void { - $version = PHP_VERSION; + // $version = PHP_VERSION; $workDir = (string)getcwd(); $svrAddr = $this->getServerAddr(); $docRoot = $this->docRoot ? $workDir . '/' . $this->docRoot : $workDir; Cli::writeln([ - "PHP $version Development Server started\nServer listening on http://$svrAddr", + "PHP Development Server started\nServer listening on http://$svrAddr", "Document root is $docRoot", 'You can use CTRL + C to stop run.', ]); From 5fa11f0722d9da989e1e3f64fcdf15e1aefb9ce4 Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 11 May 2021 13:15:38 +0800 Subject: [PATCH 098/258] update message show for php dev server --- src/Util/PhpDevServe.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Util/PhpDevServe.php b/src/Util/PhpDevServe.php index 949a8c44..632bf880 100644 --- a/src/Util/PhpDevServe.php +++ b/src/Util/PhpDevServe.php @@ -256,7 +256,7 @@ protected function printDefaultMessage(): void $docRoot = $this->docRoot ? $workDir . '/' . $this->docRoot : $workDir; Cli::writeln([ - "PHP Development Server started\nServer listening on http://$svrAddr", + "PHP Development Server start listening on http://$svrAddr", "Document root is $docRoot", 'You can use CTRL + C to stop run.', ]); From 1c98e59f41265c9b9215e540f6b33cb2f80e9f90 Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 12 May 2021 21:38:15 +0800 Subject: [PATCH 099/258] update some logic for php serve --- src/Util/PhpDevServe.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Util/PhpDevServe.php b/src/Util/PhpDevServe.php index 632bf880..c053cf8e 100644 --- a/src/Util/PhpDevServe.php +++ b/src/Util/PhpDevServe.php @@ -43,6 +43,7 @@ class PhpDevServe /** * The entry file for server. e.g web/index.php + * NOTICE: if set the entry file, will need handle static file access. * * @var string */ @@ -302,10 +303,9 @@ public function getPhpBin(): string */ public function getEntryFile(): string { - if (!$this->entryFile) { - $this->entryFile = self::IDX_FILE; - } - + // if (!$this->entryFile) { + // $this->entryFile = self::IDX_FILE; + // } return $this->entryFile; } From d4286e05f72f2fc067660f2d0c4bfd48c70a9d8b Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 14 May 2021 22:28:41 +0800 Subject: [PATCH 100/258] up: support set env vars on start php serve --- src/Util/PhpDevServe.php | 155 ++++++++++++++++++++++++--------------- 1 file changed, 95 insertions(+), 60 deletions(-) diff --git a/src/Util/PhpDevServe.php b/src/Util/PhpDevServe.php index c053cf8e..06971aba 100644 --- a/src/Util/PhpDevServe.php +++ b/src/Util/PhpDevServe.php @@ -13,6 +13,7 @@ use function file_get_contents; use function is_dir; use function is_file; +use function putenv; use function random_int; use function strpos; @@ -54,7 +55,7 @@ class PhpDevServe * * @var string */ - public $serveAddr = ''; + protected $serveAddr = ''; /** * Can custom message for print before start server. @@ -76,6 +77,11 @@ class PhpDevServe */ private $hceEnv = ''; + /** + * @var array + */ + private $envVars = []; + /** * @param string $serveAddr * @param string $docRoot @@ -121,60 +127,24 @@ public function __construct(string $serveAddr, string $docRoot, string $entryFil } /** - * @param string $hceFile - * @param bool $mustLoad - * - * @return bool - */ - public function loadHceFile(string $hceFile, bool $mustLoad = false): bool - { - if (!file_exists($hceFile)) { - if ($mustLoad) { - throw new RuntimeException('the http-client env json file not exists. file: ' . $hceFile); - } - - return false; - } - - $jsonString = file_get_contents($hceFile); - - // load data - $this->hceData = Json::decode($jsonString, true); - return true; - } - - /** - * @param string $envName + * @param Closure $fn * * @return $this */ - public function useHceEnv(string $envName): self + public function config(Closure $fn): self { - if (!isset($this->hceData[$envName])) { - throw new RuntimeException('the env name is not exist in hceData'); - } - - $info = $this->hceData[$envName]; - - $this->hceEnv = $envName; - if ($host = $info['host']) { - $this->serveAddr = $host; - } - - // TODO load more from $info - // phpBin - + $fn($this); return $this; } /** - * @param Closure $fn + * @param array $envVars * * @return $this */ - public function config(Closure $fn): self + public function setEnvVars(array $envVars): self { - $fn($this); + $this->envVars = $envVars; return $this; } @@ -191,12 +161,39 @@ public function listen(): void $this->printDefaultMessage(); } + $this->putEnvVars(); + $command = $this->getCommand(); Cli::write("> $command"); Sys::execute($command); } + /** + * @throws Exception + */ + protected function printDefaultMessage(): void + { + // $version = PHP_VERSION; + $workDir = (string)getcwd(); + $svrAddr = $this->getServerAddr(); + $docRoot = $this->docRoot ? $workDir . '/' . $this->docRoot : $workDir; + + Cli::writeln([ + "PHP Development Server start listening on http://$svrAddr", + "Document root is $docRoot", + 'You can use CTRL + C to stop run.', + ]); + } + + protected function putEnvVars(): void + { + foreach ($this->envVars as $name => $val) { + $_SERVER[$name] = (string)$val; + putenv("$name=$val"); + } + } + /** * build full command line string * @@ -231,6 +228,53 @@ public function getCommand(bool $checkEnv = true): string return $command; } + /** + * @param string $hceFile + * @param bool $mustLoad + * + * @return bool + */ + public function loadHceFile(string $hceFile, bool $mustLoad = false): bool + { + if (!file_exists($hceFile)) { + if ($mustLoad) { + throw new RuntimeException('the http-client env json file not exists. file: ' . $hceFile); + } + + return false; + } + + $jsonString = file_get_contents($hceFile); + + // load data + $this->hceData = Json::decode($jsonString, true); + return true; + } + + /** + * @param string $envName + * + * @return $this + */ + public function useHceEnv(string $envName): self + { + if (!isset($this->hceData[$envName])) { + throw new RuntimeException('the env name is not exist in hceData'); + } + + $info = $this->hceData[$envName]; + + $this->hceEnv = $envName; + if ($host = $info['host']) { + $this->serveAddr = $host; + } + + // TODO load more from $info + // phpBin + + return $this; + } + /** * @return array * @throws Exception @@ -246,23 +290,6 @@ public function getInfo(): array ]; } - /** - * @throws Exception - */ - protected function printDefaultMessage(): void - { - // $version = PHP_VERSION; - $workDir = (string)getcwd(); - $svrAddr = $this->getServerAddr(); - $docRoot = $this->docRoot ? $workDir . '/' . $this->docRoot : $workDir; - - Cli::writeln([ - "PHP Development Server start listening on http://$svrAddr", - "Document root is $docRoot", - 'You can use CTRL + C to stop run.', - ]); - } - /** * @param callable $beforeStart * @@ -385,4 +412,12 @@ public function setDocRoot(string $docRoot): self return $this; } + + /** + * @return array + */ + public function getEnvVars(): array + { + return $this->envVars; + } } From 2215ffe8a3576414714a013ca82d1815db0eb194 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 28 May 2021 20:11:17 +0800 Subject: [PATCH 101/258] update: add new method getSameIntOpt on input --- src/Concern/InputOptionsTrait.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Concern/InputOptionsTrait.php b/src/Concern/InputOptionsTrait.php index 7ce4efc2..56aa2400 100644 --- a/src/Concern/InputOptionsTrait.php +++ b/src/Concern/InputOptionsTrait.php @@ -125,6 +125,19 @@ public function getIntOpt(string $name, int $default = 0): int return (int)$this->getOpt($name, $default); } + /** + * Get an int option(long/short) value + * + * @param string|string[] $names eg 'l,length' OR ['l', 'length'] + * @param int $default + * + * @return int + */ + public function getSameIntOpt($names, int $default = 0): int + { + return (int)$this->getSameOpt($names, $default); + } + /** * Get (long/short)option value(bool) * eg: -h --help From cb6f8c79ffaa842c9c58cabce6b6a62918f16e33 Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 1 Jun 2021 21:52:12 +0800 Subject: [PATCH 102/258] update the default error message text --- src/Component/ErrorHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Component/ErrorHandler.php b/src/Component/ErrorHandler.php index 282ffe29..8fed76b8 100644 --- a/src/Component/ErrorHandler.php +++ b/src/Component/ErrorHandler.php @@ -69,7 +69,7 @@ public function handle(Throwable $e, AbstractApplication $app): void } // simple output - $app->getOutput()->error('An error occurred! MESSAGE: ' . $e->getMessage()); + $app->getOutput()->error('An error occurred! - ' . $e->getMessage()); $app->write("\nYou can use '--debug 4' to see error details."); } } From ce1e7f5b6bef201cbf76d51930937937b5b3f601 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 11 Jun 2021 12:02:49 +0800 Subject: [PATCH 103/258] feat: add an new simple interactive shell componnent --- src/AbstractApplication.php | 4 +- src/Component/Interact/AbstractQuestion.php | 43 +++ src/Component/Interact/Checkbox.php | 6 +- src/Component/Interact/Choose.php | 8 +- src/Component/Interact/Confirm.php | 4 +- src/Component/Interact/IShell.php | 336 ++++++++++++++++++++ src/Component/Interact/LimitedAsk.php | 12 +- src/Component/Interact/Password.php | 4 +- src/Component/Interact/Question.php | 4 +- src/Component/Interact/Terminal.php | 4 +- src/Component/InteractMessage.php | 12 - src/Component/InteractiveHandle.php | 51 +++ 12 files changed, 453 insertions(+), 35 deletions(-) create mode 100644 src/Component/Interact/AbstractQuestion.php create mode 100644 src/Component/Interact/IShell.php delete mode 100644 src/Component/InteractMessage.php create mode 100644 src/Component/InteractiveHandle.php diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index e37c1016..61e73cdb 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -428,8 +428,8 @@ protected function startInteractiveShell(): void $this->debugf('php is not enable "pcntl" extension, cannot listen CTRL+C signal'); } + // register signal. if ($hasPcntl) { - // register signal. ProcessUtil::installSignal(Signal::INT, static function () use ($out) { $out->colored("\nQuit by CTRL+C"); exit(0); @@ -460,8 +460,8 @@ protected function startInteractiveShell(): void } } + // listen signal. if ($hasPcntl) { - // listen signal. ProcessUtil::dispatchSignal(); } diff --git a/src/Component/Interact/AbstractQuestion.php b/src/Component/Interact/AbstractQuestion.php new file mode 100644 index 00000000..70e80709 --- /dev/null +++ b/src/Component/Interact/AbstractQuestion.php @@ -0,0 +1,43 @@ +answer = StrObject::new($str)->trim(); + } + + /** + * @return StrObject + */ + public function getAnswer(): StrObject + { + return $this->answer; + } + + /** + * @return int + */ + public function getInt(): int + { + return $this->answer->toInt(); + } +} diff --git a/src/Component/Interact/Checkbox.php b/src/Component/Interact/Checkbox.php index 70ab6c92..ba2f8df6 100644 --- a/src/Component/Interact/Checkbox.php +++ b/src/Component/Interact/Checkbox.php @@ -2,7 +2,7 @@ namespace Inhere\Console\Component\Interact; -use Inhere\Console\Component\InteractMessage; +use Inhere\Console\Component\InteractiveHandle; use Inhere\Console\Console; use Inhere\Console\Util\Show; use function array_filter; @@ -17,7 +17,7 @@ * * @package Inhere\Console\Component\Interact */ -class Checkbox extends InteractMessage +class Checkbox extends InteractiveHandle { /** * List multiple options and allow multiple selections @@ -29,7 +29,7 @@ class Checkbox extends InteractMessage * * @return array */ - public static function select(string $description, $options, $default = null, $allowExit = true): array + public static function select(string $description, $options, $default = null, bool $allowExit = true): array { if (!$description = trim($description)) { Show::error('Please provide a description text!', 1); diff --git a/src/Component/Interact/Choose.php b/src/Component/Interact/Choose.php index 0ea454bb..10a0b5a4 100644 --- a/src/Component/Interact/Choose.php +++ b/src/Component/Interact/Choose.php @@ -2,7 +2,7 @@ namespace Inhere\Console\Component\Interact; -use Inhere\Console\Component\InteractMessage; +use Inhere\Console\Component\InteractiveHandle; use Inhere\Console\Console; use Inhere\Console\Util\Show; use function array_key_exists; @@ -15,7 +15,7 @@ * * @package Inhere\Console\Component\Interact */ -class Choose extends InteractMessage +class Choose extends InteractiveHandle { /** * Choose one of several options @@ -55,11 +55,11 @@ public static function one(string $description, $options, $default = null, bool $text .= "\n $key) $value"; } - $defaultText = $default ? "[default:{$default}]" : ''; + $defaultText = $default ? "[default:$default]" : ''; Console::write($text); beginChoice: - $r = Console::readln("Your choice{$defaultText} : "); + $r = Console::readln("Your choice$defaultText : "); // error, allow try again once. if (!array_key_exists($r, $options)) { diff --git a/src/Component/Interact/Confirm.php b/src/Component/Interact/Confirm.php index 95023ed1..955ea149 100644 --- a/src/Component/Interact/Confirm.php +++ b/src/Component/Interact/Confirm.php @@ -2,7 +2,7 @@ namespace Inhere\Console\Component\Interact; -use Inhere\Console\Component\InteractMessage; +use Inhere\Console\Component\InteractiveHandle; use Inhere\Console\Console; use Inhere\Console\Util\Show; use function stripos; @@ -14,7 +14,7 @@ * * @package Inhere\Console\Component\Interact */ -class Confirm extends InteractMessage +class Confirm extends InteractiveHandle { /** * Send a message request confirmation diff --git a/src/Component/Interact/IShell.php b/src/Component/Interact/IShell.php new file mode 100644 index 00000000..71dc2309 --- /dev/null +++ b/src/Component/Interact/IShell.php @@ -0,0 +1,336 @@ + 1, + 'quit' => 1, + 'exit' => 1, + ]; + + /** + * @param array $options + * + * @return static + */ + public static function new(array $options = []): self + { + return new self($options); + } + + /** + * Quick create and start run an shell + * + * @param callable $handler + * @param array $options + * - title + * - prefix + */ + public static function run(callable $handler, array $options = []): void + { + $options['handler'] = $handler; + + (new self($options))->start(); + } + + /** + * @return bool + */ + protected function registerSignal(): bool + { + if (!($hasPcntl = ProcessUtil::hasPcntl())) { + $this->debugf('php is not enable "pcntl" extension, cannot listen CTRL+C signal'); + } + + // register signal. + if ($hasPcntl) { + ProcessUtil::installSignal(Signal::INT, static function () { + Show::colored("\nQuit by CTRL+C"); + exit(0); + }); + } + + return $hasPcntl; + } + + protected function beforeStart(): void + { + if ($this->title) { + Title::show($this->title, [ + 'titlePos' => Title::POS_MIDDLE, + ]); + } + + if (!$this->errorHandler) { + $this->errorHandler = $this->defaultErrorHandler(); + } + } + + /** + * Start shell to run + */ + public function start(): void + { + if (!$handler = $this->handler) { + throw new RuntimeException('must be set the logic handler for start'); + } + + $prefix = $this->prefix; + $this->beforeStart(); + + $hasPcntl = $this->registerSignal(); + while (true) { + $line = Interact::readln("$prefix> "); + + // listen signal. + if ($hasPcntl) { + ProcessUtil::dispatchSignal(); + } + + $state = $this->dispatch($line, $handler); + if ($state === self::STOP) { + break; + } + + Console::println(''); + } + + Show::colored("\nQuit. ByeBye!"); + } + + /** + * @param string $line + * @param callable $handler + * + * @return int + * @throws RuntimeException + */ + protected function dispatch(string $line, callable $handler): int + { + if (strlen($line) < 5) { + // exit + if (isset($this->exitKeys[$line])) { + return self::STOP; + } + + // "?" as show help + if ($line === '?') { + $line = self::HELP; + } + } + + // display help + $hasMoreKey = false; + if ($line === self::HELP || ($hasMoreKey = strpos($line, 'help ') === 0)) { + // help CMD + $moreKeys = $hasMoreKey ? explode(' ', $line) : []; + $this->handleHelp($moreKeys); + return self::GOON; + } + + $this->debugf('input line: %s', $line); + + try { + // call validator + if ($vfn = $this->validator) { + $line = $vfn($line); + } + + $handler($line); + } catch (Throwable $e) { + if ($fn = $this->errorHandler) { + $fn($e); + } else { + throw new RuntimeException('dispatch error, line: ' . $line, 500, $e); + } + } + + return self::GOON; + } + + /** + * @param array $moreKeys + */ + protected function handleHelp(array $moreKeys): void + { + if ($fn = $this->helpHandler) { + $fn($moreKeys); + return; + } + + Console::println('no help message'); + } + + /** + * @return Closure + */ + public function emptyValidator(): Closure + { + return static function (string $line) { + if ($line === '') { + throw new InvalidArgumentException('input is empty!'); + } + return $line; + }; + } + + /** + * @return Closure + */ + public function defaultErrorHandler(): Closure + { + return static function (Throwable $e) { + Console::write('ERROR: ' . $e->getMessage(), false); + }; + } + + /** + * @param string $format + * @param mixed ...$args + */ + public function debugf(string $format, ...$args): void + { + if ($this->debug) { + Console::logf(Console::VERB_DEBUG, $format, ...$args); + } + } + + /** + * @param callable $handler + * + * @return IShell + */ + public function setHandler(callable $handler): IShell + { + $this->handler = $handler; + return $this; + } + + /** + * @param bool $debug + * + * @return IShell + */ + public function setDebug(bool $debug): IShell + { + $this->debug = $debug; + return $this; + } + + /** + * @param array $exitKeys + * + * @return IShell + */ + public function setExitKeys(array $exitKeys): IShell + { + $this->exitKeys = $exitKeys; + return $this; + } + + /** + * @param string $title + * + * @return IShell + */ + public function setTitle(string $title): IShell + { + $this->title = $title; + return $this; + } + + /** + * @param string $prefix + * + * @return IShell + */ + public function setPrefix(string $prefix): IShell + { + $this->prefix = $prefix; + return $this; + } + + /** + * @param callable $helpHandler + * + * @return IShell + */ + public function setHelpHandler(callable $helpHandler): IShell + { + $this->helpHandler = $helpHandler; + return $this; + } + + /** + * @param callable $errorHandler + * + * @return IShell + */ + public function setErrorHandler(callable $errorHandler): IShell + { + $this->errorHandler = $errorHandler; + return $this; + } +} diff --git a/src/Component/Interact/LimitedAsk.php b/src/Component/Interact/LimitedAsk.php index 21d1bbfd..9f792d41 100644 --- a/src/Component/Interact/LimitedAsk.php +++ b/src/Component/Interact/LimitedAsk.php @@ -3,7 +3,7 @@ namespace Inhere\Console\Component\Interact; use Closure; -use Inhere\Console\Component\InteractMessage; +use Inhere\Console\Component\InteractiveHandle; use Inhere\Console\Console; use Inhere\Console\Util\Show; use function sprintf; @@ -15,17 +15,17 @@ * * @package Inhere\Console\Component\Interact */ -class LimitedAsk extends InteractMessage +class LimitedAsk extends InteractiveHandle { /** * Ask a question, ask for a limited number of times * 若输入了值且验证成功则返回 输入的结果 * 否则,会连续询问 $times 次, 若仍然错误,退出 * - * @param string $question 问题 - * @param string $default 默认值 - * @param Closure $validator (默认验证输入是否为空)自定义回调验证输入是否符合要求; 验证成功返回true 否则 可返回错误消息 - * @param int $times Allow input times + * @param string $question 问题 + * @param string $default 默认值 + * @param Closure|null $validator (默认验证输入是否为空)自定义回调验证输入是否符合要求; 验证成功返回true 否则 可返回错误消息 + * @param int $times Allow input times * * @return string * @example This is an example diff --git a/src/Component/Interact/Password.php b/src/Component/Interact/Password.php index 681ea109..08647c22 100644 --- a/src/Component/Interact/Password.php +++ b/src/Component/Interact/Password.php @@ -2,7 +2,7 @@ namespace Inhere\Console\Component\Interact; -use Inhere\Console\Component\InteractMessage; +use Inhere\Console\Component\InteractiveHandle; use RuntimeException; use Toolkit\Sys\Sys; use function addslashes; @@ -18,7 +18,7 @@ * * @package Inhere\Console\Component\Interact */ -class Password extends InteractMessage +class Password extends InteractiveHandle { /** * Interactively prompts for input without echoing to the terminal. diff --git a/src/Component/Interact/Question.php b/src/Component/Interact/Question.php index 9af679ce..9f74ed6d 100644 --- a/src/Component/Interact/Question.php +++ b/src/Component/Interact/Question.php @@ -3,7 +3,7 @@ namespace Inhere\Console\Component\Interact; use Closure; -use Inhere\Console\Component\InteractMessage; +use Inhere\Console\Component\InteractiveHandle; use Inhere\Console\Console; use Inhere\Console\Util\Show; use function trim; @@ -14,7 +14,7 @@ * * @package Inhere\Console\Component\Interact */ -class Question extends InteractMessage +class Question extends InteractiveHandle { /** * Ask a question, ask for results; return the result of the input diff --git a/src/Component/Interact/Terminal.php b/src/Component/Interact/Terminal.php index 9f961ac0..3acdd0f1 100644 --- a/src/Component/Interact/Terminal.php +++ b/src/Component/Interact/Terminal.php @@ -2,13 +2,13 @@ namespace Inhere\Console\Component\Interact; -use Inhere\Console\Component\InteractMessage; +use Inhere\Console\Component\InteractiveHandle; /** * Class Terminal * * @package Inhere\Console\Component\Interact */ -class Terminal extends InteractMessage +class Terminal extends InteractiveHandle { } diff --git a/src/Component/InteractMessage.php b/src/Component/InteractMessage.php deleted file mode 100644 index c18dbfd2..00000000 --- a/src/Component/InteractMessage.php +++ /dev/null @@ -1,12 +0,0 @@ -setValidator(function (string $line) { + * // check input + * if (!$line) { + * throw new InvalidArgumentException('argument is required'); + * } + * return $line; + * }); + * ``` + * + * @var callable + */ + protected $validator; + + /** + * Class constructor. + * + * @param array $options + */ + public function __construct(array $options = []) + { + Obj::init($this, $options); + } + + /** + * @param callable $validator + * + * @return self + */ + public function setValidator(callable $validator): self + { + $this->validator = $validator; + return $this; + } +} From 68001ef3bbc567ca395e22fcaf722befcfa0df59 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 14 Jun 2021 12:53:52 +0800 Subject: [PATCH 104/258] fix: doc root parse error for php serve --- src/Util/PhpDevServe.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Util/PhpDevServe.php b/src/Util/PhpDevServe.php index 06971aba..ca3e8dff 100644 --- a/src/Util/PhpDevServe.php +++ b/src/Util/PhpDevServe.php @@ -177,7 +177,11 @@ protected function printDefaultMessage(): void // $version = PHP_VERSION; $workDir = (string)getcwd(); $svrAddr = $this->getServerAddr(); - $docRoot = $this->docRoot ? $workDir . '/' . $this->docRoot : $workDir; + $docRoot = $workDir; + if ($this->docRoot) { + $docRoot = $this->docRoot; + $docRoot = Helper::isAbsPath($docRoot) ? $docRoot : $workDir . '/' . $docRoot; + } Cli::writeln([ "PHP Development Server start listening on http://$svrAddr", From 1bd802b1bfc539797a9425b193a4e54a59184c58 Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 24 Jul 2021 11:27:42 +0800 Subject: [PATCH 105/258] fix: var type error on render help Signed-off-by: inhere --- src/AbstractHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 503f002a..605bf2bd 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -566,7 +566,7 @@ protected function showHelpByMethodAnnotations(string $method, string $action = $help = []; $doc = $ref->getMethod($method)->getDocComment(); - $tags = PhpDoc::getTags($this->parseCommentsVars($doc)); + $tags = PhpDoc::getTags($this->parseCommentsVars((string)$doc)); if ($aliases) { $realName = $action ?: static::getName(); From 5d8b274775e4c866bb8564eec9f2fbd5227900c8 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 2 Aug 2021 11:44:25 +0800 Subject: [PATCH 106/258] update some for gen auto complete scripts Signed-off-by: inhere --- src/Concern/ApplicationHelpTrait.php | 73 +++++++++++++++++++--------- 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index ab1ea4ae..c761f6b5 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -9,7 +9,6 @@ namespace Inhere\Console\Concern; use Inhere\Console\AbstractHandler; -use Toolkit\Cli\Style; use Inhere\Console\Console; use Inhere\Console\Contract\CommandInterface; use Inhere\Console\IO\Input; @@ -18,7 +17,9 @@ use Inhere\Console\Util\FormatUtil; use Inhere\Console\Util\Show; use Toolkit\Cli\ColorTag; +use Toolkit\Cli\Style; use function array_merge; +use function basename; use function date; use function dirname; use function file_get_contents; @@ -68,7 +69,7 @@ public function showVersionInfo(): void /** @var Output $out */ $out = $this->output; $out->aList([ - "$logo\n {$name}, Version $version\n", + "$logo\n $name, Version $version\n", 'System Info' => "PHP version $phpVersion, on $os system", 'Application Info' => "Update at $updateAt, publish at $publishAt(current $date)", ], '', [ @@ -100,14 +101,24 @@ public function showHelpInfo(string $command = ''): void $binName = $in->getScriptName(); // built in options - $globalOptions = FormatUtil::alignOptions(self::$globalOptions); + $globalOptions = self::$globalOptions; + // append generate options: + // php examples/app --auto-completion --shell-env zsh --gen-file + // php examples/app --auto-completion --shell-env zsh --gen-file stdout + if ($this->isDebug()) { + $globalOptions['--auto-completion'] = 'Open generate auto completion script'; + $globalOptions['--shell-env'] = 'The shell env name for generate auto completion script'; + $globalOptions['--gen-file'] = 'The output file for generate auto completion script'; + } + + $globalOptions = FormatUtil::alignOptions($globalOptions); /** @var Output $out */ $out = $this->output; $out->helpPanel([ - 'Usage' => "$binName {command} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", + 'Usage' => "$binName {command} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", 'Options' => $globalOptions, - 'Example' => [ + 'Example' => [ "$binName test run a independent command", "$binName home index run a sub-command of the group", sprintf("$binName home%sindex run a sub-command of the group", $delimiter), @@ -115,11 +126,12 @@ public function showHelpInfo(string $command = ''): void "$binName home index -h see a sub-command help of the group", sprintf("$binName home%sindex -h see a sub-command help of the group", $delimiter), ], - 'Help' => [ + 'Help' => [ 'Generate shell auto completion scripts:', " $binName --auto-completion --shell-env [zsh|bash] [--gen-file stdout]", ' eg:', " $binName --auto-completion --shell-env bash --gen-file stdout", + " $binName --auto-completion --shell-env zsh --gen-file stdout", " $binName --auto-completion --shell-env bash --gen-file myapp.sh", ], ]); @@ -139,7 +151,7 @@ public function showCommandList(): void // php bin/app list --only-name if ($autoComp && $shellEnv === 'bash') { - $this->dumpAutoCompletion($shellEnv, []); + $this->dumpAutoCompletion('bash', []); return; } @@ -233,11 +245,11 @@ public function showCommandList(): void $scriptName = $this->getScriptName(); // built in options - $internalOptions = FormatUtil::alignOptions(self::$globalOptions); + $globOpts = self::$globalOptions; Show::mList([ 'Usage:' => "$scriptName {COMMAND} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", - 'Options:' => $internalOptions, + 'Options:' => FormatUtil::alignOptions($globOpts), 'Internal Commands:' => $internalCommands, 'Available Commands:' => array_merge($groupArr, $commandArr), ], [ @@ -273,18 +285,22 @@ protected function dumpAutoCompletion(string $shellEnv, array $data): void // info $glue = ' '; - $genFile = $input->getStringOpt('gen-file'); - $filename = 'auto-completion.' . $shellEnv; + $genFile = $input->getStringOpt('gen-file', 'none'); $tplDir = dirname(__DIR__, 2) . '/resource/templates'; if ($shellEnv === 'bash') { $tplFile = $tplDir . '/bash-completion.tpl'; - $list = array_merge($router->getCommandNames(), $router->getControllerNames(), - $this->getInternalCommands()); + + $list = array_merge( + $router->getCommandNames(), + $router->getControllerNames(), + $this->getInternalCommands() + ); } else { - $glue = PHP_EOL; - $list = []; $tplFile = $tplDir . '/zsh-completion.tpl'; + + $glue = PHP_EOL; + $list = []; foreach ($data as $name => $desc) { $list[] = $name . ':' . str_replace(':', '\:', $desc); } @@ -292,8 +308,8 @@ protected function dumpAutoCompletion(string $shellEnv, array $data): void $commands = implode($glue, $list); - // dump to stdout. - if (!$genFile) { + // only dump commands to stdout. + if ($genFile === 'none') { $output->write($commands, true, false, ['color' => false]); return; } @@ -303,7 +319,19 @@ protected function dumpAutoCompletion(string $shellEnv, array $data): void $commands = Style::stripColor($commands); } - // dump at script file + $toStdout = $genFile === 'stdout'; + $filename = 'auto-completion.' . $shellEnv; + if (!$toStdout) { + if ($genFile === 'true') { + $targetFile = $input->getPwd() . '/' . $filename; + } else { + $filename = basename($genFile); + // $targetDir = dirname($genFile); + $targetFile = $genFile; + } + } + + // dump to script file $binName = $input->getBinName(); $tplText = file_get_contents($tplFile); $content = strtr($tplText, [ @@ -315,19 +343,18 @@ protected function dumpAutoCompletion(string $shellEnv, array $data): void '{{fmtBinName}}' => str_replace('/', '_', $binName), ]); - // dump to stdout - if ($genFile === 'stdout') { + // dump script contents to stdout + if ($toStdout) { file_put_contents('php://stdout', $content); return; } - $targetFile = $input->getPwd() . '/' . $filename; $output->write(['Target File:', $targetFile, '']); if (file_put_contents($targetFile, $content) > 10) { - $output->success("O_O! Generate $filename successful!"); + $output->success("O_O! Generate file:$filename successful!"); } else { - $output->error("O^O! Generate $filename failure!"); + $output->error("O^O! Generate file:$filename failure!"); } } } From 564ee5b72082a9054f4fbdec7aee9982fc4687d7 Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 4 Aug 2021 17:27:41 +0800 Subject: [PATCH 107/258] add some shell completion refer files Signed-off-by: inhere --- resource/refer/shell-completion-doc.md | 11 +++++++++++ resource/refer/zsh-complete-doc.md | 15 +++++++++++++++ src/Concern/ApplicationHelpTrait.php | 2 +- 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 resource/refer/shell-completion-doc.md create mode 100644 resource/refer/zsh-complete-doc.md diff --git a/resource/refer/shell-completion-doc.md b/resource/refer/shell-completion-doc.md new file mode 100644 index 00000000..257d9ab1 --- /dev/null +++ b/resource/refer/shell-completion-doc.md @@ -0,0 +1,11 @@ +# shell completion + +## bash + +https://github.com/nosarthur/gita/blob/master/.gita-completion.bash + +## zsh + +https://github.com/nosarthur/gita/blob/master/.gita-completion.zsh + + diff --git a/resource/refer/zsh-complete-doc.md b/resource/refer/zsh-complete-doc.md new file mode 100644 index 00000000..7bdbaf7a --- /dev/null +++ b/resource/refer/zsh-complete-doc.md @@ -0,0 +1,15 @@ +# zsh complete + +参考补全代码仓库提供的文档: + +https://github.com/zsh-users/zsh-completions/blob/master/zsh-completions-howto.org + +补全代码示例: + +https://github.com/zsh-users/zsh-completions/blob/master/src/_golang + +重新加载定义的补全函数: + +```bash +unfunction _pandoc && autoload -U _pandoc +``` diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index c761f6b5..9ae9cace 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -322,7 +322,7 @@ protected function dumpAutoCompletion(string $shellEnv, array $data): void $toStdout = $genFile === 'stdout'; $filename = 'auto-completion.' . $shellEnv; if (!$toStdout) { - if ($genFile === 'true') { + if ($genFile === '1') { $targetFile = $input->getPwd() . '/' . $filename; } else { $filename = basename($genFile); From c57f161bffb95e9e11e6b9c98c80ed4cf958e47b Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 4 Aug 2021 19:31:08 +0800 Subject: [PATCH 108/258] update template file for gen zsh Signed-off-by: inhere --- resource/templates/zsh-completion.tpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resource/templates/zsh-completion.tpl b/resource/templates/zsh-completion.tpl index 4a124ca3..6a62ff80 100644 --- a/resource/templates/zsh-completion.tpl +++ b/resource/templates/zsh-completion.tpl @@ -1,7 +1,7 @@ #compdef {{binName}} # ------------------------------------------------------------------------------ # DATE: {{datetime}} -# FILE: auto-completion.zsh +# FILE: {{filename}} # AUTHOR: inhere (https://github.com/inhere) # VERSION: {{version}} # DESCRIPTION: zsh shell complete for console app: {{binName}} @@ -11,7 +11,7 @@ _complete_for_{{fmtBinName}} () { local -a commands IFS=$'\n' - commands=( + commands+=( {{commands}} ) From 9396e2ad110ac99f07ba4aad3dae966d3eba15ef Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 4 Aug 2021 23:18:30 +0800 Subject: [PATCH 109/258] add an config prop at handler Signed-off-by: inhere --- src/AbstractHandler.php | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 605bf2bd..f2f89d3a 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -22,10 +22,10 @@ use InvalidArgumentException; use LogicException; use ReflectionClass; -use ReflectionException; use RuntimeException; use Swoole\Coroutine; use Swoole\Event; +use Toolkit\Stdlib\Obj\ConfigObject; use Toolkit\Stdlib\Util\PhpDoc; use function array_diff_key; use function array_filter; @@ -105,6 +105,11 @@ abstract class AbstractHandler implements CommandHandlerInterface */ protected $processTitle = ''; + /** + * @var ConfigObject + */ + protected $params; + /** * Whether enabled * @@ -453,7 +458,6 @@ private function checkNotExistsOptions(InputDefinition $def): void $errMsg = sprintf('Input option is not exists (unknown: "%s").', (isset($first[1]) ? '--' : '-') . $first); throw new InvalidArgumentException($errMsg); } - } /************************************************************************** @@ -468,6 +472,29 @@ public function isAlone(): bool return $this instanceof CommandInterface; } + /** + * @return ConfigObject + */ + public function getParams(): ConfigObject + { + if (!$this->params) { + $this->initParams([]); + } + + return $this->params; + } + + /** + * @param array $params + * + * @return ConfigObject + */ + public function initParams(array $params): ConfigObject + { + $this->params = ConfigObject::new($params); + return $this->params; + } + /********************************************************** * display help information **********************************************************/ From a268196a6ff3d645c81bfdc6b16382c52f7602d5 Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 4 Aug 2021 23:32:05 +0800 Subject: [PATCH 110/258] fix: add newline after writef log Signed-off-by: inhere --- src/Console.php | 5 +++-- src/Controller.php | 2 +- src/Util/Show.php | 13 ++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Console.php b/src/Console.php index 1c7d7f94..3f40485d 100644 --- a/src/Console.php +++ b/src/Console.php @@ -16,6 +16,7 @@ use function trim; use const DEBUG_BACKTRACE_IGNORE_ARGS; use const JSON_UNESCAPED_SLASHES; +use const PHP_EOL; /** * Class Console @@ -114,7 +115,7 @@ public static function logf(int $level, string $format, ...$args): void $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, self::$traceIndex + 2); $position = self::formatBacktrace($backtrace, self::$traceIndex); - self::writef('%s [%s] [%s] %s', $datetime, $tagName, $position, $message); + self::writef('%s [%s] [%s] %s' . PHP_EOL, $datetime, $tagName, $position, $message); } /** @@ -153,7 +154,7 @@ public static function log(int $level, string $msg, array $data = [], array $opt $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, self::$traceIndex + 2); $position = self::formatBacktrace($backtrace, self::$traceIndex); - self::writef('%s [%s] [%s]%s %s %s', $datetime, $taggedName, $position, $optString, trim($msg), $dataString); + self::writef('%s [%s] [%s]%s %s %s' . PHP_EOL, $datetime, $taggedName, $position, $optString, trim($msg), $dataString); } /** diff --git a/src/Controller.php b/src/Controller.php index b0e818a8..af5dbdd9 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -581,7 +581,7 @@ final public function showCommandList(): void $this->output->write(ucfirst($classDes) . PHP_EOL); if ($aliases = $this->getAliases()) { - $this->output->writef('Alias: %s', implode(',', $aliases)); + $this->output->writef("Alias: %s\n", implode(',', $aliases)); } $this->output->mList([ diff --git a/src/Util/Show.php b/src/Util/Show.php index e49ce98b..94f6df6a 100644 --- a/src/Util/Show.php +++ b/src/Util/Show.php @@ -16,16 +16,16 @@ use Inhere\Console\Component\Progress\DynamicText; use Inhere\Console\Component\Progress\SimpleBar; use Inhere\Console\Component\Progress\SimpleTextBar; -use Toolkit\Cli\Style; use Inhere\Console\Console; use LogicException; use Toolkit\Cli\Cli; use Toolkit\Cli\ColorTag; +use Toolkit\Cli\Style; +use Toolkit\Stdlib\Math; use Toolkit\Stdlib\Str; use Toolkit\Sys\Sys; use function array_keys; use function array_values; -use function ceil; use function count; use function implode; use function is_array; @@ -33,7 +33,6 @@ use function json_encode; use function microtime; use function sprintf; -use function str_repeat; use function strlen; use function strpos; use function strtoupper; @@ -182,7 +181,7 @@ public static function liteBlock( * @return int * @throws LogicException */ - public static function __callStatic($method, array $args = []) + public static function __callStatic(string $method, array $args = []) { if (isset(self::$blockMethods[$method])) { $msg = $args[0]; @@ -237,11 +236,11 @@ public static function splitLine(string $title, string $char = '-', int $width = } if (!$title) { - return self::write(str_repeat($char, $width)); + return self::write(Str::repeat($char, $width)); } - $strLen = ceil(($width - Str::len($title) - 2) / 2); - $padStr = $strLen > 0 ? str_repeat($char, $strLen) : ''; + $strLen = Math::ceil(($width - Str::len($title) - 2) / 2); + $padStr = $strLen > 0 ? Str::repeat($char, $strLen) : ''; return self::write($padStr . ' ' . ucwords($title) . ' ' . $padStr); } From 513fd1cdb84ee6a7ebb52589508da910d771fcbe Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 5 Aug 2021 12:15:57 +0800 Subject: [PATCH 111/258] update some for app Signed-off-by: inhere --- src/AbstractApplication.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 61e73cdb..3f8f8c95 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -590,7 +590,7 @@ public function getInternalCommands(): array } /** - * @param $name + * @param string $name * * @return bool */ @@ -645,7 +645,7 @@ public function getConfig(): array * Get config param value * * @param string $name - * @param null|string $default + * @param null|string|mixed $default * * @return array|string */ From 3dc6e4e25e931f1959b62384661b06290000f552 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 6 Aug 2021 16:58:15 +0800 Subject: [PATCH 112/258] up: update some for input parse Signed-off-by: inhere --- resource/templates/bash-completion.tpl | 9 +- resource/templates/zsh-completion.tpl | 7 +- src/Component/AutoCompDumper.php | 12 -- src/Component/CompletionDumper.php | 15 ++ src/Component/ErrorHandler.php | 2 +- src/Component/Interact/IShell.php | 199 +++++++++++++++++++++++-- src/Component/Interact/Password.php | 3 +- src/Component/PharCompiler.php | 85 +++++------ src/Component/ReadlineCompleter.php | 109 ++++++++++++++ src/Concern/ApplicationHelpTrait.php | 19 ++- src/IO/AbstractInput.php | 16 ++ src/Util/Interact.php | 25 +--- test/IO/InputTest.php | 6 + 13 files changed, 410 insertions(+), 97 deletions(-) delete mode 100644 src/Component/AutoCompDumper.php create mode 100644 src/Component/CompletionDumper.php create mode 100644 src/Component/ReadlineCompleter.php diff --git a/resource/templates/bash-completion.tpl b/resource/templates/bash-completion.tpl index 60871d86..22fc35ad 100644 --- a/resource/templates/bash-completion.tpl +++ b/resource/templates/bash-completion.tpl @@ -1,12 +1,17 @@ #!/usr/bin/env bash # ------------------------------------------------------------------------------ # DATE: {{datetime}} -# FILE: auto-completion.bash +# FILE: {{filename}} # AUTHOR: inhere (https://github.com/inhere) # VERSION: {{version}} +# HOMEPAGE: https://github.com/inhere/php-console # DESCRIPTION: bash shell complete for console app: {{binName}} # ------------------------------------------------------------------------------ -# usage: source auto-completion.bash +# +# temp usage: +# source {{filename}} +# add to ~/.bashrc: +# source path/to/{{filename}} # run 'complete' to see registered complete function. _complete_for_{{fmtBinName}} () { diff --git a/resource/templates/zsh-completion.tpl b/resource/templates/zsh-completion.tpl index 6a62ff80..cc4ef76f 100644 --- a/resource/templates/zsh-completion.tpl +++ b/resource/templates/zsh-completion.tpl @@ -4,9 +4,14 @@ # FILE: {{filename}} # AUTHOR: inhere (https://github.com/inhere) # VERSION: {{version}} +# HOMEPAGE: https://github.com/inhere/php-console # DESCRIPTION: zsh shell complete for console app: {{binName}} # ------------------------------------------------------------------------------ -# usage: source auto-completion.zsh +# +# temp usage: +# source {{filename}} +# add to ~/.zshrc: +# source path/to/{{filename}} _complete_for_{{fmtBinName}} () { local -a commands diff --git a/src/Component/AutoCompDumper.php b/src/Component/AutoCompDumper.php deleted file mode 100644 index c0f2d532..00000000 --- a/src/Component/AutoCompDumper.php +++ /dev/null @@ -1,12 +0,0 @@ -getLine(); $file = $e->getFile(); - $snippet = Highlighter::create()->highlightSnippet(file_get_contents($file), $line, 3, 3); + $snippet = Highlighter::create()->snippet(file_get_contents($file), $line, 3, 3); $message = sprintf( $tpl, // $e->getCode(), $e->getMessage(), diff --git a/src/Component/Interact/IShell.php b/src/Component/Interact/IShell.php index 71dc2309..dbfda5a2 100644 --- a/src/Component/Interact/IShell.php +++ b/src/Component/Interact/IShell.php @@ -11,11 +11,15 @@ use InvalidArgumentException; use RuntimeException; use Throwable; +use Toolkit\Cli\Util\Readline; use Toolkit\Sys\Proc\ProcessUtil; use Toolkit\Sys\Proc\Signal; use function explode; +use function stripos; use function strlen; use function strpos; +use function substr; +use function trim; /** * Class IShell @@ -70,6 +74,39 @@ class IShell extends InteractiveHandle 'exit' => 1, ]; + /** + * @var bool + */ + private $hasPcntl = false; + + /** + * @var bool + */ + private $hasReadline = false; + + /** + * @var string + */ + private $historyFile = ''; + + /** + * @var int + */ + private $historySize = 1024; + + /** + * Auto complete handler + * + * ```php + * function (string $input, int $index) { + * // do something... + * } + * ``` + * + * @var callable + */ + private $autoCompleter; + /** * @param array $options * @@ -87,6 +124,8 @@ public static function new(array $options = []): self * @param array $options * - title * - prefix + * + * @throws Throwable */ public static function run(callable $handler, array $options = []): void { @@ -100,19 +139,32 @@ public static function run(callable $handler, array $options = []): void */ protected function registerSignal(): bool { - if (!($hasPcntl = ProcessUtil::hasPcntl())) { + $this->hasPcntl = ProcessUtil::hasPcntl(); + + if (!$this->hasPcntl) { $this->debugf('php is not enable "pcntl" extension, cannot listen CTRL+C signal'); } // register signal. - if ($hasPcntl) { + if ($this->hasPcntl) { ProcessUtil::installSignal(Signal::INT, static function () { Show::colored("\nQuit by CTRL+C"); exit(0); }); } - return $hasPcntl; + return $this->hasPcntl; + } + + protected function registerReadline(): void + { + $this->hasReadline = Readline::isSupported(); + if (!$this->hasReadline) { + return; + } + + Readline::loadHistory($this->historyFile); + Readline::registerCompleter([$this, 'autoCompleteHandle']); } protected function beforeStart(): void @@ -130,6 +182,8 @@ protected function beforeStart(): void /** * Start shell to run + * + * @throws Throwable */ public function start(): void { @@ -137,18 +191,43 @@ public function start(): void throw new RuntimeException('must be set the logic handler for start'); } - $prefix = $this->prefix; $this->beforeStart(); + $this->registerSignal(); + $this->registerReadline(); + + try { + $this->doRun($handler); + } catch (Throwable $e) { + throw new $e; + } finally { + if ($this->hasReadline) { + Readline::dumpHistory($this->historyFile); + } + } + + Show::colored("\nQuit. ByeBye!"); + } + + /** + * @param callable $handler + */ + public function doRun(callable $handler): void + { + $prefix = $this->prefix; - $hasPcntl = $this->registerSignal(); while (true) { - $line = Interact::readln("$prefix> "); + $line = Interact::readln("$prefix> "); // listen signal. - if ($hasPcntl) { + if ($this->hasPcntl) { ProcessUtil::dispatchSignal(); } + // has readline + if ($this->hasReadline) { + Readline::addHistory($line); + } + $state = $this->dispatch($line, $handler); if ($state === self::STOP) { break; @@ -156,8 +235,6 @@ public function start(): void Console::println(''); } - - Show::colored("\nQuit. ByeBye!"); } /** @@ -210,6 +287,45 @@ protected function dispatch(string $line, callable $handler): int return self::GOON; } + /** + * @param string $input + * @param int $index + * + * @return array|bool + */ + public function autoCompleteHandle(string $input, int $index) + { + // custom auto-completer + if ($fn = $this->autoCompleter) { + return $fn($input, $index); + } + + $commands = [ + '?', + 'help', + 'quit', + ]; + + $info = Readline::getInfo(); + $line = trim(substr($info['line_buffer'], 0, $info['end'])); + if ($info['point'] !== $info['end']) { + return true; + } + + $founded = []; + + // $line=$input completion for top command name prefix. + if (strpos($line, ' ') === false) { + foreach ($commands as $name) { + if (stripos($name, $input)) { + $founded[] = $name; + } + } + } // else // completion for subcommand + + return $founded ?: $commands; + } + /** * @param array $moreKeys */ @@ -262,7 +378,7 @@ public function debugf(string $format, ...$args): void * * @return IShell */ - public function setHandler(callable $handler): IShell + public function setHandler(callable $handler): self { $this->handler = $handler; return $this; @@ -273,7 +389,7 @@ public function setHandler(callable $handler): IShell * * @return IShell */ - public function setDebug(bool $debug): IShell + public function setDebug(bool $debug): self { $this->debug = $debug; return $this; @@ -284,7 +400,7 @@ public function setDebug(bool $debug): IShell * * @return IShell */ - public function setExitKeys(array $exitKeys): IShell + public function setExitKeys(array $exitKeys): self { $this->exitKeys = $exitKeys; return $this; @@ -295,7 +411,7 @@ public function setExitKeys(array $exitKeys): IShell * * @return IShell */ - public function setTitle(string $title): IShell + public function setTitle(string $title): self { $this->title = $title; return $this; @@ -306,7 +422,7 @@ public function setTitle(string $title): IShell * * @return IShell */ - public function setPrefix(string $prefix): IShell + public function setPrefix(string $prefix): self { $this->prefix = $prefix; return $this; @@ -317,7 +433,7 @@ public function setPrefix(string $prefix): IShell * * @return IShell */ - public function setHelpHandler(callable $helpHandler): IShell + public function setHelpHandler(callable $helpHandler): self { $this->helpHandler = $helpHandler; return $this; @@ -328,9 +444,60 @@ public function setHelpHandler(callable $helpHandler): IShell * * @return IShell */ - public function setErrorHandler(callable $errorHandler): IShell + public function setErrorHandler(callable $errorHandler): self { $this->errorHandler = $errorHandler; return $this; } + + /** + * @return callable + */ + public function getAutoCompleter(): callable + { + return $this->autoCompleter; + } + + /** + * @param callable $autoCompleter + * + * @return IShell + */ + public function setAutoCompleter(callable $autoCompleter): self + { + $this->autoCompleter = $autoCompleter; + return $this; + } + + /** + * @return string + */ + public function getHistoryFile(): string + { + return $this->historyFile; + } + + /** + * @param string $historyFile + */ + public function setHistoryFile(string $historyFile): void + { + $this->historyFile = $historyFile; + } + + /** + * @return int + */ + public function getHistorySize(): int + { + return $this->historySize; + } + + /** + * @param int $historySize + */ + public function setHistorySize(int $historySize): void + { + $this->historySize = $historySize; + } } diff --git a/src/Component/Interact/Password.php b/src/Component/Interact/Password.php index 08647c22..6e88ab61 100644 --- a/src/Component/Interact/Password.php +++ b/src/Component/Interact/Password.php @@ -5,6 +5,7 @@ use Inhere\Console\Component\InteractiveHandle; use RuntimeException; use Toolkit\Sys\Sys; +use Toolkit\Sys\Util\ShellUtil; use function addslashes; use function escapeshellarg; use function file_put_contents; @@ -40,7 +41,7 @@ public static function ask(string $prompt = 'Enter Password:'): string // $shell = 'echo $0'; // linux, unix, git-bash - if (Sys::shIsAvailable()) { + if (ShellUtil::shIsAvailable()) { // COMMAND: sh -c 'read -p "Enter Password:" -s user_input && echo $user_input' $command = sprintf('sh -c "read -p \'%s\' -s user_input && echo $user_input"', $prompt); $password = Sys::execute($command, false); diff --git a/src/Component/PharCompiler.php b/src/Component/PharCompiler.php index c8db34e0..ee9839b4 100644 --- a/src/Component/PharCompiler.php +++ b/src/Component/PharCompiler.php @@ -16,6 +16,7 @@ use Seld\PharUtils\Timestamps; use SplFileInfo; use SplQueue; +use Toolkit\FsUtil\File; use Toolkit\Sys\Sys; use UnexpectedValueException; use function array_merge; @@ -26,8 +27,6 @@ use function file_exists; use function file_get_contents; use function file_put_contents; -use function function_exists; -use function in_array; use function ini_get; use function is_dir; use function is_file; @@ -41,36 +40,33 @@ use function strpos; use function substr; use function substr_replace; -use function token_get_all; use function trim; use function unlink; use const DIRECTORY_SEPARATOR; use const PHP_EOL; -use const T_COMMENT; -use const T_DOC_COMMENT; -use const T_WHITESPACE; /** * Class PharCompiler + * * @since 1.0 */ class PharCompiler { - public const ON_ADD = 'add'; + public const ON_ADD = 'add'; - public const ADD_CLI_INDEX = 'add.index.cli'; - public const ADD_WEB_INDEX = 'add.index.web'; - - public const ON_SKIP = 'skip'; + public const ON_SKIP = 'skip'; public const ON_ERROR = 'error'; - public const ON_MESSAGE = 'message'; + public const ON_MESSAGE = 'message'; public const ON_COLLECTED = 'collected'; public const FILE_EXT = '.phar'; + public const ADD_CLI_INDEX = 'add.index.cli'; + public const ADD_WEB_INDEX = 'add.index.web'; + /** @var array */ private static $supportedSignatureTypes = [ Phar::SHA512 => 1, @@ -177,6 +173,7 @@ class PharCompiler * 'lastVersion' => '{@package_last_version}', * 'releaseDate' => '{@release_date}', * ] + * * @var string */ private $versionFile = ''; @@ -218,6 +215,7 @@ class PharCompiler * @param string $extractTo * @param string|array|null $files Only fetch the listed files * @param bool $overwrite + * * @return bool * @throws BadMethodCallException * @throws RuntimeException @@ -249,7 +247,9 @@ private static function checkEnv(): void /** * PharCompiler constructor. + * * @param string $basePath + * * @throws RuntimeException */ public function __construct(string $basePath) @@ -266,6 +266,7 @@ public function __construct(string $basePath) /** * @param string|array $files + * * @return $this */ public function addFile($files): self @@ -276,6 +277,7 @@ public function addFile($files): self /** * @param array $files + * * @return PharCompiler */ public function setFiles(array $files): self @@ -286,6 +288,7 @@ public function setFiles(array $files): self /** * @param string|array $suffixes + * * @return $this */ public function addSuffix($suffixes): self @@ -296,6 +299,7 @@ public function addSuffix($suffixes): self /** * @param string|array $patterns + * * @return $this */ public function addExclude($patterns): self @@ -306,6 +310,7 @@ public function addExclude($patterns): self /** * @param string|array $patterns + * * @return $this */ public function addExcludeDir($patterns): self @@ -321,6 +326,7 @@ public function addExcludeDir($patterns): self /** * @param string|array $patterns + * * @return $this */ public function addExcludeFile($patterns): self @@ -336,6 +342,7 @@ public function addExcludeFile($patterns): self /** * @param array $excludes + * * @return PharCompiler */ public function setExcludes(array $excludes): self @@ -346,6 +353,7 @@ public function setExcludes(array $excludes): self /** * @param bool $value + * * @return PharCompiler */ public function stripComments($value): self @@ -356,6 +364,7 @@ public function stripComments($value): self /** * @param bool $value + * * @return PharCompiler */ public function collectVersion($value): self @@ -366,6 +375,7 @@ public function collectVersion($value): self /** * @param Closure $stripFilter + * * @return PharCompiler */ public function setStripFilter(Closure $stripFilter): self @@ -376,6 +386,7 @@ public function setStripFilter(Closure $stripFilter): self /** * @param bool|string $shebang + * * @return PharCompiler */ public function setShebang($shebang): self @@ -386,6 +397,7 @@ public function setShebang($shebang): self /** * @param string|array $dirs + * * @return PharCompiler */ public function in($dirs): self @@ -407,8 +419,10 @@ public function setModifies($modifies): self /** * Compiles composer into a single phar file - * @param string $pharFile The full path to the file to create - * @param bool $refresh + * + * @param string $pharFile The full path to the file to create + * @param bool $refresh + * * @return string * @throws UnexpectedValueException * @throws BadMethodCallException @@ -561,6 +575,7 @@ protected function collectFileInfo(SplFileInfo $file): void /** * find changed or new created files by git status. + * * @throws RuntimeException */ public function findChangedByGit() @@ -763,40 +778,19 @@ private function getIteratorFilter(): Closure /** * Removes whitespace from a PHP source string while preserving line numbers. - * @param string $source A PHP string + * + * @param string $source A PHP string + * * @return string The PHP string with the whitespace removed */ private function stripWhitespace(string $source): string { - if (!function_exists('token_get_all')) { - return $source; - } - - $output = ''; - foreach (token_get_all($source) as $token) { - if (is_string($token)) { - $output .= $token; - } elseif (in_array($token[0], [T_COMMENT, T_DOC_COMMENT], true)) { - $output .= str_repeat("\n", substr_count($token[1], "\n")); - } elseif (T_WHITESPACE === $token[0]) { - // reduce wide spaces - $whitespace = preg_replace('{[ \t]+}', ' ', $token[1]); - // normalize newlines to \n - $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace); - // trim leading spaces - $whitespace = preg_replace('{\n +}', "\n", $whitespace); - // append - $output .= $whitespace; - } else { - $output .= $token[1]; - } - } - - return $output; + return File::stripPhpCode($source); } /** * Auto collect project information by git log + * * @throws RuntimeException * @throws Exception */ @@ -839,7 +833,8 @@ private function collectInformation(): void } /** - * @param SplFileInfo $file + * @param SplFileInfo $file + * * @return string */ private function getRelativeFilePath(SplFileInfo $file): string @@ -871,7 +866,8 @@ private function fire(string $event, ...$args) /** * add event handler - * @param string $event + * + * @param string $event * @param Closure $closure */ public function on(string $event, Closure $closure): void @@ -937,6 +933,7 @@ public function getCliIndex(): string /** * @param string $cliIndex + * * @return $this */ public function setCliIndex(string $cliIndex): self @@ -955,6 +952,7 @@ public function getWebIndex(): string /** * @param null|string $webIndex + * * @return PharCompiler */ public function setWebIndex(string $webIndex): self @@ -981,6 +979,7 @@ public function getVersionFile(): string /** * @param string $versionFile + * * @return PharCompiler */ public function setVersionFile(string $versionFile): PharCompiler @@ -999,6 +998,7 @@ public function getLastCommit(): string /** * @param null|string $lastCommit + * * @return PharCompiler */ public function setLastCommit(string $lastCommit): PharCompiler @@ -1017,6 +1017,7 @@ public function getLastVersion(): string /** * @param null|string $lastVersion + * * @return PharCompiler */ public function setLastVersion(string $lastVersion): PharCompiler diff --git a/src/Component/ReadlineCompleter.php b/src/Component/ReadlineCompleter.php new file mode 100644 index 00000000..6fd008cf --- /dev/null +++ b/src/Component/ReadlineCompleter.php @@ -0,0 +1,109 @@ +completer = $completer; + } + + /** + * @return array + */ + public function listHistory(): array + { + return Readline::listHistory(); + } + + /** + * @return bool + */ + public function loadHistory(): bool + { + if ($this->historyFile) { + return Readline::loadHistory($this->historyFile); + } + + return false; + } + + /** + * @return bool + */ + public function dumpHistory(): bool + { + if ($this->historyFile) { + return Readline::dumpHistory($this->historyFile); + } + + return false; + } + + /** + * @return string + */ + public function getHistoryFile(): string + { + return $this->historyFile; + } + + /** + * @param string $historyFile + */ + public function setHistoryFile(string $historyFile): void + { + $this->historyFile = $historyFile; + } + + /** + * @return int + */ + public function getHistorySize(): int + { + return $this->historySize; + } + + /** + * @param int $historySize + */ + public function setHistorySize(int $historySize): void + { + $this->historySize = $historySize; + } +} diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index 9ae9cace..ed016c0b 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -22,6 +22,7 @@ use function basename; use function date; use function dirname; +use function file_exists; use function file_get_contents; use function file_put_contents; use function get_class; @@ -32,7 +33,9 @@ use function ksort; use function sprintf; use function str_replace; +use function strpos; use function strtr; +use function vdump; use const PHP_EOL; use const PHP_OS; use const PHP_VERSION; @@ -128,7 +131,7 @@ public function showHelpInfo(string $command = ''): void ], 'Help' => [ 'Generate shell auto completion scripts:', - " $binName --auto-completion --shell-env [zsh|bash] [--gen-file stdout]", + " $binName --auto-completion --shell-env [zsh|bash] [--gen-file stdout] [--tpl-file filepath]", ' eg:', " $binName --auto-completion --shell-env bash --gen-file stdout", " $binName --auto-completion --shell-env zsh --gen-file stdout", @@ -148,6 +151,10 @@ public function showCommandList(): void $autoComp = $input->getBoolOpt('auto-completion'); // has option: --shell-env $shellEnv = (string)$input->getLongOpt('shell-env', ''); + // input is an path: /bin/bash + if ($shellEnv && strpos($shellEnv, '/') !== false) { + $shellEnv = basename($shellEnv); + } // php bin/app list --only-name if ($autoComp && $shellEnv === 'bash') { @@ -306,6 +313,12 @@ protected function dumpAutoCompletion(string $shellEnv, array $data): void } } + // new: support custom tpl file for gen completion script + $userTplFile = $input->getStringOpt('tpl-file'); + if ($userTplFile && file_exists($userTplFile)) { + $tplFile = $userTplFile; + } + $commands = implode($glue, $list); // only dump commands to stdout. @@ -352,9 +365,9 @@ protected function dumpAutoCompletion(string $shellEnv, array $data): void $output->write(['Target File:', $targetFile, '']); if (file_put_contents($targetFile, $content) > 10) { - $output->success("O_O! Generate file:$filename successful!"); + $output->success("O_O! Generate completion file '$filename' successful!"); } else { - $output->error("O^O! Generate file:$filename failure!"); + $output->error("O^O! Generate completion file '$filename' failure!"); } } } diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index 31261729..bf240986 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -233,6 +233,22 @@ public function setFullScript(string $fullScript): void $this->fullScript = $fullScript; } + /** + * @return array + */ + public function getFlags(): array + { + return $this->flags; + } + + /** + * @return array + */ + public function getRawArgs(): array + { + return $this->tokens; + } + /** * @return array */ diff --git a/src/Util/Interact.php b/src/Util/Interact.php index 9cd44c12..ee6a196f 100644 --- a/src/Util/Interact.php +++ b/src/Util/Interact.php @@ -20,7 +20,6 @@ use Toolkit\Cli\Cli; use function sprintf; use function strtolower; -use function trim; /** * Class Interact @@ -41,15 +40,9 @@ class Interact extends Show * * @return string */ - public static function readln($message = null, $nl = false, array $opts = []): string + public static function readln($message = null, bool $nl = false, array $opts = []): string { - if ($message) { - Console::write($message, $nl); - } - - $stream = $opts['stream'] ?? Cli::getInputStream(); - - return trim((string)fgets($stream)); + return Cli::readln($message, $nl, $opts); } /** @@ -60,9 +53,9 @@ public static function readln($message = null, $nl = false, array $opts = []): s * * @return string */ - public static function readRow($message = null, $nl = false): string + public static function readRow($message = null, bool $nl = false): string { - return self::readln($message, $nl); + return Cli::readln($message, $nl); } /** @@ -71,15 +64,9 @@ public static function readRow($message = null, $nl = false): string * * @return string */ - public static function readFirst($message = null, $nl = false): string + public static function readFirst($message = null, bool $nl = false): string { - $input = self::readln($message, $nl); - - if ($input && ($f = $input[0])) { - return $f; - } - - return ''; + return Cli::readFirst($message, $nl); } /************************************************************************************************** diff --git a/test/IO/InputTest.php b/test/IO/InputTest.php index a6fe4db8..819a1f5f 100644 --- a/test/IO/InputTest.php +++ b/test/IO/InputTest.php @@ -4,6 +4,7 @@ use Inhere\Console\IO\Input; use PHPUnit\Framework\TestCase; +use function vdump; /** * Class InputTest @@ -28,6 +29,11 @@ public function testArguments(): void $this->assertTrue($in->hasArg(0)); $this->assertSame('val0', $in->getArgument(0)); $this->assertSame('val1', $in->getArgument(1)); + + $in = new Input(["bin/kite", "jump", "get", "-"]); + $this->assertTrue($in->hasArg(0)); + $this->assertSame('get', $in->getArgument(0)); + $this->assertSame('-', $in->getArgument(1)); } public function testBindArgument(): void From 99966d79b3617cd035865528f26b2b41b9b91555 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 6 Aug 2021 22:48:42 +0800 Subject: [PATCH 113/258] up: update some output format and input parse Signed-off-by: inhere --- src/AbstractApplication.php | 30 +++- src/Application.php | 7 +- src/Component/CompletionDumper.php | 6 + src/Component/ConsoleAppIShell.php | 21 +++ src/Component/Interact/Confirm.php | 3 - src/Concern/ApplicationHelpTrait.php | 2 + src/Concern/ControllerHelpTrait.php | 168 ++++++++++++++++++++++ src/Concern/FormatOutputAwareTrait.php | 4 +- src/Concern/StyledOutputAwareTrait.php | 2 +- src/Controller.php | 187 +++---------------------- src/IO/AbstractInput.php | 6 +- src/IO/Input.php | 17 ++- src/IO/Input/ArrayInput.php | 2 + src/IO/Input/StringInput.php | 3 +- src/Util/Interact.php | 14 +- 15 files changed, 281 insertions(+), 191 deletions(-) create mode 100644 src/Component/ConsoleAppIShell.php diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 3f8f8c95..5b3f0a0d 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -293,13 +293,41 @@ public function stop(int $code = 0) */ public function subRun(string $command, InputInterface $input, OutputInterface $output) { - $app = clone $this; + $app = $this->copy(); $app->setInput($input); $app->setOutput($output); + $this->debugf('copy application and run command(%s) with new input, output', $command); + return $app->dispatch($command); } + /** + * @param InputInterface $input + * @param OutputInterface $output + */ + public function runWithIO(InputInterface $input, OutputInterface $output): void + { + $app = $this->copy(); + $app->setInput($input); + $app->setOutput($output); + + $this->debugf('copy application and run with new input, output'); + $app->run(false); + } + + /** + * @return $this + */ + public function copy(): self + { + $app = clone $this; + // reset something + $app->groupObjects = []; + + return $app; + } + /********************************************************** * helper method for the application **********************************************************/ diff --git a/src/Application.php b/src/Application.php index a0f76029..8c528c53 100644 --- a/src/Application.php +++ b/src/Application.php @@ -355,11 +355,12 @@ protected function runCommand(string $name, $handler, array $options) /** * Execute an action in a group command(controller) * - * @param array $info Matched route info - * @param array $options - * @param bool $detachedRun + * @param array $info Matched route info + * @param array $options + * @param bool $detachedRun * * @return mixed + * @throws ReflectionException */ protected function runAction(array $info, array $options, bool $detachedRun = false) { diff --git a/src/Component/CompletionDumper.php b/src/Component/CompletionDumper.php index f80a8c72..0824b910 100644 --- a/src/Component/CompletionDumper.php +++ b/src/Component/CompletionDumper.php @@ -2,6 +2,7 @@ namespace Inhere\Console\Component; +use Inhere\Console\Application; use Toolkit\Stdlib\Obj; /** @@ -12,4 +13,9 @@ */ class CompletionDumper extends Obj\AbstractObj { + /** + * @var Application + */ + public $app; + } diff --git a/src/Component/ConsoleAppIShell.php b/src/Component/ConsoleAppIShell.php new file mode 100644 index 00000000..49bb0c99 --- /dev/null +++ b/src/Component/ConsoleAppIShell.php @@ -0,0 +1,21 @@ +debugf('display command help by use help: help COMMAND'); $in->setCommand($command); $in->setSOpt('h', true); $in->clearArgs(); @@ -100,6 +101,7 @@ public function showHelpInfo(string $command = ''): void return; } + $this->debugf('display application help by input -h, --help'); $delimiter = $this->delimiter; $binName = $in->getScriptName(); diff --git a/src/Concern/ControllerHelpTrait.php b/src/Concern/ControllerHelpTrait.php index 03ca6ab7..b766807f 100644 --- a/src/Concern/ControllerHelpTrait.php +++ b/src/Concern/ControllerHelpTrait.php @@ -2,6 +2,22 @@ namespace Inhere\Console\Concern; +use Inhere\Console\Application; +use Inhere\Console\Console; +use Inhere\Console\Util\FormatUtil; +use ReflectionClass; +use Toolkit\Cli\ColorTag; +use Toolkit\Stdlib\Str; +use Toolkit\Stdlib\Util\PhpDoc; +use function array_merge; +use function implode; +use function ksort; +use function lcfirst; +use function sprintf; +use function strpos; +use function ucfirst; +use const PHP_EOL; + /** * Trait ControllerHelpTrait * @@ -9,4 +25,156 @@ */ trait ControllerHelpTrait { + /** + * Show help of the controller command group or specified command action + * @usage {name}:[command] -h OR {command} [command] OR {name} [command] -h + * + * @options + * -s, --search Search command by input keywords + * --format Set the help information dump format(raw, xml, json, markdown) + * @return int + * @example + * {script} {name} -h + * {script} {name}:help + * {script} {name}:help index + * {script} {name}:index -h + * {script} {name} index + */ + public function helpCommand(): int + { + $action = $this->action; + + // Not input action, for all sub-commands of the controller + if (!$action) { + $this->showCommandList(); + return 0; + } + + $action = Str::camelCase($action); + $method = $this->actionSuffix ? $action . ucfirst($this->actionSuffix) : $action; + $aliases = $this->getCommandAliases($action); + + // up: find global aliases from app + if ($this->app) { + $commandId = $this->input->getCommandId(); + $gAliases = $this->app->getAliases($commandId); + + if ($gAliases) { + $aliases = array_merge($aliases, $gAliases); + } + } + + $this->log(Console::VERB_DEBUG, "display help for the controller method: $method", [ + 'group' => static::$name, + 'action' => $action, + ]); + + // For a specified sub-command. + return $this->showHelpByMethodAnnotations($method, $action, $aliases); + } + + protected function beforeShowCommandList(): void + { + // do something ... + } + + /** + * Display all sub-commands list of the controller class + */ + public function showCommandList(): void + { + $this->logf(Console::VERB_DEBUG, 'display all sub-commands list of the group: %s', static::$name); + + $this->beforeShowCommandList(); + + $ref = new ReflectionClass($this); + $sName = lcfirst(self::getName() ?: $ref->getShortName()); + + if (!($classDes = self::getDescription())) { + $classDes = PhpDoc::description($ref->getDocComment()) ?: 'No description for the command group'; + } + + $commands = []; + $showDisabled = (bool)$this->getOpt('show-disabled', false); + $defaultDes = 'No description message'; + + /** + * @var $cmd string The command name. + */ + foreach ($this->getAllCommandMethods($ref) as $cmd => $m) { + if (!$cmd) { + continue; + } + + $desc = $this->getCommandMeta('desc', $defaultDes, $cmd); + if ($phpDoc = $m->getDocComment()) { + $desc = PhpDoc::firstLine($phpDoc); + } + + // is a annotation tag + if (strpos($desc, '@') === 0) { + $desc = $defaultDes; + } + + if ($this->isDisabled($cmd)) { + if (!$showDisabled) { + continue; + } + + $desc .= '(DISABLED)'; + } + + $aliases = $this->getCommandAliases($cmd); + $desc .= $aliases ? ColorTag::wrap(' (alias: ' . implode(',', $aliases) . ')', 'info') : ''; + + $commands[$cmd] = $desc; + } + + // sort commands + ksort($commands); + + // move 'help' to last. + if ($helpCmd = $commands['help'] ?? null) { + unset($commands['help']); + $commands['help'] = $helpCmd; + } + + $script = $this->getScriptName(); + + // if is alone running. + if ($detached = $this->isDetached()) { + $name = $sName . ' '; + $usage = "$script {command} [--options ...] [arguments ...]"; + } else { + $name = $sName . $this->delimiter; + // $usage = "$script {$name}{command} [--options ...] [arguments ...]"; + $usage = [ + "$script $name{command} [--options ...] [arguments ...]", + "$script $sName {command} [--options ...] [arguments ...]", + ]; + } + + $globalOptions = array_merge(Application::getGlobalOptions(), static::$globalOptions); + + $this->output->startBuffer(); + $this->output->write(ucfirst($classDes) . PHP_EOL); + + if ($aliases = $this->getAliases()) { + $this->output->writef("Alias: %s\n", implode(',', $aliases)); + } + + $this->output->mList([ + 'Usage:' => $usage, + //'Group Name:' => "$sName", + 'Global Options:' => FormatUtil::alignOptions($globalOptions), + 'Available Commands:' => $commands, + ], [ + 'sepChar' => ' ', + ]); + + $msgTpl = 'More information about a command, please see: %s %s {command} -h'; + $this->output->write(sprintf($msgTpl, $script, $detached ? '' : $sName)); + $this->output->flush(); + } + } diff --git a/src/Concern/FormatOutputAwareTrait.php b/src/Concern/FormatOutputAwareTrait.php index ab42ff9c..3c737441 100644 --- a/src/Concern/FormatOutputAwareTrait.php +++ b/src/Concern/FormatOutputAwareTrait.php @@ -71,7 +71,7 @@ public function writeln($text, $quit = false, array $opts = []): int * * @return int */ - public function println($text, $quit = false, array $opts = []): int + public function println($text, bool $quit = false, array $opts = []): int { return Console::writeln($text, $quit, $opts); } @@ -79,7 +79,7 @@ public function println($text, $quit = false, array $opts = []): int /** * @param string|mixed $text * @param bool $nl - * @param bool $quit + * @param bool|int $quit * @param array $opts * * @return int diff --git a/src/Concern/StyledOutputAwareTrait.php b/src/Concern/StyledOutputAwareTrait.php index fc5cef41..817d44f8 100644 --- a/src/Concern/StyledOutputAwareTrait.php +++ b/src/Concern/StyledOutputAwareTrait.php @@ -205,7 +205,7 @@ public function progressBar($total, array $opts = []): Generator * @return int * @throws LogicException */ - public function __call($method, array $args = []) + public function __call(string $method, array $args = []) { $map = Show::getBlockMethods(false); diff --git a/src/Controller.php b/src/Controller.php index af5dbdd9..46c58639 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -17,27 +17,19 @@ use Inhere\Console\Util\FormatUtil; use Inhere\Console\Util\Helper; use ReflectionClass; -use ReflectionException; use ReflectionObject; use RuntimeException; -use Toolkit\Cli\ColorTag; use Toolkit\Stdlib\Str; -use Toolkit\Stdlib\Util\PhpDoc; use function array_flip; use function array_keys; -use function array_merge; use function implode; use function is_array; use function is_string; -use function ksort; -use function lcfirst; use function method_exists; use function sprintf; -use function strpos; use function substr; use function trim; use function ucfirst; -use const PHP_EOL; /** * Class Controller @@ -103,6 +95,13 @@ abstract class Controller extends AbstractHandler implements ControllerInterface */ private $disabledCommands = []; + /** + * TODO ... + * + * @var array + */ + private $attachedCommands = []; + /** * Metadata for sub-commands. such as: desc, alias * Notice: you must add metadata on `init()` @@ -225,7 +224,6 @@ protected function beforeRun(): void * @param string $command command in the group * * @return int|mixed - * @throws ReflectionException */ public function run(string $command = '') { @@ -233,7 +231,7 @@ public function run(string $command = '') // if not input sub-command, render group help. if (!$command) { - $this->debugf('sub-command is empty, display help for the group: %s', self::getName()); + $this->debugf('not input subcommand, display help for the group: %s', self::getName()); return $this->showHelp(); } @@ -247,7 +245,7 @@ public function run(string $command = '') $this->setCommentsVar('binWithCmd', $this->input->getBinWithCommand()); // get real sub-command name - $command = $this->getRealCommandName($command); + $command = $this->resolveAlias($command); // convert 'boo-foo' to 'booFoo' $this->action = $action = Str::camelCase($command); @@ -410,7 +408,6 @@ protected function getMethodName(string $action): string /** * @return bool - * @throws ReflectionException */ protected function showHelp(): bool { @@ -445,159 +442,6 @@ public function getGroupOptions(): array return $this->groupOptions; } - /** - * Show help of the controller command group or specified command action - * @usage {name}:[command] -h OR {command} [command] OR {name} [command] -h - * - * @options - * -s, --search Search command by input keywords - * --format Set the help information dump format(raw, xml, json, markdown) - * @return int - * @throws ReflectionException - * @example - * {script} {name} -h - * {script} {name}:help - * {script} {name}:help index - * {script} {name}:index -h - * {script} {name} index - */ - final public function helpCommand(): int - { - $action = $this->action; - - // Not input action, for all sub-commands of the controller - if (!$action) { - $this->showCommandList(); - return 0; - } - - $action = Str::camelCase($action); - $method = $this->actionSuffix ? $action . ucfirst($this->actionSuffix) : $action; - $aliases = $this->getCommandAliases($action); - - // up: find global aliases from app - if ($this->app) { - $commandId = $this->input->getCommandId(); - $gAliases = $this->app->getAliases($commandId); - - if ($gAliases) { - $aliases = array_merge($aliases, $gAliases); - } - } - - $this->log(Console::VERB_DEBUG, "display help for the controller method: $method", [ - 'group' => static::$name, - 'action' => $action, - ]); - - // For a specified sub-command. - return $this->showHelpByMethodAnnotations($method, $action, $aliases); - } - - protected function beforeShowCommandList(): void - { - // do something ... - } - - /** - * Display all sub-commands list of the controller class - */ - final public function showCommandList(): void - { - $this->logf(Console::VERB_DEBUG, 'display all sub-commands list of the group: %s', static::$name); - - $this->beforeShowCommandList(); - - $ref = new ReflectionClass($this); - $sName = lcfirst(self::getName() ?: $ref->getShortName()); - - if (!($classDes = self::getDescription())) { - $classDes = PhpDoc::description($ref->getDocComment()) ?: 'No description for the command group'; - } - - $commands = []; - $showDisabled = (bool)$this->getOpt('show-disabled', false); - $defaultDes = 'No description message'; - - /** - * @var $cmd string The command name. - */ - foreach ($this->getAllCommandMethods($ref) as $cmd => $m) { - if (!$cmd) { - continue; - } - - $desc = $this->getCommandMeta('desc', $defaultDes, $cmd); - if ($phpDoc = $m->getDocComment()) { - $desc = PhpDoc::firstLine($phpDoc); - } - - // is a annotation tag - if (strpos($desc, '@') === 0) { - $desc = $defaultDes; - } - - if ($this->isDisabled($cmd)) { - if (!$showDisabled) { - continue; - } - - $desc .= '[DISABLED]'; - } - - $aliases = $this->getCommandAliases($cmd); - $desc .= $aliases ? ColorTag::wrap(' [alias: ' . implode(',', $aliases) . ']', 'info') : ''; - - $commands[$cmd] = $desc; - } - - // sort commands - ksort($commands); - - // move 'help' to last. - if ($helpCmd = $commands['help'] ?? null) { - unset($commands['help']); - $commands['help'] = $helpCmd; - } - - $script = $this->getScriptName(); - - // if is alone running. - if ($detached = $this->isDetached()) { - $name = $sName . ' '; - $usage = "$script {command} [--options ...] [arguments ...]"; - } else { - $name = $sName . $this->delimiter; - // $usage = "$script {$name}{command} [--options ...] [arguments ...]"; - $usage = [ - "$script $name{command} [--options ...] [arguments ...]", - "$script $sName {command} [--options ...] [arguments ...]", - ]; - } - - $globalOptions = array_merge(Application::getGlobalOptions(), static::$globalOptions); - - $this->output->startBuffer(); - $this->output->write(ucfirst($classDes) . PHP_EOL); - - if ($aliases = $this->getAliases()) { - $this->output->writef("Alias: %s\n", implode(',', $aliases)); - } - - $this->output->mList([ - 'Usage:' => $usage, - //'Group Name:' => "$sName", - 'Global Options:' => FormatUtil::alignOptions($globalOptions), - 'Available Commands:' => $commands, - ], [ - 'sepChar' => ' ', - ]); - - $msgTpl = 'More information about a command, please see: %s %s {command} -h'; - $this->output->write(sprintf($msgTpl, $script, $detached ? '' : $sName)); - $this->output->flush(); - } - /** * @param ReflectionClass|null $ref * @param bool $onlyName @@ -631,8 +475,19 @@ protected function getAllCommandMethods(ReflectionClass $ref = null, bool $onlyN * @param string $name * * @return string + * @description please use resolveAlias() */ public function getRealCommandName(string $name): string + { + return $this->resolveAlias($name); + } + + /** + * @param string $name + * + * @return string + */ + public function resolveAlias(string $name): string { if (!$name) { return ''; @@ -801,7 +656,7 @@ public function getCommandMetas(): array /** * @param string $command - * @param array $meta eg: ['desc' => '', 'alias' => []] + * @param array $meta eg: ['desc' => '', 'alias' => []] */ public function setCommandMeta(string $command, array $meta): void { diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index bf240986..4e56d99a 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -27,7 +27,7 @@ abstract class AbstractInput implements InputInterface /** * @var string */ - protected $pwd; + protected $pwd = ''; /** * The script path @@ -35,7 +35,7 @@ abstract class AbstractInput implements InputInterface * * @var string */ - protected $script; + protected $script = ''; /** * The script name @@ -43,7 +43,7 @@ abstract class AbstractInput implements InputInterface * * @var string */ - protected $scriptName; + protected $scriptName = ''; /** * the command name(Is first argument) diff --git a/src/IO/Input.php b/src/IO/Input.php index 7f422378..d3896115 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -16,6 +16,7 @@ use function fgets; use function fwrite; use function implode; +use function is_string; use function preg_match; use function trim; @@ -67,13 +68,21 @@ public function __construct(array $args = null, bool $parsing = true) protected function collectInfo(array $args): void { $this->getPwd(); + if (!$args) { + return; + } $this->tokens = $args; - $this->script = array_shift($args); - $this->flags = $args; // no script - // bin name - $this->scriptName = basename($this->script); + // first is bin file + if (isset($args[0]) && is_string($args[0])) { + $this->script = array_shift($args); + + // bin name + $this->scriptName = basename($this->script); + } + + $this->flags = $args; // no script // full script $this->fullScript = implode(' ', $args); diff --git a/src/IO/Input/ArrayInput.php b/src/IO/Input/ArrayInput.php index c1dabbf2..0221e967 100644 --- a/src/IO/Input/ArrayInput.php +++ b/src/IO/Input/ArrayInput.php @@ -28,6 +28,8 @@ public function __construct(array $args = null, bool $parsing = true) { parent::__construct([], false); + $this->collectInfo($args); + if ($parsing && $args) { $this->doParse($this->flags); } diff --git a/src/IO/Input/StringInput.php b/src/IO/Input/StringInput.php index 50e0b89c..f9f55209 100644 --- a/src/IO/Input/StringInput.php +++ b/src/IO/Input/StringInput.php @@ -32,7 +32,8 @@ public function __construct(string $line, bool $parsing = true) if ($parsing && $line) { $flags = LineParser::parseIt($line); - $this->doParse($flags); + $this->collectInfo($flags); + $this->doParse($this->flags); } } diff --git a/src/Util/Interact.php b/src/Util/Interact.php index ee6a196f..b255af9f 100644 --- a/src/Util/Interact.php +++ b/src/Util/Interact.php @@ -207,9 +207,9 @@ public static function answerIsYes(bool $default = null): bool /** * alias of the `question()` * - * @param string $question question message - * @param string $default default value - * @param Closure $validator The validate callback. It must return bool. + * @param string $question question message + * @param string $default default value + * @param Closure|null $validator The validate callback. It must return bool. * * @return string|null */ @@ -236,10 +236,10 @@ public static function question(string $question, string $default = '', Closure /** * Ask a question, ask for a limited number of times * - * @param string $question 问题 - * @param string $default 默认值 - * @param Closure $validator (默认验证输入是否为空)自定义回调验证输入是否符合要求; 验证成功返回true 否则 可返回错误消息 - * @param int $times Allow input times + * @param string $question 问题 + * @param string $default 默认值 + * @param Closure|null $validator (默认验证输入是否为空)自定义回调验证输入是否符合要求; 验证成功返回true 否则 可返回错误消息 + * @param int $times Allow input times * * @return string * @see LimitedAsk::ask() From 412b8dace83b43fc45556dd62baacfb8449753a5 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 6 Aug 2021 23:30:29 +0800 Subject: [PATCH 114/258] style: update some comments Signed-off-by: inhere --- src/Concern/ApplicationHelpTrait.php | 3 +-- src/Concern/SimpleEventAwareTrait.php | 14 +++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index 053a7411..2432e202 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -35,7 +35,6 @@ use function str_replace; use function strpos; use function strtr; -use function vdump; use const PHP_EOL; use const PHP_OS; use const PHP_VERSION; @@ -198,7 +197,7 @@ public function showCommandList(): void /** @var AbstractHandler $controller */ $desc = $controller::getDescription() ?: $placeholder; $aliases = $options['aliases']; - $extra = $aliases ? ColorTag::wrap(' [alias: ' . implode(',', $aliases) . ']', 'info') : ''; + $extra = $aliases ? ColorTag::wrap(' (alias: ' . implode(',', $aliases) . ')', 'info') : ''; // collect $groupArr[$name] = $desc . $extra; diff --git a/src/Concern/SimpleEventAwareTrait.php b/src/Concern/SimpleEventAwareTrait.php index b42a58dc..991f2c84 100644 --- a/src/Concern/SimpleEventAwareTrait.php +++ b/src/Concern/SimpleEventAwareTrait.php @@ -49,17 +49,17 @@ trait SimpleEventAwareTrait /** * register a event handler * - * @param $event + * @param string $event * @param callable $handler * @param bool $once */ - public function on(string $event, callable $handler, $once = false): void + public function on(string $event, callable $handler, bool $once = false): void { if (self::isSupportedEvent($event)) { self::$eventHandlers[$event][] = $handler; if (!isset(self::$events[$event])) { - self::$events[$event] = (bool)$once; + self::$events[$event] = $once; } } } @@ -67,7 +67,7 @@ public function on(string $event, callable $handler, $once = false): void /** * register a once event handler * - * @param $event + * @param string $event * @param callable $handler */ public function once(string $event, callable $handler): void @@ -111,7 +111,7 @@ public function fire(string $event, ...$args): bool /** * remove event and it's handlers * - * @param $event + * @param string $event * * @return bool */ @@ -127,7 +127,7 @@ public function off(string $event): bool } /** - * @param $event + * @param string $event * * @return bool */ @@ -137,7 +137,7 @@ public function hasEvent(string $event): bool } /** - * @param $event + * @param string $event * * @return bool */ From 8e39a95cb8a7875de271395bc8735cc358c67a26 Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 7 Aug 2021 21:42:15 +0800 Subject: [PATCH 115/258] update some for base input class Signed-off-by: inhere --- src/IO/AbstractInput.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index 4e56d99a..56606cd1 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -241,6 +241,14 @@ public function getFlags(): array return $this->flags; } + /** + * @return array + */ + public function getRawFlags(): array + { + return $this->tokens; + } + /** * @return array */ From 2b43156fe0316e1e5cebf15c616ad5a23d4e5f37 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 8 Aug 2021 23:18:03 +0800 Subject: [PATCH 116/258] update readme, update default screenshots image --- README.md | 14 +++++-- README.zh-CN.md | 7 ++++ docs/screenshots/app-command-list.png | Bin 78616 -> 344467 bytes examples/Command/CorCommand.php | 2 +- examples/Command/DemoCommand.php | 2 +- examples/app | 5 ++- src/Component/Formatter/HelpPanel.php | 5 ++- src/Concern/ApplicationHelpTrait.php | 56 ++++++++++++++++---------- 8 files changed, 62 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index c2f11720..2d661dcf 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console) [![Github Actions Status](https://github.com/inhere/php-console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/php-console/actions) -A simple, full-featured php command line application library. +Full-featured php command line application library. Provide console parameter parsing, command run, color style output, user information interaction, and special format information display. @@ -20,7 +20,8 @@ Provide console parameter parsing, command run, color style output, user informa > Easy to use. Can be easily integrated into any existing project. - Command line application, `controller`, `command` parsing run on the command line -- Support for setting aliases for commands. A command can have multiple aliases. Support command display/hide, enable/disable +- Support for setting aliases for commands. A command can have multiple aliases. +- Support command display/hide, enable/disable. - Full-featured command line option parameter parsing (named parameters, short options `-s`, long options `--long`). - The `input`, `output` of the command line, management, use - Command method comments are automatically parsed as help information (by default, `@usage` `@arguments` `@options` `@example`) @@ -82,11 +83,18 @@ phpunit phpdbg -dauto_globals_jit=Off -qrr /usr/local/bin/phpunit --coverage-text ``` +## Project use + +Check out these projects, which use https://github.com/inhere/php-console : + +- [kite](https://github.com/inhere/kite) Kite is a tool for help development. +- More, please see [github used by](https://github.com/inhere/php-console/network/dependents?package_id=UGFja2FnZS01NDI5NzMxOTI%3D) + ## License [MIT](LICENSE) -## My other projects +## My projects - [inhere/php-validate](https://github.com/inhere/php-validate) A compact and full-featured php verification library - [inhere/sroute](https://github.com/inhere/php-srouter) Lightweight and fast HTTP request routing library diff --git a/README.zh-CN.md b/README.zh-CN.md index 66151d18..6e8cdcbd 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -79,6 +79,13 @@ phpunit phpdbg -dauto_globals_jit=Off -qrr /usr/local/bin/phpunit --coverage-text ``` +## 使用console的项目 + +看看这些使用了 https://github.com/inhere/php-console 的项目: + +- [kite](https://github.com/inhere/kite) PHP编写的,方便本地开发和使用的一些CLI工具应用 +- More, please see [github used by](https://github.com/inhere/php-console/network/dependents?package_id=UGFja2FnZS01NDI5NzMxOTI%3D) + ## License [MIT](LICENSE) diff --git a/docs/screenshots/app-command-list.png b/docs/screenshots/app-command-list.png index a078c9be72504382d4fd71144ad6bdd9919f838a..27bd3588bacde1498a7934393578498417f37d16 100644 GIT binary patch literal 344467 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�m^2aI_+ec8&W-kFG>>fg$>AY6;&;E%DvJ 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

    Y&PW6}zLSZ5t&i=@bKsK1 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 z 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 Wh$2y$pEGO#0000;=vM diff --git a/examples/Command/CorCommand.php b/examples/Command/CorCommand.php index dd24130f..166817bf 100644 --- a/examples/Command/CorCommand.php +++ b/examples/Command/CorCommand.php @@ -30,7 +30,7 @@ class CorCommand extends Command */ public static function aliases(): array { - return ['coro']; + return ['co', 'coro']; } /** diff --git a/examples/Command/DemoCommand.php b/examples/Command/DemoCommand.php index f1240921..561694c0 100644 --- a/examples/Command/DemoCommand.php +++ b/examples/Command/DemoCommand.php @@ -21,7 +21,7 @@ class DemoCommand extends Command { protected static $name = 'demo'; - protected static $description = 'this is a demo alone command. but config use configure(), like symfony console: argument define by position'; + protected static $description = 'this is a demo alone command. but use Definition instead of annotations'; /** * {@inheritDoc} diff --git a/examples/app b/examples/app index e8bec0a1..e2399363 100644 --- a/examples/app +++ b/examples/app @@ -14,8 +14,9 @@ require dirname(__DIR__) . '/test/boot.php'; // create app instance $app = new Application([ - 'debug' => true, - 'rootPath' => dirname(__DIR__), + 'debug' => true, + 'rootPath' => dirname(__DIR__), + 'description' => 'This is demo console application', ]); $app->setLogo(" diff --git a/src/Component/Formatter/HelpPanel.php b/src/Component/Formatter/HelpPanel.php index 9c36c125..9a055ade 100644 --- a/src/Component/Formatter/HelpPanel.php +++ b/src/Component/Formatter/HelpPanel.php @@ -48,6 +48,8 @@ class HelpPanel extends MessageFormatter * Show console help message * * There are config structure. you can setting some or ignore some. will only render it when value is not empty. + * + * ``` * [ * description string The description text. e.g 'Composer version 1.3.2' * usage string The usage message text. e.g 'command [options] [arguments]' @@ -73,6 +75,7 @@ class HelpPanel extends MessageFormatter * ] * examples array|string The command usage example. e.g 'php server.php {start|reload|restart|stop} [-d]' * ] + * ``` * * @param array $config The config data */ @@ -135,7 +138,7 @@ public static function show(array $config): void if (is_string($value)) { $value = trim($value); $section = ucfirst($section); - $parts[] = "$section:\n {$value}\n"; + $parts[] = "$section:\n $value\n"; } } diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index 2432e202..2bd29ed4 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -46,6 +46,11 @@ */ trait ApplicationHelpTrait { + /** + * @var string|array + */ + protected $moreHelpInfo = ''; + /*************************************************************************** * Show information for the application ***************************************************************************/ @@ -55,10 +60,11 @@ trait ApplicationHelpTrait */ public function showVersionInfo(): void { - $os = PHP_OS; - $date = date('Y.m.d'); - $logo = ''; - $name = $this->getParam('name', 'Console Application'); + $os = PHP_OS; + $date = date('Y.m.d'); + $logo = ''; + $name = $this->getParam('name', 'Console Application'); + $version = $this->getParam('version', 'Unknown'); $publishAt = $this->getParam('publishAt', 'Unknown'); $updateAt = $this->getParam('updateAt', 'Unknown'); @@ -115,20 +121,19 @@ public function showHelpInfo(string $command = ''): void $globalOptions['--gen-file'] = 'The output file for generate auto completion script'; } - $globalOptions = FormatUtil::alignOptions($globalOptions); - - /** @var Output $out */ - $out = $this->output; - $out->helpPanel([ + $helpInfo = [ 'Usage' => "$binName {command} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", - 'Options' => $globalOptions, + 'Options' => FormatUtil::alignOptions($globalOptions), 'Example' => [ + '- run a command/subcommand:', "$binName test run a independent command", - "$binName home index run a sub-command of the group", - sprintf("$binName home%sindex run a sub-command of the group", $delimiter), - "$binName help {command} see a command help information", - "$binName home index -h see a sub-command help of the group", - sprintf("$binName home%sindex -h see a sub-command help of the group", $delimiter), + "$binName home index run a subcommand of the group", + sprintf("$binName home%sindex run a subcommand of the group", $delimiter), + '', + '- display help for command:', + "$binName help COMMAND see a command help information", + "$binName home index -h see a subcommand help of the group", + sprintf("$binName home%sindex -h see a subcommand help of the group", $delimiter), ], 'Help' => [ 'Generate shell auto completion scripts:', @@ -138,7 +143,16 @@ public function showHelpInfo(string $command = ''): void " $binName --auto-completion --shell-env zsh --gen-file stdout", " $binName --auto-completion --shell-env bash --gen-file myapp.sh", ], - ]); + ]; + + // custom more help info + if ($this->moreHelpInfo) { + $helpInfo['More Information'] = $this->moreHelpInfo; + } + + /** @var Output $out */ + $out = $this->output; + $out->helpPanel($helpInfo); } /** @@ -224,7 +238,7 @@ public function showCommandList(): void } $aliases = $options['aliases']; - $extra = $aliases ? ColorTag::wrap(' [alias: ' . implode(',', $aliases) . ']', 'info') : ''; + $extra = $aliases ? ColorTag::wrap(' (alias: ' . implode(',', $aliases) . ')', 'info') : ''; $commandArr[$name] = $desc . $extra; } @@ -265,7 +279,7 @@ public function showCommandList(): void ]); unset($groupArr, $commandArr, $internalCommands); - Console::write("More command information, please use: $scriptName {command} -h"); + Console::write("More command information, please use: $scriptName COMMAND -h"); Console::flushBuffer(); } @@ -292,9 +306,9 @@ protected function dumpAutoCompletion(string $shellEnv, array $data): void $router = $this->getRouter(); // info - $glue = ' '; - $genFile = $input->getStringOpt('gen-file', 'none'); - $tplDir = dirname(__DIR__, 2) . '/resource/templates'; + $glue = ' '; + $genFile = $input->getStringOpt('gen-file', 'none'); + $tplDir = dirname(__DIR__, 2) . '/resource/templates'; if ($shellEnv === 'bash') { $tplFile = $tplDir . '/bash-completion.tpl'; From 8d5c2ffd95df9fc98be3c16b3f734a561b8cd360 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 8 Aug 2021 23:22:49 +0800 Subject: [PATCH 117/258] style: update readme image links --- README.md | 2 +- README.zh-CN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2d661dcf..8fc3638d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Provide console parameter parsing, command run, color style output, user informa ## Command line preview -![app-command-list](https://raw.githubusercontent.com/inhere/php-console/master/docs/screenshots/app-command-list.png) +![app-command-list](https://raw.githubusercontent.com/inhere/php-console/3.x/docs/screenshots/app-command-list.png) ## Features diff --git a/README.zh-CN.md b/README.zh-CN.md index 6e8cdcbd..3c30b353 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -11,7 +11,7 @@ ## 命令行预览 -![app-command-list](https://raw.githubusercontent.com/inhere/php-console/master/docs/screenshots/app-command-list.png) +![app-command-list](https://raw.githubusercontent.com/inhere/php-console/3.x/docs/screenshots/app-command-list.png) ## 功能概览 From b06ef05c6dc2bdd41df4f4d9737fc2b006dea1c3 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 9 Aug 2021 00:01:28 +0800 Subject: [PATCH 118/258] feat: add more event trigger on command run --- README.md | 12 ++++++++++++ README.zh-CN.md | 12 ++++++++++++ src/AbstractApplication.php | 12 +++++++++++- src/AbstractHandler.php | 9 ++++++++- src/Command.php | 10 ++++------ src/Concern/ApplicationHelpTrait.php | 4 ++++ src/Console.php | 3 +++ src/ConsoleEvent.php | 15 +++++++++++---- src/Controller.php | 3 +++ 9 files changed, 68 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 8fc3638d..c997a57a 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,18 @@ phpunit phpdbg -dauto_globals_jit=Off -qrr /usr/local/bin/phpunit --coverage-text ``` +## Debuging + +You can set debug level by ENV `CONSOLE_DEBUG=level`, global option `--debug level` + +```bash +# by ENV +$ CONSOLE_DEBUG=4 php examples/app +$ CONSOLE_DEBUG=5 php examples/app +# by global options +$ php examples/app --debug 4 +``` + ## Project use Check out these projects, which use https://github.com/inhere/php-console : diff --git a/README.zh-CN.md b/README.zh-CN.md index 3c30b353..53e660cb 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -79,6 +79,18 @@ phpunit phpdbg -dauto_globals_jit=Off -qrr /usr/local/bin/phpunit --coverage-text ``` +## 开发调试 + +你可以通过环境变量 `CONSOLE_DEBUG=level`, 全局选项 `--debug level` 设置debug级别 + +```bash +# by ENV +$ CONSOLE_DEBUG=4 php examples/app +$ CONSOLE_DEBUG=5 php examples/app +# by global options +$ php examples/app --debug 4 +``` + ## 使用console的项目 看看这些使用了 https://github.com/inhere/php-console 的项目: diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 5b3f0a0d..3bb25fa2 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -27,6 +27,7 @@ use Toolkit\Cli\Style; use Toolkit\Cli\Util\LineParser; use Toolkit\Stdlib\Helper\PhpHelper; +use Toolkit\Stdlib\OS; use Toolkit\Sys\Proc\ProcessUtil; use Toolkit\Sys\Proc\Signal; use function array_keys; @@ -42,6 +43,7 @@ use function set_error_handler; use function set_exception_handler; use function trim; +use function vdump; use const PHP_SAPI; /** @@ -711,7 +713,15 @@ public function getVerbLevel(): int { $key = GlobalOption::DEBUG; - return (int)$this->input->getLongOpt($key, (int)$this->config[$key]); + // feat: support set debug level by ENV var: CONSOLE_DEBUG + $envVal = OS::getEnvVal(Console::DEBUG_ENV_KEY); + if ($envVal !== '') { + $setVal = (int)$envVal; + } else { + $setVal = (int)$this->config[$key]; + } + + return (int)$this->input->getLongOpt($key, $setVal); } /** diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index f2f89d3a..0d2f56ac 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -260,6 +260,13 @@ public function run(string $command = '') return -1; } + // trigger event + $this->fire(ConsoleEvent::COMMAND_RUN_BEFORE, $this); + + if ($this->isAlone()) { + $this->fire(ConsoleEvent::ALONE_COMMAND_RUN_BEFORE, $this); + } + // if enable swoole coroutine if (static::isCoroutine() && Helper::isSupportCoroutine()) { $result = $this->coExecute(); @@ -583,7 +590,7 @@ protected function showHelpByMethodAnnotations(string $method, string $action = return 0; } - // is a console controller command + // subcommand: is a console controller subcommand if ($action && !$ref->getMethod($method)->isPublic()) { $this->write("The command [$name] don't allow access in the class."); return 0; diff --git a/src/Command.php b/src/Command.php index ffde75ac..71692b40 100644 --- a/src/Command.php +++ b/src/Command.php @@ -9,7 +9,6 @@ namespace Inhere\Console; use Inhere\Console\Contract\CommandInterface; -use ReflectionException; /** * Class Command @@ -53,17 +52,16 @@ abstract class Command extends AbstractHandler implements CommandInterface */ // protected function configure() // { - // $this - // ->createDefinition() - // ->addArgument('test') - // ->addOption('test'); + // $this + // ->createDefinition() + // ->addArgument('test') + // ->addOption('test'); // } /** * Show help information * * @return bool - * @throws ReflectionException */ protected function showHelp(): bool { diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index 2bd29ed4..750bb358 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -10,6 +10,7 @@ use Inhere\Console\AbstractHandler; use Inhere\Console\Console; +use Inhere\Console\ConsoleEvent; use Inhere\Console\Contract\CommandInterface; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; @@ -70,6 +71,8 @@ public function showVersionInfo(): void $updateAt = $this->getParam('updateAt', 'Unknown'); $phpVersion = PHP_VERSION; + $this->fire(ConsoleEvent::BEFORE_RENDER_APP_VERSION, $this); + if ($logoTxt = $this->getLogoText()) { $logo = ColorTag::wrap($logoTxt, $this->getLogoStyle()); } @@ -107,6 +110,7 @@ public function showHelpInfo(string $command = ''): void } $this->debugf('display application help by input -h, --help'); + $this->fire(ConsoleEvent::BEFORE_RENDER_APP_HELP, $this); $delimiter = $this->delimiter; $binName = $in->getScriptName(); diff --git a/src/Console.php b/src/Console.php index 3f40485d..9bc11357 100644 --- a/src/Console.php +++ b/src/Console.php @@ -57,6 +57,9 @@ class Console extends Cli self::VERB_CRAZY => 'magenta', ]; + // eg: CONSOLE_DEBUG=4 php my-console + public const DEBUG_ENV_KEY = 'CONSOLE_DEBUG'; + /** * @var Application */ diff --git a/src/ConsoleEvent.php b/src/ConsoleEvent.php index f6f53c02..09c5502a 100644 --- a/src/ConsoleEvent.php +++ b/src/ConsoleEvent.php @@ -29,17 +29,24 @@ final class ConsoleEvent public const BEFORE_RENDER_APP_COMMANDS_LIST = 'app.commands.list.render.before'; - // ----- group/sub-command + // group command and subcommand + public const COMMAND_RUN_BEFORE = 'each.command.run.before'; - public const SUB_COMMAND_NOT_FOUND = 'group.command.notFound'; + public const ALONE_COMMAND_RUN_BEFORE = 'alone.command.run.before'; + + public const SUBCOMMAND_RUN_BEFORE = 'group.subcommand.run.before'; + + // ----- group/subcommand + + public const SUBCOMMAND_NOT_FOUND = 'group.subcommand.not.found'; public const BEFORE_RENDER_GROUP_HELP = 'group.help.render.before'; public const AFTER_RENDER_GROUP_HELP = 'group.help.render.after'; - public const BEFORE_RENDER_SUB_COMMAND_HELP = 'group.command.help.render.before'; + public const BEFORE_RENDER_SUBCOMMAND_HELP = 'group.command.help.render.before'; - public const AFTER_RENDER_SUB_COMMAND_HELP = 'group.command.help.render.after'; + public const AFTER_RENDER_SUBCOMMAND_HELP = 'group.command.help.render.after'; // ----- command diff --git a/src/Controller.php b/src/Controller.php index 46c58639..d355ef34 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -334,6 +334,9 @@ final public function execute($input, $output) $method = $this->getMethodName($action); + // trigger event + $this->fire(ConsoleEvent::SUBCOMMAND_RUN_BEFORE, $this); + // the action method exists and only allow access public method. // if (method_exists($this, $method)) { // before run action From 46f58b366370ef0c1627eb6761ebac145106e0e3 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 9 Aug 2021 17:21:00 +0800 Subject: [PATCH 119/258] feat: add more events fire point Signed-off-by: inhere --- src/AbstractHandler.php | 11 +++++++---- src/Concern/AttachApplicationTrait.php | 2 ++ src/ConsoleEvent.php | 10 +++++++--- src/Controller.php | 6 ++++-- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 0d2f56ac..4a0105cd 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -260,11 +260,9 @@ public function run(string $command = '') return -1; } - // trigger event - $this->fire(ConsoleEvent::COMMAND_RUN_BEFORE, $this); - + // only fire for alone command run. if ($this->isAlone()) { - $this->fire(ConsoleEvent::ALONE_COMMAND_RUN_BEFORE, $this); + $this->fire(ConsoleEvent::COMMAND_RUN_BEFORE, $this); } // if enable swoole coroutine @@ -315,6 +313,9 @@ public function coExecute(): bool */ protected function beforeExecute(): bool { + // trigger event + $this->fire(ConsoleEvent::COMMAND_EXEC_BEFORE, $this); + return true; } @@ -333,6 +334,8 @@ abstract protected function execute($input, $output); */ protected function afterExecute(): void { + // trigger event + $this->fire(ConsoleEvent::COMMAND_EXEC_AFTER, $this); } /** diff --git a/src/Concern/AttachApplicationTrait.php b/src/Concern/AttachApplicationTrait.php index 89e6cf15..c9c94ac1 100644 --- a/src/Concern/AttachApplicationTrait.php +++ b/src/Concern/AttachApplicationTrait.php @@ -154,6 +154,8 @@ public function log(int $level, string $message, array $extra = []): void */ public function fire(string $event, ...$args): bool { + $this->debugf("fire event: $event"); + // if has application instance if ($this->attached) { $stop = $this->app->fire($event, ...$args); diff --git a/src/ConsoleEvent.php b/src/ConsoleEvent.php index 09c5502a..747d3f1f 100644 --- a/src/ConsoleEvent.php +++ b/src/ConsoleEvent.php @@ -29,10 +29,14 @@ final class ConsoleEvent public const BEFORE_RENDER_APP_COMMANDS_LIST = 'app.commands.list.render.before'; - // group command and subcommand - public const COMMAND_RUN_BEFORE = 'each.command.run.before'; + // ----- group command and subcommand - public const ALONE_COMMAND_RUN_BEFORE = 'alone.command.run.before'; + // every command/subcommand run will fire it + public const COMMAND_RUN_BEFORE = 'every.command.run.before'; + + // every command/subcommand exec will fire it + public const COMMAND_EXEC_BEFORE = 'every.command.exec.before'; + public const COMMAND_EXEC_AFTER = 'every.command.exec.after'; public const SUBCOMMAND_RUN_BEFORE = 'group.subcommand.run.before'; diff --git a/src/Controller.php b/src/Controller.php index d355ef34..b54afc29 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -229,7 +229,7 @@ public function run(string $command = '') { $command = $this->findCommandName($command); - // if not input sub-command, render group help. + // if not input subcommand, render group help. if (!$command) { $this->debugf('not input subcommand, display help for the group: %s', self::getName()); return $this->showHelp(); @@ -249,8 +249,10 @@ public function run(string $command = '') // convert 'boo-foo' to 'booFoo' $this->action = $action = Str::camelCase($command); - $this->debugf("will run the '%s' group action: %s, sub-command: %s", static::getName(), $this->action, $command); + $this->debugf("will run the '%s' group action: %s, subcommand: %s", static::getName(), $this->action, $command); + // fire event + $this->fire(ConsoleEvent::COMMAND_RUN_BEFORE, $this); $this->beforeRun(); // check method not exist From 485d65f6c0b97df1b420d89f4736d0e31af9c3db Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 9 Aug 2021 19:44:43 +0800 Subject: [PATCH 120/258] fix: command id should use real command name Signed-off-by: inhere --- src/Controller.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Controller.php b/src/Controller.php index b54afc29..6126df9d 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -204,12 +204,6 @@ protected function findCommandName(string $command): string // try use next arg as sub-command name. if (!$command) { $command = $this->input->findCommandName(); - - // update the command id. - if ($command) { - $group = $this->input->getCommand(); - $this->input->setCommandId("$group:$command"); - } } } @@ -247,6 +241,9 @@ public function run(string $command = '') // get real sub-command name $command = $this->resolveAlias($command); + // update the command id. + $this->input->setCommandId(static::getName() . ":$command"); + // convert 'boo-foo' to 'booFoo' $this->action = $action = Str::camelCase($command); $this->debugf("will run the '%s' group action: %s, subcommand: %s", static::getName(), $this->action, $command); From 0646bc8ec920b4fc6f84c1138802fc1591627d2c Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 12 Aug 2021 16:13:16 +0800 Subject: [PATCH 121/258] refactor: move input flag class to Flag namespace --- src/AbstractHandler.php | 17 ++++--- src/Command.php | 4 +- src/Concern/StyledOutputAwareTrait.php | 2 +- src/Console.php | 16 ++++++ src/Contract/InputFlagInterface.php | 15 ++++++ src/{IO/Input => Flag}/InputArgument.php | 2 +- src/{IO/Input => Flag}/InputArguments.php | 4 +- src/{IO/Input => Flag}/InputFlag.php | 35 +++++++------ src/{IO/Input => Flag}/InputOption.php | 4 +- src/{IO/Input => Flag}/InputOptions.php | 4 +- src/Handler/CallableCommand.php | 60 +++++++++++++++++++++++ 11 files changed, 130 insertions(+), 33 deletions(-) rename src/{IO/Input => Flag}/InputArgument.php (96%) rename src/{IO/Input => Flag}/InputArguments.php (93%) rename src/{IO/Input => Flag}/InputFlag.php (82%) rename src/{IO/Input => Flag}/InputOption.php (96%) rename src/{IO/Input => Flag}/InputOptions.php (73%) create mode 100644 src/Handler/CallableCommand.php diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 4a0105cd..cb174b60 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -95,6 +95,11 @@ abstract class AbstractHandler implements CommandHandlerInterface 'help' => true, ]; + /** + * @var bool + */ + private $initialized = false; + /** * @var InputDefinition|null */ @@ -134,10 +139,11 @@ public static function aliases(): array /** * Command constructor. * - * @param Input $input - * @param Output $output + * @param Input $input + * @param Output $output * @param InputDefinition|null $definition */ + // TODO public function __construct(Input $input = null, Output $output = null, InputDefinition $definition = null) public function __construct(Input $input, Output $output, InputDefinition $definition = null) { $this->input = $input; @@ -147,15 +153,14 @@ public function __construct(Input $input, Output $output, InputDefinition $defin $this->definition = $definition; } - $this->commentsVars = $this->annotationVars(); - $this->init(); - - $this->afterInit(); } protected function init(): void { + $this->commentsVars = $this->annotationVars(); + + $this->afterInit(); } protected function afterInit(): void diff --git a/src/Command.php b/src/Command.php index 71692b40..d9c95f13 100644 --- a/src/Command.php +++ b/src/Command.php @@ -27,6 +27,8 @@ */ abstract class Command extends AbstractHandler implements CommandInterface { + public const METHOD = 'execute'; + /** * @var Command */ @@ -73,7 +75,7 @@ protected function showHelp(): bool return true; } - $execMethod = 'execute'; + $execMethod = self::METHOD; $this->logf(Console::VERB_CRAZY, "display help info for the command: %s", self::getName()); diff --git a/src/Concern/StyledOutputAwareTrait.php b/src/Concern/StyledOutputAwareTrait.php index 817d44f8..ab941ba3 100644 --- a/src/Concern/StyledOutputAwareTrait.php +++ b/src/Concern/StyledOutputAwareTrait.php @@ -64,7 +64,7 @@ trait StyledOutputAwareTrait { /** * @param string $text - * @param string $tag + * @param string $tag all tag please {@see Color::STYLES} * * @return int */ diff --git a/src/Console.php b/src/Console.php index 9bc11357..d3f98c09 100644 --- a/src/Console.php +++ b/src/Console.php @@ -81,6 +81,22 @@ public static function setApp(Application $app): void self::$app = $app; } + /** + * @return Input + */ + public static function getInput(): Input + { + return self::$app->getInput(); + } + + /** + * @return Output + */ + public static function getOutput(): Output + { + return self::$app->getOutput(); + } + /** * @param array $config * @param Input|null $input diff --git a/src/Contract/InputFlagInterface.php b/src/Contract/InputFlagInterface.php index 8c8e49d3..13a0cf74 100644 --- a/src/Contract/InputFlagInterface.php +++ b/src/Contract/InputFlagInterface.php @@ -9,6 +9,21 @@ */ interface InputFlagInterface { + // fixed args and opts for a command/controller-command + public const ARG_REQUIRED = 1; + + public const ARG_OPTIONAL = 2; + + public const ARG_IS_ARRAY = 4; + + public const OPT_BOOLEAN = 1; // eq symfony InputOption::VALUE_NONE + + public const OPT_REQUIRED = 2; + + public const OPT_OPTIONAL = 4; + + public const OPT_IS_ARRAY = 8; + /** * @param int $mode * diff --git a/src/IO/Input/InputArgument.php b/src/Flag/InputArgument.php similarity index 96% rename from src/IO/Input/InputArgument.php rename to src/Flag/InputArgument.php index 00316ae7..4ae8a4d1 100644 --- a/src/IO/Input/InputArgument.php +++ b/src/Flag/InputArgument.php @@ -6,7 +6,7 @@ * Time: 10:33 */ -namespace Inhere\Console\IO\Input; +namespace Inhere\Console\Flag; use Inhere\Console\IO\Input; diff --git a/src/IO/Input/InputArguments.php b/src/Flag/InputArguments.php similarity index 93% rename from src/IO/Input/InputArguments.php rename to src/Flag/InputArguments.php index 67ad84c8..b7967347 100644 --- a/src/IO/Input/InputArguments.php +++ b/src/Flag/InputArguments.php @@ -6,13 +6,13 @@ * Time: 10:11 */ -namespace Inhere\Console\IO\Input; +namespace Inhere\Console\Flag; /** * Class InputArguments * - input arguments builder * - * @package Inhere\Console\IO\Input + * @package Inhere\Console\Flag */ class InputArguments { diff --git a/src/IO/Input/InputFlag.php b/src/Flag/InputFlag.php similarity index 82% rename from src/IO/Input/InputFlag.php rename to src/Flag/InputFlag.php index 0d322fb7..289174e4 100644 --- a/src/IO/Input/InputFlag.php +++ b/src/Flag/InputFlag.php @@ -6,14 +6,13 @@ * Time: 10:28 */ -namespace Inhere\Console\IO\Input; +namespace Inhere\Console\Flag; use Inhere\Console\Contract\InputFlagInterface; -use Inhere\Console\IO\Input; /** - * Class InputFlag - * - definition a input item(option|argument) + * Class Flag + * - - definition a input flag item(option|argument) * * @package Inhere\Console\IO\Input */ @@ -27,7 +26,7 @@ abstract class InputFlag implements InputFlagInterface /** * @var string */ - private $description; + private $desc; /** * @var int @@ -51,14 +50,14 @@ abstract class InputFlag implements InputFlagInterface /** * @param string $name * @param int $mode see Input::ARG_* or Input::OPT_* - * @param string $description - * @param null $default + * @param string $desc + * @param mixed|null $default * * @return static */ - public static function make(string $name, int $mode = 0, string $description = '', $default = null) + public static function make(string $name, int $mode = 0, string $desc = '', $default = null) { - return new static($name, $mode, $description, $default); + return new static($name, $mode, $desc, $default); } /** @@ -66,18 +65,18 @@ public static function make(string $name, int $mode = 0, string $description = ' * * @param string $name * @param int $mode see Input::ARG_* or Input::OPT_* - * @param string $description + * @param string $desc * @param mixed $default The default value * - for Input::ARG_OPTIONAL mode only * - must be null for InputOption::OPT_BOOL */ - public function __construct(string $name, int $mode = 0, string $description = '', $default = null) + public function __construct(string $name, int $mode = 0, string $desc = '', $default = null) { $this->name = $name; $this->mode = $mode; $this->default = $default; - $this->setDescription($description); + $this->setDesc($desc); } /****************************************************************** @@ -166,17 +165,17 @@ public function setDefault($default): void /** * @return string */ - public function getDescription(): string + public function getDesc(): string { - return $this->description; + return $this->desc; } /** - * @param string $description + * @param string $desc */ - public function setDescription(string $description): void + public function setDesc(string $desc): void { - $this->description = $description; + $this->desc = $desc; } /** @@ -192,7 +191,7 @@ public function toArray(): array 'isArray' => $this->isArray(), 'isOptional' => $this->isOptional(), 'isRequired' => $this->isRequired(), - 'description' => $this->description, + 'description' => $this->desc, ]; } } diff --git a/src/IO/Input/InputOption.php b/src/Flag/InputOption.php similarity index 96% rename from src/IO/Input/InputOption.php rename to src/Flag/InputOption.php index 236d7a65..b0fd756e 100644 --- a/src/IO/Input/InputOption.php +++ b/src/Flag/InputOption.php @@ -6,7 +6,7 @@ * Time: 10:28 */ -namespace Inhere\Console\IO\Input; +namespace Inhere\Console\Flag; use Inhere\Console\IO\Input; use function implode; @@ -15,7 +15,7 @@ * Class InputOption * - definition a input option * - * @package Inhere\Console\IO\Input + * @package Inhere\Console\Flag */ class InputOption extends InputFlag { diff --git a/src/IO/Input/InputOptions.php b/src/Flag/InputOptions.php similarity index 73% rename from src/IO/Input/InputOptions.php rename to src/Flag/InputOptions.php index f509f26e..ebb7056e 100644 --- a/src/IO/Input/InputOptions.php +++ b/src/Flag/InputOptions.php @@ -6,13 +6,13 @@ * Time: 10:10 */ -namespace Inhere\Console\IO\Input; +namespace Inhere\Console\Flag; /** * Class InputOptions * - input options builder * - * @package Inhere\Console\IO\Input + * @package Inhere\Console\Flag */ class InputOptions { diff --git a/src/Handler/CallableCommand.php b/src/Handler/CallableCommand.php new file mode 100644 index 00000000..490d52fd --- /dev/null +++ b/src/Handler/CallableCommand.php @@ -0,0 +1,60 @@ +setCallable($cb); + // } + + /** + * @param callable $callable + * + * @return CallableCommand + */ + public function setCallable(callable $callable): self + { + $this->callable = $callable; + return $this; + } + + /** + * Do execute command + * + * @param Input $input + * @param Output $output + * + * @return int|mixed + */ + protected function execute($input, $output) + { + if (!$call = $this->callable) { + throw new \BadMethodCallException('The callable property is empty'); + } + + // call custom callable + return $call($input, $output); + } +} From fb101ad5ce240321152c487d9b3c79de1ec68c6c Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 12 Aug 2021 16:15:21 +0800 Subject: [PATCH 122/258] refactor: rename some flag class --- src/Flag/{InputArgument.php => Argument.php} | 2 +- src/Flag/{InputArguments.php => Arguments.php} | 2 +- src/Flag/{InputFlag.php => Flag.php} | 2 +- src/Flag/{InputOption.php => Option.php} | 2 +- src/Flag/{InputOptions.php => Options.php} | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename src/Flag/{InputArgument.php => Argument.php} (96%) rename src/Flag/{InputArguments.php => Arguments.php} (98%) rename src/Flag/{InputFlag.php => Flag.php} (98%) rename src/Flag/{InputOption.php => Option.php} (98%) rename src/Flag/{InputOptions.php => Options.php} (92%) diff --git a/src/Flag/InputArgument.php b/src/Flag/Argument.php similarity index 96% rename from src/Flag/InputArgument.php rename to src/Flag/Argument.php index 4ae8a4d1..0c0bb034 100644 --- a/src/Flag/InputArgument.php +++ b/src/Flag/Argument.php @@ -16,7 +16,7 @@ * * @package Inhere\Console\IO\Input */ -class InputArgument extends InputFlag +class Argument extends Flag { /** * The argument position diff --git a/src/Flag/InputArguments.php b/src/Flag/Arguments.php similarity index 98% rename from src/Flag/InputArguments.php rename to src/Flag/Arguments.php index b7967347..25f036c0 100644 --- a/src/Flag/InputArguments.php +++ b/src/Flag/Arguments.php @@ -14,7 +14,7 @@ * * @package Inhere\Console\Flag */ -class InputArguments +class Arguments { /** * @var array diff --git a/src/Flag/InputFlag.php b/src/Flag/Flag.php similarity index 98% rename from src/Flag/InputFlag.php rename to src/Flag/Flag.php index 289174e4..d17cfa0c 100644 --- a/src/Flag/InputFlag.php +++ b/src/Flag/Flag.php @@ -16,7 +16,7 @@ * * @package Inhere\Console\IO\Input */ -abstract class InputFlag implements InputFlagInterface +abstract class Flag implements InputFlagInterface { /** * @var string diff --git a/src/Flag/InputOption.php b/src/Flag/Option.php similarity index 98% rename from src/Flag/InputOption.php rename to src/Flag/Option.php index b0fd756e..42294f25 100644 --- a/src/Flag/InputOption.php +++ b/src/Flag/Option.php @@ -17,7 +17,7 @@ * * @package Inhere\Console\Flag */ -class InputOption extends InputFlag +class Option extends Flag { /** * alias name diff --git a/src/Flag/InputOptions.php b/src/Flag/Options.php similarity index 92% rename from src/Flag/InputOptions.php rename to src/Flag/Options.php index ebb7056e..e059e103 100644 --- a/src/Flag/InputOptions.php +++ b/src/Flag/Options.php @@ -14,6 +14,6 @@ * * @package Inhere\Console\Flag */ -class InputOptions +class Options { } From b819685d766bf339dd0c94fc6e430fdbd890c305 Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 12 Aug 2021 20:00:24 +0800 Subject: [PATCH 123/258] update some flag parse logic --- phpunit.xml.dist => phpunit.xml | 2 +- src/Concern/InputFlagsWareTrait.php | 13 + src/Console.php | 3 + src/Exception/FlagException.php | 15 ++ src/Flag/Flag.php | 121 ++++++++- src/Flag/Flags.php | 358 +++++++++++++++++++++++++ src/Flag/Option.php | 33 ++- src/Flag/Traits/FlagArgumentsTrait.php | 81 ++++++ src/Flag/Traits/FlagOptionsTrait.php | 115 ++++++++ src/IO/AbstractInput.php | 18 +- {test => src/IO}/TempStream.php | 6 +- src/Util/Helper.php | 17 ++ test/BaseTestCase.php | 14 + test/ControllerTest.php | 5 +- test/Flag/FlagsTest.php | 35 +++ test/{boot.php => bootstrap.php} | 0 16 files changed, 799 insertions(+), 37 deletions(-) rename phpunit.xml.dist => phpunit.xml (93%) create mode 100644 src/Concern/InputFlagsWareTrait.php create mode 100644 src/Exception/FlagException.php create mode 100644 src/Flag/Flags.php create mode 100644 src/Flag/Traits/FlagArgumentsTrait.php create mode 100644 src/Flag/Traits/FlagOptionsTrait.php rename {test => src/IO}/TempStream.php (94%) create mode 100644 test/BaseTestCase.php create mode 100644 test/Flag/FlagsTest.php rename test/{boot.php => bootstrap.php} (100%) diff --git a/phpunit.xml.dist b/phpunit.xml similarity index 93% rename from phpunit.xml.dist rename to phpunit.xml index fcd6bd50..cd3f80d8 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml @@ -2,7 +2,7 @@ 'magenta', ]; + public const CMD_GROUP = 1; + public const CMD_SINGLE = 2; + // eg: CONSOLE_DEBUG=4 php my-console public const DEBUG_ENV_KEY = 'CONSOLE_DEBUG'; diff --git a/src/Exception/FlagException.php b/src/Exception/FlagException.php new file mode 100644 index 00000000..eda7b013 --- /dev/null +++ b/src/Exception/FlagException.php @@ -0,0 +1,15 @@ +name = $name; $this->mode = $mode; @@ -79,6 +116,13 @@ public function __construct(string $name, int $mode = 0, string $desc = '', $def $this->setDesc($desc); } + public function init(): void + { + if ($this->isArray()) { + $this->type = self::TYPE_ARRAY; + } + } + /****************************************************************** * mode value *****************************************************************/ @@ -93,6 +137,63 @@ public function hasMode(int $mode): bool return ($this->mode & $mode) > 0; } + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * @param mixed $value + */ + public function setValue($value): void + { + // filter value by type + switch ($this->type) { + case self::TYPE_INT: + $value = (int)$value; + break; + case self::TYPE_BOOL: + $value = (bool)$value; + break; + case self::TYPE_FLOAT: + $value = (float)$value; + break; + case self::TYPE_STRING: + $value = (string)$value; + break; + // case self::TYPE_ARRAY: + // $value = (string)$value; + // break; + default: + // nothing + break; + } + + // has validator + if ($cb = $this->validator) { + $value = $cb($value); + // if (false === $ok) { + // throw new FlagException(''); + // } + } + + if ($this->isArray()) { + $this->value[] = $value; + } else { + $this->value = $value; + } + } + + /** + * @param callable $validator + */ + public function setValidator(callable $validator): void + { + $this->validator = $validator; + } /****************************************************************** * diff --git a/src/Flag/Flags.php b/src/Flag/Flags.php new file mode 100644 index 00000000..e335ab4a --- /dev/null +++ b/src/Flag/Flags.php @@ -0,0 +1,358 @@ +parse($args); + } + + /** + * @var string + */ + private $curOptKey = ''; + + private $parseStatus = self::STATUS_OK; + + public const STATUS_OK = 0; + public const STATUS_ERR = 1; + public const STATUS_END = 2; + public const STATUS_HELP = 3; // found `-h|--help` flag + + /** + * @param array|null $args + * + * @return array + */ + public function parse(array $args = null): array + { + if ($args === null) { + $args = $_SERVER['argv']; + } + + $this->parsed = true; + $this->rawArgs = $this->args = $args; + + while (true) { + [$goon, $status] = $this->parseOne(); + if ($goon) { + continue; + } + + if (self::STATUS_OK === $status) { + break; + } + } + + // binding remaining args. + if ($this->autoBindArgs && $this->args) { + $this->bindingArguments(); + } + + return []; + } + + /** + * parse one flag. + * + * will stop on: + * - found `-h|--help` flag + * - found first arg(not an option) + * + * @return array [bool, status] + */ + protected function parseOne(): array + { + $count = count($this->args); + if ($count === 0) { + return [false, self::STATUS_OK]; + } + + $args = $this->args; + $arg = array_shift($this->args); + + // empty, continue. + if ('' === $arg) { + return [true, self::STATUS_OK]; + } + + // is not an option flag. exit. + if ($arg[0] !== '-') { + $this->args = $args; // revert args on exit + return [false, self::STATUS_OK]; + } + + $name = ltrim($arg, '-'); + + // invalid arg. eg: '--' // ignore + if ('' === $name) { + return [true, self::STATUS_OK]; + } + + $value = ''; + $hasVal = false; + + $len = strlen($name); + for ($i = 0; $i < $len; $i++) { + if ($name[$i] === '=') { + $hasVal = true; + $name = substr($name, 0, $i); + + // fix: `--name=` no value string. + if ($i + 1 < $len) { + $value = substr($name, $i + 1); + } + } + } + + $rName = $this->resolveAlias($name); + if (!isset($this->defined[$rName])) { + throw new FlagException("flag option provided but not defined: $arg", 404); + } + + $opt = $this->defined[$rName]; + + // bool option default always set TRUE. + if ($opt->isBoolean()) { + $boolVal = true; + if ($hasVal) { + // only allow set bool value by --opt=false + $boolVal = self::filterBool($value); + } + + $opt->setValue($boolVal); + } else { + if (!$hasVal && count($this->args) > 0) { + // value is next arg + $hasVal = true; + $ntArg = $this->args[0]; + + // is not an option value. + if ($ntArg[0] === '-') { + $hasVal = false; + } else { + $value = array_shift($this->args); + } + } + + if (!$hasVal) { + throw new FlagException("flag option '$arg' needs an value", 400); + } + + // set value + $opt->setValue($value); + } + + $this->addMatched($opt); + return [true, self::STATUS_OK]; + } + + /** + * @param bool $clearDefined + */ + public function reset(bool $clearDefined = false): void + { + if ($clearDefined) { + $this->defined = []; + $this->resetArguments(); + } + + // clear match results + $this->parsed = false; + $this->matched = []; + $this->rawArgs = $this->args = []; + } + + // These words will be as a Boolean value + private const TRUE_WORDS = '|on|yes|true|'; + private const FALSE_WORDS = '|off|no|false|'; + + /** + * @param string $val + * + * @return bool|null + */ + public static function filterBool(string $val): ?bool + { + // check it is a bool value. + return false !== stripos(self::TRUE_WORDS, "|$val|"); + } + + /** + * @param string $val + * + * @return bool|null + */ + public static function filterBoolV2(string $val): ?bool + { + // check it is a bool value. + if (false !== stripos(self::TRUE_WORDS, "|$val|")) { + return true; + } + + if (false !== stripos(self::FALSE_WORDS, "|$val|")) { + return false; + } + + // return null; + return null; + } + + /************************************************************************** + * parse and binding command arguments + **************************************************************************/ + + /** + * parse and binding command arguments + * + * NOTICE: must call it on options parsed. + */ + public function bindingArguments(): void + { + if (!$this->args) { + return; + } + + // TODO ... + } + + /** + * @return callable + */ + public function getHelpRenderer(): callable + { + return $this->helpRenderer; + } + + /** + * @param callable $helpRenderer + */ + public function setHelpRenderer(callable $helpRenderer): void + { + $this->helpRenderer = $helpRenderer; + } + + /** + * @return array + */ + public function getRawArgs(): array + { + return $this->rawArgs; + } + + /** + * @return array + */ + public function getArgs(): array + { + return $this->args; + } + + /** + * @return bool + */ + public function isAutoBindArgs(): bool + { + return $this->autoBindArgs; + } + + /** + * @param bool $autoBindArgs + */ + public function setAutoBindArgs(bool $autoBindArgs): void + { + $this->autoBindArgs = $autoBindArgs; + } + + /** + * @return bool + */ + public function isParsed(): bool + { + return $this->parsed; + } +} diff --git a/src/Flag/Option.php b/src/Flag/Option.php index 42294f25..9845d86e 100644 --- a/src/Flag/Option.php +++ b/src/Flag/Option.php @@ -8,11 +8,10 @@ namespace Inhere\Console\Flag; -use Inhere\Console\IO\Input; use function implode; /** - * Class InputOption + * Class Option * - definition a input option * * @package Inhere\Console\Flag @@ -31,10 +30,10 @@ class Option extends Flag * * @var array */ - private $shortcuts = []; + private $shorts = []; /** - * eg: 'a|b' + * Shortcuts of the option, string format. eg: 'a|b' * * @var string */ @@ -45,7 +44,7 @@ class Option extends Flag */ public function isArray(): bool { - return $this->hasMode(Input::OPT_IS_ARRAY); + return $this->hasMode(Flag::OPT_IS_ARRAY); } /** @@ -53,7 +52,7 @@ public function isArray(): bool */ public function isOptional(): bool { - return $this->hasMode(Input::OPT_OPTIONAL); + return $this->hasMode(Flag::OPT_OPTIONAL); } /** @@ -61,7 +60,7 @@ public function isOptional(): bool */ public function isRequired(): bool { - return $this->hasMode(Input::OPT_REQUIRED); + return $this->hasMode(Flag::OPT_REQUIRED); } /** @@ -69,7 +68,7 @@ public function isRequired(): bool */ public function isBoolean(): bool { - return $this->hasMode(Input::OPT_BOOLEAN); + return $this->hasMode(Flag::OPT_BOOLEAN); } /** @@ -97,31 +96,31 @@ public function getShortcut(): string } /** - * @param string $shortcut + * @param string $shortcut eg: 'a|b' */ - public function setShortcutsByString(string $shortcut): void + public function setShortcut(string $shortcut): void { $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); $shortcuts = array_filter($shortcuts); - $this->setShortcuts($shortcuts); + $this->setShorts($shortcuts); } /** * @return array */ - public function getShortcuts(): array + public function getShorts(): array { - return $this->shortcuts; + return $this->shorts; } /** - * @param array $shortcuts + * @param array $shorts */ - public function setShortcuts(array $shortcuts): void + public function setShorts(array $shorts): void { - $this->shortcuts = $shortcuts; - $this->shortcut = implode('|', $shortcuts); + $this->shorts = $shorts; + $this->shortcut = implode('|', $shorts); } } diff --git a/src/Flag/Traits/FlagArgumentsTrait.php b/src/Flag/Traits/FlagArgumentsTrait.php new file mode 100644 index 00000000..4344e3ed --- /dev/null +++ b/src/Flag/Traits/FlagArgumentsTrait.php @@ -0,0 +1,81 @@ + index] + */ + private $name2index = []; + + /** + * @var Argument[] + */ + private $arguments = []; + + /** + * @param Argument $argument + */ + public function addArgument(Argument $argument): void + { + // record index + $this->name2index[$argument->getName()] = count($this->arguments); + // append + $this->arguments[] = $argument; + } + + /** + * @param string $name + * @param string $desc + * @param int|null $mode + * @param string|null $type The argument data type. (eg: 'string', 'array', 'mixed') + * @param null|mixed $default + * @param string $alias + */ + public function add( + string $name, + string $desc = '', + int $mode = 0, + string $type = '', + $default = null, + string $alias = '' + ): void { + $argObj = Argument::new($name, $desc, $mode, $default); + $argObj->setType($type); + $argObj->setAlias($alias); + + $this->addArgument($argObj); + } + + /** + * @return array + */ + public function getArguments(): array + { + return $this->arguments; + } + + /** + * @param array $arguments + */ + public function setArguments(array $arguments): void + { + $this->arguments = $arguments; + } + + protected function resetArguments(): void + { + $this->name2index = []; + $this->arguments = []; + } +} diff --git a/src/Flag/Traits/FlagOptionsTrait.php b/src/Flag/Traits/FlagOptionsTrait.php new file mode 100644 index 00000000..8e2799e3 --- /dev/null +++ b/src/Flag/Traits/FlagOptionsTrait.php @@ -0,0 +1,115 @@ +setShortcut($shorts); + + $this->addOption($opt); + } + + /** + * @param Option $option + */ + public function addOption(Option $option): void + { + $name = $option->getName(); + + if (isset($this->defined[$name])) { + throw new FlagException('cannot repeat option: ' . $name); + } + + // add to defined + $this->defined[$name] = $option; + } + + /** + * @param Option[] $options + */ + public function addOptions(array $options): void + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * @param string $name + * + * @return bool + */ + public function hasDefined(string $name): bool + { + return isset($this->defined[$name]); + } + + /** + * @param Option $option + */ + public function addMatched(Option $option): void + { + $name = $option->getName(); + // add to matched + $this->matched[$name] = $option; + } + + /** + * @param string $name + * + * @return bool + */ + public function hasMatched(string $name): bool + { + return isset($this->matched[$name]); + } + + /** + * @return Option[] + */ + public function getDefinedOptions(): array + { + return $this->defined; + } + + /** + * @return Option[] + */ + public function getMatchedOptions(): array + { + return $this->matched; + } +} \ No newline at end of file diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index 56606cd1..d992accc 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -30,7 +30,7 @@ abstract class AbstractInput implements InputInterface protected $pwd = ''; /** - * The script path + * The bin script path * e.g `./bin/app` OR `bin/cli.php` * * @var string @@ -38,7 +38,7 @@ abstract class AbstractInput implements InputInterface protected $script = ''; /** - * The script name + * The bin script name * e.g `app` OR `cli.php` * * @var string @@ -177,12 +177,24 @@ public function getScript(): string return $this->script; } + /** + * @return string + */ + public function getScriptPath(): string + { + return $this->script; + } + /** * @param string $script */ public function setScript(string $script): void { - $this->script = $script; + if ($script) { + $this->script = $script; + // update scriptName + $this->scriptName = basename($script); + } } /** diff --git a/test/TempStream.php b/src/IO/TempStream.php similarity index 94% rename from test/TempStream.php rename to src/IO/TempStream.php index bae3c2df..fb292c86 100644 --- a/test/TempStream.php +++ b/src/IO/TempStream.php @@ -1,6 +1,6 @@ addOption(Option::new('name')); + self::assertTrue($fs->hasDefined('name')); + self::assertFalse($fs->hasMatched('name')); + + $args = ['--name', 'inhere', 'arg0', 'arg1']; + $fs->parse($args); + self::assertTrue($fs->hasMatched('name')); + + $fs->reset(); + $args = ['--name', 'inhere', '-s', 'sv', '-f']; + self::expectException(FlagException::class); + $fs->parse($args); + } +} diff --git a/test/boot.php b/test/bootstrap.php similarity index 100% rename from test/boot.php rename to test/bootstrap.php From 53485de5cc367e3284d41c518f10195bda2f91cc Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 17 Aug 2021 13:19:11 +0800 Subject: [PATCH 124/258] feat: add new method for check many option exists --- src/AbstractApplication.php | 3 +- src/Concern/InputOptionsTrait.php | 53 ++++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 3bb25fa2..19cadbf3 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -43,7 +43,6 @@ use function set_error_handler; use function set_exception_handler; use function trim; -use function vdump; use const PHP_SAPI; /** @@ -714,7 +713,7 @@ public function getVerbLevel(): int $key = GlobalOption::DEBUG; // feat: support set debug level by ENV var: CONSOLE_DEBUG - $envVal = OS::getEnvVal(Console::DEBUG_ENV_KEY); + $envVal = OS::getEnvStrVal(Console::DEBUG_ENV_KEY); if ($envVal !== '') { $setVal = (int)$envVal; } else { diff --git a/src/Concern/InputOptionsTrait.php b/src/Concern/InputOptionsTrait.php index 56aa2400..7182d462 100644 --- a/src/Concern/InputOptionsTrait.php +++ b/src/Concern/InputOptionsTrait.php @@ -82,7 +82,7 @@ public function getRequiredOpt(string $name, string $errMsg = '') return $val; } - $errMsg = $errMsg ?: "The option '{$name}' is required"; + $errMsg = $errMsg ?: "The option '$name' is required"; throw new PromptException($errMsg); } @@ -103,7 +103,7 @@ public function getStringOpt(string $name, string $default = ''): string * Get an string option(long/short) value * * @param string|string[] $names eg 'n,name' OR ['n', 'name'] - * @param string $default + * @param string $default * * @return string */ @@ -129,7 +129,7 @@ public function getIntOpt(string $name, int $default = 0): int * Get an int option(long/short) value * * @param string|string[] $names eg 'l,length' OR ['l', 'length'] - * @param int $default + * @param int $default * * @return int */ @@ -157,7 +157,7 @@ public function getBoolOpt(string $name, bool $default = false): bool * eg: -h --help * * @param string|string[] $names eg 'n,name' OR ['n', 'name'] - * @param bool $default + * @param bool $default * * @return bool */ @@ -182,7 +182,7 @@ public function boolOpt(string $name, bool $default = false): bool /** * check option exists * - * @param $name + * @param string $name * * @return bool */ @@ -191,6 +191,35 @@ public function hasOpt(string $name): bool return isset($this->sOpts[$name]) || isset($this->lOpts[$name]); } + /** + * The give options exists + * + * ```php + * $input->hasOneOpt('h,help'); + * $input->hasOneOpt(['h','help']); + * ``` + * + * @param string|array $names + * + * @return bool + */ + public function hasOneOpt($names): bool + { + if (is_string($names)) { + $names = array_map('trim', explode(',', $names)); + } elseif (!is_array($names)) { + $names = (array)$names; + } + + foreach ($names as $name) { + if ($this->hasOpt($name)) { + return true; + } + } + + return false; + } + /** * Get same opts value * eg: -h --help @@ -201,7 +230,7 @@ public function hasOpt(string $name): bool * ``` * * @param string|string[] $names eg 'n,name' OR ['n', 'name'] - * @param mixed $default + * @param mixed $default * * @return bool|mixed|null */ @@ -226,7 +255,7 @@ public function getSameOpt($names, $default = null) * Alias of the getSameOpt() * * @param string|array $names - * @param mixed $default + * @param mixed $default * * @return bool|mixed|null */ @@ -333,8 +362,8 @@ public function findOneShortOpts(array $names): string /** * get short-opt value(bool) * - * @param string $name - * @param bool $default + * @param string $name + * @param bool|mixed $default * * @return bool */ @@ -443,8 +472,8 @@ public function hasLOpt(string $name): bool /** * get long-opt value(bool) * - * @param string $name - * @param bool $default + * @param string $name + * @param bool|mixed $default * * @return bool */ @@ -465,7 +494,7 @@ public function getLongOpts(): array /** * @param string $name - * @param $value + * @param mixed $value */ public function setLOpt(string $name, $value): void { From 6934fc4bebe8ef932ed8115ed3933b95783ea415 Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 19 Aug 2021 01:05:36 +0800 Subject: [PATCH 125/258] add new method for print JSON --- src/Component/Progress/CounterText.php | 2 +- src/Concern/FormatOutputAwareTrait.php | 14 ++++++ src/Concern/StyledOutputAwareTrait.php | 1 + src/Util/Show.php | 60 ++++++++++++-------------- 4 files changed, 44 insertions(+), 33 deletions(-) diff --git a/src/Component/Progress/CounterText.php b/src/Component/Progress/CounterText.php index 26d4600a..831c24da 100644 --- a/src/Component/Progress/CounterText.php +++ b/src/Component/Progress/CounterText.php @@ -35,7 +35,7 @@ class CounterText extends NotifyMessage * * @return Generator */ - public static function gen(string $msg, $doneMsg = ''): Generator + public static function gen(string $msg, string $doneMsg = ''): Generator { $counter = 0; $finished = false; diff --git a/src/Concern/FormatOutputAwareTrait.php b/src/Concern/FormatOutputAwareTrait.php index 3c737441..ff959737 100644 --- a/src/Concern/FormatOutputAwareTrait.php +++ b/src/Concern/FormatOutputAwareTrait.php @@ -9,6 +9,7 @@ namespace Inhere\Console\Concern; use Inhere\Console\Console; +use Toolkit\Stdlib\Helper\JsonHelper; use Toolkit\Stdlib\Php; use function array_merge; use function json_encode; @@ -110,6 +111,19 @@ public function json( return $string; } + /** + * @param mixed $data + * @param string $title + */ + public function prettyJSON($data, string $title = 'JSON:'): void + { + if ($title) { + Console::colored($title, 'ylw0'); + } + + Console::write(JsonHelper::prettyJSON($data)); + } + /** * @param mixed ...$vars */ diff --git a/src/Concern/StyledOutputAwareTrait.php b/src/Concern/StyledOutputAwareTrait.php index ab941ba3..3bdbbe2c 100644 --- a/src/Concern/StyledOutputAwareTrait.php +++ b/src/Concern/StyledOutputAwareTrait.php @@ -59,6 +59,7 @@ * @method checkbox(string $description, $options, $default = null, bool $allowExit = true): array * @method ask(string $question, string $default = '', Closure $validator = null): string * @method askPassword(string $prompt = 'Enter Password:'): string + * @see Interact */ trait StyledOutputAwareTrait { diff --git a/src/Util/Show.php b/src/Util/Show.php index 94f6df6a..ee3f34f2 100644 --- a/src/Util/Show.php +++ b/src/Util/Show.php @@ -21,6 +21,7 @@ use Toolkit\Cli\Cli; use Toolkit\Cli\ColorTag; use Toolkit\Cli\Style; +use Toolkit\Stdlib\Helper\JsonHelper; use Toolkit\Stdlib\Math; use Toolkit\Stdlib\Str; use Toolkit\Sys\Sys; @@ -30,7 +31,6 @@ use function implode; use function is_array; use function is_string; -use function json_encode; use function microtime; use function sprintf; use function strlen; @@ -38,9 +38,6 @@ use function strtoupper; use function substr; use function ucwords; -use const JSON_PRETTY_PRINT; -use const JSON_UNESCAPED_SLASHES; -use const JSON_UNESCAPED_UNICODE; use const PHP_EOL; /** @@ -208,17 +205,15 @@ public static function __callStatic(string $method, array $args = []) * Print JSON * * @param mixed $data - * @param int $flags - * - * @return int + * @param string $title */ - public static function prettyJSON( - $data, - int $flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES - ): int { - $string = (string)json_encode($data, $flags); + public static function prettyJSON($data, string $title = 'JSON:'): void + { + if ($title) { + Console::colored($title, 'ylw0'); + } - return Console::write($string); + Console::write(JsonHelper::prettyJSON($data)); } /** @@ -424,7 +419,7 @@ public static function table(array $data, string $title = 'Data Table', array $o * @param string $msg * @param bool $ended */ - public static function spinner(string $msg = '', $ended = false): void + public static function spinner(string $msg = '', bool $ended = false): void { static $chars = '-\|/'; static $counter = 0; @@ -457,7 +452,7 @@ public static function spinner(string $msg = '', $ended = false): void * @param string $msg * @param bool $ended */ - public static function loading(string $msg = 'Loading ', $ended = false): void + public static function loading(string $msg = 'Loading ', bool $ended = false): void { self::pending($msg, $ended); } @@ -476,7 +471,7 @@ public static function loading(string $msg = 'Loading ', $ended = false): void * @param string $msg * @param bool $ended */ - public static function pending(string $msg = 'Pending ', $ended = false): void + public static function pending(string $msg = 'Pending ', bool $ended = false): void { static $counter = 0; static $lastTime = null; @@ -516,9 +511,9 @@ public static function pending(string $msg = 'Pending ', $ended = false): void * @param string $msg * @param bool $ended * - * @return int|mixed + * @return int */ - public static function pointing(string $msg = 'handling ', $ended = false) + public static function pointing(string $msg = 'handling ', bool $ended = false): int { static $counter = 0; @@ -532,40 +527,41 @@ public static function pointing(string $msg = 'handling ', $ended = false) $counter++; - return print '.'; + print '.'; + return 0; } /** * 与文本进度条相比,没有 total * * @param string $msg - * @param string|null $doneMsg + * @param string $doneMsg * * @return Generator */ - public static function counterTxt(string $msg, $doneMsg = ''): Generator + public static function counterTxt(string $msg, string $doneMsg = ''): Generator { return CounterText::gen($msg, $doneMsg); } /** * @param string $doneMsg - * @param string|null $fixMsg + * @param string $fixMsg * * @return Generator */ - public static function dynamicTxt(string $doneMsg, string $fixMsg = null): Generator + public static function dynamicTxt(string $doneMsg, string $fixMsg = ''): Generator { return self::dynamicText($doneMsg, $fixMsg); } /** * @param string $doneMsg - * @param string|null $fixedMsg + * @param string $fixedMsg * * @return Generator */ - public static function dynamicText(string $doneMsg, string $fixedMsg = null): Generator + public static function dynamicText(string $doneMsg, string $fixedMsg = ''): Generator { return DynamicText::gen($doneMsg, $fixedMsg); } @@ -685,14 +681,14 @@ public static function clearBuffer(): void * * @param bool $flush Whether flush buffer to output stream * @param bool $nl Default is False, because the last write() have been added "\n" - * @param bool $quit + * @param bool|int $quit * @param array $opts * * @return null|string If flush = False, will return all buffer text. * @see Show::write() * @deprecated Please use \Inhere\Console\Console method instead it. */ - public static function stopBuffer($flush = true, $nl = false, $quit = false, array $opts = []): ?string + public static function stopBuffer(bool $flush = true, bool $nl = false, $quit = false, array $opts = []): ?string { self::$buffering = false; @@ -720,7 +716,7 @@ public static function stopBuffer($flush = true, $nl = false, $quit = false, arr * @see Show::write() * @deprecated Please use \Inhere\Console\Console method instead it. */ - public static function flushBuffer($nl = false, $quit = false, array $opts = []): void + public static function flushBuffer(bool $nl = false, $quit = false, array $opts = []): void { self::stopBuffer(true, $nl, $quit, $opts); } @@ -747,7 +743,7 @@ public static function writef(string $format, ...$args): int * * @param string|array $messages Output message * @param boolean $nl True 会添加换行符, False 原样输出,不添加换行符 - * @param int|boolean $quit If is int, setting it is exit code. 'True' translate as code 0 and exit, 'False' will not exit. + * @param int|bool $quit If is int, setting it is exit code. 'True' translate as code 0 and exit, 'False' will not exit. * @param array $opts Some options for write * refer: * [ @@ -758,7 +754,7 @@ public static function writef(string $format, ...$args): int * * @return int */ - public static function write($messages, $nl = true, $quit = false, array $opts = []): int + public static function write($messages, bool $nl = true, $quit = false, array $opts = []): int { return Console::write($messages, $nl, $quit, $opts); } @@ -801,7 +797,7 @@ public static function writeln($message, $quit = false, array $opts = []): int * * @return int */ - public static function colored($message, string $style = 'info', $nl = true, array $opts = []): int + public static function colored($message, string $style = 'info', bool $nl = true, array $opts = []): int { $quit = isset($opts['quit']) ? (bool)$opts['quit'] : false; @@ -817,7 +813,7 @@ public static function colored($message, string $style = 'info', $nl = true, arr * * @return array */ - public static function getBlockMethods($onlyKey = true): array + public static function getBlockMethods(bool $onlyKey = true): array { return $onlyKey ? array_keys(self::$blockMethods) : self::$blockMethods; } From 28f6cd0bff3ddba91abec00a4d864fe948ec5365 Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 19 Aug 2021 20:09:27 +0800 Subject: [PATCH 126/258] up: update the list data show logic --- src/AbstractApplication.php | 4 +-- src/Component/Formatter/SingleList.php | 8 ++--- src/Concern/ApplicationHelpTrait.php | 49 ++++++++++++++++---------- src/Util/FormatUtil.php | 16 +++++---- src/Util/Show.php | 2 +- 5 files changed, 46 insertions(+), 33 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 19cadbf3..11684edd 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -90,6 +90,7 @@ abstract class AbstractApplication implements ApplicationInterface 'debug' => Console::VERB_ERROR, 'profile' => false, 'version' => '0.5.1', + 'homepage' => '', // can provide you app homepage url 'publishAt' => '2017.03.24', 'updateAt' => '2019.01.01', 'rootPath' => '', @@ -384,7 +385,7 @@ public function handleError(int $num, string $str, string $file, int $line): voi * * @throws InvalidArgumentException */ - public function handleException($e): void + public function handleException(Throwable $e): void { // you can log error on sub class ... $this->errorHandler->handle($e, $this); @@ -501,7 +502,6 @@ protected function startInteractiveShell(): void $in->parse($args); $in->setFullScript($line); - // \vdump($in); $this->run(false); $out->println(''); } diff --git a/src/Component/Formatter/SingleList.php b/src/Component/Formatter/SingleList.php index 0d24f998..dc2cefa0 100644 --- a/src/Component/Formatter/SingleList.php +++ b/src/Component/Formatter/SingleList.php @@ -29,9 +29,9 @@ class SingleList extends MessageFormatter * ]; * ``` * - * @param array $data - * @param string $title - * @param array $opts More {@see FormatUtil::spliceKeyValue()} + * @param array|mixed $data + * @param string $title + * @param array $opts More {@see FormatUtil::spliceKeyValue()} * * @return int|string */ @@ -52,7 +52,7 @@ public static function show($data, string $title = 'Information', array $opts = // title if ($title) { - $title = $opts['ucTitleWords'] ? Str::ucwords(trim($title)) : $title; + $title = $opts['ucTitleWords'] ? Str::ucwords(trim($title)) : $title; $string .= ColorTag::wrap($title, $opts['titleStyle']) . PHP_EOL; } diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index 750bb358..0c297e06 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -14,7 +14,6 @@ use Inhere\Console\Contract\CommandInterface; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; -use Inhere\Console\Router; use Inhere\Console\Util\FormatUtil; use Inhere\Console\Util\Show; use Toolkit\Cli\ColorTag; @@ -39,6 +38,7 @@ use const PHP_EOL; use const PHP_OS; use const PHP_VERSION; +use const STR_PAD_LEFT; /** * Trait ApplicationHelpTrait @@ -61,32 +61,46 @@ trait ApplicationHelpTrait */ public function showVersionInfo(): void { - $os = PHP_OS; - $date = date('Y.m.d'); + $this->fire(ConsoleEvent::BEFORE_RENDER_APP_VERSION, $this); + + Show::aList($this->buildVersionInfo(), '', [ + 'leftChar' => '', + 'sepChar' => ' : ', + 'keyPadPos' => STR_PAD_LEFT, + ]); + } + + /** + * @return string[] + */ + protected function buildVersionInfo(): array + { $logo = ''; + $date = date('Y.m.d'); $name = $this->getParam('name', 'Console Application'); - $version = $this->getParam('version', 'Unknown'); - $publishAt = $this->getParam('publishAt', 'Unknown'); - $updateAt = $this->getParam('updateAt', 'Unknown'); - $phpVersion = PHP_VERSION; + $osName = PHP_OS; + $phpVer = PHP_VERSION; + $version = $this->getParam('version', 'Unknown'); - $this->fire(ConsoleEvent::BEFORE_RENDER_APP_VERSION, $this); + $updateAt = $this->getParam('updateAt', 'Unknown'); + $publishAt = $this->getParam('publishAt', 'Unknown'); if ($logoTxt = $this->getLogoText()) { $logo = ColorTag::wrap($logoTxt, $this->getLogoStyle()); } - /** @var Output $out */ - $out = $this->output; - $out->aList([ + $info = [ "$logo\n $name, Version $version\n", - 'System Info' => "PHP version $phpVersion, on $os system", + 'System Info' => "PHP version $phpVer, on $osName system", 'Application Info' => "Update at $updateAt, publish at $publishAt(current $date)", - ], '', [ - 'leftChar' => '', - 'sepChar' => ' : ' - ]); + ]; + + if ($hUrl = $this->getParam('homepage')) { + $info['Homepage URL'] = $hUrl; + } + + return $info; } /** @@ -183,8 +197,6 @@ public function showCommandList(): void $this->logf(Console::VERB_DEBUG, 'Display the application commands list'); - /** @var Output $output */ // $output = $this->output; - /** @var Router $router */ $router = $this->getRouter(); $hasGroup = $hasCommand = false; @@ -306,7 +318,6 @@ protected function dumpAutoCompletion(string $shellEnv, array $data): void $input = $this->input; /** @var Output $output */ $output = $this->output; - /** @var Router $router */ $router = $this->getRouter(); // info diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index 53e0a32c..c1bbd1c2 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -10,6 +10,7 @@ use Toolkit\Cli\ColorTag; use Toolkit\Stdlib\Helper\JsonHelper; +use Toolkit\Stdlib\Str; use Toolkit\Sys\Sys; use function array_keys; use function array_merge; @@ -17,7 +18,6 @@ use function date; use function explode; use function floor; -use function gettype; use function implode; use function is_array; use function is_bool; @@ -26,13 +26,13 @@ use function is_scalar; use function rtrim; use function sprintf; -use function str_pad; use function str_repeat; use function str_replace; use function strpos; use function trim; use function ucfirst; use function wordwrap; +use const STR_PAD_RIGHT; /** * Class FormatUtil @@ -232,7 +232,7 @@ public static function howLongAgo(int $secs): string } /** - * splice Array + * Splice array * * @param array $data * e.g [ @@ -251,12 +251,13 @@ public static function spliceKeyValue(array $data, array $opts = []): string 'sepChar' => ' ', // e.g ' | ' OUT: key | value 'keyStyle' => '', // e.g 'info','comment' 'valStyle' => '', // e.g 'info','comment' + 'keyPadPos' => STR_PAD_RIGHT, 'keyMinWidth' => 8, - 'keyMaxWidth' => null, // if not set, will automatic calculation + 'keyMaxWidth' => 0, // if not set, will automatic calculation 'ucFirst' => true, // upper first char for value ], $opts); - if (!is_numeric($opts['keyMaxWidth'])) { + if ($opts['keyMaxWidth'] < 1) { $opts['keyMaxWidth'] = Helper::getKeyMaxWidth($data); } @@ -265,14 +266,15 @@ public static function spliceKeyValue(array $data, array $opts = []): string $opts['keyMaxWidth'] = $opts['keyMinWidth']; } - $keyStyle = trim($opts['keyStyle']); + $keyStyle = trim($opts['keyStyle']); + $keyPadPos = (int)$opts['keyPadPos']; foreach ($data as $key => $value) { $hasKey = !is_int($key); $text .= $opts['leftChar']; if ($hasKey && $opts['keyMaxWidth']) { - $key = str_pad((string)$key, $opts['keyMaxWidth'], ' '); + $key = Str::pad((string)$key, $opts['keyMaxWidth'], ' ', $keyPadPos); $text .= ColorTag::wrap($key, $keyStyle) . $opts['sepChar']; } diff --git a/src/Util/Show.php b/src/Util/Show.php index ee3f34f2..708115fb 100644 --- a/src/Util/Show.php +++ b/src/Util/Show.php @@ -288,7 +288,7 @@ public static function padding(array $data, string $title = '', array $opts = [] * ]; * ``` * - * @param array $data + * @param array|object $data * @param string $title * @param array $opts More {@see FormatUtil::spliceKeyValue()} * From 5181a372e1eca9dafc9160fe146eb00c599ad658 Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 20 Aug 2021 13:30:18 +0800 Subject: [PATCH 127/258] update some for phar building --- phar.build.inc | 7 ++- src/Application.php | 4 +- src/BuiltIn/PharController.php | 11 ++--- src/Component/PharCompiler.php | 89 ++++++++++++++++++++-------------- 4 files changed, 64 insertions(+), 47 deletions(-) diff --git a/phar.build.inc b/phar.build.inc index 5b8191f9..652f6855 100644 --- a/phar.build.inc +++ b/phar.build.inc @@ -14,6 +14,9 @@ $compiler ->setShebang(true) ->addExclude([ 'demo', + 'example', + 'runtime', + 'node_modules', 'test', 'tmp', ]) @@ -21,7 +24,7 @@ $compiler 'LICENSE', 'composer.json', 'README.md', - 'test/boot.php', + 'test/bootstrap.php', ]) ->setCliIndex('examples/app') // ->setWebIndex('web/index.php') @@ -30,7 +33,7 @@ $compiler // Console 下的 Command Controller 命令类不去除注释,注释上是命令帮助信息 $compiler->setStripFilter(static function ($file) { - /** @var \SplFileInfo $file */ + /** @var SplFileInfo $file */ $name = $file->getFilename(); return false === strpos($name, 'Command.php') && false === strpos($name, 'Controller.php'); diff --git a/src/Application.php b/src/Application.php index 8c528c53..bb0942eb 100644 --- a/src/Application.php +++ b/src/Application.php @@ -14,7 +14,6 @@ use Inhere\Console\IO\Output; use Inhere\Console\Util\Helper; use InvalidArgumentException; -use ReflectionException; use RuntimeException; use SplFileInfo; use function class_exists; @@ -285,7 +284,7 @@ public function dispatch(string $name, bool $detachedRun = false) } $commands = $this->router->getAllNames(); - $this->output->error("The command '{$name}' is not exists!"); + $this->output->error("The command '$name' is not exists!"); // find similar command names by similar_text() if ($similar = Helper::findSimilar($name, $commands)) { @@ -360,7 +359,6 @@ protected function runCommand(string $name, $handler, array $options) * @param bool $detachedRun * * @return mixed - * @throws ReflectionException */ protected function runAction(array $info, array $options, bool $detachedRun = false) { diff --git a/src/BuiltIn/PharController.php b/src/BuiltIn/PharController.php index 349f87dc..6d26cb43 100644 --- a/src/BuiltIn/PharController.php +++ b/src/BuiltIn/PharController.php @@ -72,10 +72,10 @@ protected function packConfigure(Input $input): void * default is current work-dir(default: {workDir}) * -c, --config STRING Use the custom config file for build phar(default: ./phar.build.inc) * -o, --output STRING Setting the output file name({defaultPkgName}) - * --fast Fast build. only add modified files by git status -s - * --refresh Whether build vendor folder files on phar file exists(False) - * --files STRING Only pack the list files to the exist phar, multi use ',' split - * --no-progress Disable output progress on the runtime + * --fast Fast build. only add modified files by git status -s + * --refresh Whether build vendor folder files on phar file exists(False) + * --files STRING Only pack the list files to the exist phar, multi use ',' split + * --no-progress Disable output progress on the runtime * * @param Input $input * @param Output $output @@ -92,7 +92,7 @@ protected function packConfigure(Input $input): void * only update the input files: * php -d phar.readonly=0 {binFile} phar:pack -o=mycli.phar --debug --files app/Command/ServeCommand.php */ - public function packCommand($input, $output): int + public function packCommand(Input $input, Output $output): int { $startAt = microtime(true); $workDir = $input->getPwd(); @@ -169,7 +169,6 @@ protected function configCompiler(string $dir): PharCompiler $configFile = $this->input->getSameOpt(['c', 'config']) ?: $dir . '/phar.build.inc'; if ($configFile && is_file($configFile)) { - /** @noinspection PhpIncludeInspection */ require $configFile; return $compiler->in($dir); } diff --git a/src/Component/PharCompiler.php b/src/Component/PharCompiler.php index ee9839b4..68f5ef54 100644 --- a/src/Component/PharCompiler.php +++ b/src/Component/PharCompiler.php @@ -8,6 +8,7 @@ use DateTimeZone; use Exception; use FilesystemIterator; +use Generator; use Inhere\Console\Util\Helper; use InvalidArgumentException; use Iterator; @@ -116,20 +117,29 @@ class PharCompiler private $suffixes = ['.php']; /** - * @var array Want to exclude directory/file name list + * Want to exclude directory/file name list + * + * ```php * [ * '/test/', // exclude all contains '/test/' path * ] + * ``` + * + * @var array */ private $excludes = []; /** - * @var array The directory paths, will collect files in there. + * The directory paths, will collect files in there. + * + * @var array */ private $directories = []; /** - * @var Closure[] Some events. if you want to get some info on packing. + * Some events. if you want to get some info on packing. + * + * @var Closure[] */ private $events = []; @@ -178,6 +188,9 @@ class PharCompiler */ private $versionFile = ''; + /** + * @var string + */ private $versionFileContent = ''; // -------------------- internal properties -------------------- @@ -210,6 +223,22 @@ class PharCompiler */ private $fileQueue; + /** + * @throws RuntimeException + */ + private static function checkEnv(): void + { + if (!class_exists(Phar::class, false)) { + throw new RuntimeException("The 'phar' extension is required for build phar package"); + } + + if (ini_get('phar.readonly')) { + throw new RuntimeException( + "The 'phar.readonly' is 'On', build phar must setting it 'Off' or exec with 'php -d phar.readonly=0'" + ); + } + } + /** * @param string $pharFile * @param string $extractTo @@ -220,7 +249,7 @@ class PharCompiler * @throws BadMethodCallException * @throws RuntimeException */ - public static function unpack(string $pharFile, string $extractTo, $files = null, $overwrite = false): bool + public static function unpack(string $pharFile, string $extractTo, $files = null, bool $overwrite = false): bool { self::checkEnv(); @@ -229,22 +258,6 @@ public static function unpack(string $pharFile, string $extractTo, $files = null return $phar->extractTo($extractTo, $files, $overwrite); } - /** - * @throws RuntimeException - */ - private static function checkEnv(): void - { - if (!class_exists(Phar::class, false)) { - throw new RuntimeException("The 'phar' extension is required for build phar package"); - } - - if (ini_get('phar.readonly')) { - throw new RuntimeException( - "The 'phar.readonly' is 'On', build phar must setting it 'Off' or exec with 'php -d phar.readonly=0'" - ); - } - } - /** * PharCompiler constructor. * @@ -256,7 +269,7 @@ public function __construct(string $basePath) { self::checkEnv(); - $this->basePath = realpath($basePath); + $this->basePath = File::realpath($basePath); $this->fileQueue = new SplQueue(); if (!is_dir($this->basePath)) { @@ -352,7 +365,7 @@ public function setExcludes(array $excludes): self } /** - * @param bool $value + * @param bool|string|int $value * * @return PharCompiler */ @@ -363,7 +376,7 @@ public function stripComments($value): self } /** - * @param bool $value + * @param bool|string|int $value * * @return PharCompiler */ @@ -578,9 +591,9 @@ protected function collectFileInfo(SplFileInfo $file): void * * @throws RuntimeException */ - public function findChangedByGit() + public function findChangedByGit(): ?Generator { - // -u expand dir's files + // -u expand dir files [, $output,] = Sys::run('git status -s -u', $this->basePath); // 'D some.file' deleted @@ -608,10 +621,10 @@ public function findChangedByGit() /** * @param string $directory * - * @return Iterator|SplFileInfo[] + * @return Iterator * @throws InvalidArgumentException */ - protected function findFiles(string $directory) + protected function findFiles(string $directory): Iterator { return Helper::directoryIterator( $directory, @@ -711,19 +724,21 @@ private function createStub(): string $stub = "$shebang\n$stub"; } - if ($this->cliIndex && $this->webIndex) { + $cliIndex = $this->cliIndex; + $webIndex = $this->webIndex; + if ($cliIndex && $webIndex) { $stub .= <<cliIndex}'; + require 'phar://$pharName/$cliIndex'; } else { - require 'phar://$pharName/{$this->webIndex}'; + require 'phar://$pharName/$webIndex'; } EOF; - } elseif ($this->cliIndex) { - $stub .= "\nrequire 'phar://$pharName/{$this->cliIndex}';\n"; - } elseif ($this->webIndex) { - $stub .= "\nrequire 'phar://$pharName/{$this->webIndex}';\n"; + } elseif ($cliIndex) { + $stub .= "\nrequire 'phar://$pharName/$cliIndex';\n"; + } elseif ($webIndex) { + $stub .= "\nrequire 'phar://$pharName/$webIndex';\n"; } else { throw new RuntimeException("'cliIndex' and 'webIndex', please set at least one"); } @@ -989,11 +1004,13 @@ public function setVersionFile(string $versionFile): PharCompiler } /** + * @param bool $abbrev + * * @return string */ - public function getLastCommit(): string + public function getLastCommit(bool $abbrev = true): string { - return $this->lastCommit; + return $abbrev ? substr($this->lastCommit, 0, 7) : $this->lastCommit; } /** From 6b0881c9750577ca40e7c7308d4d7970d064b2e9 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 22 Aug 2021 01:18:58 +0800 Subject: [PATCH 128/258] add some new idea for flags parse --- src/Flag/Flags.php | 66 +++++------------------ src/Flag/SFlags.php | 62 +++++++++++++++++++++ src/Flag/Traits/FlagParsingTrait.php | 81 ++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 54 deletions(-) create mode 100644 src/Flag/SFlags.php create mode 100644 src/Flag/Traits/FlagParsingTrait.php diff --git a/src/Flag/Flags.php b/src/Flag/Flags.php index e335ab4a..e7a9d6ca 100644 --- a/src/Flag/Flags.php +++ b/src/Flag/Flags.php @@ -6,6 +6,8 @@ use Inhere\Console\Exception\FlagException; use Inhere\Console\Flag\Traits\FlagArgumentsTrait; use Inhere\Console\Flag\Traits\FlagOptionsTrait; +use Inhere\Console\Flag\Traits\FlagParsingTrait; +use Toolkit\Stdlib\Obj\AbstractObj; use function array_shift; use function count; use function ltrim; @@ -17,10 +19,11 @@ * * @package Inhere\Console\Flag */ -class Flags +class Flags extends AbstractObj { use FlagArgumentsTrait; use FlagOptionsTrait; + use FlagParsingTrait; use NameAliasTrait; /** @@ -33,38 +36,11 @@ class Flags */ private $helpRenderer; - /** - * @var bool - */ - private $parsed = false; - /** * @var bool */ private $autoBindArgs = false; - /** - * The raw input args - * - * @var array - */ - private $rawArgs = []; - - /** - * The remaining args on parsed - * - * @var array - */ - private $args = []; - - /** - * @return $this - */ - public static function new(): self - { - return new self(); - } - /** * @return $this */ @@ -143,7 +119,7 @@ public function parse(array $args = null): array * - found `-h|--help` flag * - found first arg(not an option) * - * @return array [bool, status] + * @return array [goon: bool, status: int] */ protected function parseOne(): array { @@ -166,6 +142,11 @@ protected function parseOne(): array return [false, self::STATUS_OK]; } + // NOTICE: will stop parse option on found '--' + if ($arg === '--') { + return [false, self::STATUS_OK]; + } + $name = ltrim($arg, '-'); // invalid arg. eg: '--' // ignore @@ -209,7 +190,7 @@ protected function parseOne(): array if (!$hasVal && count($this->args) > 0) { // value is next arg $hasVal = true; - $ntArg = $this->args[0]; + $ntArg = $this->args[0]; // is not an option value. if ($ntArg[0] === '-') { @@ -242,7 +223,7 @@ public function reset(bool $clearDefined = false): void } // clear match results - $this->parsed = false; + $this->parsed = false; $this->matched = []; $this->rawArgs = $this->args = []; } @@ -316,22 +297,6 @@ public function setHelpRenderer(callable $helpRenderer): void $this->helpRenderer = $helpRenderer; } - /** - * @return array - */ - public function getRawArgs(): array - { - return $this->rawArgs; - } - - /** - * @return array - */ - public function getArgs(): array - { - return $this->args; - } - /** * @return bool */ @@ -348,11 +313,4 @@ public function setAutoBindArgs(bool $autoBindArgs): void $this->autoBindArgs = $autoBindArgs; } - /** - * @return bool - */ - public function isParsed(): bool - { - return $this->parsed; - } } diff --git a/src/Flag/SFlags.php b/src/Flag/SFlags.php new file mode 100644 index 00000000..d742aa44 --- /dev/null +++ b/src/Flag/SFlags.php @@ -0,0 +1,62 @@ + [], // ['debug', 'h'] + // Whether merge short-opts and long-opts + 'mergeOpts' => false, + // Only want parsed options. + // if not empty, will ignore no matched + 'wantParsedOpts' => [], + // List of option allow array values. + 'arrayOpts' => [], // ['names', 'status'] + // Special short style + // posix: -abc will expand: -a -b -c + // unix: -abc will expand: -a=bc + 'shortStyle' => 'posix', + ]; + + /** + * @param array $rawArgs + * @param array $settings + * + * @return array + */ + public function parse(array $rawArgs, array $settings = []): array + { + $this->setSettings($settings); + } + + /** + * @return array + */ + public function getSettings(): array + { + return $this->settings; + } + + /** + * @param array $settings + */ + public function setSettings(array $settings): void + { + $this->settings = array_merge($this->settings, $settings); + } +} diff --git a/src/Flag/Traits/FlagParsingTrait.php b/src/Flag/Traits/FlagParsingTrait.php new file mode 100644 index 00000000..a492141e --- /dev/null +++ b/src/Flag/Traits/FlagParsingTrait.php @@ -0,0 +1,81 @@ +args; + } + + /** + * @return array + */ + public function getRawArgs(): array + { + return $this->rawArgs; + } + + /** + * @return bool + */ + public function isParsed(): bool + { + return $this->parsed; + } + + /** + * @return bool + */ + public function isStopOnArg(): bool + { + return $this->stopOnArg; + } + + /** + * @param bool $stopOnArg + * + * @return static + */ + public function setStopOnArg(bool $stopOnArg): self + { + $this->stopOnArg = $stopOnArg; + return $this; + } +} \ No newline at end of file From f74ec9c3d1eb612320c45f7d444e9c3d6aebafde Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 22 Aug 2021 01:46:12 +0800 Subject: [PATCH 129/258] add more setting for flags parse --- src/Flag/Flags.php | 2 +- src/Flag/SFlags.php | 65 +++++++++++++++++++++++++--- src/Flag/Traits/FlagParsingTrait.php | 17 ++++---- 3 files changed, 70 insertions(+), 14 deletions(-) diff --git a/src/Flag/Flags.php b/src/Flag/Flags.php index e7a9d6ca..d260abc6 100644 --- a/src/Flag/Flags.php +++ b/src/Flag/Flags.php @@ -91,7 +91,7 @@ public function parse(array $args = null): array } $this->parsed = true; - $this->rawArgs = $this->args = $args; + $this->rawFlags = $this->args = $args; while (true) { [$goon, $status] = $this->parseOne(); diff --git a/src/Flag/SFlags.php b/src/Flag/SFlags.php index d742aa44..48a42fc7 100644 --- a/src/Flag/SFlags.php +++ b/src/Flag/SFlags.php @@ -28,18 +28,73 @@ class SFlags extends AbstractObj // List of option allow array values. 'arrayOpts' => [], // ['names', 'status'] // Special short style - // posix: -abc will expand: -a -b -c - // unix: -abc will expand: -a=bc - 'shortStyle' => 'posix', + // gnu: `-abc` will expand: `-a -b -c` + // posix: `-abc` will expand: `-a=bc` + 'shortStyle' => 'posix', ]; /** + * Special short style + * gnu: `-abc` will expand: `-a -b -c` + * posix: `-abc` will expand: `-a=bc` + * + * @var string + */ + private $shortStyle = 'posix'; + + /** + * Whether stop parse option on found unknown option + * + * @var bool + */ + private $stopOnUnknown = true; + + /** + * Whether parse the remaining args {@see $rawArgs}. + * + * eg: 'arg=value' -> [arg => value] + * + * @var bool + */ + private $parseRawArgs = true; + + /** + * Parsed options + * + * @var array + */ + private $opts = []; + + /** + * Parsed arguments + * + * @var array + */ + private $args = []; + + /** + * Parse options by pre-defined + * + * ```php + * // element format: + * // - k-v: k is option, v is value type + * // - v: v is option, type is string. + * $defines = [ + * 's,long', // use default type: string + * // option => value type, + * 's,long' => string, + * 's' => bool, + * 'long' => int, + * 'long' => array, // TODO int[], string[] + * ]; + * ``` + * * @param array $rawArgs - * @param array $settings + * @param array $defines * * @return array */ - public function parse(array $rawArgs, array $settings = []): array + public function parseDefined(array $rawArgs, array $defines, array $settings = []): array { $this->setSettings($settings); } diff --git a/src/Flag/Traits/FlagParsingTrait.php b/src/Flag/Traits/FlagParsingTrait.php index a492141e..c60882ca 100644 --- a/src/Flag/Traits/FlagParsingTrait.php +++ b/src/Flag/Traits/FlagParsingTrait.php @@ -23,33 +23,34 @@ trait FlagParsingTrait private $stopOnArg = true; /** - * The remaining args on option parsed + * The raw input flags * * @var array */ - private $args = []; + protected $rawFlags = []; /** - * The raw input args + * The remaining args. + * After on option parsed from {@see $rawFlags} * * @var array */ - private $rawArgs = []; + protected $rawArgs = []; /** * @return array */ - public function getArgs(): array + public function getRawArgs(): array { - return $this->args; + return $this->rawArgs; } /** * @return array */ - public function getRawArgs(): array + public function getRawFlags(): array { - return $this->rawArgs; + return $this->rawFlags; } /** From e90ec3187b69f1a2d5f69d17e4cc9293387f1a90 Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 23 Aug 2021 00:17:26 +0800 Subject: [PATCH 130/258] feat: add an new simple flags parser --- src/Flag/Flag.php | 14 +- src/Flag/FlagType.php | 131 +++++++++ src/Flag/Flags.php | 22 +- src/Flag/SFlags.php | 411 ++++++++++++++++++++++++--- src/Flag/Traits/FlagParsingTrait.php | 47 ++- src/IO/InputDefinition.php | 2 +- test/Flag/SFlagsTest.php | 34 +++ 7 files changed, 587 insertions(+), 74 deletions(-) create mode 100644 src/Flag/FlagType.php create mode 100644 test/Flag/SFlagsTest.php diff --git a/src/Flag/Flag.php b/src/Flag/Flag.php index 5a363150..086c5579 100644 --- a/src/Flag/Flag.php +++ b/src/Flag/Flag.php @@ -60,7 +60,7 @@ abstract class Flag implements InputFlagInterface * * @var string */ - private $type = self::TYPE_UNKNOWN; + private $type = FlagType::UNKNOWN; /** * The default value @@ -119,7 +119,7 @@ public function __construct(string $name, string $desc = '', int $mode = 0, $def public function init(): void { if ($this->isArray()) { - $this->type = self::TYPE_ARRAY; + $this->type = FlagType::ARRAY; } } @@ -152,19 +152,19 @@ public function setValue($value): void { // filter value by type switch ($this->type) { - case self::TYPE_INT: + case FlagType::INT: $value = (int)$value; break; - case self::TYPE_BOOL: + case FlagType::BOOL: $value = (bool)$value; break; - case self::TYPE_FLOAT: + case FlagType::FLOAT: $value = (float)$value; break; - case self::TYPE_STRING: + case FlagType::STRING: $value = (string)$value; break; - // case self::TYPE_ARRAY: + // case FlagType::ARRAY: // $value = (string)$value; // break; default: diff --git a/src/Flag/FlagType.php b/src/Flag/FlagType.php new file mode 100644 index 00000000..0a96192f --- /dev/null +++ b/src/Flag/FlagType.php @@ -0,0 +1,131 @@ + 2, + self::INTS => 3, + self::STRINGS => 3, + ]; + + public const TYPES_MAP = [ + self::INT => 1, + self::BOOL => 1, + self::FLOAT => 1, + self::STRING => 1, + + // ------ complex types ------ + self::ARRAY => 2, + self::OBJECT => 2, + self::CALLABLE => 2, + + // ------ extend types ------ + self::INTS => 3, + self::STRINGS => 3, + self::MIXED => 3, + self::CUSTOM => 3, + self::UNKNOWN => 3, + ]; + + /** + * @param string $type + * + * @return bool + */ + public static function isValid(string $type): bool + { + return isset(self::TYPES_MAP[$type]); + } + + /** + * @param string $type + * @param mixed $value + * + * @return bool|float|int|mixed|string + */ + public static function fmtBasicTypeValue(string $type, $value) + { + // filter value by type + switch ($type) { + case self::INT: + case self::INTS: + $value = (int)$value; + break; + case self::BOOL: + $value = (bool)$value; + break; + case self::FLOAT: + $value = (float)$value; + break; + case self::STRING: + case self::STRINGS: + $value = (string)$value; + break; + // case FlagType::ARRAY: + // $value = (string)$value; + // break; + default: + // nothing + break; + } + + return $value; + } + + // These words will be as a Boolean value + private const TRUE_WORDS = '|on|yes|true|'; + + private const FALSE_WORDS = '|off|no|false|'; + + public static function str2bool(string $val): bool + { + // check it is a bool value. + if (false !== stripos(self::TRUE_WORDS, "|$val|")) { + return true; + } + + if (false !== stripos(self::FALSE_WORDS, "|$val|")) { + return false; + } + + // TODO throws error + return false; + } +} \ No newline at end of file diff --git a/src/Flag/Flags.php b/src/Flag/Flags.php index d260abc6..90dcf203 100644 --- a/src/Flag/Flags.php +++ b/src/Flag/Flags.php @@ -91,7 +91,7 @@ public function parse(array $args = null): array } $this->parsed = true; - $this->rawFlags = $this->args = $args; + $this->rawFlags = $this->rawArgs = $args; while (true) { [$goon, $status] = $this->parseOne(); @@ -105,7 +105,7 @@ public function parse(array $args = null): array } // binding remaining args. - if ($this->autoBindArgs && $this->args) { + if ($this->autoBindArgs && $this->rawArgs) { $this->bindingArguments(); } @@ -123,13 +123,13 @@ public function parse(array $args = null): array */ protected function parseOne(): array { - $count = count($this->args); + $count = count($this->rawArgs); if ($count === 0) { return [false, self::STATUS_OK]; } - $args = $this->args; - $arg = array_shift($this->args); + $args = $this->rawArgs; + $arg = array_shift($this->rawArgs); // empty, continue. if ('' === $arg) { @@ -138,7 +138,7 @@ protected function parseOne(): array // is not an option flag. exit. if ($arg[0] !== '-') { - $this->args = $args; // revert args on exit + $this->rawArgs = $args; // revert args on exit return [false, self::STATUS_OK]; } @@ -187,16 +187,16 @@ protected function parseOne(): array $opt->setValue($boolVal); } else { - if (!$hasVal && count($this->args) > 0) { + if (!$hasVal && count($this->rawArgs) > 0) { // value is next arg $hasVal = true; - $ntArg = $this->args[0]; + $ntArg = $this->rawArgs[0]; // is not an option value. if ($ntArg[0] === '-') { $hasVal = false; } else { - $value = array_shift($this->args); + $value = array_shift($this->rawArgs); } } @@ -225,7 +225,7 @@ public function reset(bool $clearDefined = false): void // clear match results $this->parsed = false; $this->matched = []; - $this->rawArgs = $this->args = []; + $this->rawArgs = $this->rawArgs = []; } // These words will be as a Boolean value @@ -274,7 +274,7 @@ public static function filterBoolV2(string $val): ?bool */ public function bindingArguments(): void { - if (!$this->args) { + if (!$this->rawArgs) { return; } diff --git a/src/Flag/SFlags.php b/src/Flag/SFlags.php index 48a42fc7..3b3ad8fa 100644 --- a/src/Flag/SFlags.php +++ b/src/Flag/SFlags.php @@ -2,9 +2,26 @@ namespace Inhere\Console\Flag; +use Inhere\Console\Concern\NameAliasTrait; +use Inhere\Console\Exception\FlagException; use Inhere\Console\Flag\Traits\FlagParsingTrait; use Toolkit\Stdlib\Obj\AbstractObj; -use function array_merge; +use Toolkit\Stdlib\Str; +use function current; +use function escapeshellarg; +use function explode; +use function is_bool; +use function is_int; +use function is_numeric; +use function is_string; +use function next; +use function preg_match; +use function str_split; +use function stripos; +use function strlen; +use function strpos; +use function substr; +use function trim; /** * Class SFlags @@ -13,41 +30,15 @@ class SFlags extends AbstractObj { use FlagParsingTrait; + use NameAliasTrait; - /** - * @var array - */ - private $settings = [ - // List of parameters without values(bool option keys) - 'boolOpts' => [], // ['debug', 'h'] - // Whether merge short-opts and long-opts - 'mergeOpts' => false, - // Only want parsed options. - // if not empty, will ignore no matched - 'wantParsedOpts' => [], - // List of option allow array values. - 'arrayOpts' => [], // ['names', 'status'] - // Special short style - // gnu: `-abc` will expand: `-a -b -c` - // posix: `-abc` will expand: `-a=bc` - 'shortStyle' => 'posix', - ]; + // These words will be as a Boolean value + private const TRUE_WORDS = '|on|yes|true|'; - /** - * Special short style - * gnu: `-abc` will expand: `-a -b -c` - * posix: `-abc` will expand: `-a=bc` - * - * @var string - */ - private $shortStyle = 'posix'; + private const FALSE_WORDS = '|off|no|false|'; - /** - * Whether stop parse option on found unknown option - * - * @var bool - */ - private $stopOnUnknown = true; + public const SHORT_STYLE_GUN = 'gnu'; + public const SHORT_STYLE_POSIX = 'posix'; /** * Whether parse the remaining args {@see $rawArgs}. @@ -58,6 +49,11 @@ class SFlags extends AbstractObj */ private $parseRawArgs = true; + /** + * @var array + */ + private $defined = []; + /** * Parsed options * @@ -73,45 +69,368 @@ class SFlags extends AbstractObj private $args = []; /** - * Parse options by pre-defined + * load option defines * * ```php * // element format: * // - k-v: k is option, v is value type * // - v: v is option, type is string. * $defines = [ - * 's,long', // use default type: string + * 'long,s', // use default type: string * // option => value type, - * 's,long' => string, + * 'long,s' => int, * 's' => bool, - * 'long' => int, + * 'long' => string, * 'long' => array, // TODO int[], string[] * ]; * ``` * - * @param array $rawArgs * @param array $defines + */ + protected function loadDefined(array $defines): void + { + foreach ($defines as $key => $type) { + if (is_int($key)) { + $key = $type; + $type = FlagType::STRING; + } + + if (is_string($key)) { + if (strpos($key, ',') === false) { + $this->defined[$key] = (string)$type; + continue; + } + + $name = '_'; + $keys = Str::explode($key, ','); + + // first is the option name. other is aliases. + foreach ($keys as $i => $k) { + if ($i === 0) { + $name = $k; + + $this->defined[$k] = (string)$type; + } else { + $this->setAlias($name, $k, true); + } + } + } + } + + } + + /** + * Parse options by pre-defined + * + * Usage: + * + * ```php + * $rawFlags = $_SERVER['argv']; + * // NOTICE: must shift first element. + * $scriptFile = \array_shift($rawFlags); + * $rawArgs = Flags::new()->parseDefined($rawFlags); + * ``` + * + * ```php + * // element format: + * // - k-v: k is option, v is value type + * // - v: v is option, type is string. + * $defines = [ + * 'long,s', // use default type: string + * // option => value type, + * 'long,s' => int, + * 's' => bool, + * 'long' => string, + * 'long' => array, // TODO int[], string[] + * ]; + * ``` + * + * @param array $rawFlags + * @param array $defines The want parsed options defines * * @return array */ - public function parseDefined(array $rawArgs, array $defines, array $settings = []): array + public function parseDefined(array $rawFlags, array $defines): array { - $this->setSettings($settings); + if ($this->parsed) { + return $this->rawArgs; + } + + $this->parsed = true; + $this->rawFlags = $rawFlags; + $this->loadDefined($defines); + + $optParseEnd = false; + while (false !== ($p = current($rawFlags))) { + next($rawFlags); + + // option parse end, collect remaining arguments. + if ($optParseEnd) { + $this->rawArgs[] = $p; + continue; + } + + // is options and not equals '-' '--' + if ($p !== '' && $p[0] === '-' && '' !== trim($p, '-')) { + $value = true; + $hasVal = false; + + $isShort = true; + $option = substr($p, 1); + // long-opt: (--) + if (strpos($option, '-') === 0) { + $isShort = false; + $option = substr($option, 1); + + // long-opt: value specified inline (--=) + if (strpos($option, '=') !== false) { + [$option, $value] = explode('=', $option, 2); + $hasVal = $value !== ''; + } + + // short-opt: value specified inline (-=) + } elseif (isset($option[1]) && $option[1] === '=') { + [$option, $value] = explode('=', $option, 2); + $hasVal = $value !== ''; + } + + // Is special short opts. eg: -abc + if ($isShort && strlen($option) > 1) { + $this->parseSpecialShorts($option); + continue; + } + + $option = $this->resolveAlias($option); + if (!isset($this->defined[$option])) { + throw new FlagException("flag option provided but not defined: $option", 404); + } + + $type = $this->defined[$option]; + + // eg: -o=false + $isBool = $type === FlagType::BOOL; + if ($hasVal && $isBool) { + $value = FlagType::str2bool($value); + $this->setRealOptValue($option, $type, $value); + continue; + } + + // check if next element is a descriptor or a value + $next = current($rawFlags); + if ($hasVal === false && $isBool === false) { + if (false === self::nextIsValue($next)) { + throw new FlagException("must provide value for the option: $option", 404); + } + + $value = $next; + next($rawFlags); + } + + $this->setRealOptValue($option, $type, $value); + continue; + } + + // collect arguments. + $this->rawArgs[] = $p; + + // stop parse options: + // - on found fist argument. + // - found '--' will stop parse options + if ($this->stopOnFistArg || $p === '--') { + $optParseEnd = true; + } + } + + return $this->rawArgs; } /** - * @return array + * @param string $shorts + */ + private function parseSpecialShorts(string $shorts): void + { + // posix: '-abc' will expand to '-a=bc' + if ($this->shortStyle === self::SHORT_STYLE_POSIX) { + $this->setOptValue($shorts[0], substr($shorts, 1)); + return; + } + + // gnu: '-abc' will expand to '-a -b -c' + foreach (str_split($shorts) as $short) { + $option = $this->resolveAlias($short); + if (!isset($this->defined[$option])) { + throw new FlagException("flag option provided but not defined: $option", 404); + } + + $type = $this->defined[$option]; + $this->setRealOptValue($option, $type, true); + } + } + + /** + * @param string $option + * @param string $type + * @param mixed $value + */ + protected function setRealOptValue(string $option, string $type, $value): void + { + if (isset(FlagType::ARRAY_TYPES[$type])) { + $this->opts[$option][] = $value; + } else { + $this->opts[$option] = $value; + } + } + + /** + * @param string $option + * @param mixed $value + */ + public function setOptValue(string $option, $value): void + { + $option = $this->resolveAlias($option); + if (!isset($this->defined[$option])) { + throw new FlagException("flag option provided but not defined: $option", 404); + } + + $type = $this->defined[$option]; + $value = FlagType::fmtBasicTypeValue($type, $value); + + $this->setRealOptValue($option, $type, $value); + } + + /** + * The mapping name argument index + * + * @var array + */ + private $name2index = []; + + /** + * parse remaining rawArgs as arguments + * + * ```php + * $defines = [ + * // type see FlagType::* + * 'type', // not set name, use index for get value. + * 'type, name', // allow set argument name. + * ]; + * ``` + * + * @param array $defines + */ + public function parseRawArgs(array $defines): void + { + // TODO + foreach ($this->rawArgs as $index => $arg) { + // value specified inline (=) + if (strpos($arg, '=') !== false) { + [$name, $value] = explode('=', $arg, 2); + + if (self::isValidArgName($name)) { + $this->args[$name] = self::filterBool($value); + } else { + $this->args[] = $arg; + } + } else { + $this->args[] = $arg; + } + } + } + + /** + * @param bool $resetDefines + */ + public function reset(bool $resetDefines = true): void + { + $this->parsed = false; + $this->rawArgs = $this->rawFlags = []; + + $this->opts = $this->args = []; + + if ($resetDefines) { + $this->defined = []; + } + } + + /** + * @param string|bool|int|mixed $val + * + * @return bool|int|mixed + */ + public static function filterBool($val) + { + if (is_bool($val) || is_numeric($val)) { + return $val; + } + + // check it is a bool value. + if (false !== stripos(self::TRUE_WORDS, "|$val|")) { + return true; + } + + if (false !== stripos(self::FALSE_WORDS, "|$val|")) { + return false; + } + + return $val; + } + + /** + * check next is option value + * + * @param mixed $val + * + * @return bool + */ + public static function nextIsValue($val): bool + { + // current() fetch error, will return FALSE + if ($val === false) { + return false; + } + + // if is: '', 0 + if (!$val) { + return true; + } + + // is not option name. + if ($val[0] !== '-') { + // ensure is option value. + if (false === strpos($val, '=')) { + return true; + } + + // is string value, but contains '=' + [$name,] = explode('=', $val, 2); + + // named argument OR invalid: 'some = string' + return false === self::isValidArgName($name); + } + + // is option name. + return false; + } + + /** + * @param string $name + * + * @return bool */ - public function getSettings(): array + public static function isValidArgName(string $name): bool { - return $this->settings; + return preg_match('#^\w+$#', $name) === 1; } /** - * @param array $settings + * Escapes a token through escape shell arg if it contains unsafe chars. + * + * @param string $token + * + * @return string */ - public function setSettings(array $settings): void + public static function escapeToken(string $token): string { - $this->settings = array_merge($this->settings, $settings); + return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); } } diff --git a/src/Flag/Traits/FlagParsingTrait.php b/src/Flag/Traits/FlagParsingTrait.php index c60882ca..a65b9717 100644 --- a/src/Flag/Traits/FlagParsingTrait.php +++ b/src/Flag/Traits/FlagParsingTrait.php @@ -13,6 +13,15 @@ trait FlagParsingTrait */ private $parsed = false; + /** + * Special short style + * gnu: `-abc` will expand: `-a -b -c` + * posix: `-abc` will expand: `-a=bc` + * + * @var string + */ + private $shortStyle = 'posix'; + /** * Whether stop parse option on first argument * @@ -20,7 +29,14 @@ trait FlagParsingTrait */ // private $stopOnNoOption = true; // private $stopOnNoOpt = true; - private $stopOnArg = true; + private $stopOnFistArg = true; + + /** + * Whether stop parse option on found undefined option + * + * @var bool + */ + private $stopOnUndefined = true; /** * The raw input flags @@ -64,19 +80,32 @@ public function isParsed(): bool /** * @return bool */ - public function isStopOnArg(): bool + public function isStopOnFistArg(): bool { - return $this->stopOnArg; + return $this->stopOnFistArg; } /** - * @param bool $stopOnArg - * - * @return static + * @param bool $stopOnFistArg + */ + public function setStopOnFistArg(bool $stopOnFistArg): void + { + $this->stopOnFistArg = $stopOnFistArg; + } + + /** + * @return bool + */ + public function isStopOnUndefined(): bool + { + return $this->stopOnUndefined; + } + + /** + * @param bool $stopOnUndefined */ - public function setStopOnArg(bool $stopOnArg): self + public function setStopOnUndefined(bool $stopOnUndefined): void { - $this->stopOnArg = $stopOnArg; - return $this; + $this->stopOnUndefined = $stopOnUndefined; } } \ No newline at end of file diff --git a/src/IO/InputDefinition.php b/src/IO/InputDefinition.php index a71b520a..b5b4ff12 100644 --- a/src/IO/InputDefinition.php +++ b/src/IO/InputDefinition.php @@ -597,7 +597,7 @@ public function getSynopsis(bool $short = false): array } /** - * @param string $name + * @param string|int $name * * @return bool */ diff --git a/test/Flag/SFlagsTest.php b/test/Flag/SFlagsTest.php new file mode 100644 index 00000000..7a6c9ad6 --- /dev/null +++ b/test/Flag/SFlagsTest.php @@ -0,0 +1,34 @@ +parseDefined($flags); + vdump($rArgs); + + $fs->reset(); + + $flags = ['--name', 'inhere', '-s', 'sv', '-f']; + self::expectException(FlagException::class); + $fs->parse($flags); + } +} From 0cd1c346d11a5e922e4dc5f7d083d1620c980db7 Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 24 Aug 2021 22:02:18 +0800 Subject: [PATCH 131/258] update: move Flag classes to package toolkit/pflag --- composer.json | 1 + src/Concern/NameAliasTrait.php | 4 +- src/Contract/InputFlagInterface.php | 48 --- src/Exception/FlagException.php | 15 - src/Flag/Argument.php | 67 ---- src/Flag/Arguments.php | 57 ---- src/Flag/Flag.php | 298 ----------------- src/Flag/FlagType.php | 131 -------- src/Flag/Flags.php | 316 ------------------ src/Flag/Option.php | 126 ------- src/Flag/Options.php | 19 -- src/Flag/Parser.php | 13 - src/Flag/SFlags.php | 436 ------------------------- src/Flag/Traits/FlagArgumentsTrait.php | 81 ----- src/Flag/Traits/FlagOptionsTrait.php | 115 ------- src/Flag/Traits/FlagParsingTrait.php | 111 ------- test/Flag/FlagsTest.php | 35 -- test/Flag/SFlagsTest.php | 34 -- test/bootstrap.php | 4 +- 19 files changed, 6 insertions(+), 1905 deletions(-) delete mode 100644 src/Contract/InputFlagInterface.php delete mode 100644 src/Exception/FlagException.php delete mode 100644 src/Flag/Argument.php delete mode 100644 src/Flag/Arguments.php delete mode 100644 src/Flag/Flag.php delete mode 100644 src/Flag/FlagType.php delete mode 100644 src/Flag/Flags.php delete mode 100644 src/Flag/Option.php delete mode 100644 src/Flag/Options.php delete mode 100644 src/Flag/Parser.php delete mode 100644 src/Flag/SFlags.php delete mode 100644 src/Flag/Traits/FlagArgumentsTrait.php delete mode 100644 src/Flag/Traits/FlagOptionsTrait.php delete mode 100644 src/Flag/Traits/FlagParsingTrait.php delete mode 100644 test/Flag/FlagsTest.php delete mode 100644 test/Flag/SFlagsTest.php diff --git a/composer.json b/composer.json index b78a50e5..b3cb2177 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ "require": { "php": ">7.2.0", "toolkit/cli-utils": "~1.0", + "toolkit/pflag": "~1.0", "toolkit/stdlib": "~1.0", "toolkit/sys-utils": "~1.0" }, diff --git a/src/Concern/NameAliasTrait.php b/src/Concern/NameAliasTrait.php index 3f1785c8..e6164ed0 100644 --- a/src/Concern/NameAliasTrait.php +++ b/src/Concern/NameAliasTrait.php @@ -36,7 +36,9 @@ public function setAlias(string $name, $alias, bool $validate = false): void $this->aliases[$aliasName] = $name; } elseif ($validate) { $oldName = $this->aliases[$aliasName]; - throw new InvalidArgumentException("Alias '{$aliasName}' has been registered by '{$oldName}', cannot assign to the '{$name}'"); + throw new InvalidArgumentException( + "Alias '$aliasName' has been registered by '$oldName', cannot assign to the '$name'" + ); } } } diff --git a/src/Contract/InputFlagInterface.php b/src/Contract/InputFlagInterface.php deleted file mode 100644 index 13a0cf74..00000000 --- a/src/Contract/InputFlagInterface.php +++ /dev/null @@ -1,48 +0,0 @@ -hasMode(Input::ARG_IS_ARRAY); - } - - /** - * @return bool - */ - public function isOptional(): bool - { - return $this->hasMode(Input::ARG_OPTIONAL); - } - - /** - * @return bool - */ - public function isRequired(): bool - { - return $this->hasMode(Input::ARG_REQUIRED); - } - - /** - * @return int - */ - public function getIndex(): int - { - return $this->index; - } - - /** - * @param int $index - */ - public function setIndex(int $index): void - { - $this->index = $index; - } -} diff --git a/src/Flag/Arguments.php b/src/Flag/Arguments.php deleted file mode 100644 index 25f036c0..00000000 --- a/src/Flag/Arguments.php +++ /dev/null @@ -1,57 +0,0 @@ -arguments; - } - - /** - * @param array $arguments - */ - public function setArguments(array $arguments): void - { - $this->arguments = $arguments; - } -} diff --git a/src/Flag/Flag.php b/src/Flag/Flag.php deleted file mode 100644 index 086c5579..00000000 --- a/src/Flag/Flag.php +++ /dev/null @@ -1,298 +0,0 @@ -name = $name; - $this->mode = $mode; - - $this->default = $default; - $this->setDesc($desc); - } - - public function init(): void - { - if ($this->isArray()) { - $this->type = FlagType::ARRAY; - } - } - - /****************************************************************** - * mode value - *****************************************************************/ - - /** - * @param int $mode - * - * @return bool - */ - public function hasMode(int $mode): bool - { - return ($this->mode & $mode) > 0; - } - - /** - * @return mixed - */ - public function getValue() - { - return $this->value; - } - - /** - * @param mixed $value - */ - public function setValue($value): void - { - // filter value by type - switch ($this->type) { - case FlagType::INT: - $value = (int)$value; - break; - case FlagType::BOOL: - $value = (bool)$value; - break; - case FlagType::FLOAT: - $value = (float)$value; - break; - case FlagType::STRING: - $value = (string)$value; - break; - // case FlagType::ARRAY: - // $value = (string)$value; - // break; - default: - // nothing - break; - } - - // has validator - if ($cb = $this->validator) { - $value = $cb($value); - // if (false === $ok) { - // throw new FlagException(''); - // } - } - - if ($this->isArray()) { - $this->value[] = $value; - } else { - $this->value = $value; - } - } - - /** - * @param callable $validator - */ - public function setValidator(callable $validator): void - { - $this->validator = $validator; - } - - /****************************************************************** - * - *****************************************************************/ - - /** - * @return string - */ - public function getType(): string - { - return $this->type; - } - - /** - * @param string $type - */ - public function setType(string $type): void - { - $this->type = $type; - } - - /** - * @return int - */ - public function getMode(): int - { - return $this->mode; - } - - /** - * @param int $mode - */ - public function setMode(int $mode): void - { - $this->mode = $mode; - } - - /** - * @return string - */ - public function getName(): string - { - return $this->name; - } - - /** - * @param string $name - */ - public function setName(string $name): void - { - $this->name = $name; - } - - /** - * @return mixed - */ - public function getDefault() - { - return $this->default; - } - - /** - * @param mixed $default - */ - public function setDefault($default): void - { - $this->default = $default; - } - - /** - * @return string - */ - public function getDesc(): string - { - return $this->desc; - } - - /** - * @param string $desc - */ - public function setDesc(string $desc): void - { - $this->desc = $desc; - } - - /** - * @return array - */ - public function toArray(): array - { - return [ - 'name' => $this->name, - 'mode' => $this->mode, - 'type' => $this->type, - 'default' => $this->default, - 'isArray' => $this->isArray(), - 'isOptional' => $this->isOptional(), - 'isRequired' => $this->isRequired(), - 'description' => $this->desc, - ]; - } -} diff --git a/src/Flag/FlagType.php b/src/Flag/FlagType.php deleted file mode 100644 index 0a96192f..00000000 --- a/src/Flag/FlagType.php +++ /dev/null @@ -1,131 +0,0 @@ - 2, - self::INTS => 3, - self::STRINGS => 3, - ]; - - public const TYPES_MAP = [ - self::INT => 1, - self::BOOL => 1, - self::FLOAT => 1, - self::STRING => 1, - - // ------ complex types ------ - self::ARRAY => 2, - self::OBJECT => 2, - self::CALLABLE => 2, - - // ------ extend types ------ - self::INTS => 3, - self::STRINGS => 3, - self::MIXED => 3, - self::CUSTOM => 3, - self::UNKNOWN => 3, - ]; - - /** - * @param string $type - * - * @return bool - */ - public static function isValid(string $type): bool - { - return isset(self::TYPES_MAP[$type]); - } - - /** - * @param string $type - * @param mixed $value - * - * @return bool|float|int|mixed|string - */ - public static function fmtBasicTypeValue(string $type, $value) - { - // filter value by type - switch ($type) { - case self::INT: - case self::INTS: - $value = (int)$value; - break; - case self::BOOL: - $value = (bool)$value; - break; - case self::FLOAT: - $value = (float)$value; - break; - case self::STRING: - case self::STRINGS: - $value = (string)$value; - break; - // case FlagType::ARRAY: - // $value = (string)$value; - // break; - default: - // nothing - break; - } - - return $value; - } - - // These words will be as a Boolean value - private const TRUE_WORDS = '|on|yes|true|'; - - private const FALSE_WORDS = '|off|no|false|'; - - public static function str2bool(string $val): bool - { - // check it is a bool value. - if (false !== stripos(self::TRUE_WORDS, "|$val|")) { - return true; - } - - if (false !== stripos(self::FALSE_WORDS, "|$val|")) { - return false; - } - - // TODO throws error - return false; - } -} \ No newline at end of file diff --git a/src/Flag/Flags.php b/src/Flag/Flags.php deleted file mode 100644 index 90dcf203..00000000 --- a/src/Flag/Flags.php +++ /dev/null @@ -1,316 +0,0 @@ -parse($args); - } - - /** - * @var string - */ - private $curOptKey = ''; - - private $parseStatus = self::STATUS_OK; - - public const STATUS_OK = 0; - public const STATUS_ERR = 1; - public const STATUS_END = 2; - public const STATUS_HELP = 3; // found `-h|--help` flag - - /** - * @param array|null $args - * - * @return array - */ - public function parse(array $args = null): array - { - if ($args === null) { - $args = $_SERVER['argv']; - } - - $this->parsed = true; - $this->rawFlags = $this->rawArgs = $args; - - while (true) { - [$goon, $status] = $this->parseOne(); - if ($goon) { - continue; - } - - if (self::STATUS_OK === $status) { - break; - } - } - - // binding remaining args. - if ($this->autoBindArgs && $this->rawArgs) { - $this->bindingArguments(); - } - - return []; - } - - /** - * parse one flag. - * - * will stop on: - * - found `-h|--help` flag - * - found first arg(not an option) - * - * @return array [goon: bool, status: int] - */ - protected function parseOne(): array - { - $count = count($this->rawArgs); - if ($count === 0) { - return [false, self::STATUS_OK]; - } - - $args = $this->rawArgs; - $arg = array_shift($this->rawArgs); - - // empty, continue. - if ('' === $arg) { - return [true, self::STATUS_OK]; - } - - // is not an option flag. exit. - if ($arg[0] !== '-') { - $this->rawArgs = $args; // revert args on exit - return [false, self::STATUS_OK]; - } - - // NOTICE: will stop parse option on found '--' - if ($arg === '--') { - return [false, self::STATUS_OK]; - } - - $name = ltrim($arg, '-'); - - // invalid arg. eg: '--' // ignore - if ('' === $name) { - return [true, self::STATUS_OK]; - } - - $value = ''; - $hasVal = false; - - $len = strlen($name); - for ($i = 0; $i < $len; $i++) { - if ($name[$i] === '=') { - $hasVal = true; - $name = substr($name, 0, $i); - - // fix: `--name=` no value string. - if ($i + 1 < $len) { - $value = substr($name, $i + 1); - } - } - } - - $rName = $this->resolveAlias($name); - if (!isset($this->defined[$rName])) { - throw new FlagException("flag option provided but not defined: $arg", 404); - } - - $opt = $this->defined[$rName]; - - // bool option default always set TRUE. - if ($opt->isBoolean()) { - $boolVal = true; - if ($hasVal) { - // only allow set bool value by --opt=false - $boolVal = self::filterBool($value); - } - - $opt->setValue($boolVal); - } else { - if (!$hasVal && count($this->rawArgs) > 0) { - // value is next arg - $hasVal = true; - $ntArg = $this->rawArgs[0]; - - // is not an option value. - if ($ntArg[0] === '-') { - $hasVal = false; - } else { - $value = array_shift($this->rawArgs); - } - } - - if (!$hasVal) { - throw new FlagException("flag option '$arg' needs an value", 400); - } - - // set value - $opt->setValue($value); - } - - $this->addMatched($opt); - return [true, self::STATUS_OK]; - } - - /** - * @param bool $clearDefined - */ - public function reset(bool $clearDefined = false): void - { - if ($clearDefined) { - $this->defined = []; - $this->resetArguments(); - } - - // clear match results - $this->parsed = false; - $this->matched = []; - $this->rawArgs = $this->rawArgs = []; - } - - // These words will be as a Boolean value - private const TRUE_WORDS = '|on|yes|true|'; - private const FALSE_WORDS = '|off|no|false|'; - - /** - * @param string $val - * - * @return bool|null - */ - public static function filterBool(string $val): ?bool - { - // check it is a bool value. - return false !== stripos(self::TRUE_WORDS, "|$val|"); - } - - /** - * @param string $val - * - * @return bool|null - */ - public static function filterBoolV2(string $val): ?bool - { - // check it is a bool value. - if (false !== stripos(self::TRUE_WORDS, "|$val|")) { - return true; - } - - if (false !== stripos(self::FALSE_WORDS, "|$val|")) { - return false; - } - - // return null; - return null; - } - - /************************************************************************** - * parse and binding command arguments - **************************************************************************/ - - /** - * parse and binding command arguments - * - * NOTICE: must call it on options parsed. - */ - public function bindingArguments(): void - { - if (!$this->rawArgs) { - return; - } - - // TODO ... - } - - /** - * @return callable - */ - public function getHelpRenderer(): callable - { - return $this->helpRenderer; - } - - /** - * @param callable $helpRenderer - */ - public function setHelpRenderer(callable $helpRenderer): void - { - $this->helpRenderer = $helpRenderer; - } - - /** - * @return bool - */ - public function isAutoBindArgs(): bool - { - return $this->autoBindArgs; - } - - /** - * @param bool $autoBindArgs - */ - public function setAutoBindArgs(bool $autoBindArgs): void - { - $this->autoBindArgs = $autoBindArgs; - } - -} diff --git a/src/Flag/Option.php b/src/Flag/Option.php deleted file mode 100644 index 9845d86e..00000000 --- a/src/Flag/Option.php +++ /dev/null @@ -1,126 +0,0 @@ -hasMode(Flag::OPT_IS_ARRAY); - } - - /** - * @return bool - */ - public function isOptional(): bool - { - return $this->hasMode(Flag::OPT_OPTIONAL); - } - - /** - * @return bool - */ - public function isRequired(): bool - { - return $this->hasMode(Flag::OPT_REQUIRED); - } - - /** - * @return bool - */ - public function isBoolean(): bool - { - return $this->hasMode(Flag::OPT_BOOLEAN); - } - - /** - * @return string - */ - public function getAlias(): string - { - return $this->alias; - } - - /** - * @param string $alias - */ - public function setAlias(string $alias): void - { - $this->alias = $alias; - } - - /** - * @return string - */ - public function getShortcut(): string - { - return $this->shortcut; - } - - /** - * @param string $shortcut eg: 'a|b' - */ - public function setShortcut(string $shortcut): void - { - $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); - $shortcuts = array_filter($shortcuts); - - $this->setShorts($shortcuts); - } - - /** - * @return array - */ - public function getShorts(): array - { - return $this->shorts; - } - - /** - * @param array $shorts - */ - public function setShorts(array $shorts): void - { - $this->shorts = $shorts; - $this->shortcut = implode('|', $shorts); - } - -} diff --git a/src/Flag/Options.php b/src/Flag/Options.php deleted file mode 100644 index e059e103..00000000 --- a/src/Flag/Options.php +++ /dev/null @@ -1,19 +0,0 @@ - [arg => value] - * - * @var bool - */ - private $parseRawArgs = true; - - /** - * @var array - */ - private $defined = []; - - /** - * Parsed options - * - * @var array - */ - private $opts = []; - - /** - * Parsed arguments - * - * @var array - */ - private $args = []; - - /** - * load option defines - * - * ```php - * // element format: - * // - k-v: k is option, v is value type - * // - v: v is option, type is string. - * $defines = [ - * 'long,s', // use default type: string - * // option => value type, - * 'long,s' => int, - * 's' => bool, - * 'long' => string, - * 'long' => array, // TODO int[], string[] - * ]; - * ``` - * - * @param array $defines - */ - protected function loadDefined(array $defines): void - { - foreach ($defines as $key => $type) { - if (is_int($key)) { - $key = $type; - $type = FlagType::STRING; - } - - if (is_string($key)) { - if (strpos($key, ',') === false) { - $this->defined[$key] = (string)$type; - continue; - } - - $name = '_'; - $keys = Str::explode($key, ','); - - // first is the option name. other is aliases. - foreach ($keys as $i => $k) { - if ($i === 0) { - $name = $k; - - $this->defined[$k] = (string)$type; - } else { - $this->setAlias($name, $k, true); - } - } - } - } - - } - - /** - * Parse options by pre-defined - * - * Usage: - * - * ```php - * $rawFlags = $_SERVER['argv']; - * // NOTICE: must shift first element. - * $scriptFile = \array_shift($rawFlags); - * $rawArgs = Flags::new()->parseDefined($rawFlags); - * ``` - * - * ```php - * // element format: - * // - k-v: k is option, v is value type - * // - v: v is option, type is string. - * $defines = [ - * 'long,s', // use default type: string - * // option => value type, - * 'long,s' => int, - * 's' => bool, - * 'long' => string, - * 'long' => array, // TODO int[], string[] - * ]; - * ``` - * - * @param array $rawFlags - * @param array $defines The want parsed options defines - * - * @return array - */ - public function parseDefined(array $rawFlags, array $defines): array - { - if ($this->parsed) { - return $this->rawArgs; - } - - $this->parsed = true; - $this->rawFlags = $rawFlags; - $this->loadDefined($defines); - - $optParseEnd = false; - while (false !== ($p = current($rawFlags))) { - next($rawFlags); - - // option parse end, collect remaining arguments. - if ($optParseEnd) { - $this->rawArgs[] = $p; - continue; - } - - // is options and not equals '-' '--' - if ($p !== '' && $p[0] === '-' && '' !== trim($p, '-')) { - $value = true; - $hasVal = false; - - $isShort = true; - $option = substr($p, 1); - // long-opt: (--) - if (strpos($option, '-') === 0) { - $isShort = false; - $option = substr($option, 1); - - // long-opt: value specified inline (--=) - if (strpos($option, '=') !== false) { - [$option, $value] = explode('=', $option, 2); - $hasVal = $value !== ''; - } - - // short-opt: value specified inline (-=) - } elseif (isset($option[1]) && $option[1] === '=') { - [$option, $value] = explode('=', $option, 2); - $hasVal = $value !== ''; - } - - // Is special short opts. eg: -abc - if ($isShort && strlen($option) > 1) { - $this->parseSpecialShorts($option); - continue; - } - - $option = $this->resolveAlias($option); - if (!isset($this->defined[$option])) { - throw new FlagException("flag option provided but not defined: $option", 404); - } - - $type = $this->defined[$option]; - - // eg: -o=false - $isBool = $type === FlagType::BOOL; - if ($hasVal && $isBool) { - $value = FlagType::str2bool($value); - $this->setRealOptValue($option, $type, $value); - continue; - } - - // check if next element is a descriptor or a value - $next = current($rawFlags); - if ($hasVal === false && $isBool === false) { - if (false === self::nextIsValue($next)) { - throw new FlagException("must provide value for the option: $option", 404); - } - - $value = $next; - next($rawFlags); - } - - $this->setRealOptValue($option, $type, $value); - continue; - } - - // collect arguments. - $this->rawArgs[] = $p; - - // stop parse options: - // - on found fist argument. - // - found '--' will stop parse options - if ($this->stopOnFistArg || $p === '--') { - $optParseEnd = true; - } - } - - return $this->rawArgs; - } - - /** - * @param string $shorts - */ - private function parseSpecialShorts(string $shorts): void - { - // posix: '-abc' will expand to '-a=bc' - if ($this->shortStyle === self::SHORT_STYLE_POSIX) { - $this->setOptValue($shorts[0], substr($shorts, 1)); - return; - } - - // gnu: '-abc' will expand to '-a -b -c' - foreach (str_split($shorts) as $short) { - $option = $this->resolveAlias($short); - if (!isset($this->defined[$option])) { - throw new FlagException("flag option provided but not defined: $option", 404); - } - - $type = $this->defined[$option]; - $this->setRealOptValue($option, $type, true); - } - } - - /** - * @param string $option - * @param string $type - * @param mixed $value - */ - protected function setRealOptValue(string $option, string $type, $value): void - { - if (isset(FlagType::ARRAY_TYPES[$type])) { - $this->opts[$option][] = $value; - } else { - $this->opts[$option] = $value; - } - } - - /** - * @param string $option - * @param mixed $value - */ - public function setOptValue(string $option, $value): void - { - $option = $this->resolveAlias($option); - if (!isset($this->defined[$option])) { - throw new FlagException("flag option provided but not defined: $option", 404); - } - - $type = $this->defined[$option]; - $value = FlagType::fmtBasicTypeValue($type, $value); - - $this->setRealOptValue($option, $type, $value); - } - - /** - * The mapping name argument index - * - * @var array - */ - private $name2index = []; - - /** - * parse remaining rawArgs as arguments - * - * ```php - * $defines = [ - * // type see FlagType::* - * 'type', // not set name, use index for get value. - * 'type, name', // allow set argument name. - * ]; - * ``` - * - * @param array $defines - */ - public function parseRawArgs(array $defines): void - { - // TODO - foreach ($this->rawArgs as $index => $arg) { - // value specified inline (=) - if (strpos($arg, '=') !== false) { - [$name, $value] = explode('=', $arg, 2); - - if (self::isValidArgName($name)) { - $this->args[$name] = self::filterBool($value); - } else { - $this->args[] = $arg; - } - } else { - $this->args[] = $arg; - } - } - } - - /** - * @param bool $resetDefines - */ - public function reset(bool $resetDefines = true): void - { - $this->parsed = false; - $this->rawArgs = $this->rawFlags = []; - - $this->opts = $this->args = []; - - if ($resetDefines) { - $this->defined = []; - } - } - - /** - * @param string|bool|int|mixed $val - * - * @return bool|int|mixed - */ - public static function filterBool($val) - { - if (is_bool($val) || is_numeric($val)) { - return $val; - } - - // check it is a bool value. - if (false !== stripos(self::TRUE_WORDS, "|$val|")) { - return true; - } - - if (false !== stripos(self::FALSE_WORDS, "|$val|")) { - return false; - } - - return $val; - } - - /** - * check next is option value - * - * @param mixed $val - * - * @return bool - */ - public static function nextIsValue($val): bool - { - // current() fetch error, will return FALSE - if ($val === false) { - return false; - } - - // if is: '', 0 - if (!$val) { - return true; - } - - // is not option name. - if ($val[0] !== '-') { - // ensure is option value. - if (false === strpos($val, '=')) { - return true; - } - - // is string value, but contains '=' - [$name,] = explode('=', $val, 2); - - // named argument OR invalid: 'some = string' - return false === self::isValidArgName($name); - } - - // is option name. - return false; - } - - /** - * @param string $name - * - * @return bool - */ - public static function isValidArgName(string $name): bool - { - return preg_match('#^\w+$#', $name) === 1; - } - - /** - * Escapes a token through escape shell arg if it contains unsafe chars. - * - * @param string $token - * - * @return string - */ - public static function escapeToken(string $token): string - { - return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); - } -} diff --git a/src/Flag/Traits/FlagArgumentsTrait.php b/src/Flag/Traits/FlagArgumentsTrait.php deleted file mode 100644 index 4344e3ed..00000000 --- a/src/Flag/Traits/FlagArgumentsTrait.php +++ /dev/null @@ -1,81 +0,0 @@ - index] - */ - private $name2index = []; - - /** - * @var Argument[] - */ - private $arguments = []; - - /** - * @param Argument $argument - */ - public function addArgument(Argument $argument): void - { - // record index - $this->name2index[$argument->getName()] = count($this->arguments); - // append - $this->arguments[] = $argument; - } - - /** - * @param string $name - * @param string $desc - * @param int|null $mode - * @param string|null $type The argument data type. (eg: 'string', 'array', 'mixed') - * @param null|mixed $default - * @param string $alias - */ - public function add( - string $name, - string $desc = '', - int $mode = 0, - string $type = '', - $default = null, - string $alias = '' - ): void { - $argObj = Argument::new($name, $desc, $mode, $default); - $argObj->setType($type); - $argObj->setAlias($alias); - - $this->addArgument($argObj); - } - - /** - * @return array - */ - public function getArguments(): array - { - return $this->arguments; - } - - /** - * @param array $arguments - */ - public function setArguments(array $arguments): void - { - $this->arguments = $arguments; - } - - protected function resetArguments(): void - { - $this->name2index = []; - $this->arguments = []; - } -} diff --git a/src/Flag/Traits/FlagOptionsTrait.php b/src/Flag/Traits/FlagOptionsTrait.php deleted file mode 100644 index 8e2799e3..00000000 --- a/src/Flag/Traits/FlagOptionsTrait.php +++ /dev/null @@ -1,115 +0,0 @@ -setShortcut($shorts); - - $this->addOption($opt); - } - - /** - * @param Option $option - */ - public function addOption(Option $option): void - { - $name = $option->getName(); - - if (isset($this->defined[$name])) { - throw new FlagException('cannot repeat option: ' . $name); - } - - // add to defined - $this->defined[$name] = $option; - } - - /** - * @param Option[] $options - */ - public function addOptions(array $options): void - { - foreach ($options as $option) { - $this->addOption($option); - } - } - - /** - * @param string $name - * - * @return bool - */ - public function hasDefined(string $name): bool - { - return isset($this->defined[$name]); - } - - /** - * @param Option $option - */ - public function addMatched(Option $option): void - { - $name = $option->getName(); - // add to matched - $this->matched[$name] = $option; - } - - /** - * @param string $name - * - * @return bool - */ - public function hasMatched(string $name): bool - { - return isset($this->matched[$name]); - } - - /** - * @return Option[] - */ - public function getDefinedOptions(): array - { - return $this->defined; - } - - /** - * @return Option[] - */ - public function getMatchedOptions(): array - { - return $this->matched; - } -} \ No newline at end of file diff --git a/src/Flag/Traits/FlagParsingTrait.php b/src/Flag/Traits/FlagParsingTrait.php deleted file mode 100644 index a65b9717..00000000 --- a/src/Flag/Traits/FlagParsingTrait.php +++ /dev/null @@ -1,111 +0,0 @@ -rawArgs; - } - - /** - * @return array - */ - public function getRawFlags(): array - { - return $this->rawFlags; - } - - /** - * @return bool - */ - public function isParsed(): bool - { - return $this->parsed; - } - - /** - * @return bool - */ - public function isStopOnFistArg(): bool - { - return $this->stopOnFistArg; - } - - /** - * @param bool $stopOnFistArg - */ - public function setStopOnFistArg(bool $stopOnFistArg): void - { - $this->stopOnFistArg = $stopOnFistArg; - } - - /** - * @return bool - */ - public function isStopOnUndefined(): bool - { - return $this->stopOnUndefined; - } - - /** - * @param bool $stopOnUndefined - */ - public function setStopOnUndefined(bool $stopOnUndefined): void - { - $this->stopOnUndefined = $stopOnUndefined; - } -} \ No newline at end of file diff --git a/test/Flag/FlagsTest.php b/test/Flag/FlagsTest.php deleted file mode 100644 index b929aa33..00000000 --- a/test/Flag/FlagsTest.php +++ /dev/null @@ -1,35 +0,0 @@ -addOption(Option::new('name')); - self::assertTrue($fs->hasDefined('name')); - self::assertFalse($fs->hasMatched('name')); - - $args = ['--name', 'inhere', 'arg0', 'arg1']; - $fs->parse($args); - self::assertTrue($fs->hasMatched('name')); - - $fs->reset(); - $args = ['--name', 'inhere', '-s', 'sv', '-f']; - self::expectException(FlagException::class); - $fs->parse($args); - } -} diff --git a/test/Flag/SFlagsTest.php b/test/Flag/SFlagsTest.php deleted file mode 100644 index 7a6c9ad6..00000000 --- a/test/Flag/SFlagsTest.php +++ /dev/null @@ -1,34 +0,0 @@ -parseDefined($flags); - vdump($rArgs); - - $fs->reset(); - - $flags = ['--name', 'inhere', '-s', 'sv', '-f']; - self::expectException(FlagException::class); - $fs->parse($flags); - } -} diff --git a/test/bootstrap.php b/test/bootstrap.php index 8b6de9a4..0b938076 100644 --- a/test/bootstrap.php +++ b/test/bootstrap.php @@ -11,10 +11,10 @@ if (0 === strpos($class, 'Inhere\Console\Examples\\')) { $path = str_replace('\\', '/', substr($class, strlen('Inhere\Console\Examples\\'))); - $file = dirname(__DIR__) . "/examples/{$path}.php"; + $file = dirname(__DIR__) . "/examples/$path.php"; } elseif (0 === strpos($class, 'Inhere\ConsoleTest\\')) { $path = str_replace('\\', '/', substr($class, strlen('Inhere\ConsoleTest\\'))); - $file = __DIR__ . "/{$path}.php"; + $file = __DIR__ . "/$path.php"; } elseif (0 === strpos($class, 'Inhere\Console\\')) { $path = str_replace('\\', '/', substr($class, strlen('Inhere\Console\\'))); $file = dirname(__DIR__) . "/src/{$path}.php"; From f92f411ff4857bab8e48581f6c518540e83e7ad8 Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 24 Aug 2021 22:31:29 +0800 Subject: [PATCH 132/258] replase some deprecated methods --- src/IO/Input.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/IO/Input.php b/src/IO/Input.php index d3896115..fcd6e122 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -10,6 +10,7 @@ use Toolkit\Cli\Cli; use Toolkit\Cli\Flags; +use Toolkit\Cli\Helper\FlagHelper; use function array_map; use function array_shift; use function basename; @@ -136,11 +137,11 @@ public function toString(): string protected function tokenEscape(string $token): string { if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { - return $match[1] . Flags::escapeToken($match[2]); + return $match[1] . FlagHelper::escapeToken($match[2]); } if ($token && $token[0] !== '-') { - return Flags::escapeToken($token); + return FlagHelper::escapeToken($token); } return $token; From 3924621b65d6ec5a7406003da592b9dfb914ff59 Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 25 Aug 2021 22:58:49 +0800 Subject: [PATCH 133/258] update some for handle error --- src/Component/ErrorHandler.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Component/ErrorHandler.php b/src/Component/ErrorHandler.php index b9075a11..c46487f4 100644 --- a/src/Component/ErrorHandler.php +++ b/src/Component/ErrorHandler.php @@ -10,7 +10,7 @@ use Inhere\Console\AbstractApplication; use Inhere\Console\Contract\ErrorHandlerInterface; -use Inhere\Console\Exception\PromptException; +use InvalidArgumentException; use Throwable; use Toolkit\Cli\Util\Highlighter; use function file_get_contents; @@ -30,7 +30,7 @@ class ErrorHandler implements ErrorHandlerInterface */ public function handle(Throwable $e, AbstractApplication $app): void { - if ($e instanceof PromptException) { + if ($e instanceof InvalidArgumentException) { $app->getOutput()->error($e->getMessage()); return; } @@ -69,7 +69,7 @@ public function handle(Throwable $e, AbstractApplication $app): void } // simple output - $app->getOutput()->error('An error occurred! - ' . $e->getMessage()); + $app->getOutput()->error($e->getMessage() ?: 'unknown error'); $app->write("\nYou can use '--debug 4' to see error details."); } } From 8520aebf32ba21a0e630a7386c3628bbdc8e4eb2 Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 28 Aug 2021 21:04:04 +0800 Subject: [PATCH 134/258] repleace some util methods --- examples/alone | 2 +- examples/alone-app | 2 +- examples/app | 2 +- src/Concern/ApplicationHelpTrait.php | 1 + src/Util/FormatUtil.php | 56 +++++----------------------- src/Util/Helper.php | 32 ++++------------ 6 files changed, 21 insertions(+), 74 deletions(-) diff --git a/examples/alone b/examples/alone index 186bcd0e..1675be59 100644 --- a/examples/alone +++ b/examples/alone @@ -11,7 +11,7 @@ use Toolkit\Cli\Color; define('BASE_PATH', dirname(__DIR__)); -require dirname(__DIR__) . '/test/boot.php'; +require dirname(__DIR__) . '/test/bootstrap.php'; $input = new Input(); $ctrl = new HomeController($input, new Output()); diff --git a/examples/alone-app b/examples/alone-app index 6a89f1c5..3c2ec1b7 100644 --- a/examples/alone-app +++ b/examples/alone-app @@ -11,7 +11,7 @@ use Toolkit\Cli\Color; define('BASE_PATH', dirname(__DIR__)); -require dirname(__DIR__) . '/test/boot.php'; +require dirname(__DIR__) . '/test/bootstrap.php'; try { $input = new Input(); diff --git a/examples/app b/examples/app index e2399363..96f85c01 100644 --- a/examples/app +++ b/examples/app @@ -10,7 +10,7 @@ use Inhere\Console\Application; define('BASE_PATH', dirname(__DIR__)); -require dirname(__DIR__) . '/test/boot.php'; +require dirname(__DIR__) . '/test/bootstrap.php'; // create app instance $app = new Application([ diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index 0c297e06..f4fdb21b 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -35,6 +35,7 @@ use function str_replace; use function strpos; use function strtr; +use function vdump; use const PHP_EOL; use const PHP_OS; use const PHP_VERSION; diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index c1bbd1c2..0218f40d 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -9,15 +9,13 @@ namespace Inhere\Console\Util; use Toolkit\Cli\ColorTag; +use Toolkit\Stdlib\Helper\Format; use Toolkit\Stdlib\Helper\JsonHelper; use Toolkit\Stdlib\Str; use Toolkit\Sys\Sys; use function array_keys; use function array_merge; -use function count; -use function date; use function explode; -use function floor; use function implode; use function is_array; use function is_bool; @@ -25,7 +23,6 @@ use function is_numeric; use function is_scalar; use function rtrim; -use function sprintf; use function str_repeat; use function str_replace; use function strpos; @@ -169,28 +166,18 @@ public static function alignOptions(array $options): array } /** - * @param float $memory - * - * @return string * ``` * FormatUtil::memoryUsage(memory_get_usage(true)); * ``` + * + * @param float|int $memory + * + * @return string + * @deprecated use Format::memory($secs); */ public static function memoryUsage($memory): string { - if ($memory >= 1024 * 1024 * 1024) { - return sprintf('%.2f Gb', $memory / 1024 / 1024 / 1024); - } - - if ($memory >= 1024 * 1024) { - return sprintf('%.2f Mb', $memory / 1024 / 1024); - } - - if ($memory >= 1024) { - return sprintf('%.2f Kb', $memory / 1024); - } - - return sprintf('%d B', $memory); + return Format::memory($memory); } /** @@ -199,36 +186,11 @@ public static function memoryUsage($memory): string * @param int $secs * * @return string + * @deprecated use Format::howLongAgo($secs); */ public static function howLongAgo(int $secs): string { - static $timeFormats = [ - [0, '< 1 sec'], - [1, '1 sec'], - [2, 'secs', 1], - [60, '1 min'], - [120, 'mins', 60], - [3600, '1 hr'], - [7200, 'hrs', 3600], - [86400, '1 day'], - [172800, 'days', 86400], - ]; - - foreach ($timeFormats as $index => $format) { - if ($secs >= $format[0]) { - $next = $timeFormats[$index + 1] ?? false; - - if (($next && $secs < $next[0]) || $index === count($timeFormats) - 1) { - if (2 === count($format)) { - return $format[1]; - } - - return floor($secs / $format[2]) . ' ' . $format[1]; - } - } - } - - return date('Y-m-d H:i:s', $secs); + return Format::howLongAgo($secs); } /** diff --git a/src/Util/Helper.php b/src/Util/Helper.php index 5de4b6cd..66677573 100644 --- a/src/Util/Helper.php +++ b/src/Util/Helper.php @@ -19,6 +19,7 @@ use RecursiveIteratorIterator; use RuntimeException; use Swoole\Coroutine; +use Toolkit\Stdlib\Arr\ArrayHelper; use function class_exists; use function file_exists; use function is_dir; @@ -173,29 +174,19 @@ public static function findSimilar(string $need, $iterator, int $similarPercent /** * get key Max Width * - * @param array $data - * [ + * [ * 'key1' => 'value1', * 'key2-test' => 'value2', - * ] - * @param bool $expectInt + * ] + * + * @param array $data + * @param bool $excludeInt * * @return int */ - public static function getKeyMaxWidth(array $data, bool $expectInt = false): int + public static function getKeyMaxWidth(array $data, bool $excludeInt = true): int { - $keyMaxWidth = 0; - - foreach ($data as $key => $value) { - // key is not a integer - if (!$expectInt || !is_numeric($key)) { - $width = mb_strlen((string)$key, 'UTF-8'); - - $keyMaxWidth = $width > $keyMaxWidth ? $width : $keyMaxWidth; - } - } - - return $keyMaxWidth; + return ArrayHelper::getKeyMaxWidth($data, $excludeInt); } /** @@ -206,11 +197,4 @@ public static function throwInvalidArgument(string $format, ...$args): void { throw new InvalidArgumentException(sprintf($format, ...$args)); } - - /** - * @param string $optsStr - */ - public static function formatOptions(string $optsStr): void - { - } } From 3001e1ddcd07466faf39fc2626a9484de0dd6caf Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 2 Sep 2021 22:13:33 +0800 Subject: [PATCH 135/258] update gh action scripts, add mew method to app class --- .github/changelog.yml | 27 +++++++++++++++++++++++++++ .github/dependabot.yml | 13 +++++++++++++ .gitignore | 2 ++ src/AbstractApplication.php | 15 +++++++++++++++ 4 files changed, 57 insertions(+) create mode 100644 .github/changelog.yml create mode 100644 .github/dependabot.yml diff --git a/.github/changelog.yml b/.github/changelog.yml new file mode 100644 index 00000000..f5df9693 --- /dev/null +++ b/.github/changelog.yml @@ -0,0 +1,27 @@ +options: + title: '## Change Log' + style: gh-release +filters: + # message length >= 12 + - name: msgLen + minLen: 12 + # message words >= 3 + - name: wordsLen + minLen: 3 + - name: keywords + keywords: ['format code'] + exclude: true + +# not matched will use 'Other' group. +groups: + - name: New + keywords: [add, new] + - name: Fixed + startWiths: [add, new] + keywords: [add, new] + - name: Feat + startWiths: [feat] + keywords: [feature] + - name: Update + startWiths: [update, 'up:'] + keywords: [update] diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..fc10f0ba --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +version: 2 +updates: + - package-ecosystem: composer + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every weekday + interval: "daily" diff --git a/.gitignore b/.gitignore index db34bec8..15f2b453 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ .idea/ .phpintel/ +.phpdoc/ +docs/api/ caches/ vendor/ !README.md diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 11684edd..b4bdf378 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -670,6 +670,21 @@ public function getConfig(): array return $this->config; } + /** + * @param string $name + * @param array $default + * + * @return array + */ + public function getArrayParam(string $name, array $default = []): array + { + if (isset($this->config[$name])) { + return (array)$this->config[$name]; + } + + return $default; + } + /** * Get config param value * From 2fed8f3e3d041fda3cb982fa984f5ebcbaee1d60 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 5 Sep 2021 13:32:05 +0800 Subject: [PATCH 136/258] refactor input output logic --- src/Concern/InputOutputAwareTrait.php | 13 +- src/Contract/InputInterface.php | 7 +- src/Contract/OutputInterface.php | 22 +++- src/IO/AbstractInput.php | 67 ++++++++-- src/IO/AbstractOutput.php | 21 ++++ src/IO/Input.php | 23 ++-- src/IO/Input/StreamInput.php | 112 +++++++++++++++++ src/IO/Input/StrictInput.php | 115 ------------------ src/IO/MemoryIO.php | 13 ++ src/IO/Output.php | 3 +- src/IO/Output/BufferedOutput.php | 69 +++++++++++ .../{BufferOutput.php => MemoryOutput.php} | 3 +- src/IO/Output/StreamOutput.php | 88 ++++++++++++++ src/IO/StreamIO.php | 13 ++ 14 files changed, 420 insertions(+), 149 deletions(-) create mode 100644 src/IO/AbstractOutput.php create mode 100644 src/IO/Input/StreamInput.php delete mode 100644 src/IO/Input/StrictInput.php create mode 100644 src/IO/MemoryIO.php create mode 100644 src/IO/Output/BufferedOutput.php rename src/IO/Output/{BufferOutput.php => MemoryOutput.php} (84%) create mode 100644 src/IO/Output/StreamOutput.php create mode 100644 src/IO/StreamIO.php diff --git a/src/Concern/InputOutputAwareTrait.php b/src/Concern/InputOutputAwareTrait.php index 88f6c470..062772d6 100644 --- a/src/Concern/InputOutputAwareTrait.php +++ b/src/Concern/InputOutputAwareTrait.php @@ -8,11 +8,11 @@ namespace Inhere\Console\Concern; -use Inhere\Console\Console; use Inhere\Console\IO\Input; use Inhere\Console\Contract\InputInterface; use Inhere\Console\IO\Output; use Inhere\Console\Contract\OutputInterface; +use Toolkit\PFlag\SFlags; /** * Class InputOutputAwareTrait @@ -21,6 +21,11 @@ */ trait InputOutputAwareTrait { + /** + * @var SFlags + */ + // protected $flags; + /** * @var Input|InputInterface */ @@ -140,9 +145,9 @@ public function getRequiredOpt(string $name, string $errMsg = '') * * @return string */ - public function read(string $question = '', bool $nl = false): string + public function readln(string $question = '', bool $nl = false): string { - return $this->input->read($question, $nl); + return $this->input->readln($question, $nl); } /** @@ -152,7 +157,7 @@ public function read(string $question = '', bool $nl = false): string * * @return int */ - public function write($message, $nl = true, $quit = false): int + public function write($message, bool $nl = true, $quit = false): int { return $this->output->write($message, $nl, $quit); } diff --git a/src/Contract/InputInterface.php b/src/Contract/InputInterface.php index 4e778678..9659e522 100644 --- a/src/Contract/InputInterface.php +++ b/src/Contract/InputInterface.php @@ -38,7 +38,7 @@ interface InputInterface * * @return string */ - public function read(string $question = '', bool $nl = false): string; + public function readln(string $question = '', bool $nl = false): string; /** * @return string @@ -77,4 +77,9 @@ public function getOpts(): array; * @return bool|mixed|null */ public function getOpt(string $name, $default = null); + + /** + * Whether the stream is an interactive terminal + */ + public function isInteractive() : bool; } diff --git a/src/Contract/OutputInterface.php b/src/Contract/OutputInterface.php index 2deb09e3..c8582057 100644 --- a/src/Contract/OutputInterface.php +++ b/src/Contract/OutputInterface.php @@ -16,13 +16,25 @@ interface OutputInterface { /** - * Write a message to standard output stream. + * Write a message to output * - * @param mixed $messages Output message - * @param bool $nl Output with newline - * @param int|boolean $quit If is int, setting it is exit code. + * @param string $content * * @return int */ - public function write($messages, $nl = true, $quit = false): int; + public function write(string $content): int; + + /** + * Write a message to output with newline + * + * @param string $content + * + * @return int + */ + public function writeln(string $content): int; + + /** + * Whether the stream is an interactive terminal + */ + public function isInteractive() : bool; } diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index d992accc..f1c8183f 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -11,6 +11,8 @@ use Inhere\Console\Concern\InputArgumentsTrait; use Inhere\Console\Concern\InputOptionsTrait; use Inhere\Console\Contract\InputInterface; +use Toolkit\PFlag\AbstractFlags; +use Toolkit\PFlag\SFlags; use function getcwd; use function is_int; use function trim; @@ -24,6 +26,20 @@ abstract class AbstractInput implements InputInterface { use InputArgumentsTrait, InputOptionsTrait; + /** + * Global flags parser + * + * @var AbstractFlags|SFlags + */ + protected $gfs; + + /** + * Command flags parser + * + * @var AbstractFlags|SFlags + */ + protected $fs; + /** * @var string */ @@ -69,7 +85,8 @@ abstract class AbstractInput implements InputInterface protected $fullScript; /** - * Raw argv data. + * Raw input argv data. + * - first element is script file * * @var array */ @@ -137,6 +154,14 @@ public function getCommandPath(): string return $path; } + /** + * @return bool + */ + public function isInteractive(): bool + { + return false; + } + /*********************************************************************************** * getter/setter ***********************************************************************************/ @@ -261,14 +286,6 @@ public function getRawFlags(): array return $this->tokens; } - /** - * @return array - */ - public function getRawArgs(): array - { - return $this->tokens; - } - /** * @return array */ @@ -300,4 +317,36 @@ public function setSubCommand(string $subCommand): void { $this->subCommand = $subCommand; } + + /** + * @return AbstractFlags + */ + public function getGfs(): AbstractFlags + { + return $this->gfs; + } + + /** + * @param AbstractFlags $gfs + */ + public function setGfs(AbstractFlags $gfs): void + { + $this->gfs = $gfs; + } + + /** + * @return AbstractFlags + */ + public function getFs(): AbstractFlags + { + return $this->fs; + } + + /** + * @param AbstractFlags $fs + */ + public function setFs(AbstractFlags $fs): void + { + $this->fs = $fs; + } } diff --git a/src/IO/AbstractOutput.php b/src/IO/AbstractOutput.php new file mode 100644 index 00000000..d0d197aa --- /dev/null +++ b/src/IO/AbstractOutput.php @@ -0,0 +1,21 @@ +getPwd(); - if (!$args) { + if (!$rawFlags) { return; } - $this->tokens = $args; + $this->tokens = $rawFlags; // first is bin file - if (isset($args[0]) && is_string($args[0])) { - $this->script = array_shift($args); + if (isset($rawFlags[0]) && is_string($rawFlags[0])) { + $this->script = array_shift($rawFlags); // bin name $this->scriptName = basename($this->script); } - $this->flags = $args; // no script + $this->flags = $rawFlags; // no script // full script - $this->fullScript = implode(' ', $args); + $this->fullScript = implode(' ', $rawFlags); } /** @@ -155,13 +156,13 @@ protected function tokenEscape(string $token): string * * @return string */ - public function read(string $question = '', bool $nl = false): string + public function readln(string $question = '', bool $nl = false): string { if ($question) { - fwrite(Cli::getOutputStream(), $question . ($nl ? "\n" : '')); + fwrite($this->inputStream, $question . ($nl ? "\n" : '')); } - return trim((string)fgets($this->inputStream)); + return File::streamFgets($this->inputStream); } /*********************************************************************************** diff --git a/src/IO/Input/StreamInput.php b/src/IO/Input/StreamInput.php new file mode 100644 index 00000000..06340ec2 --- /dev/null +++ b/src/IO/Input/StreamInput.php @@ -0,0 +1,112 @@ +setStream($stream); + } + + public function toString(): string + { + return ''; + } + + /** + * @return string + */ + public function readAll(): string + { + return File::streamReadAll($this->stream); + } + + /** + * @param string $question + * @param bool $nl + * + * @return string + */ + public function read(string $question = '', bool $nl = false): string + { + if ($question) { + fwrite($this->stream, $question . ($nl ? "\n" : '')); + } + + return File::streamFgets($this->stream); + } + + /** + * @param string $question + * @param bool $nl + * + * @return string + */ + public function readln(string $question = '', bool $nl = false): string + { + if ($question) { + fwrite($this->stream, $question . ($nl ? "\n" : '')); + } + + return File::streamFgets($this->stream); + } + + /** + * @return resource + */ + public function getStream() + { + return $this->stream; + } + + /** + * @param resource $stream + */ + protected function setStream($stream): void + { + File::assertStream($stream); + + $meta = stream_get_meta_data($stream); + if (strpos($meta['mode'], 'r') === false && strpos($meta['mode'], '+') === false) { + throw new InvalidArgumentException('Expected a readable stream'); + } + + $this->stream = $stream; + } + + /** + * Whether the stream is an interactive terminal + * + * @return bool + */ + public function isInteractive(): bool + { + return OS::isInteractive($this->stream); + } +} diff --git a/src/IO/Input/StrictInput.php b/src/IO/Input/StrictInput.php deleted file mode 100644 index f606b886..00000000 --- a/src/IO/Input/StrictInput.php +++ /dev/null @@ -1,115 +0,0 @@ - has value - 'h' => false, - 'V' => false, - 'help' => false, - 'debug' => true, - 'profile' => false, - 'version' => false, - ]; - - /** @var array */ - private $cleanedTokens; - - /** - * FixedInput constructor. - * - * @param null|array $args - */ - public function __construct(array $args = null) - { - if (null === $args) { - $args = (array)$_SERVER['argv']; - } - - parent::__construct($args, false); - - $copy = $args; - - // command name - if (!empty($copy[1]) && $copy[1][0] !== '-' && false === strpos($copy[1], '=')) { - $this->setCommand($copy[1]); - - // unset command - unset($copy[1]); - } - - // pop script name - array_shift($copy); - - $this->cleanedTokens = $copy; - $this->collectPreParsed($copy); - } - - private function collectPreParsed(array $tokens): void - { - // foreach ($this->preParsed as $name => $hasVal) { - // - // } - } - - /** - * @param array $allowArray - * @param array $noValues - */ - public function parseTokens(array $allowArray = [], array $noValues = []): void - { - $params = $this->getTokens(); - array_shift($params); // pop script name - } - - /** - * @return array - */ - public function getPreParsed(): array - { - return $this->preParsed; - } - - /** - * @param array $preParsed - */ - public function setPreParsed(array $preParsed): void - { - $this->preParsed = $preParsed; - } - - /** - * @return array|null - */ - public function getCleanedTokens(): array - { - return $this->cleanedTokens; - } -} diff --git a/src/IO/MemoryIO.php b/src/IO/MemoryIO.php new file mode 100644 index 00000000..869756d7 --- /dev/null +++ b/src/IO/MemoryIO.php @@ -0,0 +1,13 @@ +buffer; + + if ($reset) { + $this->buffer = ''; + } + + return $str; + } + + public function __toString(): string + { + return $this->fetch(); + } + + /** + * @param string $content + * + * @return int + */ + public function write(string $content): int + { + $this->buffer .= $content; + return strlen($content); + } + + /** + * @param string $content + * + * @return int + */ + public function writeln(string $content): int + { + $this->buffer .= $content . PHP_EOL; + + return strlen($content) + 1; + } + + public function reset(): void + { + $this->buffer = ''; + } +} \ No newline at end of file diff --git a/src/IO/Output/BufferOutput.php b/src/IO/Output/MemoryOutput.php similarity index 84% rename from src/IO/Output/BufferOutput.php rename to src/IO/Output/MemoryOutput.php index 42b1269b..9398c5a6 100644 --- a/src/IO/Output/BufferOutput.php +++ b/src/IO/Output/MemoryOutput.php @@ -2,7 +2,6 @@ namespace Inhere\Console\IO\Output; -use Inhere\Console\IO\Output; use function fopen; /** @@ -10,7 +9,7 @@ * * @package Inhere\Console\IO\Output */ -class BufferOutput extends Output +class MemoryOutput extends StreamOutput { public function __construct() { diff --git a/src/IO/Output/StreamOutput.php b/src/IO/Output/StreamOutput.php new file mode 100644 index 00000000..917acc93 --- /dev/null +++ b/src/IO/Output/StreamOutput.php @@ -0,0 +1,88 @@ +setStream($stream); + } + + /** + * @param string $content + * + * @return int + */ + public function write(string $content): int + { + return File::streamWrite($this->stream, $content); + } + + /** + * @param string $content + * + * @return int + */ + public function writeln(string $content): int + { + return File::streamWrite($this->stream, $content . PHP_EOL); + } + + /** + * @return resource + */ + public function getStream() + { + return $this->stream; + } + + /** + * @param resource $stream + */ + protected function setStream($stream): void + { + File::assertStream($stream); + + $meta = stream_get_meta_data($stream); + if (strpos($meta['mode'], 'w') === false && strpos($meta['mode'], '+') === false) { + throw new InvalidArgumentException('Expected a readable stream'); + } + + $this->stream = $stream; + } + + /** + * Whether the stream is an interactive terminal + * + * @return bool + */ + public function isInteractive(): bool + { + return OS::isInteractive($this->stream); + } +} \ No newline at end of file diff --git a/src/IO/StreamIO.php b/src/IO/StreamIO.php new file mode 100644 index 00000000..1f4fedf3 --- /dev/null +++ b/src/IO/StreamIO.php @@ -0,0 +1,13 @@ + Date: Sun, 5 Sep 2021 20:46:19 +0800 Subject: [PATCH 137/258] update some --- examples/Controller/InteractController.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/Controller/InteractController.php b/examples/Controller/InteractController.php index f6340090..1f7fc7a4 100644 --- a/examples/Controller/InteractController.php +++ b/examples/Controller/InteractController.php @@ -11,7 +11,7 @@ use Inhere\Console\Controller; use Inhere\Console\Util\Interact; use Inhere\Console\Util\Show; -use Toolkit\Cli\Terminal; +use Toolkit\Cli\Util\Terminal; use function preg_match; /** @@ -141,28 +141,28 @@ public function cursorCommand(): void $this->write('this is a message text.', false); sleep(1); - Terminal::make()->cursor(Terminal::CUR_BACKWARD, 6); + Terminal::instance()->cursor(Terminal::CURSOR_BACKWARD, 6); sleep(1); - Terminal::make()->cursor(Terminal::CUR_FORWARD, 3); + Terminal::instance()->cursor(Terminal::CURSOR_FORWARD, 3); sleep(1); - Terminal::make()->cursor(Terminal::CUR_BACKWARD, 2); + Terminal::instance()->cursor(Terminal::CURSOR_BACKWARD, 2); sleep(2); - Terminal::make()->screen(Terminal::CLEAR_LINE, 3); + Terminal::instance()->screen(Terminal::CLEAR_LINE, 3); $this->write('after 2s scroll down 3 row.'); sleep(2); - Terminal::make()->screen(Terminal::SCROLL_DOWN, 3); + Terminal::instance()->screen(Terminal::SCROLL_DOWN, 3); $this->write('after 3s clear screen.'); sleep(3); - Terminal::make()->screen(Terminal::CLEAR); + Terminal::instance()->screen(Terminal::CLEAR); } } From 6fa7b4b5bc76911bd5fe48b9463cab4d5dd6a351 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 5 Sep 2021 22:33:20 +0800 Subject: [PATCH 138/258] fix run controller error --- src/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Application.php b/src/Application.php index 449603e9..b658584d 100644 --- a/src/Application.php +++ b/src/Application.php @@ -373,7 +373,7 @@ protected function runAction(array $info, array $options, bool $detachedRun = f } // Command method, no suffix - return $controller->run($info['action']); + return $controller->run([$info['action']]); } /** From b0a63e890a345cd4e5f80fd027c6a33268d10b13 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 5 Sep 2021 22:45:10 +0800 Subject: [PATCH 139/258] remove some unused class --- src/Flag/FlagOptionsTrait.php | 83 --------- src/Flag/Flags.php | 335 ---------------------------------- test/Flag/FlagsTest.php | 24 --- 3 files changed, 442 deletions(-) delete mode 100644 src/Flag/FlagOptionsTrait.php delete mode 100644 src/Flag/Flags.php delete mode 100644 test/Flag/FlagsTest.php diff --git a/src/Flag/FlagOptionsTrait.php b/src/Flag/FlagOptionsTrait.php deleted file mode 100644 index 03718bd6..00000000 --- a/src/Flag/FlagOptionsTrait.php +++ /dev/null @@ -1,83 +0,0 @@ -setShortcut($shorts); - - $this->addOption($opt); - } - - /** - * @param Option $option - */ - public function addOption(Option $option): void - { - $name = $option->getName(); - - $this->defined[$name] = $option; - } - - /** - * @param Option[] $options - */ - public function addOptions(array $options): void - { - foreach ($options as $option) { - $this->addOption($option); - } - } - - /** - * @return Option[] - */ - public function getDefinedOptions(): array - { - return $this->defined; - } - - /** - * @return Option[] - */ - public function getMatchedOptions(): array - { - return $this->matched; - } -} diff --git a/src/Flag/Flags.php b/src/Flag/Flags.php deleted file mode 100644 index 9cc3d2f6..00000000 --- a/src/Flag/Flags.php +++ /dev/null @@ -1,335 +0,0 @@ -parse($args); - } - - /** - * @var string - */ - private $curOptKey = ''; - - private $parseStatus = self::STATUS_OK; - - public const STATUS_OK = 0; - public const STATUS_ERR = 1; - public const STATUS_END = 2; - public const STATUS_HELP = 3; // found `-h|--help` flag - - /** - * @param array|null $args - * - * @return array - */ - public function parse(array $args = null): array - { - if ($args === null) { - $args = $_SERVER['argv']; - } - - $this->parsed = true; - $this->rawArgs = $this->args = $args; - - while (true) { - [$goon, $status] = $this->parseOne(); - if ($goon) { - continue; - } - - if (self::STATUS_OK === $status) { - break; - } - } - - // binding remaining args. - if ($this->args && $this->autoBindArgs) { - $this->bindingArguments(); - } - - return []; - } - - /** - * parse one flag. - * - * will stop on: - * - found `-h|--help` flag - * - found first arg(not an option) - * - * @return array [bool, status] - */ - protected function parseOne(): array - { - $count = count($this->args); - if ($count === 0) { - return [false, self::STATUS_OK]; - } - - $arg = array_shift($this->args); - - // empty, continue. - if ('' === $arg) { - return [true, self::STATUS_OK]; - } - - // is not an option flag. exit. - if ($arg[0] !== '-') { - return [false, self::STATUS_OK]; - } - - $name = ltrim($arg, '-'); - - // invalid arg. eg: '--' // ignore - if ('' === $name) { - return [true, self::STATUS_OK]; - } - - $value = ''; - $hasVal = false; - - $len = strlen($name); - for ($i = 0; $i < $len; $i++) { - if ($name[$i] === '=') { - $hasVal = true; - $name = substr($name, 0, $i); - - // fix: `--name=` no value string. - if ($i + 1 < $len) { - $value = substr($name, $i + 1); - } - } - } - - $rName = $this->resolveAlias($name); - if (!isset($this->defined[$rName])) { - throw new FlagException("flag option provided but not defined: $arg", 404); - } - - $opt = $this->defined[$rName]; - - // bool option always set TRUE. - if ($opt->isBoolean()) { - $boolVal = true; - if ($hasVal) { - $boolVal = self::filterBool($value); - } - $opt->setValue($boolVal); - } else { - if (!$hasVal && count($this->args) > 0) { - // value is next arg - $hasVal = true; - $ntArg = $this->args[0]; - - // is not an option value. - if ($ntArg[0] === '-') { - $hasVal = false; - } else { - $value = array_shift($this->args); - } - } - - if (!$hasVal) { - throw new FlagException("flag option '$arg' needs an value", 400); - } - - // set value - $opt->setValue($value); - } - - return [true, self::STATUS_OK]; - } - - // These words will be as a Boolean value - private const TRUE_WORDS = '|on|yes|true|'; - private const FALSE_WORDS = '|off|no|false|'; - - /** - * @param string $val - * - * @return bool|null - */ - public static function filterBool(string $val): ?bool - { - // check it is a bool value. - return false !== stripos(self::TRUE_WORDS, "|$val|"); - } - - /** - * @param string $val - * - * @return bool|null - */ - public static function filterBoolV2(string $val): ?bool - { - // check it is a bool value. - if (false !== stripos(self::TRUE_WORDS, "|$val|")) { - return true; - } - - if (false !== stripos(self::FALSE_WORDS, "|$val|")) { - return false; - } - - // return null; - return null; - } - - /************************************************************************** - * parse and binding command arguments - **************************************************************************/ - - /** - * parse and binding command arguments - * - * NOTICE: must call it on options parsed. - */ - public function bindingArguments(): void - { - if (!$this->args) { - return; - } - - // TODO ... - } - - /** - * @return callable - */ - public function getHelpRenderer(): callable - { - return $this->helpRenderer; - } - - /** - * @param callable $helpRenderer - */ - public function setHelpRenderer(callable $helpRenderer): void - { - $this->helpRenderer = $helpRenderer; - } - - /** - * @return array - */ - public function getRawArgs(): array - { - return $this->rawArgs; - } - - /** - * @return array - */ - public function getArgs(): array - { - return $this->args; - } - - /** - * @return bool - */ - public function isAutoBindArgs(): bool - { - return $this->autoBindArgs; - } - - /** - * @param bool $autoBindArgs - */ - public function setAutoBindArgs(bool $autoBindArgs): void - { - $this->autoBindArgs = $autoBindArgs; - } - - /** - * @return bool - */ - public function isParsed(): bool - { - return $this->parsed; - } -} diff --git a/test/Flag/FlagsTest.php b/test/Flag/FlagsTest.php deleted file mode 100644 index c93886ad..00000000 --- a/test/Flag/FlagsTest.php +++ /dev/null @@ -1,24 +0,0 @@ -addOption(Option::new('name')); - - $args = ['--name', 'inhere', '-s', 'sv', '-f']; - } -} From d46ddc1b0b643e63bd1b687746a27a431501e6fa Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 5 Sep 2021 23:09:28 +0800 Subject: [PATCH 140/258] remove some empty class --- src/IO/MemoryIO.php | 13 ------------- src/IO/StreamIO.php | 13 ------------- 2 files changed, 26 deletions(-) delete mode 100644 src/IO/MemoryIO.php delete mode 100644 src/IO/StreamIO.php diff --git a/src/IO/MemoryIO.php b/src/IO/MemoryIO.php deleted file mode 100644 index 869756d7..00000000 --- a/src/IO/MemoryIO.php +++ /dev/null @@ -1,13 +0,0 @@ - Date: Sun, 5 Sep 2021 23:47:06 +0800 Subject: [PATCH 141/258] update some for global options --- src/AbstractApplication.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index b4bdf378..b66e6a5e 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -66,12 +66,19 @@ abstract class AbstractApplication implements ApplicationInterface /** @var array */ protected static $globalOptions = [ + // '--debug' => 'int;no;1;Setting the runtime log debug level(quiet 0 - 5 crazy)', '--debug' => 'Setting the runtime log debug level(quiet 0 - 5 crazy)', + // '--ishell' => 'bool;no;false;Run application an interactive shell environment', '--ishell' => 'Run application an interactive shell environment', + // '--profile' => 'bool;no;false;Display timing and memory usage information', '--profile' => 'Display timing and memory usage information', + // '--no-color' => 'bool;no;false;Disable color/ANSI for message output', '--no-color' => 'Disable color/ANSI for message output', + // '-h, --help' => 'bool;no;false;Display this help message', '-h, --help' => 'Display this help message', + // '-V, --version' => 'bool;no;false;Show application version information', '-V, --version' => 'Show application version information', + // '--no-interactive' => 'bool;no;false;Run commands in a non-interactive environment', '--no-interactive' => 'Run commands in a non-interactive environment', ]; From 1e14a53aaae1787eea8c6aa52de1777b14c14aec Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 9 Sep 2021 11:37:31 +0800 Subject: [PATCH 142/258] update some for choose logic --- src/Component/Interact/Choose.php | 62 +++++++++++++++++++++----- src/Component/InteractiveHandle.php | 21 +++++++++ src/Concern/StyledOutputAwareTrait.php | 2 +- src/Util/Interact.php | 14 +++--- 4 files changed, 81 insertions(+), 18 deletions(-) diff --git a/src/Component/Interact/Choose.php b/src/Component/Interact/Choose.php index 10a0b5a4..262cbb3e 100644 --- a/src/Component/Interact/Choose.php +++ b/src/Component/Interact/Choose.php @@ -17,23 +17,54 @@ */ class Choose extends InteractiveHandle { + /** + * @var array + */ + protected $data = []; + + /** + * @var bool + */ + protected $allowExit = true; + + /** + * The default selected key + * + * @var string + */ + protected $default; + + /** + * The selected key + * + * @var string + */ + protected $selected; + + /** + * @var string + */ + protected $selectedVal; + /** * Choose one of several options * - * @param string $description - * @param string|array $options Option data - * e.g - * [ - * // option => value - * '1' => 'chengdu', - * '2' => 'beijing' - * ] - * @param string|int $default Default option - * @param bool $allowExit + * @param string $description + * @param string|array $options Option data + * e.g + * [ + * // option => value + * '1' => 'chengdu', + * '2' => 'beijing' + * ] + * @param null|int|string $default Default option + * @param bool $allowExit + * @param array $opts + * @psalm-param array{returnVal: bool, retFilter: callable} $opts * * @return string */ - public static function one(string $description, $options, $default = null, bool $allowExit = true): string + public static function one(string $description, $options, $default = null, bool $allowExit = true, array $opts = []): string { if (!$description = trim($description)) { Show::error('Please provide a description text!', 1); @@ -71,6 +102,15 @@ public static function one(string $description, $options, $default = null, bool Console::write("\n Quit,ByeBye.", true, true); } + // return value + if ($opts['returnVal'] ?? false) { + $r = $options[$r]; + } + + if ($retFn = $opts['retFilter'] ?? null) { + $r = $retFn($r); + } + return $r; } } diff --git a/src/Component/InteractiveHandle.php b/src/Component/InteractiveHandle.php index 01d4ecd1..03300fce 100644 --- a/src/Component/InteractiveHandle.php +++ b/src/Component/InteractiveHandle.php @@ -28,6 +28,11 @@ abstract class InteractiveHandle */ protected $validator; + /** + * @var callable + */ + protected $ansFilter; + /** * Class constructor. * @@ -48,4 +53,20 @@ public function setValidator(callable $validator): self $this->validator = $validator; return $this; } + + /** + * @return callable + */ + public function getAnsFilter(): callable + { + return $this->ansFilter; + } + + /** + * @param callable $ansFilter + */ + public function setAnsFilter(callable $ansFilter): void + { + $this->ansFilter = $ansFilter; + } } diff --git a/src/Concern/StyledOutputAwareTrait.php b/src/Concern/StyledOutputAwareTrait.php index 3bdbbe2c..dc42aab6 100644 --- a/src/Concern/StyledOutputAwareTrait.php +++ b/src/Concern/StyledOutputAwareTrait.php @@ -55,7 +55,7 @@ * * @method confirm(string $question, bool $default = true): bool * @method unConfirm(string $question, bool $default = true): bool - * @method select(string $description, $options, $default = null, bool $allowExit = true): string + * @method string select(string $description, $options, $default = null, bool $allowExit = true, array $opts = []) * @method checkbox(string $description, $options, $default = null, bool $allowExit = true): array * @method ask(string $question, string $default = '', Closure $validator = null): string * @method askPassword(string $prompt = 'Enter Password:'): string diff --git a/src/Util/Interact.php b/src/Util/Interact.php index b255af9f..908e3db0 100644 --- a/src/Util/Interact.php +++ b/src/Util/Interact.php @@ -78,14 +78,15 @@ public static function readFirst($message = null, bool $nl = false): string * * @param string $description 说明 * @param string|array $options 选项数据 - * @param int|string $default 默认选项 + * @param null $default 默认选项 * @param bool $allowExit 有退出选项 默认 true + * @param array $opts * * @return string */ - public static function select(string $description, $options, $default = null, bool $allowExit = true): string + public static function select(string $description, $options, $default = null, bool $allowExit = true, array $opts = []): string { - return self::choice($description, $options, $default, $allowExit); + return self::choice($description, $options, $default, $allowExit, $opts); } /** @@ -99,14 +100,15 @@ public static function select(string $description, $options, $default = null, bo * '1' => 'chengdu', * '2' => 'beijing' * ] - * @param string|int $default Default option + * @param null $default Default option * @param bool $allowExit + * @param array $opts * * @return string */ - public static function choice(string $description, $options, $default = null, bool $allowExit = true): string + public static function choice(string $description, $options, $default = null, bool $allowExit = true, array $opts = []): string { - return Choose::one($description, $options, $default, $allowExit); + return Choose::one($description, $options, $default, $allowExit, $opts); } /** From dbba4c2b4882392908f556792cacb336c1884485 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 12 Sep 2021 02:02:20 +0800 Subject: [PATCH 143/258] update some logic for controller action run --- examples/Controller/HomeController.php | 2 +- examples/Controller/ShowController.php | 2 +- src/AbstractApplication.php | 291 +++++++++++++--------- src/AbstractHandler.php | 108 +++++++- src/Application.php | 25 +- src/{Anotation => Attr}/.keep | 0 src/Attr/CmdArgument.php | 11 + src/Attr/CmdOption.php | 11 + src/CallableCommand.php | 54 ---- src/Component/Interact/AbstractSelect.php | 38 +++ src/Component/Interact/Checkbox.php | 5 +- src/Component/Interact/Choose.php | 31 +-- src/Component/Interact/MultiSelect.php | 61 +++++ src/Component/Interact/SingleSelect.php | 61 +++++ src/Component/PharCompiler.php | 3 + src/Concern/ApplicationHelpTrait.php | 6 +- src/Concern/ControllerHelpTrait.php | 18 +- src/Concern/InputArgumentsTrait.php | 2 +- src/Concern/InputOutputAwareTrait.php | 31 ++- src/ConsoleConst.php | 17 ++ src/Contract/ApplicationInterface.php | 4 +- src/Contract/InputInterface.php | 2 +- src/Controller.php | 134 ++++++---- src/GlobalOption.php | 80 +++++- src/Handler/CallableCommand.php | 9 + src/IO/AbstractInput.php | 20 +- src/IO/Input.php | 8 +- src/IO/Output.php | 2 +- src/Util/FormatUtil.php | 4 + src/Util/Helper.php | 33 ++- test/IO/InputTest.php | 2 +- 31 files changed, 740 insertions(+), 335 deletions(-) rename src/{Anotation => Attr}/.keep (100%) create mode 100644 src/Attr/CmdArgument.php create mode 100644 src/Attr/CmdOption.php delete mode 100644 src/CallableCommand.php create mode 100644 src/Component/Interact/AbstractSelect.php create mode 100644 src/Component/Interact/MultiSelect.php create mode 100644 src/Component/Interact/SingleSelect.php create mode 100644 src/ConsoleConst.php diff --git a/examples/Controller/HomeController.php b/examples/Controller/HomeController.php index ae4f002b..cbacbf59 100644 --- a/examples/Controller/HomeController.php +++ b/examples/Controller/HomeController.php @@ -61,7 +61,7 @@ protected function init(): void /** * @return array */ - protected function groupOptions(): array + protected function options(): array { return [ '-c, --common' => 'This is a common option for all sub-commands', diff --git a/examples/Controller/ShowController.php b/examples/Controller/ShowController.php index 4bafc984..1730bd8d 100644 --- a/examples/Controller/ShowController.php +++ b/examples/Controller/ShowController.php @@ -42,7 +42,7 @@ public static function commandAliases(): array /** * @return array */ - protected function groupOptions(): array + protected function options(): array { return [ '-c, --common' => 'This is a common option for all sub-commands', diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index b66e6a5e..047a27bc 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -11,27 +11,30 @@ use ErrorException; use Inhere\Console\Component\ErrorHandler; use Inhere\Console\Component\Formatter\Title; +use Inhere\Console\Concern\ApplicationHelpTrait; +use Inhere\Console\Concern\InputOutputAwareTrait; +use Inhere\Console\Concern\SimpleEventAwareTrait; use Inhere\Console\Concern\StyledOutputAwareTrait; use Inhere\Console\Contract\ApplicationInterface; use Inhere\Console\Contract\ErrorHandlerInterface; use Inhere\Console\Contract\InputInterface; +use Inhere\Console\Contract\OutputInterface; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; -use Inhere\Console\Contract\OutputInterface; -use Inhere\Console\Concern\ApplicationHelpTrait; -use Inhere\Console\Concern\InputOutputAwareTrait; -use Inhere\Console\Concern\SimpleEventAwareTrait; +use Inhere\Console\Util\Helper; use Inhere\Console\Util\Interact; use InvalidArgumentException; use Throwable; use Toolkit\Cli\Style; use Toolkit\Cli\Util\LineParser; +use Toolkit\PFlag\SFlags; use Toolkit\Stdlib\Helper\PhpHelper; use Toolkit\Stdlib\OS; use Toolkit\Sys\Proc\ProcessUtil; use Toolkit\Sys\Proc\Signal; use function array_keys; use function array_merge; +use function array_shift; use function error_get_last; use function header; use function in_array; @@ -64,24 +67,6 @@ abstract class AbstractApplication implements ApplicationInterface 'list' => 'List all group and alone commands', ]; - /** @var array */ - protected static $globalOptions = [ - // '--debug' => 'int;no;1;Setting the runtime log debug level(quiet 0 - 5 crazy)', - '--debug' => 'Setting the runtime log debug level(quiet 0 - 5 crazy)', - // '--ishell' => 'bool;no;false;Run application an interactive shell environment', - '--ishell' => 'Run application an interactive shell environment', - // '--profile' => 'bool;no;false;Display timing and memory usage information', - '--profile' => 'Display timing and memory usage information', - // '--no-color' => 'bool;no;false;Disable color/ANSI for message output', - '--no-color' => 'Disable color/ANSI for message output', - // '-h, --help' => 'bool;no;false;Display this help message', - '-h, --help' => 'Display this help message', - // '-V, --version' => 'bool;no;false;Show application version information', - '-V, --version' => 'Show application version information', - // '--no-interactive' => 'bool;no;false;Run commands in a non-interactive environment', - '--no-interactive' => 'Run commands in a non-interactive environment', - ]; - /** @var array Application runtime stats */ protected $stats = [ 'startTime' => 0, @@ -90,12 +75,23 @@ abstract class AbstractApplication implements ApplicationInterface 'endMemory' => 0, ]; + /** + * @var string + */ + public $delimiter = ':'; // '/' ':' + + /** + * @var string + */ + protected $commandName = ''; + + /** + * @var string Command delimiter char. e.g dev:serve + */ /** @var array Application config data */ protected $config = [ 'name' => 'My Console Application', 'description' => 'This is my console application', - 'debug' => Console::VERB_ERROR, - 'profile' => false, 'version' => '0.5.1', 'homepage' => '', // can provide you app homepage url 'publishAt' => '2017.03.24', @@ -103,23 +99,22 @@ abstract class AbstractApplication implements ApplicationInterface 'rootPath' => '', 'ishellName' => '', // name prefix on i-shell env. 'strictMode' => false, + // hide root path on dump error stack 'hideRootPath' => true, - // global options - 'no-interactive' => true, + // ---- global options + 'no-interactive' => false, + 'debug' => Console::VERB_ERROR, + 'profile' => false, // 'timeZone' => 'Asia/Shanghai', // 'env' => 'prod', // dev test prod // 'charset' => 'UTF-8', - 'logoText' => '', - 'logoStyle' => 'info', + // other settings. + 'logoText' => '', + 'logoStyle' => 'info', ]; - /** - * @var string Command delimiter char. e.g dev:serve - */ - public $delimiter = ':'; // '/' ':' - /** * @var Router */ @@ -149,6 +144,8 @@ public function __construct(array $config = [], Input $input = null, Output $out $this->input = $input ?: new Input(); $this->output = $output ?: new Output(); + + $this->flags = new SFlags(); $this->router = new Router(); $this->init(); @@ -175,50 +172,104 @@ protected function init(): void $this->logf(Console::VERB_DEBUG, 'console application init completed'); } - /** - * @param string $name - * - * @return bool - */ - public static function isGlobalOption(string $name): bool + /********************************************************** + * app run + **********************************************************/ + + protected function initForRun(Input $input): void { - return isset(GlobalOption::KEY_MAP[$name]); + $input->setGfs($this->flags); + + // binding global options + $this->logf(Console::VERB_DEBUG, 'init - add and binding global options'); + $this->flags->addOptsByRules(GlobalOption::getOptions()); + $this->flags->setScriptFile($input->getScriptFile()); + $this->flags->setAutoBindArgs(false); + + // set helper render + $this->flags->setHelpRenderer(function () { + $this->showHelpInfo(); + $this->stop(); + }); + + // parse options + $this->logf(Console::VERB_DEBUG, 'init - begin parse global options'); + $this->flags->parse($input->getFlags()); + } - /** - * @return array - */ - public static function getGlobalOptions(): array + protected function prepareRun(): void { - return self::$globalOptions; + // if ($this->input->getSameOpt(['no-color'])) { + // if disable color + if ($this->flags->getOpt(GlobalOption::NO_COLOR)) { + Style::setNoColor(); + } + + // date_default_timezone_set($this->config('timeZone', 'UTC')); + // new AutoCompletion(array_merge($this->getCommandNames(), $this->getControllerNames())); } /** - * @param array $options + * @return bool Return true for continue handle. */ - public function addGlobalOptions(array $options): void + protected function handleGlobalOption(): bool { - if ($options) { - self::$globalOptions = array_merge(self::$globalOptions, $options); - } - } + // TIP help option has been handled by `initGlobalFlags.setHelpRenderer` - /********************************************************** - * app run - **********************************************************/ + // if ($this->input->getSameBoolOpt(GlobalOption::VERSION_OPTS)) { + if ($this->flags->getOpt(GlobalOption::VERSION)) { + $this->showVersionInfo(); + return false; + } - protected function prepareRun(): void - { - if ($this->input->getSameOpt(['no-color'])) { - Style::setNoColor(); + // if ($this->input->getBoolOpt(GlobalOption::ISHELL)) { + if ($this->flags->getOpt(GlobalOption::ISHELL)) { + $this->startInteractiveShell(); + return false; } - // date_default_timezone_set($this->config('timeZone', 'UTC')); - // new AutoCompletion(array_merge($this->getCommandNames(), $this->getControllerNames())); + return true; } - protected function beforeRun(): void + protected function beforeRun(): bool { + // eg: --version, --help + if (!$this->handleGlobalOption()) { + return false; + } + + $remainArgs = $this->flags->getRawArgs(); + + // not input command + if (!$remainArgs) { + $this->showCommandList(); + return false; + } + + // remain first arg as command name. + $firstArg = array_shift($remainArgs); + + if (!Helper::isValidCmdPath($firstArg)) { + $this->output->liteError('input an invalid command name'); + $this->showCommandList(); + return false; + } + + $command = trim($firstArg, $this->delimiter); + // save name. + $this->commandName = $command; + $this->flags->popFirstRawArg(); + $this->input->setCommand($command); + $this->logf(Console::VERB_DEBUG, 'found the application command: %s', $command); + + // like: help, version, list + if (!$this->handleGlobalCommand($command)) { + return false; + } + + // continue + return true; } /** @@ -231,24 +282,22 @@ protected function beforeRun(): void */ public function run(bool $exit = true) { - $command = trim($this->input->getCommand(), $this->delimiter); - - $this->logf(Console::VERB_DEBUG, 'begin run the application, command is: %s', $command); - try { + // init + $this->initForRun($this->input); + $this->prepareRun(); - // like: help, version, list - if ($this->filterSpecialCommand($command)) { + // fire event ON_BEFORE_RUN, if it is registered. + $this->fire(ConsoleEvent::ON_BEFORE_RUN, $this); + if (!$this->beforeRun()) { return 0; } - // call 'onBeforeRun' service, if it is registered. - $this->fire(ConsoleEvent::ON_BEFORE_RUN, $this); - $this->beforeRun(); - // do run ... - $result = $this->dispatch($command); + $command = $this->commandName; + $result = $this->dispatch($command, $this->flags->getRawArgs()); + } catch (Throwable $e) { $this->fire(ConsoleEvent::ON_RUN_ERROR, $e, $this); $result = $e->getCode() === 0 ? $e->getLine() : $e->getCode(); @@ -293,36 +342,43 @@ public function stop(int $code = 0) exit($code); } + public function runWithArgs(array $args) + { + $this->input->setArgs($args); + + return $this->run(false); + } + /** - * @param string $command * @param InputInterface $input * @param OutputInterface $output - * - * @return int|mixed */ - public function subRun(string $command, InputInterface $input, OutputInterface $output) + public function runWithIO(InputInterface $input, OutputInterface $output): void { $app = $this->copy(); $app->setInput($input); $app->setOutput($output); - $this->debugf('copy application and run command(%s) with new input, output', $command); - - return $app->dispatch($command); + $this->debugf('copy application and run with new input, output'); + $app->run(false); } /** + * @param string $command * @param InputInterface $input * @param OutputInterface $output + * + * @return int|mixed */ - public function runWithIO(InputInterface $input, OutputInterface $output): void + public function subRun(string $command, InputInterface $input, OutputInterface $output) { $app = $this->copy(); $app->setInput($input); $app->setOutput($output); - $this->debugf('copy application and run with new input, output'); - $app->run(false); + $this->debugf('copy application and run command(%s) with new input, output', $command); + + return $app->dispatch($command); } /** @@ -401,34 +457,15 @@ public function handleException(Throwable $e): void /** * @param string $command * - * @return bool True will stop run, False will goon run give command. + * @return bool False will stop run */ - protected function filterSpecialCommand(string $command): bool + protected function handleGlobalCommand(string $command): bool { - if (!$command) { - if ($this->input->getSameBoolOpt(GlobalOption::VERSION_OPTS)) { - $this->showVersionInfo(); - return true; - } - - if ($this->input->getSameBoolOpt(GlobalOption::HELP_OPTS)) { - $this->showHelpInfo(); - return true; - } - - if ($this->input->getBoolOpt(GlobalOption::ISHELL)) { - $this->startInteractiveShell(); - return true; - } - - // default run list command - // $command = $this->defaultCommand ? 'list'; - $command = 'list'; - // is user command - } elseif (!$this->isInternalCommand($command)) { - return false; + if (!$this->isInternalCommand($command)) { + return true; } + $this->logf(Console::VERB_DEBUG, 'run the global command: %s', $command); switch ($command) { case 'help': $this->showHelpInfo($this->input->getFirstArg()); @@ -439,10 +476,8 @@ protected function filterSpecialCommand(string $command): bool case 'version': $this->showVersionInfo(); break; - default: - return false; } - return true; + return false; } /********************************************************** @@ -454,7 +489,7 @@ protected function filterSpecialCommand(string $command): bool */ protected function startInteractiveShell(): void { - $in = $this->input; + $in = $this->input; $out = $this->output; $out->title("Welcome interactive shell for run application", [ @@ -695,7 +730,7 @@ public function getArrayParam(string $name, array $default = []): array /** * Get config param value * - * @param string $name + * @param string $name * @param null|string|mixed $default * * @return array|string @@ -732,17 +767,22 @@ public function isDebug(int $level = Console::VERB_DEBUG): bool */ public function getVerbLevel(): int { - $key = GlobalOption::DEBUG; + $optKey = GlobalOption::DEBUG; // feat: support set debug level by ENV var: CONSOLE_DEBUG $envVal = OS::getEnvStrVal(Console::DEBUG_ENV_KEY); if ($envVal !== '') { $setVal = (int)$envVal; } else { - $setVal = (int)$this->config[$key]; + $setVal = (int)$this->config[$optKey]; } - return (int)$this->input->getLongOpt($key, $setVal); + if (!$this->flags->hasOpt($optKey)) { + return $setVal; + } + + // return (int)$this->input->getLongOpt($key, $setVal); + return $this->flags->getOpt($optKey, $setVal); } /** @@ -752,10 +792,11 @@ public function getVerbLevel(): int */ public function isProfile(): bool { - $key = GlobalOption::PROFILE; - $def = (bool)$this->getParam($key, false); + $optKey = GlobalOption::PROFILE; + $default = (bool)$this->getParam($optKey, false); - return $this->input->getBoolOpt($key, $def); + // return $this->input->getBoolOpt($key, $def); + return $this->flags->getOpt($optKey, $default); } /** @@ -765,11 +806,11 @@ public function isProfile(): bool */ public function isInteractive(): bool { - $key = GlobalOption::NO_INTERACTIVE; - $def = (bool)$this->getParam($key, true); - $val = $this->input->getBoolOpt($key, $def); + $optKey = GlobalOption::NO_INTERACTIVE; + $default = (bool)$this->getParam($optKey, false); - return $val === false; + // $value = $this->input->getBoolOpt($optKey, $default); + return $this->flags->getOpt($optKey, $default) === false; } /** @@ -787,4 +828,12 @@ public function setErrorHandler(ErrorHandlerInterface $errorHandler): void { $this->errorHandler = $errorHandler; } + + /** + * @return string + */ + public function getCommandName(): string + { + return $this->commandName; + } } diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 574da863..c3f1895f 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -26,6 +26,7 @@ use RuntimeException; use Swoole\Coroutine; use Swoole\Event; +use Toolkit\PFlag\SFlags; use Toolkit\Stdlib\Obj\ConfigObject; use Toolkit\Stdlib\Util\PhpDoc; use function array_diff_key; @@ -117,6 +118,11 @@ abstract class AbstractHandler implements CommandHandlerInterface */ protected $params; + /** + * @var array Command options + */ + protected $commandOptions = []; + /** * Whether enabled * @@ -143,17 +149,15 @@ public static function aliases(): array * * @param Input $input * @param Output $output - * @param InputDefinition|null $definition */ // TODO public function __construct(Input $input = null, Output $output = null, InputDefinition $definition = null) - public function __construct(Input $input, Output $output, InputDefinition $definition = null) + public function __construct(Input $input, Output $output) { $this->input = $input; $this->output = $output; - if ($definition) { - $this->definition = $definition; - } + // init an flags object + $this->flags = new SFlags(); $this->init(); } @@ -167,13 +171,33 @@ protected function init(): void $this->addCommands($this->commands()); } + /** + * command options + * + * **Alone Command** + * + * - set options for command + * + * **Group Controller** + * + * - set options for the group. + * you can set common options for all sub-commands + * + * @return array + */ + protected function options(): array + { + // ['--skip-invalid' => 'Whether ignore invalid arguments and options, when use input definition',] + return []; + } + protected function afterInit(): void { // do something... } /** - * Configure input definition for command, like symfony console. + * Configure for the command/controller. */ protected function configure(): void { @@ -214,7 +238,7 @@ protected function createDefinition(): InputDefinition protected function annotationVars(): array { $fullCmd = $this->input->getFullCommand(); - $binFile = $this->input->getScript(); // bin/app + $binFile = $this->input->getScriptFile(); // bin/app $binName = $this->input->getScriptName(); $command = $this->input->getCommand(); @@ -238,6 +262,54 @@ protected function annotationVars(): array * running a command **************************************************************************/ + protected function initForRun(Input $input, array $args): void + { + $input->setFs($this->flags); + + // set options by options() + $optRules = $this->options(); + // TODO merge static::$globalOptions + + $this->flags->addOptsByRules($optRules); + $this->flags->setHelpRenderer(function () { + $this->showHelp(); + }); + + } + + /** + * @param array $args + * + * @return bool|int|mixed + */ + public function run(array $args) + { + try { + $this->initForRun($this->input, $args); + + $this->logf(Console::VERB_DEBUG, 'init run - begin parse options'); + + // parse options + if (!$this->flags->parse($args)) { + return -1; // on error, help + } + + $args = $this->flags->getRawArgs(); + + return $this->doRun($args); + } catch (\Throwable $e) { + if ($this->isDetached()) { + // TODO exception handle + } else { + // throw $e; + } + + throw $e; + } + + return -1; + } + /** * run command * @@ -245,7 +317,7 @@ protected function annotationVars(): array * * @return int|mixed */ - public function run(array $args) + public function doRun(array $args) { if (isset($args[0])) { $first = $args[0]; @@ -475,7 +547,7 @@ private function checkNotExistsOptions(InputDefinition $def): void // $first = array_shift($names); $first = ''; foreach ($names as $name) { - if (!Application::isGlobalOption($name)) { + if (!GlobalOption::isExists($name)) { $first = $name; break; } @@ -571,7 +643,7 @@ protected function showHelpByDefinition(InputDefinition $definition, array $alia } // align global options - $help['global options:'] = FormatUtil::alignOptions(Application::getGlobalOptions()); + $help['global options:'] = FormatUtil::alignOptions(GlobalOption::getOptions()); $isAlone = $this->isAlone(); if ($isAlone && empty($help[0])) { @@ -677,7 +749,7 @@ protected function showHelpByMethodAnnotations(string $method, string $action = } $help['Group Options:'] = null; - $help['Global Options:'] = FormatUtil::alignOptions(Application::getGlobalOptions()); + $help['Global Options:'] = FormatUtil::alignOptions(GlobalOption::getOptions()); $this->beforeRenderCommandHelp($help); @@ -734,12 +806,22 @@ public static function getDescription(): string return static::$description; } + /** + * @param string $desc + */ + public static function setDesc(string $desc): void + { + self::setDescription($desc); + } + /** * @param string $description */ public static function setDescription(string $description): void { - static::$description = $description; + if ($description) { + static::$description = $description; + } } /** @@ -751,7 +833,7 @@ public static function isCoroutine(): bool } /** - * @param bool $coroutine + * @param bool|mixed $coroutine */ public static function setCoroutine($coroutine): void { diff --git a/src/Application.php b/src/Application.php index b658584d..80d48f8d 100644 --- a/src/Application.php +++ b/src/Application.php @@ -16,6 +16,7 @@ use InvalidArgumentException; use RuntimeException; use SplFileInfo; +use function array_unshift; use function class_exists; use function implode; use function is_object; @@ -256,9 +257,12 @@ protected function getFileFilter(): callable ****************************************************************************/ /** - * @inheritdoc + * @param string $name + * @param array $args + * + * @return int|mixed */ - public function dispatch(string $name, bool $detachedRun = false) + public function dispatch(string $name, array $args = []) { if (!$name = trim($name)) { throw new InvalidArgumentException('cannot dispatch an empty command'); @@ -300,6 +304,7 @@ public function dispatch(string $name, bool $detachedRun = false) // save command ID $cmdOptions = $info['options']; + unset($info['options']); $this->input->setCommandId($info['cmdId']); // is command @@ -308,7 +313,7 @@ public function dispatch(string $name, bool $detachedRun = false) } // is controller/group - return $this->runAction($info, $cmdOptions, $detachedRun); + return $this->runAction($info, $cmdOptions, $args); } /** @@ -355,25 +360,27 @@ protected function runCommand(string $name, $handler, array $options) * Execute an action in a group command(controller) * * @param array $info Matched route info + * @psalm-param array{action: string} $info Matched route info * @param array $options * @param bool $detachedRun * * @return mixed */ - protected function runAction(array $info, array $options, bool $detachedRun = false) + protected function runAction(array $info, array $options, array $args, bool $detachedRun = false) { $controller = $this->createController($info); - - if ($desc = $options['description'] ?? '') { - $controller::setDescription($desc); - } + $controller::setDesc($options['description'] ?? ''); if ($detachedRun) { $controller->setDetached(); } + if ($info['action']) { + array_unshift($args, $info['action']); + } + // Command method, no suffix - return $controller->run([$info['action']]); + return $controller->run($args); } /** diff --git a/src/Anotation/.keep b/src/Attr/.keep similarity index 100% rename from src/Anotation/.keep rename to src/Attr/.keep diff --git a/src/Attr/CmdArgument.php b/src/Attr/CmdArgument.php new file mode 100644 index 00000000..b478cddf --- /dev/null +++ b/src/Attr/CmdArgument.php @@ -0,0 +1,11 @@ +callable) { - throw new \BadMethodCallException('The callable property is empty'); - } - - // call custom callable - return $call($input, $output); - } - - /** - * @param callable $callable - */ - public function setCallable(callable $callable): void - { - $this->callable = $callable; - } -} diff --git a/src/Component/Interact/AbstractSelect.php b/src/Component/Interact/AbstractSelect.php new file mode 100644 index 00000000..b2ec6fe4 --- /dev/null +++ b/src/Component/Interact/AbstractSelect.php @@ -0,0 +1,38 @@ +allowExit; + } + + /** + * @param bool $allowExit + */ + public function setAllowExit(bool $allowExit): void + { + $this->allowExit = $allowExit; + } + +} diff --git a/src/Component/Interact/Checkbox.php b/src/Component/Interact/Checkbox.php index ba2f8df6..94d967db 100644 --- a/src/Component/Interact/Checkbox.php +++ b/src/Component/Interact/Checkbox.php @@ -2,7 +2,6 @@ namespace Inhere\Console\Component\Interact; -use Inhere\Console\Component\InteractiveHandle; use Inhere\Console\Console; use Inhere\Console\Util\Show; use function array_filter; @@ -17,7 +16,7 @@ * * @package Inhere\Console\Component\Interact */ -class Checkbox extends InteractiveHandle +class Checkbox extends MultiSelect { /** * List multiple options and allow multiple selections @@ -40,7 +39,7 @@ public static function select(string $description, $options, $default = null, bo // If default option is error if (null !== $default && !isset($options[$default])) { - Show::error("The default option [{$default}] don't exists.", true); + Show::error("The default option [$default] don't exists.", true); } if ($allowExit) { diff --git a/src/Component/Interact/Choose.php b/src/Component/Interact/Choose.php index 262cbb3e..16e5bb77 100644 --- a/src/Component/Interact/Choose.php +++ b/src/Component/Interact/Choose.php @@ -15,37 +15,8 @@ * * @package Inhere\Console\Component\Interact */ -class Choose extends InteractiveHandle +class Choose extends SingleSelect { - /** - * @var array - */ - protected $data = []; - - /** - * @var bool - */ - protected $allowExit = true; - - /** - * The default selected key - * - * @var string - */ - protected $default; - - /** - * The selected key - * - * @var string - */ - protected $selected; - - /** - * @var string - */ - protected $selectedVal; - /** * Choose one of several options * diff --git a/src/Component/Interact/MultiSelect.php b/src/Component/Interact/MultiSelect.php new file mode 100644 index 00000000..a2c057fa --- /dev/null +++ b/src/Component/Interact/MultiSelect.php @@ -0,0 +1,61 @@ +selected; + } + + /** + * @return string[] + */ + public function getSelectedVals(): array + { + return $this->selectedVals; + } + + /** + * @return string + */ + public function getDefaults(): string + { + return $this->defaults; + } + + /** + * @param string $defaults + */ + public function setDefaults(string $defaults): void + { + $this->defaults = $defaults; + } +} diff --git a/src/Component/Interact/SingleSelect.php b/src/Component/Interact/SingleSelect.php new file mode 100644 index 00000000..1cda8f44 --- /dev/null +++ b/src/Component/Interact/SingleSelect.php @@ -0,0 +1,61 @@ +default; + } + + /** + * @param string $default + */ + public function setDefault(string $default): void + { + $this->default = $default; + } + + /** + * @return string + */ + public function getSelected(): string + { + return $this->selected; + } + + /** + * @return string + */ + public function getSelectedVal(): string + { + return $this->selectedVal; + } + +} diff --git a/src/Component/PharCompiler.php b/src/Component/PharCompiler.php index 68f5ef54..36123104 100644 --- a/src/Component/PharCompiler.php +++ b/src/Component/PharCompiler.php @@ -59,6 +59,8 @@ class PharCompiler public const ON_ERROR = 'error'; + public const ON_BEFORE_PACK = 'before_pack'; + public const ON_MESSAGE = 'message'; public const ON_COLLECTED = 'collected'; @@ -459,6 +461,7 @@ public function pack(string $pharFile, bool $refresh = true): string // $this->excludes = \array_flip($this->excludes); $this->collectInformation(); + $this->fire(self::ON_BEFORE_PACK, $this); $phar = new Phar($pharFile, 0, $pharName); diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index f4fdb21b..5ab232c1 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -130,7 +130,8 @@ public function showHelpInfo(string $command = ''): void $binName = $in->getScriptName(); // built in options - $globalOptions = self::$globalOptions; + // $globalOptions = self::$globalOptions; + $globalOptions = $this->flags->getOptSimpleDefines(); // append generate options: // php examples/app --auto-completion --shell-env zsh --gen-file // php examples/app --auto-completion --shell-env zsh --gen-file stdout @@ -284,7 +285,8 @@ public function showCommandList(): void $scriptName = $this->getScriptName(); // built in options - $globOpts = self::$globalOptions; + // $globOpts = self::$globalOptions; + $globOpts = $this->flags->getOptSimpleDefines(); Show::mList([ 'Usage:' => "$scriptName {COMMAND} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", diff --git a/src/Concern/ControllerHelpTrait.php b/src/Concern/ControllerHelpTrait.php index b766807f..20ab0d01 100644 --- a/src/Concern/ControllerHelpTrait.php +++ b/src/Concern/ControllerHelpTrait.php @@ -2,8 +2,8 @@ namespace Inhere\Console\Concern; -use Inhere\Console\Application; use Inhere\Console\Console; +use Inhere\Console\GlobalOption; use Inhere\Console\Util\FormatUtil; use ReflectionClass; use Toolkit\Cli\ColorTag; @@ -144,17 +144,23 @@ public function showCommandList(): void // if is alone running. if ($detached = $this->isDetached()) { $name = $sName . ' '; - $usage = "$script {command} [--options ...] [arguments ...]"; + $usage = "$script COMMAND [--options ...] [arguments ...]"; } else { - $name = $sName . $this->delimiter; + $name = $sName . $this->delimiter; // $usage = "$script {$name}{command} [--options ...] [arguments ...]"; $usage = [ - "$script $name{command} [--options ...] [arguments ...]", - "$script $sName {command} [--options ...] [arguments ...]", + "$script $nameCOMMAND [--options ...] [arguments ...]", + "$script $sName COMMAND [--options ...] [arguments ...]", ]; } - $globalOptions = array_merge(Application::getGlobalOptions(), static::$globalOptions); + $globalOptions = static::$globalOptions; + if ($app = $this->getApp()) { + $globalOptions = array_merge( + $app->getFlags()->getOptSimpleDefines(), + static::$globalOptions + ); + } $this->output->startBuffer(); $this->output->write(ucfirst($classDes) . PHP_EOL); diff --git a/src/Concern/InputArgumentsTrait.php b/src/Concern/InputArgumentsTrait.php index f2ec7bf0..76f831bd 100644 --- a/src/Concern/InputArgumentsTrait.php +++ b/src/Concern/InputArgumentsTrait.php @@ -115,7 +115,7 @@ public function getArgument($name, $default = null) } /** - * get Argument + * get argument * * @param null|int|string $name * @param mixed $default diff --git a/src/Concern/InputOutputAwareTrait.php b/src/Concern/InputOutputAwareTrait.php index 062772d6..f3247877 100644 --- a/src/Concern/InputOutputAwareTrait.php +++ b/src/Concern/InputOutputAwareTrait.php @@ -12,6 +12,7 @@ use Inhere\Console\Contract\InputInterface; use Inhere\Console\IO\Output; use Inhere\Console\Contract\OutputInterface; +use Toolkit\PFlag\AbstractFlags; use Toolkit\PFlag\SFlags; /** @@ -22,9 +23,9 @@ trait InputOutputAwareTrait { /** - * @var SFlags + * @var SFlags|AbstractFlags */ - // protected $flags; + protected $flags; /** * @var Input|InputInterface @@ -41,7 +42,7 @@ trait InputOutputAwareTrait */ public function getScript(): string { - return $this->input->getScript(); + return $this->input->getScriptFile(); } /** @@ -52,14 +53,6 @@ public function getScriptName(): string return $this->input->getScriptName(); } - /** - * @return string - */ - public function getCommandName(): string - { - return $this->input->getCommand(); - } - /** * @param int|string $name * @param mixed $default @@ -204,4 +197,20 @@ public function setOutput(OutputInterface $output): void { $this->output = $output; } + + /** + * @return AbstractFlags|SFlags + */ + public function getFlags(): AbstractFlags + { + return $this->flags; + } + + /** + * @param AbstractFlags|SFlags $flags + */ + public function setFlags(AbstractFlags $flags): void + { + $this->flags = $flags; + } } diff --git a/src/ConsoleConst.php b/src/ConsoleConst.php new file mode 100644 index 00000000..a8f73856 --- /dev/null +++ b/src/ConsoleConst.php @@ -0,0 +1,17 @@ + AbstractFlags, + * action2 => AbstractFlags2, + * ] + * ``` + * + * @var AbstractFlags[] + * @psalm-var array + */ + private $subFss = []; /** * @var array From disabledCommands() @@ -142,31 +161,20 @@ protected static function commandAliases(): array protected function init(): void { + parent::init(); + self::loadCommandAliases(); $list = $this->disabledCommands(); // save to property $this->disabledCommands = $list ? array_flip($list) : []; - $this->groupOptions = $this->groupOptions(); if (!$this->actionSuffix) { $this->actionSuffix = self::COMMAND_SUFFIX; } } - /** - * Options for the group all commands. - * you can set common options for all sub-commands - * - * @return array - */ - protected function groupOptions(): array - { - // ['--skip-invalid' => 'Whether ignore invalid arguments and options, when use input definition',] - return []; - } - /** * define disabled command list. * @@ -191,25 +199,6 @@ protected function onNotFound(string $action): bool return false; } - /** - * @param string $command - * - * @return string - */ - protected function findCommandName(string $command): string - { - if (!$command = trim($command, $this->delimiter)) { - $command = $this->defaultAction; - - // try use next arg as sub-command name. - if (!$command) { - $command = $this->input->findCommandName(); - } - } - - return $command; - } - protected function beforeRun(): void { } @@ -218,16 +207,27 @@ protected function beforeRun(): void * @param array $args * * @return int|mixed + * @throws \Throwable */ - public function run(array $args) + public function doRun(array $args) { - $command = $args[0]; - $command = $this->findCommandName($command); + $name = self::getName(); + if (!$args) { + $command = $this->defaultAction; + + // and not default command + if (!$command) { + $this->debugf('run args is empty, display help for the group: %s', $name); + return $this->showHelp(); + } + } else { + $first = $args[0]; + if (!FlagHelper::isValidName($first)) { + $this->debugf('not input subcommand, display help for the group: %s', self::getName()); + return $this->showHelp(); + } - // if not input subcommand, render group help. - if (!$command) { - $this->debugf('not input subcommand, display help for the group: %s', self::getName()); - return $this->showHelp(); + $command = $first; } // update subcommand @@ -262,7 +262,7 @@ public function run(array $args) } // do running - return parent::run([$command]); + return parent::doRun([$command]); } /** @@ -278,6 +278,18 @@ protected function configure(): void } } + /** + * @return AbstractFlags + */ + protected function createSubFlags(): AbstractFlags + { + $fs = new SFlags(); + + $this->subFss[$this->action] = $fs; + + return $fs; + } + /** * @return InputDefinition */ @@ -434,15 +446,7 @@ protected function showHelp(): bool */ protected function beforeRenderCommandHelp(array &$help): void { - $help['Group Options:'] = FormatUtil::alignOptions($this->groupOptions); - } - - /** - * @return array - */ - public function getGroupOptions(): array - { - return $this->groupOptions; + $help['Group Options:'] = FormatUtil::alignOptions($this->commandOptions); } /** @@ -681,4 +685,32 @@ public function getCommandMeta(string $key, $default = null, string $command = ' return $this->commandMetas[$action][$key] ?? $default; } + + /** + * @param string $action + * + * @return AbstractFlags|null + */ + public function getActionFlags(string $action): ?AbstractFlags + { + $action = $action ?: $this->action; + + return $this->subFss[$action] ?? null; + } + + /** + * @param string $action + * + * @return AbstractFlags + */ + public function actionFlags(string $action): AbstractFlags + { + $action = $action ?: $this->action; + + if (!isset($this->subFss[$action])) { + throw new ConsoleException("not found flags parser for the action: $action"); + } + + return $this->subFss[$action]; + } } diff --git a/src/GlobalOption.php b/src/GlobalOption.php index 2d2e378e..07cfe246 100644 --- a/src/GlobalOption.php +++ b/src/GlobalOption.php @@ -2,6 +2,8 @@ namespace Inhere\Console; +use function array_merge; + /** * Class GlobalOption * @@ -32,10 +34,84 @@ class GlobalOption 'ishell' => 1, 'profile' => 1, 'no-color' => 1, - 'h' => 1, + // 'h' => 1, 'help' => 1, - 'V' => 1, + // 'V' => 1, 'version' => 1, 'no-interactive' => 1, ]; + + /** + * @var array + * @psalm-var array + */ + private static $options = [ + '--debug' => 'int;Setting the runtime log debug level(quiet 0 - 5 crazy);no;1', + // '--debug' => 'Setting the runtime log debug level(quiet 0 - 5 crazy)', + '--ishell' => 'bool;Run application an interactive shell environment', + // '--ishell' => 'Run application an interactive shell environment', + '--profile' => 'bool;Display timing and memory usage information', + // '--profile' => 'Display timing and memory usage information', + '--no-color' => 'bool;Disable color/ANSI for message output', + // '--no-color' => 'Disable color/ANSI for message output', + '--help' => 'bool;Display this help message;;;h', + // '-h, --help' => 'Display this help message', + '--version' => 'bool;Show application version information;;;V', + // '-V, --version' => 'Show application version information', + '--no-interactive' => 'bool;Run commands in a non-interactive environment', + // '--no-interactive' => 'Run commands in a non-interactive environment', + ]; + + /** + * @var array global options for the group command + */ + protected static $groupOptions = [ + '--show-disabled' => 'string;Whether display disabled commands', + ]; + + /** + * @param string $name + * + * @return bool + */ + public static function isExists(string $name): bool + { + return isset(self::KEY_MAP[$name]); + } + + /** + * @param array $options + */ + public function setOptions(array $options): void + { + if ($options) { + self::$options = $options; + } + } + + /** + * @param array $options + */ + public function addOptions(array $options): void + { + if ($options) { + self::$options = array_merge(self::$options, $options); + } + } + + /** + * @return array + */ + public static function getOptions(): array + { + return self::$options; + } + + /** + * @return array + */ + public static function getGroupOptions(): array + { + return self::$groupOptions; + } } diff --git a/src/Handler/CallableCommand.php b/src/Handler/CallableCommand.php index 490d52fd..54067a9d 100644 --- a/src/Handler/CallableCommand.php +++ b/src/Handler/CallableCommand.php @@ -19,6 +19,15 @@ class CallableCommand extends Command */ private $callable; + // public function new(callable $callable): self + // { + // } + + // public function __construct(Input $input, Output $output, InputDefinition $definition = null) + // { + // parent::__construct($input, $output, $definition); + // } + // /** // * @param callable $cb // * diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index 18e15c74..fecc0915 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -47,12 +47,12 @@ abstract class AbstractInput implements InputInterface protected $pwd = ''; /** - * The bin script path + * The bin script file * e.g `./bin/app` OR `bin/cli.php` * * @var string */ - protected $script = ''; + protected $scriptFile = ''; /** * The bin script name @@ -198,9 +198,9 @@ public function setPwd(string $pwd): void /** * @return string */ - public function getScript(): string + public function getScriptFile(): string { - return $this->script; + return $this->scriptFile; } /** @@ -208,18 +208,18 @@ public function getScript(): string */ public function getScriptPath(): string { - return $this->script; + return $this->scriptFile; } /** - * @param string $script + * @param string $scriptFile */ - public function setScript(string $script): void + public function setScriptFile(string $scriptFile): void { - if ($script) { - $this->script = $script; + if ($scriptFile) { + $this->scriptFile = $scriptFile; // update scriptName - $this->scriptName = basename($script); + $this->scriptName = basename($scriptFile); } } diff --git a/src/IO/Input.php b/src/IO/Input.php index d4b6f813..19b88620 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -15,12 +15,10 @@ use function array_map; use function array_shift; use function basename; -use function fgets; use function fwrite; use function implode; use function is_string; use function preg_match; -use function trim; /** * Class Input - The input information. by parse global var $argv. @@ -78,10 +76,10 @@ protected function collectInfo(array $rawFlags): void // first is bin file if (isset($rawFlags[0]) && is_string($rawFlags[0])) { - $this->script = array_shift($rawFlags); + $this->scriptFile = array_shift($rawFlags); // bin name - $this->scriptName = basename($this->script); + $this->scriptName = basename($this->scriptFile); } $this->flags = $rawFlags; // no script @@ -182,7 +180,7 @@ public function getBinWithCommand(): string */ public function getFullCommand(): string { - return $this->script . ' ' . $this->getCommandPath(); + return $this->scriptFile . ' ' . $this->getCommandPath(); } /** diff --git a/src/IO/Output.php b/src/IO/Output.php index 1290d0bf..a03d1496 100644 --- a/src/IO/Output.php +++ b/src/IO/Output.php @@ -137,7 +137,7 @@ public function read(string $question = '', bool $nl = false): string * * @return int */ - public function stderr(string $text = '', $nl = true): int + public function stderr(string $text = '', bool $nl = true): int { return Console::write($text, $nl, [ 'steam' => $this->errorStream, diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index 0218f40d..bb8e50d1 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -139,6 +139,10 @@ public static function wrapText($text, $indent = 0, $width = 0): string */ public static function alignOptions(array $options): array { + if (!$options) { + return []; + } + // e.g '-h, --help' $hasShort = (bool)strpos(implode('', array_keys($options)), ','); diff --git a/src/Util/Helper.php b/src/Util/Helper.php index 66677573..8c4f87d7 100644 --- a/src/Util/Helper.php +++ b/src/Util/Helper.php @@ -12,6 +12,7 @@ use FilesystemIterator; use Inhere\Console\Concern\RuntimeProfileTrait; +use Inhere\Console\ConsoleConst; use InvalidArgumentException; use Iterator; use RecursiveCallbackFilterIterator; @@ -23,12 +24,11 @@ use function class_exists; use function file_exists; use function is_dir; -use function is_numeric; -use function mb_strlen; use function mkdir; use function preg_match; use function similar_text; use function sprintf; +use function strlen; use function strpos; /** @@ -81,21 +81,34 @@ public static function isAbsPath(string $path): bool return strpos($path, '/') === 0 || 1 === preg_match('#^[a-z]:[\/|\\\]{1}.+#i', $path); } + /** + * @param string $name + */ + public static function checkCmdPath(string $name): void + { + if (!self::isValidCmdPath($name)) { + throw new InvalidArgumentException("The command name '$name' is invalid"); + } + } + /** * @param string $name * * @return bool */ - public static function validName(string $name): bool + public static function isValidCmdPath(string $name): bool { - // '/^[a-z][\w-]*:?([a-z][\w-]+)?$/' - $pattern = '/^[a-z][\w:-]+$/'; - - if (1 !== preg_match($pattern, $name)) { - throw new InvalidArgumentException("The command name '$name' is must match: $pattern"); - } + return strlen($name) < ConsoleConst::CMD_PATH_MAX_LEN && preg_match(ConsoleConst::REGEX_CMD_PATH, $name) === 1; + } - return true; + /** + * @param string $name + * + * @return bool + */ + public static function isValidCmdName(string $name): bool + { + return strlen($name) < ConsoleConst::CMD_NAME_MAX_LEN && preg_match(ConsoleConst::REGEX_CMD_NAME, $name) === 1; } /** diff --git a/test/IO/InputTest.php b/test/IO/InputTest.php index 819a1f5f..61d5a05d 100644 --- a/test/IO/InputTest.php +++ b/test/IO/InputTest.php @@ -17,7 +17,7 @@ public function testBasic(): void { $in = new Input(['./bin/app', 'cmd', 'val0', 'val1']); - $this->assertSame('./bin/app', $in->getScript()); + $this->assertSame('./bin/app', $in->getScriptFile()); $this->assertSame('app', $in->getScriptName()); $this->assertSame('cmd', $in->getCommand()); } From ce757606001eab27e846a8f464ac34e6aa6a084f Mon Sep 17 00:00:00 2001 From: inhere Date: Mon, 13 Sep 2021 00:51:23 +0800 Subject: [PATCH 144/258] feat: update some logic for run group action --- composer.json | 1 + examples/Controller/HomeController.php | 25 ++- resource/deprecated/AbstractHandlerOld.php | 170 ++++++++++++++++++ src/AbstractHandler.php | 193 ++++++--------------- src/Application.php | 6 +- src/Attr/CmdArgument.php | 17 +- src/Attr/CmdOption.php | 19 +- src/Attr/RuleArg.php | 24 +++ src/Attr/RuleOpt.php | 24 +++ src/BuiltIn/DevServerCommand.php | 2 + src/BuiltIn/SelfUpdateCommand.php | 74 ++++---- src/Command.php | 2 +- src/Concern/ControllerHelpTrait.php | 2 +- src/Controller.php | 81 +++++---- src/GlobalOption.php | 52 +++++- src/IO/AbstractInput.php | 6 + 16 files changed, 469 insertions(+), 229 deletions(-) create mode 100644 resource/deprecated/AbstractHandlerOld.php create mode 100644 src/Attr/RuleArg.php create mode 100644 src/Attr/RuleOpt.php diff --git a/composer.json b/composer.json index b3cb2177..bdefeedf 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ ], "require": { "php": ">7.2.0", + "symfony/polyfill-php80": "~1.0", "toolkit/cli-utils": "~1.0", "toolkit/pflag": "~1.0", "toolkit/stdlib": "~1.0", diff --git a/examples/Controller/HomeController.php b/examples/Controller/HomeController.php index cbacbf59..4770411a 100644 --- a/examples/Controller/HomeController.php +++ b/examples/Controller/HomeController.php @@ -114,17 +114,28 @@ public function disabledCommand(): void */ protected function defArgConfigure(): void { - $this->createDefinition() - // ->setDescription('the command args and opts config use defined configure, it like symfony console, please see defArgConfigure()') - ->addArgument('name', Input::ARG_REQUIRED, "description for the argument 'name'") - ->addOption('yes', 'y', Input::OPT_BOOLEAN, "description for the option 'yes'") - ->addOption('opt1', null, Input::OPT_REQUIRED, "description for the option 'opt1'"); - } + $fs = $this->newActionFlags(); + $fs->addOptByRule('yes,y', "bool;description for the option 'yes'"); + $fs->addOptByRule('opt1', "bool;description for the option 'opt1'"); + + $fs->addArgByRule('name', "string;description for the argument 'name';true"); + } // desc set at $this->commandMetas. public function defArgCommand(): void { - $this->output->dump($this->input->getArgs(), $this->input->getOpts(), $this->input->getBoolOpt('y')); + $this->output->dump( + $this->input->getArgs(), + $this->input->getOpts(), + $this->input->getBoolOpt('y') + ); + + $fs = $this->curActionFlags(); + $this->output->dump( + $fs->getArgs(), + $fs->getOpts(), + $fs->getOpt('yes') + ); } /** diff --git a/resource/deprecated/AbstractHandlerOld.php b/resource/deprecated/AbstractHandlerOld.php new file mode 100644 index 00000000..3b0a6ee2 --- /dev/null +++ b/resource/deprecated/AbstractHandlerOld.php @@ -0,0 +1,170 @@ +definition) { + $this->definition = new InputDefinition(); + $this->definition->setDescription(self::getDescription()); + } + + return $this->definition; + } + + /** + * @return InputDefinition + */ + protected function createDefinition2(): InputDefinition + { + if (!$this->definition) { + $this->definition = new InputDefinition(); + + // if have been set desc for the sub-command + $cmdDesc = $this->commandMetas[$this->action]['desc'] ?? ''; + if ($cmdDesc) { + $this->definition->setDescription($cmdDesc); + } + } + + return $this->definition; + } + + /** + * validate input arguments and options + * + * @return bool + * @throws InvalidArgumentException + */ + public function validateInput(): bool + { + if (!$def = $this->definition) { + return true; + } + + $this->logf(Console::VERB_DEBUG, 'validate the input arguments and options by Definition'); + + $in = $this->input; + $out = $this->output; + + $givenArgs = $errArgs = []; + foreach ($in->getArgs() as $key => $value) { + if (is_int($key)) { + $givenArgs[$key] = $value; + } else { + $errArgs[] = $key; + } + } + + if (count($errArgs) > 0) { + $out->liteError(sprintf('Unknown arguments (error: "%s").', implode(', ', $errArgs))); + return false; + } + + $defArgs = $def->getArguments(); + $missingArgs = array_filter(array_keys($defArgs), static function ($name, $key) use ($def, $givenArgs) { + return !array_key_exists($key, $givenArgs) && $def->argumentIsRequired($name); + }, ARRAY_FILTER_USE_BOTH); + + if (count($missingArgs) > 0) { + $out->liteError(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArgs))); + return false; + } + + $index = 0; + $args = []; + + foreach ($defArgs as $name => $conf) { + $args[$name] = $givenArgs[$index] ?? $conf['default']; + $index++; + } + + $in->setArgs($args); + $this->checkNotExistsOptions($def); + + // check options + $opts = $missingOpts = []; + + $defOpts = $def->getOptions(); + foreach ($defOpts as $name => $conf) { + if (!$in->hasLOpt($name)) { + // support multi short: 'a|b|c' + $shortNames = $conf['shortcut'] ? explode('|', $conf['shortcut']) : []; + if ($srt = $in->findOneShortOpts($shortNames)) { + $opts[$name] = $in->sOpt($srt); + } elseif ($conf['default'] !== null) { + $opts[$name] = $conf['default']; + } elseif ($conf['required']) { + $missingOpts[] = "--{$name}" . ($srt ? "|-{$srt}" : ''); + } + } + } + + if (count($missingOpts) > 0) { + $out->liteError(sprintf('Not enough options parameters (missing: "%s").', implode(', ', $missingOpts))); + return false; + } + + if ($opts) { + $in->setLOpts($opts); + } + + return true; + } + + private function checkNotExistsOptions(InputDefinition $def): void + { + $givenOpts = $this->input->getOptions(); + $allDefOpts = $def->getAllOptionNames(); + + // check unknown options + if ($unknown = array_diff_key($givenOpts, $allDefOpts)) { + $names = array_keys($unknown); + + // $first = array_shift($names); + $first = ''; + foreach ($names as $name) { + if (!GlobalOption::isExists($name)) { + $first = $name; + break; + } + } + + if (!$first) { + return; + } + + $errMsg = sprintf('Input option is not exists (unknown: "%s").', (isset($first[1]) ? '--' : '-') . $first); + throw new InvalidArgumentException($errMsg); + } + } + +} diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index c3f1895f..8d0b3697 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -10,14 +10,14 @@ use Inhere\Console\Concern\AttachApplicationTrait; use Inhere\Console\Concern\CommandHelpTrait; +use Inhere\Console\Concern\InputOutputAwareTrait; use Inhere\Console\Concern\SubCommandsWareTrait; +use Inhere\Console\Concern\UserInteractAwareTrait; use Inhere\Console\Contract\CommandHandlerInterface; use Inhere\Console\Contract\CommandInterface; use Inhere\Console\IO\Input; use Inhere\Console\IO\InputDefinition; use Inhere\Console\IO\Output; -use Inhere\Console\Concern\InputOutputAwareTrait; -use Inhere\Console\Concern\UserInteractAwareTrait; use Inhere\Console\Util\FormatUtil; use Inhere\Console\Util\Helper; use InvalidArgumentException; @@ -26,27 +26,21 @@ use RuntimeException; use Swoole\Coroutine; use Swoole\Event; +use Throwable; use Toolkit\PFlag\SFlags; use Toolkit\Stdlib\Obj\ConfigObject; use Toolkit\Stdlib\Util\PhpDoc; -use function array_diff_key; -use function array_filter; -use function array_key_exists; use function array_keys; use function array_merge; use function cli_set_process_title; -use function count; use function error_get_last; -use function explode; use function function_exists; use function implode; use function is_array; -use function is_int; use function is_string; use function preg_replace; use function sprintf; use function ucfirst; -use const ARRAY_FILTER_USE_BOTH; use const PHP_EOL; use const PHP_OS; @@ -58,7 +52,7 @@ abstract class AbstractHandler implements CommandHandlerInterface { use AttachApplicationTrait; - Use CommandHelpTrait; + use CommandHelpTrait; use InputOutputAwareTrait; use UserInteractAwareTrait; use SubCommandsWareTrait; @@ -103,6 +97,13 @@ abstract class AbstractHandler implements CommandHandlerInterface */ private $initialized = false; + /** + * Compatible mode run command. + * + * @var bool + */ + private $compatible = true; + /** * @var InputDefinition|null */ @@ -147,8 +148,8 @@ public static function aliases(): array /** * Command constructor. * - * @param Input $input - * @param Output $output + * @param Input $input + * @param Output $output */ // TODO public function __construct(Input $input = null, Output $output = null, InputDefinition $definition = null) public function __construct(Input $input, Output $output) @@ -171,6 +172,11 @@ protected function init(): void $this->addCommands($this->commands()); } + protected function afterInit(): void + { + // do something... + } + /** * command options * @@ -191,11 +197,6 @@ protected function options(): array return []; } - protected function afterInit(): void - { - // do something... - } - /** * Configure for the command/controller. */ @@ -262,30 +263,46 @@ protected function annotationVars(): array * running a command **************************************************************************/ - protected function initForRun(Input $input, array $args): void + protected function initForRun(Input $input): void { $input->setFs($this->flags); + $this->flags->setScriptName(self::getName()); + $this->flags->setDesc(self::getDescription()); + + // load built in options + // $builtInOpts = GlobalOption::getAloneOptions(); + $builtInOpts = $this->getBuiltInOptions(); + $this->flags->addOptsByRules($builtInOpts); // set options by options() $optRules = $this->options(); - // TODO merge static::$globalOptions - $this->flags->addOptsByRules($optRules); $this->flags->setHelpRenderer(function () { + $this->logf(Console::VERB_DEBUG, 'show help message by input flags: -h, --help'); $this->showHelp(); }); + // old mode: options and arguments at method annotations + if ($this->compatible) { + $this->flags->setSkipOnUndefined(true); + } + } + + protected function getBuiltInOptions(): array + { + return GlobalOption::getAloneOptions(); } /** * @param array $args * * @return bool|int|mixed + * @throws Throwable */ public function run(array $args) { try { - $this->initForRun($this->input, $args); + $this->initForRun($this->input); $this->logf(Console::VERB_DEBUG, 'init run - begin parse options'); @@ -297,7 +314,7 @@ public function run(array $args) $args = $this->flags->getRawArgs(); return $this->doRun($args); - } catch (\Throwable $e) { + } catch (Throwable $e) { if ($this->isDetached()) { // TODO exception handle } else { @@ -326,18 +343,17 @@ public function doRun(array $args) if ($this->isSubCommand($rName)) { } - } - $this->debugf('begin run command. load input definition configure'); + $this->debugf('begin run command. load configure for command'); // load input definition configure $this->configure(); // if with option: -h|--help - if ($this->input->getSameBoolOpt(['h', 'help'])) { - $this->showHelp(); - return 0; - } + // if ($this->input->getSameBoolOpt(['h', 'help'])) { + // $this->showHelp(); + // return 0; + // } // some prepare check // - validate input arguments @@ -450,118 +466,10 @@ protected function prepare(): bool } } - return $this->validateInput(); - } - - /** - * validate input arguments and options - * - * @return bool - * @throws InvalidArgumentException - */ - public function validateInput(): bool - { - if (!$def = $this->definition) { - return true; - } - - $this->logf(Console::VERB_DEBUG, 'validate the input arguments and options by Definition'); - - $in = $this->input; - $out = $this->output; - - $givenArgs = $errArgs = []; - foreach ($in->getArgs() as $key => $value) { - if (is_int($key)) { - $givenArgs[$key] = $value; - } else { - $errArgs[] = $key; - } - } - - if (count($errArgs) > 0) { - $out->liteError(sprintf('Unknown arguments (error: "%s").', implode(', ', $errArgs))); - return false; - } - - $defArgs = $def->getArguments(); - $missingArgs = array_filter(array_keys($defArgs), static function ($name, $key) use ($def, $givenArgs) { - return !array_key_exists($key, $givenArgs) && $def->argumentIsRequired($name); - }, ARRAY_FILTER_USE_BOTH); - - if (count($missingArgs) > 0) { - $out->liteError(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArgs))); - return false; - } - - $index = 0; - $args = []; - - foreach ($defArgs as $name => $conf) { - $args[$name] = $givenArgs[$index] ?? $conf['default']; - $index++; - } - - $in->setArgs($args); - $this->checkNotExistsOptions($def); - - // check options - $opts = $missingOpts = []; - - $defOpts = $def->getOptions(); - foreach ($defOpts as $name => $conf) { - if (!$in->hasLOpt($name)) { - // support multi short: 'a|b|c' - $shortNames = $conf['shortcut'] ? explode('|', $conf['shortcut']) : []; - if ($srt = $in->findOneShortOpts($shortNames)) { - $opts[$name] = $in->sOpt($srt); - } elseif ($conf['default'] !== null) { - $opts[$name] = $conf['default']; - } elseif ($conf['required']) { - $missingOpts[] = "--{$name}" . ($srt ? "|-{$srt}" : ''); - } - } - } - - if (count($missingOpts) > 0) { - $out->liteError(sprintf('Not enough options parameters (missing: "%s").', implode(', ', $missingOpts))); - return false; - } - - if ($opts) { - $in->setLOpts($opts); - } - + // return $this->validateInput(); return true; } - private function checkNotExistsOptions(InputDefinition $def): void - { - $givenOpts = $this->input->getOptions(); - $allDefOpts = $def->getAllOptionNames(); - - // check unknown options - if ($unknown = array_diff_key($givenOpts, $allDefOpts)) { - $names = array_keys($unknown); - - // $first = array_shift($names); - $first = ''; - foreach ($names as $name) { - if (!GlobalOption::isExists($name)) { - $first = $name; - break; - } - } - - if (!$first) { - return; - } - - $errMsg = sprintf('Input option is not exists (unknown: "%s").', (isset($first[1]) ? '--' : '-') . $first); - throw new InvalidArgumentException($errMsg); - } - } - /************************************************************************** * helper methods **************************************************************************/ @@ -674,7 +582,7 @@ protected function showHelpByDefinition(InputDefinition $definition, array $alia * * @return int */ - protected function showHelpByMethodAnnotations(string $method, string $action = '', array $aliases = []): int + protected function showHelpByAnnotations(string $method, string $action = '', array $aliases = []): int { $ref = new ReflectionClass($this); $name = $this->input->getCommand(); @@ -708,7 +616,7 @@ protected function showHelpByMethodAnnotations(string $method, string $action = $path = $binName . ' ' . $name; if ($action) { $group = static::getName(); - $path = "$binName $group $action"; + $path = "$binName $group $action"; } // is an command object @@ -748,11 +656,14 @@ protected function showHelpByMethodAnnotations(string $method, string $action = unset($help['Description:']); } - $help['Group Options:'] = null; - $help['Global Options:'] = FormatUtil::alignOptions(GlobalOption::getOptions()); + $help['Group Options:'] = null; $this->beforeRenderCommandHelp($help); + if ($app = $this->getApp()) { + $help['Global Options:'] = FormatUtil::alignOptions($app->getFlags()->getOptSimpleDefines()); + } + $this->output->mList($help, [ 'sepChar' => ' ', 'lastNewline' => 0, diff --git a/src/Application.php b/src/Application.php index 80d48f8d..0b4c0aef 100644 --- a/src/Application.php +++ b/src/Application.php @@ -309,7 +309,7 @@ public function dispatch(string $name, array $args = []) // is command if ($info['type'] === Router::TYPE_SINGLE) { - return $this->runCommand($info['name'], $info['handler'], $cmdOptions); + return $this->runCommand($info['name'], $info['handler'], $cmdOptions, $args); } // is controller/group @@ -326,7 +326,7 @@ public function dispatch(string $name, array $args = []) * @return mixed * @throws InvalidArgumentException */ - protected function runCommand(string $name, $handler, array $options) + protected function runCommand(string $name, $handler, array $options, array $args) { if (is_object($handler) && method_exists($handler, '__invoke')) { if ($this->input->getSameOpt(['h', 'help'])) { @@ -350,7 +350,7 @@ protected function runCommand(string $name, $handler, array $options) $object::setName($name); $object->setApp($this); - $result = $object->run([]); + $result = $object->run($args); } return $result; diff --git a/src/Attr/CmdArgument.php b/src/Attr/CmdArgument.php index b478cddf..7d83d206 100644 --- a/src/Attr/CmdArgument.php +++ b/src/Attr/CmdArgument.php @@ -2,10 +2,25 @@ namespace Inhere\Console\Attr; +use Attribute; +use Toolkit\PFlag\Flag\Argument; + /** * class CmdArgument */ -class CmdArgument +#[Attribute(Attribute::TARGET_CLASS|Attribute::TARGET_METHOD)] +class CmdArgument extends Argument { + public function __construct( + string $name, + string $desc = '', + string $type = 'string', + bool $required = false, + $default = null, + string $envVar = '' + ) { + parent::__construct($name, $desc, $type, $required, $default); + $this->setEnvVar($envVar); + } } diff --git a/src/Attr/CmdOption.php b/src/Attr/CmdOption.php index 0ab3451e..d57efea9 100644 --- a/src/Attr/CmdOption.php +++ b/src/Attr/CmdOption.php @@ -2,10 +2,27 @@ namespace Inhere\Console\Attr; +use Attribute; +use Toolkit\PFlag\Flag\Option; + /** * class CmdOption */ -class CmdOption +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +class CmdOption extends Option { + public function __construct( + string $name, + string $desc = '', + string $type = 'string', + bool $required = false, + $default = null, + string $shortcut = '', + string $envVar = '' + ) { + parent::__construct($name, $desc, $type, $required, $default); + $this->setEnvVar($envVar); + $this->setShortcut($shortcut); + } } diff --git a/src/Attr/RuleArg.php b/src/Attr/RuleArg.php new file mode 100644 index 00000000..4752530d --- /dev/null +++ b/src/Attr/RuleArg.php @@ -0,0 +1,24 @@ +getSameStringOpt('s,S,addr'); diff --git a/src/BuiltIn/SelfUpdateCommand.php b/src/BuiltIn/SelfUpdateCommand.php index 2ea2eb3a..5191186a 100644 --- a/src/BuiltIn/SelfUpdateCommand.php +++ b/src/BuiltIn/SelfUpdateCommand.php @@ -16,6 +16,7 @@ use Inhere\Console\Command; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; +use Toolkit\PFlag\FlagType; use function strlen; /** @@ -270,7 +271,7 @@ protected function printVersion(Updater $updater): void if ($updater->getStrategy() instanceof ShaStrategy) { $stability = 'development'; } elseif ($updater->getStrategy() instanceof GithubStrategy && $updater->getStrategy() - ->getStability() === GithubStrategy::UNSTABLE + ->getStability() === GithubStrategy::UNSTABLE ) { $stability = 'pre-release'; } @@ -294,39 +295,42 @@ protected function printVersion(Updater $updater): void protected function configure(): void { - $this->createDefinition() - // ->setName('self-update') - // ->setDescription(self::$description) - ->addOption( - 'dev', - 'd', - Input::OPT_BOOLEAN, - 'Update to most recent development build of package.' - ) - ->addOption( - 'non-dev', - 'N', - Input::OPT_BOOLEAN, - 'Update to most recent non-development (alpha/beta/stable) build of package tagged on Github.' - ) - ->addOption( - 'pre', - 'p', - Input::OPT_BOOLEAN, - 'Update to most recent pre-release version of package (alpha/beta/rc) tagged on Github.' - ) - ->addOption('stable', 's', Input::OPT_BOOLEAN, 'Update to most recent stable version tagged on Github.') - ->addOption( - 'rollback', - 'r', - Input::OPT_BOOLEAN, - 'Rollback to previous version of package if available on filesystem.' - ) - ->addOption( - 'check', - 'c', - Input::OPT_BOOLEAN, - 'Checks what updates are available across all possible stability tracks.' - ); + $this->flags + ->addOpt( + 'dev', + 'd', + 'Update to most recent development build of package.', + FlagType::BOOL + ) + ->addOpt( + 'non-dev', + 'N', + 'Update to most recent non-development (alpha/beta/stable) build of package tagged on Github.', + FlagType::BOOL + ) + ->addOpt( + 'pre', + 'p', + 'Update to most recent pre-release version of package (alpha/beta/rc) tagged on Github.', + FlagType::BOOL + ) + ->addOpt( + 'stable', + 's', + 'Update to most recent stable version tagged on Github.', + FlagType::BOOL + ) + ->addOpt( + 'rollback', + 'r', + 'Rollback to previous version of package if available on filesystem.', + FlagType::BOOL + ) + ->addOpt( + 'check', + 'c', + 'Checks what updates are available across all possible stability tracks.', + FlagType::BOOL + ); } } diff --git a/src/Command.php b/src/Command.php index 8b9aeb59..af08d2b7 100644 --- a/src/Command.php +++ b/src/Command.php @@ -100,6 +100,6 @@ protected function showHelp(): bool $this->logf(Console::VERB_CRAZY, "display help info for the command: %s", self::getName()); - return $this->showHelpByMethodAnnotations($execMethod, '', $aliases) !== 0; + return $this->showHelpByAnnotations($execMethod, '', $aliases) !== 0; } } diff --git a/src/Concern/ControllerHelpTrait.php b/src/Concern/ControllerHelpTrait.php index 20ab0d01..1e5aac61 100644 --- a/src/Concern/ControllerHelpTrait.php +++ b/src/Concern/ControllerHelpTrait.php @@ -70,7 +70,7 @@ public function helpCommand(): int ]); // For a specified sub-command. - return $this->showHelpByMethodAnnotations($method, $action, $aliases); + return $this->showHelpByAnnotations($method, $action, $aliases); } protected function beforeShowCommandList(): void diff --git a/src/Controller.php b/src/Controller.php index 743c3342..896c8bdf 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -13,7 +13,6 @@ use Inhere\Console\Contract\ControllerInterface; use Inhere\Console\Exception\ConsoleException; use Inhere\Console\IO\Input; -use Inhere\Console\IO\InputDefinition; use Inhere\Console\IO\Output; use Inhere\Console\Util\FormatUtil; use Inhere\Console\Util\Helper; @@ -65,34 +64,37 @@ abstract class Controller extends AbstractHandler implements ControllerInterface ]; /** - * Action name, no suffix. + * Action name - real subcommand name, no suffix 'Command'. + * * eg: updateCommand() -> action: 'update' * * @var string */ - private $action; + private $action = ''; /** - * eg: '/' ':' + * Input subcommand name. * * @var string */ - private $delimiter = ':'; + private $commandName = ''; /** + * eg: '/' ':' + * * @var string */ - private $defaultAction = ''; + private $delimiter = ':'; /** * @var string */ - private $actionSuffix = self::COMMAND_SUFFIX; + private $defaultAction = ''; /** * @var string */ - private $commandName = ''; + private $actionSuffix = self::COMMAND_SUFFIX; /** * Flags for all action commands @@ -199,6 +201,11 @@ protected function onNotFound(string $action): bool return false; } + protected function getBuiltInOptions(): array + { + return GlobalOption::getGroupOptions(); + } + protected function beforeRun(): void { } @@ -228,9 +235,11 @@ public function doRun(array $args) } $command = $first; + $this->input->popFirstArg(); } // update subcommand + $this->commandName = $command; $this->input->setSubCommand($command); // update some comment vars @@ -262,6 +271,7 @@ public function doRun(array $args) } // do running + $this->newActionFlags(); return parent::doRun([$command]); } @@ -281,31 +291,15 @@ protected function configure(): void /** * @return AbstractFlags */ - protected function createSubFlags(): AbstractFlags + protected function newActionFlags(): AbstractFlags { - $fs = new SFlags(); - - $this->subFss[$this->action] = $fs; - - return $fs; - } - - /** - * @return InputDefinition - */ - protected function createDefinition(): InputDefinition - { - if (!$this->definition) { - $this->definition = new InputDefinition(); - - // if have been set desc for the sub-command - $cmdDesc = $this->commandMetas[$this->action]['desc'] ?? ''; - if ($cmdDesc) { - $this->definition->setDescription($cmdDesc); - } + if (!$fs = $this->getActionFlags($this->action)) { + $fs = new SFlags(); + // save + $this->subFss[$this->action] = $fs; } - return $this->definition; + return $fs; } /** @@ -366,7 +360,8 @@ final public function execute($input, $output) } // run action - $result = $this->$method($input, $output); + $flags = $this->actionFlags($action); + $result = $this->$method($input, $output, $flags); // after run action if (method_exists($this, $after = 'after' . ucfirst($action))) { @@ -446,7 +441,7 @@ protected function showHelp(): bool */ protected function beforeRenderCommandHelp(array &$help): void { - $help['Group Options:'] = FormatUtil::alignOptions($this->commandOptions); + $help['Group Options:'] = FormatUtil::alignOptions($this->flags->getOptSimpleDefines()); } /** @@ -574,6 +569,14 @@ public function getAction(): string return $this->action; } + /** + * @return string + */ + public function getCommandName(): string + { + return $this->commandName; + } + /** * @param string $action * @@ -597,11 +600,11 @@ public function getDefaultAction(): string } /** - * @param string $defaultAction + * @param string $action */ - public function setDefaultAction(string $defaultAction): void + public function setDefaultAction(string $action): void { - $this->defaultAction = trim($defaultAction, $this->delimiter); + $this->defaultAction = trim($action, $this->delimiter); } /** @@ -698,6 +701,14 @@ public function getActionFlags(string $action): ?AbstractFlags return $this->subFss[$action] ?? null; } + /** + * @return AbstractFlags + */ + public function curActionFlags(): AbstractFlags + { + return $this->actionFlags($this->action); + } + /** * @param string $action * diff --git a/src/GlobalOption.php b/src/GlobalOption.php index 07cfe246..da63e226 100644 --- a/src/GlobalOption.php +++ b/src/GlobalOption.php @@ -54,21 +54,37 @@ class GlobalOption // '--profile' => 'Display timing and memory usage information', '--no-color' => 'bool;Disable color/ANSI for message output', // '--no-color' => 'Disable color/ANSI for message output', - '--help' => 'bool;Display this help message;;;h', + '--help' => 'bool;Display this help message;;;h', // '-h, --help' => 'Display this help message', - '--version' => 'bool;Show application version information;;;V', + '--version' => 'bool;Show application version information;;;V', // '-V, --version' => 'Show application version information', '--no-interactive' => 'bool;Run commands in a non-interactive environment', // '--no-interactive' => 'Run commands in a non-interactive environment', ]; /** - * @var array global options for the group command + * @var array built-in options for the alone command + */ + protected static $aloneOptions = [ + // '--help' => 'bool;Display this help message;;;h', + // '--show-disabled' => 'string;Whether display disabled commands', + ]; + + /** + * @var array built-in options for the group command */ protected static $groupOptions = [ + // '--help' => 'bool;Display this help message;;;h', '--show-disabled' => 'string;Whether display disabled commands', ]; + /** + * @var array common options for the group/command + */ + protected static $commonOptions = [ + '--help' => 'bool;Display this help message;;;h', + ]; + /** * @param string $name * @@ -110,8 +126,36 @@ public static function getOptions(): array /** * @return array */ - public static function getGroupOptions(): array + public static function getCommonOptions(): array + { + return self::$commonOptions; + } + + /** + * @param bool $withCommon + * + * @return array + */ + public static function getAloneOptions(bool $withCommon = true): array { + if ($withCommon) { + return array_merge(self::$commonOptions, self::$aloneOptions); + } + + return self::$aloneOptions; + } + + /** + * @param bool $withCommon + * + * @return array + */ + public static function getGroupOptions(bool $withCommon = true): array + { + if ($withCommon) { + return array_merge(self::$commonOptions, self::$groupOptions); + } + return self::$groupOptions; } } diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index fecc0915..9b2ce7e4 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -13,6 +13,7 @@ use Inhere\Console\Contract\InputInterface; use Toolkit\PFlag\AbstractFlags; use Toolkit\PFlag\SFlags; +use function array_shift; use function basename; use function getcwd; use function is_int; @@ -142,6 +143,11 @@ public function findCommandName(): string return $command; } + public function popFirstArg() + { + return array_shift($this->args); + } + /** * @return string */ From 58b05677ea8703e62a018abdf76fa4224480d12b Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 14 Sep 2021 20:21:09 +0800 Subject: [PATCH 145/258] update some logic for config subcommand flags --- examples/Command/DemoCommand.php | 15 +-- src/AbstractHandler.php | 68 ++++------ src/Application.php | 7 +- src/Attr/RuleArg.php | 4 +- src/Attr/RuleOpt.php | 4 +- src/Command.php | 20 ++- src/Concern/CommandHelpTrait.php | 2 +- src/Concern/InputOutputAwareTrait.php | 12 +- src/Concern/SubCommandsWareTrait.php | 10 +- src/Contract/CommandHandlerInterface.php | 7 +- src/Controller.php | 154 ++++++++++++++++------- src/IO/AbstractInput.php | 22 ++-- 12 files changed, 185 insertions(+), 140 deletions(-) diff --git a/examples/Command/DemoCommand.php b/examples/Command/DemoCommand.php index 561694c0..7a855a26 100644 --- a/examples/Command/DemoCommand.php +++ b/examples/Command/DemoCommand.php @@ -12,6 +12,7 @@ use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; use LogicException; +use Toolkit\PFlag\FlagType; /** * Class DemoCommand @@ -29,14 +30,14 @@ class DemoCommand extends Command */ protected function configure(): void { - $this->createDefinition() + $this->getFlags() + ->addArg('name', 'description for the argument [name], is required', FlagType::STRING, true) + ->addArg('sex', 'description for the argument [sex], is optional') + ->addArg('age', 'description for the argument [age], is optional', FlagType::INT) + ->addOpt('yes', 'y', 'description for the option [yes], is boolean', FlagType::BOOL) + ->addOpt('opt1', '', 'description for the option [opt1], is required', FlagType::STRING, true) + ->addOpt('opt2', '', 'description for the option [opt2], is optional') ->setExample($this->parseCommentsVars('{script} {command} john male 43 --opt1 value1')) - ->addArgument('name', Input::ARG_REQUIRED, 'description for the argument [name], is required') - ->addArgument('sex', Input::ARG_OPTIONAL, 'description for the argument [sex], is optional') - ->addArgument('age', Input::ARG_OPTIONAL, 'description for the argument [age], is optional') - ->addOption('yes', 'y', Input::OPT_BOOLEAN, 'description for the option [yes], is boolean') - ->addOption('opt1', null, Input::OPT_REQUIRED, 'description for the option [opt1], is required') - ->addOption('opt2', null, Input::OPT_OPTIONAL, 'description for the option [opt2], is optional') ; } diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 8d0b3697..e6a68ecf 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -21,7 +21,6 @@ use Inhere\Console\Util\FormatUtil; use Inhere\Console\Util\Helper; use InvalidArgumentException; -use LogicException; use ReflectionClass; use RuntimeException; use Swoole\Coroutine; @@ -41,6 +40,7 @@ use function preg_replace; use function sprintf; use function ucfirst; +use function vdump; use const PHP_EOL; use const PHP_OS; @@ -102,7 +102,7 @@ abstract class AbstractHandler implements CommandHandlerInterface * * @var bool */ - private $compatible = true; + protected $compatible = true; /** * @var InputDefinition|null @@ -168,7 +168,7 @@ protected function init(): void $this->commentsVars = $this->annotationVars(); $this->afterInit(); - $this->debugf('attach inner sub-commands to "%s"', self::getName()); + $this->debugf('attach inner subcommands to "%s"', self::getName()); $this->addCommands($this->commands()); } @@ -204,21 +204,6 @@ protected function configure(): void { } - /** - * @return InputDefinition - * @throws LogicException - * @throws InvalidArgumentException - */ - protected function createDefinition(): InputDefinition - { - if (!$this->definition) { - $this->definition = new InputDefinition(); - $this->definition->setDescription(self::getDescription()); - } - - return $this->definition; - } - /** * Provides parsable substitution variables for command annotations. Can be used in comments in commands * 为命令注解提供可解析的替换变量. 可以在命令的注释中使用 @@ -277,6 +262,9 @@ protected function initForRun(Input $input): void // set options by options() $optRules = $this->options(); $this->flags->addOptsByRules($optRules); + $this->flags->setBeforePrintHelp(function (string $text) { + return $this->parseCommentsVars($text); + }); $this->flags->setHelpRenderer(function () { $this->logf(Console::VERB_DEBUG, 'show help message by input flags: -h, --help'); $this->showHelp(); @@ -301,14 +289,16 @@ protected function getBuiltInOptions(): array */ public function run(array $args) { + $name = self::getName(); + try { $this->initForRun($this->input); - $this->logf(Console::VERB_DEBUG, 'init run - begin parse options'); + $this->log(Console::VERB_DEBUG, "begin run '$name' - parse options", ['args' => $args]); // parse options if (!$this->flags->parse($args)) { - return -1; // on error, help + return 0; // on error, help } $args = $this->flags->getRawArgs(); @@ -334,7 +324,7 @@ public function run(array $args) * * @return int|mixed */ - public function doRun(array $args) + protected function doRun(array $args) { if (isset($args[0])) { $first = $args[0]; @@ -345,15 +335,9 @@ public function doRun(array $args) } } - $this->debugf('begin run command. load configure for command'); - // load input definition configure - $this->configure(); - - // if with option: -h|--help - // if ($this->input->getSameBoolOpt(['h', 'help'])) { - // $this->showHelp(); - // return 0; - // } + // $this->debugf('begin run command. load configure for command'); + // // load input definition configure + // $this->configure(); // some prepare check // - validate input arguments @@ -709,6 +693,14 @@ final public static function getName(): string return static::$name; } + /** + * @return string + */ + public static function getDesc(): string + { + return static::$description; + } + /** * @return string */ @@ -778,22 +770,6 @@ public static function setAnnotationTags(array $annotationTags, $replace = false self::$annotationTags = $replace ? $annotationTags : array_merge(self::$annotationTags, $annotationTags); } - /** - * @return InputDefinition|null - */ - public function getDefinition(): ?InputDefinition - { - return $this->definition; - } - - /** - * @param InputDefinition $definition - */ - public function setDefinition(InputDefinition $definition): void - { - $this->definition = $definition; - } - /** * @return string */ diff --git a/src/Application.php b/src/Application.php index 0b4c0aef..921d94d2 100644 --- a/src/Application.php +++ b/src/Application.php @@ -16,6 +16,7 @@ use InvalidArgumentException; use RuntimeException; use SplFileInfo; +use Throwable; use function array_unshift; use function class_exists; use function implode; @@ -261,6 +262,7 @@ protected function getFileFilter(): callable * @param array $args * * @return int|mixed + * @throws Throwable */ public function dispatch(string $name, array $args = []) { @@ -322,9 +324,10 @@ public function dispatch(string $name, array $args = []) * @param string $name Command name * @param Closure|string $handler Command class or handler func * @param array $options + * @param array $args * * @return mixed - * @throws InvalidArgumentException + * @throws Throwable */ protected function runCommand(string $name, $handler, array $options, array $args) { @@ -362,9 +365,11 @@ protected function runCommand(string $name, $handler, array $options, array $arg * @param array $info Matched route info * @psalm-param array{action: string} $info Matched route info * @param array $options + * @param array $args * @param bool $detachedRun * * @return mixed + * @throws Throwable */ protected function runAction(array $info, array $options, array $args, bool $detachedRun = false) { diff --git a/src/Attr/RuleArg.php b/src/Attr/RuleArg.php index 4752530d..0414e620 100644 --- a/src/Attr/RuleArg.php +++ b/src/Attr/RuleArg.php @@ -3,7 +3,7 @@ namespace Inhere\Console\Attr; use Attribute; -use Toolkit\PFlag\AbstractFlags; +use Toolkit\PFlag\FlagsParser; /** * class RuleArg @@ -18,7 +18,7 @@ class RuleArg /** * @var string - * @see AbstractFlags::$argRules + * @see FlagsParser::$argRules */ public $rule; } diff --git a/src/Attr/RuleOpt.php b/src/Attr/RuleOpt.php index 21f620b3..21c47e10 100644 --- a/src/Attr/RuleOpt.php +++ b/src/Attr/RuleOpt.php @@ -3,7 +3,7 @@ namespace Inhere\Console\Attr; use Attribute; -use Toolkit\PFlag\AbstractFlags; +use Toolkit\PFlag\FlagsParser; /** * class RuleOpt @@ -18,7 +18,7 @@ class RuleOpt /** * @var string - * @see AbstractFlags::$optRules + * @see FlagsParser::$optRules */ public $rule; } diff --git a/src/Command.php b/src/Command.php index af08d2b7..0f5ca4d3 100644 --- a/src/Command.php +++ b/src/Command.php @@ -42,6 +42,15 @@ abstract class Command extends AbstractHandler implements CommandInterface // // something logic ... // } + protected function doRun(array $args) + { + $this->debugf('load configure for command: %s', self::getName()); + // load input definition configure + $this->configure(); + + parent::doRun($args); + } + /* * Configure command */ @@ -91,10 +100,13 @@ protected function showHelp(): bool $aliases = $this->getAliases(); // render help by input definition. - if ($definition = $this->getDefinition()) { - $this->showHelpByDefinition($definition, $aliases); - return true; - } + // if ($definition = $this->getDefinition()) { + // $this->showHelpByDefinition($definition, $aliases); + // return true; + // } + + // TODO show help by flags + $execMethod = self::METHOD; diff --git a/src/Concern/CommandHelpTrait.php b/src/Concern/CommandHelpTrait.php index 032bdb9b..57e74dfa 100644 --- a/src/Concern/CommandHelpTrait.php +++ b/src/Concern/CommandHelpTrait.php @@ -72,7 +72,7 @@ protected function setCommentsVar(string $name, $value): void * * @return string */ - protected function parseCommentsVars(string $str): string + public function parseCommentsVars(string $str): string { // not use vars if (false === strpos($str, self::HELP_VAR_LEFT)) { diff --git a/src/Concern/InputOutputAwareTrait.php b/src/Concern/InputOutputAwareTrait.php index f3247877..a66ed0ea 100644 --- a/src/Concern/InputOutputAwareTrait.php +++ b/src/Concern/InputOutputAwareTrait.php @@ -12,7 +12,7 @@ use Inhere\Console\Contract\InputInterface; use Inhere\Console\IO\Output; use Inhere\Console\Contract\OutputInterface; -use Toolkit\PFlag\AbstractFlags; +use Toolkit\PFlag\FlagsParser; use Toolkit\PFlag\SFlags; /** @@ -23,7 +23,7 @@ trait InputOutputAwareTrait { /** - * @var SFlags|AbstractFlags + * @var SFlags|FlagsParser */ protected $flags; @@ -199,17 +199,17 @@ public function setOutput(OutputInterface $output): void } /** - * @return AbstractFlags|SFlags + * @return FlagsParser|SFlags */ - public function getFlags(): AbstractFlags + public function getFlags(): FlagsParser { return $this->flags; } /** - * @param AbstractFlags|SFlags $flags + * @param FlagsParser|SFlags $flags */ - public function setFlags(AbstractFlags $flags): void + public function setFlags(FlagsParser $flags): void { $this->flags = $flags; } diff --git a/src/Concern/SubCommandsWareTrait.php b/src/Concern/SubCommandsWareTrait.php index e51e0942..a1b76acd 100644 --- a/src/Concern/SubCommandsWareTrait.php +++ b/src/Concern/SubCommandsWareTrait.php @@ -85,10 +85,8 @@ protected function dispatchCommand(string $name): void */ public function addCommand(string $name, $handler = null, array $options = []): void { - /** - * @var Command $name name is an command class - */ if (!$handler && class_exists($name)) { + /** @var Command $name name is an command class */ $handler = $name; $name = $name::getName(); } @@ -107,11 +105,11 @@ public function addCommand(string $name, $handler = null, array $options = []): if (is_string($handler)) { if (!class_exists($handler)) { - Helper::throwInvalidArgument("The console command class [$handler] not exists!"); + Helper::throwInvalidArgument("The command handler class [$handler] not exists!"); } if (!is_subclass_of($handler, Command::class)) { - Helper::throwInvalidArgument('The console command class must is subclass of the: ' . Command::class); + Helper::throwInvalidArgument('The command handler class must is subclass of the: ' . Command::class); } // not enable @@ -175,7 +173,7 @@ public function isSubCommand(string $name): bool } /** - * @param $name + * @param string $name * * @throws InvalidArgumentException */ diff --git a/src/Contract/CommandHandlerInterface.php b/src/Contract/CommandHandlerInterface.php index 21e04352..6dcae395 100644 --- a/src/Contract/CommandHandlerInterface.php +++ b/src/Contract/CommandHandlerInterface.php @@ -39,11 +39,6 @@ interface CommandHandlerInterface */ public function run(array $args); - /** - * @return InputDefinition|null - */ - public function getDefinition(): ?InputDefinition; - /** * @return AbstractApplication|ApplicationInterface */ @@ -57,5 +52,5 @@ public static function getName(): string; /** * @return string */ - public static function getDescription(): string; + public static function getDesc(): string; } diff --git a/src/Controller.php b/src/Controller.php index 896c8bdf..6ae0960b 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -17,14 +17,19 @@ use Inhere\Console\Util\FormatUtil; use Inhere\Console\Util\Helper; use ReflectionClass; +use ReflectionException; +use ReflectionMethod; use ReflectionObject; use RuntimeException; +use Throwable; use Toolkit\Cli\Helper\FlagHelper; -use Toolkit\PFlag\AbstractFlags; +use Toolkit\PFlag\FlagsParser; use Toolkit\PFlag\SFlags; +use Toolkit\Stdlib\Obj\ObjectHelper; use Toolkit\Stdlib\Str; use function array_flip; use function array_keys; +use function array_shift; use function implode; use function is_array; use function is_string; @@ -72,6 +77,11 @@ abstract class Controller extends AbstractHandler implements ControllerInterface */ private $action = ''; + /** + * @var string + */ + private $actionMethod = ''; + /** * Input subcommand name. * @@ -106,8 +116,8 @@ abstract class Controller extends AbstractHandler implements ControllerInterface * ] * ``` * - * @var AbstractFlags[] - * @psalm-var array + * @var FlagsParser[] + * @psalm-var array */ private $subFss = []; @@ -214,7 +224,7 @@ protected function beforeRun(): void * @param array $args * * @return int|mixed - * @throws \Throwable + * @throws Throwable */ public function doRun(array $args) { @@ -230,11 +240,12 @@ public function doRun(array $args) } else { $first = $args[0]; if (!FlagHelper::isValidName($first)) { - $this->debugf('not input subcommand, display help for the group: %s', self::getName()); + $this->debugf('not input subcommand, display help for the group: %s', $name); return $this->showHelp(); } $command = $first; + array_shift($args); $this->input->popFirstArg(); } @@ -256,23 +267,38 @@ public function doRun(array $args) // convert 'boo-foo' to 'booFoo' $this->action = $action = Str::camelCase($command); - $this->debugf("will run the '%s' group action: %s, subcommand: %s", static::getName(), $this->action, $command); + $this->debugf("will run the '%s' group action: %s, subcommand: %s", $name, $action, $command); + $this->actionMethod = $method = $this->getMethodName($action); // fire event $this->fire(ConsoleEvent::COMMAND_RUN_BEFORE, $this); $this->beforeRun(); // check method not exist - $method = $this->getMethodName($action); - - // if command method not exists. + // - if command method not exists. if (!method_exists($this, $method)) { - return $this->handleNotFound(static::getName(), $action); + return $this->handleNotFound($name, $action); + } + + // init flags for subcommand + $fs = $this->newActionFlags(); + if (!$this->compatible) { + $this->input->setFs($fs); + } + + $this->debugf('load configure for subcommand: %s', $command); + // load input definition configure + $this->configure(); + + $this->log(Console::VERB_DEBUG, "run subcommand '$command' - parse options", ['args' => $args]); + + // parse subcommand flags. + if (!$fs->parse($args)) { + return 0; } // do running - $this->newActionFlags(); - return parent::doRun([$command]); + return parent::doRun($args); } /** @@ -288,20 +314,6 @@ protected function configure(): void } } - /** - * @return AbstractFlags - */ - protected function newActionFlags(): AbstractFlags - { - if (!$fs = $this->getActionFlags($this->action)) { - $fs = new SFlags(); - // save - $this->subFss[$this->action] = $fs; - } - - return $fs; - } - /** * Before controller method execute * @@ -326,6 +338,7 @@ protected function afterAction(): void * @param Output $output * * @return mixed + * @throws ReflectionException */ final public function execute($input, $output) { @@ -359,9 +372,21 @@ final public function execute($input, $output) } } - // run action - $flags = $this->actionFlags($action); - $result = $this->$method($input, $output, $flags); + // current action flags + $flags = $this->actionFlags($action); + + $rftMethod = new ReflectionMethod($this, $method); + $callArgs = ObjectHelper::buildReflectCallArgs($rftMethod, [ + Input::class => $this->input, + Output::class => $this->output, + FlagsParser::class => $flags, + // 'args' => $args, + ]); + + // call action method + $result = $rftMethod->invokeArgs($this, $callArgs); + // call action method + // $result = $this->$method($input, $output, $flags); // after run action if (method_exists($this, $after = 'after' . ucfirst($action))) { @@ -406,6 +431,37 @@ protected function handleNotFound(string $group, string $action): int return -1; } + /** + * @param string $action + * + * @return FlagsParser + */ + protected function newActionFlags(string $action = ''): FlagsParser + { + $action = $action ?: $this->action; + if (!$fs = $this->getActionFlags($action)) { + $fs = new SFlags(['name' => $action]); + // $fs->setStopOnFistArg(false); + $fs->setBeforePrintHelp(function (string $text) { + return $this->parseCommentsVars($text); + }); + $fs->setHelpRenderer(function () { + $this->logf(Console::VERB_DEBUG, 'show subcommand help by input flags: -h, --help'); + $this->showHelp(); + }); + + // old mode: options and arguments at method annotations + if ($this->compatible) { + $fs->setSkipOnUndefined(true); + } + + // save + $this->subFss[$action] = $fs; + } + + return $fs; + } + /** * @param string $action * @@ -422,16 +478,18 @@ protected function getMethodName(string $action): string protected function showHelp(): bool { // render help by Definition - if ($definition = $this->getDefinition()) { - if ($action = $this->action) { - $aliases = $this->getCommandAliases($action); - } else { - $aliases = $this->getAliases(); - } + // if ($definition = $this->getDefinition()) { + // if ($action = $this->action) { + // $aliases = $this->getCommandAliases($action); + // } else { + // $aliases = $this->getAliases(); + // } + // + // $this->showHelpByDefinition($definition, $aliases); + // return true; + // } - $this->showHelpByDefinition($definition, $aliases); - return true; - } + // TODO show help by flags return $this->helpCommand() === 0; } @@ -485,18 +543,18 @@ public function getRealCommandName(string $name): string } /** - * @param string $name + * @param string $alias * * @return string */ - public function resolveAlias(string $name): string + public function resolveAlias(string $alias): string { - if (!$name) { + if (!$alias) { return ''; } $map = $this->getCommandAliases(); - return $map[$name] ?? $name; + return $map[$alias] ?? $alias; } /** @@ -692,9 +750,9 @@ public function getCommandMeta(string $key, $default = null, string $command = ' /** * @param string $action * - * @return AbstractFlags|null + * @return FlagsParser|null */ - public function getActionFlags(string $action): ?AbstractFlags + public function getActionFlags(string $action): ?FlagsParser { $action = $action ?: $this->action; @@ -702,9 +760,9 @@ public function getActionFlags(string $action): ?AbstractFlags } /** - * @return AbstractFlags + * @return FlagsParser */ - public function curActionFlags(): AbstractFlags + public function curActionFlags(): FlagsParser { return $this->actionFlags($this->action); } @@ -712,9 +770,9 @@ public function curActionFlags(): AbstractFlags /** * @param string $action * - * @return AbstractFlags + * @return FlagsParser */ - public function actionFlags(string $action): AbstractFlags + public function actionFlags(string $action): FlagsParser { $action = $action ?: $this->action; diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index 9b2ce7e4..52316199 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -11,7 +11,7 @@ use Inhere\Console\Concern\InputArgumentsTrait; use Inhere\Console\Concern\InputOptionsTrait; use Inhere\Console\Contract\InputInterface; -use Toolkit\PFlag\AbstractFlags; +use Toolkit\PFlag\FlagsParser; use Toolkit\PFlag\SFlags; use function array_shift; use function basename; @@ -31,14 +31,14 @@ abstract class AbstractInput implements InputInterface /** * Global flags parser * - * @var AbstractFlags|SFlags + * @var FlagsParser|SFlags */ protected $gfs; /** * Command flags parser * - * @var AbstractFlags|SFlags + * @var FlagsParser|SFlags */ protected $fs; @@ -326,33 +326,33 @@ public function setSubCommand(string $subCommand): void } /** - * @return AbstractFlags + * @return FlagsParser */ - public function getGfs(): AbstractFlags + public function getGfs(): FlagsParser { return $this->gfs; } /** - * @param AbstractFlags $gfs + * @param FlagsParser $gfs */ - public function setGfs(AbstractFlags $gfs): void + public function setGfs(FlagsParser $gfs): void { $this->gfs = $gfs; } /** - * @return AbstractFlags + * @return FlagsParser */ - public function getFs(): AbstractFlags + public function getFs(): FlagsParser { return $this->fs; } /** - * @param AbstractFlags $fs + * @param FlagsParser $fs */ - public function setFs(AbstractFlags $fs): void + public function setFs(FlagsParser $fs): void { $this->fs = $fs; } From 9f6c6c39dc375d49eab21a78b7456bebcfee2e74 Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 14 Sep 2021 21:30:47 +0800 Subject: [PATCH 146/258] update some command error handle logic --- src/AbstractApplication.php | 13 ++-- src/AbstractHandler.php | 13 +--- src/Application.php | 3 +- src/Command.php | 17 +++-- src/Component/ErrorHandler.php | 99 ++++++++++++++++++++++++-- src/Contract/ErrorHandlerInterface.php | 7 +- src/Controller.php | 12 ++-- src/GlobalOption.php | 8 ++- 8 files changed, 132 insertions(+), 40 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 047a27bc..c8bdacab 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -28,6 +28,7 @@ use Toolkit\Cli\Style; use Toolkit\Cli\Util\LineParser; use Toolkit\PFlag\SFlags; +use Toolkit\Stdlib\Helper\DataHelper; use Toolkit\Stdlib\Helper\PhpHelper; use Toolkit\Stdlib\OS; use Toolkit\Sys\Proc\ProcessUtil; @@ -39,7 +40,6 @@ use function header; use function in_array; use function is_int; -use function json_encode; use function memory_get_usage; use function microtime; use function register_shutdown_function; @@ -164,7 +164,10 @@ protected function init(): void ]; if (!$this->errorHandler) { - $this->errorHandler = new ErrorHandler(); + $this->errorHandler = new ErrorHandler([ + 'rootPath' => $this->config['rootPath'], + 'hideRootPath' => (bool)$this->config['hideRootPath'], + ]); } $this->registerErrorHandle(); @@ -196,6 +199,8 @@ protected function initForRun(Input $input): void $this->logf(Console::VERB_DEBUG, 'init - begin parse global options'); $this->flags->parse($input->getFlags()); + // set debug to error handler + $this->errorHandler->setDebug($this->isDebug()); } protected function prepareRun(): void @@ -451,7 +456,7 @@ public function handleError(int $num, string $str, string $file, int $line): voi public function handleException(Throwable $e): void { // you can log error on sub class ... - $this->errorHandler->handle($e, $this); + $this->errorHandler->handle($e); } /** @@ -538,7 +543,7 @@ protected function startInteractiveShell(): void } $args = LineParser::parseIt($line); - $this->debugf('input line: %s, parsed args: %s', $line, json_encode($args)); + $this->debugf('input line: %s, parsed args: %s', $line, DataHelper::toString($args)); // reload and parse args $in->parse($args); diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index e6a68ecf..7fec9a3a 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -8,6 +8,7 @@ namespace Inhere\Console; +use Inhere\Console\Component\ErrorHandler; use Inhere\Console\Concern\AttachApplicationTrait; use Inhere\Console\Concern\CommandHelpTrait; use Inhere\Console\Concern\InputOutputAwareTrait; @@ -40,7 +41,6 @@ use function preg_replace; use function sprintf; use function ucfirst; -use function vdump; use const PHP_EOL; use const PHP_OS; @@ -269,11 +269,6 @@ protected function initForRun(Input $input): void $this->logf(Console::VERB_DEBUG, 'show help message by input flags: -h, --help'); $this->showHelp(); }); - - // old mode: options and arguments at method annotations - if ($this->compatible) { - $this->flags->setSkipOnUndefined(true); - } } protected function getBuiltInOptions(): array @@ -306,12 +301,10 @@ public function run(array $args) return $this->doRun($args); } catch (Throwable $e) { if ($this->isDetached()) { - // TODO exception handle + ErrorHandler::new()->handle($e); } else { - // throw $e; + throw $e; } - - throw $e; } return -1; diff --git a/src/Application.php b/src/Application.php index 921d94d2..934b50f8 100644 --- a/src/Application.php +++ b/src/Application.php @@ -17,6 +17,7 @@ use RuntimeException; use SplFileInfo; use Throwable; +use Toolkit\Stdlib\Helper\DataHelper; use function array_unshift; use function class_exists; use function implode; @@ -271,7 +272,7 @@ public function dispatch(string $name, array $args = []) } $cmdId = $name; - $this->debugf('begin dispatch the input command: %s', $name); + $this->debugf('begin dispatch the input command: %s, args: %s', $name, DataHelper::toString($args)); // format is: `group action` if (strpos($name, ' ') > 0) { diff --git a/src/Command.php b/src/Command.php index 0f5ca4d3..a608a113 100644 --- a/src/Command.php +++ b/src/Command.php @@ -9,6 +9,7 @@ namespace Inhere\Console; use Inhere\Console\Contract\CommandInterface; +use Inhere\Console\IO\Input; /** * Class Command @@ -34,13 +35,15 @@ abstract class Command extends AbstractHandler implements CommandInterface */ protected $parent; - /* - * Do execute command - */ - // protected function execute($input, $output) - // { - // // something logic ... - // } + protected function initForRun(Input $input): void + { + parent::initForRun($input); + + // old mode: options and arguments at method annotations + if ($this->compatible) { + $this->flags->setSkipOnUndefined(true); + } + } protected function doRun(array $args) { diff --git a/src/Component/ErrorHandler.php b/src/Component/ErrorHandler.php index c46487f4..c4a3a08a 100644 --- a/src/Component/ErrorHandler.php +++ b/src/Component/ErrorHandler.php @@ -12,7 +12,9 @@ use Inhere\Console\Contract\ErrorHandlerInterface; use InvalidArgumentException; use Throwable; +use Toolkit\Cli\Cli; use Toolkit\Cli\Util\Highlighter; +use Toolkit\Stdlib\Obj\ObjectHelper; use function file_get_contents; use function get_class; use function sprintf; @@ -25,20 +27,55 @@ */ class ErrorHandler implements ErrorHandlerInterface { + /** + * @var bool + */ + protected $debug = false; + + /** + * @var string + */ + protected $rootPath = ''; + + /** + * @var bool + */ + protected $hideRootPath = false; + + /** + * @param array $config + * + * @return static + */ + public static function new(array $config = []): self + { + return new self($config); + } + + /** + * Class constructor. + * + * @param array $config + */ + public function __construct(array $config = []) + { + ObjectHelper::init($this, $config); + } + /** * @inheritdoc */ - public function handle(Throwable $e, AbstractApplication $app): void + public function handle(Throwable $e): void { if ($e instanceof InvalidArgumentException) { - $app->getOutput()->error($e->getMessage()); + Cli::error($e->getMessage()); return; } $class = get_class($e); // open debug, throw exception - if ($app->isDebug()) { + if ($this->isDebug()) { $tpl = << Error %s @@ -60,16 +97,64 @@ public function handle(Throwable $e, AbstractApplication $app): void $e->getTraceAsString()// \str_replace('):', '): -', $e->getTraceAsString()) ); - if ($app->getParam('hideRootPath') && ($rootPath = $app->getParam('rootPath'))) { + if ($this->hideRootPath && ($rootPath = $this->rootPath)) { $message = str_replace($rootPath, '{ROOT}', $message); } - $app->write($message, false); + Cli::write($message, false); return; } // simple output - $app->getOutput()->error($e->getMessage() ?: 'unknown error'); - $app->write("\nYou can use '--debug 4' to see error details."); + Cli::error($e->getMessage() ?: 'unknown error'); + Cli::write("\nYou can use '--debug 4' to see error details."); + } + + /** + * @return bool + */ + public function isDebug(): bool + { + return $this->debug; + } + + /** + * @param bool $debug + */ + public function setDebug(bool $debug): void + { + $this->debug = $debug; + } + + /** + * @return string + */ + public function getRootPath(): string + { + return $this->rootPath; + } + + /** + * @param string $rootPath + */ + public function setRootPath(string $rootPath): void + { + $this->rootPath = $rootPath; + } + + /** + * @return bool + */ + public function isHideRootPath(): bool + { + return $this->hideRootPath; + } + + /** + * @param bool $hideRootPath + */ + public function setHideRootPath(bool $hideRootPath): void + { + $this->hideRootPath = $hideRootPath; } } diff --git a/src/Contract/ErrorHandlerInterface.php b/src/Contract/ErrorHandlerInterface.php index 8d1c4ef7..38d4d178 100644 --- a/src/Contract/ErrorHandlerInterface.php +++ b/src/Contract/ErrorHandlerInterface.php @@ -8,8 +8,6 @@ namespace Inhere\Console\Contract; -use Inhere\Console\AbstractApplication; -use Inhere\Console\Application; use Throwable; /** @@ -20,8 +18,7 @@ interface ErrorHandlerInterface { /** - * @param Throwable $e - * @param Application|AbstractApplication $app + * @param Throwable $e */ - public function handle(Throwable $e, AbstractApplication $app): void; + public function handle(Throwable $e): void; } diff --git a/src/Controller.php b/src/Controller.php index 6ae0960b..a4695f81 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -202,10 +202,11 @@ protected function disabledCommands(): array * Will call it on action(sub-command) not found on the group. * * @param string $action + * @param array $args * * @return bool if return True, will stop goon render group help. */ - protected function onNotFound(string $action): bool + protected function onNotFound(string $action, array $args): bool { // you can add custom logic on sub-command not found. return false; @@ -277,7 +278,7 @@ public function doRun(array $args) // check method not exist // - if command method not exists. if (!method_exists($this, $method)) { - return $this->handleNotFound($name, $action); + return $this->handleNotFound($name, $action, $args); } // init flags for subcommand @@ -290,7 +291,7 @@ public function doRun(array $args) // load input definition configure $this->configure(); - $this->log(Console::VERB_DEBUG, "run subcommand '$command' - parse options", ['args' => $args]); + $this->log(Console::VERB_DEBUG, "run subcommand '$name.$command' - parse options", ['args' => $args]); // parse subcommand flags. if (!$fs->parse($args)) { @@ -400,13 +401,14 @@ final public function execute($input, $output) /** * @param string $group * @param string $action + * @param array $args * * @return int */ - protected function handleNotFound(string $group, string $action): int + protected function handleNotFound(string $group, string $action, array $args): int { // if user custom handle not found logic. - if ($this->onNotFound($action)) { + if ($this->onNotFound($action, $args)) { $this->debugf('user custom handle the "%s" action "%s" not found', $group, $action); return 0; } diff --git a/src/GlobalOption.php b/src/GlobalOption.php index da63e226..accb1169 100644 --- a/src/GlobalOption.php +++ b/src/GlobalOption.php @@ -2,6 +2,7 @@ namespace Inhere\Console; +use Toolkit\PFlag\FlagType; use function array_merge; /** @@ -46,7 +47,12 @@ class GlobalOption * @psalm-var array */ private static $options = [ - '--debug' => 'int;Setting the runtime log debug level(quiet 0 - 5 crazy);no;1', + // '--debug' => 'int;Setting the runtime log debug level(quiet 0 - 5 crazy);no;1', + '--debug' => [ + 'type' => FlagType::INT, + 'desc' => 'Setting the runtime log debug level(quiet 0 - 5 crazy)', + 'envVar' => Console::DEBUG_ENV_KEY, + ], // '--debug' => 'Setting the runtime log debug level(quiet 0 - 5 crazy)', '--ishell' => 'bool;Run application an interactive shell environment', // '--ishell' => 'Run application an interactive shell environment', From 8d8b6afacc0130ee66a77d6eeb803501045a6b02 Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 15 Sep 2021 13:49:39 +0800 Subject: [PATCH 147/258] feat: support parse flag rules by method docComment --- src/AbstractHandler.php | 38 +++- src/Annotate/AnnotateRules.php | 33 ++++ src/{ => Annotate}/Attr/CmdArgument.php | 2 +- src/{ => Annotate}/Attr/CmdOption.php | 2 +- src/{ => Annotate}/Attr/RuleArg.php | 2 +- src/{ => Annotate}/Attr/RuleOpt.php | 2 +- src/Annotate/DocblockRules.php | 228 ++++++++++++++++++++++++ src/Attr/.keep | 0 src/Command.php | 3 +- src/Concern/ControllerHelpTrait.php | 1 - src/Concern/NameAliasTrait.php | 88 --------- src/Concern/SubCommandsWareTrait.php | 2 +- src/Router.php | 2 +- test/Annotate/DocblockRulesTest.php | 69 +++++++ 14 files changed, 373 insertions(+), 99 deletions(-) create mode 100644 src/Annotate/AnnotateRules.php rename src/{ => Annotate}/Attr/CmdArgument.php (93%) rename src/{ => Annotate}/Attr/CmdOption.php (93%) rename src/{ => Annotate}/Attr/RuleArg.php (89%) rename src/{ => Annotate}/Attr/RuleOpt.php (89%) create mode 100644 src/Annotate/DocblockRules.php delete mode 100644 src/Attr/.keep delete mode 100644 src/Concern/NameAliasTrait.php create mode 100644 test/Annotate/DocblockRulesTest.php diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 7fec9a3a..4a8d7d26 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -8,6 +8,7 @@ namespace Inhere\Console; +use Inhere\Console\Annotate\DocblockRules; use Inhere\Console\Component\ErrorHandler; use Inhere\Console\Concern\AttachApplicationTrait; use Inhere\Console\Concern\CommandHelpTrait; @@ -23,10 +24,12 @@ use Inhere\Console\Util\Helper; use InvalidArgumentException; use ReflectionClass; +use ReflectionMethod; use RuntimeException; use Swoole\Coroutine; use Swoole\Event; use Throwable; +use Toolkit\PFlag\FlagsParser; use Toolkit\PFlag\SFlags; use Toolkit\Stdlib\Obj\ConfigObject; use Toolkit\Stdlib\Util\PhpDoc; @@ -459,6 +462,14 @@ public function isAlone(): bool return $this instanceof CommandInterface; } + /** + * @return bool + */ + public function isCommand(): bool + { + return $this instanceof CommandInterface; + } + /** * @return ConfigObject */ @@ -482,6 +493,24 @@ public function initParams(array $params): ConfigObject return $this->params; } + /** + * @param string $method + * @param FlagsParser $fs + * + * @throws \ReflectionException + */ + public function loadRulesByDocblock(string $method, FlagsParser $fs): void + { + $rftMth = new ReflectionMethod($this, $method); + + // parse doc for get flag rules + $dr = DocblockRules::newByDocblock($rftMth->getDocComment()); + $dr->parse(); + + $fs->addArgsByRules($dr->getArgRules()); + $fs->addOptsByRules($dr->getOptRules()); + } + /********************************************************** * display help information **********************************************************/ @@ -576,11 +605,14 @@ protected function showHelpByAnnotations(string $method, string $action = '', ar return 0; } + $allowedTags = array_keys(self::$annotationTags); $this->logf(Console::VERB_DEBUG, "render help for the command: %s", $this->input->getCommandId()); $help = []; $doc = $ref->getMethod($method)->getDocComment(); - $tags = PhpDoc::getTags($this->parseCommentsVars((string)$doc)); + $tags = PhpDoc::getTags($this->parseCommentsVars((string)$doc), [ + 'allow' => $allowedTags, + ]); if ($aliases) { $realName = $action ?: static::getName(); @@ -598,11 +630,11 @@ protected function showHelpByAnnotations(string $method, string $action = '', ar // is an command object $isCommand = $ref->isSubclassOf(CommandInterface::class); - foreach (array_keys(self::$annotationTags) as $tag) { + foreach ($allowedTags as $tag) { if (empty($tags[$tag]) || !is_string($tags[$tag])) { // for alone command if ($tag === 'description' && $isCommand) { - $help['Description:'] = static::getDescription(); + $help['Description:'] = static::getDesc(); continue; } diff --git a/src/Annotate/AnnotateRules.php b/src/Annotate/AnnotateRules.php new file mode 100644 index 00000000..dfc4ee6b --- /dev/null +++ b/src/Annotate/AnnotateRules.php @@ -0,0 +1,33 @@ + allow multi tags + 'desc' => false, + 'usage' => false, + 'argument' => true, + 'option' => true, + 'example' => false, + 'help' => false, + ]; + + /** + * @return array + */ + public static function getAllowedTags(): array + { + return self::$allowedTags; + } + +} diff --git a/src/Attr/CmdArgument.php b/src/Annotate/Attr/CmdArgument.php similarity index 93% rename from src/Attr/CmdArgument.php rename to src/Annotate/Attr/CmdArgument.php index 7d83d206..8dc67928 100644 --- a/src/Attr/CmdArgument.php +++ b/src/Annotate/Attr/CmdArgument.php @@ -1,6 +1,6 @@ multi line align + 'desc' => false, + 'usage' => false, + 'arguments' => true, + 'options' => true, + 'example' => true, + 'help' => true, + ]; + + /** + * Parsed docblock tags + * + * @var array + * @see $allowedTags for keys + * @psalm-var array + */ + private $docTags; + + /** + * @var array + */ + private $argRules = []; + + /** + * @var array + */ + private $optRules = []; + + /** + * @param string $doc + * + * @return static + */ + public static function newByDocblock(string $doc): self + { + $dr = new self(); + $dr->setDocTagsByDocblock($doc); + + return $dr; + } + + /** + * @param array $docTags + * + * @return static + */ + public static function new(array $docTags = []): self + { + return new self($docTags); + } + + /** + * Class constructor. + * + * @param array $docTags + */ + public function __construct(array $docTags = []) + { + $this->docTags = $docTags; + } + + /** + * @param string $doc + */ + public function setDocTagsByDocblock(string $doc): void + { + $docTags = PhpDoc::parseDocs($doc, [ + 'default' => 'desc', + 'allow' => self::getAllowedTags(), + ]); + + $this->docTags = $docTags; + } + + /** + * parse multi line text to flag rules + * + * @options + * -r, --remote The git remote name. default is `origin` + * --main bool;Use the config `mainRemote` name + * + * @arguments + * repoPath The remote git repo URL or repository group/name. + * If not input, will auto parse from current work directory + * + * @return $this + * @example + * {fullCmd} php-toolkit/cli-utils + * {fullCmd} https://github.com/php-toolkit/cli-utils + * + */ + public function parse(): self + { + if ($argsText = $this->docTags['arguments'] ?? '') { + $lines = explode("\n", $argsText); + + $this->argRules = $this->parseMultiLines($lines); + } + + if ($optsText = $this->docTags['options'] ?? '') { + $lines = explode("\n", $optsText); + + $this->optRules = $this->parseMultiLines($lines); + } + + return $this; + } + + /** + * @param array $lines + * + * @return array + */ + protected function parseMultiLines(array $lines): array + { + $index = 0; + $rules = $kvRules = []; + + // $keyWidth = 16; + foreach ($lines as $line) { + $trimmed = trim($line); + if (!$trimmed) { + continue; + } + + $nodes = Str::explode($trimmed, ' ', 2); + if (!isset($nodes[1])) { + if ($index === 0) { // invalid first line + continue; + } + + // multi desc message. + $rules[$index - 1][1] .= "\n" . $trimmed; + continue; + } + + $name = $nodes[0]; + if (!preg_match('/^[\w ,-]{0,48}$/', $name)) { + // multi desc message. + $rules[$index - 1][1] .= "\n" . $trimmed; + continue; + } + + $rules[$index] = $nodes; + $index++; + } + + if ($rules) { + foreach ($rules as [$name, $rule]) { + $kvRules[$name] = $rule; + } + } + + return $kvRules; + } + + /** + * @param string $tag + * + * @return string + */ + public function getTagValue(string $tag): string + { + return $this->docTags[$tag] ?? ''; + } + + /** + * @return array + */ + public static function getAllowedTags(): array + { + return array_keys(self::$allowedTags); + } + + /** + * @return array + */ + public function getArgRules(): array + { + return $this->argRules; + } + + /** + * @return array + */ + public function getOptRules(): array + { + return $this->optRules; + } + + /** + * @return array + */ + public function getDocTags(): array + { + return $this->docTags; + } + + /** + * @param array $docTags + */ + public function setDocTags(array $docTags): void + { + $this->docTags = $docTags; + } +} diff --git a/src/Attr/.keep b/src/Attr/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Command.php b/src/Command.php index a608a113..3f2cd016 100644 --- a/src/Command.php +++ b/src/Command.php @@ -109,7 +109,8 @@ protected function showHelp(): bool // } // TODO show help by flags - + // if ($this->flags->isNotEmpty()) { + // } $execMethod = self::METHOD; diff --git a/src/Concern/ControllerHelpTrait.php b/src/Concern/ControllerHelpTrait.php index 1e5aac61..03c6eae4 100644 --- a/src/Concern/ControllerHelpTrait.php +++ b/src/Concern/ControllerHelpTrait.php @@ -3,7 +3,6 @@ namespace Inhere\Console\Concern; use Inhere\Console\Console; -use Inhere\Console\GlobalOption; use Inhere\Console\Util\FormatUtil; use ReflectionClass; use Toolkit\Cli\ColorTag; diff --git a/src/Concern/NameAliasTrait.php b/src/Concern/NameAliasTrait.php deleted file mode 100644 index e6164ed0..00000000 --- a/src/Concern/NameAliasTrait.php +++ /dev/null @@ -1,88 +0,0 @@ -aliases[$aliasName])) { - $this->aliases[$aliasName] = $name; - } elseif ($validate) { - $oldName = $this->aliases[$aliasName]; - throw new InvalidArgumentException( - "Alias '$aliasName' has been registered by '$oldName', cannot assign to the '$name'" - ); - } - } - } - - /** - * Get real name by alias - * - * @param string $alias - * - * @return mixed - */ - public function resolveAlias(string $alias): string - { - return $this->aliases[$alias] ?? $alias; - } - - /** - * @param string $alias - * - * @return bool - */ - public function hasAlias(string $alias): bool - { - return isset($this->aliases[$alias]); - } - - /** - * @param string $name - * - * @return array - */ - public function getAliases(string $name = ''): array - { - if ($name) { - $aliases = []; - foreach ($this->aliases as $alias => $n) { - if ($name === $n) { - $aliases[] = $alias; - } - } - - return $aliases; - } - - return $this->aliases; - } -} diff --git a/src/Concern/SubCommandsWareTrait.php b/src/Concern/SubCommandsWareTrait.php index a1b76acd..3da54bdc 100644 --- a/src/Concern/SubCommandsWareTrait.php +++ b/src/Concern/SubCommandsWareTrait.php @@ -26,7 +26,7 @@ */ trait SubCommandsWareTrait { - use NameAliasTrait; + use \Toolkit\Stdlib\Obj\Traits\NameAliasTrait; /** * @var array diff --git a/src/Router.php b/src/Router.php index aeb598fa..fe44118c 100644 --- a/src/Router.php +++ b/src/Router.php @@ -6,9 +6,9 @@ use Inhere\Console\Contract\CommandInterface; use Inhere\Console\Contract\ControllerInterface; use Inhere\Console\Contract\RouterInterface; -use Inhere\Console\Concern\NameAliasTrait; use Inhere\Console\Util\Helper; use InvalidArgumentException; +use Toolkit\Stdlib\Obj\Traits\NameAliasTrait; use function array_keys; use function array_merge; use function class_exists; diff --git a/test/Annotate/DocblockRulesTest.php b/test/Annotate/DocblockRulesTest.php new file mode 100644 index 00000000..487c51b4 --- /dev/null +++ b/test/Annotate/DocblockRulesTest.php @@ -0,0 +1,69 @@ +setDocTagsByDocblock($rftMth->getDocComment()); + $dr->parse(); + + $this->assertNotEmpty($dr->getTagValue('desc')); + $this->assertNotEmpty($dr->getTagValue('example')); + + $this->assertNotEmpty($dr->getArgRules()); + $this->assertNotEmpty($dr->getOptRules()); + } + + /** + */ + public function testParse_byDocComment(): void + { + $dr = DocblockRules::new(); + $dr->setDocTagsByDocblock(<<parse(); + + $this->assertNotEmpty($dr->getTagValue('desc')); + $this->assertNotEmpty($dr->getTagValue('example')); + + $this->assertNotEmpty($opts = $dr->getOptRules()); + $this->assertNotEmpty($args = $dr->getArgRules()); + + $this->assertArrayHasKey('-r, --remote', $opts); + $this->assertArrayHasKey('--main', $opts); + $this->assertArrayHasKey('repoPath', $args); + } +} From 3e19677bd6eb1225d4a4ada4595edad185daeb99 Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 16 Sep 2021 11:17:43 +0800 Subject: [PATCH 148/258] update soem for run group subcommand --- src/AbstractHandler.php | 2 ++ src/Command.php | 19 ++++++++++++++----- src/Controller.php | 12 ++++++++---- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 4a8d7d26..96d36561 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -295,6 +295,7 @@ public function run(array $args) $this->log(Console::VERB_DEBUG, "begin run '$name' - parse options", ['args' => $args]); // parse options + $this->flags->lock(); if (!$this->flags->parse($args)) { return 0; // on error, help } @@ -501,6 +502,7 @@ public function initParams(array $params): ConfigObject */ public function loadRulesByDocblock(string $method, FlagsParser $fs): void { + $this->debugf('not config flags, load flag rules by docblock, method: %s', $method); $rftMth = new ReflectionMethod($this, $method); // parse doc for get flag rules diff --git a/src/Command.php b/src/Command.php index 3f2cd016..72458f93 100644 --- a/src/Command.php +++ b/src/Command.php @@ -10,6 +10,7 @@ use Inhere\Console\Contract\CommandInterface; use Inhere\Console\IO\Input; +use ReflectionException; /** * Class Command @@ -35,6 +36,9 @@ abstract class Command extends AbstractHandler implements CommandInterface */ protected $parent; + /** + * @throws ReflectionException + */ protected function initForRun(Input $input): void { parent::initForRun($input); @@ -43,17 +47,22 @@ protected function initForRun(Input $input): void if ($this->compatible) { $this->flags->setSkipOnUndefined(true); } - } - protected function doRun(array $args) - { - $this->debugf('load configure for command: %s', self::getName()); + $this->debugf('load flags configure for command: %s', self::getName()); // load input definition configure $this->configure(); - parent::doRun($args); + // not config flags. load rules from method doc-comments + if ($this->flags->isEmpty()) { + $this->loadRulesByDocblock(self::METHOD, $this->flags); + } } + // protected function doRun(array $args) + // { + // parent::doRun($args); + // } + /* * Configure command */ diff --git a/src/Controller.php b/src/Controller.php index a4695f81..b6601ccf 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -38,6 +38,7 @@ use function substr; use function trim; use function ucfirst; +use function vdump; /** * Class Controller @@ -264,7 +265,7 @@ public function doRun(array $args) $command = $this->resolveAlias($command); // update the command id. - $this->input->setCommandId(static::getName() . ":$command"); + $this->input->setCommandId("$name:$command"); // convert 'boo-foo' to 'booFoo' $this->action = $action = Str::camelCase($command); @@ -287,12 +288,15 @@ public function doRun(array $args) $this->input->setFs($fs); } - $this->debugf('load configure for subcommand: %s', $command); - // load input definition configure + $this->debugf('load flags by configure method, subcommand: %s', $command); $this->configure(); - $this->log(Console::VERB_DEBUG, "run subcommand '$name.$command' - parse options", ['args' => $args]); + // not config flags. load rules from method doc-comments + if ($fs->isEmpty()) { + $this->loadRulesByDocblock($method, $fs); + } + $this->log(Console::VERB_DEBUG, "run subcommand '$name.$command' - parse options", ['args' => $args]); // parse subcommand flags. if (!$fs->parse($args)) { return 0; From 115a071b5166855de3df57c8eddc4fae2457ce94 Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 16 Sep 2021 11:56:43 +0800 Subject: [PATCH 149/258] fix parse method comments as flag rules --- src/Annotate/DocblockRules.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Annotate/DocblockRules.php b/src/Annotate/DocblockRules.php index a4758d7e..a6fb174d 100644 --- a/src/Annotate/DocblockRules.php +++ b/src/Annotate/DocblockRules.php @@ -145,7 +145,7 @@ protected function parseMultiLines(array $lines): array continue; } - $nodes = Str::explode($trimmed, ' ', 2); + $nodes = Str::explode($trimmed, ' ', 2); if (!isset($nodes[1])) { if ($index === 0) { // invalid first line continue; @@ -156,14 +156,18 @@ protected function parseMultiLines(array $lines): array continue; } - $name = $nodes[0]; + $name = trim($nodes[0], '.'); if (!preg_match('/^[\w ,-]{0,48}$/', $name)) { + if ($index === 0) { // invalid first line + continue; + } + // multi desc message. $rules[$index - 1][1] .= "\n" . $trimmed; continue; } - $rules[$index] = $nodes; + $rules[$index] = [$name, $nodes[1]]; $index++; } From 661a36b4fe5681395fef9f5f9bb78f31d9b3ed55 Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 16 Sep 2021 13:26:15 +0800 Subject: [PATCH 150/258] fix an error for parse method comments --- src/AbstractHandler.php | 94 ++++++---------------------------- src/Annotate/DocblockRules.php | 6 ++- 2 files changed, 19 insertions(+), 81 deletions(-) diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 96d36561..0c0a8c8f 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -23,23 +23,21 @@ use Inhere\Console\Util\FormatUtil; use Inhere\Console\Util\Helper; use InvalidArgumentException; -use ReflectionClass; -use ReflectionMethod; +use ReflectionException; use RuntimeException; use Swoole\Coroutine; use Swoole\Event; use Throwable; use Toolkit\PFlag\FlagsParser; use Toolkit\PFlag\SFlags; +use Toolkit\Stdlib\Helper\PhpHelper; use Toolkit\Stdlib\Obj\ConfigObject; use Toolkit\Stdlib\Util\PhpDoc; -use function array_keys; use function array_merge; use function cli_set_process_title; use function error_get_last; use function function_exists; use function implode; -use function is_array; use function is_string; use function preg_replace; use function sprintf; @@ -151,7 +149,7 @@ public static function aliases(): array /** * Command constructor. * - * @param Input $input + * @param Input $input * @param Output $output */ // TODO public function __construct(Input $input = null, Output $output = null, InputDefinition $definition = null) @@ -411,7 +409,7 @@ protected function beforeExecute(): bool /** * Do execute command * - * @param Input $input + * @param Input $input * @param Output $output * * @return int|mixed @@ -498,12 +496,12 @@ public function initParams(array $params): ConfigObject * @param string $method * @param FlagsParser $fs * - * @throws \ReflectionException + * @throws ReflectionException */ public function loadRulesByDocblock(string $method, FlagsParser $fs): void { $this->debugf('not config flags, load flag rules by docblock, method: %s', $method); - $rftMth = new ReflectionMethod($this, $method); + $rftMth = PhpHelper::reflectMethod($this, $method); // parse doc for get flag rules $dr = DocblockRules::newByDocblock($rftMth->getDocComment()); @@ -524,95 +522,33 @@ public function loadRulesByDocblock(string $method, FlagsParser $fs): void */ abstract protected function showHelp(): bool; - /** - * @param InputDefinition $definition - * @param array $aliases - */ - protected function showHelpByDefinition(InputDefinition $definition, array $aliases = []): void - { - $this->log(Console::VERB_DEBUG, 'display help information by input definition'); - - // if has InputDefinition object. (The comment of the command will not be parsed and used at this time.) - $help = $definition->getSynopsis(); - // parse example - $example = $help['example:']; - if (!empty($example)) { - if (is_string($example)) { - $help['example:'] = $this->parseCommentsVars($example); - } elseif (is_array($example)) { - foreach ($example as &$item) { - $item = $this->parseCommentsVars($item); - } - unset($item); - } else { - $help['example:'] = ''; - } - } - - $binName = $this->getScriptName(); - - // build usage - if ($this->isAttached()) { - $help['usage:'] = sprintf('%s %s %s', $binName, $this->getCommandName(), $help['usage:']); - } else { - $help['usage:'] = $binName . ' ' . $help['usage:']; - } - - // align global options - $help['global options:'] = FormatUtil::alignOptions(GlobalOption::getOptions()); - - $isAlone = $this->isAlone(); - if ($isAlone && empty($help[0])) { - $help[0] = self::getDescription(); - } - - if (empty($help[0])) { - $help[0] = 'No description message for the command'; - } - - // output description - $this->write(ucfirst($help[0]) . PHP_EOL); - - if ($aliases) { - $this->output->writef('Alias: %s', implode(',', $aliases)); - } - - unset($help[0]); - $this->output->mList($help, ['sepChar' => ' ']); - } - /** * Display command/action help by parse method annotations * * @param string $method * @param string $action action of an group - * @param array $aliases + * @param array $aliases * * @return int + * @throws ReflectionException */ protected function showHelpByAnnotations(string $method, string $action = '', array $aliases = []): int { - $ref = new ReflectionClass($this); $name = $this->input->getCommand(); - if (!$ref->hasMethod($method)) { - $subCmd = $this->input->getSubCommand(); - $this->write("The command '$subCmd' dont exist in the group: " . static::getName()); - return 0; - } - // subcommand: is a console controller subcommand - if ($action && !$ref->getMethod($method)->isPublic()) { + $rftMth = PhpHelper::reflectMethod($this, $method); + if ($action && !$rftMth->isPublic()) { $this->write("The command [$name] don't allow access in the class."); return 0; } - $allowedTags = array_keys(self::$annotationTags); + $allowedTags = DocblockRules::getAllowedTags(); $this->logf(Console::VERB_DEBUG, "render help for the command: %s", $this->input->getCommandId()); $help = []; - $doc = $ref->getMethod($method)->getDocComment(); - $tags = PhpDoc::getTags($this->parseCommentsVars((string)$doc), [ + $doc = $this->parseCommentsVars((string)$rftMth->getDocComment()); + $tags = PhpDoc::getTags($doc, [ 'allow' => $allowedTags, ]); @@ -631,7 +567,7 @@ protected function showHelpByAnnotations(string $method, string $action = '', ar } // is an command object - $isCommand = $ref->isSubclassOf(CommandInterface::class); + $isCommand = $this->isCommand(); foreach ($allowedTags as $tag) { if (empty($tags[$tag]) || !is_string($tags[$tag])) { // for alone command @@ -790,7 +726,7 @@ public static function addAnnotationTag(string $name): void /** * @param array $annotationTags - * @param bool $replace + * @param bool $replace */ public static function setAnnotationTags(array $annotationTags, $replace = false): void { diff --git a/src/Annotate/DocblockRules.php b/src/Annotate/DocblockRules.php index a6fb174d..89550698 100644 --- a/src/Annotate/DocblockRules.php +++ b/src/Annotate/DocblockRules.php @@ -191,11 +191,13 @@ public function getTagValue(string $tag): string } /** + * @param bool $onlyName + * * @return array */ - public static function getAllowedTags(): array + public static function getAllowedTags(bool $onlyName = true): array { - return array_keys(self::$allowedTags); + return $onlyName ? array_keys(self::$allowedTags) : self::$allowedTags; } /** From 391938b48331c5c10fae5c11dae1b2bfd790ff1d Mon Sep 17 00:00:00 2001 From: inhere Date: Fri, 17 Sep 2021 14:28:24 +0800 Subject: [PATCH 151/258] refeator: command and subcommand help render by flags parser --- .github/workflows/php.yml | 2 +- phpunit.xml | 16 +- src/AbstractHandler.php | 147 ++----------------- src/Annotate/DocblockRules.php | 21 +++ src/Command.php | 37 +++-- src/Concern/ApplicationHelpTrait.php | 4 +- src/Concern/CommandHelpTrait.php | 177 ++++++++++++++++++++++- src/Concern/ControllerHelpTrait.php | 8 +- src/Contract/CommandHandlerInterface.php | 15 ++ src/Contract/ControllerInterface.php | 5 + src/Controller.php | 10 +- 11 files changed, 280 insertions(+), 162 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 85f5505a..0bdf5442 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -53,4 +53,4 @@ jobs: # Docs: https://getcomposer.org/doc/articles/scripts.md - name: Run unit tests - run: phpunit -vv + run: phpunit -v --debug diff --git a/phpunit.xml b/phpunit.xml index cd3f80d8..d615c158 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,23 +1,25 @@ - - + test - - + + src - - + + diff --git a/src/AbstractHandler.php b/src/AbstractHandler.php index 0c0a8c8f..d3d476fe 100644 --- a/src/AbstractHandler.php +++ b/src/AbstractHandler.php @@ -20,7 +20,6 @@ use Inhere\Console\IO\Input; use Inhere\Console\IO\InputDefinition; use Inhere\Console\IO\Output; -use Inhere\Console\Util\FormatUtil; use Inhere\Console\Util\Helper; use InvalidArgumentException; use ReflectionException; @@ -32,17 +31,10 @@ use Toolkit\PFlag\SFlags; use Toolkit\Stdlib\Helper\PhpHelper; use Toolkit\Stdlib\Obj\ConfigObject; -use Toolkit\Stdlib\Util\PhpDoc; use function array_merge; use function cli_set_process_title; use function error_get_last; use function function_exists; -use function implode; -use function is_string; -use function preg_replace; -use function sprintf; -use function ucfirst; -use const PHP_EOL; use const PHP_OS; /** @@ -252,8 +244,8 @@ protected function annotationVars(): array protected function initForRun(Input $input): void { $input->setFs($this->flags); + $this->flags->setDesc(self::getDesc()); $this->flags->setScriptName(self::getName()); - $this->flags->setDesc(self::getDescription()); // load built in options // $builtInOpts = GlobalOption::getAloneOptions(); @@ -507,6 +499,7 @@ public function loadRulesByDocblock(string $method, FlagsParser $fs): void $dr = DocblockRules::newByDocblock($rftMth->getDocComment()); $dr->parse(); + $fs->setDesc($dr->getTagValue('desc') ?: self::getDesc()); $fs->addArgsByRules($dr->getArgRules()); $fs->addOptsByRules($dr->getOptRules()); } @@ -522,111 +515,26 @@ public function loadRulesByDocblock(string $method, FlagsParser $fs): void */ abstract protected function showHelp(): bool; + /************************************************************************** + * getter/setter methods + **************************************************************************/ + /** - * Display command/action help by parse method annotations - * - * @param string $method - * @param string $action action of an group - * @param array $aliases - * - * @return int - * @throws ReflectionException + * @return string */ - protected function showHelpByAnnotations(string $method, string $action = '', array $aliases = []): int + public function getGroupName(): string { - $name = $this->input->getCommand(); - - // subcommand: is a console controller subcommand - $rftMth = PhpHelper::reflectMethod($this, $method); - if ($action && !$rftMth->isPublic()) { - $this->write("The command [$name] don't allow access in the class."); - return 0; - } - - $allowedTags = DocblockRules::getAllowedTags(); - $this->logf(Console::VERB_DEBUG, "render help for the command: %s", $this->input->getCommandId()); - - $help = []; - $doc = $this->parseCommentsVars((string)$rftMth->getDocComment()); - $tags = PhpDoc::getTags($doc, [ - 'allow' => $allowedTags, - ]); - - if ($aliases) { - $realName = $action ?: static::getName(); - // command name - $help['Command:'] = sprintf('%s(alias: %s)', $realName, implode(',', $aliases)); - } - - $binName = $this->input->getBinName(); - - $path = $binName . ' ' . $name; - if ($action) { - $group = static::getName(); - $path = "$binName $group $action"; - } - - // is an command object - $isCommand = $this->isCommand(); - foreach ($allowedTags as $tag) { - if (empty($tags[$tag]) || !is_string($tags[$tag])) { - // for alone command - if ($tag === 'description' && $isCommand) { - $help['Description:'] = static::getDesc(); - continue; - } - - if ($tag === 'usage') { - $help['Usage:'] = "$path [--options ...] [arguments ...]"; - } - - continue; - } - - // $msg = trim($tags[$tag]); - $message = $tags[$tag]; - $labelName = ucfirst($tag) . ':'; - - // for alone command - if ($tag === 'description' && $isCommand) { - $message = static::getDescription(); - } else { - $message = preg_replace('#(\n)#', '$1 ', $message); - } - - $help[$labelName] = $message; - } - - if (isset($help['Description:'])) { - $description = $help['Description:'] ?: 'No description message for the command'; - $this->write(ucfirst($this->parseCommentsVars($description)) . PHP_EOL); - unset($help['Description:']); - } - - $help['Group Options:'] = null; - - $this->beforeRenderCommandHelp($help); - - if ($app = $this->getApp()) { - $help['Global Options:'] = FormatUtil::alignOptions($app->getFlags()->getOptSimpleDefines()); - } - - $this->output->mList($help, [ - 'sepChar' => ' ', - 'lastNewline' => 0, - ]); - - return 0; + return ''; } - protected function beforeRenderCommandHelp(array &$help): void + /** + * @return string + */ + public function getRealName(): string { + return self::getName(); } - /************************************************************************** - * getter/setter methods - **************************************************************************/ - /** * @return array */ @@ -706,33 +614,6 @@ public static function setCoroutine($coroutine): void static::$coroutine = (bool)$coroutine; } - /** - * @return array - */ - final public static function getAnnotationTags(): array - { - return self::$annotationTags; - } - - /** - * @param string $name - */ - public static function addAnnotationTag(string $name): void - { - if (!isset(self::$annotationTags[$name])) { - self::$annotationTags[$name] = true; - } - } - - /** - * @param array $annotationTags - * @param bool $replace - */ - public static function setAnnotationTags(array $annotationTags, $replace = false): void - { - self::$annotationTags = $replace ? $annotationTags : array_merge(self::$annotationTags, $annotationTags); - } - /** * @return string */ diff --git a/src/Annotate/DocblockRules.php b/src/Annotate/DocblockRules.php index 89550698..59911e29 100644 --- a/src/Annotate/DocblockRules.php +++ b/src/Annotate/DocblockRules.php @@ -5,6 +5,7 @@ use Toolkit\Stdlib\Str; use Toolkit\Stdlib\Util\PhpDoc; use function array_keys; +use function array_merge; use function explode; use function preg_match; use function trim; @@ -200,6 +201,26 @@ public static function getAllowedTags(bool $onlyName = true): array return $onlyName ? array_keys(self::$allowedTags) : self::$allowedTags; } + + /** + * @param string $name + */ + public static function addAllowedTag(string $name): void + { + if (!isset(self::$allowedTags[$name])) { + self::$allowedTags[$name] = true; + } + } + + /** + * @param array $allowedTags + * @param bool $replace + */ + public static function setAllowedTags(array $allowedTags, bool $replace = false): void + { + self::$allowedTags = $replace ? $allowedTags : array_merge(self::$allowedTags, $allowedTags); + } + /** * @return array */ diff --git a/src/Command.php b/src/Command.php index 72458f93..f6d86f5a 100644 --- a/src/Command.php +++ b/src/Command.php @@ -36,6 +36,18 @@ abstract class Command extends AbstractHandler implements CommandInterface */ protected $parent; + /** + * @var string + */ + protected $commandName = ''; + + protected function init(): void + { + $this->commandName = self::getName(); + + parent::init(); + } + /** * @throws ReflectionException */ @@ -43,6 +55,7 @@ protected function initForRun(Input $input): void { parent::initForRun($input); + $this->flags->setStopOnFistArg(false); // old mode: options and arguments at method annotations if ($this->compatible) { $this->flags->setSkipOnUndefined(true); @@ -111,20 +124,18 @@ protected function showHelp(): bool { $aliases = $this->getAliases(); - // render help by input definition. - // if ($definition = $this->getDefinition()) { - // $this->showHelpByDefinition($definition, $aliases); - // return true; - // } + $this->logf(Console::VERB_CRAZY, "display help info for the command: %s", $this->commandName); - // TODO show help by flags - // if ($this->flags->isNotEmpty()) { - // } - - $execMethod = self::METHOD; - - $this->logf(Console::VERB_CRAZY, "display help info for the command: %s", self::getName()); + // $execMethod = self::METHOD; + // return $this->showHelpByAnnotations($execMethod, '', $aliases) !== 0; + return $this->showHelpByFlagsParser($this->flags, $aliases) !== 0; + } - return $this->showHelpByAnnotations($execMethod, '', $aliases) !== 0; + /** + * @return string + */ + public function getCommandName(): string + { + return $this->commandName; } } diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index 5ab232c1..7ccdb259 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -131,7 +131,7 @@ public function showHelpInfo(string $command = ''): void // built in options // $globalOptions = self::$globalOptions; - $globalOptions = $this->flags->getOptSimpleDefines(); + $globalOptions = $this->flags->getOptsHelpData(); // append generate options: // php examples/app --auto-completion --shell-env zsh --gen-file // php examples/app --auto-completion --shell-env zsh --gen-file stdout @@ -286,7 +286,7 @@ public function showCommandList(): void // built in options // $globOpts = self::$globalOptions; - $globOpts = $this->flags->getOptSimpleDefines(); + $globOpts = $this->flags->getOptsHelpData(); Show::mList([ 'Usage:' => "$scriptName {COMMAND} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", diff --git a/src/Concern/CommandHelpTrait.php b/src/Concern/CommandHelpTrait.php index 57e74dfa..d34ea5b3 100644 --- a/src/Concern/CommandHelpTrait.php +++ b/src/Concern/CommandHelpTrait.php @@ -3,8 +3,21 @@ namespace Inhere\Console\Concern; use Inhere\Console\AbstractHandler; +use Inhere\Console\Annotate\DocblockRules; +use Inhere\Console\Console; +use Inhere\Console\Util\FormatUtil; +use ReflectionException; +use Toolkit\PFlag\FlagsParser; +use Toolkit\Stdlib\Helper\PhpHelper; +use Toolkit\Stdlib\Util\PhpDoc; +use function implode; +use function is_string; +use function preg_replace; +use function sprintf; use function strpos; use function strtr; +use function ucfirst; +use const PHP_EOL; /** * Trait CommandHelpTrait @@ -36,7 +49,7 @@ public function setCommentsVars(array $commentsVars): void } /** - * @param string $name + * @param string $name * @param string|array $value */ protected function addCommentsVar(string $name, $value): void @@ -57,7 +70,7 @@ protected function addCommentsVars(array $map): void } /** - * @param string $name + * @param string $name * @param string|array $value */ protected function setCommentsVar(string $name, $value): void @@ -91,4 +104,164 @@ public function parseCommentsVars(string $str): string return $map ? strtr($str, $map) : $str; } + + /** + * @param FlagsParser $fs + * @param string $action + * @param array $aliases + * + * @return int + */ + public function showHelpByFlagsParser(FlagsParser $fs, array $aliases = [], string $action = ''): int + { + $help = []; + $name = $this->getCommandName(); + + // $isCommand = $this->isCommand(); + $commandId = $this->input->getCommandId(); + $this->logf(Console::VERB_DEBUG, "render help for the command: %s", $commandId); + + if ($aliases) { + $realName = $action ?: $this->getRealName(); + // command name + $help['Command:'] = sprintf('%s(alias: %s)', $realName, implode(',', $aliases)); + } + + $binName = $this->input->getBinName(); + + $path = $binName . ' ' . $name; + if ($action) { + $group = $this->getGroupName(); + $path = "$binName $group $action"; + } + + $desc = $fs->getDesc(); + $this->writeln(ucfirst($this->parseCommentsVars($desc))); + + $help['Usage:'] = "$path [--options ...] [arguments ...]"; + + $help['Options:'] = FormatUtil::alignOptions($fs->getOptsHelpData()); + $help['Argument:'] = $fs->getArgsHelpData(); + $help['Example:'] = $fs->getExampleHelp(); + + $help['More Help:'] = $fs->getMoreHelp(); + + // no group options. only set key position. + $help['Group Options:'] = null; + $this->beforeRenderCommandHelp($help); + + // attached to console app + if ($app = $this->getApp()) { + $help['Global Options:'] = FormatUtil::alignOptions($app->getFlags()->getOptsHelpData()); + } + + $this->output->mList($help, [ + 'sepChar' => ' ', + 'lastNewline' => false, + ]); + + return 0; + } + + /** + * Display command/action help by parse method annotations + * + * @param string $method + * @param string $action action of an group + * @param array $aliases + * + * @return int + * @throws ReflectionException + */ + protected function showHelpByAnnotations(string $method, string $action = '', array $aliases = []): int + { + $name = $this->input->getCommand(); + + // subcommand: is a console controller subcommand + $rftMth = PhpHelper::reflectMethod($this, $method); + if ($action && !$rftMth->isPublic()) { + $this->write("The command [$name] don't allow access in the class."); + return 0; + } + + $allowedTags = DocblockRules::getAllowedTags(); + $this->logf(Console::VERB_DEBUG, "render help for the command: %s", $this->input->getCommandId()); + + $help = []; + $doc = $this->parseCommentsVars((string)$rftMth->getDocComment()); + $tags = PhpDoc::getTags($doc, [ + 'allow' => $allowedTags, + ]); + + if ($aliases) { + $realName = $action ?: static::getName(); + // command name + $help['Command:'] = sprintf('%s(alias: %s)', $realName, implode(',', $aliases)); + } + + $binName = $this->input->getBinName(); + + $path = $binName . ' ' . $name; + if ($action) { + $group = static::getName(); + $path = "$binName $group $action"; + } + + // is an command object + $isCommand = $this->isCommand(); + foreach ($allowedTags as $tag) { + if (empty($tags[$tag]) || !is_string($tags[$tag])) { + // for alone command + if ($tag === 'description' && $isCommand) { + $help['Description:'] = static::getDesc(); + continue; + } + + if ($tag === 'usage') { + $help['Usage:'] = "$path [--options ...] [arguments ...]"; + } + continue; + } + + // $msg = trim($tags[$tag]); + $message = $tags[$tag]; + $labelName = ucfirst($tag) . ':'; + + // for alone command + if ($tag === 'description' && $isCommand) { + $message = static::getDescription(); + } else { + $message = preg_replace('#(\n)#', '$1 ', $message); + } + + $help[$labelName] = $message; + } + + if (isset($help['Description:'])) { + $description = $help['Description:'] ?: 'No description message for the command'; + $this->write(ucfirst($this->parseCommentsVars($description)) . PHP_EOL); + unset($help['Description:']); + } + + $help['Group Options:'] = null; + $this->beforeRenderCommandHelp($help); + + if ($app = $this->getApp()) { + $help['Global Options:'] = FormatUtil::alignOptions($app->getFlags()->getOptsHelpData()); + } + + $this->output->mList($help, [ + 'sepChar' => ' ', + 'lastNewline' => 0, + ]); + + return 0; + } + + /** + * @param array $help + */ + protected function beforeRenderCommandHelp(array &$help): void + { + } } diff --git a/src/Concern/ControllerHelpTrait.php b/src/Concern/ControllerHelpTrait.php index 03c6eae4..c8f2204a 100644 --- a/src/Concern/ControllerHelpTrait.php +++ b/src/Concern/ControllerHelpTrait.php @@ -32,6 +32,7 @@ trait ControllerHelpTrait * -s, --search Search command by input keywords * --format Set the help information dump format(raw, xml, json, markdown) * @return int + * @throws \ReflectionException * @example * {script} {name} -h * {script} {name}:help @@ -64,12 +65,13 @@ public function helpCommand(): int } $this->log(Console::VERB_DEBUG, "display help for the controller method: $method", [ - 'group' => static::$name, + 'group' => $this->getGroupName(), 'action' => $action, ]); // For a specified sub-command. - return $this->showHelpByAnnotations($method, $action, $aliases); + // return $this->showHelpByAnnotations($method, $action, $aliases); + return $this->showHelpByFlagsParser($this->curActionFlags(), $aliases, $action); } protected function beforeShowCommandList(): void @@ -156,7 +158,7 @@ public function showCommandList(): void $globalOptions = static::$globalOptions; if ($app = $this->getApp()) { $globalOptions = array_merge( - $app->getFlags()->getOptSimpleDefines(), + $app->getFlags()->getOptsHelpData(), static::$globalOptions ); } diff --git a/src/Contract/CommandHandlerInterface.php b/src/Contract/CommandHandlerInterface.php index 6dcae395..96740a78 100644 --- a/src/Contract/CommandHandlerInterface.php +++ b/src/Contract/CommandHandlerInterface.php @@ -44,6 +44,21 @@ public function run(array $args); */ public function getApp(): AbstractApplication; + /** + * @return string + */ + public function getGroupName(): string; + + /** + * @return string + */ + public function getRealName(): string; + + /** + * @return string + */ + public function getCommandName(): string; + /** * @return string */ diff --git a/src/Contract/ControllerInterface.php b/src/Contract/ControllerInterface.php index db12f5f0..0ea150ea 100644 --- a/src/Contract/ControllerInterface.php +++ b/src/Contract/ControllerInterface.php @@ -36,6 +36,11 @@ public function showCommandList(); */ public function getAction(): string; + /** + * @return string + */ + public function getGroupName(): string; + /** * @return string */ diff --git a/src/Controller.php b/src/Controller.php index b6601ccf..418bd76f 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -505,7 +505,7 @@ protected function showHelp(): bool */ protected function beforeRenderCommandHelp(array &$help): void { - $help['Group Options:'] = FormatUtil::alignOptions($this->flags->getOptSimpleDefines()); + $help['Group Options:'] = FormatUtil::alignOptions($this->flags->getOptsHelpData()); } /** @@ -563,6 +563,14 @@ public function resolveAlias(string $alias): string return $map[$alias] ?? $alias; } + /** + * @return string + */ + public function getGroupName(): string + { + return self::getName(); + } + /** * @param string $name * From 496bb5cfde5a5068200c2274983dd0f9218b64f1 Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 18 Sep 2021 13:37:54 +0800 Subject: [PATCH 152/258] remove some old methods, update some method param define. --- examples/Command/CorCommand.php | 2 +- examples/Controller/HomeController.php | 48 ++++++++------ examples/Controller/ShowController.php | 14 +++-- src/BuiltIn/SelfUpdateCommand.php | 2 +- src/Command.php | 54 ++++++++-------- src/Component/ConsoleAppIShell.php | 4 +- src/Concern/ApplicationHelpTrait.php | 2 +- src/Concern/CommandHelpTrait.php | 2 +- src/Concern/ControllerHelpTrait.php | 6 +- src/Concern/InputFlagsWareTrait.php | 13 ---- src/Concern/InputOutputAwareTrait.php | 79 ------------------------ src/Contract/CommandHandlerInterface.php | 3 +- src/Controller.php | 52 ++++++++++------ src/GlobalOption.php | 18 ++---- src/{ => Handler}/AbstractHandler.php | 69 +++++++++++---------- src/Handler/CallableCommand.php | 2 +- src/Router.php | 14 ++--- test/TestCommand.php | 2 +- 18 files changed, 158 insertions(+), 228 deletions(-) delete mode 100644 src/Concern/InputFlagsWareTrait.php rename src/{ => Handler}/AbstractHandler.php (92%) diff --git a/examples/Command/CorCommand.php b/examples/Command/CorCommand.php index 166817bf..2a377929 100644 --- a/examples/Command/CorCommand.php +++ b/examples/Command/CorCommand.php @@ -38,7 +38,7 @@ public static function aliases(): array * @param Input $input * @param Output $output */ - protected function execute($input, $output) + protected function execute(Input $input, Output $output) { $output->aList([ 'support coroutine?' => Helper::isSupportCoroutine() ? 'Y' : 'N', diff --git a/examples/Controller/HomeController.php b/examples/Controller/HomeController.php index 4770411a..494dd7ce 100644 --- a/examples/Controller/HomeController.php +++ b/examples/Controller/HomeController.php @@ -11,7 +11,8 @@ use LogicException; use RuntimeException; use Toolkit\Cli\Cli; -use Toolkit\Cli\Download; +use Toolkit\Cli\Util\Download; +use Toolkit\PFlag\FlagsParser; use Toolkit\Stdlib\Php; use function sleep; use function trigger_error; @@ -114,7 +115,7 @@ public function disabledCommand(): void */ protected function defArgConfigure(): void { - $fs = $this->newActionFlags(); + $fs = $this->curActionFlags(); $fs->addOptByRule('yes,y', "bool;description for the option 'yes'"); $fs->addOptByRule('opt1', "bool;description for the option 'opt1'"); @@ -311,29 +312,29 @@ public function dynamicTextCommand(): void * a progress bar example show, by Show::progressBar() * * @options - * --type the progress type, allow: bar,txt. txt - * --done-char the done show char. = - * --wait-char the waiting show char. - - * --sign-char the sign char show. > + * --type the progress type, allow: bar,txt. txt + * --done-char the done show char. = + * --wait-char the waiting show char. - + * --sign-char the sign char show. > * - * @param Input $input + * @param FlagsParser $fs * * @return int * @example * {script} {command} * {script} {command} --done-char '#' --wait-char ' ' */ - public function progressCommand($input): int + public function progressCommand(FlagsParser $fs): int { $i = 0; $total = 120; - if ($input->getOpt('type') === 'bar') { + if ($fs->getOpt('type') === 'bar') { $bar = $this->output->progressBar($total, [ 'msg' => 'Msg Text', 'doneMsg' => 'Done Msg Text', - 'doneChar' => $input->getOpt('done-char', '='), // ▓ - 'waitChar' => $input->getOpt('wait-char', '-'), // ░ - 'signChar' => $input->getOpt('sign-char', '>'), + 'doneChar' => $fs->getOpt('done-char', '='), // ▓ + 'waitChar' => $fs->getOpt('wait-char', '-'), // ░ + 'signChar' => $fs->getOpt('sign-char', '>'), ]); } else { $bar = $this->output->progressTxt($total, 'Doing go g...', 'Done'); @@ -496,6 +497,7 @@ public function paddingCommand(): void * a example for use arguments on command * * @usage home:useArg [arg1=val1 arg2=arg2] [options] + * * @example * home:useArg status=2 name=john arg0 -s=test --page=23 -d -rf --debug --test=false -a v1 --ab -c -g --cd val -h '' -i stat=online * home:useArg status=2 name=john name=tom name=jack arg0 -s=test --page=23 --id=23 --id=154 --id=456 -d -rf --debug --test=false @@ -533,20 +535,28 @@ public function envCommand(): void /** * This is a demo for download a file to local - * @usage {command} url=url saveTo=[saveAs] type=[bar|text] + * + * @usage {command} url=url saveTo=[saveAs] --type=[bar|text] + * + * @options + * --type The progress bar type. allow: bar, text(default) + * + * @arguments + * url string;The remote file url;required + * saveAs The local file path for save download file. * * @example {command} url=https://github.com/inhere/php-console/archive/master.zip type=bar */ - public function downCommand(): int + public function downCommand(FlagsParser $fs): int { - $url = $this->input->getArg('url'); - + $url = $fs->getArg('url'); if (!$url) { - $this->output->liteError('Please input you want to downloaded file url, use: url=[url]', 1); + $this->output->liteError('Please input the downloaded file url'); + return 1; } - $saveAs = $this->input->getArg('saveAs'); - $type = $this->input->getArg('type', 'text'); + $saveAs = $fs->getArg('saveAs'); + $type = $fs->getOpt('type', 'text'); if (!$saveAs) { $saveAs = __DIR__ . '/' . basename($url); diff --git a/examples/Controller/ShowController.php b/examples/Controller/ShowController.php index 1730bd8d..dad3461c 100644 --- a/examples/Controller/ShowController.php +++ b/examples/Controller/ShowController.php @@ -18,6 +18,7 @@ use ReflectionException; use Toolkit\Cli\Color; use Toolkit\Cli\Highlighter; +use Toolkit\PFlag\FlagsParser; use function file_get_contents; /** @@ -61,14 +62,17 @@ public function titleCommand(): int /** * output format message: splitLine + * * @options - * -w, --width WIDTH The split line width. default is current screen width. + * -w, --width int;The split line width. default is current screen width. */ - public function splitLineCommand(): int + public function splitLineCommand(FlagsParser $fs): int { - $this->output->splitLine('', '=', $this->getSameOpt(['w', 'width'], 0)); - $this->output->splitLine('split Line', '-', $this->getSameOpt(['w', 'width'], 0)); - $this->output->splitLine('split 中文 Line', '-', $this->getSameOpt(['w', 'width'], 0)); + $width = $fs->getOpt('width'); + + $this->output->splitLine('', '=', $width); + $this->output->splitLine('split Line', '-', $width); + $this->output->splitLine('split 中文 Line', '-', $width); return 0; } diff --git a/src/BuiltIn/SelfUpdateCommand.php b/src/BuiltIn/SelfUpdateCommand.php index 5191186a..2dc7d9aa 100644 --- a/src/BuiltIn/SelfUpdateCommand.php +++ b/src/BuiltIn/SelfUpdateCommand.php @@ -49,7 +49,7 @@ class SelfUpdateCommand extends Command * @param Input $input * @param Output $output */ - protected function execute($input, $output) + protected function execute(Input $input, Output $output) { $this->version = $this->getApp()->getVersion(); $parser = new VersionParser; diff --git a/src/Command.php b/src/Command.php index f6d86f5a..6ce2001e 100644 --- a/src/Command.php +++ b/src/Command.php @@ -9,8 +9,9 @@ namespace Inhere\Console; use Inhere\Console\Contract\CommandInterface; -use Inhere\Console\IO\Input; +use Inhere\Console\Handler\AbstractHandler; use ReflectionException; +use Toolkit\PFlag\FlagsParser; /** * Class Command @@ -20,7 +21,7 @@ * ```php * class MyCommand extends Command * { - * protected function execute($input, $output) + * protected function execute(Input $input, Output $output) * { * // some logic ... * } @@ -49,44 +50,39 @@ protected function init(): void } /** - * @throws ReflectionException + * @param FlagsParser $fs */ - protected function initForRun(Input $input): void + protected function beforeInitFlagsParser(FlagsParser $fs): void { - parent::initForRun($input); + $fs->setStopOnFistArg(false); - $this->flags->setStopOnFistArg(false); // old mode: options and arguments at method annotations if ($this->compatible) { - $this->flags->setSkipOnUndefined(true); + $fs->setSkipOnUndefined(true); } + } - $this->debugf('load flags configure for command: %s', self::getName()); - // load input definition configure + /** + * @param FlagsParser $fs + * + * @throws ReflectionException + */ + protected function afterInitFlagsParser(FlagsParser $fs): void + { + $this->debugf('load flags configure for command: %s', $this->getRealName()); $this->configure(); + $isEmpty = $this->flags->isEmpty(); + + // load built in options + $fs->addOptsByRules(GlobalOption::getAloneOptions()); + // not config flags. load rules from method doc-comments - if ($this->flags->isEmpty()) { - $this->loadRulesByDocblock(self::METHOD, $this->flags); + if ($isEmpty) { + $this->loadRulesByDocblock(self::METHOD, $fs); } } - // protected function doRun(array $args) - // { - // parent::doRun($args); - // } - - /* - * Configure command - */ - // protected function configure() - // { - // $this - // ->createDefinition() - // ->addArgument('test') - // ->addOption('test'); - // } - /** * @param Command $parent */ @@ -98,10 +94,10 @@ public function setParent(Command $parent): void /** * @return $this */ - public function getRootCommand(): Command + public function getRoot(): Command { if ($this->parent) { - return $this->parent->getRootCommand(); + return $this->parent->getRoot(); } return $this; diff --git a/src/Component/ConsoleAppIShell.php b/src/Component/ConsoleAppIShell.php index 49bb0c99..01457f00 100644 --- a/src/Component/ConsoleAppIShell.php +++ b/src/Component/ConsoleAppIShell.php @@ -3,14 +3,14 @@ namespace Inhere\Console\Component; use Inhere\Console\Application; -use Toolkit\Stdlib\Obj\AbstractObj; +use Inhere\Console\Component\Interact\IShell; /** * Class ConsoleAppIShell * * @package Inhere\Console */ -class ConsoleAppIShell extends AbstractObj +class ConsoleAppIShell extends IShell { /** * @var Application diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index 7ccdb259..a65caf41 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -8,7 +8,7 @@ namespace Inhere\Console\Concern; -use Inhere\Console\AbstractHandler; +use Inhere\Console\Handler\AbstractHandler; use Inhere\Console\Console; use Inhere\Console\ConsoleEvent; use Inhere\Console\Contract\CommandInterface; diff --git a/src/Concern/CommandHelpTrait.php b/src/Concern/CommandHelpTrait.php index d34ea5b3..564016c5 100644 --- a/src/Concern/CommandHelpTrait.php +++ b/src/Concern/CommandHelpTrait.php @@ -2,7 +2,7 @@ namespace Inhere\Console\Concern; -use Inhere\Console\AbstractHandler; +use Inhere\Console\Handler\AbstractHandler; use Inhere\Console\Annotate\DocblockRules; use Inhere\Console\Console; use Inhere\Console\Util\FormatUtil; diff --git a/src/Concern/ControllerHelpTrait.php b/src/Concern/ControllerHelpTrait.php index c8f2204a..5e305f98 100644 --- a/src/Concern/ControllerHelpTrait.php +++ b/src/Concern/ControllerHelpTrait.php @@ -3,6 +3,7 @@ namespace Inhere\Console\Concern; use Inhere\Console\Console; +use Inhere\Console\GlobalOption; use Inhere\Console\Util\FormatUtil; use ReflectionClass; use Toolkit\Cli\ColorTag; @@ -32,7 +33,6 @@ trait ControllerHelpTrait * -s, --search Search command by input keywords * --format Set the help information dump format(raw, xml, json, markdown) * @return int - * @throws \ReflectionException * @example * {script} {name} -h * {script} {name}:help @@ -96,7 +96,7 @@ public function showCommandList(): void } $commands = []; - $showDisabled = (bool)$this->getOpt('show-disabled', false); + $showDisabled = $this->flags->getOpt(GlobalOption::SHOW_DISABLED, false); $defaultDes = 'No description message'; /** @@ -144,7 +144,7 @@ public function showCommandList(): void // if is alone running. if ($detached = $this->isDetached()) { - $name = $sName . ' '; + // $name = $sName . ' '; $usage = "$script COMMAND [--options ...] [arguments ...]"; } else { $name = $sName . $this->delimiter; diff --git a/src/Concern/InputFlagsWareTrait.php b/src/Concern/InputFlagsWareTrait.php deleted file mode 100644 index e9709a6d..00000000 --- a/src/Concern/InputFlagsWareTrait.php +++ /dev/null @@ -1,13 +0,0 @@ -input->getScriptName(); } - /** - * @param int|string $name - * @param mixed $default - * - * @return mixed|null - * @see Input::getArg() - */ - public function getArg($name, $default = null) - { - return $this->input->getArg($name, $default); - } - - /** - * @param string $default - * - * @return string - * @see Input::getFirstArg() - */ - public function getFirstArg(string $default = ''): string - { - return $this->input->getFirstArg($default); - } - - /** - * @param int|string $name - * - * @return mixed - * @see Input::getRequiredArg() - */ - public function getRequiredArg($name) - { - return $this->input->getRequiredArg($name); - } - - /** - * @param string|array $names - * @param mixed $default - * - * @return bool|mixed|null - * @see Input::getSameArg() - */ - public function getSameArg($names, $default = null) - { - return $this->input->getSameArg($names, $default); - } - - /** - * @param int|string $name - * @param mixed $default - * - * @return mixed - */ - public function getOpt($name, $default = null) - { - return $this->input->getOpt($name, $default); - } - - /** - * @param string|string[] $names eg 'n,name' OR ['n', 'name'] - * @param mixed $default - * - * @return mixed - */ - public function getSameOpt($names, $default = null) - { - return $this->input->getSameOpt($names, $default); - } - - /** - * @param string $name - * @param string $errMsg - * - * @return mixed - */ - public function getRequiredOpt(string $name, string $errMsg = '') - { - return $this->input->getRequiredOpt($name, $errMsg); - } - /** * @param string $question * @param bool $nl diff --git a/src/Contract/CommandHandlerInterface.php b/src/Contract/CommandHandlerInterface.php index 96740a78..3b053c4c 100644 --- a/src/Contract/CommandHandlerInterface.php +++ b/src/Contract/CommandHandlerInterface.php @@ -9,7 +9,6 @@ namespace Inhere\Console\Contract; use Inhere\Console\AbstractApplication; -use Inhere\Console\IO\InputDefinition; /** * Interface CommandHandlerInterface @@ -50,6 +49,8 @@ public function getApp(): AbstractApplication; public function getGroupName(): string; /** + * Alias of the getName() + * * @return string */ public function getRealName(): string; diff --git a/src/Controller.php b/src/Controller.php index 418bd76f..c6115385 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -12,6 +12,7 @@ use Inhere\Console\Concern\ControllerHelpTrait; use Inhere\Console\Contract\ControllerInterface; use Inhere\Console\Exception\ConsoleException; +use Inhere\Console\Handler\AbstractHandler; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; use Inhere\Console\Util\FormatUtil; @@ -38,7 +39,6 @@ use function substr; use function trim; use function ucfirst; -use function vdump; /** * Class Controller @@ -213,9 +213,36 @@ protected function onNotFound(string $action, array $args): bool return false; } - protected function getBuiltInOptions(): array + /** + * @param FlagsParser $fs + */ + protected function afterInitFlagsParser(FlagsParser $fs): void { - return GlobalOption::getGroupOptions(); + $fs->addOptsByRules(GlobalOption::getGroupOptions()); + } + + /** + * Run an action with args + * + * Usage: + * + * ```php + * $args = $this->flags->getRawArgs(); + * // add option + * $args[] = '--push'; + * $this->runActionWithArgs('subcmd', $args); + * ``` + * + * @param string $cmd + * @param array $args + * + * @return bool|int|mixed + * @throws Throwable + */ + public function runActionWithArgs(string $cmd, array $args) + { + $args[0] = $cmd; + return $this->doRun($args); } protected function beforeRun(): void @@ -345,7 +372,7 @@ protected function afterAction(): void * @return mixed * @throws ReflectionException */ - final public function execute($input, $output) + final public function execute(Input $input, Output $output) { $action = $this->action; $group = static::getName(); @@ -447,7 +474,7 @@ protected function newActionFlags(string $action = ''): FlagsParser $action = $action ?: $this->action; if (!$fs = $this->getActionFlags($action)) { $fs = new SFlags(['name' => $action]); - // $fs->setStopOnFistArg(false); + $fs->setStopOnFistArg(false); $fs->setBeforePrintHelp(function (string $text) { return $this->parseCommentsVars($text); }); @@ -480,23 +507,10 @@ protected function getMethodName(string $action): string /** * @return bool + * @throws ReflectionException */ protected function showHelp(): bool { - // render help by Definition - // if ($definition = $this->getDefinition()) { - // if ($action = $this->action) { - // $aliases = $this->getCommandAliases($action); - // } else { - // $aliases = $this->getAliases(); - // } - // - // $this->showHelpByDefinition($definition, $aliases); - // return true; - // } - - // TODO show help by flags - return $this->helpCommand() === 0; } diff --git a/src/GlobalOption.php b/src/GlobalOption.php index accb1169..98ad71fd 100644 --- a/src/GlobalOption.php +++ b/src/GlobalOption.php @@ -26,10 +26,6 @@ class GlobalOption public const NO_INTERACTIVE = 'no-interactive'; - public const HELP_OPTS = ['h', 'help']; - - public const VERSION_OPTS = ['V', 'version']; - public const KEY_MAP = [ 'debug' => 1, 'ishell' => 1, @@ -47,25 +43,17 @@ class GlobalOption * @psalm-var array */ private static $options = [ - // '--debug' => 'int;Setting the runtime log debug level(quiet 0 - 5 crazy);no;1', '--debug' => [ 'type' => FlagType::INT, 'desc' => 'Setting the runtime log debug level(quiet 0 - 5 crazy)', 'envVar' => Console::DEBUG_ENV_KEY, ], - // '--debug' => 'Setting the runtime log debug level(quiet 0 - 5 crazy)', '--ishell' => 'bool;Run application an interactive shell environment', - // '--ishell' => 'Run application an interactive shell environment', '--profile' => 'bool;Display timing and memory usage information', - // '--profile' => 'Display timing and memory usage information', '--no-color' => 'bool;Disable color/ANSI for message output', - // '--no-color' => 'Disable color/ANSI for message output', '--help' => 'bool;Display this help message;;;h', - // '-h, --help' => 'Display this help message', '--version' => 'bool;Show application version information;;;V', - // '-V, --version' => 'Show application version information', '--no-interactive' => 'bool;Run commands in a non-interactive environment', - // '--no-interactive' => 'Run commands in a non-interactive environment', ]; /** @@ -76,19 +64,21 @@ class GlobalOption // '--show-disabled' => 'string;Whether display disabled commands', ]; + public const SHOW_DISABLED = 'show-disabled'; + /** * @var array built-in options for the group command */ protected static $groupOptions = [ // '--help' => 'bool;Display this help message;;;h', - '--show-disabled' => 'string;Whether display disabled commands', + self::SHOW_DISABLED => 'string;Whether display disabled commands', ]; /** * @var array common options for the group/command */ protected static $commonOptions = [ - '--help' => 'bool;Display this help message;;;h', + self::HELP => 'bool;Display this help message;;;h', ]; /** diff --git a/src/AbstractHandler.php b/src/Handler/AbstractHandler.php similarity index 92% rename from src/AbstractHandler.php rename to src/Handler/AbstractHandler.php index d3d476fe..4187e0cf 100644 --- a/src/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -6,7 +6,7 @@ * Time: 11:40 */ -namespace Inhere\Console; +namespace Inhere\Console\Handler; use Inhere\Console\Annotate\DocblockRules; use Inhere\Console\Component\ErrorHandler; @@ -15,6 +15,8 @@ use Inhere\Console\Concern\InputOutputAwareTrait; use Inhere\Console\Concern\SubCommandsWareTrait; use Inhere\Console\Concern\UserInteractAwareTrait; +use Inhere\Console\Console; +use Inhere\Console\ConsoleEvent; use Inhere\Console\Contract\CommandHandlerInterface; use Inhere\Console\Contract\CommandInterface; use Inhere\Console\IO\Input; @@ -24,14 +26,11 @@ use InvalidArgumentException; use ReflectionException; use RuntimeException; -use Swoole\Coroutine; -use Swoole\Event; use Throwable; use Toolkit\PFlag\FlagsParser; use Toolkit\PFlag\SFlags; use Toolkit\Stdlib\Helper\PhpHelper; use Toolkit\Stdlib\Obj\ConfigObject; -use function array_merge; use function cli_set_process_title; use function error_get_last; use function function_exists; @@ -241,20 +240,22 @@ protected function annotationVars(): array * running a command **************************************************************************/ - protected function initForRun(Input $input): void + /** + * @param Input $input + */ + protected function initFlagsParser(Input $input): void { $input->setFs($this->flags); $this->flags->setDesc(self::getDesc()); $this->flags->setScriptName(self::getName()); - // load built in options - // $builtInOpts = GlobalOption::getAloneOptions(); - $builtInOpts = $this->getBuiltInOptions(); - $this->flags->addOptsByRules($builtInOpts); + $this->beforeInitFlagsParser($this->flags); // set options by options() $optRules = $this->options(); $this->flags->addOptsByRules($optRules); + + // for render help $this->flags->setBeforePrintHelp(function (string $text) { return $this->parseCommentsVars($text); }); @@ -262,11 +263,24 @@ protected function initForRun(Input $input): void $this->logf(Console::VERB_DEBUG, 'show help message by input flags: -h, --help'); $this->showHelp(); }); + + $this->afterInitFlagsParser($this->flags); } - protected function getBuiltInOptions(): array + /** + * @param FlagsParser $fs + */ + protected function beforeInitFlagsParser(FlagsParser $fs): void { - return GlobalOption::getAloneOptions(); + // $fs->addOptsByRules(GlobalOption::getAloneOptions()); + } + + /** + * @param FlagsParser $fs + */ + protected function afterInitFlagsParser(FlagsParser $fs): void + { + // $fs->addOptsByRules(GlobalOption::getAloneOptions()); } /** @@ -280,7 +294,7 @@ public function run(array $args) $name = self::getName(); try { - $this->initForRun($this->input); + $this->initFlagsParser($this->input); $this->log(Console::VERB_DEBUG, "begin run '$name' - parse options", ['args' => $args]); @@ -318,14 +332,10 @@ protected function doRun(array $args) $rName = $this->resolveAlias($first); if ($this->isSubCommand($rName)) { - + // TODO } } - // $this->debugf('begin run command. load configure for command'); - // // load input definition configure - // $this->configure(); - // some prepare check // - validate input arguments if (true !== $this->prepare()) { @@ -352,34 +362,27 @@ protected function doRun(array $args) } $this->afterExecute(); - return $result; } /** * coroutine run by swoole go() * - * @return bool + * @return int */ - public function coExecute(): bool + public function coExecute(): int { - // $ch = new Coroutine\Channel(1); - $ok = Coroutine::create(function () { + $cid = \Swoole\Coroutine\run(function () { $this->execute($this->input, $this->output); - // $ch->push($result); }); // if create co fail - if ((int)$ok === 0) { - // if open debug, output a tips + if ($cid < 0) { $this->logf(Console::VERB_DEBUG, 'ERROR: The coroutine create failed'); - // exec by normal flow - $result = $this->execute($this->input, $this->output); + $result = (int)$this->execute($this->input, $this->output); } else { // success: wait coroutine exec. - Event::wait(); $result = 0; - // $result = $ch->pop(10); } return $result; @@ -406,7 +409,7 @@ protected function beforeExecute(): bool * * @return int|mixed */ - abstract protected function execute($input, $output); + abstract protected function execute(Input $input, Output $output); /** * After command execute @@ -499,9 +502,13 @@ public function loadRulesByDocblock(string $method, FlagsParser $fs): void $dr = DocblockRules::newByDocblock($rftMth->getDocComment()); $dr->parse(); - $fs->setDesc($dr->getTagValue('desc') ?: self::getDesc()); $fs->addArgsByRules($dr->getArgRules()); $fs->addOptsByRules($dr->getOptRules()); + + // more info + $fs->setDesc($dr->getTagValue('desc'), true); + $fs->setMoreHelp($dr->getTagValue('help')); + $fs->setExample($dr->getTagValue('example')); } /********************************************************** diff --git a/src/Handler/CallableCommand.php b/src/Handler/CallableCommand.php index 54067a9d..f65ac843 100644 --- a/src/Handler/CallableCommand.php +++ b/src/Handler/CallableCommand.php @@ -57,7 +57,7 @@ public function setCallable(callable $callable): self * * @return int|mixed */ - protected function execute($input, $output) + protected function execute(Input $input, Output $output) { if (!$call = $this->callable) { throw new \BadMethodCallException('The callable property is empty'); diff --git a/src/Router.php b/src/Router.php index fe44118c..1be04049 100644 --- a/src/Router.php +++ b/src/Router.php @@ -156,14 +156,14 @@ public function addGroup(string $name, $class = null, array $options = []): Rout */ public function addCommand(string $name, $handler = null, array $options = []): RouterInterface { - /** - * @var Command $name name is an command class - */ if (!$handler && class_exists($name)) { $handler = $name; $name = $name::getName(); } + /** + * @var Command $name name is an command class + */ if (!$name || !$handler) { Helper::throwInvalidArgument("Command 'name' and 'handler' cannot be empty! name: $name"); } @@ -311,7 +311,7 @@ public function match(string $name): array **********************************************************/ /** - * @param $name + * @param string $name * * @throws InvalidArgumentException */ @@ -376,7 +376,7 @@ public function getControllers(): array } /** - * @param $name + * @param string $name * * @return array */ @@ -386,7 +386,7 @@ public function getControllerInfo(string $name): array } /** - * @param $name + * @param string $name * * @return bool */ @@ -404,7 +404,7 @@ public function getCommands(): array } /** - * @param $name + * @param string $name * * @return bool */ diff --git a/test/TestCommand.php b/test/TestCommand.php index 2dcdb383..5c6aaf10 100644 --- a/test/TestCommand.php +++ b/test/TestCommand.php @@ -31,7 +31,7 @@ class TestCommand extends Command * * @return int|mixed */ - protected function execute($input, $output) + protected function execute(Input $input, Output $output) { return __METHOD__; } From eec62e3e2134f870b66fef1c7086a68af7464477 Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 18 Sep 2021 15:47:21 +0800 Subject: [PATCH 153/258] enhance: parse method doc comments enhance --- src/Annotate/DocblockRules.php | 15 +++++- test/Annotate/DocblockRulesTest.php | 71 ++++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/src/Annotate/DocblockRules.php b/src/Annotate/DocblockRules.php index 59911e29..bfd75c4d 100644 --- a/src/Annotate/DocblockRules.php +++ b/src/Annotate/DocblockRules.php @@ -8,6 +8,8 @@ use function array_merge; use function explode; use function preg_match; +use function strlen; +use function substr; use function trim; /** @@ -139,7 +141,7 @@ protected function parseMultiLines(array $lines): array $index = 0; $rules = $kvRules = []; - // $keyWidth = 16; + $keyWidth = 16; // with an default value. foreach ($lines as $line) { $trimmed = trim($line); if (!$trimmed) { @@ -157,6 +159,12 @@ protected function parseMultiLines(array $lines): array continue; } + // TIP: special - if line indent space len gt keyWidth, is desc message of multi line. + if (!trim(substr($line, 0, $keyWidth))) { + $rules[$index - 1][1] .= "\n" . $trimmed; // multi desc message. + continue; + } + $name = trim($nodes[0], '.'); if (!preg_match('/^[\w ,-]{0,48}$/', $name)) { if ($index === 0) { // invalid first line @@ -168,10 +176,15 @@ protected function parseMultiLines(array $lines): array continue; } + $nameLen = strlen($name); + $keyWidth = $nameLen > $keyWidth ? $nameLen : $keyWidth; + + // append $rules[$index] = [$name, $nodes[1]]; $index++; } + // convert to k-v data. if ($rules) { foreach ($rules as [$name, $rule]) { $kvRules[$name] = $rule; diff --git a/test/Annotate/DocblockRulesTest.php b/test/Annotate/DocblockRulesTest.php index 487c51b4..e856448b 100644 --- a/test/Annotate/DocblockRulesTest.php +++ b/test/Annotate/DocblockRulesTest.php @@ -4,7 +4,9 @@ use Inhere\Console\Annotate\DocblockRules; use Inhere\ConsoleTest\BaseTestCase; +use ReflectionException; use ReflectionMethod; +use function vdump; /** * class DocblockRulesTest @@ -12,7 +14,7 @@ class DocblockRulesTest extends BaseTestCase { /** - * @throws \ReflectionException + * @throws ReflectionException */ public function testParse_byReflect(): void { @@ -53,7 +55,7 @@ public function testParse_byDocComment(): void * */ DOC -); + ); $dr->parse(); $this->assertNotEmpty($dr->getTagValue('desc')); @@ -66,4 +68,69 @@ public function testParse_byDocComment(): void $this->assertArrayHasKey('--main', $opts); $this->assertArrayHasKey('repoPath', $args); } + + public function testParse_byDocComment_complex(): void + { + $dr = DocblockRules::new(); + $dr->setDocTagsByDocblock(<< and tformat:. + * --style The style for generate for changelog. + * allow: markdown(default), simple, gh-release + * --repo-url The git repo URL address. eg: https://github.com/inhere/kite + * default will auto use current git origin remote url + * --no-merges bool;No contains merge request logs + * --unshallow bool;Convert to a complete warehouse, useful on GitHub Action. + * --with-author bool;Display commit author name + * + * @example + * {binWithCmd} last head + * {binWithCmd} last head --style gh-release --no-merges + * {binWithCmd} v2.0.9 v2.0.10 --no-merges --style gh-release --exclude "cs-fixer,format codes" + */ +DOC + ); + + $dr->parse(); + + $this->assertNotEmpty($dr->getTagValue('desc')); + $this->assertNotEmpty($dr->getTagValue('example')); + + $this->assertNotEmpty($opts = $dr->getOptRules()); + $this->assertArrayHasKey('--exclude', $opts); + $this->assertArrayHasKey('--with-author', $opts); + $this->assertArrayHasKey('--format', $opts); + $this->assertArrayHasKey('--style', $opts); + $this->assertArrayHasKey('--filters', $opts); + + $this->assertStringContainsString('can be one of oneline, short', $opts['--format']); + $this->assertStringContainsString('kw keyword filter', $opts['--filters']); + $this->assertStringContainsString('wl word length filter', $opts['--filters']); + vdump($opts); + + $this->assertNotEmpty($args = $dr->getArgRules()); + $this->assertCount(2, $args); + $this->assertArrayHasKey('oldVersion', $args); + $this->assertArrayHasKey('newVersion', $args); + } } From 799d22bb0f3f942c476b98eeaa1beddfb71d3ab5 Mon Sep 17 00:00:00 2001 From: sy-records <52o@qq52o.cn> Date: Sat, 18 Sep 2021 18:04:16 +0800 Subject: [PATCH 154/258] Fix invalid default value of Choose::one --- src/Component/Interact/Choose.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Component/Interact/Choose.php b/src/Component/Interact/Choose.php index 10a0b5a4..8b822de7 100644 --- a/src/Component/Interact/Choose.php +++ b/src/Component/Interact/Choose.php @@ -55,12 +55,16 @@ public static function one(string $description, $options, $default = null, bool $text .= "\n $key) $value"; } - $defaultText = $default ? "[default:$default]" : ''; + $defaultText = $default ? "[default:$default]" : ''; Console::write($text); beginChoice: $r = Console::readln("Your choice$defaultText : "); + if (empty($r) && $default !== null) { + return $default; + } + // error, allow try again once. if (!array_key_exists($r, $options)) { goto beginChoice; From 0524147ea964bbe548b86fa9f1f1174bc83b65cc Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 21 Sep 2021 15:16:20 +0800 Subject: [PATCH 155/258] update some for examples --- examples/Command/DemoCommand.php | 2 +- examples/Command/TestCommand.php | 6 ++- examples/Controller/ProcessController.php | 2 +- examples/Controller/ShowController.php | 11 ++--- psalm.xml | 16 +++++++ src/Util/ProgressBar.php | 52 +++++++++++++++++++---- 6 files changed, 72 insertions(+), 17 deletions(-) create mode 100644 psalm.xml diff --git a/examples/Command/DemoCommand.php b/examples/Command/DemoCommand.php index 7a855a26..15cadf7f 100644 --- a/examples/Command/DemoCommand.php +++ b/examples/Command/DemoCommand.php @@ -47,7 +47,7 @@ protected function configure(): void * @param Output $output * @return int|void */ - public function execute($input, $output) + public function execute(Input $input, Output $output) { $output->write('hello, this in ' . __METHOD__); // $name = $input->getArg('name'); diff --git a/examples/Command/TestCommand.php b/examples/Command/TestCommand.php index 896edcab..9153d5f3 100644 --- a/examples/Command/TestCommand.php +++ b/examples/Command/TestCommand.php @@ -9,6 +9,7 @@ namespace Inhere\Console\Examples\Command; use Inhere\Console\Command; +use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; /** @@ -32,17 +33,20 @@ protected function commands(): array /** * test text + * * @usage {name} test message * @arguments * arg1 argument description 1 * arg2 argument description 2 + * * @options * --long,-s option description 1 * --opt option description 2 + * * @param $input * @param Output $output */ - public function execute($input, $output) + public function execute(Input $input, Output $output) { $output->write('hello, this in ' . __METHOD__); } diff --git a/examples/Controller/ProcessController.php b/examples/Controller/ProcessController.php index a52e749e..999273e7 100644 --- a/examples/Controller/ProcessController.php +++ b/examples/Controller/ProcessController.php @@ -10,7 +10,7 @@ use Inhere\Console\Controller; use RuntimeException; -use Toolkit\Sys\ProcessUtil; +use Toolkit\Sys\Proc\ProcessUtil; use Toolkit\Sys\Sys; use function is_resource; diff --git a/examples/Controller/ShowController.php b/examples/Controller/ShowController.php index dad3461c..235e7860 100644 --- a/examples/Controller/ShowController.php +++ b/examples/Controller/ShowController.php @@ -13,7 +13,6 @@ use Inhere\Console\Component\Symbol\Char; use Inhere\Console\Component\Symbol\Emoji; use Inhere\Console\Controller; -use Inhere\Console\IO\Input; use Inhere\Console\Util\Show; use ReflectionException; use Toolkit\Cli\Color; @@ -217,17 +216,19 @@ public function emojiCommand(): int /** * a example for highlight code + * * @options - * --ln Display with line number - * @param Input $in + * --ln bool;Display with line number + * + * @param FlagsParser $fs */ - public function highlightCommand($in): void + public function highlightCommand(FlagsParser $fs): void { // $file = $this->app->getRootPath() . '/examples/routes.php'; $file = $this->app->getRootPath() . '/src/Utils/Show.php'; $src = file_get_contents($file); - $code = Highlighter::create()->highlight($src, $in->getBoolOpt('ln')); + $code = Highlighter::create()->highlight($src, $fs->getOpt('ln')); $this->output->writeRaw($code); } diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 00000000..033821fa --- /dev/null +++ b/psalm.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/src/Util/ProgressBar.php b/src/Util/ProgressBar.php index 7defeaa8..21a2344f 100644 --- a/src/Util/ProgressBar.php +++ b/src/Util/ProgressBar.php @@ -13,6 +13,7 @@ use Inhere\Console\Contract\OutputInterface; use LogicException; use RuntimeException; +use Toolkit\Stdlib\Helper\Format; use Toolkit\Stdlib\Str; use function max; @@ -31,16 +32,34 @@ class ProgressBar { // options + /** + * @var int + */ private $barWidth = 30; - private $completeChar = '='; // 已完成的显示字符 + /** + * @var string 已完成的显示字符 + */ + private $completeChar = '='; - private $progressChar = '>'; // 当前进度的显示字符 + /** + * @var string 当前进度的显示字符 + */ + private $progressChar = '>'; - private $remainingChar = '-'; // 剩下的的显示字符 + /** + * @var string 剩下的的显示字符 + */ + private $remainingChar = '-'; + /** + * @var int + */ private $redrawFreq = 1; + /** + * @var string + */ private $format; /** @@ -74,14 +93,29 @@ class ProgressBar */ private $stepWidth; - private $startTime; + /** + * @var int + */ + private $startTime = 0; - private $finishTime; + /** + * @var int + */ + private $finishTime = 0; + /** + * @var bool + */ private $overwrite = true; + /** + * @var bool + */ private $started = false; + /** + * @var bool + */ private $firstRun = true; /** @@ -107,7 +141,7 @@ class ProgressBar public const DEFAULT_FORMAT = '[{@bar}] {@percent:3s}%({@current}/{@max}) {@elapsed:6s}/{@estimated:-6s} {@memory:6s}'; /** - * @param OutputInterface $output + * @param OutputInterface|null $output * @param int $maxSteps * * @return ProgressBar @@ -118,8 +152,8 @@ public static function create(OutputInterface $output = null, int $maxSteps = 0) } /** - * @param OutputInterface $output - * @param int $maxSteps + * @param OutputInterface|null $output + * @param int $maxSteps */ public function __construct(OutputInterface $output = null, int $maxSteps = 0) { @@ -594,7 +628,7 @@ private static function loadDefaultParsers(): array return FormatUtil::howLongAgo($estimated); }, 'memory' => static function () { - return FormatUtil::memoryUsage(memory_get_usage(true)); + return Format::memory(memory_get_usage(true)); }, 'current' => static function (self $bar) { return Str::pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT); From fb34f323576b1f8785f84a551e42979a67b0ef61 Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 22 Sep 2021 22:10:14 +0800 Subject: [PATCH 156/258] fix: some flags error on built in command method comments --- src/BuiltIn/DevServerCommand.php | 14 +++++++------- src/BuiltIn/PharController.php | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/BuiltIn/DevServerCommand.php b/src/BuiltIn/DevServerCommand.php index 32646233..3098c9b1 100644 --- a/src/BuiltIn/DevServerCommand.php +++ b/src/BuiltIn/DevServerCommand.php @@ -40,24 +40,24 @@ public static function aliases(): array * {command} [-H HOST] [-p PORT] * {command} [-S HOST:PORT] [file=]web/index.php * @options - * -s, -S, --addr STRING The http server address. e.g 127.0.0.1:8552 - * -t, --doc-root STRING The document root dir for server(public) - * -H,--host STRING The server host address(127.0.0.1) - * -p,--port INTEGER The server port number(8552) - * -b,--php-bin STRING The php binary file(php) + * -s, -S, --addr The http server address. e.g 127.0.0.1:8552 + * -t, --doc-root The document root dir for server(public) + * -H,--host The server host address(127.0.0.1) + * -p,--port The server port number(8552) + * -b,--php-bin The php binary file(php) * @arguments * file=STRING The entry file for server. e.g web/index.php * * @param Input $input * @param Output $output * - * @return int|mixed|void + * @return void * @throws Exception * @example * {command} -S 127.0.0.1:8552 web/index.php */ #[CmdOption('dev-serve', 'start a php built-in http server for developmentd')] - public function execute($input, $output) + public function execute(Input $input, Output $output) { $serveAddr = $input->getSameStringOpt('s,S,addr'); if (!$serveAddr) { diff --git a/src/BuiltIn/PharController.php b/src/BuiltIn/PharController.php index 6d26cb43..1e84f616 100644 --- a/src/BuiltIn/PharController.php +++ b/src/BuiltIn/PharController.php @@ -68,14 +68,14 @@ protected function packConfigure(Input $input): void * @usage {fullCommand} [--dir DIR] [--output FILE] [...] * * @options - * -d, --dir STRING Setting the project directory for packing. - * default is current work-dir(default: {workDir}) - * -c, --config STRING Use the custom config file for build phar(default: ./phar.build.inc) - * -o, --output STRING Setting the output file name({defaultPkgName}) - * --fast Fast build. only add modified files by git status -s - * --refresh Whether build vendor folder files on phar file exists(False) - * --files STRING Only pack the list files to the exist phar, multi use ',' split - * --no-progress Disable output progress on the runtime + * -d, --dir Setting the project directory for packing. + * default is current work-dir(default: {workDir}) + * -c, --config Use the custom config file for build phar(default: ./phar.build.inc) + * -o, --output Setting the output file name({defaultPkgName}) + * --fast bool;Fast build. only add modified files by git status -s + * --refresh bool;Whether build vendor folder files on phar file exists(False) + * --files Only pack the list files to the exist phar, multi use ',' split + * --no-progress bool;Disable output progress on the runtime * * @param Input $input * @param Output $output @@ -83,8 +83,8 @@ protected function packConfigure(Input $input): void * @return int * @throws Exception * @example - * {fullCommand} Pack current dir to a phar file. - * {fullCommand} --dir vendor/swoft/devtool Pack the specified dir to a phar file. + * {fullCommand} Pack current dir to a phar file. + * {fullCommand} --dir vendor/swoft/devtool Pack the specified dir to a phar file. * * custom output phar file name * php -d phar.readonly=0 {binFile} phar:pack -o=mycli.phar @@ -223,10 +223,10 @@ public function setCompilerConfiger(Closure $compilerConfiger): void * @usage {fullCommand} -f FILE [-d DIR] * * @options - * -f, --file STRING The packed phar file path - * -d, --dir STRING The output dir on extract phar package. - * -y, --yes BOOL Whether display goon tips message. - * --overwrite BOOL Whether overwrite exists files on extract phar + * -f, --file The packed phar file path + * -d, --dir The output dir on extract phar package. + * -y, --yes bool;Whether display goon tips message. + * --overwrite bool;Whether overwrite exists files on extract phar * * @param Input $in * @param Output $out From b2ee9dd84b63103d4e128f62ff5efa7ba18c4c56 Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 22 Sep 2021 22:22:31 +0800 Subject: [PATCH 157/258] up: use new flags replace the input object --- src/BuiltIn/DevServerCommand.php | 22 +++++++------- src/BuiltIn/PharController.php | 41 +++++++++++++------------- src/Concern/AttachApplicationTrait.php | 21 +++++++++++++ 3 files changed, 53 insertions(+), 31 deletions(-) diff --git a/src/BuiltIn/DevServerCommand.php b/src/BuiltIn/DevServerCommand.php index 3098c9b1..85c00d10 100644 --- a/src/BuiltIn/DevServerCommand.php +++ b/src/BuiltIn/DevServerCommand.php @@ -9,7 +9,7 @@ namespace Inhere\Console\BuiltIn; use Exception; -use Inhere\Console\Attr\CmdOption; +use Inhere\Console\Annotate\Attr\CmdOption; use Inhere\Console\Command; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; @@ -45,8 +45,9 @@ public static function aliases(): array * -H,--host The server host address(127.0.0.1) * -p,--port The server port number(8552) * -b,--php-bin The php binary file(php) + * * @arguments - * file=STRING The entry file for server. e.g web/index.php + * file The entry file for server. e.g web/index.php * * @param Input $input * @param Output $output @@ -59,23 +60,22 @@ public static function aliases(): array #[CmdOption('dev-serve', 'start a php built-in http server for developmentd')] public function execute(Input $input, Output $output) { - $serveAddr = $input->getSameStringOpt('s,S,addr'); + $serveAddr = $this->flags->getOpt('addr'); if (!$serveAddr) { - $serveAddr = $input->getSameStringOpt(['H', 'host']); + $serveAddr = $this->flags->getOpt('host'); } - $port = $input->getSameStringOpt(['p', 'port']); + $port = $this->flags->getOpt('port'); if ($port && strpos($serveAddr, ':') === false) { $serveAddr .= ':' . $port; } - $docRoot = $input->getSameStringOpt('t,doc-root'); - $hceFile = $input->getStringOpt('hce-file'); - $hceEnv = $input->getStringOpt('hce-env'); - $phpBin = $input->getStringOpt('php-bin'); + $docRoot = $this->flags->getOpt('doc-root'); + $hceFile = $this->flags->getOpt('hce-file'); + $hceEnv = $this->flags->getOpt('hce-env'); + $phpBin = $this->flags->getOpt('php-bin'); - $input->bindArgument('file', 0); - $entryFile = $input->getStringArg('file'); + $entryFile = $this->flags->getArg('file'); $pds = PhpDevServe::new($serveAddr, $docRoot, $entryFile); $pds->setPhpBin($phpBin); diff --git a/src/BuiltIn/PharController.php b/src/BuiltIn/PharController.php index 1e84f616..282bd2ef 100644 --- a/src/BuiltIn/PharController.php +++ b/src/BuiltIn/PharController.php @@ -17,6 +17,7 @@ use Inhere\Console\Util\Helper; use Inhere\Console\Util\Show; use RuntimeException; +use Toolkit\PFlag\FlagsParser; use Toolkit\Stdlib\Str; use function basename; use function file_exists; @@ -77,8 +78,9 @@ protected function packConfigure(Input $input): void * --files Only pack the list files to the exist phar, multi use ',' split * --no-progress bool;Disable output progress on the runtime * - * @param Input $input + * @param Input $input * @param Output $output + * @param FlagsParser $fs * * @return int * @throws Exception @@ -92,16 +94,16 @@ protected function packConfigure(Input $input): void * only update the input files: * php -d phar.readonly=0 {binFile} phar:pack -o=mycli.phar --debug --files app/Command/ServeCommand.php */ - public function packCommand(Input $input, Output $output): int + public function packCommand(Input $input, Output $output, FlagsParser $fs): int { $startAt = microtime(true); $workDir = $input->getPwd(); - $dir = $input->getOpt('dir') ?: $workDir; + $dir = $fs->getOpt('dir') ?: $workDir; $cpr = $this->configCompiler($dir); - $refresh = $input->boolOpt('refresh'); - $outFile = $input->getSameStringOpt(['o', 'output'], $this->defPkgName); + $refresh = $fs->getOpt('refresh'); + $outFile = $fs->getOpt('output', $this->defPkgName); $pharFile = $workDir . '/' . $outFile; Show::aList([ @@ -111,13 +113,13 @@ public function packCommand(Input $input, Output $output): int ], 'Building Information'); // use fast build - if ($this->input->boolOpt('fast')) { + if ($fs->getOpt('fast')) { $cpr->setModifies($cpr->findChangedByGit()); $this->output->liteNote('Use fast build, will only pack changed or new files(from git status)'); } // Manual append some files - if ($files = $input->getStringOpt('files')) { + if ($files = $fs->getOpt('files')) { $cpr->setModifies(Str::explode($files)); $output->liteInfo("will only pack input files to the exists phar: $outFile"); } @@ -130,7 +132,10 @@ public function packCommand(Input $input, Output $output): int }); $output->colored('Collect Pack files', 'comment'); - $this->outputProgress($cpr, $input); + + if (!$fs->getOpt('no-progress')) { + $this->outputProgress($cpr); + } // packing ... $cpr->pack($pharFile, $refresh); @@ -178,17 +183,12 @@ protected function configCompiler(string $dir): PharCompiler /** * @param PharCompiler $cpr - * @param Input $input * * @return void */ - private function outputProgress(PharCompiler $cpr,Input $input): void + private function outputProgress(PharCompiler $cpr): void { - if ($input->getBoolOpt('no-progress')) { - return; - } - - if ($input->getOpt('debug')) { + if ($this->isDebug()) { // $output->info('Pack file to Phar ... ...'); $cpr->onAdd(function (string $path) { $this->writeln(" + $path"); @@ -228,15 +228,16 @@ public function setCompilerConfiger(Closure $compilerConfiger): void * -y, --yes bool;Whether display goon tips message. * --overwrite bool;Whether overwrite exists files on extract phar * - * @param Input $in + * @param Input $in * @param Output $out + * @param FlagsParser $fs * * @return int * @example {fullCommand} -f myapp.phar -d var/www/app */ - public function unpackCommand($in, $out): int + public function unpackCommand(Input $in, Output $out, FlagsParser $fs): int { - if (!$path = $in->getSameOpt(['f', 'file'])) { + if (!$path = $fs->getOpt('file')) { return $out->error("Please input the phar file path by option '-f|--file'"); } @@ -247,8 +248,8 @@ public function unpackCommand($in, $out): int return $out->error("The phar file not exists. File: $file"); } - $dir = $in->getSameOpt(['d', 'dir']) ?: $basePath; - $overwrite = $in->getBoolOpt('overwrite'); + $dir = $fs->getOpt('dir') ?: $basePath; + $overwrite = $fs->getOpt('overwrite'); if (!is_dir($dir)) { Helper::mkdir($dir); diff --git a/src/Concern/AttachApplicationTrait.php b/src/Concern/AttachApplicationTrait.php index c9c94ac1..9792e814 100644 --- a/src/Concern/AttachApplicationTrait.php +++ b/src/Concern/AttachApplicationTrait.php @@ -6,6 +6,7 @@ use Inhere\Console\Application; use Inhere\Console\Console; use Inhere\Console\GlobalOption; +use Toolkit\Stdlib\OS; /** * Trait AttachApplicationTrait @@ -101,6 +102,26 @@ public function getVerbLevel(): int return (int)$this->input->getLongOpt('debug', Console::VERB_ERROR); } + /** + * @param int $level + * + * @return bool + */ + public function isDebug(int $level = Console::VERB_DEBUG): bool + { + if ($this->app) { + return $this->app->isDebug(); + } + + $setVal = Console::VERB_ERROR; + $envVal = OS::getEnvStrVal(Console::DEBUG_ENV_KEY); + if ($envVal !== '') { + $setVal = (int)$envVal; + } + + return $level <= $setVal; + } + /** * @param string $format * @param mixed ...$args From e799fc4e787b549281262cdab051ac22c91aaaf9 Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 23 Sep 2021 16:05:42 +0800 Subject: [PATCH 158/258] Update Choose.php --- src/Component/Interact/Choose.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Component/Interact/Choose.php b/src/Component/Interact/Choose.php index 8b822de7..978153e1 100644 --- a/src/Component/Interact/Choose.php +++ b/src/Component/Interact/Choose.php @@ -61,7 +61,7 @@ public static function one(string $description, $options, $default = null, bool beginChoice: $r = Console::readln("Your choice$defaultText : "); - if (empty($r) && $default !== null) { + if ($r === '' && $default !== null) { return $default; } From 41db27d75b8d00858cc599157a23cd0209d9e205 Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 23 Sep 2021 17:37:10 +0800 Subject: [PATCH 159/258] fix: command help vars not replace as value --- src/Component/Formatter/MultiList.php | 23 ++++++++++++++++++++--- src/Component/Formatter/Section.php | 4 ++-- src/Component/MessageFormatter.php | 21 +++++++++++++++++++++ src/Concern/CommandHelpTrait.php | 1 + src/Util/FormatUtil.php | 8 ++++---- 5 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/Component/Formatter/MultiList.php b/src/Component/Formatter/MultiList.php index c120e461..cbd447bf 100644 --- a/src/Component/Formatter/MultiList.php +++ b/src/Component/Formatter/MultiList.php @@ -17,7 +17,7 @@ class MultiList extends MessageFormatter * Format and render multi list * * ```php - * [ + * $data = [ * 'list1 title' => [ * 'name' => 'value text', * 'name2' => 'value text 2', @@ -27,11 +27,15 @@ class MultiList extends MessageFormatter * 'name2' => 'value text 2', * ], * ... ... - * ] + * ]; + * + * MultiList::show($data); * ``` * * @param array $data * @param array $opts + * + * @psalm-param array{beforeWrite: callable, lastNewline: bool} $opts */ public static function show(array $data, array $opts = []): void { @@ -45,6 +49,12 @@ public static function show(array $data, array $opts = []): void unset($opts['lastNewline']); } + $beforeWrite = null; + if (isset($opts['beforeWrite'])) { + $beforeWrite = $opts['beforeWrite']; + unset($opts['beforeWrite']); + } + foreach ($data as $title => $list) { if ($ignoreEmpty && !$list) { continue; @@ -53,6 +63,13 @@ public static function show(array $data, array $opts = []): void $stringList[] = SingleList::show($list, (string)$title, $opts); } - Console::write(implode("\n", $stringList), $lastNewline); + $str = implode("\n", $stringList); + + // before write handler + if ($beforeWrite) { + $str = $beforeWrite($str); + } + + Console::write($str, $lastNewline); } } diff --git a/src/Component/Formatter/Section.php b/src/Component/Formatter/Section.php index 8c166879..15fda14c 100644 --- a/src/Component/Formatter/Section.php +++ b/src/Component/Formatter/Section.php @@ -68,11 +68,11 @@ public static function show(string $title, $body, array $opts = []): void $border = Str::pad($char, $width, $char); if ($showTBorder) { - $topBorder = "{$indentStr}$border\n"; + $topBorder = "$indentStr$border\n"; } if ($showBBorder) { - $bottomBorder = "{$indentStr}$border\n"; + $bottomBorder = "$indentStr$border\n"; } } diff --git a/src/Component/MessageFormatter.php b/src/Component/MessageFormatter.php index 392ed5ff..80d4db84 100644 --- a/src/Component/MessageFormatter.php +++ b/src/Component/MessageFormatter.php @@ -32,6 +32,11 @@ abstract class MessageFormatter implements FormatterInterface */ protected $config = []; + /** + * @var callable + */ + protected $beforeWrite; + /** * @param array $config * @@ -105,4 +110,20 @@ public function getConfig(): array { return $this->config; } + + /** + * @return callable + */ + public function getBeforeWrite(): callable + { + return $this->beforeWrite; + } + + /** + * @param callable $beforeWrite + */ + public function setBeforeWrite(callable $beforeWrite): void + { + $this->beforeWrite = $beforeWrite; + } } diff --git a/src/Concern/CommandHelpTrait.php b/src/Concern/CommandHelpTrait.php index 564016c5..f18260f7 100644 --- a/src/Concern/CommandHelpTrait.php +++ b/src/Concern/CommandHelpTrait.php @@ -158,6 +158,7 @@ public function showHelpByFlagsParser(FlagsParser $fs, array $aliases = [], stri $this->output->mList($help, [ 'sepChar' => ' ', 'lastNewline' => false, + 'beforeWrite' => [$this, 'parseCommentsVars'], ]); return 0; diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index bb8e50d1..61df478d 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -94,20 +94,20 @@ public static function applyIndent(string $string, int $indent = 2, string $inde * amet. * ``` * - * @param string $text the text to be wrapped + * @param string $text the text to be wrapped * @param integer $indent number of spaces to use for indentation. * @param integer $width * * @return string the wrapped text. * @from yii2 */ - public static function wrapText($text, $indent = 0, $width = 0): string + public static function wrapText(string $text, int $indent = 0, int $width = 0): string { if (!$text) { return $text; } - if ((int)$width <= 0) { + if ($width <= 0) { $size = Sys::getScreenSize(); if ($size === false || $size[0] <= $indent) { @@ -156,8 +156,8 @@ public static function alignOptions(array $options): array continue; } + // padding length equals to '-h, ' if (!strpos($name, ',')) { - // padding length equals to '-h, ' $name = ' ' . $name; } else { $name = str_replace([' ', ','], ['', ', '], $name); From 7d6b84500a53baa61826ca2021d7b24e6e6959e4 Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 24 Sep 2021 11:47:08 +0800 Subject: [PATCH 160/258] enhance: support auto indent multi line text on command help --- src/AbstractApplication.php | 5 +++ src/Application.php | 2 +- src/Component/Formatter/Padding.php | 4 +- src/Controller.php | 1 - src/Util/FormatUtil.php | 65 +++++++++++++++++++++-------- src/Util/Helper.php | 1 + 6 files changed, 57 insertions(+), 21 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index c8bdacab..e80c0080 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -347,6 +347,11 @@ public function stop(int $code = 0) exit($code); } + /** + * @param array $args + * + * @return int|mixed + */ public function runWithArgs(array $args) { $this->input->setArgs($args); diff --git a/src/Application.php b/src/Application.php index 934b50f8..fa298a4c 100644 --- a/src/Application.php +++ b/src/Application.php @@ -259,7 +259,7 @@ protected function getFileFilter(): callable ****************************************************************************/ /** - * @param string $name + * @param string $name command name or command ID or command path. * @param array $args * * @return int|mixed diff --git a/src/Component/Formatter/Padding.php b/src/Component/Formatter/Padding.php index 57d5f869..3c289b70 100644 --- a/src/Component/Formatter/Padding.php +++ b/src/Component/Formatter/Padding.php @@ -4,8 +4,8 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; -use Inhere\Console\Util\Helper; use Toolkit\Cli\ColorTag; +use Toolkit\Stdlib\Arr\ArrayHelper; use Toolkit\Stdlib\Str; use function array_merge; use function trim; @@ -45,7 +45,7 @@ public static function show(array $data, string $title = '', array $opts = []): 'valueStyle' => 'info', ], $opts); - $keyMaxLen = Helper::getKeyMaxWidth($data); + $keyMaxLen = ArrayHelper::getKeyMaxWidth($data); $paddingLen = $keyMaxLen > $opts['padding'] ? $keyMaxLen : $opts['padding']; foreach ($data as $label => $value) { diff --git a/src/Controller.php b/src/Controller.php index c6115385..43a5505f 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -507,7 +507,6 @@ protected function getMethodName(string $action): string /** * @return bool - * @throws ReflectionException */ protected function showHelp(): bool { diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index 61df478d..37a5b3d7 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -9,12 +9,14 @@ namespace Inhere\Console\Util; use Toolkit\Cli\ColorTag; +use Toolkit\Stdlib\Arr\ArrayHelper; use Toolkit\Stdlib\Helper\Format; use Toolkit\Stdlib\Helper\JsonHelper; use Toolkit\Stdlib\Str; use Toolkit\Sys\Sys; use function array_keys; use function array_merge; +use function array_shift; use function explode; use function implode; use function is_array; @@ -200,18 +202,20 @@ public static function howLongAgo(int $secs): string /** * Splice array * - * @param array $data - * e.g [ + * ```php + * $data = [ * 'system' => 'Linux', - * 'version' => '4.4.5', - * ] + * 'version' => '4.4.5', + * ]; + * ``` + * + * @param array $data * @param array $opts * * @return string */ public static function spliceKeyValue(array $data, array $opts = []): string { - $text = ''; $opts = array_merge([ 'leftChar' => '', // e.g ' ', ' * ' 'sepChar' => ' ', // e.g ' | ' OUT: key | value @@ -221,33 +225,37 @@ public static function spliceKeyValue(array $data, array $opts = []): string 'keyMinWidth' => 8, 'keyMaxWidth' => 0, // if not set, will automatic calculation 'ucFirst' => true, // upper first char for value + 'endNewline' => true, // with newline on end. ], $opts); if ($opts['keyMaxWidth'] < 1) { - $opts['keyMaxWidth'] = Helper::getKeyMaxWidth($data); + $opts['keyMaxWidth'] = ArrayHelper::getKeyMaxWidth($data); } // compare - if ((int)$opts['keyMinWidth'] > $opts['keyMaxWidth']) { - $opts['keyMaxWidth'] = $opts['keyMinWidth']; + if ($opts['keyMinWidth'] > $opts['keyMaxWidth']) { + $opts['keyMaxWidth'] = (int)$opts['keyMinWidth']; } + $keyWidth = $opts['keyMaxWidth']; $keyStyle = trim($opts['keyStyle']); $keyPadPos = (int)$opts['keyPadPos']; + $fmtLines = []; foreach ($data as $key => $value) { - $hasKey = !is_int($key); - $text .= $opts['leftChar']; + $hasKey = !is_int($key); + $fmtLine = $opts['leftChar']; - if ($hasKey && $opts['keyMaxWidth']) { - $key = Str::pad((string)$key, $opts['keyMaxWidth'], ' ', $keyPadPos); - $text .= ColorTag::wrap($key, $keyStyle) . $opts['sepChar']; + if ($hasKey && $keyWidth) { + $strKey = Str::pad((string)$key, $keyWidth, ' ', $keyPadPos); + $fmtLine .= ColorTag::wrap($strKey, $keyStyle) . $opts['sepChar']; } + $lines = []; + // if value is array, translate array to string if (is_array($value)) { $temp = '['; - foreach ($value as $k => $val) { if (is_bool($val)) { $val = $val ? '(True)' : '(False)'; @@ -261,14 +269,37 @@ public static function spliceKeyValue(array $data, array $opts = []): string $value = rtrim($temp, ' ,') . ']'; } elseif (is_bool($value)) { $value = $value ? '(True)' : '(False)'; - } else { + } else { // to string. $value = (string)$value; + + // multi line + if ($hasKey && strpos($value, "\n") > 0) { + $lines = explode("\n", $value); + $value = array_shift($lines); + } } + // uc-first $value = $hasKey && $opts['ucFirst'] ? ucfirst($value) : $value; - $text .= ColorTag::wrap($value, $opts['valStyle']) . "\n"; + + // append value. + $fmtLine .= ColorTag::wrap($value, $opts['valStyle']); + // append fmt line. + $fmtLines[] = $fmtLine; + + // value has multi line + if ($lines) { + $linePrefix = $opts['leftChar'] . Str::repeat(' ', $keyWidth + 1) . $opts['sepChar']; + foreach ($lines as $line) { + $fmtLines[] = $linePrefix . $line; + } + } + } + + if ($opts['endNewline']) { + return implode("\n", $fmtLines) . "\n"; } - return $text; + return implode("\n", $fmtLines); } } diff --git a/src/Util/Helper.php b/src/Util/Helper.php index 8c4f87d7..49bb9c37 100644 --- a/src/Util/Helper.php +++ b/src/Util/Helper.php @@ -196,6 +196,7 @@ public static function findSimilar(string $need, $iterator, int $similarPercent * @param bool $excludeInt * * @return int + * @deprecated please use ArrayHelper::getKeyMaxWidth($data, $excludeInt); */ public static function getKeyMaxWidth(array $data, bool $excludeInt = true): int { From d7ac28c167e6e33ea28d74eccf240f1ce07665e0 Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 24 Sep 2021 13:43:44 +0800 Subject: [PATCH 161/258] up readme. require php >= 7.3 --- README.md | 2 +- README.zh-CN.md | 2 +- composer.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c997a57a..437c632b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # PHP Console [![License](https://img.shields.io/packagist/l/inhere/console.svg?style=flat-square)](LICENSE) -[![Php Version](https://img.shields.io/badge/php-%3E=7.2.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) +[![Php Version](https://img.shields.io/badge/php-%3E=7.3.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) [![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console) [![Github Actions Status](https://github.com/inhere/php-console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/php-console/actions) diff --git a/README.zh-CN.md b/README.zh-CN.md index 53e660cb..a7719124 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,7 +1,7 @@ # PHP Console [![License](https://img.shields.io/packagist/l/inhere/console.svg?style=flat-square)](LICENSE) -[![Php Version](https://img.shields.io/badge/php-%3E=7.2.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) +[![Php Version](https://img.shields.io/badge/php-%3E=7.3.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) [![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console) [![Github Actions Status](https://github.com/inhere/php-console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/php-console/actions) diff --git a/composer.json b/composer.json index bdefeedf..2b8351c2 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ } ], "require": { - "php": ">7.2.0", + "php": ">7.3.0", "symfony/polyfill-php80": "~1.0", "toolkit/cli-utils": "~1.0", "toolkit/pflag": "~1.0", From a86db072c484df48bbc4a744b21419d44258192a Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 24 Sep 2021 15:24:15 +0800 Subject: [PATCH 162/258] up: update the github action script --- .github/workflows/php.yml | 10 +++++----- .github/workflows/release.yml | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 0bdf5442..25b7e51d 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -17,10 +17,10 @@ jobs: matrix: php: [7.3, 7.4, 8.0] # os: [ubuntu-latest, macOS-latest] # windows-latest, - include: - - os: 'ubuntu-latest' - php: '7.2' - phpunit: '8.5.13' +# include: +# - os: 'ubuntu-latest' +# php: '7.2' +# phpunit: '8.5.13' steps: - name: Checkout @@ -47,7 +47,7 @@ jobs: coverage: none #optional, setup coverage driver: xdebug, none - name: Install dependencies - run: composer install --no-progress --no-suggest + run: composer install --no-progress # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" # Docs: https://getcomposer.org/doc/articles/scripts.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a33279f9..a22b0e83 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: true matrix: - php: [7.3] + php: [8.0] steps: - name: Checkout @@ -40,7 +40,7 @@ jobs: run: | tag1=${GITHUB_REF#refs/*/} echo "release tag: ${tag1}" - composer install --no-progress --no-suggest + composer install --no-progress # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" # Docs: https://getcomposer.org/doc/articles/scripts.md From c605220112cef8c3369d8731cb7b17ceda30281d Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 24 Sep 2021 20:20:12 +0800 Subject: [PATCH 163/258] fix: inner method dipplay on help. --- src/Handler/AbstractHandler.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index 4187e0cf..d1181325 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -331,7 +331,7 @@ protected function doRun(array $args) $first = $args[0]; $rName = $this->resolveAlias($first); - if ($this->isSubCommand($rName)) { + if ($this->isSub($rName)) { // TODO } } @@ -459,7 +459,7 @@ public function isAlone(): bool /** * @return bool */ - public function isCommand(): bool + public function isAloneCmd(): bool { return $this instanceof CommandInterface; } From 05a433796d5c33437827fbbeb75417dc07ad025e Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 24 Sep 2021 20:21:03 +0800 Subject: [PATCH 164/258] enhance: will display group options on group/command help --- src/Concern/AttachApplicationTrait.php | 1 - src/Concern/CommandHelpTrait.php | 2 +- src/Concern/ControllerHelpTrait.php | 15 +++++++-------- src/Concern/SubCommandsWareTrait.php | 2 +- src/GlobalOption.php | 4 ++-- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/Concern/AttachApplicationTrait.php b/src/Concern/AttachApplicationTrait.php index 9792e814..ebd39c83 100644 --- a/src/Concern/AttachApplicationTrait.php +++ b/src/Concern/AttachApplicationTrait.php @@ -84,7 +84,6 @@ public function isInteractive(): bool } $value = $this->input->getBoolOpt(GlobalOption::NO_INTERACTIVE); - return $value === false; } diff --git a/src/Concern/CommandHelpTrait.php b/src/Concern/CommandHelpTrait.php index f18260f7..fc594cd5 100644 --- a/src/Concern/CommandHelpTrait.php +++ b/src/Concern/CommandHelpTrait.php @@ -209,7 +209,7 @@ protected function showHelpByAnnotations(string $method, string $action = '', ar } // is an command object - $isCommand = $this->isCommand(); + $isCommand = $this->isAlone(); foreach ($allowedTags as $tag) { if (empty($tags[$tag]) || !is_string($tags[$tag])) { // for alone command diff --git a/src/Concern/ControllerHelpTrait.php b/src/Concern/ControllerHelpTrait.php index 5e305f98..53347e92 100644 --- a/src/Concern/ControllerHelpTrait.php +++ b/src/Concern/ControllerHelpTrait.php @@ -150,17 +150,14 @@ public function showCommandList(): void $name = $sName . $this->delimiter; // $usage = "$script {$name}{command} [--options ...] [arguments ...]"; $usage = [ - "$script $nameCOMMAND [--options ...] [arguments ...]", - "$script $sName COMMAND [--options ...] [arguments ...]", + "$script [--global options] $sName [--group options] COMMAND [--options ...] [arguments ...]", + "$script [--global options] $nameCOMMAND [--options ...] [arguments ...]", ]; } - $globalOptions = static::$globalOptions; + $globalOptions = []; if ($app = $this->getApp()) { - $globalOptions = array_merge( - $app->getFlags()->getOptsHelpData(), - static::$globalOptions - ); + $globalOptions = $app->getFlags()->getOptsHelpData(); } $this->output->startBuffer(); @@ -170,16 +167,18 @@ public function showCommandList(): void $this->output->writef("Alias: %s\n", implode(',', $aliases)); } + $groupOptions = $this->flags->getOptsHelpData(); $this->output->mList([ 'Usage:' => $usage, //'Group Name:' => "$sName", + 'Group Options:' => FormatUtil::alignOptions($groupOptions), 'Global Options:' => FormatUtil::alignOptions($globalOptions), 'Available Commands:' => $commands, ], [ 'sepChar' => ' ', ]); - $msgTpl = 'More information about a command, please see: %s %s {command} -h'; + $msgTpl = 'More information about a command, please see: %s %s COMMAND -h'; $this->output->write(sprintf($msgTpl, $script, $detached ? '' : $sName)); $this->output->flush(); } diff --git a/src/Concern/SubCommandsWareTrait.php b/src/Concern/SubCommandsWareTrait.php index 3da54bdc..10ce7458 100644 --- a/src/Concern/SubCommandsWareTrait.php +++ b/src/Concern/SubCommandsWareTrait.php @@ -167,7 +167,7 @@ public function addCommands(array $commands): void * * @return bool */ - public function isSubCommand(string $name): bool + public function isSub(string $name): bool { return isset($this->commands[$name]); } diff --git a/src/GlobalOption.php b/src/GlobalOption.php index 98ad71fd..8d87851f 100644 --- a/src/GlobalOption.php +++ b/src/GlobalOption.php @@ -51,7 +51,7 @@ class GlobalOption '--ishell' => 'bool;Run application an interactive shell environment', '--profile' => 'bool;Display timing and memory usage information', '--no-color' => 'bool;Disable color/ANSI for message output', - '--help' => 'bool;Display this help message;;;h', + '--help' => 'bool;Display application help message;;;h', '--version' => 'bool;Show application version information;;;V', '--no-interactive' => 'bool;Run commands in a non-interactive environment', ]; @@ -78,7 +78,7 @@ class GlobalOption * @var array common options for the group/command */ protected static $commonOptions = [ - self::HELP => 'bool;Display this help message;;;h', + self::HELP => 'bool;Display command help message;;;h', ]; /** From c92753ca08aaedc4d72d32aa47a64483581ca38f Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 24 Sep 2021 23:05:05 +0800 Subject: [PATCH 165/258] fix some error on ishell env --- examples/Controller/ShowController.php | 2 +- src/AbstractApplication.php | 32 ++++++++++++---- src/Component/Formatter/JSONPretty.php | 53 ++++++++++++++++++++++++++ src/Component/MessageFormatter.php | 2 +- src/Handler/AbstractHandler.php | 5 +++ 5 files changed, 84 insertions(+), 10 deletions(-) create mode 100644 src/Component/Formatter/JSONPretty.php diff --git a/examples/Controller/ShowController.php b/examples/Controller/ShowController.php index 235e7860..3ba2217f 100644 --- a/examples/Controller/ShowController.php +++ b/examples/Controller/ShowController.php @@ -114,7 +114,7 @@ public function panelCommand(): void 'borderChar' => '=' ]); - Panel::create([ + Panel::new([ 'data' => $data, 'title' => 'panel show', 'titleBorder' => '=', diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index e80c0080..ac9514c1 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -302,7 +302,6 @@ public function run(bool $exit = true) // do run ... $command = $this->commandName; $result = $this->dispatch($command, $this->flags->getRawArgs()); - } catch (Throwable $e) { $this->fire(ConsoleEvent::ON_RUN_ERROR, $e, $this); $result = $e->getCode() === 0 ? $e->getLine() : $e->getCode(); @@ -355,7 +354,6 @@ public function stop(int $code = 0) public function runWithArgs(array $args) { $this->input->setArgs($args); - return $this->run(false); } @@ -499,9 +497,8 @@ protected function handleGlobalCommand(string $command): bool */ protected function startInteractiveShell(): void { - $in = $this->input; + // $in = $this->input; $out = $this->output; - $out->title("Welcome interactive shell for run application", [ 'titlePos' => Title::POS_MIDDLE, ]); @@ -529,6 +526,12 @@ protected function startInteractiveShell(): void 'exit' => 1, ]; + // set helper render + $this->flags->setHelpRenderer(function () { + $this->showHelpInfo(); + // $this->stop(); not exit + }); + while (true) { $line = Interact::readln("$prefix > "); if (strlen($line) < 5) { @@ -548,13 +551,26 @@ protected function startInteractiveShell(): void } $args = LineParser::parseIt($line); - $this->debugf('input line: %s, parsed args: %s', $line, DataHelper::toString($args)); + $this->debugf('ishell - input line: %s, split args: %s', $line, DataHelper::toString($args)); // reload and parse args - $in->parse($args); - $in->setFullScript($line); + $this->flags->resetResults(); + // $this->flags->setTrustedOpt('debug'); + $this->flags->parse($args); + // $in->parse($args); + // $in->setFullScript($line); + + // fire event ON_BEFORE_RUN, if it is registered. + $this->fire(ConsoleEvent::ON_BEFORE_RUN, $this); + if (!$this->beforeRun()) { + continue; + } + + // do run ... + $command = $this->commandName; + $this->dispatch($command, $this->flags->getRawArgs()); - $this->run(false); + $this->debugf('ishell - the command "%s" run completed', $command); $out->println(''); } diff --git a/src/Component/Formatter/JSONPretty.php b/src/Component/Formatter/JSONPretty.php new file mode 100644 index 00000000..d1819f1a --- /dev/null +++ b/src/Component/Formatter/JSONPretty.php @@ -0,0 +1,53 @@ +setData((array)json_decode($json, true)); + + return $self; + } + + /** + * @return string + */ + public function format(): string + { + $buf = Str\StrBuffer::new(); + + foreach ($this->data as $key => $item) { + // TODO + } + + return $buf->toString(); + } + + /** + * @param array|iterable $data + */ + public function setData($data): void + { + $this->data = $data; + } +} diff --git a/src/Component/MessageFormatter.php b/src/Component/MessageFormatter.php index 80d4db84..0935266b 100644 --- a/src/Component/MessageFormatter.php +++ b/src/Component/MessageFormatter.php @@ -42,7 +42,7 @@ abstract class MessageFormatter implements FormatterInterface * * @return MessageFormatter */ - public static function create(array $config = []): self + public static function new(array $config = []): self { return new static($config); } diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index d1181325..e2db1a74 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -245,6 +245,11 @@ protected function annotationVars(): array */ protected function initFlagsParser(Input $input): void { + // if on interactive shell environment(GlobalOption::ISHELL=true) + if ($this->flags->isLocked()) { + return; + } + $input->setFs($this->flags); $this->flags->setDesc(self::getDesc()); $this->flags->setScriptName(self::getName()); From 40a18a55e2fd70767a10fcf615054f991133029f Mon Sep 17 00:00:00 2001 From: Inhere Date: Sat, 25 Sep 2021 21:30:12 +0800 Subject: [PATCH 166/258] update some for built in command --- src/BuiltIn/SelfUpdateCommand.php | 16 ++++++++++------ src/Concern/SubCommandsWareTrait.php | 6 +++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/BuiltIn/SelfUpdateCommand.php b/src/BuiltIn/SelfUpdateCommand.php index 2dc7d9aa..9c72b48b 100644 --- a/src/BuiltIn/SelfUpdateCommand.php +++ b/src/BuiltIn/SelfUpdateCommand.php @@ -46,6 +46,10 @@ class SelfUpdateCommand extends Command /** * Execute the command. * + * @options + * --check bool;check + * --rollback bool;Rollback to prev version + * * @param Input $input * @param Output $output */ @@ -57,13 +61,13 @@ protected function execute(Input $input, Output $output) /** * Check for ancilliary options */ - if ($input->getOption('rollback')) { + if ($this->flags->getOpt('rollback')) { $this->rollback(); return; } - if ($input->getOption('check')) { + if ($this->flags->getOpt('check')) { $this->printAvailableUpdates(); return; @@ -72,25 +76,25 @@ protected function execute(Input $input, Output $output) /** * Update to any specified stability option */ - if ($input->getOption('dev')) { + if ($this->flags->getOpt('dev')) { $this->updateToDevelopmentBuild(); return; } - if ($input->getOption('pre')) { + if ($this->flags->getOpt('pre')) { $this->updateToPreReleaseBuild(); return; } - if ($input->getOption('stable')) { + if ($this->flags->getOpt('stable')) { $this->updateToStableBuild(); return; } - if ($input->getOption('non-dev')) { + if ($this->flags->getOpt('non-dev')) { $this->updateToMostRecentNonDevRemote(); return; diff --git a/src/Concern/SubCommandsWareTrait.php b/src/Concern/SubCommandsWareTrait.php index 10ce7458..4febd0cd 100644 --- a/src/Concern/SubCommandsWareTrait.php +++ b/src/Concern/SubCommandsWareTrait.php @@ -83,7 +83,7 @@ protected function dispatchCommand(string $name): void * * @throws InvalidArgumentException */ - public function addCommand(string $name, $handler = null, array $options = []): void + public function addSub(string $name, $handler = null, array $options = []): void { if (!$handler && class_exists($name)) { /** @var Command $name name is an command class */ @@ -151,9 +151,9 @@ public function addCommands(array $commands): void { foreach ($commands as $name => $handler) { if (is_int($name)) { - $this->addCommand($handler); + $this->addSub($handler); } else { - $this->addCommand($name, $handler); + $this->addSub($name, $handler); } } } From 87c8fffee34b333bdfea6c4e77cfe3659f021935 Mon Sep 17 00:00:00 2001 From: Inhere Date: Sun, 26 Sep 2021 17:44:52 +0800 Subject: [PATCH 167/258] fix: method docs parse error on multi line --- src/Annotate/DocblockRules.php | 21 ++++++++--------- test/Annotate/DocblockRulesTest.php | 35 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/Annotate/DocblockRules.php b/src/Annotate/DocblockRules.php index bfd75c4d..cc1ec483 100644 --- a/src/Annotate/DocblockRules.php +++ b/src/Annotate/DocblockRules.php @@ -138,17 +138,18 @@ public function parse(): self */ protected function parseMultiLines(array $lines): array { - $index = 0; + $index = $keyWidth = 0; $rules = $kvRules = []; - $keyWidth = 16; // with an default value. + $sepChar = ' '; + $sepLen = strlen($sepChar); foreach ($lines as $line) { $trimmed = trim($line); if (!$trimmed) { continue; } - $nodes = Str::explode($trimmed, ' ', 2); + $nodes = Str::explode($trimmed, $sepChar, 2); if (!isset($nodes[1])) { if ($index === 0) { // invalid first line continue; @@ -159,12 +160,6 @@ protected function parseMultiLines(array $lines): array continue; } - // TIP: special - if line indent space len gt keyWidth, is desc message of multi line. - if (!trim(substr($line, 0, $keyWidth))) { - $rules[$index - 1][1] .= "\n" . $trimmed; // multi desc message. - continue; - } - $name = trim($nodes[0], '.'); if (!preg_match('/^[\w ,-]{0,48}$/', $name)) { if ($index === 0) { // invalid first line @@ -176,9 +171,15 @@ protected function parseMultiLines(array $lines): array continue; } - $nameLen = strlen($name); + $nameLen = strlen($name) + $sepLen; $keyWidth = $nameLen > $keyWidth ? $nameLen : $keyWidth; + // TIP: special - if line indent space len gt keyWidth, is desc message of multi line. + if (!trim(substr($line, 0, $keyWidth))) { + $rules[$index - 1][1] .= "\n" . $trimmed; // multi desc message. + continue; + } + // append $rules[$index] = [$name, $nodes[1]]; $index++; diff --git a/test/Annotate/DocblockRulesTest.php b/test/Annotate/DocblockRulesTest.php index e856448b..ad7bb6cc 100644 --- a/test/Annotate/DocblockRulesTest.php +++ b/test/Annotate/DocblockRulesTest.php @@ -69,6 +69,41 @@ public function testParse_byDocComment(): void $this->assertArrayHasKey('repoPath', $args); } + public function testParse_byDocComment_mlOptions(): void + { + $dr = DocblockRules::new(); + $dr->setDocTagsByDocblock(<<parse(); + + $this->assertNotEmpty($opts = $dr->getOptRules()); + $this->assertCount(3, $opts); + vdump($opts); + $this->assertStringContainsString('2 Only match history path list', $opts['--flag']); + + $this->assertNotEmpty($args = $dr->getArgRules()); + $this->assertCount(1, $args); + $this->assertArrayHasKey('keywords', $args); + } + public function testParse_byDocComment_complex(): void { $dr = DocblockRules::new(); From 3e06b079bd022903a6af725b6084a2000856253e Mon Sep 17 00:00:00 2001 From: Inhere Date: Sun, 26 Sep 2021 17:51:48 +0800 Subject: [PATCH 168/258] breaking: delete some Input methods, use flags instead the input --- examples/Controller/HomeController.php | 22 +-- examples/Controller/InteractController.php | 12 +- src/AbstractApplication.php | 13 +- src/BuiltIn/PharController.php | 16 +- src/Concern/ApplicationHelpTrait.php | 11 +- src/Concern/AttachApplicationTrait.php | 2 +- src/Concern/ControllerHelpTrait.php | 11 +- src/Concern/InputArgumentsTrait.php | 125 -------------- src/Concern/InputOptionsTrait.php | 179 +-------------------- src/Handler/AbstractHandler.php | 6 +- src/IO/AbstractInput.php | 37 +++++ src/IO/Input.php | 26 --- src/Util/FormatUtil.php | 4 + 13 files changed, 90 insertions(+), 374 deletions(-) diff --git a/examples/Controller/HomeController.php b/examples/Controller/HomeController.php index 494dd7ce..11494f77 100644 --- a/examples/Controller/HomeController.php +++ b/examples/Controller/HomeController.php @@ -4,7 +4,6 @@ use Inhere\Console\Component\Symbol\ArtFont; use Inhere\Console\Controller; -use Inhere\Console\IO\Input; use Inhere\Console\Util\Interact; use Inhere\Console\Util\ProgressBar; use Inhere\Console\Util\Show; @@ -123,15 +122,8 @@ protected function defArgConfigure(): void } // desc set at $this->commandMetas. - public function defArgCommand(): void + public function defArgCommand(FlagsParser $fs): void { - $this->output->dump( - $this->input->getArgs(), - $this->input->getOpts(), - $this->input->getBoolOpt('y') - ); - - $fs = $this->curActionFlags(); $this->output->dump( $fs->getArgs(), $fs->getOpts(), @@ -198,12 +190,12 @@ public function colorCheckCommand(): void * output art font text * * @options - * --font Set the art font name(allow: {internalFonts}). - * --italic Set the art font type is italic. - * --style Set the art font style. + * --font Set the art font name(allow: {internalFonts}). + * --italic bool;Set the art font type is italic. + * --style Set the art font style. * @return int */ - public function artFontCommand(): int + public function artFontCommand(FlagsParser $fs): int { $name = $this->input->getLongOpt('font', '404'); @@ -212,8 +204,8 @@ public function artFontCommand(): int } ArtFont::create()->show($name, ArtFont::INTERNAL_GROUP, [ - 'type' => $this->input->getBoolOpt('italic') ? 'italic' : '', - 'style' => $this->input->getOpt('style'), + 'type' => $fs->getOpt('italic') ? 'italic' : '', + 'style' => $fs->getOpt('style'), ]); return 0; diff --git a/examples/Controller/InteractController.php b/examples/Controller/InteractController.php index 1f7fc7a4..0d802de7 100644 --- a/examples/Controller/InteractController.php +++ b/examples/Controller/InteractController.php @@ -12,6 +12,7 @@ use Inhere\Console\Util\Interact; use Inhere\Console\Util\Show; use Toolkit\Cli\Util\Terminal; +use Toolkit\PFlag\FlagsParser; use function preg_match; /** @@ -97,15 +98,16 @@ public function askCommand(): void /** * This is a demo for use Interact::limitedAsk() method + * * @options - * --nv Not use validator. - * --limit limit times.(default: 3) + * --nv bool;Not use validator. + * --limit int;limit times.(default: 3) */ - public function limitedAskCommand(): void + public function limitedAskCommand(FlagsParser $fs): void { - $times = (int)$this->input->getOpt('limit', 3); + $times = $fs->getOpt('limit', 3); - if ($this->input->getBoolOpt('nv')) { + if ($fs->getOpt('nv')) { $a = Interact::limitedAsk('you name is: ', '', null, $times); } else { $a = Interact::limitedAsk('you name is: ', '', static function ($val) { diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index ac9514c1..b35d79c2 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -25,6 +25,7 @@ use Inhere\Console\Util\Interact; use InvalidArgumentException; use Throwable; +use Toolkit\Cli\Helper\FlagHelper; use Toolkit\Cli\Style; use Toolkit\Cli\Util\LineParser; use Toolkit\PFlag\SFlags; @@ -353,7 +354,7 @@ public function stop(int $code = 0) */ public function runWithArgs(array $args) { - $this->input->setArgs($args); + $this->input->setFlags($args); return $this->run(false); } @@ -473,10 +474,16 @@ protected function handleGlobalCommand(string $command): bool return true; } - $this->logf(Console::VERB_DEBUG, 'run the global command: %s', $command); + $this->debugf('run the global command: %s', $command); switch ($command) { case 'help': - $this->showHelpInfo($this->input->getFirstArg()); + $cmd = ''; + $args = $this->flags->getRawArgs(); + if ($args && FlagHelper::isValidName($args[0])) { + $cmd = $args[0]; + } + + $this->showHelpInfo($cmd); break; case 'list': $this->showCommandList(); diff --git a/src/BuiltIn/PharController.php b/src/BuiltIn/PharController.php index 282bd2ef..bdd26436 100644 --- a/src/BuiltIn/PharController.php +++ b/src/BuiltIn/PharController.php @@ -100,7 +100,10 @@ public function packCommand(Input $input, Output $output, FlagsParser $fs): int $workDir = $input->getPwd(); $dir = $fs->getOpt('dir') ?: $workDir; - $cpr = $this->configCompiler($dir); + // get config file + $confFile = $fs->getOpt('config') ?: $dir . '/phar.build.inc'; + + $cpr = $this->configCompiler($dir, $confFile); $refresh = $fs->getOpt('refresh'); $outFile = $fs->getOpt('output', $this->defPkgName); @@ -155,10 +158,11 @@ public function packCommand(Input $input, Output $output, FlagsParser $fs): int /** * @param string $dir + * @param string $confFile * * @return PharCompiler */ - protected function configCompiler(string $dir): PharCompiler + protected function configCompiler(string $dir, string $confFile): PharCompiler { // create compiler $compiler = new PharCompiler($dir); @@ -171,14 +175,12 @@ protected function configCompiler(string $dir): PharCompiler } // use config file - $configFile = $this->input->getSameOpt(['c', 'config']) ?: $dir . '/phar.build.inc'; - - if ($configFile && is_file($configFile)) { - require $configFile; + if ($confFile && is_file($confFile)) { + require $confFile; return $compiler->in($dir); } - throw new RuntimeException("The phar build config file not exists! File: $configFile"); + throw new RuntimeException("The phar build config file not exists! File: $confFile"); } /** diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index a65caf41..d105cadc 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -117,10 +117,7 @@ public function showHelpInfo(string $command = ''): void // display help for a special command if ($command) { $this->debugf('display command help by use help: help COMMAND'); - $in->setCommand($command); - $in->setSOpt('h', true); - $in->clearArgs(); - $this->dispatch($command); + $this->dispatch($command, ['-h']); return; } @@ -183,7 +180,7 @@ public function showCommandList(): void /** @var Input $input */ $input = $this->input; // has option: --auto-completion - $autoComp = $input->getBoolOpt('auto-completion'); + $autoComp = $input->getOpt('auto-completion'); // has option: --shell-env $shellEnv = (string)$input->getLongOpt('shell-env', ''); // input is an path: /bin/bash @@ -325,7 +322,7 @@ protected function dumpAutoCompletion(string $shellEnv, array $data): void // info $glue = ' '; - $genFile = $input->getStringOpt('gen-file', 'none'); + $genFile = $input->getOpt('gen-file', 'none'); $tplDir = dirname(__DIR__, 2) . '/resource/templates'; if ($shellEnv === 'bash') { @@ -347,7 +344,7 @@ protected function dumpAutoCompletion(string $shellEnv, array $data): void } // new: support custom tpl file for gen completion script - $userTplFile = $input->getStringOpt('tpl-file'); + $userTplFile = $input->getOpt('tpl-file'); if ($userTplFile && file_exists($userTplFile)) { $tplFile = $userTplFile; } diff --git a/src/Concern/AttachApplicationTrait.php b/src/Concern/AttachApplicationTrait.php index ebd39c83..4ce834c3 100644 --- a/src/Concern/AttachApplicationTrait.php +++ b/src/Concern/AttachApplicationTrait.php @@ -83,7 +83,7 @@ public function isInteractive(): bool return $this->app->isInteractive(); } - $value = $this->input->getBoolOpt(GlobalOption::NO_INTERACTIVE); + $value = $this->input->getOpt(GlobalOption::NO_INTERACTIVE); return $value === false; } diff --git a/src/Concern/ControllerHelpTrait.php b/src/Concern/ControllerHelpTrait.php index 53347e92..fa8a49ed 100644 --- a/src/Concern/ControllerHelpTrait.php +++ b/src/Concern/ControllerHelpTrait.php @@ -32,13 +32,14 @@ trait ControllerHelpTrait * @options * -s, --search Search command by input keywords * --format Set the help information dump format(raw, xml, json, markdown) + * * @return int * @example - * {script} {name} -h - * {script} {name}:help - * {script} {name}:help index - * {script} {name}:index -h - * {script} {name} index + * {binName} {name} -h + * {binName} {name}:help + * {binName} {name}:help index + * {binName} {name}:index -h + * {binName} {name} index */ public function helpCommand(): int { diff --git a/src/Concern/InputArgumentsTrait.php b/src/Concern/InputArgumentsTrait.php index 76f831bd..5483f996 100644 --- a/src/Concern/InputArgumentsTrait.php +++ b/src/Concern/InputArgumentsTrait.php @@ -167,131 +167,6 @@ public function getRequiredArg($name, string $errMsg = '') throw new PromptException($errMsg); } - /** - * Get first argument - * - * @param string $default - * - * @return string - */ - public function getFirstArg(string $default = ''): string - { - return $this->get(0, $default); - } - - /** - * Get second argument - * - * @param string $default - * - * @return string - */ - public function getSecondArg(string $default = ''): string - { - return $this->get(1, $default); - } - - /** - * Get an string argument value - * - * @param string|int $key - * @param string $default - * - * @return string - */ - public function getStringArg($key, string $default = ''): string - { - return (string)$this->get($key, $default); - } - - /** - * Get an int argument value - * - * @param string|int $key - * @param int $default - * - * @return int - */ - public function getInt($key, int $default = 0): int - { - return $this->getIntArg($key, $default); - } - - /** - * Get an int argument value - * - * @param string|int $key - * @param int $default - * - * @return int - */ - public function getIntArg($key, int $default = 0): int - { - $value = $this->get($key); - - return $value === null ? $default : (int)$value; - } - - /** - * Get an array argument value - * - * @param string|int $key - * @param array $default - * - * @return array - */ - public function getArrayArg($key, array $default = []): array - { - $value = $this->get($key); - if (is_array($value)) { - return $value; - } - - return $value ? [$value] : $default; - } - - /** - * Get same args value - * eg: des = description - * - * ```php - * $input->sameArg('des, description'); - * $input->sameArg(['des', 'description']); - * ``` - * - * @param string|array $names - * @param mixed $default - * - * @return mixed - */ - public function getSameArg($names, $default = null) - { - if (is_string($names)) { - $names = array_map('trim', explode(',', $names)); - } elseif (!is_array($names)) { - $names = (array)$names; - } - - foreach ($names as $name) { - if ($this->hasArg($name)) { - return $this->get($name); - } - } - - return $default; - } - - /** - * @param string|array $names - * @param mixed $default - * - * @return mixed - */ - public function sameArg($names, $default = null) - { - return $this->getSameArg($names, $default); - } - /** * clear args */ diff --git a/src/Concern/InputOptionsTrait.php b/src/Concern/InputOptionsTrait.php index 7182d462..f834eaa2 100644 --- a/src/Concern/InputOptionsTrait.php +++ b/src/Concern/InputOptionsTrait.php @@ -48,10 +48,10 @@ public function getOpt(string $name, $default = null) { // It's long-opt if (isset($name[1])) { - return $this->lOpt($name, $default); + return $this->getLongOpt($name, $default); } - return $this->sOpt($name, $default); + return $this->getShortOpt($name, $default); } /** @@ -86,99 +86,6 @@ public function getRequiredOpt(string $name, string $errMsg = '') throw new PromptException($errMsg); } - /** - * Get an string option(long/short) value - * - * @param string $name - * @param string $default - * - * @return string - */ - public function getStringOpt(string $name, string $default = ''): string - { - return (string)$this->getOpt($name, $default); - } - - /** - * Get an string option(long/short) value - * - * @param string|string[] $names eg 'n,name' OR ['n', 'name'] - * @param string $default - * - * @return string - */ - public function getSameStringOpt($names, string $default = ''): string - { - return (string)$this->getSameOpt($names, $default); - } - - /** - * Get an int option(long/short) value - * - * @param string $name - * @param int $default - * - * @return int - */ - public function getIntOpt(string $name, int $default = 0): int - { - return (int)$this->getOpt($name, $default); - } - - /** - * Get an int option(long/short) value - * - * @param string|string[] $names eg 'l,length' OR ['l', 'length'] - * @param int $default - * - * @return int - */ - public function getSameIntOpt($names, int $default = 0): int - { - return (int)$this->getSameOpt($names, $default); - } - - /** - * Get (long/short)option value(bool) - * eg: -h --help - * - * @param string $name - * @param bool $default - * - * @return bool - */ - public function getBoolOpt(string $name, bool $default = false): bool - { - return (bool)$this->getOpt($name, $default); - } - - /** - * Get (long/short)option value(bool) - * eg: -h --help - * - * @param string|string[] $names eg 'n,name' OR ['n', 'name'] - * @param bool $default - * - * @return bool - */ - public function getSameBoolOpt($names, bool $default = false): bool - { - return (bool)$this->getSameOpt($names, $default); - } - - /** - * Alias of the getBoolOpt() - * - * @param string $name - * @param bool $default - * - * @return bool - */ - public function boolOpt(string $name, bool $default = false): bool - { - return (bool)$this->getOpt($name, $default); - } - /** * check option exists * @@ -251,19 +158,6 @@ public function getSameOpt($names, $default = null) return $default; } - /** - * Alias of the getSameOpt() - * - * @param string|array $names - * @param mixed $default - * - * @return bool|mixed|null - */ - public function sameOpt($names, $default = null) - { - return $this->getSameOpt($names, $default); - } - /** * @return array */ @@ -290,32 +184,6 @@ public function clearOpts(): void /************************** short-opts **********************/ - /** - * Get short-opt value - * - * @param string $name - * @param null $default - * - * @return mixed|null - */ - public function sOpt(string $name, $default = null) - { - return $this->sOpts[$name] ?? $default; - } - - /** - * Alias of the sOpt() - * - * @param string $name - * @param null $default - * - * @return mixed|null - */ - public function shortOpt(string $name, $default = null) - { - return $this->sOpts[$name] ?? $default; - } - /** * Alias of the sOpt() * @@ -359,21 +227,6 @@ public function findOneShortOpts(array $names): string return ''; } - /** - * get short-opt value(bool) - * - * @param string $name - * @param bool|mixed $default - * - * @return bool - */ - public function sBoolOpt(string $name, $default = false): bool - { - $val = $this->sOpt($name); - - return is_bool($val) ? $val : (bool)$default; - } - /** * @return array */ @@ -431,19 +284,6 @@ public function lOpt(string $name, $default = null) return $this->lOpts[$name] ?? $default; } - /** - * Alias of the getLongOpt() - * - * @param string $name - * @param null $default - * - * @return mixed|null - */ - public function longOpt(string $name, $default = null) - { - return $this->lOpts[$name] ?? $default; - } - /** * Get long-opt value * @@ -469,21 +309,6 @@ public function hasLOpt(string $name): bool return isset($this->lOpts[$name]); } - /** - * get long-opt value(bool) - * - * @param string $name - * @param bool|mixed $default - * - * @return bool - */ - public function lBoolOpt(string $name, $default = false): bool - { - $val = $this->lOpt($name); - - return is_bool($val) ? $val : (bool)$default; - } - /** * @return array */ diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index e2db1a74..53aa53e1 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -336,9 +336,9 @@ protected function doRun(array $args) $first = $args[0]; $rName = $this->resolveAlias($first); - if ($this->isSub($rName)) { - // TODO - } + // TODO + // if ($this->isSub($rName)) { + // } } // some prepare check diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index 52316199..275c9940 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -16,7 +16,9 @@ use function array_shift; use function basename; use function getcwd; +use function implode; use function is_int; +use function is_string; use function trim; /** @@ -114,6 +116,32 @@ public function __toString(): string */ abstract public function toString(): string; + /** + * @param array $rawFlags + */ + protected function collectInfo(array $rawFlags): void + { + $this->getPwd(); + if (!$rawFlags) { + return; + } + + $this->tokens = $rawFlags; + + // first is bin file + if (isset($rawFlags[0]) && is_string($rawFlags[0])) { + $this->scriptFile = array_shift($rawFlags); + + // bin name + $this->scriptName = basename($this->scriptFile); + } + + $this->flags = $rawFlags; // no script + + // full script + $this->fullScript = implode(' ', $rawFlags); + } + /** * find command name, it is first argument. * TIP: will reset args data after founded. @@ -285,6 +313,14 @@ public function getFlags(): array return $this->flags; } + /** + * @param array $flags + */ + public function setFlags(array $flags): void + { + $this->flags = $flags; + } + /** * @return array */ @@ -307,6 +343,7 @@ public function getTokens(): array public function setTokens(array $tokens): void { $this->tokens = $tokens; + $this->collectInfo($tokens); } /** diff --git a/src/IO/Input.php b/src/IO/Input.php index 19b88620..1873175c 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -62,32 +62,6 @@ public function __construct(array $args = null, bool $parsing = true) } } - /** - * @param array $rawFlags - */ - protected function collectInfo(array $rawFlags): void - { - $this->getPwd(); - if (!$rawFlags) { - return; - } - - $this->tokens = $rawFlags; - - // first is bin file - if (isset($rawFlags[0]) && is_string($rawFlags[0])) { - $this->scriptFile = array_shift($rawFlags); - - // bin name - $this->scriptName = basename($this->scriptFile); - } - - $this->flags = $rawFlags; // no script - - // full script - $this->fullScript = implode(' ', $rawFlags); - } - /** * re-parse args/opts from given args * diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index 37a5b3d7..6ae3a1b6 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -246,6 +246,10 @@ public static function spliceKeyValue(array $data, array $opts = []): string $hasKey = !is_int($key); $fmtLine = $opts['leftChar']; + // if ($hasKey) { + // $fmtLine = $opts['leftChar']; + // } + if ($hasKey && $keyWidth) { $strKey = Str::pad((string)$key, $keyWidth, ' ', $keyPadPos); $fmtLine .= ColorTag::wrap($strKey, $keyStyle) . $opts['sepChar']; From 71d41b127317eb65536c9512c7edc2c151641233 Mon Sep 17 00:00:00 2001 From: Inhere Date: Sun, 26 Sep 2021 20:43:03 +0800 Subject: [PATCH 169/258] breaking: remove more not used methods and class --- .php_cs => .php-cs-fixer.php | 0 examples/Command/TestCommand.php | 2 +- .../deprecated}/InputDefinition.php | 2 +- src/AbstractApplication.php | 2 +- src/Application.php | 80 +++++++------- src/Component/Formatter/HelpPanel.php | 12 +-- src/Concern/ApplicationHelpTrait.php | 10 +- src/Concern/CommandHelpTrait.php | 95 ---------------- src/Concern/InputArgumentsTrait.php | 3 +- src/Concern/InputOptionsTrait.php | 101 ++---------------- src/Contract/ApplicationInterface.php | 36 +++---- src/Controller.php | 4 +- src/Handler/AbstractHandler.php | 23 +--- test/ApplicationTest.php | 14 ++- test/IO/InputDefinitionTest.php | 24 ----- test/IO/InputTest.php | 29 ----- 16 files changed, 92 insertions(+), 345 deletions(-) rename .php_cs => .php-cs-fixer.php (100%) rename {src/IO => resource/deprecated}/InputDefinition.php (99%) delete mode 100644 test/IO/InputDefinitionTest.php diff --git a/.php_cs b/.php-cs-fixer.php similarity index 100% rename from .php_cs rename to .php-cs-fixer.php diff --git a/examples/Command/TestCommand.php b/examples/Command/TestCommand.php index 9153d5f3..2428ff92 100644 --- a/examples/Command/TestCommand.php +++ b/examples/Command/TestCommand.php @@ -43,7 +43,7 @@ protected function commands(): array * --long,-s option description 1 * --opt option description 2 * - * @param $input + * @param Input $input * @param Output $output */ public function execute(Input $input, Output $output) diff --git a/src/IO/InputDefinition.php b/resource/deprecated/InputDefinition.php similarity index 99% rename from src/IO/InputDefinition.php rename to resource/deprecated/InputDefinition.php index b5b4ff12..8074785d 100644 --- a/src/IO/InputDefinition.php +++ b/resource/deprecated/InputDefinition.php @@ -6,7 +6,7 @@ * Time: 下午8:14 */ -namespace Inhere\Console\IO; +namespace Deprecated; use Inhere\Console\Util\Helper; use InvalidArgumentException; diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index b35d79c2..85188d6d 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -92,7 +92,7 @@ abstract class AbstractApplication implements ApplicationInterface /** @var array Application config data */ protected $config = [ 'name' => 'My Console Application', - 'description' => 'This is my console application', + 'desc' => 'This is my console application', 'version' => '0.5.1', 'homepage' => '', // can provide you app homepage url 'publishAt' => '2017.03.24', diff --git a/src/Application.php b/src/Application.php index fa298a4c..2ffd4513 100644 --- a/src/Application.php +++ b/src/Application.php @@ -9,6 +9,7 @@ namespace Inhere\Console; use Closure; +use Inhere\Console\Contract\ApplicationInterface; use Inhere\Console\Contract\ControllerInterface; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; @@ -17,6 +18,7 @@ use RuntimeException; use SplFileInfo; use Throwable; +use Toolkit\PFlag\SFlags; use Toolkit\Stdlib\Helper\DataHelper; use function array_unshift; use function class_exists; @@ -40,8 +42,8 @@ class Application extends AbstractApplication /** * Class constructor. * - * @param array $config - * @param Input|null $input + * @param array $config + * @param Input|null $input * @param Output|null $output */ public function __construct(array $config = [], Input $input = null, Output $output = null) @@ -58,16 +60,10 @@ public function __construct(array $config = [], Input $input = null, Output $out /** * {@inheritdoc} */ - public function controller(string $name, $class = null, $option = null) + public function controller(string $name, $class = null, array $config = []): ApplicationInterface { - if (is_string($option)) { - $option = [ - 'description' => $option, - ]; - } - - $this->logf(Console::VERB_CRAZY, 'load group controller: %s', $name); - $this->router->addGroup($name, $class, (array)$option); + $this->logf(Console::VERB_CRAZY, 'register group controller: %s', $name); + $this->router->addGroup($name, $class, $config); return $this; } @@ -75,29 +71,29 @@ public function controller(string $name, $class = null, $option = null) /** * Add group/controller * - * @param string $name + * @param string $name * @param string|ControllerInterface|null $class The controller class - * @param null|array|string $option + * @param array $config * * @return Application|Contract\ApplicationInterface * @see controller() */ - public function addGroup(string $name, $class = null, $option = null) + public function addGroup(string $name, $class = null, array $config = []): ApplicationInterface { - return $this->controller($name, $class, $option); + return $this->controller($name, $class, $config); } /** - * @param string $name + * @param string $name * @param string|ControllerInterface|null $class The controller class - * @param null|array|string $option + * @param array $config * * @return Application|Contract\ApplicationInterface * @see controller() */ - public function addController(string $name, $class = null, $option = null) + public function addController(string $name, $class = null, array $config = []): ApplicationInterface { - return $this->controller($name, $class, $option); + return $this->controller($name, $class, $config); } /** @@ -134,16 +130,10 @@ public function addControllers(array $controllers): void /** * {@inheritdoc} */ - public function command(string $name, $handler = null, $option = null) + public function command(string $name, $handler = null, array $config = []) { - if (is_string($option)) { - $option = [ - 'description' => $option, - ]; - } - - $this->logf(Console::VERB_CRAZY, 'load application command: %s', $name); - $this->router->addCommand($name, $handler, (array)$option); + $this->logf(Console::VERB_CRAZY, 'register alone command: %s', $name); + $this->router->addCommand($name, $handler, $config); return $this; } @@ -152,15 +142,15 @@ public function command(string $name, $handler = null, $option = null) * add command * * @param string $name - * @param null $handler - * @param null $option + * @param null|mixed $handler + * @param array $config * * @return Application * @see command() */ - public function addCommand(string $name, $handler = null, $option = null): self + public function addCommand(string $name, $handler = null, array $config = []): self { - return $this->command($name, $handler, $option); + return $this->command($name, $handler, $config); } /** @@ -260,7 +250,7 @@ protected function getFileFilter(): callable /** * @param string $name command name or command ID or command path. - * @param array $args + * @param array $args * * @return int|mixed * @throws Throwable @@ -322,10 +312,10 @@ public function dispatch(string $name, array $args = []) /** * run a independent command * - * @param string $name Command name + * @param string $name Command name * @param Closure|string $handler Command class or handler func - * @param array $options - * @param array $args + * @param array $options + * @param array $args * * @return mixed * @throws Throwable @@ -333,10 +323,16 @@ public function dispatch(string $name, array $args = []) protected function runCommand(string $name, $handler, array $options, array $args) { if (is_object($handler) && method_exists($handler, '__invoke')) { - if ($this->input->getSameOpt(['h', 'help'])) { - $desc = $options['description'] ?? 'No command description message'; + $fs = SFlags::new(); + $fs->addOptsByRules(GlobalOption::getAloneOptions()); + $desc = $options['desc'] ?? 'No command description message'; + $fs->setDesc($desc); - return $this->output->write($desc); + // save to input object + $this->input->setFs($fs); + + if (!$fs->parse($args)) { + return 0; // render help } $result = $handler($this->input, $this->output); @@ -364,10 +360,12 @@ protected function runCommand(string $name, $handler, array $options, array $arg * Execute an action in a group command(controller) * * @param array $info Matched route info + * * @psalm-param array{action: string} $info Matched route info + * * @param array $options * @param array $args - * @param bool $detachedRun + * @param bool $detachedRun * * @return mixed * @throws Throwable @@ -375,7 +373,7 @@ protected function runCommand(string $name, $handler, array $options, array $arg protected function runAction(array $info, array $options, array $args, bool $detachedRun = false) { $controller = $this->createController($info); - $controller::setDesc($options['description'] ?? ''); + $controller::setDesc($options['desc'] ?? ''); if ($detachedRun) { $controller->setDetached(); diff --git a/src/Component/Formatter/HelpPanel.php b/src/Component/Formatter/HelpPanel.php index 9a055ade..6ac1a7d9 100644 --- a/src/Component/Formatter/HelpPanel.php +++ b/src/Component/Formatter/HelpPanel.php @@ -30,7 +30,7 @@ class HelpPanel extends MessageFormatter /** * help panel keys */ - public const DESC = 'description'; + public const DESC = 'desc'; public const USAGE = 'usage'; @@ -86,8 +86,8 @@ public static function show(array $config): void 'indentDes' => ' ', ]; $config = array_merge([ - 'description' => '', - 'usage' => '', + 'desc' => '', + 'usage' => '', 'commands' => [], 'arguments' => [], @@ -108,9 +108,9 @@ public static function show(array $config): void } // description - if ($config['description']) { - $parts[] = "{$option['indentDes']}{$config['description']}\n"; - unset($config['description']); + if ($config['desc']) { + $parts[] = "{$option['indentDes']}{$config['desc']}\n"; + unset($config['desc']); } // now, render usage,commands,arguments,options,examples ... diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index d105cadc..5016a50a 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -35,7 +35,6 @@ use function str_replace; use function strpos; use function strtr; -use function vdump; use const PHP_EOL; use const PHP_OS; use const PHP_VERSION; @@ -123,8 +122,6 @@ public function showHelpInfo(string $command = ''): void $this->debugf('display application help by input -h, --help'); $this->fire(ConsoleEvent::BEFORE_RENDER_APP_HELP, $this); - $delimiter = $this->delimiter; - $binName = $in->getScriptName(); // built in options // $globalOptions = self::$globalOptions; @@ -138,6 +135,7 @@ public function showHelpInfo(string $command = ''): void $globalOptions['--gen-file'] = 'The output file for generate auto completion script'; } + $binName = $in->getScriptName(); $helpInfo = [ 'Usage' => "$binName {command} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", 'Options' => FormatUtil::alignOptions($globalOptions), @@ -145,12 +143,10 @@ public function showHelpInfo(string $command = ''): void '- run a command/subcommand:', "$binName test run a independent command", "$binName home index run a subcommand of the group", - sprintf("$binName home%sindex run a subcommand of the group", $delimiter), '', '- display help for command:', "$binName help COMMAND see a command help information", "$binName home index -h see a subcommand help of the group", - sprintf("$binName home%sindex -h see a subcommand help of the group", $delimiter), ], 'Help' => [ 'Generate shell auto completion scripts:', @@ -244,7 +240,7 @@ public function showCommandList(): void /** @var AbstractHandler $command */ if (is_subclass_of($command, CommandInterface::class)) { $desc = $command::getDescription() ?: $placeholder; - } elseif ($msg = $options['description'] ?? '') { + } elseif ($msg = $options['desc'] ?? '') { $desc = $msg; } elseif (is_string($command)) { $desc = 'A handler : ' . $command; @@ -274,7 +270,7 @@ public function showCommandList(): void ksort($internalCommands); Console::startBuffer(); - if ($appDesc = $this->getParam('description', '')) { + if ($appDesc = $this->getParam('desc', '')) { $appVer = $this->getParam('version', ''); Console::writeln(sprintf('%s%s' . PHP_EOL, $appDesc, $appVer ? " (Version: $appVer)" : '')); } diff --git a/src/Concern/CommandHelpTrait.php b/src/Concern/CommandHelpTrait.php index fc594cd5..79850e54 100644 --- a/src/Concern/CommandHelpTrait.php +++ b/src/Concern/CommandHelpTrait.php @@ -164,101 +164,6 @@ public function showHelpByFlagsParser(FlagsParser $fs, array $aliases = [], stri return 0; } - /** - * Display command/action help by parse method annotations - * - * @param string $method - * @param string $action action of an group - * @param array $aliases - * - * @return int - * @throws ReflectionException - */ - protected function showHelpByAnnotations(string $method, string $action = '', array $aliases = []): int - { - $name = $this->input->getCommand(); - - // subcommand: is a console controller subcommand - $rftMth = PhpHelper::reflectMethod($this, $method); - if ($action && !$rftMth->isPublic()) { - $this->write("The command [$name] don't allow access in the class."); - return 0; - } - - $allowedTags = DocblockRules::getAllowedTags(); - $this->logf(Console::VERB_DEBUG, "render help for the command: %s", $this->input->getCommandId()); - - $help = []; - $doc = $this->parseCommentsVars((string)$rftMth->getDocComment()); - $tags = PhpDoc::getTags($doc, [ - 'allow' => $allowedTags, - ]); - - if ($aliases) { - $realName = $action ?: static::getName(); - // command name - $help['Command:'] = sprintf('%s(alias: %s)', $realName, implode(',', $aliases)); - } - - $binName = $this->input->getBinName(); - - $path = $binName . ' ' . $name; - if ($action) { - $group = static::getName(); - $path = "$binName $group $action"; - } - - // is an command object - $isCommand = $this->isAlone(); - foreach ($allowedTags as $tag) { - if (empty($tags[$tag]) || !is_string($tags[$tag])) { - // for alone command - if ($tag === 'description' && $isCommand) { - $help['Description:'] = static::getDesc(); - continue; - } - - if ($tag === 'usage') { - $help['Usage:'] = "$path [--options ...] [arguments ...]"; - } - continue; - } - - // $msg = trim($tags[$tag]); - $message = $tags[$tag]; - $labelName = ucfirst($tag) . ':'; - - // for alone command - if ($tag === 'description' && $isCommand) { - $message = static::getDescription(); - } else { - $message = preg_replace('#(\n)#', '$1 ', $message); - } - - $help[$labelName] = $message; - } - - if (isset($help['Description:'])) { - $description = $help['Description:'] ?: 'No description message for the command'; - $this->write(ucfirst($this->parseCommentsVars($description)) . PHP_EOL); - unset($help['Description:']); - } - - $help['Group Options:'] = null; - $this->beforeRenderCommandHelp($help); - - if ($app = $this->getApp()) { - $help['Global Options:'] = FormatUtil::alignOptions($app->getFlags()->getOptsHelpData()); - } - - $this->output->mList($help, [ - 'sepChar' => ' ', - 'lastNewline' => 0, - ]); - - return 0; - } - /** * @param array $help */ diff --git a/src/Concern/InputArgumentsTrait.php b/src/Concern/InputArgumentsTrait.php index 5483f996..92c5ce26 100644 --- a/src/Concern/InputArgumentsTrait.php +++ b/src/Concern/InputArgumentsTrait.php @@ -4,7 +4,6 @@ use Inhere\Console\Exception\PromptException; use function array_merge; -use function is_array; use function is_int; /** @@ -36,7 +35,7 @@ trait InputArgumentsTrait /** * @param string $name * @param int $index - * @return self|mixed + * @return self */ public function bindArgument(string $name, int $index) { diff --git a/src/Concern/InputOptionsTrait.php b/src/Concern/InputOptionsTrait.php index f834eaa2..d070e4ee 100644 --- a/src/Concern/InputOptionsTrait.php +++ b/src/Concern/InputOptionsTrait.php @@ -3,12 +3,7 @@ namespace Inhere\Console\Concern; use Inhere\Console\Exception\PromptException; -use function array_map; use function array_merge; -use function explode; -use function is_array; -use function is_bool; -use function is_string; /** * Trait InputOptionsTrait @@ -40,7 +35,7 @@ trait InputOptionsTrait * eg: -e dev --name sam * * @param string $name - * @param null $default + * @param null $default * * @return bool|mixed|null */ @@ -58,7 +53,7 @@ public function getOpt(string $name, $default = null) * Alias of the getOpt() * * @param string $name - * @param mixed $default + * @param mixed $default * * @return mixed */ @@ -98,66 +93,6 @@ public function hasOpt(string $name): bool return isset($this->sOpts[$name]) || isset($this->lOpts[$name]); } - /** - * The give options exists - * - * ```php - * $input->hasOneOpt('h,help'); - * $input->hasOneOpt(['h','help']); - * ``` - * - * @param string|array $names - * - * @return bool - */ - public function hasOneOpt($names): bool - { - if (is_string($names)) { - $names = array_map('trim', explode(',', $names)); - } elseif (!is_array($names)) { - $names = (array)$names; - } - - foreach ($names as $name) { - if ($this->hasOpt($name)) { - return true; - } - } - - return false; - } - - /** - * Get same opts value - * eg: -h --help - * - * ```php - * $input->sameOpt('h,help'); - * $input->sameOpt(['h','help']); - * ``` - * - * @param string|string[] $names eg 'n,name' OR ['n', 'name'] - * @param mixed $default - * - * @return bool|mixed|null - */ - public function getSameOpt($names, $default = null) - { - if (is_string($names)) { - $names = array_map('trim', explode(',', $names)); - } elseif (!is_array($names)) { - $names = (array)$names; - } - - foreach ($names as $name) { - if ($this->hasOpt($name)) { - return $this->getOpt($name); - } - } - - return $default; - } - /** * @return array */ @@ -188,7 +123,7 @@ public function clearOpts(): void * Alias of the sOpt() * * @param string $name - * @param null $default + * @param null $default * * @return mixed|null */ @@ -209,24 +144,6 @@ public function hasSOpt(string $name): bool return isset($this->sOpts[$name]); } - /** - * Check multi short-opt exists - * - * @param string[] $names - * - * @return string - */ - public function findOneShortOpts(array $names): string - { - foreach ($names as $name) { - if (isset($this->sOpts[$name])) { - return $name; - } - } - - return ''; - } - /** * @return array */ @@ -237,7 +154,7 @@ public function getShortOpts(): array /** * @param string $name - * @param mixed $value + * @param mixed $value */ public function setSOpt(string $name, $value): void { @@ -254,7 +171,7 @@ public function getSOpts(): array /** * @param array $sOpts - * @param bool $replace + * @param bool $replace */ public function setSOpts(array $sOpts, bool $replace = false): void { @@ -275,7 +192,7 @@ public function clearSOpts(): void * Alias of the getLongOpt() * * @param string $name - * @param null $default + * @param null $default * * @return mixed|null */ @@ -288,7 +205,7 @@ public function lOpt(string $name, $default = null) * Get long-opt value * * @param string $name - * @param null $default + * @param null $default * * @return mixed|null */ @@ -319,7 +236,7 @@ public function getLongOpts(): array /** * @param string $name - * @param mixed $value + * @param mixed $value */ public function setLOpt(string $name, $value): void { @@ -336,7 +253,7 @@ public function getLOpts(): array /** * @param array $lOpts - * @param bool $replace + * @param bool $replace */ public function setLOpts(array $lOpts, bool $replace = false): void { diff --git a/src/Contract/ApplicationInterface.php b/src/Contract/ApplicationInterface.php index 0566aa28..76dda78a 100644 --- a/src/Contract/ApplicationInterface.php +++ b/src/Contract/ApplicationInterface.php @@ -21,13 +21,13 @@ interface ApplicationInterface // event name list public const ON_BEFORE_RUN = 'app.beforeRun'; - public const ON_AFTER_RUN = 'app.afterRun'; + public const ON_AFTER_RUN = 'app.afterRun'; - public const ON_RUN_ERROR = 'app.runError'; + public const ON_RUN_ERROR = 'app.runError'; - public const ON_STOP_RUN = 'app.stopRun'; + public const ON_STOP_RUN = 'app.stopRun'; - public const ON_NOT_FOUND = 'app.notFound'; + public const ON_NOT_FOUND = 'app.notFound'; /** * @param bool $exit @@ -39,11 +39,11 @@ public function run(bool $exit = true); /** * Dispatch input command, exec found command handler. * - * @param string $name Inputted command name. allow: + * @param string $name Inputted command name. allow: * - 'command' * - 'group:action' * - 'group action' - * @param array $args + * @param array $args * * @return int|mixed */ @@ -59,34 +59,30 @@ public function stop(int $code = 0); /** * Register a app group command(by controller) * - * @param string $name The controller name + * @param string $name The controller name * @param string|ControllerInterface $class The controller class - * @param null|array|string $option - * string: define the description message. - * array: - * - aliases The command aliases - * - description The description message + * @param array $config config the controller + * - aliases The command aliases + * - desc The description message * * @return static * @throws InvalidArgumentException */ - public function controller(string $name, $class = null, $option = null); + public function controller(string $name, $class = null, array $config = []): ApplicationInterface; /** * Register a app independent console command * - * @param string|CommandInterface $name + * @param string|CommandInterface $name * @param string|Closure|CommandInterface $handler - * @param null|array|string $option - * string: define the description message. - * array: - * - aliases The command aliases - * - description The description message + * @param array $config config the command + * - aliases The command aliases + * - desc The description message * * @return mixed * @throws InvalidArgumentException */ - public function command(string $name, $handler = null, $option = null); + public function command(string $name, $handler = null, array $config = []); public function showCommandList(); diff --git a/src/Controller.php b/src/Controller.php index 43a5505f..7c75846e 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -311,10 +311,8 @@ public function doRun(array $args) // init flags for subcommand $fs = $this->newActionFlags(); - if (!$this->compatible) { - $this->input->setFs($fs); - } + $this->input->setFs($fs); $this->debugf('load flags by configure method, subcommand: %s', $command); $this->configure(); diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index 53aa53e1..b180c964 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -20,7 +20,6 @@ use Inhere\Console\Contract\CommandHandlerInterface; use Inhere\Console\Contract\CommandInterface; use Inhere\Console\IO\Input; -use Inhere\Console\IO\InputDefinition; use Inhere\Console\IO\Output; use Inhere\Console\Util\Helper; use InvalidArgumentException; @@ -69,21 +68,6 @@ abstract class AbstractHandler implements CommandHandlerInterface */ protected static $coroutine = false; - /** - * Allow display message tags in the command annotation - * - * @var array - */ - protected static $annotationTags = [ - // tag name => multi line align - 'description' => false, - 'usage' => false, - 'arguments' => true, - 'options' => true, - 'example' => true, - 'help' => true, - ]; - /** * @var bool */ @@ -96,11 +80,6 @@ abstract class AbstractHandler implements CommandHandlerInterface */ protected $compatible = true; - /** - * @var InputDefinition|null - */ - protected $definition; - /** * @var string */ @@ -143,7 +122,7 @@ public static function aliases(): array * @param Input $input * @param Output $output */ - // TODO public function __construct(Input $input = null, Output $output = null, InputDefinition $definition = null) + // TODO public function __construct(Input $input = null, Output $output = null) public function __construct(Input $input, Output $output) { $this->input = $input; diff --git a/test/ApplicationTest.php b/test/ApplicationTest.php index 3d580724..3e50239c 100644 --- a/test/ApplicationTest.php +++ b/test/ApplicationTest.php @@ -82,7 +82,19 @@ public function testAddCommandError(): void } } - public function testRunCommand(): void + public function testRunCommand_class(): void + { + $app = $this->newApp([ + './app', + 'test1' + ]); + $app->addCommand(TestCommand::class); + + $ret = $app->run(false); + self::assertSame('Inhere\ConsoleTest\TestCommand::execute', $ret); + } + + public function testRunCommand_callback(): void { $app = $this->newApp([ './app', diff --git a/test/IO/InputDefinitionTest.php b/test/IO/InputDefinitionTest.php deleted file mode 100644 index 50a7022e..00000000 --- a/test/IO/InputDefinitionTest.php +++ /dev/null @@ -1,24 +0,0 @@ -addArg('arg0', Input::OPT_OPTIONAL, 'this is arg0'); - - $this->assertNotEmpty($def->getArgument('arg0')); - } -} diff --git a/test/IO/InputTest.php b/test/IO/InputTest.php index 61d5a05d..1bd8cc19 100644 --- a/test/IO/InputTest.php +++ b/test/IO/InputTest.php @@ -21,33 +21,4 @@ public function testBasic(): void $this->assertSame('app', $in->getScriptName()); $this->assertSame('cmd', $in->getCommand()); } - - public function testArguments(): void - { - $in = new Input(['./bin/app', 'cmd', 'val0', 'val1']); - - $this->assertTrue($in->hasArg(0)); - $this->assertSame('val0', $in->getArgument(0)); - $this->assertSame('val1', $in->getArgument(1)); - - $in = new Input(["bin/kite", "jump", "get", "-"]); - $this->assertTrue($in->hasArg(0)); - $this->assertSame('get', $in->getArgument(0)); - $this->assertSame('-', $in->getArgument(1)); - } - - public function testBindArgument(): void - { - $in = new Input(['./bin/app', 'cmd', 'val0', 'val1']); - - $this->assertTrue($in->hasArg(0)); - $this->assertFalse($in->hasArg('arg0')); - $this->assertFalse($in->hasArg('arg1')); - - $in->bindArgument('arg0', 0); - $this->assertTrue($in->hasArg('arg0')); - - $in->bindArguments(['arg1' => 1]); - $this->assertTrue($in->hasArg('arg1')); - } } From d237b6ba6c66855d94a83560f47605e9f5ade228 Mon Sep 17 00:00:00 2001 From: Inhere Date: Sun, 26 Sep 2021 20:45:01 +0800 Subject: [PATCH 170/258] style: run cs fix for all php files --- .php-cs-fixer.php | 4 ++-- examples/Command/CorCommand.php | 11 ++++----- examples/Command/DemoCommand.php | 9 ++++---- examples/Command/TestCommand.php | 13 ++++++----- examples/Controller/HomeController.php | 9 +++++++- examples/Controller/InteractController.php | 9 ++++---- examples/Controller/ProcessController.php | 13 ++++++----- examples/Controller/ShowController.php | 9 ++++---- examples/commands.php | 12 +++++----- examples/demo/cli-spinner.php | 16 ++++++------- examples/demo/color.php | 8 ++++++- examples/demo/constants.php | 9 ++++---- examples/demo/progress_bar.php | 7 ++++++ examples/demo/progress_bar1.php | 8 +++++++ examples/demo/progress_bar3.php | 12 +++++----- examples/demo/sf2_color.php | 22 +++++++++--------- examples/demo/spinner.php | 13 ++++++----- src/AbstractApplication.php | 25 ++++++++++++--------- src/Annotate/AnnotateRules.php | 8 ++++++- src/Annotate/Attr/CmdArgument.php | 7 ++++++ src/Annotate/Attr/CmdOption.php | 7 ++++++ src/Annotate/Attr/RuleArg.php | 7 ++++++ src/Annotate/Attr/RuleOpt.php | 7 ++++++ src/Annotate/DocblockRules.php | 8 ++++++- src/Application.php | 9 ++++---- src/BuiltIn/DevServerCommand.php | 12 +++++----- src/BuiltIn/PharController.php | 21 ++++++++--------- src/BuiltIn/SelfUpdateCommand.php | 11 ++++----- src/Command.php | 11 ++++----- src/Component/Animation/Animation.php | 9 ++++---- src/Component/CompletionDumper.php | 8 ++++++- src/Component/ConsoleAppIShell.php | 7 ++++++ src/Component/ConsoleRenderer.php | 9 ++++---- src/Component/ErrorHandler.php | 10 ++++----- src/Component/Formatter/Alert.php | 9 ++++---- src/Component/Formatter/Block.php | 9 ++++---- src/Component/Formatter/Body.php | 9 ++++---- src/Component/Formatter/HelpPanel.php | 9 ++++---- src/Component/Formatter/JSONPretty.php | 7 ++++++ src/Component/Formatter/MultiList.php | 7 ++++++ src/Component/Formatter/Padding.php | 7 ++++++ src/Component/Formatter/Panel.php | 9 ++++---- src/Component/Formatter/Section.php | 7 ++++++ src/Component/Formatter/SingleList.php | 7 ++++++ src/Component/Formatter/Table.php | 17 +++++++------- src/Component/Formatter/Title.php | 7 ++++++ src/Component/Formatter/Tree.php | 9 ++++---- src/Component/Interact/AbstractQuestion.php | 7 ++++++ src/Component/Interact/AbstractSelect.php | 8 ++++++- src/Component/Interact/Checkbox.php | 7 ++++++ src/Component/Interact/Choose.php | 8 ++++++- src/Component/Interact/Confirm.php | 7 ++++++ src/Component/Interact/IShell.php | 12 ++++++++-- src/Component/Interact/LimitedAsk.php | 7 ++++++ src/Component/Interact/MultiSelect.php | 8 ++++++- src/Component/Interact/Password.php | 7 ++++++ src/Component/Interact/Question.php | 7 ++++++ src/Component/Interact/SingleSelect.php | 8 ++++++- src/Component/Interact/Terminal.php | 7 ++++++ src/Component/InteractiveHandle.php | 7 ++++++ src/Component/MessageFormatter.php | 9 ++++---- src/Component/NotifyMessage.php | 9 ++++---- src/Component/PharCompiler.php | 10 ++++++++- src/Component/Progress/Bar.php | 9 ++++---- src/Component/Progress/CounterText.php | 7 ++++++ src/Component/Progress/DynamicText.php | 7 ++++++ src/Component/Progress/Pending.php | 9 ++++---- src/Component/Progress/SimpleBar.php | 7 ++++++ src/Component/Progress/SimpleTextBar.php | 7 ++++++ src/Component/Progress/Spinner.php | 9 ++++---- src/Component/Progress/TextBar.php | 9 ++++---- src/Component/ReadlineCompleter.php | 7 ++++++ src/Component/Symbol/ArtFont.php | 9 ++++---- src/Component/Symbol/Char.php | 13 +++++++---- src/Component/Symbol/Emoji.php | 9 ++++---- src/Concern/AdvancedFormatOutputTrait.php | 9 ++++---- src/Concern/ApplicationHelpTrait.php | 9 ++++---- src/Concern/AttachApplicationTrait.php | 8 +++++++ src/Concern/CommandHelpTrait.php | 16 ++++++------- src/Concern/ControllerHelpTrait.php | 8 ++++++- src/Concern/FormatOutputAwareTrait.php | 9 ++++---- src/Concern/InputArgumentsTrait.php | 7 ++++++ src/Concern/InputOptionsTrait.php | 7 ++++++ src/Concern/InputOutputAwareTrait.php | 9 ++++---- src/Concern/RuntimeProfileTrait.php | 9 ++++---- src/Concern/SimpleEventAwareTrait.php | 9 ++++---- src/Concern/StyledOutputAwareTrait.php | 8 ++++++- src/Concern/SubCommandsWareTrait.php | 8 ++++++- src/Concern/UserInteractAwareTrait.php | 9 ++++---- src/Console.php | 8 +++++++ src/ConsoleConst.php | 9 +++++++- src/ConsoleEvent.php | 8 +++++++ src/Contract/ApplicationInterface.php | 9 ++++---- src/Contract/CommandHandlerInterface.php | 9 ++++---- src/Contract/CommandInterface.php | 9 ++++---- src/Contract/ControllerInterface.php | 9 ++++---- src/Contract/ErrorHandlerInterface.php | 9 ++++---- src/Contract/FormatterInterface.php | 9 ++++---- src/Contract/InputInterface.php | 11 ++++----- src/Contract/OutputInterface.php | 11 ++++----- src/Contract/RouterInterface.php | 9 ++++---- src/Controller.php | 11 ++++----- src/Exception/ConsoleException.php | 9 ++++---- src/Exception/PromptException.php | 9 ++++---- src/GlobalOption.php | 7 ++++++ src/Handler/AbstractHandler.php | 17 +++++++++----- src/Handler/CallableCommand.php | 11 +++++++-- src/IO/AbstractInput.php | 9 ++++---- src/IO/AbstractOutput.php | 8 ++++++- src/IO/Input.php | 12 +++++----- src/IO/Input/AloneInput.php | 7 ++++++ src/IO/Input/ArrayInput.php | 9 ++++---- src/IO/Input/StreamInput.php | 9 ++++++-- src/IO/Input/StringInput.php | 9 ++++---- src/IO/Output.php | 9 ++++---- src/IO/Output/BufferedOutput.php | 10 +++++++-- src/IO/Output/MemoryOutput.php | 7 ++++++ src/IO/Output/StreamOutput.php | 10 +++++++-- src/IO/TempStream.php | 7 ++++++ src/Router.php | 7 ++++++ src/Util/FormatUtil.php | 9 ++++---- src/Util/Helper.php | 11 +++++---- src/Util/Interact.php | 9 ++++---- src/Util/PhpDevServe.php | 9 ++++++++ src/Util/ProgressBar.php | 10 +++++---- src/Util/Show.php | 7 ++++++ test/Annotate/DocblockRulesTest.php | 16 ++++++++++--- test/ApplicationTest.php | 9 +++++++- test/BaseTestCase.php | 10 +++++++-- test/CommandTest.php | 7 ++++++ test/ControllerTest.php | 7 ++++++ test/IO/InputTest.php | 8 ++++++- test/TestCommand.php | 9 ++++---- test/TestController.php | 9 ++++---- test/bootstrap.php | 8 +++++-- 135 files changed, 885 insertions(+), 368 deletions(-) diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 3bed08d5..58d7078c 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -37,12 +37,12 @@ ]; $finder = PhpCsFixer\Finder::create() - // ->exclude('test') ->exclude('docs') ->exclude('vendor') + ->exclude('resource') ->in(__DIR__); -return PhpCsFixer\Config::create() +return (new PhpCsFixer\Config) ->setRiskyAllowed(true) ->setRules($rules) ->setFinder($finder) diff --git a/examples/Command/CorCommand.php b/examples/Command/CorCommand.php index 2a377929..0c530dfc 100644 --- a/examples/Command/CorCommand.php +++ b/examples/Command/CorCommand.php @@ -1,9 +1,10 @@ aList([ 'support coroutine?' => Helper::isSupportCoroutine() ? 'Y' : 'N', diff --git a/examples/Command/DemoCommand.php b/examples/Command/DemoCommand.php index 15cadf7f..b820c031 100644 --- a/examples/Command/DemoCommand.php +++ b/examples/Command/DemoCommand.php @@ -1,9 +1,10 @@ static function($in, $out) { + 'sub' => static function ($in, $out): void { $out->println('hello, this is an sub command of test.'); }, ]; @@ -46,7 +47,7 @@ protected function commands(): array * @param Input $input * @param Output $output */ - public function execute(Input $input, Output $output) + public function execute(Input $input, Output $output): void { $output->write('hello, this in ' . __METHOD__); } diff --git a/examples/Controller/HomeController.php b/examples/Controller/HomeController.php index 11494f77..3cd581f0 100644 --- a/examples/Controller/HomeController.php +++ b/examples/Controller/HomeController.php @@ -1,4 +1,11 @@ addOptByRule('opt1', "bool;description for the option 'opt1'"); $fs->addArgByRule('name', "string;description for the argument 'name';true"); - } + } // desc set at $this->commandMetas. public function defArgCommand(FlagsParser $fs): void diff --git a/examples/Controller/InteractController.php b/examples/Controller/InteractController.php index 0d802de7..90f1de3a 100644 --- a/examples/Controller/InteractController.php +++ b/examples/Controller/InteractController.php @@ -1,9 +1,10 @@ output->info("will running background by new process: $pid"); }); diff --git a/examples/Controller/ShowController.php b/examples/Controller/ShowController.php index 3ba2217f..5a5930e2 100644 --- a/examples/Controller/ShowController.php +++ b/examples/Controller/ShowController.php @@ -1,9 +1,10 @@ command(DemoCommand::class); -$app->command('exam', function (Input $in, Output $out) { +$app->command('exam', function (Input $in, Output $out): void { $cmd = $in->getCommand(); $out->info('hello, this is a test command: ' . $cmd); diff --git a/examples/demo/cli-spinner.php b/examples/demo/cli-spinner.php index d0dad556..b143e3d9 100644 --- a/examples/demo/cli-spinner.php +++ b/examples/demo/cli-spinner.php @@ -1,15 +1,15 @@ +/** + * The file is part of inhere/console * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * @author https://github.com/inhere + * @homepage https://github.com/inhere/php-console + * @license https://github.com/inhere/php-console/blob/master/LICENSE */ - /** * Formatter style class for defining styles. * @@ -81,7 +79,7 @@ public function __construct($foreground = null, $background = null, array $optio * * @throws \InvalidArgumentException When the color name isn't defined */ - public function setForeground($color = null) + public function setForeground($color = null): void { if (null === $color) { $this->foreground = null; @@ -104,7 +102,7 @@ public function setForeground($color = null) * * @throws \InvalidArgumentException When the color name isn't defined */ - public function setBackground($color = null) + public function setBackground($color = null): void { if (null === $color) { $this->background = null; @@ -127,7 +125,7 @@ public function setBackground($color = null) * * @throws \InvalidArgumentException When the option name isn't defined */ - public function setOption($option) + public function setOption($option): void { if (!isset(static::$availableOptions[$option])) { throw new \InvalidArgumentException(sprintf( @@ -148,7 +146,7 @@ public function setOption($option) * * @throws \InvalidArgumentException When the option name isn't defined */ - public function unsetOption($option) + public function unsetOption($option): void { if (!isset(static::$availableOptions[$option])) { throw new \InvalidArgumentException(sprintf( @@ -168,7 +166,7 @@ public function unsetOption($option) * * @param array $options */ - public function setOptions(array $options) + public function setOptions(array $options): void { $this->options = []; foreach ($options as $option) { diff --git a/examples/demo/spinner.php b/examples/demo/spinner.php index 05f96e79..7b027812 100644 --- a/examples/demo/spinner.php +++ b/examples/demo/spinner.php @@ -1,9 +1,10 @@ 'My Console Application', @@ -191,7 +196,7 @@ protected function initForRun(Input $input): void $this->flags->setAutoBindArgs(false); // set helper render - $this->flags->setHelpRenderer(function () { + $this->flags->setHelpRenderer(function (): void { $this->showHelpInfo(); $this->stop(); }); @@ -329,7 +334,7 @@ protected function afterRun(): void /** * @param int $code */ - public function stop(int $code = 0) + public function stop(int $code = 0): void { // call 'onAppStop' event, if it is registered. $this->fire(self::ON_STOP_RUN, $this); @@ -427,7 +432,7 @@ protected function registerErrorHandle(): void { set_error_handler([$this, 'handleError']); set_exception_handler([$this, 'handleException']); - register_shutdown_function(function () { + register_shutdown_function(function (): void { if ($e = error_get_last()) { $this->handleError($e['type'], $e['message'], $e['file'], $e['line']); } @@ -506,7 +511,7 @@ protected function startInteractiveShell(): void { // $in = $this->input; $out = $this->output; - $out->title("Welcome interactive shell for run application", [ + $out->title('Welcome interactive shell for run application', [ 'titlePos' => Title::POS_MIDDLE, ]); @@ -516,7 +521,7 @@ protected function startInteractiveShell(): void // register signal. if ($hasPcntl) { - ProcessUtil::installSignal(Signal::INT, static function () use ($out) { + ProcessUtil::installSignal(Signal::INT, static function () use ($out): void { $out->colored("\nQuit by CTRL+C"); exit(0); }); @@ -534,7 +539,7 @@ protected function startInteractiveShell(): void ]; // set helper render - $this->flags->setHelpRenderer(function () { + $this->flags->setHelpRenderer(function (): void { $this->showHelpInfo(); // $this->stop(); not exit }); diff --git a/src/Annotate/AnnotateRules.php b/src/Annotate/AnnotateRules.php index dfc4ee6b..26675b78 100644 --- a/src/Annotate/AnnotateRules.php +++ b/src/Annotate/AnnotateRules.php @@ -1,4 +1,11 @@ flags->getOpt('addr'); if (!$serveAddr) { diff --git a/src/BuiltIn/PharController.php b/src/BuiltIn/PharController.php index bdd26436..acb324bd 100644 --- a/src/BuiltIn/PharController.php +++ b/src/BuiltIn/PharController.php @@ -1,9 +1,10 @@ liteInfo("will only pack input files to the exists phar: $outFile"); } - $cpr->onError(function ($error) { + $cpr->onError(function ($error): void { $this->writeln("$error"); }); - $cpr->on(PharCompiler::ON_MESSAGE, function ($msg) { + $cpr->on(PharCompiler::ON_MESSAGE, function ($msg): void { $this->output->colored('> ' . $msg); }); @@ -192,20 +193,20 @@ private function outputProgress(PharCompiler $cpr): void { if ($this->isDebug()) { // $output->info('Pack file to Phar ... ...'); - $cpr->onAdd(function (string $path) { + $cpr->onAdd(function (string $path): void { $this->writeln(" + $path"); }); - $cpr->on('skip', function (string $path, bool $isFile) { + $cpr->on('skip', function (string $path, bool $isFile): void { $mark = $isFile ? '[F]' : '[D]'; $this->writeln(" - $path $mark"); }); } else { $counter = Show::counterTxt('Collecting ...', 'Done.'); - $cpr->onAdd(static function () use ($counter) { + $cpr->onAdd(static function () use ($counter): void { $counter->send(1); }); - $cpr->on(PharCompiler::ON_COLLECTED, function () use ($counter) { + $cpr->on(PharCompiler::ON_COLLECTED, function () use ($counter): void { $counter->send(-1); $this->writeln(''); }); diff --git a/src/BuiltIn/SelfUpdateCommand.php b/src/BuiltIn/SelfUpdateCommand.php index 9c72b48b..3706628d 100644 --- a/src/BuiltIn/SelfUpdateCommand.php +++ b/src/BuiltIn/SelfUpdateCommand.php @@ -1,9 +1,10 @@ version = $this->getApp()->getVersion(); $parser = new VersionParser; diff --git a/src/Command.php b/src/Command.php index 6ce2001e..306424b0 100644 --- a/src/Command.php +++ b/src/Command.php @@ -1,9 +1,10 @@ getAliases(); - $this->logf(Console::VERB_CRAZY, "display help info for the command: %s", $this->commandName); + $this->logf(Console::VERB_CRAZY, 'display help info for the command: %s', $this->commandName); // $execMethod = self::METHOD; // return $this->showHelpByAnnotations($execMethod, '', $aliases) !== 0; diff --git a/src/Component/Animation/Animation.php b/src/Component/Animation/Animation.php index fc05e814..c2f8b529 100644 --- a/src/Component/Animation/Animation.php +++ b/src/Component/Animation/Animation.php @@ -1,9 +1,10 @@ write($headBorder . "\n"); } } diff --git a/src/Component/Formatter/Title.php b/src/Component/Formatter/Title.php index 552850a4..d44c2612 100644 --- a/src/Component/Formatter/Title.php +++ b/src/Component/Formatter/Title.php @@ -1,4 +1,11 @@ allowExit = $allowExit; } - } diff --git a/src/Component/Interact/Checkbox.php b/src/Component/Interact/Checkbox.php index 94d967db..5ef8489a 100644 --- a/src/Component/Interact/Checkbox.php +++ b/src/Component/Interact/Checkbox.php @@ -1,4 +1,11 @@ hasPcntl) { - ProcessUtil::installSignal(Signal::INT, static function () { + ProcessUtil::installSignal(Signal::INT, static function (): void { Show::colored("\nQuit by CTRL+C"); exit(0); }); @@ -357,7 +365,7 @@ public function emptyValidator(): Closure */ public function defaultErrorHandler(): Closure { - return static function (Throwable $e) { + return static function (Throwable $e): void { Console::write('ERROR: ' . $e->getMessage(), false); }; } diff --git a/src/Component/Interact/LimitedAsk.php b/src/Component/Interact/LimitedAsk.php index 9f792d41..55b529b2 100644 --- a/src/Component/Interact/LimitedAsk.php +++ b/src/Component/Interact/LimitedAsk.php @@ -1,4 +1,11 @@ selectedVal; } - } diff --git a/src/Component/Interact/Terminal.php b/src/Component/Interact/Terminal.php index 3acdd0f1..44576579 100644 --- a/src/Component/Interact/Terminal.php +++ b/src/Component/Interact/Terminal.php @@ -1,4 +1,11 @@ isCommand(); $commandId = $this->input->getCommandId(); - $this->logf(Console::VERB_DEBUG, "render help for the command: %s", $commandId); + $this->logf(Console::VERB_DEBUG, 'render help for the command: %s', $commandId); if ($aliases) { $realName = $action ?: $this->getRealName(); diff --git a/src/Concern/ControllerHelpTrait.php b/src/Concern/ControllerHelpTrait.php index fa8a49ed..52b68b5d 100644 --- a/src/Concern/ControllerHelpTrait.php +++ b/src/Concern/ControllerHelpTrait.php @@ -1,4 +1,11 @@ output->write(sprintf($msgTpl, $script, $detached ? '' : $sName)); $this->output->flush(); } - } diff --git a/src/Concern/FormatOutputAwareTrait.php b/src/Concern/FormatOutputAwareTrait.php index ff959737..f9371616 100644 --- a/src/Concern/FormatOutputAwareTrait.php +++ b/src/Concern/FormatOutputAwareTrait.php @@ -1,9 +1,10 @@ setBeforePrintHelp(function (string $text) { return $this->parseCommentsVars($text); }); - $fs->setHelpRenderer(function () { + $fs->setHelpRenderer(function (): void { $this->logf(Console::VERB_DEBUG, 'show subcommand help by input flags: -h, --help'); $this->showHelp(); }); diff --git a/src/Exception/ConsoleException.php b/src/Exception/ConsoleException.php index 52741829..dcfe7526 100644 --- a/src/Exception/ConsoleException.php +++ b/src/Exception/ConsoleException.php @@ -1,9 +1,10 @@ flags->setBeforePrintHelp(function (string $text) { return $this->parseCommentsVars($text); }); - $this->flags->setHelpRenderer(function () { + $this->flags->setHelpRenderer(function (): void { $this->logf(Console::VERB_DEBUG, 'show help message by input flags: -h, --help'); $this->showHelp(); }); @@ -356,7 +361,7 @@ protected function doRun(array $args) */ public function coExecute(): int { - $cid = \Swoole\Coroutine\run(function () { + $cid = \Swoole\Coroutine\run(function (): void { $this->execute($this->input, $this->output); }); diff --git a/src/Handler/CallableCommand.php b/src/Handler/CallableCommand.php index f65ac843..cc85c182 100644 --- a/src/Handler/CallableCommand.php +++ b/src/Handler/CallableCommand.php @@ -1,11 +1,18 @@ callable) { - throw new \BadMethodCallException('The callable property is empty'); + throw new BadMethodCallException('The callable property is empty'); } // call custom callable diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index 275c9940..473b95b7 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -1,9 +1,10 @@ buffer = ''; } -} \ No newline at end of file +} diff --git a/src/IO/Output/MemoryOutput.php b/src/IO/Output/MemoryOutput.php index 9398c5a6..1a197f21 100644 --- a/src/IO/Output/MemoryOutput.php +++ b/src/IO/Output/MemoryOutput.php @@ -1,4 +1,11 @@ stream); } -} \ No newline at end of file +} diff --git a/src/IO/TempStream.php b/src/IO/TempStream.php index fb292c86..cd7093e5 100644 --- a/src/IO/TempStream.php +++ b/src/IO/TempStream.php @@ -1,4 +1,11 @@ setDocTagsByDocblock(<<setDocTagsByDocblock( + <<setDocTagsByDocblock(<<setDocTagsByDocblock( + <<setDocTagsByDocblock(<<setDocTagsByDocblock( + <<on(Application::ON_BEFORE_RUN, function (Application $app) { + $app->on(Application::ON_BEFORE_RUN, function (Application $app): void { $this->assertEquals('Tests', $app->getName()); }); diff --git a/test/BaseTestCase.php b/test/BaseTestCase.php index 9ade0b6c..631f5be6 100644 --- a/test/BaseTestCase.php +++ b/test/BaseTestCase.php @@ -1,4 +1,11 @@ Date: Wed, 29 Sep 2021 12:06:38 +0800 Subject: [PATCH 171/258] update some examples --- examples/app | 6 +++--- examples/commands.php | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/app b/examples/app index 96f85c01..1bde95e1 100644 --- a/examples/app +++ b/examples/app @@ -14,9 +14,9 @@ require dirname(__DIR__) . '/test/bootstrap.php'; // create app instance $app = new Application([ - 'debug' => true, - 'rootPath' => dirname(__DIR__), - 'description' => 'This is demo console application', + 'debug' => true, + 'rootPath' => dirname(__DIR__), + 'desc' => 'This is demo console application', ]); $app->setLogo(" diff --git a/examples/commands.php b/examples/commands.php index 7c59d7ec..2baf7f7b 100644 --- a/examples/commands.php +++ b/examples/commands.php @@ -7,6 +7,7 @@ * @license https://github.com/inhere/php-console/blob/master/LICENSE */ +use Inhere\Console\Application; use Inhere\Console\BuiltIn\PharController; use Inhere\Console\BuiltIn\SelfUpdateCommand; use Inhere\Console\Examples\Command\CorCommand; @@ -19,12 +20,15 @@ use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; +/** @var Application $app */ $app->command(DemoCommand::class); $app->command('exam', function (Input $in, Output $out): void { $cmd = $in->getCommand(); $out->info('hello, this is a test command: ' . $cmd); -}, 'a description message'); +}, [ + 'desc' => 'a description message', +]); $app->command('test', TestCommand::class, [ 'aliases' => ['t'] From bd7e047f912ed602d1981310c72249dc726abbbd Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 30 Sep 2021 10:14:00 +0800 Subject: [PATCH 172/258] up: gen changelog on relase new tag --- .github/workflows/release.yml | 40 +++++++++++++++-------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a22b0e83..f5a3c690 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,32 +31,26 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php}} - tools: pecl, php-cs-fixer, phpunit - extensions: mbstring, dom, fileinfo, mysql, openssl # , swoole-4.4.19 #optional, setup extensions + tools: php-cs-fixer, phpunit + extensions: mbstring, dom, fileinfo, openssl # , swoole-4.4.19 #optional, setup extensions ini-values: post_max_size=56M, short_open_tag=On #optional, setup php.ini configuration coverage: none #optional, setup coverage driver: xdebug, none - - name: Install dependencies # eg: v1.0.3 + - name: Generate changelog file + id: changelog run: | - tag1=${GITHUB_REF#refs/*/} - echo "release tag: ${tag1}" - composer install --no-progress - - # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" - # Docs: https://getcomposer.org/doc/articles/scripts.md - -# - name: Build phar and send to github assets -# run: | -# echo $RELEASE_TAG -# echo $RELEASE_NAME -# php -d phar.readonly=0 bin/kite phar:pack -o kite-${RELEASE_TAG}.phar --no-progress -# php kite-${RELEASE_TAG}.phar -V - - # https://github.com/actions/create-release - - uses: meeDamian/github-release@2.0 + wget -c -q https://github.com/inhere/kite/releases/latest/download/kite.phar + php kite.phar git cl prev last --style gh-release --no-merges --fetch-tags --unshallow --file changelog.md + cat changelog.md + + # https://github.com/softprops/action-gh-release + - name: Create release and upload assets + uses: softprops/action-gh-release@v1 + # if: startsWith(github.ref, 'refs/tags/') with: - gzip: false - token: ${{ secrets.GITHUB_TOKEN }} - tag: ${{ env.RELEASE_TAG }} name: ${{ env.RELEASE_TAG }} -# files: kite-${{ env.RELEASE_TAG }}.phar + tag_name: ${{ env.RELEASE_TAG }} + body_path: changelog.md + # files: kite-${{ env.RELEASE_TAG }}.phar + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From 323ae9b3dcff0a31b602f4d72189cb5d29592b9c Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 14 Oct 2021 22:16:29 +0800 Subject: [PATCH 173/258] feat: add more common methods for group/command --- src/Application.php | 24 ++++++--- src/Command.php | 11 ++-- src/Contract/CommandHandlerInterface.php | 27 +++++++++- src/Controller.php | 67 +++++++++++++++++++----- src/Handler/AbstractHandler.php | 49 +++++++++++++++++ src/Router.php | 33 +++++++----- 6 files changed, 169 insertions(+), 42 deletions(-) diff --git a/src/Application.php b/src/Application.php index 7653cb64..b8a55e6c 100644 --- a/src/Application.php +++ b/src/Application.php @@ -303,7 +303,7 @@ public function dispatch(string $name, array $args = []) // is command if ($info['type'] === Router::TYPE_SINGLE) { - return $this->runCommand($info['name'], $info['handler'], $cmdOptions, $args); + return $this->runCommand($info, $cmdOptions, $args); } // is controller/group @@ -313,16 +313,18 @@ public function dispatch(string $name, array $args = []) /** * run a independent command * - * @param string $name Command name - * @param Closure|string $handler Command class or handler func + * @param array{name: string, handler: mixed, realName: string} $info * @param array $options * @param array $args * * @return mixed * @throws Throwable */ - protected function runCommand(string $name, $handler, array $options, array $args) + protected function runCommand(array $info, array $options, array $args) { + /** @var Closure|string $handler Command class or handler func */ + $handler = $info['handler']; + if (is_object($handler) && method_exists($handler, '__invoke')) { $fs = SFlags::new(); $fs->addOptsByRules(GlobalOption::getAloneOptions()); @@ -344,13 +346,13 @@ protected function runCommand(string $name, $handler, array $options, array $arg /** @var Command $object */ $object = new $handler($this->input, $this->output); - if (!($object instanceof Command)) { Helper::throwInvalidArgument("The console command class [$handler] must instanceof the " . Command::class); } - $object::setName($name); + $object::setName($info['cmdId']); // real command name. $object->setApp($this); + $object->setCommandName($info['name']); $result = $object->run($args); } @@ -380,8 +382,8 @@ protected function runAction(array $info, array $options, array $args, bool $det $controller->setDetached(); } - if ($info['action']) { - array_unshift($args, $info['action']); + if ($info['sub']) { + array_unshift($args, $info['sub']); } // Command method, no suffix @@ -441,6 +443,12 @@ protected function createController(array $info): Controller // force set name and description $handler::setName($group); $handler->setApp($this); + + // set input name + if ($inputName = $info['name'] ?? '') { + $handler->setGroupName($inputName); + } + $handler->setDelimiter($this->delimiter); // cache object diff --git a/src/Command.php b/src/Command.php index 306424b0..7c3dffcc 100644 --- a/src/Command.php +++ b/src/Command.php @@ -38,11 +38,6 @@ abstract class Command extends AbstractHandler implements CommandInterface */ protected $parent; - /** - * @var string - */ - protected $commandName = ''; - protected function init(): void { $this->commandName = self::getName(); @@ -70,7 +65,7 @@ protected function beforeInitFlagsParser(FlagsParser $fs): void */ protected function afterInitFlagsParser(FlagsParser $fs): void { - $this->debugf('load flags configure for command: %s', $this->getRealName()); + $this->debugf('load flags configure for command: %s', $this->getRealCName()); $this->configure(); $isEmpty = $this->flags->isEmpty(); @@ -131,8 +126,8 @@ protected function showHelp(): bool /** * @return string */ - public function getCommandName(): string + public function getRealCName(): string { - return $this->commandName; + return self::getName(); } } diff --git a/src/Contract/CommandHandlerInterface.php b/src/Contract/CommandHandlerInterface.php index 48db6c10..eefd7bd8 100644 --- a/src/Contract/CommandHandlerInterface.php +++ b/src/Contract/CommandHandlerInterface.php @@ -45,22 +45,47 @@ public function run(array $args); public function getApp(): AbstractApplication; /** + * The input group name. + * * @return string */ public function getGroupName(): string; /** - * Alias of the getName() + * The real group or command name. Alias of the getName() * * @return string */ public function getRealName(): string; /** + * The real group name. + * + * @return string + */ + public function getRealGName(): string; + + /** + * The real command name. + * + * @return string + */ + public function getRealCName(): string; + + /** + * The input command/subcommand name. + * * @return string */ public function getCommandName(): string; + /** + * @param bool $useReal + * + * @return string + */ + public function getCommandId(bool $useReal = true): string; + /** * @return string */ diff --git a/src/Controller.php b/src/Controller.php index 7afb421a..e64cdd16 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -80,19 +80,21 @@ abstract class Controller extends AbstractHandler implements ControllerInterface private $action = ''; /** + * The action method name on the controller. + * * @var string */ private $actionMethod = ''; /** - * Input subcommand name. + * The input group name. * * @var string */ - private $commandName = ''; + private $groupName = ''; /** - * eg: '/' ':' + * The delimiter. eg: '/' ':' * * @var string */ @@ -181,6 +183,7 @@ protected function init(): void $list = $this->disabledCommands(); + $this->groupName = $this->getRealGName(); // save to property $this->disabledCommands = $list ? array_flip($list) : []; @@ -575,14 +578,6 @@ public function resolveAlias(string $alias): string return $map[$alias] ?? $alias; } - /** - * @return string - */ - public function getGroupName(): string - { - return self::getName(); - } - /** * @param string $name * @@ -648,7 +643,31 @@ public function getCommandAliases(string $name = ''): array /** * @return string */ - public function getAction(): string + public function getGroupName(): string + { + return $this->groupName; + } + + /** + * @return string + */ + public function getRealGName(): string + { + return self::getName(); + } + + /** + * @param string $groupName + */ + public function setGroupName(string $groupName): void + { + $this->groupName = $groupName; + } + + /** + * @return string + */ + public function getRealCName(): string { return $this->action; } @@ -656,11 +675,33 @@ public function getAction(): string /** * @return string */ - public function getCommandName(): string + public function getSubName(): string { return $this->commandName; } + /** + * @param bool $useReal + * + * @return string + */ + public function getCommandId(bool $useReal = true): string + { + if ($useReal) { + return self::getName() . $this->delimiter . $this->action; + } + + return $this->groupName . $this->delimiter . $this->commandName; + } + + /** + * @return string + */ + public function getAction(): string + { + return $this->action; + } + /** * @param string $action * diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index 9c07e0b3..e056ea58 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -95,6 +95,13 @@ abstract class AbstractHandler implements CommandHandlerInterface */ protected $params; + /** + * The input command name. maybe is an alias name. + * + * @var string + */ + protected $commandName = ''; + /** * @var array Command options */ @@ -523,6 +530,38 @@ public function getGroupName(): string return ''; } + /** + * @return string + */ + public function getRealGName(): string + { + return ''; + } + + /** + * @return string + */ + public function getRealCName(): string + { + return ''; + } + + /** + * @param string $commandName + */ + public function setCommandName(string $commandName): void + { + $this->commandName = $commandName; + } + + /** + * @return string + */ + public function getCommandName(): string + { + return $this->commandName; + } + /** * @return string */ @@ -531,6 +570,16 @@ public function getRealName(): string return self::getName(); } + /** + * @param bool $useReal + * + * @return string + */ + public function getCommandId(bool $useReal = true): string + { + return $useReal ? self::getName() : $this->commandName; + } + /** * @return array */ diff --git a/src/Router.php b/src/Router.php index 3c34921c..041acab3 100644 --- a/src/Router.php +++ b/src/Router.php @@ -261,18 +261,25 @@ public function addControllers(array $controllers): void **********************************************************/ /** - * @param string $name The input command name - * - * @return array return route info array. If not found, will return empty array. - * [ - * type => 1, // 1 group 2 command - * name => '', // formatted $name + * ```php + * return [ + * type => 1, // 1 group 2 command + * name => '', // input group/command name. + * cmdId => '', // format and resolved $name + * // for group + * group => '', // group name. + * sub => '', // input subcommand name. on name is group. + * // common info * handler => handler class/object/func ... * options => [ * aliases => [], * description => '', * ], * ] + * ``` + * @param string $name The input command name + * + * @return array return route info array. If not found, will return empty array. */ public function match(string $name): array { @@ -283,18 +290,20 @@ public function match(string $name): array // is a command name if ($route = $this->commands[$realName] ?? []) { - $route['name'] = $route['cmdId'] = $realName; + $route['name'] = $name; + $route['cmdId'] = $realName; return $route; } // maybe is a controller/group name $action = ''; - $group = $realName; + $iptGrp = $group = $realName; // like 'home:index' if (strpos($realName, $sep) > 0) { [$group, $action] = explode($sep, $realName, 2); + $iptGrp = $group; $action = trim($action, ': '); // resolve alias $group = $this->resolveAlias($group); @@ -302,10 +311,10 @@ public function match(string $name): array // is group name if ($route = $this->controllers[$group] ?? []) { - $route['name'] = $realName; - $route['group'] = $group; - $route['action'] = $action; - $route['cmdId'] = $group . $sep . $action; + $route['name'] = $iptGrp; + $route['group'] = $group; + $route['sub'] = $action; + $route['cmdId'] = $group . $sep . $action; return $route; } From bb7c1ecd2396594ca652acc51e71fd175d449e22 Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 16 Oct 2021 13:19:08 +0800 Subject: [PATCH 174/258] sytle: update readme --- README.en.md | 112 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 111 +++++++++++++++++++++++------------------------ README.zh-CN.md | 109 ---------------------------------------------- 3 files changed, 166 insertions(+), 166 deletions(-) create mode 100644 README.en.md delete mode 100644 README.zh-CN.md diff --git a/README.en.md b/README.en.md new file mode 100644 index 00000000..437c632b --- /dev/null +++ b/README.en.md @@ -0,0 +1,112 @@ +# PHP Console + +[![License](https://img.shields.io/packagist/l/inhere/console.svg?style=flat-square)](LICENSE) +[![Php Version](https://img.shields.io/badge/php-%3E=7.3.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) +[![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console) +[![Github Actions Status](https://github.com/inhere/php-console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/php-console/actions) + +Full-featured php command line application library. + +Provide console parameter parsing, command run, color style output, user information interaction, and special format information display. + +> **[中文README](./README.zh-CN.md)** + +## Command line preview + +![app-command-list](https://raw.githubusercontent.com/inhere/php-console/3.x/docs/screenshots/app-command-list.png) + +## Features + +> Easy to use. Can be easily integrated into any existing project. + +- Command line application, `controller`, `command` parsing run on the command line +- Support for setting aliases for commands. A command can have multiple aliases. +- Support command display/hide, enable/disable. +- Full-featured command line option parameter parsing (named parameters, short options `-s`, long options `--long`). +- The `input`, `output` of the command line, management, use +- Command method comments are automatically parsed as help information (by default, `@usage` `@arguments` `@options` `@example`) +- Support for outputting message texts of multiple color styles (`info`, `comment`, `success`, `warning`, `danger`, `error` ... ) +- Commonly used special format information display (`section`, `panel`, `padding`, `helpPanel`, `table`, `tree`, `title`, `list`, `multiList`) +- Rich dynamic information display (`pending/loading`, `pointing`, `spinner`, `counterTxt`, `dynamicText`, `progressTxt`, `progressBar`) +- Common user information interaction support (`select`, `multiSelect`, `confirm`, `ask/question`, `askPassword/askHiddenInput`) +- Support for predefined parameter definitions like `symfony/console` (giving parameter values by position, recommended when strict parameter restrictions are required) +- The color output is `windows` `linux` `mac` compatible. Environments that do not support color will automatically remove the relevant CODE. +- Quickly generate auto-completion scripts for the current application in the `bash/zsh` environment +- NEW: Support start an interactive shell for run application + +### Built-in tools + +- Built-in Phar packaging tool class, which can be easily packaged into `phar` files. Easy to distribute and use + - Run the command `php examples/app phar:pack` in the example, which will package this console library into an `app.phar` +- Built-in file download tool class under command line with progress bar display +- Command line php code highlighting support (from `jakub-onderka/php-console-highlighter` and making some adjustments) +- Simple Terminal screen, cursor control operation class +- Simple process operations using classes (fork, run, stop, wait ..., etc.) + +> All features, effects; can be run in the example code `phps/app` in `examples/`. Basically covers all the features and can be tested directly + +## Installation + +```bash +composer require inhere/console +``` + +## Document List + +> Please go to WIKI for detailed usage documentation + +- **[Document Home](https://github.com/inhere/php-console/wiki/home)** +- **[Feature Overview](https://github.com/inhere/php-console/wiki/overview)** +- **[Install](https://github.com/inhere/php-console/wiki/install)** +- **[Create Application](https://github.com/inhere/php-console/wiki/quick-start)** +- **[Add Command](https://github.com/inhere/php-console/wiki/add-command)** +- **[Add Command Group](https://github.com/inhere/php-console/wiki/add-group)** +- **[Register Command](https://github.com/inhere/php-console/wiki/register-command)** +- **[Error/Exception Capture](https://github.com/inhere/php-console/wiki/error-handle)** +- **[Input Object](https://github.com/inhere/php-console/wiki/input-instance)** +- **[output object](https://github.com/inhere/php-console/wiki/output-instance)** +- **[Formatted Output](https://github.com/inhere/php-console/wiki/format-output)** +- **[Progress Dynamic Output](https://github.com/inhere/php-console/wiki/process-output)** +- **[User Interaction](https://github.com/inhere/php-console/wiki/user-interactive)** +- **[Extension Tools](https://github.com/inhere/php-console/wiki/extra-tools)** + +## Project address + +- **github** https://github.com/inhere/php-console.git +- **gitee** https://gitee.com/inhere/php-console.git + +## Unit test + +```bash +phpunit +// output coverage without xdebug +phpdbg -dauto_globals_jit=Off -qrr /usr/local/bin/phpunit --coverage-text +``` + +## Debuging + +You can set debug level by ENV `CONSOLE_DEBUG=level`, global option `--debug level` + +```bash +# by ENV +$ CONSOLE_DEBUG=4 php examples/app +$ CONSOLE_DEBUG=5 php examples/app +# by global options +$ php examples/app --debug 4 +``` + +## Project use + +Check out these projects, which use https://github.com/inhere/php-console : + +- [kite](https://github.com/inhere/kite) Kite is a tool for help development. +- More, please see [github used by](https://github.com/inhere/php-console/network/dependents?package_id=UGFja2FnZS01NDI5NzMxOTI%3D) + +## License + +[MIT](LICENSE) + +## My projects + +- [inhere/php-validate](https://github.com/inhere/php-validate) A compact and full-featured php verification library +- [inhere/sroute](https://github.com/inhere/php-srouter) Lightweight and fast HTTP request routing library diff --git a/README.md b/README.md index 437c632b..50c432f4 100644 --- a/README.md +++ b/README.md @@ -5,87 +5,83 @@ [![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console) [![Github Actions Status](https://github.com/inhere/php-console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/php-console/actions) -Full-featured php command line application library. +简洁、功能全面的php命令行应用库。提供控制台参数解析, 命令运行,颜色风格输出, 用户信息交互, 特殊格式信息显示。 -Provide console parameter parsing, command run, color style output, user information interaction, and special format information display. +> **[EN README](./README.md)** -> **[中文README](./README.zh-CN.md)** - -## Command line preview +## 命令行预览 ![app-command-list](https://raw.githubusercontent.com/inhere/php-console/3.x/docs/screenshots/app-command-list.png) -## Features +## 功能概览 -> Easy to use. Can be easily integrated into any existing project. +> 使用方便简单。可以方便的整合到任何已有项目中。 -- Command line application, `controller`, `command` parsing run on the command line -- Support for setting aliases for commands. A command can have multiple aliases. -- Support command display/hide, enable/disable. -- Full-featured command line option parameter parsing (named parameters, short options `-s`, long options `--long`). -- The `input`, `output` of the command line, management, use -- Command method comments are automatically parsed as help information (by default, `@usage` `@arguments` `@options` `@example`) -- Support for outputting message texts of multiple color styles (`info`, `comment`, `success`, `warning`, `danger`, `error` ... ) -- Commonly used special format information display (`section`, `panel`, `padding`, `helpPanel`, `table`, `tree`, `title`, `list`, `multiList`) -- Rich dynamic information display (`pending/loading`, `pointing`, `spinner`, `counterTxt`, `dynamicText`, `progressTxt`, `progressBar`) -- Common user information interaction support (`select`, `multiSelect`, `confirm`, `ask/question`, `askPassword/askHiddenInput`) -- Support for predefined parameter definitions like `symfony/console` (giving parameter values by position, recommended when strict parameter restrictions are required) -- The color output is `windows` `linux` `mac` compatible. Environments that do not support color will automatically remove the relevant CODE. -- Quickly generate auto-completion scripts for the current application in the `bash/zsh` environment -- NEW: Support start an interactive shell for run application +- 命令行应用, 命令行的 `controller`, `command` 解析运行 +- 支持给命令设置别名,一个命令可以有多个别名。支持命令的显示/隐藏,启用/禁用 +- 功能全面的命令行的选项参数解析(命名参数,短选项 `-s`,长选项 `--long`) +- 命令行下的 输入`input`, 输出 `output` 管理、使用 +- 命令方法注释自动解析为帮助信息(默认提取 `@usage` `@arguments` `@options` `@example` 等信息) +- 支持输出多种颜色风格的消息文本(`info`, `comment`, `success`, `warning`, `danger`, `error` ... ) +- 常用的特殊格式信息显示(`section`, `panel`, `padding`, `helpPanel`, `table`, `tree`, `title`, `list`, `multiList`) +- 丰富的动态信息显示(`pending/loading`, `pointing`, `spinner`, `counterTxt`, `dynamicText`, `progressTxt`, `progressBar`) +- 常用的用户信息交互支持(`select`, `multiSelect`, `confirm`, `ask/question`, `askPassword/askHiddenInput`) +- 支持类似 `symfony/console` 的预定义参数定义(按位置赋予参数值, 需要严格限制参数选项时推荐使用) +- 颜色输出是 `windows` `linux` `mac` 兼容的,不支持颜色的环境会自动去除相关CODE +- 快速的为当前应用生成 `bash/zsh` 环境下的自动补全脚本 -### Built-in tools +### 内置工具 -- Built-in Phar packaging tool class, which can be easily packaged into `phar` files. Easy to distribute and use - - Run the command `php examples/app phar:pack` in the example, which will package this console library into an `app.phar` -- Built-in file download tool class under command line with progress bar display -- Command line php code highlighting support (from `jakub-onderka/php-console-highlighter` and making some adjustments) -- Simple Terminal screen, cursor control operation class -- Simple process operations using classes (fork, run, stop, wait ..., etc.) +- 内置Phar打包工具类,可以方便的将应用打包成`phar`文件。方便分发和使用 + - 运行示例中的命令 `php examples/app phar:pack`,会将此console库打包成一个`app.phar` +- 内置了命令行下的文件下载工具类,带有进度条显示 +- 命令行的php代码高亮支持(来自于`jakub-onderka/php-console-highlighter`并做了一些调整) +- 简单的Terminal屏幕、光标控制操作类 +- 简单的进程操作使用类(fork,run,stop,wait ... 等) -> All features, effects; can be run in the example code `phps/app` in `examples/`. Basically covers all the features and can be tested directly +> 所有的特性,效果;都可以运行 `examples/` 中的示例代码 `php examples/app` 展示出来的。基本上涵盖了所有功能,可以直接测试运行 -## Installation +## 快速安装 ```bash composer require inhere/console ``` -## Document List +## 文档列表 -> Please go to WIKI for detailed usage documentation +> 请到WIKI查看详细的使用文档 -- **[Document Home](https://github.com/inhere/php-console/wiki/home)** -- **[Feature Overview](https://github.com/inhere/php-console/wiki/overview)** -- **[Install](https://github.com/inhere/php-console/wiki/install)** -- **[Create Application](https://github.com/inhere/php-console/wiki/quick-start)** -- **[Add Command](https://github.com/inhere/php-console/wiki/add-command)** -- **[Add Command Group](https://github.com/inhere/php-console/wiki/add-group)** -- **[Register Command](https://github.com/inhere/php-console/wiki/register-command)** -- **[Error/Exception Capture](https://github.com/inhere/php-console/wiki/error-handle)** -- **[Input Object](https://github.com/inhere/php-console/wiki/input-instance)** -- **[output object](https://github.com/inhere/php-console/wiki/output-instance)** -- **[Formatted Output](https://github.com/inhere/php-console/wiki/format-output)** -- **[Progress Dynamic Output](https://github.com/inhere/php-console/wiki/process-output)** -- **[User Interaction](https://github.com/inhere/php-console/wiki/user-interactive)** -- **[Extension Tools](https://github.com/inhere/php-console/wiki/extra-tools)** +- **[文档首页](https://github.com/inhere/php-console/wiki/home)** +- **[功能概览](https://github.com/inhere/php-console/wiki/overview)** +- **[安装](https://github.com/inhere/php-console/wiki/install)** +- **[创建应用](https://github.com/inhere/php-console/wiki/quick-start)** +- **[添加命令](https://github.com/inhere/php-console/wiki/add-command)** +- **[添加命令组](https://github.com/inhere/php-console/wiki/add-group)** +- **[注册命令](https://github.com/inhere/php-console/wiki/register-command)** +- **[错误/异常捕获](https://github.com/inhere/php-console/wiki/error-handle)** +- **[输入对象](https://github.com/inhere/php-console/wiki/input-instance)** +- **[输出对象](https://github.com/inhere/php-console/wiki/output-instance)** +- **[格式化输出](https://github.com/inhere/php-console/wiki/format-output)** `section`, `panel`, `padding`, `helpPanel`, `table`, `tree`, `title`, `list`, `multiList` 等风格信息输出 +- **[进度动态输出](https://github.com/inhere/php-console/wiki/process-output)** 动态信息显示(`pending/loading`, `pointing`, `spinner`, `counterTxt`, `dynamicText`, `progressTxt`, `progressBar`) +- **[用户交互](https://github.com/inhere/php-console/wiki/user-interactive)** 用户信息交互支持(`select`, `multiSelect`, `confirm`, `ask/question`, `askPassword/askHiddenInput`) +- **[扩展工具](https://github.com/inhere/php-console/wiki/extra-tools)** 打包phar, 命令行下的文件下载, 命令行的php代码高亮 -## Project address +## 项目地址 - **github** https://github.com/inhere/php-console.git - **gitee** https://gitee.com/inhere/php-console.git -## Unit test +## 单元测试 ```bash phpunit -// output coverage without xdebug +// 没有xdebug时输出覆盖率 phpdbg -dauto_globals_jit=Off -qrr /usr/local/bin/phpunit --coverage-text ``` -## Debuging +## 开发调试 -You can set debug level by ENV `CONSOLE_DEBUG=level`, global option `--debug level` +你可以通过环境变量 `CONSOLE_DEBUG=level`, 全局选项 `--debug level` 设置debug级别 ```bash # by ENV @@ -95,18 +91,19 @@ $ CONSOLE_DEBUG=5 php examples/app $ php examples/app --debug 4 ``` -## Project use +## 使用console的项目 -Check out these projects, which use https://github.com/inhere/php-console : +看看这些使用了 https://github.com/inhere/php-console 的项目: -- [kite](https://github.com/inhere/kite) Kite is a tool for help development. +- [kite](https://github.com/inhere/kite) PHP编写的,方便本地开发和使用的个人CLI工具应用 - More, please see [github used by](https://github.com/inhere/php-console/network/dependents?package_id=UGFja2FnZS01NDI5NzMxOTI%3D) ## License [MIT](LICENSE) -## My projects +## 我的其他项目 + +- [inhere/php-validate](https://github.com/inhere/php-validate) 一个简洁小巧且功能完善的php验证库 +- [inhere/sroute](https://github.com/inhere/php-srouter) 轻量且快速的HTTP请求路由库 -- [inhere/php-validate](https://github.com/inhere/php-validate) A compact and full-featured php verification library -- [inhere/sroute](https://github.com/inhere/php-srouter) Lightweight and fast HTTP request routing library diff --git a/README.zh-CN.md b/README.zh-CN.md deleted file mode 100644 index a7719124..00000000 --- a/README.zh-CN.md +++ /dev/null @@ -1,109 +0,0 @@ -# PHP Console - -[![License](https://img.shields.io/packagist/l/inhere/console.svg?style=flat-square)](LICENSE) -[![Php Version](https://img.shields.io/badge/php-%3E=7.3.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) -[![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console) -[![Github Actions Status](https://github.com/inhere/php-console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/php-console/actions) - -简洁、功能全面的php命令行应用库。提供控制台参数解析, 命令运行,颜色风格输出, 用户信息交互, 特殊格式信息显示。 - -> **[EN README](./README.md)** - -## 命令行预览 - -![app-command-list](https://raw.githubusercontent.com/inhere/php-console/3.x/docs/screenshots/app-command-list.png) - -## 功能概览 - -> 使用方便简单。可以方便的整合到任何已有项目中。 - -- 命令行应用, 命令行的 `controller`, `command` 解析运行 -- 支持给命令设置别名,一个命令可以有多个别名。支持命令的显示/隐藏,启用/禁用 -- 功能全面的命令行的选项参数解析(命名参数,短选项 `-s`,长选项 `--long`) -- 命令行下的 输入`input`, 输出 `output` 管理、使用 -- 命令方法注释自动解析为帮助信息(默认提取 `@usage` `@arguments` `@options` `@example` 等信息) -- 支持输出多种颜色风格的消息文本(`info`, `comment`, `success`, `warning`, `danger`, `error` ... ) -- 常用的特殊格式信息显示(`section`, `panel`, `padding`, `helpPanel`, `table`, `tree`, `title`, `list`, `multiList`) -- 丰富的动态信息显示(`pending/loading`, `pointing`, `spinner`, `counterTxt`, `dynamicText`, `progressTxt`, `progressBar`) -- 常用的用户信息交互支持(`select`, `multiSelect`, `confirm`, `ask/question`, `askPassword/askHiddenInput`) -- 支持类似 `symfony/console` 的预定义参数定义(按位置赋予参数值, 需要严格限制参数选项时推荐使用) -- 颜色输出是 `windows` `linux` `mac` 兼容的,不支持颜色的环境会自动去除相关CODE -- 快速的为当前应用生成 `bash/zsh` 环境下的自动补全脚本 - -### 内置工具 - -- 内置Phar打包工具类,可以方便的将应用打包成`phar`文件。方便分发和使用 - - 运行示例中的命令 `php examples/app phar:pack`,会将此console库打包成一个`app.phar` -- 内置了命令行下的文件下载工具类,带有进度条显示 -- 命令行的php代码高亮支持(来自于`jakub-onderka/php-console-highlighter`并做了一些调整) -- 简单的Terminal屏幕、光标控制操作类 -- 简单的进程操作使用类(fork,run,stop,wait ... 等) - -> 所有的特性,效果;都可以运行 `examples/` 中的示例代码 `php examples/app` 展示出来的。基本上涵盖了所有功能,可以直接测试运行 - -## 快速安装 - -```bash -composer require inhere/console -``` - -## 文档列表 - -> 请到WIKI查看详细的使用文档 - -- **[文档首页](https://github.com/inhere/php-console/wiki/home)** -- **[功能概览](https://github.com/inhere/php-console/wiki/overview)** -- **[安装](https://github.com/inhere/php-console/wiki/install)** -- **[创建应用](https://github.com/inhere/php-console/wiki/quick-start)** -- **[添加命令](https://github.com/inhere/php-console/wiki/add-command)** -- **[添加命令组](https://github.com/inhere/php-console/wiki/add-group)** -- **[注册命令](https://github.com/inhere/php-console/wiki/register-command)** -- **[错误/异常捕获](https://github.com/inhere/php-console/wiki/error-handle)** -- **[输入对象](https://github.com/inhere/php-console/wiki/input-instance)** -- **[输出对象](https://github.com/inhere/php-console/wiki/output-instance)** -- **[格式化输出](https://github.com/inhere/php-console/wiki/format-output)** `section`, `panel`, `padding`, `helpPanel`, `table`, `tree`, `title`, `list`, `multiList` 等风格信息输出 -- **[进度动态输出](https://github.com/inhere/php-console/wiki/process-output)** 动态信息显示(`pending/loading`, `pointing`, `spinner`, `counterTxt`, `dynamicText`, `progressTxt`, `progressBar`) -- **[用户交互](https://github.com/inhere/php-console/wiki/user-interactive)** 用户信息交互支持(`select`, `multiSelect`, `confirm`, `ask/question`, `askPassword/askHiddenInput`) -- **[扩展工具](https://github.com/inhere/php-console/wiki/extra-tools)** 打包phar, 命令行下的文件下载, 命令行的php代码高亮 - -## 项目地址 - -- **github** https://github.com/inhere/php-console.git -- **gitee** https://gitee.com/inhere/php-console.git - -## 单元测试 - -```bash -phpunit -// 没有xdebug时输出覆盖率 -phpdbg -dauto_globals_jit=Off -qrr /usr/local/bin/phpunit --coverage-text -``` - -## 开发调试 - -你可以通过环境变量 `CONSOLE_DEBUG=level`, 全局选项 `--debug level` 设置debug级别 - -```bash -# by ENV -$ CONSOLE_DEBUG=4 php examples/app -$ CONSOLE_DEBUG=5 php examples/app -# by global options -$ php examples/app --debug 4 -``` - -## 使用console的项目 - -看看这些使用了 https://github.com/inhere/php-console 的项目: - -- [kite](https://github.com/inhere/kite) PHP编写的,方便本地开发和使用的一些CLI工具应用 -- More, please see [github used by](https://github.com/inhere/php-console/network/dependents?package_id=UGFja2FnZS01NDI5NzMxOTI%3D) - -## License - -[MIT](LICENSE) - -## 我的其他项目 - -- [inhere/php-validate](https://github.com/inhere/php-validate) 一个简洁小巧且功能完善的php验证库 -- [inhere/sroute](https://github.com/inhere/php-srouter) 轻量且快速的HTTP请求路由库 - From 28bd7a24ddd1bbb0b8090ee841122ec9c25c877b Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 16 Oct 2021 13:35:13 +0800 Subject: [PATCH 175/258] sytle: update readme, add more doc on readme --- README.md | 64 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 50c432f4..8c897662 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,20 @@ > 使用方便简单。可以方便的整合到任何已有项目中。 - 命令行应用, 命令行的 `controller`, `command` 解析运行 -- 支持给命令设置别名,一个命令可以有多个别名。支持命令的显示/隐藏,启用/禁用 -- 功能全面的命令行的选项参数解析(命名参数,短选项 `-s`,长选项 `--long`) -- 命令行下的 输入`input`, 输出 `output` 管理、使用 -- 命令方法注释自动解析为帮助信息(默认提取 `@usage` `@arguments` `@options` `@example` 等信息) +- 支持给命令设置别名,一个命令可以有多个别名。 + - 支持命令的显示/隐藏,启用/禁用 +- 命令行下的 输入`input`, 输出 `output`, Flag解析管理、使用 +- 功能全面的命令行的选项、参数解析 + - 命名参数,数组参数 + - 选项绑定 短选项 `-s`,长选项 `--long` + - 支持定义数据类型(`bool,int,string,array`),解析后会自动格式化输入值 +- 命令方法注释自动解析为帮助信息 + - 默认提取 `@usage` `@arguments` `@options` `@example` 等信息 + - **NEW** v4 版本支持从注释定义选项参数的数据类型 +- 支持注册事件监听,错误处理等 + +### 更多特性 + - 支持输出多种颜色风格的消息文本(`info`, `comment`, `success`, `warning`, `danger`, `error` ... ) - 常用的特殊格式信息显示(`section`, `panel`, `padding`, `helpPanel`, `table`, `tree`, `title`, `list`, `multiList`) - 丰富的动态信息显示(`pending/loading`, `pointing`, `spinner`, `counterTxt`, `dynamicText`, `progressTxt`, `progressBar`) @@ -43,13 +53,43 @@ ## 快速安装 +- Requirement php 7.3+ + ```bash composer require inhere/console ``` +## 快速开始 + +```php +// file: examples/app +use Inhere\Console\IO\Input; +use Inhere\Console\IO\Output; + +$meta = [ + 'name' => 'My Console App', + 'version' => '1.0.2', +]; + +// 通常无需传入 $input $output ,会自动创建 +// $app = new \Inhere\Console\Application($meta, $input, $output); +$app = new \Inhere\Console\Application($meta); + +// 注册命令 +$app->command(DemoCommand::class); +// 注册命令组 +$app->addGroup(MyController::class); +// ... ... + +// run +$app->run(); +``` + ## 文档列表 -> 请到WIKI查看详细的使用文档 +请到[WIKI](https://github.com/inhere/php-console/wiki)查看更多详细的使用文档 + + - **[文档首页](https://github.com/inhere/php-console/wiki/home)** - **[功能概览](https://github.com/inhere/php-console/wiki/overview)** @@ -98,12 +138,18 @@ $ php examples/app --debug 4 - [kite](https://github.com/inhere/kite) PHP编写的,方便本地开发和使用的个人CLI工具应用 - More, please see [github used by](https://github.com/inhere/php-console/network/dependents?package_id=UGFja2FnZS01NDI5NzMxOTI%3D) -## License - -[MIT](LICENSE) - ## 我的其他项目 - [inhere/php-validate](https://github.com/inhere/php-validate) 一个简洁小巧且功能完善的php验证库 - [inhere/sroute](https://github.com/inhere/php-srouter) 轻量且快速的HTTP请求路由库 +## 依赖包 + +- [toolkit/cli-utils](https://github.com/php-toolkit/cli-utils) +- [toolkit/pflag](https://github.com/php-toolkit/pflag) +- [toolkit/stdlib](https://github.com/php-toolkit/stdlib) +- [toolkit/sys-utils](https://github.com/php-toolkit/sys-utils) + +## License + +[MIT](LICENSE) From 106d72bec300980826219b526403503c886dc0fd Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 16 Oct 2021 14:21:54 +0800 Subject: [PATCH 176/258] chore: update readme and some method comments --- README.md | 2 +- src/Command.php | 25 +++++++++++++++++++++++++ src/Contract/RouterInterface.php | 13 ++++++------- src/Controller.php | 16 ++++++++-------- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 8c897662..92d9ce45 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ - 功能全面的命令行的选项、参数解析 - 命名参数,数组参数 - 选项绑定 短选项 `-s`,长选项 `--long` - - 支持定义数据类型(`bool,int,string,array`),解析后会自动格式化输入值 + - **NEW** v4 支持定义数据类型(`bool,int,string,array`),解析后会自动格式化输入值 - 命令方法注释自动解析为帮助信息 - 默认提取 `@usage` `@arguments` `@options` `@example` 等信息 - **NEW** v4 版本支持从注释定义选项参数的数据类型 diff --git a/src/Command.php b/src/Command.php index 7c3dffcc..6036add9 100644 --- a/src/Command.php +++ b/src/Command.php @@ -33,6 +33,11 @@ abstract class Command extends AbstractHandler implements CommandInterface { public const METHOD = 'execute'; + /** + * @var Controller + */ + protected $group; + /** * @var Command */ @@ -130,4 +135,24 @@ public function getRealCName(): string { return self::getName(); } + + /** + * Get the group + * + * @return Controller + */ + public function getGroup() + { + return $this->group; + } + + /** + * Set the value of group + * + * @param Controller $group + */ + public function setGroup(Controller $group): void + { + $this->group = $group; + } } diff --git a/src/Contract/RouterInterface.php b/src/Contract/RouterInterface.php index 86fa8b62..fbafc5b7 100644 --- a/src/Contract/RouterInterface.php +++ b/src/Contract/RouterInterface.php @@ -32,9 +32,9 @@ interface RouterInterface * * @param string $name The controller name * @param string|ControllerInterface $class The controller class - * @param array $options array: - * - aliases The command aliases - * - description The description message + * @param array{aliases: array, desc: string} $options The options + * - aliases The command aliases + * - desc The description message * * @return static * @throws InvalidArgumentException @@ -46,10 +46,9 @@ public function addGroup(string $name, $class = null, array $options = []): self * * @param string|CommandInterface $name * @param string|Closure|CommandInterface $handler - * @param array $options - * array: - * - aliases The command aliases - * - description The description message + * @param array{aliases: array, desc: string} $options The options + * - aliases The command aliases + * - desc The description message * * @return static * @throws InvalidArgumentException diff --git a/src/Controller.php b/src/Controller.php index e64cdd16..e6828d95 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -79,13 +79,6 @@ abstract class Controller extends AbstractHandler implements ControllerInterface */ private $action = ''; - /** - * The action method name on the controller. - * - * @var string - */ - private $actionMethod = ''; - /** * The input group name. * @@ -105,6 +98,13 @@ abstract class Controller extends AbstractHandler implements ControllerInterface */ private $defaultAction = ''; + /** + * The action method name on the controller. + * + * @var string + */ + private $actionMethod = ''; + /** * @var string */ @@ -213,7 +213,7 @@ protected function disabledCommands(): array */ protected function onNotFound(string $action, array $args): bool { - // you can add custom logic on sub-command not found. + // TIP: you can add custom logic on sub-command not found. return false; } From 5ff2d149843ec78795adfeea9178f628a104145e Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 16 Oct 2021 14:24:41 +0800 Subject: [PATCH 177/258] fix: select component assert default is empty error --- src/Component/Interact/Checkbox.php | 2 +- src/Component/Interact/Choose.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Component/Interact/Checkbox.php b/src/Component/Interact/Checkbox.php index 5ef8489a..8727806f 100644 --- a/src/Component/Interact/Checkbox.php +++ b/src/Component/Interact/Checkbox.php @@ -59,7 +59,7 @@ public static function select(string $description, $options, $default = null, bo } Console::write($text); - $defText = $default ? "[default:{$default}]" : ''; + $defText = $default !== null ? "[default:{$default}]" : ''; $filter = function ($val) use ($options) { return $val !== 'q' && isset($options[$val]); }; diff --git a/src/Component/Interact/Choose.php b/src/Component/Interact/Choose.php index e088d4ea..301e2ae6 100644 --- a/src/Component/Interact/Choose.php +++ b/src/Component/Interact/Choose.php @@ -63,7 +63,7 @@ public static function one(string $description, $options, $default = null, bool $text .= "\n $key) $value"; } - $defaultText = $default ? "[default:$default]" : ''; + $defaultText = $default !== null ? "[default:$default]" : ''; Console::write($text); beginChoice: From 9285fe0bcd8b9a58cd7a8233c20a181709665bde Mon Sep 17 00:00:00 2001 From: inhere Date: Sat, 16 Oct 2021 19:24:20 +0800 Subject: [PATCH 178/258] chore: update readme --- README.md | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 92d9ce45..6dcb0c79 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,15 @@ - 命令行的php代码高亮支持(来自于`jakub-onderka/php-console-highlighter`并做了一些调整) - 简单的Terminal屏幕、光标控制操作类 - 简单的进程操作使用类(fork,run,stop,wait ... 等) + +所有的特性,功能: -> 所有的特性,效果;都可以运行 `examples/` 中的示例代码 `php examples/app` 展示出来的。基本上涵盖了所有功能,可以直接测试运行 + 都可以运行 `examples/` 中的示例代码 `php examples/app` 展示出来的。基本上涵盖了所有功能,可以直接测试运行 + +## 项目地址 + +- **github** https://github.com/inhere/php-console.git +- **gitee** https://gitee.com/inhere/php-console.git ## 快速安装 @@ -85,31 +92,21 @@ $app->addGroup(MyController::class); $app->run(); ``` -## 文档列表 - -请到[WIKI](https://github.com/inhere/php-console/wiki)查看更多详细的使用文档 +运行示例应用: `php examples/app` +## 文档列表 +**[从v3升级到v4](https://github.com/inhere/php-console/wiki/v3-upgrade-to-v4)** - **[文档首页](https://github.com/inhere/php-console/wiki/home)** - **[功能概览](https://github.com/inhere/php-console/wiki/overview)** - **[安装](https://github.com/inhere/php-console/wiki/install)** - **[创建应用](https://github.com/inhere/php-console/wiki/quick-start)** -- **[添加命令](https://github.com/inhere/php-console/wiki/add-command)** -- **[添加命令组](https://github.com/inhere/php-console/wiki/add-group)** +- **[创建命令/组](https://github.com/inhere/php-console/wiki/create-command-and-group)** - **[注册命令](https://github.com/inhere/php-console/wiki/register-command)** - **[错误/异常捕获](https://github.com/inhere/php-console/wiki/error-handle)** -- **[输入对象](https://github.com/inhere/php-console/wiki/input-instance)** -- **[输出对象](https://github.com/inhere/php-console/wiki/output-instance)** -- **[格式化输出](https://github.com/inhere/php-console/wiki/format-output)** `section`, `panel`, `padding`, `helpPanel`, `table`, `tree`, `title`, `list`, `multiList` 等风格信息输出 -- **[进度动态输出](https://github.com/inhere/php-console/wiki/process-output)** 动态信息显示(`pending/loading`, `pointing`, `spinner`, `counterTxt`, `dynamicText`, `progressTxt`, `progressBar`) -- **[用户交互](https://github.com/inhere/php-console/wiki/user-interactive)** 用户信息交互支持(`select`, `multiSelect`, `confirm`, `ask/question`, `askPassword/askHiddenInput`) -- **[扩展工具](https://github.com/inhere/php-console/wiki/extra-tools)** 打包phar, 命令行下的文件下载, 命令行的php代码高亮 - -## 项目地址 -- **github** https://github.com/inhere/php-console.git -- **gitee** https://gitee.com/inhere/php-console.git +更多使用文档请点击跳转到[WIKI](https://github.com/inhere/php-console/wiki)查看 ## 单元测试 From 0350a915cd7212e0907ebf990936db7e802c1149 Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 21 Oct 2021 23:16:54 +0800 Subject: [PATCH 179/258] refactor: remove some not used intpu methods --- examples/Controller/HomeController.php | 13 +- resource/deprecated/AbstractInput.php | 397 ++++++++++++++++++++ resource/deprecated/InputArgumentsTrait.php | 183 +++++++++ resource/deprecated/InputOptionsTrait.php | 277 ++++++++++++++ src/Concern/InputArgumentsTrait.php | 138 ------- src/Concern/InputOptionsTrait.php | 157 -------- src/Contract/InputInterface.php | 10 - src/GlobalOption.php | 19 + 8 files changed, 881 insertions(+), 313 deletions(-) create mode 100644 resource/deprecated/AbstractInput.php create mode 100644 resource/deprecated/InputArgumentsTrait.php create mode 100644 resource/deprecated/InputOptionsTrait.php diff --git a/examples/Controller/HomeController.php b/examples/Controller/HomeController.php index 3cd581f0..e9bde610 100644 --- a/examples/Controller/HomeController.php +++ b/examples/Controller/HomeController.php @@ -495,19 +495,16 @@ public function paddingCommand(): void /** * a example for use arguments on command * - * @usage home:useArg [arg1=val1 arg2=arg2] [options] + * @usage home useArg [arg1=val1 arg2=arg2] [options] * * @example - * home:useArg status=2 name=john arg0 -s=test --page=23 -d -rf --debug --test=false -a v1 --ab -c -g --cd val -h '' -i stat=online - * home:useArg status=2 name=john name=tom name=jack arg0 -s=test --page=23 --id=23 --id=154 --id=456 -d -rf --debug --test=false + * home useArg status=2 name=john arg0 -s=test --page=23 -d -rf --debug --test=false -a v1 --ab -c -g --cd val -h '' -i stat=online + * home useArg status=2 name=john name=tom name=jack arg0 -s=test --page=23 --id=23 --id=154 --id=456 -d -rf --debug --test=false */ public function useArgCommand(): void { - $this->write('input arguments:'); - $this->output->dump($this->input->getArgs()); - - $this->write('input options:'); - $this->output->dump($this->input->getOpts()); + $this->write('input flags:'); + $this->output->dump($this->input->getFlags()); $this->write('raw argv:'); $this->output->dump($this->input->getTokens()); diff --git a/resource/deprecated/AbstractInput.php b/resource/deprecated/AbstractInput.php new file mode 100644 index 00000000..43873da9 --- /dev/null +++ b/resource/deprecated/AbstractInput.php @@ -0,0 +1,397 @@ +toString(); + } + + /** + * @return string + */ + abstract public function toString(): string; + + /** + * @param array $rawFlags + */ + protected function collectInfo(array $rawFlags): void + { + $this->getPwd(); + if (!$rawFlags) { + return; + } + + $this->tokens = $rawFlags; + + // first is bin file + if (isset($rawFlags[0]) && is_string($rawFlags[0])) { + $this->scriptFile = array_shift($rawFlags); + + // bin name + $this->scriptName = basename($this->scriptFile); + } + + $this->flags = $rawFlags; // no script + + // full script + $this->fullScript = implode(' ', $rawFlags); + } + + /** + * find command name, it is first argument. + * TIP: will reset args data after founded. + */ + public function findCommandName(): string + { + if (!isset($this->args[0])) { + return ''; + } + + $command = ''; + $newArgs = []; + foreach ($this->args as $key => $value) { + if ($key === 0) { + $command = trim($value); + } elseif (is_int($key)) { + $newArgs[] = $value; + } else { + $newArgs[$key] = $value; + } + } + + if ($command) { + $this->args = $newArgs; + } + + return $command; + } + + public function popFirstArg() + { + return array_shift($this->args); + } + + /** + * @return string + */ + public function getCommandPath(): string + { + $path = $this->command; + if ($this->subCommand) { + $path .= ' ' . $this->subCommand; + } + + return $path; + } + + /** + * @return bool + */ + public function isInteractive(): bool + { + return false; + } + + /*********************************************************************************** + * getter/setter + ***********************************************************************************/ + + /** + * @return string + */ + public function getPwd(): string + { + if (!$this->pwd) { + $this->pwd = (string)getcwd(); + } + + return $this->pwd; + } + + /** + * @return string + */ + public function getWorkDir(): string + { + return $this->getPwd(); + } + + /** + * @param string $pwd + */ + public function setPwd(string $pwd): void + { + $this->pwd = $pwd; + } + + /** + * @return string + */ + public function getScriptFile(): string + { + return $this->scriptFile; + } + + /** + * @return string + */ + public function getScriptPath(): string + { + return $this->scriptFile; + } + + /** + * @param string $scriptFile + */ + public function setScriptFile(string $scriptFile): void + { + if ($scriptFile) { + $this->scriptFile = $scriptFile; + // update scriptName + $this->scriptName = basename($scriptFile); + } + } + + /** + * @return string + */ + public function getScriptName(): string + { + return $this->scriptName; + } + + /** + * @return string + */ + public function getBinName(): string + { + return $this->scriptName; + } + + /** + * @return string + */ + public function getCommand(): string + { + return $this->command; + } + + /** + * @param string $command + */ + public function setCommand(string $command): void + { + $this->command = $command; + } + + /** + * @return string + */ + public function getFullScript(): string + { + return $this->fullScript; + } + + /** + * @param string $fullScript + */ + public function setFullScript(string $fullScript): void + { + $this->fullScript = $fullScript; + } + + /** + * @return array + */ + public function getFlags(): array + { + return $this->flags; + } + + /** + * @param array $flags + */ + public function setFlags(array $flags): void + { + $this->flags = $flags; + } + + /** + * @return array + */ + public function getRawFlags(): array + { + return $this->tokens; + } + + /** + * @return array + */ + public function getTokens(): array + { + return $this->tokens; + } + + /** + * @param array $tokens + */ + public function setTokens(array $tokens): void + { + $this->tokens = $tokens; + $this->collectInfo($tokens); + } + + /** + * @return string + */ + public function getSubCommand(): string + { + return $this->subCommand; + } + + /** + * @param string $subCommand + */ + public function setSubCommand(string $subCommand): void + { + $this->subCommand = $subCommand; + } + + /** + * @return FlagsParser + */ + public function getGfs(): FlagsParser + { + return $this->gfs; + } + + /** + * @param FlagsParser $gfs + */ + public function setGfs(FlagsParser $gfs): void + { + $this->gfs = $gfs; + } + + /** + * @return FlagsParser + */ + public function getFs(): FlagsParser + { + return $this->fs; + } + + /** + * @param FlagsParser $fs + */ + public function setFs(FlagsParser $fs): void + { + $this->fs = $fs; + } +} diff --git a/resource/deprecated/InputArgumentsTrait.php b/resource/deprecated/InputArgumentsTrait.php new file mode 100644 index 00000000..03ec7b1f --- /dev/null +++ b/resource/deprecated/InputArgumentsTrait.php @@ -0,0 +1,183 @@ + 0, + * 'name2' => 1, + * ] + * + * @var array + */ + protected $binds = []; + + /** + * @param string $name + * @param int $index + * @return self + */ + public function bindArgument(string $name, int $index) + { + $this->binds[$name] = $index; + return $this; + } + + /** + * @param array $map [ argName => index, ] + * @param bool $replace + */ + public function bindArguments(array $map, bool $replace = false): void + { + if ($replace) { + $this->binds = []; + } + + foreach ($map as $name => $index) { + $this->bindArgument($name, (int)$index); + } + } + + /*********************************************************************************** + * arguments (eg: arg0 name=john city=chengdu) + ***********************************************************************************/ + + /** + * @return array + */ + public function getArgs(): array + { + return $this->args; + } + + /** + * @return array + */ + public function getArguments(): array + { + return $this->getArgs(); + } + + /** + * @param array $args + * @param bool $replace + */ + public function setArgs(array $args, bool $replace = false): void + { + $this->args = $replace ? $args : array_merge($this->args, $args); + } + + /** + * @param string|int $name + * + * @return bool + */ + public function hasArg($name): bool + { + // get real index key + $key = $this->binds[$name] ?? $name; + + return isset($this->args[$key]); + } + + /** + * get Argument + * + * @param null|int|string $name + * @param mixed $default + * + * @return mixed + */ + public function getArgument($name, $default = null) + { + return $this->get($name, $default); + } + + /** + * get argument + * + * @param null|int|string $name + * @param mixed $default + * + * @return mixed + */ + public function getArg($name, $default = null) + { + return $this->get($name, $default); + } + + /** + * get Argument + * + * @param null|int|string $name + * @param mixed $default + * + * @return mixed + */ + public function get($name, $default = null) + { + // get real index key + $key = $this->binds[$name] ?? $name; + + return $this->args[$key] ?? $default; + } + + /** + * Get a required argument + * + * @param int|string $name argument index or name + * @param string $errMsg + * + * @return mixed + */ + public function getRequiredArg($name, string $errMsg = '') + { + // get real index key + $key = $this->binds[$name] ?? $name; + if (isset($this->args[$key])) { + return $this->args[$key]; + } + + if (!$errMsg) { + $errName = is_int($key) ? "'{$name}'(position#{$key})" : "'{$name}'"; + $errMsg = "The argument {$errName} is required"; + } + + throw new PromptException($errMsg); + } + + /** + * clear args + */ + public function clearArgs(): void + { + $this->args = []; + } +} diff --git a/resource/deprecated/InputOptionsTrait.php b/resource/deprecated/InputOptionsTrait.php new file mode 100644 index 00000000..563de77d --- /dev/null +++ b/resource/deprecated/InputOptionsTrait.php @@ -0,0 +1,277 @@ +getLongOpt($name, $default); + } + + return $this->getShortOpt($name, $default); + } + + /** + * Alias of the getOpt() + * + * @param string $name + * @param mixed $default + * + * @return mixed + */ + public function getOption(string $name, $default = null) + { + return $this->getOpt($name, $default); + } + + /** + * Get a required option value + * + * @param string $name + * + * @param string $errMsg + * + * @return mixed + */ + public function getRequiredOpt(string $name, string $errMsg = '') + { + if (null !== ($val = $this->getOpt($name))) { + return $val; + } + + $errMsg = $errMsg ?: "The option '$name' is required"; + throw new PromptException($errMsg); + } + + /** + * check option exists + * + * @param string $name + * + * @return bool + */ + public function hasOpt(string $name): bool + { + return isset($this->sOpts[$name]) || isset($this->lOpts[$name]); + } + + /** + * @return array + */ + public function getOpts(): array + { + return array_merge($this->sOpts, $this->lOpts); + } + + /** + * @return array + */ + public function getOptions(): array + { + return $this->getOpts(); + } + + /** + * clear (l/s)opts + */ + public function clearOpts(): void + { + $this->sOpts = $this->lOpts = []; + } + + /************************** short-opts **********************/ + + /** + * Alias of the sOpt() + * + * @param string $name + * @param null $default + * + * @return mixed|null + */ + public function getShortOpt(string $name, $default = null) + { + return $this->sOpts[$name] ?? $default; + } + + /** + * Check short-opt exists + * + * @param string $name + * + * @return bool + */ + public function hasSOpt(string $name): bool + { + return isset($this->sOpts[$name]); + } + + /** + * @return array + */ + public function getShortOpts(): array + { + return $this->sOpts; + } + + /** + * @param string $name + * @param mixed $value + */ + public function setSOpt(string $name, $value): void + { + $this->sOpts[$name] = $value; + } + + /** + * @return array + */ + public function getSOpts(): array + { + return $this->sOpts; + } + + /** + * @param array $sOpts + * @param bool $replace + */ + public function setSOpts(array $sOpts, bool $replace = false): void + { + $this->sOpts = $replace ? $sOpts : array_merge($this->sOpts, $sOpts); + } + + /** + * clear s-opts + */ + public function clearSOpts(): void + { + $this->sOpts = []; + } + + /************************** long-opts **********************/ + + /** + * Alias of the getLongOpt() + * + * @param string $name + * @param null $default + * + * @return mixed|null + */ + public function lOpt(string $name, $default = null) + { + return $this->lOpts[$name] ?? $default; + } + + /** + * Get long-opt value + * + * @param string $name + * @param null $default + * + * @return mixed|null + */ + public function getLongOpt(string $name, $default = null) + { + return $this->lOpts[$name] ?? $default; + } + + /** + * check long-opt exists + * + * @param string $name + * + * @return bool + */ + public function hasLOpt(string $name): bool + { + return isset($this->lOpts[$name]); + } + + /** + * @return array + */ + public function getLongOpts(): array + { + return $this->lOpts; + } + + /** + * @param string $name + * @param mixed $value + */ + public function setLOpt(string $name, $value): void + { + $this->lOpts[$name] = $value; + } + + /** + * @return array + */ + public function getLOpts(): array + { + return $this->lOpts; + } + + /** + * @param array $lOpts + * @param bool $replace + */ + public function setLOpts(array $lOpts, bool $replace = false): void + { + $this->lOpts = $replace ? $lOpts : array_merge($this->lOpts, $lOpts); + } + + /** + * clear lang opts + */ + public function clearLOpts(): void + { + $this->lOpts = []; + } +} diff --git a/src/Concern/InputArgumentsTrait.php b/src/Concern/InputArgumentsTrait.php index 466c8657..ac1d62ef 100644 --- a/src/Concern/InputArgumentsTrait.php +++ b/src/Concern/InputArgumentsTrait.php @@ -9,10 +9,6 @@ namespace Inhere\Console\Concern; -use Inhere\Console\Exception\PromptException; -use function array_merge; -use function is_int; - /** * Trait InputArgumentsTrait * @@ -27,44 +23,6 @@ trait InputArgumentsTrait */ protected $args = []; - /** - * Bind an name for argument index - * - * [ - * 'name1' => 0, - * 'name2' => 1, - * ] - * - * @var array - */ - protected $binds = []; - - /** - * @param string $name - * @param int $index - * @return self - */ - public function bindArgument(string $name, int $index) - { - $this->binds[$name] = $index; - return $this; - } - - /** - * @param array $map [ argName => index, ] - * @param bool $replace - */ - public function bindArguments(array $map, bool $replace = false): void - { - if ($replace) { - $this->binds = []; - } - - foreach ($map as $name => $index) { - $this->bindArgument($name, (int)$index); - } - } - /*********************************************************************************** * arguments (eg: arg0 name=john city=chengdu) ***********************************************************************************/ @@ -77,102 +35,6 @@ public function getArgs(): array return $this->args; } - /** - * @return array - */ - public function getArguments(): array - { - return $this->getArgs(); - } - - /** - * @param array $args - * @param bool $replace - */ - public function setArgs(array $args, bool $replace = false): void - { - $this->args = $replace ? $args : array_merge($this->args, $args); - } - - /** - * @param string|int $name - * - * @return bool - */ - public function hasArg($name): bool - { - // get real index key - $key = $this->binds[$name] ?? $name; - - return isset($this->args[$key]); - } - - /** - * get Argument - * - * @param null|int|string $name - * @param mixed $default - * - * @return mixed - */ - public function getArgument($name, $default = null) - { - return $this->get($name, $default); - } - - /** - * get argument - * - * @param null|int|string $name - * @param mixed $default - * - * @return mixed - */ - public function getArg($name, $default = null) - { - return $this->get($name, $default); - } - - /** - * get Argument - * - * @param null|int|string $name - * @param mixed $default - * - * @return mixed - */ - public function get($name, $default = null) - { - // get real index key - $key = $this->binds[$name] ?? $name; - - return $this->args[$key] ?? $default; - } - - /** - * Get a required argument - * - * @param int|string $name argument index or name - * @param string $errMsg - * - * @return mixed - */ - public function getRequiredArg($name, string $errMsg = '') - { - // get real index key - $key = $this->binds[$name] ?? $name; - if (isset($this->args[$key])) { - return $this->args[$key]; - } - - if (!$errMsg) { - $errName = is_int($key) ? "'{$name}'(position#{$key})" : "'{$name}'"; - $errMsg = "The argument {$errName} is required"; - } - - throw new PromptException($errMsg); - } - /** * clear args */ diff --git a/src/Concern/InputOptionsTrait.php b/src/Concern/InputOptionsTrait.php index 69177669..e0ff35b6 100644 --- a/src/Concern/InputOptionsTrait.php +++ b/src/Concern/InputOptionsTrait.php @@ -56,50 +56,6 @@ public function getOpt(string $name, $default = null) return $this->getShortOpt($name, $default); } - /** - * Alias of the getOpt() - * - * @param string $name - * @param mixed $default - * - * @return mixed - */ - public function getOption(string $name, $default = null) - { - return $this->getOpt($name, $default); - } - - /** - * Get a required option value - * - * @param string $name - * - * @param string $errMsg - * - * @return mixed - */ - public function getRequiredOpt(string $name, string $errMsg = '') - { - if (null !== ($val = $this->getOpt($name))) { - return $val; - } - - $errMsg = $errMsg ?: "The option '$name' is required"; - throw new PromptException($errMsg); - } - - /** - * check option exists - * - * @param string $name - * - * @return bool - */ - public function hasOpt(string $name): bool - { - return isset($this->sOpts[$name]) || isset($this->lOpts[$name]); - } - /** * @return array */ @@ -108,14 +64,6 @@ public function getOpts(): array return array_merge($this->sOpts, $this->lOpts); } - /** - * @return array - */ - public function getOptions(): array - { - return $this->getOpts(); - } - /** * clear (l/s)opts */ @@ -139,52 +87,6 @@ public function getShortOpt(string $name, $default = null) return $this->sOpts[$name] ?? $default; } - /** - * Check short-opt exists - * - * @param string $name - * - * @return bool - */ - public function hasSOpt(string $name): bool - { - return isset($this->sOpts[$name]); - } - - /** - * @return array - */ - public function getShortOpts(): array - { - return $this->sOpts; - } - - /** - * @param string $name - * @param mixed $value - */ - public function setSOpt(string $name, $value): void - { - $this->sOpts[$name] = $value; - } - - /** - * @return array - */ - public function getSOpts(): array - { - return $this->sOpts; - } - - /** - * @param array $sOpts - * @param bool $replace - */ - public function setSOpts(array $sOpts, bool $replace = false): void - { - $this->sOpts = $replace ? $sOpts : array_merge($this->sOpts, $sOpts); - } - /** * clear s-opts */ @@ -195,19 +97,6 @@ public function clearSOpts(): void /************************** long-opts **********************/ - /** - * Alias of the getLongOpt() - * - * @param string $name - * @param null $default - * - * @return mixed|null - */ - public function lOpt(string $name, $default = null) - { - return $this->lOpts[$name] ?? $default; - } - /** * Get long-opt value * @@ -221,52 +110,6 @@ public function getLongOpt(string $name, $default = null) return $this->lOpts[$name] ?? $default; } - /** - * check long-opt exists - * - * @param string $name - * - * @return bool - */ - public function hasLOpt(string $name): bool - { - return isset($this->lOpts[$name]); - } - - /** - * @return array - */ - public function getLongOpts(): array - { - return $this->lOpts; - } - - /** - * @param string $name - * @param mixed $value - */ - public function setLOpt(string $name, $value): void - { - $this->lOpts[$name] = $value; - } - - /** - * @return array - */ - public function getLOpts(): array - { - return $this->lOpts; - } - - /** - * @param array $lOpts - * @param bool $replace - */ - public function setLOpts(array $lOpts, bool $replace = false): void - { - $this->lOpts = $replace ? $lOpts : array_merge($this->lOpts, $lOpts); - } - /** * clear lang opts */ diff --git a/src/Contract/InputInterface.php b/src/Contract/InputInterface.php index 878f4e6e..87586db3 100644 --- a/src/Contract/InputInterface.php +++ b/src/Contract/InputInterface.php @@ -56,16 +56,6 @@ public function getCommand(): string; */ public function getArgs(): array; - /** - * get Argument - * - * @param null|int|string $name - * @param mixed $default - * - * @return mixed - */ - public function getArg($name, $default = null); - /** * @return array */ diff --git a/src/GlobalOption.php b/src/GlobalOption.php index 1b1c8fd9..e63d7fb9 100644 --- a/src/GlobalOption.php +++ b/src/GlobalOption.php @@ -61,6 +61,25 @@ class GlobalOption '--help' => 'bool;Display application help message;;;h', '--version' => 'bool;Show application version information;;;V', '--no-interactive' => 'bool;Run commands in a non-interactive environment', + '--auto-completion' => [ + 'type' => FlagType::BOOL, + 'hidden' => true, + 'desc' => 'Open generate auto completion script', + // 'envVar' => Console::DEBUG_ENV_KEY, + ], + '--shell-env' => [ + 'type' => FlagType::STRING, + 'hidden' => true, + 'desc' => 'The shell env name for generate auto completion script', + // 'envVar' => Console::DEBUG_ENV_KEY, + 'default' => 'stdout', + ], + '--gen-file' => [ + 'type' => FlagType::STRING, + 'hidden' => true, + 'desc' => 'The output file for generate auto completion script', + // 'envVar' => Console::DEBUG_ENV_KEY, + ], ]; /** From ba69f2e55f5b7bdeef2affaa8a3680fba4b48242 Mon Sep 17 00:00:00 2001 From: Inhere Date: Sun, 24 Oct 2021 23:08:48 +0800 Subject: [PATCH 180/258] breaking: remove more unused input methods --- examples/Controller/HomeController.php | 2 +- src/Concern/ApplicationHelpTrait.php | 23 +++-- src/Concern/AttachApplicationTrait.php | 8 +- src/Concern/InputArgumentsTrait.php | 45 ---------- src/Concern/InputOptionsTrait.php | 120 ------------------------- src/Contract/InputInterface.php | 33 ------- src/Controller.php | 2 +- src/GlobalOption.php | 29 +++--- src/IO/AbstractInput.php | 38 -------- src/IO/Input.php | 32 +------ src/IO/Input/ArrayInput.php | 26 +----- src/IO/Input/StringInput.php | 28 +----- 12 files changed, 41 insertions(+), 345 deletions(-) delete mode 100644 src/Concern/InputArgumentsTrait.php delete mode 100644 src/Concern/InputOptionsTrait.php diff --git a/examples/Controller/HomeController.php b/examples/Controller/HomeController.php index e9bde610..df316dcc 100644 --- a/examples/Controller/HomeController.php +++ b/examples/Controller/HomeController.php @@ -204,7 +204,7 @@ public function colorCheckCommand(): void */ public function artFontCommand(FlagsParser $fs): int { - $name = $this->input->getLongOpt('font', '404'); + $name = $fs->getOpt('font', '404'); if (!ArtFont::isInternalFont($name)) { return $this->output->liteError("Your input font name: $name, is not exists. Please use '-h' see allowed."); diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index 34cce286..4a479806 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -34,7 +34,6 @@ use function ksort; use function sprintf; use function str_replace; -use function strpos; use function strtr; use const PHP_EOL; use const PHP_OS; @@ -174,14 +173,13 @@ public function showHelpInfo(string $command = ''): void */ public function showCommandList(): void { - /** @var Input $input */ - $input = $this->input; + $flags = $this->flags; // has option: --auto-completion - $autoComp = $input->getOpt('auto-completion'); + $autoComp = $flags->getOpt('auto-completion'); // has option: --shell-env - $shellEnv = (string)$input->getLongOpt('shell-env', ''); + $shellEnv = $flags->getOpt('shell-env'); // input is an path: /bin/bash - if ($shellEnv && strpos($shellEnv, '/') !== false) { + if ($shellEnv && str_contains($shellEnv, '/')) { $shellEnv = basename($shellEnv); } @@ -193,13 +191,11 @@ public function showCommandList(): void $this->logf(Console::VERB_DEBUG, 'Display the application commands list'); - $router = $this->getRouter(); - - $hasGroup = $hasCommand = false; - $groupArr = $commandArr = []; - $placeholder = 'No description of the command'; + $hasGroup = $hasCommand = false; + $groupArr = $commandArr = []; // all console groups/controllers + $router = $this->getRouter(); if ($groups = $router->getControllers()) { $hasGroup = true; ksort($groups); @@ -217,6 +213,7 @@ public function showCommandList(): void $commandArr[] = PHP_EOL . '- Alone Commands'; } + $placeholder = 'No description of the command'; foreach ($groups as $name => $info) { $options = $info['options']; $controller = $info['handler']; @@ -319,7 +316,7 @@ protected function dumpAutoCompletion(string $shellEnv, array $data): void // info $glue = ' '; - $genFile = $input->getOpt('gen-file', 'none'); + $genFile = $this->flags->getOpt('gen-file', 'none'); $tplDir = dirname(__DIR__, 2) . '/resource/templates'; if ($shellEnv === 'bash') { @@ -341,7 +338,7 @@ protected function dumpAutoCompletion(string $shellEnv, array $data): void } // new: support custom tpl file for gen completion script - $userTplFile = $input->getOpt('tpl-file'); + $userTplFile = $this->flags->getOpt('tpl-file'); if ($userTplFile && file_exists($userTplFile)) { $tplFile = $userTplFile; } diff --git a/src/Concern/AttachApplicationTrait.php b/src/Concern/AttachApplicationTrait.php index bb47d234..5cabaf3a 100644 --- a/src/Concern/AttachApplicationTrait.php +++ b/src/Concern/AttachApplicationTrait.php @@ -91,8 +91,8 @@ public function isInteractive(): bool return $this->app->isInteractive(); } - $value = $this->input->getOpt(GlobalOption::NO_INTERACTIVE); - return $value === false; + // $value = $this->input->getOpt(GlobalOption::NO_INTERACTIVE); + return $this->input->isInteractive(); } /** @@ -106,7 +106,9 @@ public function getVerbLevel(): int return $this->app->getVerbLevel(); } - return (int)$this->input->getLongOpt('debug', Console::VERB_ERROR); + // return (int)$this->input->getLongOpt('debug', Console::VERB_ERROR); + $envVal = OS::getEnvStrVal(Console::DEBUG_ENV_KEY); + return $envVal !== '' ? (int)$envVal : Console::VERB_ERROR; } /** diff --git a/src/Concern/InputArgumentsTrait.php b/src/Concern/InputArgumentsTrait.php deleted file mode 100644 index ac1d62ef..00000000 --- a/src/Concern/InputArgumentsTrait.php +++ /dev/null @@ -1,45 +0,0 @@ -args; - } - - /** - * clear args - */ - public function clearArgs(): void - { - $this->args = []; - } -} diff --git a/src/Concern/InputOptionsTrait.php b/src/Concern/InputOptionsTrait.php deleted file mode 100644 index e0ff35b6..00000000 --- a/src/Concern/InputOptionsTrait.php +++ /dev/null @@ -1,120 +0,0 @@ -getLongOpt($name, $default); - } - - return $this->getShortOpt($name, $default); - } - - /** - * @return array - */ - public function getOpts(): array - { - return array_merge($this->sOpts, $this->lOpts); - } - - /** - * clear (l/s)opts - */ - public function clearOpts(): void - { - $this->sOpts = $this->lOpts = []; - } - - /************************** short-opts **********************/ - - /** - * Alias of the sOpt() - * - * @param string $name - * @param null $default - * - * @return mixed|null - */ - public function getShortOpt(string $name, $default = null) - { - return $this->sOpts[$name] ?? $default; - } - - /** - * clear s-opts - */ - public function clearSOpts(): void - { - $this->sOpts = []; - } - - /************************** long-opts **********************/ - - /** - * Get long-opt value - * - * @param string $name - * @param null $default - * - * @return mixed|null - */ - public function getLongOpt(string $name, $default = null) - { - return $this->lOpts[$name] ?? $default; - } - - /** - * clear lang opts - */ - public function clearLOpts(): void - { - $this->lOpts = []; - } -} diff --git a/src/Contract/InputInterface.php b/src/Contract/InputInterface.php index 87586db3..099a3283 100644 --- a/src/Contract/InputInterface.php +++ b/src/Contract/InputInterface.php @@ -16,21 +16,6 @@ */ interface InputInterface { - // fixed args and opts for a command/controller-command - public const ARG_REQUIRED = 1; - - public const ARG_OPTIONAL = 2; - - public const ARG_IS_ARRAY = 4; - - public const OPT_BOOLEAN = 1; // eq symfony InputOption::VALUE_NONE - - public const OPT_REQUIRED = 2; - - public const OPT_OPTIONAL = 4; - - public const OPT_IS_ARRAY = 8; - /** * 读取输入信息 * @@ -51,24 +36,6 @@ public function getScriptFile(): string; */ public function getCommand(): string; - /** - * @return array - */ - public function getArgs(): array; - - /** - * @return array - */ - public function getOpts(): array; - - /** - * @param string $name - * @param null $default - * - * @return bool|mixed|null - */ - public function getOpt(string $name, $default = null); - /** * Whether the stream is an interactive terminal */ diff --git a/src/Controller.php b/src/Controller.php index e6828d95..79bf6a78 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -279,7 +279,7 @@ public function doRun(array $args) $command = $first; array_shift($args); - $this->input->popFirstArg(); + // $this->input->popFirstArg(); } // update subcommand diff --git a/src/GlobalOption.php b/src/GlobalOption.php index e63d7fb9..48607f99 100644 --- a/src/GlobalOption.php +++ b/src/GlobalOption.php @@ -50,35 +50,42 @@ class GlobalOption * @psalm-var array */ private static $options = [ - '--debug' => [ + 'debug' => [ 'type' => FlagType::INT, 'desc' => 'Setting the runtime log debug level(quiet 0 - 5 crazy)', 'envVar' => Console::DEBUG_ENV_KEY, ], - '--ishell' => 'bool;Run application an interactive shell environment', - '--profile' => 'bool;Display timing and memory usage information', - '--no-color' => 'bool;Disable color/ANSI for message output', - '--help' => 'bool;Display application help message;;;h', - '--version' => 'bool;Show application version information;;;V', - '--no-interactive' => 'bool;Run commands in a non-interactive environment', - '--auto-completion' => [ + 'ishell' => 'bool;Run application an interactive shell environment', + 'profile' => 'bool;Display timing and memory usage information', + 'no-color' => 'bool;Disable color/ANSI for message output', + 'help' => 'bool;Display application help message;;;h', + 'version' => 'bool;Show application version information;;;V', + 'no-interactive' => 'bool;Run commands in a non-interactive environment', + // - hidden options + 'auto-completion' => [ 'type' => FlagType::BOOL, 'hidden' => true, 'desc' => 'Open generate auto completion script', // 'envVar' => Console::DEBUG_ENV_KEY, ], - '--shell-env' => [ + 'shell-env' => [ 'type' => FlagType::STRING, 'hidden' => true, 'desc' => 'The shell env name for generate auto completion script', // 'envVar' => Console::DEBUG_ENV_KEY, - 'default' => 'stdout', ], - '--gen-file' => [ + 'gen-file' => [ 'type' => FlagType::STRING, 'hidden' => true, 'desc' => 'The output file for generate auto completion script', // 'envVar' => Console::DEBUG_ENV_KEY, + 'default' => 'stdout', + ], + 'tpl-file' => [ + 'type' => FlagType::STRING, + 'hidden' => true, + 'desc' => 'custom tpl file for generate completion script', + // 'default' => 'stdout', ], ]; diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index 473b95b7..a526c43f 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -9,8 +9,6 @@ namespace Inhere\Console\IO; -use Inhere\Console\Concern\InputArgumentsTrait; -use Inhere\Console\Concern\InputOptionsTrait; use Inhere\Console\Contract\InputInterface; use Toolkit\PFlag\FlagsParser; use Toolkit\PFlag\SFlags; @@ -29,8 +27,6 @@ */ abstract class AbstractInput implements InputInterface { - use InputArgumentsTrait, InputOptionsTrait; - /** * Global flags parser * @@ -143,40 +139,6 @@ protected function collectInfo(array $rawFlags): void $this->fullScript = implode(' ', $rawFlags); } - /** - * find command name, it is first argument. - * TIP: will reset args data after founded. - */ - public function findCommandName(): string - { - if (!isset($this->args[0])) { - return ''; - } - - $command = ''; - $newArgs = []; - foreach ($this->args as $key => $value) { - if ($key === 0) { - $command = trim($value); - } elseif (is_int($key)) { - $newArgs[] = $value; - } else { - $newArgs[$key] = $value; - } - } - - if ($command) { - $this->args = $newArgs; - } - - return $command; - } - - public function popFirstArg() - { - return array_shift($this->args); - } - /** * @return string */ diff --git a/src/IO/Input.php b/src/IO/Input.php index c9be3a9c..ed4efa6a 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -44,9 +44,8 @@ class Input extends AbstractInput * Input constructor. * * @param null|array $args - * @param bool $parsing */ - public function __construct(array $args = null, bool $parsing = true) + public function __construct(array $args = null) { if (null === $args) { $args = $_SERVER['argv']; @@ -54,35 +53,6 @@ public function __construct(array $args = null, bool $parsing = true) $this->inputStream = Cli::getInputStream(); $this->collectInfo($args); - - if ($parsing) { - $this->doParse($this->flags); - } - } - - /** - * re-parse args/opts from given args - * - * @param array $args - */ - public function parse(array $args): void - { - $this->doParse($args); - } - - /** - * @param array $args - */ - protected function doParse(array $args): void - { - [ - $this->args, - $this->sOpts, - $this->lOpts - ] = Flags::parseArgv($args); - - // find command name - $this->command = $this->findCommandName(); } public function resetInputStream(): void diff --git a/src/IO/Input/ArrayInput.php b/src/IO/Input/ArrayInput.php index 0dbde325..138b2da6 100644 --- a/src/IO/Input/ArrayInput.php +++ b/src/IO/Input/ArrayInput.php @@ -23,31 +23,9 @@ class ArrayInput extends Input * Input constructor. * * @param null|array $args - * @param bool $parsing */ - public function __construct(array $args = null, bool $parsing = true) + public function __construct(array $args = null) { - parent::__construct([], false); - - $this->collectInfo($args); - - if ($parsing && $args) { - $this->doParse($this->flags); - } - } - - /** - * @param array $args - */ - protected function doParse(array $args): void - { - [ - $this->args, - $this->sOpts, - $this->lOpts - ] = Flags::parseArray($args); - - // find command name - $this->command = $this->findCommandName(); + parent::__construct($args); } } diff --git a/src/IO/Input/StringInput.php b/src/IO/Input/StringInput.php index 66f8e85c..fad5d0b0 100644 --- a/src/IO/Input/StringInput.php +++ b/src/IO/Input/StringInput.php @@ -24,32 +24,10 @@ class StringInput extends Input * Input constructor. * * @param string $line - * @param bool $parsing */ - public function __construct(string $line, bool $parsing = true) + public function __construct(string $line) { - parent::__construct([], false); - - if ($parsing && $line) { - $flags = LineParser::parseIt($line); - - $this->collectInfo($flags); - $this->doParse($this->flags); - } - } - - /** - * @param array $args - */ - protected function doParse(array $args): void - { - [ - $this->args, - $this->sOpts, - $this->lOpts - ] = Flags::parseArray($args); - - // find command name - $this->command = $this->findCommandName(); + $flags = LineParser::parseIt($line); + parent::__construct($flags); } } From a90478f356400e79976bd1f86627cde778138f71 Mon Sep 17 00:00:00 2001 From: Inhere Date: Tue, 26 Oct 2021 19:49:17 +0800 Subject: [PATCH 181/258] breaking: rename flag package methods --- src/Concern/ApplicationHelpTrait.php | 4 ++-- src/Concern/CommandHelpTrait.php | 6 +++--- src/Concern/ControllerHelpTrait.php | 4 ++-- src/Controller.php | 3 ++- src/GlobalOption.php | 12 ++++++++---- src/Util/FormatUtil.php | 4 ++-- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index 4a479806..54cd17f9 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -125,7 +125,7 @@ public function showHelpInfo(string $command = ''): void // built in options // $globalOptions = self::$globalOptions; - $globalOptions = $this->flags->getOptsHelpData(); + $globalOptions = $this->flags->getOptsHelpLines(); // append generate options: // php examples/app --auto-completion --shell-env zsh --gen-file // php examples/app --auto-completion --shell-env zsh --gen-file stdout @@ -277,7 +277,7 @@ public function showCommandList(): void // built in options // $globOpts = self::$globalOptions; - $globOpts = $this->flags->getOptsHelpData(); + $globOpts = $this->flags->getOptsHelpLines(); Show::mList([ 'Usage:' => "$scriptName {COMMAND} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", diff --git a/src/Concern/CommandHelpTrait.php b/src/Concern/CommandHelpTrait.php index 435e431e..97bec6d7 100644 --- a/src/Concern/CommandHelpTrait.php +++ b/src/Concern/CommandHelpTrait.php @@ -140,8 +140,8 @@ public function showHelpByFlagsParser(FlagsParser $fs, array $aliases = [], stri $help['Usage:'] = "$path [--options ...] [arguments ...]"; - $help['Options:'] = FormatUtil::alignOptions($fs->getOptsHelpData()); - $help['Argument:'] = $fs->getArgsHelpData(); + $help['Options:'] = FormatUtil::alignOptions($fs->getOptsHelpLines()); + $help['Argument:'] = $fs->getArgsHelpLines(); $help['Example:'] = $fs->getExampleHelp(); $help['More Help:'] = $fs->getMoreHelp(); @@ -152,7 +152,7 @@ public function showHelpByFlagsParser(FlagsParser $fs, array $aliases = [], stri // attached to console app if ($app = $this->getApp()) { - $help['Global Options:'] = FormatUtil::alignOptions($app->getFlags()->getOptsHelpData()); + $help['Global Options:'] = FormatUtil::alignOptions($app->getFlags()->getOptsHelpLines()); } $this->output->mList($help, [ diff --git a/src/Concern/ControllerHelpTrait.php b/src/Concern/ControllerHelpTrait.php index 52b68b5d..a0b454c0 100644 --- a/src/Concern/ControllerHelpTrait.php +++ b/src/Concern/ControllerHelpTrait.php @@ -165,7 +165,7 @@ public function showCommandList(): void $globalOptions = []; if ($app = $this->getApp()) { - $globalOptions = $app->getFlags()->getOptsHelpData(); + $globalOptions = $app->getFlags()->getOptsHelpLines(); } $this->output->startBuffer(); @@ -175,7 +175,7 @@ public function showCommandList(): void $this->output->writef("Alias: %s\n", implode(',', $aliases)); } - $groupOptions = $this->flags->getOptsHelpData(); + $groupOptions = $this->flags->getOptsHelpLines(); $this->output->mList([ 'Usage:' => $usage, //'Group Name:' => "$sName", diff --git a/src/Controller.php b/src/Controller.php index 79bf6a78..41e88e27 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -40,6 +40,7 @@ use function substr; use function trim; use function ucfirst; +use function vdump; /** * Class Controller @@ -520,7 +521,7 @@ protected function showHelp(): bool */ protected function beforeRenderCommandHelp(array &$help): void { - $help['Group Options:'] = FormatUtil::alignOptions($this->flags->getOptsHelpData()); + $help['Group Options:'] = FormatUtil::alignOptions($this->flags->getOptsHelpLines()); } /** diff --git a/src/GlobalOption.php b/src/GlobalOption.php index 48607f99..b6ebc100 100644 --- a/src/GlobalOption.php +++ b/src/GlobalOption.php @@ -51,9 +51,10 @@ class GlobalOption */ private static $options = [ 'debug' => [ - 'type' => FlagType::INT, - 'desc' => 'Setting the runtime log debug level(quiet 0 - 5 crazy)', - 'envVar' => Console::DEBUG_ENV_KEY, + 'type' => FlagType::INT, + 'desc' => 'Setting the runtime log debug level, quiet 0 - crazy 5', + 'envVar' => Console::DEBUG_ENV_KEY, + 'default' => Console::VERB_ERROR, ], 'ishell' => 'bool;Run application an interactive shell environment', 'profile' => 'bool;Display timing and memory usage information', @@ -104,7 +105,10 @@ class GlobalOption */ protected static $groupOptions = [ // '--help' => 'bool;Display this help message;;;h', - self::SHOW_DISABLED => 'string;Whether display disabled commands', + self::SHOW_DISABLED => [ + 'hidden' => true, + 'desc' => 'Whether display disabled commands' + ], ]; /** diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index 75dfe8ea..ba43c240 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -160,10 +160,10 @@ public static function alignOptions(array $options): array } // padding length equals to '-h, ' - if (!strpos($name, ',')) { + if (!str_contains($name, ',')) { $name = ' ' . $name; } else { - $name = str_replace([' ', ','], ['', ', '], $name); + $name = str_replace([',-'], [', -'], $name); } $formatted[$name] = $des; From cd448f26078140604b1ba37dfee5b13dfdefce8b Mon Sep 17 00:00:00 2001 From: Inhere Date: Wed, 27 Oct 2021 18:38:25 +0800 Subject: [PATCH 182/258] feat: add new formatter for pretty JSON render --- src/Component/Formatter/JSONPretty.php | 93 +++++++++++++++++++++----- src/IO/Input.php | 38 ++++++++--- src/IO/Output.php | 9 +++ 3 files changed, 111 insertions(+), 29 deletions(-) diff --git a/src/Component/Formatter/JSONPretty.php b/src/Component/Formatter/JSONPretty.php index 73af488a..35538800 100644 --- a/src/Component/Formatter/JSONPretty.php +++ b/src/Component/Formatter/JSONPretty.php @@ -9,52 +9,109 @@ namespace Inhere\Console\Component\Formatter; -use Inhere\Console\Component\MessageFormatter; -use Toolkit\Stdlib\Str; +use JsonException; +use Toolkit\Cli\Color\ColorTag; +use Toolkit\Stdlib\Helper\JsonHelper; +use Toolkit\Stdlib\Obj\AbstractObj; +use Toolkit\Stdlib\Str\StrBuffer; +use function array_merge; +use function explode; +use function is_numeric; use function json_decode; +use function preg_replace_callback; +use function rtrim; +use function str_contains; +use function str_ends_with; +use function trim; /** * class JSONPretty */ -class JSONPretty extends MessageFormatter +class JSONPretty extends AbstractObj { + public const DEFAULT_THEME = [ + 'keyName' => 'mga', + 'strVal' => 'info', + 'intVal' => 'cyan', + 'boolVal' => 'red', + ]; + + /** + * @var array{keyName: string, strVal: string, intVal: string, boolVal: string} + */ + protected array $theme = self::DEFAULT_THEME; + /** - * @var array|iterable + * @var int */ - public $data; + public int $maxDepth = 10; /** * @param string $json * - * @return static + * @return string + * @throws JsonException */ - public static function newFromString(string $json): self + public function render(string $json): string { - $self = new self(); - $self->setData((array)json_decode($json, true)); + $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR); - return $self; + return $this->renderData($data); } /** + * @param array $data + * * @return string */ - public function format(): string + public function renderData(array $data): string { - $buf = Str\StrBuffer::new(); + $buf = StrBuffer::new(); + $json = JsonHelper::prettyJSON($data); - foreach ($this->data as $key => $item) { - // TODO + foreach (explode("\n", $json) as $line) { + $trimmed = trim($line); + // start or end chars. eg: {} [] + if (!str_contains($trimmed, ': ')) { + $buf->writeln($line); + continue; + } + + [$key, $val] = explode(': ', $line); + + // format key name. + if ($keyTag = $this->theme['keyName']) { + $key = preg_replace_callback('/"[\w-]+"/', static function ($m) use ($keyTag) { + return ColorTag::wrap($m[0], $keyTag); + }, $key); + } + + // has end ',' clear it. + if ($hasEndComma = str_ends_with($val, ',')) { + $val = rtrim($val, ','); + } + + // bool val + if ($val === 'true' || $val === 'false') { + $val = ColorTag::wrap($val, $this->theme['boolVal']); + } elseif (is_numeric($val)) { // number + $val = ColorTag::wrap($val, $this->theme['intVal']); + } else { // string + $val = ColorTag::wrap($val, $this->theme['strVal']); + } + + $buf->writeln($key . ': ' . $val . ($hasEndComma ? ',' : '')); } - return $buf->toString(); + return $buf->getAndClear(); } /** - * @param array|iterable $data + * @param array $theme */ - public function setData($data): void + public function setTheme(array $theme): void { - $this->data = $data; + $this->theme = array_merge($this->theme, $theme); } } + diff --git a/src/IO/Input.php b/src/IO/Input.php index ed4efa6a..0d7e90db 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -38,7 +38,7 @@ class Input extends AbstractInput * * @var resource */ - protected $inputStream; + protected $stream; /** * Input constructor. @@ -51,15 +51,10 @@ public function __construct(array $args = null) $args = $_SERVER['argv']; } - $this->inputStream = Cli::getInputStream(); + $this->stream = Cli::getInputStream(); $this->collectInfo($args); } - public function resetInputStream(): void - { - $this->inputStream = Cli::getInputStream(); - } - /** * @return string */ @@ -88,6 +83,14 @@ protected function tokenEscape(string $token): string return $token; } + /** + * @return string + */ + public function readAll(): string + { + return File::streamReadAll($this->stream); + } + /** * Read input information * @@ -99,10 +102,23 @@ protected function tokenEscape(string $token): string public function readln(string $question = '', bool $nl = false): string { if ($question) { - fwrite($this->inputStream, $question . ($nl ? "\n" : '')); + fwrite($this->stream, $question . ($nl ? "\n" : '')); } - return File::streamFgets($this->inputStream); + return File::streamFgets($this->stream); + } + + /** + * @return resource + */ + public function getInputStream() + { + return $this->stream; + } + + public function resetInputStream(): void + { + $this->stream = Cli::getInputStream(); } /*********************************************************************************** @@ -128,9 +144,9 @@ public function getFullCommand(): string /** * @return resource */ - public function getInputStream() + public function getStream() { - return $this->inputStream; + return $this->stream; } /** diff --git a/src/IO/Output.php b/src/IO/Output.php index 209cddaf..bcd18c8f 100644 --- a/src/IO/Output.php +++ b/src/IO/Output.php @@ -13,6 +13,7 @@ use Inhere\Console\Console; use Inhere\Console\Concern\FormatOutputAwareTrait; use Toolkit\Cli\Cli; +use Toolkit\FsUtil\File; /** * Class Output @@ -130,6 +131,14 @@ public function read(string $question = '', bool $nl = false): string return Console::read($question, $nl); } + /** + * @return string + */ + public function readAll(): string + { + return File::streamReadAll($this->outputStream); + } + /** * Write a message to standard error output stream. * From e4a269512151bda1c029a9f536acb7d6e60531af Mon Sep 17 00:00:00 2001 From: Inhere Date: Wed, 27 Oct 2021 21:36:16 +0800 Subject: [PATCH 183/258] chore: update some for format message render --- src/Component/Interact/Choose.php | 6 +++++- src/Concern/StyledOutputAwareTrait.php | 10 +++++----- src/Util/FormatUtil.php | 4 ++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Component/Interact/Choose.php b/src/Component/Interact/Choose.php index 301e2ae6..85b6f97e 100644 --- a/src/Component/Interact/Choose.php +++ b/src/Component/Interact/Choose.php @@ -11,8 +11,11 @@ use Inhere\Console\Console; use Inhere\Console\Util\Show; +use Toolkit\Cli\Cli; use function array_key_exists; +use function array_keys; use function explode; +use function implode; use function is_array; use function trim; @@ -51,7 +54,7 @@ public static function one(string $description, $options, $default = null, bool // If default option is error if (null !== $default && !isset($options[$default])) { - Show::error("The default option [{$default}] don't exists.", true); + Show::error("The default option [$default] don't exists.", true); } if ($allowExit) { @@ -75,6 +78,7 @@ public static function one(string $description, $options, $default = null, bool // error, allow try again once. if (!array_key_exists($r, $options)) { + Cli::warn('[WARN] input must in the list: ' . implode(',', array_keys($options))); goto beginChoice; } diff --git a/src/Concern/StyledOutputAwareTrait.php b/src/Concern/StyledOutputAwareTrait.php index 2c7c2b29..9bc85e91 100644 --- a/src/Concern/StyledOutputAwareTrait.php +++ b/src/Concern/StyledOutputAwareTrait.php @@ -60,12 +60,12 @@ * * @method Generator counterTxt($msg = 'Pending ', $ended = false) * - * @method confirm(string $question, bool $default = true): bool - * @method unConfirm(string $question, bool $default = true): bool + * @method bool confirm(string $question, bool $default = true) + * @method bool unConfirm(string $question, bool $default = true) * @method string select(string $description, $options, $default = null, bool $allowExit = true, array $opts = []) - * @method checkbox(string $description, $options, $default = null, bool $allowExit = true): array - * @method ask(string $question, string $default = '', Closure $validator = null): string - * @method askPassword(string $prompt = 'Enter Password:'): string + * @method array checkbox(string $description, $options, $default = null, bool $allowExit = true) + * @method string ask(string $question, string $default = '', Closure $validator = null) + * @method string askPassword(string $prompt = 'Enter Password:') * @see Interact */ trait StyledOutputAwareTrait diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index ba43c240..805cc66f 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -19,11 +19,13 @@ use function array_merge; use function array_shift; use function explode; +use function get_class; use function implode; use function is_array; use function is_bool; use function is_int; use function is_numeric; +use function is_object; use function is_scalar; use function rtrim; use function str_repeat; @@ -272,6 +274,8 @@ public static function spliceKeyValue(array $data, array $opts = []): string } $value = rtrim($temp, ' ,') . ']'; + // } elseif (is_object($value)) { + // $value = get_class($value); } elseif (is_bool($value)) { $value = $value ? '(True)' : '(False)'; } else { // to string. From 6f9705025d1c08686367f8e2cfa9fad8eaca5b36 Mon Sep 17 00:00:00 2001 From: Inhere Date: Wed, 27 Oct 2021 22:04:44 +0800 Subject: [PATCH 184/258] refactor: refacting the input output class. --- src/Contract/InputInterface.php | 5 ++ src/IO/AbstractInput.php | 31 ++++++++++++- src/IO/Input.php | 82 ++------------------------------- src/IO/Input/AloneInput.php | 33 ------------- src/IO/Input/ArrayInput.php | 14 +++++- src/IO/Input/StreamInput.php | 7 +-- src/IO/Input/StringInput.php | 2 +- src/IO/Output.php | 66 +++++++++++--------------- src/IO/Output/MemoryOutput.php | 3 ++ src/IO/Output/StreamOutput.php | 4 +- src/IO/TempStream.php | 2 +- test/IO/InputTest.php | 4 +- 12 files changed, 91 insertions(+), 162 deletions(-) delete mode 100644 src/IO/Input/AloneInput.php diff --git a/src/Contract/InputInterface.php b/src/Contract/InputInterface.php index 099a3283..1f24a44b 100644 --- a/src/Contract/InputInterface.php +++ b/src/Contract/InputInterface.php @@ -40,4 +40,9 @@ public function getCommand(): string; * Whether the stream is an interactive terminal */ public function isInteractive(): bool; + + /** + * @return string + */ + public function toString(): string; } diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index a526c43f..df1f421b 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -10,14 +10,17 @@ namespace Inhere\Console\IO; use Inhere\Console\Contract\InputInterface; +use Toolkit\Cli\Helper\FlagHelper; use Toolkit\PFlag\FlagsParser; use Toolkit\PFlag\SFlags; +use function array_map; use function array_shift; use function basename; use function getcwd; use function implode; use function is_int; use function is_string; +use function preg_match; use function trim; /** @@ -111,7 +114,33 @@ public function __toString(): string /** * @return string */ - abstract public function toString(): string; + public function toString(): string + { + if (!$this->tokens) { + return ''; + } + + $tokens = array_map([$this, 'tokenEscape'], $this->tokens); + return implode(' ', $tokens); + } + + /** + * @param string $token + * + * @return string + */ + protected function tokenEscape(string $token): string + { + if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { + return $match[1] . FlagHelper::escapeToken($match[2]); + } + + if ($token && $token[0] !== '-') { + return FlagHelper::escapeToken($token); + } + + return $token; + } /** * @param array $rawFlags diff --git a/src/IO/Input.php b/src/IO/Input.php index 0d7e90db..84f930f9 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -9,21 +9,15 @@ namespace Inhere\Console\IO; +use Inhere\Console\IO\Input\StreamInput; use Toolkit\Cli\Cli; -use Toolkit\Cli\Flags; -use Toolkit\Cli\Helper\FlagHelper; -use Toolkit\FsUtil\File; -use function array_map; -use function fwrite; -use function implode; -use function preg_match; /** - * Class Input - The input information. by parse global var $argv. + * Class Input - The std input. * * @package Inhere\Console\IO */ -class Input extends AbstractInput +class Input extends StreamInput { /** * The real command ID(group:command) @@ -33,13 +27,6 @@ class Input extends AbstractInput */ protected $commandId = ''; - /** - * Default is STDIN - * - * @var resource - */ - protected $stream; - /** * Input constructor. * @@ -51,63 +38,10 @@ public function __construct(array $args = null) $args = $_SERVER['argv']; } - $this->stream = Cli::getInputStream(); + parent::__construct(Cli::getInputStream()); $this->collectInfo($args); } - /** - * @return string - */ - public function toString(): string - { - $tokens = array_map([$this, 'tokenEscape'], $this->tokens); - - return implode(' ', $tokens); - } - - /** - * @param string $token - * - * @return string - */ - protected function tokenEscape(string $token): string - { - if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { - return $match[1] . FlagHelper::escapeToken($match[2]); - } - - if ($token && $token[0] !== '-') { - return FlagHelper::escapeToken($token); - } - - return $token; - } - - /** - * @return string - */ - public function readAll(): string - { - return File::streamReadAll($this->stream); - } - - /** - * Read input information - * - * @param string $question 若不为空,则先输出文本消息 - * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 - * - * @return string - */ - public function readln(string $question = '', bool $nl = false): string - { - if ($question) { - fwrite($this->stream, $question . ($nl ? "\n" : '')); - } - - return File::streamFgets($this->stream); - } - /** * @return resource */ @@ -141,14 +75,6 @@ public function getFullCommand(): string return $this->scriptFile . ' ' . $this->getCommandPath(); } - /** - * @return resource - */ - public function getStream() - { - return $this->stream; - } - /** * Get command ID e.g `http:start` * diff --git a/src/IO/Input/AloneInput.php b/src/IO/Input/AloneInput.php deleted file mode 100644 index db5d8fc0..00000000 --- a/src/IO/Input/AloneInput.php +++ /dev/null @@ -1,33 +0,0 @@ -args, - $this->sOpts, - $this->lOpts - ] = Flags::parseArgv($args); - } -} diff --git a/src/IO/Input/ArrayInput.php b/src/IO/Input/ArrayInput.php index 138b2da6..8d372d3a 100644 --- a/src/IO/Input/ArrayInput.php +++ b/src/IO/Input/ArrayInput.php @@ -11,6 +11,7 @@ use Inhere\Console\IO\Input; use Toolkit\Cli\Flags; +use function is_int; /** * Class ArrayInput @@ -22,10 +23,19 @@ class ArrayInput extends Input /** * Input constructor. * - * @param null|array $args + * @param array $arr */ - public function __construct(array $args = null) + public function __construct(array $arr = []) { + $args = []; + foreach ($arr as $key => $val) { + if (!is_int($key)) { + $args[] = $key; + } + + $args[] = $val; + } + parent::__construct($args); } } diff --git a/src/IO/Input/StreamInput.php b/src/IO/Input/StreamInput.php index 8f4c8f86..5e0e24c5 100644 --- a/src/IO/Input/StreamInput.php +++ b/src/IO/Input/StreamInput.php @@ -39,11 +39,6 @@ public function __construct($stream = STDIN) $this->setStream($stream); } - public function toString(): string - { - return ''; - } - /** * @return string */ @@ -98,7 +93,7 @@ protected function setStream($stream): void File::assertStream($stream); $meta = stream_get_meta_data($stream); - if (strpos($meta['mode'], 'r') === false && strpos($meta['mode'], '+') === false) { + if (!str_contains($meta['mode'], 'r') && !str_contains($meta['mode'], '+')) { throw new InvalidArgumentException('Expected a readable stream'); } diff --git a/src/IO/Input/StringInput.php b/src/IO/Input/StringInput.php index fad5d0b0..ea777c1e 100644 --- a/src/IO/Input/StringInput.php +++ b/src/IO/Input/StringInput.php @@ -10,7 +10,6 @@ namespace Inhere\Console\IO\Input; use Inhere\Console\IO\Input; -use Toolkit\Cli\Flags; use Toolkit\Cli\Util\LineParser; /** @@ -28,6 +27,7 @@ class StringInput extends Input public function __construct(string $line) { $flags = LineParser::parseIt($line); + parent::__construct($flags); } } diff --git a/src/IO/Output.php b/src/IO/Output.php index bcd18c8f..9aabb7f7 100644 --- a/src/IO/Output.php +++ b/src/IO/Output.php @@ -9,28 +9,21 @@ namespace Inhere\Console\IO; -use Toolkit\Cli\Style; -use Inhere\Console\Console; use Inhere\Console\Concern\FormatOutputAwareTrait; +use Inhere\Console\Console; +use Inhere\Console\IO\Output\StreamOutput; use Toolkit\Cli\Cli; -use Toolkit\FsUtil\File; +use Toolkit\Cli\Style; /** * Class Output * * @package Inhere\Console\IO */ -class Output extends AbstractOutput +class Output extends StreamOutput { use FormatOutputAwareTrait; - /** - * Normal output stream. Default is STDOUT - * - * @var resource - */ - protected $outputStream; - /** * Error output stream. Default is STDERR * @@ -48,25 +41,14 @@ class Output extends AbstractOutput /** * Output constructor. - * - * @param null|resource $outputStream */ - public function __construct($outputStream = null) + public function __construct() { - if ($outputStream) { - $this->outputStream = $outputStream; - } else { - $this->outputStream = Cli::getOutputStream(); - } + parent::__construct(Cli::getOutputStream()); $this->getStyle(); } - public function resetOutputStream(): void - { - $this->outputStream = Cli::getOutputStream(); - } - /*************************************************************************** * Output buffer ***************************************************************************/ @@ -90,14 +72,14 @@ public function clearBuffer(): void /** * stop buffering and flush buffer text * - * @param bool $flush - * @param bool $nl - * @param bool $quit + * @param bool $flush + * @param bool $nl + * @param bool|int $quit * @param array $opts * * @see Console::stopBuffer() */ - public function stopBuffer(bool $flush = true, $nl = false, $quit = false, array $opts = []): void + public function stopBuffer(bool $flush = true, bool $nl = false, $quit = false, array $opts = []): void { Console::stopBuffer($flush, $nl, $quit, $opts); } @@ -105,8 +87,8 @@ public function stopBuffer(bool $flush = true, $nl = false, $quit = false, array /** * stop buffering and flush buffer text * - * @param bool $nl - * @param bool $quit + * @param bool $nl + * @param bool|int $quit * @param array $opts */ public function flush(bool $nl = false, $quit = false, array $opts = []): void @@ -122,7 +104,7 @@ public function flush(bool $nl = false, $quit = false, array $opts = []): void * Read input information * * @param string $question 若不为空,则先输出文本 - * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 + * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 * * @return string */ @@ -132,17 +114,22 @@ public function read(string $question = '', bool $nl = false): string } /** + * Read input information + * + * @param string $question + * @param bool $nl + * * @return string */ - public function readAll(): string + public function readln(string $question = '', bool $nl = false): string { - return File::streamReadAll($this->outputStream); + return Console::readln($question, $nl); } /** * Write a message to standard error output stream. * - * @param string $text + * @param string $text * @param boolean $nl True (default) to append a new line at the end of the output string. * * @return int @@ -183,7 +170,12 @@ public function supportColor(): bool */ public function getOutputStream() { - return $this->outputStream; + return $this->stream; + } + + public function resetOutputStream(): void + { + $this->stream = Cli::getOutputStream(); } /** @@ -195,8 +187,7 @@ public function getOutputStream() */ public function setOutputStream($outStream): self { - $this->outputStream = $outStream; - + $this->stream = $outStream; return $this; } @@ -218,7 +209,6 @@ public function getErrorStream() public function setErrorStream($errorStream): self { $this->errorStream = $errorStream; - return $this; } } diff --git a/src/IO/Output/MemoryOutput.php b/src/IO/Output/MemoryOutput.php index 1a197f21..ded3af00 100644 --- a/src/IO/Output/MemoryOutput.php +++ b/src/IO/Output/MemoryOutput.php @@ -23,6 +23,9 @@ public function __construct() parent::__construct(fopen('php://memory', 'rwb')); } + /** + * @return string + */ public function getBuffer(): string { return ''; diff --git a/src/IO/Output/StreamOutput.php b/src/IO/Output/StreamOutput.php index 36fdeee2..23143714 100644 --- a/src/IO/Output/StreamOutput.php +++ b/src/IO/Output/StreamOutput.php @@ -25,6 +25,8 @@ class StreamOutput extends AbstractOutput { /** + * Normal output stream. Default is STDOUT + * * @var resource */ protected $stream; @@ -75,7 +77,7 @@ protected function setStream($stream): void File::assertStream($stream); $meta = stream_get_meta_data($stream); - if (strpos($meta['mode'], 'w') === false && strpos($meta['mode'], '+') === false) { + if (!str_contains($meta['mode'], 'w') && !str_contains($meta['mode'], '+')) { throw new InvalidArgumentException('Expected a readable stream'); } diff --git a/src/IO/TempStream.php b/src/IO/TempStream.php index cd7093e5..e535bdd7 100644 --- a/src/IO/TempStream.php +++ b/src/IO/TempStream.php @@ -66,7 +66,7 @@ public static function close(): void } /** - * @param string|int $string + * @param string|int|mixed $string */ public static function write($string): void { diff --git a/test/IO/InputTest.php b/test/IO/InputTest.php index aab85a12..b647980d 100644 --- a/test/IO/InputTest.php +++ b/test/IO/InputTest.php @@ -25,6 +25,8 @@ public function testBasic(): void $this->assertSame('./bin/app', $in->getScriptFile()); $this->assertSame('app', $in->getScriptName()); - $this->assertSame('cmd', $in->getCommand()); + // $this->assertSame('cmd', $in->getCommand()); + $this->assertEquals('cmd val0 val1', $in->getFullScript()); + $this->assertEquals("'./bin/app' cmd val0 val1", $in->toString()); } } From 32f386f8fc78d8903a588ed4422d47a12b640398 Mon Sep 17 00:00:00 2001 From: Inhere Date: Wed, 27 Oct 2021 22:07:57 +0800 Subject: [PATCH 185/258] fix: dep package toolkit/fsutil not added --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 2b8351c2..b93f4002 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ "php": ">7.3.0", "symfony/polyfill-php80": "~1.0", "toolkit/cli-utils": "~1.0", + "toolkit/fsutil": "~1.0", "toolkit/pflag": "~1.0", "toolkit/stdlib": "~1.0", "toolkit/sys-utils": "~1.0" From 907e556cb740ca5a0f70bf5bc65b04f2cbfc9003 Mon Sep 17 00:00:00 2001 From: Inhere Date: Wed, 27 Oct 2021 22:19:27 +0800 Subject: [PATCH 186/258] fix: Undefined property: Inhere\Console\IO\Output:: --- src/Concern/FormatOutputAwareTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Concern/FormatOutputAwareTrait.php b/src/Concern/FormatOutputAwareTrait.php index f9371616..8d41bff9 100644 --- a/src/Concern/FormatOutputAwareTrait.php +++ b/src/Concern/FormatOutputAwareTrait.php @@ -32,7 +32,7 @@ public function write($messages, $nl = true, $quit = false, array $opts = []): i { return Console::write($messages, $nl, $quit, array_merge([ 'flush' => true, - 'stream' => $this->outputStream, + 'stream' => $this->getOutputStream(), ], $opts)); } From 8c5eb38b8a35d28cf88d6b75e9419d8f3af62c37 Mon Sep 17 00:00:00 2001 From: Inhere Date: Mon, 1 Nov 2021 10:09:40 +0800 Subject: [PATCH 187/258] chore: update some for cli json render --- src/Component/Formatter/JSONPretty.php | 22 +++++++++++++++++++--- src/Concern/CommandHelpTrait.php | 2 +- src/Concern/FormatOutputAwareTrait.php | 21 ++++++++++++++++++--- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/Component/Formatter/JSONPretty.php b/src/Component/Formatter/JSONPretty.php index 35538800..a6396f0a 100644 --- a/src/Component/Formatter/JSONPretty.php +++ b/src/Component/Formatter/JSONPretty.php @@ -23,6 +23,7 @@ use function str_contains; use function str_ends_with; use function trim; +use function vdump; /** * class JSONPretty @@ -36,6 +37,21 @@ class JSONPretty extends AbstractObj 'boolVal' => 'red', ]; + public const THEME_ONE = [ + 'keyName' => 'blue', + 'strVal' => 'cyan', + 'intVal' => 'red', + 'boolVal' => 'green', + ]; + + // json.cn + public const THEME_TWO = [ + 'keyName' => 'mga1', + 'strVal' => 'info', + 'intVal' => 'hiBlue', + 'boolVal' => 'red', + ]; + /** * @var array{keyName: string, strVal: string, intVal: string, boolVal: string} */ @@ -91,8 +107,8 @@ public function renderData(array $data): string $val = rtrim($val, ','); } - // bool val - if ($val === 'true' || $val === 'false') { + // NULL or BOOL val + if ($val === 'null' || $val === 'true' || $val === 'false') { $val = ColorTag::wrap($val, $this->theme['boolVal']); } elseif (is_numeric($val)) { // number $val = ColorTag::wrap($val, $this->theme['intVal']); @@ -100,7 +116,7 @@ public function renderData(array $data): string $val = ColorTag::wrap($val, $this->theme['strVal']); } - $buf->writeln($key . ': ' . $val . ($hasEndComma ? ',' : '')); + $buf->writeln($key . ': ' . $val . ($hasEndComma ? ',' : '')); } return $buf->getAndClear(); diff --git a/src/Concern/CommandHelpTrait.php b/src/Concern/CommandHelpTrait.php index 97bec6d7..cff0d563 100644 --- a/src/Concern/CommandHelpTrait.php +++ b/src/Concern/CommandHelpTrait.php @@ -88,7 +88,7 @@ protected function setCommentsVar(string $name, $value): void public function parseCommentsVars(string $str): string { // not use vars - if (false === strpos($str, self::HELP_VAR_LEFT)) { + if (!str_contains($str, self::HELP_VAR_LEFT)) { return $str; } diff --git a/src/Concern/FormatOutputAwareTrait.php b/src/Concern/FormatOutputAwareTrait.php index 8d41bff9..7ef7df23 100644 --- a/src/Concern/FormatOutputAwareTrait.php +++ b/src/Concern/FormatOutputAwareTrait.php @@ -13,6 +13,7 @@ use Toolkit\Stdlib\Helper\JsonHelper; use Toolkit\Stdlib\Php; use function array_merge; +use function count; use function json_encode; /** @@ -36,6 +37,20 @@ public function write($messages, $nl = true, $quit = false, array $opts = []): i ], $opts)); } + // public function print(...$args): void + // { + // if (count($args) > 1) { + // echo ; + // } + // + // + // } + // + // public function echo(...$args): void + // { + // Console::printf($format, ...$args); + // } + /** * @param string $format * @param mixed ...$args @@ -56,7 +71,7 @@ public function printf(string $format, ...$args): void /** * @param string|mixed $text - * @param bool $quit + * @param bool|int $quit * @param array $opts * * @return int @@ -130,7 +145,7 @@ public function prettyJSON($data, string $title = 'JSON:'): void */ public function dump(...$vars): void { - Console::write(Php::dumpVars(...$vars)); + Console::writeRaw(Php::dumpVars(...$vars)); } /** @@ -138,6 +153,6 @@ public function dump(...$vars): void */ public function prints(...$vars): void { - Console::write(Php::printVars(...$vars)); + Console::writeRaw(Php::printVars(...$vars)); } } From 257dd87432caa920634b56b0541818b72928ef66 Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 5 Nov 2021 19:15:24 +0800 Subject: [PATCH 188/258] update: stdlib class name changed --- src/Handler/AbstractHandler.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index e056ea58..c81b5b38 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -30,7 +30,7 @@ use Toolkit\PFlag\FlagsParser; use Toolkit\PFlag\SFlags; use Toolkit\Stdlib\Helper\PhpHelper; -use Toolkit\Stdlib\Obj\ConfigObject; +use Toolkit\Stdlib\Obj\DataObject; use function cli_set_process_title; use function error_get_last; use function function_exists; @@ -91,7 +91,7 @@ abstract class AbstractHandler implements CommandHandlerInterface protected $processTitle = ''; /** - * @var ConfigObject + * @var DataObject */ protected $params; @@ -461,9 +461,9 @@ public function isAloneCmd(): bool } /** - * @return ConfigObject + * @return DataObject */ - public function getParams(): ConfigObject + public function getParams(): DataObject { if (!$this->params) { $this->initParams([]); @@ -475,11 +475,11 @@ public function getParams(): ConfigObject /** * @param array $params * - * @return ConfigObject + * @return DataObject */ - public function initParams(array $params): ConfigObject + public function initParams(array $params): DataObject { - $this->params = ConfigObject::new($params); + $this->params = DataObject::new($params); return $this->params; } From a254803242880d77134ce1ea0da1a41e2ce5fc43 Mon Sep 17 00:00:00 2001 From: Inhere Date: Sun, 7 Nov 2021 03:44:24 +0800 Subject: [PATCH 189/258] prof: add some useful methods --- src/Concern/CommandHelpTrait.php | 17 ++++++++++++++++- src/Concern/ControllerHelpTrait.php | 2 +- src/Concern/InputOutputAwareTrait.php | 8 ++++++++ src/Controller.php | 1 - 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/Concern/CommandHelpTrait.php b/src/Concern/CommandHelpTrait.php index cff0d563..f3ec185c 100644 --- a/src/Concern/CommandHelpTrait.php +++ b/src/Concern/CommandHelpTrait.php @@ -32,6 +32,13 @@ trait CommandHelpTrait */ private $commentsVars; + /** + * on display command help + * + * @var bool + */ + protected $renderGlobalOption = false; + /** * @return array */ @@ -151,7 +158,7 @@ public function showHelpByFlagsParser(FlagsParser $fs, array $aliases = [], stri $this->beforeRenderCommandHelp($help); // attached to console app - if ($app = $this->getApp()) { + if ($this->renderGlobalOption && ($app = $this->getApp())) { $help['Global Options:'] = FormatUtil::alignOptions($app->getFlags()->getOptsHelpLines()); } @@ -170,4 +177,12 @@ public function showHelpByFlagsParser(FlagsParser $fs, array $aliases = [], stri protected function beforeRenderCommandHelp(array &$help): void { } + + /** + * @param bool $renderGlobalOption + */ + public function setRenderGlobalOption(bool $renderGlobalOption): void + { + $this->renderGlobalOption = $renderGlobalOption; + } } diff --git a/src/Concern/ControllerHelpTrait.php b/src/Concern/ControllerHelpTrait.php index a0b454c0..0cc38949 100644 --- a/src/Concern/ControllerHelpTrait.php +++ b/src/Concern/ControllerHelpTrait.php @@ -121,7 +121,7 @@ public function showCommandList(): void } // is a annotation tag - if (strpos($desc, '@') === 0) { + if (str_starts_with($desc, '@')) { $desc = $defaultDes; } diff --git a/src/Concern/InputOutputAwareTrait.php b/src/Concern/InputOutputAwareTrait.php index 19ff9816..5e9722b7 100644 --- a/src/Concern/InputOutputAwareTrait.php +++ b/src/Concern/InputOutputAwareTrait.php @@ -46,6 +46,14 @@ public function getScript(): string return $this->input->getScriptFile(); } + /** + * @return string + */ + public function getWorkdir(): string + { + return $this->input->getWorkDir(); + } + /** * @return string */ diff --git a/src/Controller.php b/src/Controller.php index 41e88e27..a3e1287c 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -40,7 +40,6 @@ use function substr; use function trim; use function ucfirst; -use function vdump; /** * Class Controller From 9cc675a77652490a11ba4127dd6ac7af5274f5f0 Mon Sep 17 00:00:00 2001 From: Inhere Date: Tue, 9 Nov 2021 13:46:31 +0800 Subject: [PATCH 190/258] up: AbstractHandler add new prop:desc instead the prop:description --- src/Concern/ApplicationHelpTrait.php | 4 ++-- src/Concern/ControllerHelpTrait.php | 2 +- src/Controller.php | 2 +- src/Handler/AbstractHandler.php | 32 +++++++++++++++++----------- test/CommandTest.php | 2 +- 5 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index 54cd17f9..5d28d47c 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -218,7 +218,7 @@ public function showCommandList(): void $options = $info['options']; $controller = $info['handler']; /** @var AbstractHandler $controller */ - $desc = $controller::getDescription() ?: $placeholder; + $desc = $controller::getDesc() ?: $placeholder; $aliases = $options['aliases']; $extra = $aliases ? ColorTag::wrap(' (alias: ' . implode(',', $aliases) . ')', 'info') : ''; @@ -237,7 +237,7 @@ public function showCommandList(): void /** @var AbstractHandler $command */ if (is_subclass_of($command, CommandInterface::class)) { - $desc = $command::getDescription() ?: $placeholder; + $desc = $command::getDesc() ?: $placeholder; } elseif ($msg = $options['desc'] ?? '') { $desc = $msg; } elseif (is_string($command)) { diff --git a/src/Concern/ControllerHelpTrait.php b/src/Concern/ControllerHelpTrait.php index 0cc38949..f1d33b66 100644 --- a/src/Concern/ControllerHelpTrait.php +++ b/src/Concern/ControllerHelpTrait.php @@ -99,7 +99,7 @@ public function showCommandList(): void $ref = new ReflectionClass($this); $sName = lcfirst(self::getName() ?: $ref->getShortName()); - if (!($classDes = self::getDescription())) { + if (!($classDes = self::getDesc())) { $classDes = PhpDoc::description($ref->getDocComment()) ?: 'No description for the command group'; } diff --git a/src/Controller.php b/src/Controller.php index a3e1287c..7e006d56 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -556,7 +556,7 @@ protected function getAllCommandMethods(ReflectionClass $ref = null, bool $onlyN * @param string $name * * @return string - * @description please use resolveAlias() + * @deprecated please use resolveAlias() */ public function getRealCommandName(string $name): string { diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index c81b5b38..f8b90f4b 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -66,6 +66,14 @@ abstract class AbstractHandler implements CommandHandlerInterface * * @var string */ + protected static $desc = ''; + + /** + * command/controller description message + * + * @var string + * @deprecated please use {@see $desc} + */ protected static $description = ''; /** @@ -614,33 +622,33 @@ final public static function getName(): string */ public static function getDesc(): string { - return static::$description; + return static::$desc ?: static::$description; } /** - * @return string + * @param string $desc */ - public static function getDescription(): string + public static function setDesc(string $desc): void { - return static::$description; + if ($desc) { + static::$desc = $desc; + } } /** - * @param string $desc + * @return string */ - public static function setDesc(string $desc): void + public static function getDescription(): string { - self::setDescription($desc); + return self::getDesc(); } /** - * @param string $description + * @param string $desc */ - public static function setDescription(string $description): void + public static function setDescription(string $desc): void { - if ($description) { - static::$description = $description; - } + self::setDesc($desc); } /** diff --git a/test/CommandTest.php b/test/CommandTest.php index 3aa963f6..21888f8a 100644 --- a/test/CommandTest.php +++ b/test/CommandTest.php @@ -24,6 +24,6 @@ public function testBasic(): void $c = new TestCommand(new Input(), new Output()); $this->assertSame('test1', $c::getName()); - $this->assertStringContainsString('desc', $c::getDescription()); + $this->assertStringContainsString('desc', $c::getDesc()); } } From 321d252d2b1d66d79f9fb3c078de7b98eef31ad4 Mon Sep 17 00:00:00 2001 From: Inhere Date: Mon, 15 Nov 2021 21:37:13 +0800 Subject: [PATCH 191/258] up: update some output format methods --- src/Component/Formatter/Title.php | 7 ++++--- src/Concern/FormatOutputAwareTrait.php | 23 ++++++++++++++++++----- src/IO/Input/StreamInput.php | 10 ++++++++-- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/Component/Formatter/Title.php b/src/Component/Formatter/Title.php index d44c2612..dc6b86f5 100644 --- a/src/Component/Formatter/Title.php +++ b/src/Component/Formatter/Title.php @@ -43,7 +43,7 @@ public static function show(string $title, array $opts = []): void // list($sW, $sH) = Helper::getScreenSize(); $width = (int)$opts['width']; $char = trim($opts['char']); - $indent = (int)$opts['indent'] >= 0 ? $opts['indent'] : 2; + $indent = (int)$opts['indent'] >= 0 ? $opts['indent'] : 0; $indentStr = ''; if ($indent > 0) { @@ -60,18 +60,19 @@ public static function show(string $title, array $opts = []): void } // title position + $titleIndent = ''; if ($tLength >= $width) { $titleIndent = Str::pad(self::CHAR_SPACE, $indent, self::CHAR_SPACE); } elseif ($opts['titlePos'] === self::POS_RIGHT) { $titleIndent = Str::pad(self::CHAR_SPACE, ceil($width - $tLength) + $indent, self::CHAR_SPACE); } elseif ($opts['titlePos'] === self::POS_MIDDLE) { $titleIndent = Str::pad(self::CHAR_SPACE, ceil(($width - $tLength) / 2) + $indent, self::CHAR_SPACE); - } else { + } elseif ($indent > 0) { $titleIndent = Str::pad(self::CHAR_SPACE, $indent, self::CHAR_SPACE); } $titleText = ColorTag::wrap($title, $opts['titleStyle']); - $titleLine = "$titleIndent{$titleText}\n"; + $titleLine = "$titleIndent$titleText\n"; $border = $indentStr . Str::pad($char, $width, $char); diff --git a/src/Concern/FormatOutputAwareTrait.php b/src/Concern/FormatOutputAwareTrait.php index 7ef7df23..179a848d 100644 --- a/src/Concern/FormatOutputAwareTrait.php +++ b/src/Concern/FormatOutputAwareTrait.php @@ -14,7 +14,9 @@ use Toolkit\Stdlib\Php; use function array_merge; use function count; +use function implode; use function json_encode; +use const PHP_EOL; /** * Class FormatOutputAwareTrait @@ -45,11 +47,22 @@ public function write($messages, $nl = true, $quit = false, array $opts = []): i // // // } - // - // public function echo(...$args): void - // { - // Console::printf($format, ...$args); - // } + + /** + * @param ...$args + */ + public function echo(...$args): void + { + echo count($args) > 1 ? implode(' ', $args) : $args; + } + + /** + * @param ...$args + */ + public function echoln(...$args): void + { + echo (count($args) > 1 ? implode(' ', $args) : $args), PHP_EOL; + } /** * @param string $format diff --git a/src/IO/Input/StreamInput.php b/src/IO/Input/StreamInput.php index 5e0e24c5..a624667b 100644 --- a/src/IO/Input/StreamInput.php +++ b/src/IO/Input/StreamInput.php @@ -13,8 +13,12 @@ use InvalidArgumentException; use Toolkit\FsUtil\File; use Toolkit\Stdlib\OS; +use function fclose; +use function fopen; use function fwrite; +use function stream_get_contents; use function stream_get_meta_data; +use function stream_set_blocking; use function strpos; use const STDIN; @@ -40,11 +44,13 @@ public function __construct($stream = STDIN) } /** + * @param bool $blocking + * * @return string */ - public function readAll(): string + public function readAll(bool $blocking = true): string { - return File::streamReadAll($this->stream); + return File::streamReadAll($this->stream, $blocking); } /** From 973fd28e6107128cb93cc1027b45de80ec308e1a Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 25 Nov 2021 21:39:42 +0800 Subject: [PATCH 192/258] fix: echoln and json cli pretty error --- src/Component/Formatter/JSONPretty.php | 2 +- src/Concern/FormatOutputAwareTrait.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Component/Formatter/JSONPretty.php b/src/Component/Formatter/JSONPretty.php index a6396f0a..19236480 100644 --- a/src/Component/Formatter/JSONPretty.php +++ b/src/Component/Formatter/JSONPretty.php @@ -93,7 +93,7 @@ public function renderData(array $data): string continue; } - [$key, $val] = explode(': ', $line); + [$key, $val] = explode(': ', $line, 2); // format key name. if ($keyTag = $this->theme['keyName']) { diff --git a/src/Concern/FormatOutputAwareTrait.php b/src/Concern/FormatOutputAwareTrait.php index 179a848d..97867097 100644 --- a/src/Concern/FormatOutputAwareTrait.php +++ b/src/Concern/FormatOutputAwareTrait.php @@ -53,7 +53,7 @@ public function write($messages, $nl = true, $quit = false, array $opts = []): i */ public function echo(...$args): void { - echo count($args) > 1 ? implode(' ', $args) : $args; + echo count($args) > 1 ? implode(' ', $args) : $args[0]; } /** @@ -61,7 +61,7 @@ public function echo(...$args): void */ public function echoln(...$args): void { - echo (count($args) > 1 ? implode(' ', $args) : $args), PHP_EOL; + echo (count($args) > 1 ? implode(' ', $args) : $args[0]), PHP_EOL; } /** From 4517972afab71eaddf0c46423ffb4c3d13e20ace Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 26 Nov 2021 14:24:46 +0800 Subject: [PATCH 193/258] style: update some for help render --- src/Util/FormatUtil.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index 805cc66f..de0f97f1 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -33,6 +33,7 @@ use function strpos; use function trim; use function ucfirst; +use function vdump; use function wordwrap; use const STR_PAD_RIGHT; @@ -150,7 +151,6 @@ public static function alignOptions(array $options): array // e.g '-h, --help' $hasShort = (bool)strpos(implode('', array_keys($options)), ','); - if (!$hasShort) { return $options; } @@ -161,8 +161,8 @@ public static function alignOptions(array $options): array continue; } - // padding length equals to '-h, ' - if (!str_contains($name, ',')) { + // start with '--', padding length equals to '-h, ' + if (isset($name[1]) && $name[1] === '-') { $name = ' ' . $name; } else { $name = str_replace([',-'], [', -'], $name); From 1a386f1c3cd726e18634d8483b3e86ee230e28e5 Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 26 Nov 2021 15:13:01 +0800 Subject: [PATCH 194/258] breaking: begin v4.1.x, require php 8.0+ --- CHANGELOG.md | 5 +++++ README.en.md | 5 +++-- README.md | 5 +++-- composer.json | 11 +++++------ 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df2165ab..6dbfbd33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,12 @@ # CHANGELOG +## v4.1.x + +> require php 8.0+ + ## v4.0.x +> require php 7.3+ > begin at: 2020.08.21, branch `master` ## v3.1.21 diff --git a/README.en.md b/README.en.md index 437c632b..e510eb5d 100644 --- a/README.en.md +++ b/README.en.md @@ -1,15 +1,16 @@ # PHP Console [![License](https://img.shields.io/packagist/l/inhere/console.svg?style=flat-square)](LICENSE) -[![Php Version](https://img.shields.io/badge/php-%3E=7.3.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) +[![Php Version](https://img.shields.io/badge/php-%3E=8.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) [![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console) [![Github Actions Status](https://github.com/inhere/php-console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/php-console/actions) +[![zh-CN readme](https://img.shields.io/badge/Readme-中文-brightgreen.svg?maxAge=2592000)](README.md) Full-featured php command line application library. Provide console parameter parsing, command run, color style output, user information interaction, and special format information display. -> **[中文README](./README.zh-CN.md)** +> NOTICE: Current v4.1+, require **PHP 8.0+** ## Command line preview diff --git a/README.md b/README.md index 6dcb0c79..cdada962 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@ # PHP Console [![License](https://img.shields.io/packagist/l/inhere/console.svg?style=flat-square)](LICENSE) -[![Php Version](https://img.shields.io/badge/php-%3E=7.3.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) +[![Php Version](https://img.shields.io/badge/php-%3E=8.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) [![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console) [![Github Actions Status](https://github.com/inhere/php-console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/php-console/actions) +[![English](https://img.shields.io/badge/Readme-English-brightgreen.svg?maxAge=2592000)](README.en.md) 简洁、功能全面的php命令行应用库。提供控制台参数解析, 命令运行,颜色风格输出, 用户信息交互, 特殊格式信息显示。 -> **[EN README](./README.md)** +> NOTICE: Current v4.1+, require **PHP 8.0+** ## 命令行预览 diff --git a/composer.json b/composer.json index b93f4002..365f2be1 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "inhere/console", "type": "library", - "description": "a php console library, provide console argument parse, console controller/command run, color style, user interactive, information show.", + "description": "php console library, provide console argument parse, console controller/command run, color style, user interactive, information show.", "keywords": [ "cli", "phar", @@ -18,20 +18,19 @@ { "name": "inhere", "email": "in.798@qq.com", - "homepage": "http://www.yzone.net/" + "homepage": "https://github.com/inhere" } ], "require": { - "php": ">7.3.0", - "symfony/polyfill-php80": "~1.0", + "php": ">=8.0", "toolkit/cli-utils": "~1.0", "toolkit/fsutil": "~1.0", "toolkit/pflag": "~1.0", - "toolkit/stdlib": "~1.0", + "toolkit/stdlib": "^1.0", "toolkit/sys-utils": "~1.0" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.1" + "phpunit/phpunit": "^9.1" }, "autoload": { "psr-4": { From edf350afa9d7bddaa27deb8203a5e8a18151dcfb Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 26 Nov 2021 15:15:12 +0800 Subject: [PATCH 195/258] chore: update action script, only test on php 8.0+ --- .github/workflows/php.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 25b7e51d..356313b9 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -9,14 +9,14 @@ on: jobs: test: - name: Test on php ${{ matrix.php}} and ${{ matrix.os }} - runs-on: ${{ matrix.os }} + name: Test on php ${{ matrix.php}} + runs-on: ubuntu-latest timeout-minutes: 10 strategy: fail-fast: true matrix: - php: [7.3, 7.4, 8.0] # - os: [ubuntu-latest, macOS-latest] # windows-latest, + php: [8.0] # 7.3, 7.4, +# os: [ubuntu-latest] # windows-latest, # include: # - os: 'ubuntu-latest' # php: '7.2' From 001ca21e50c4584e12ebc49c48bf7b32c73c274a Mon Sep 17 00:00:00 2001 From: Inhere Date: Mon, 29 Nov 2021 21:52:52 +0800 Subject: [PATCH 196/258] chore: add tests on php 8.1, update readme --- .github/workflows/php.yml | 2 +- README.en.md | 4 +++- README.md | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 356313b9..8bd00c57 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: true matrix: - php: [8.0] # 7.3, 7.4, + php: [8.0, 8.1] # 7.3, 7.4, # os: [ubuntu-latest] # windows-latest, # include: # - os: 'ubuntu-latest' diff --git a/README.en.md b/README.en.md index e510eb5d..3e25fc20 100644 --- a/README.en.md +++ b/README.en.md @@ -10,7 +10,7 @@ Full-featured php command line application library. Provide console parameter parsing, command run, color style output, user information interaction, and special format information display. -> NOTICE: Current v4.1+, require **PHP 8.0+** +> NOTICE: Current version **v4.1+**, require **PHP 8.0+** ## Command line preview @@ -48,6 +48,8 @@ Provide console parameter parsing, command run, color style output, user informa ## Installation +- Requirement PHP 8.0+ + ```bash composer require inhere/console ``` diff --git a/README.md b/README.md index cdada962..2b7aa417 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ 简洁、功能全面的php命令行应用库。提供控制台参数解析, 命令运行,颜色风格输出, 用户信息交互, 特殊格式信息显示。 -> NOTICE: Current v4.1+, require **PHP 8.0+** +> NOTICE: Current version **v4.1+**, require **PHP 8.0+** ## 命令行预览 @@ -61,7 +61,7 @@ ## 快速安装 -- Requirement php 7.3+ +- Requirement PHP 8.0+ ```bash composer require inhere/console From cc52396f6fe68189f9ef04b39f147129254edf81 Mon Sep 17 00:00:00 2001 From: Inhere Date: Mon, 29 Nov 2021 23:00:42 +0800 Subject: [PATCH 197/258] style: run code inspect for src/Component --- src/Component/CompletionDumper.php | 4 +- src/Component/ConsoleAppIShell.php | 4 +- src/Component/ConsoleRenderer.php | 4 +- src/Component/ErrorHandler.php | 6 +- src/Component/Formatter/Body.php | 8 +- src/Component/Formatter/JSONPretty.php | 1 - src/Component/Formatter/Panel.php | 37 +++---- src/Component/Formatter/Section.php | 4 +- src/Component/Formatter/SingleList.php | 2 +- src/Component/Formatter/Table.php | 22 ++-- src/Component/Formatter/Tree.php | 8 +- src/Component/Interact/AbstractQuestion.php | 12 +- src/Component/Interact/AbstractSelect.php | 4 +- src/Component/Interact/Checkbox.php | 12 +- src/Component/Interact/Choose.php | 7 +- src/Component/Interact/IShell.php | 23 ++-- src/Component/Interact/LimitedAsk.php | 4 +- src/Component/Interact/MultiSelect.php | 6 +- src/Component/Interact/SingleSelect.php | 6 +- src/Component/MessageFormatter.php | 2 +- src/Component/NotifyMessage.php | 4 +- src/Component/PharCompiler.php | 116 +++++++++----------- src/Component/Progress/SimpleBar.php | 4 +- src/Component/ReadlineCompleter.php | 4 +- src/Component/Symbol/ArtFont.php | 14 +-- src/Component/Symbol/Char.php | 2 +- src/Component/Symbol/Emoji.php | 2 +- 27 files changed, 154 insertions(+), 168 deletions(-) diff --git a/src/Component/CompletionDumper.php b/src/Component/CompletionDumper.php index a5f395fa..6ca5f578 100644 --- a/src/Component/CompletionDumper.php +++ b/src/Component/CompletionDumper.php @@ -21,7 +21,7 @@ class CompletionDumper extends Obj\AbstractObj { /** - * @var Application + * @var Application|null */ - public $app; + public ?Application $app; } diff --git a/src/Component/ConsoleAppIShell.php b/src/Component/ConsoleAppIShell.php index 92e07fca..65d012c4 100644 --- a/src/Component/ConsoleAppIShell.php +++ b/src/Component/ConsoleAppIShell.php @@ -20,9 +20,9 @@ class ConsoleAppIShell extends IShell { /** - * @var Application + * @var Application|null */ - public $app; + public ?Application $app; // TODO start an shell for run app. } diff --git a/src/Component/ConsoleRenderer.php b/src/Component/ConsoleRenderer.php index 56140f92..1a67a76c 100644 --- a/src/Component/ConsoleRenderer.php +++ b/src/Component/ConsoleRenderer.php @@ -19,9 +19,9 @@ class ConsoleRenderer { /** - * @var FormatterInterface + * @var FormatterInterface|null */ - private $formatter; + private ?FormatterInterface $formatter; /** * @return string diff --git a/src/Component/ErrorHandler.php b/src/Component/ErrorHandler.php index 4914eaee..d10fd1e8 100644 --- a/src/Component/ErrorHandler.php +++ b/src/Component/ErrorHandler.php @@ -30,17 +30,17 @@ class ErrorHandler implements ErrorHandlerInterface /** * @var bool */ - protected $debug = false; + protected bool $debug = false; /** * @var string */ - protected $rootPath = ''; + protected string $rootPath = ''; /** * @var bool */ - protected $hideRootPath = false; + protected bool $hideRootPath = false; /** * @param array $config diff --git a/src/Component/Formatter/Body.php b/src/Component/Formatter/Body.php index bbc2fb11..668b107d 100644 --- a/src/Component/Formatter/Body.php +++ b/src/Component/Formatter/Body.php @@ -16,11 +16,11 @@ */ class Body { - public $nodes = []; + public array $nodes = []; - public $style = ''; + public string $style = ''; - public $keyStyle = 'info'; + public string $keyStyle = 'info'; - public $valStyle = ''; + public string $valStyle = ''; } diff --git a/src/Component/Formatter/JSONPretty.php b/src/Component/Formatter/JSONPretty.php index 19236480..2b22021c 100644 --- a/src/Component/Formatter/JSONPretty.php +++ b/src/Component/Formatter/JSONPretty.php @@ -23,7 +23,6 @@ use function str_contains; use function str_ends_with; use function trim; -use function vdump; /** * class JSONPretty diff --git a/src/Component/Formatter/Panel.php b/src/Component/Formatter/Panel.php index 28dc1abb..8b7af719 100644 --- a/src/Component/Formatter/Panel.php +++ b/src/Component/Formatter/Panel.php @@ -35,48 +35,48 @@ class Panel extends MessageFormatter { /** @var string */ - public $title = ''; + public string $title = ''; /** @var string */ - public $titleBorder = '-'; + public string $titleBorder = '-'; /** @var string */ - public $titleStyle = 'bold'; + public string $titleStyle = 'bold'; /** @var string */ - public $titleAlign = self::ALIGN_LEFT; + public string $titleAlign = self::ALIGN_LEFT; /** @var string|array */ - public $data; + public string|array $data; /** @var string */ - public $bodyAlign = self::ALIGN_LEFT; + public string $bodyAlign = self::ALIGN_LEFT; /** @var string */ - public $footerBorder = '-'; + public string $footerBorder = '-'; /** @var string */ - public $footer = ''; + public string $footer = ''; /** @var bool */ - public $ucFirst = true; + public bool $ucFirst = true; /** @var int */ - public $width = 0; + public int $width = 0; /** @var bool */ - public $showBorder = true; + public bool $showBorder = true; /** @var string */ - public $borderYChar = '-'; + public string $borderYChar = '-'; /** @var string */ - public $borderXChar = '|'; + public string $borderXChar = '|'; /** * @var string Template for the panel. don't contains border */ - public $template = <<No data to display!'); @@ -164,7 +164,7 @@ public static function show($data, string $title = 'Information Panel', array $o $titleLength = mb_strlen($title, 'UTF-8'); $panelWidth = $panelWidth > $titleLength ? $panelWidth : $titleLength; $indentSpace = Str::pad(' ', ceil($panelWidth / 2) - ceil($titleLength / 2) + 2 * 2, ' '); - Console::write(" {$indentSpace}{$title}"); + Console::write(" $indentSpace$title"); } // output panel top border @@ -226,7 +226,6 @@ public function format(): string if (is_array($value)) { $temp = ''; - /** @var array $value */ foreach ($value as $key => $val) { if (is_bool($val)) { $val = $val ? 'True' : 'False'; @@ -262,7 +261,7 @@ public function format(): string $titleLength = mb_strlen($title, 'UTF-8'); $panelWidth = $panelWidth > $titleLength ? $panelWidth : $titleLength; $indentSpace = Str::pad(' ', ceil($panelWidth / 2) - ceil($titleLength / 2) + 2 * 2, ' '); - $buffer->write(" {$indentSpace}{$title}\n"); + $buffer->write(" $indentSpace$title\n"); } // output panel top border @@ -297,7 +296,7 @@ public function format(): string * * @return $this */ - public function showBorder($border): self + public function showBorder(bool $border): self { $this->showBorder = (bool)$border; return $this; diff --git a/src/Component/Formatter/Section.php b/src/Component/Formatter/Section.php index d15eea78..d25c57dc 100644 --- a/src/Component/Formatter/Section.php +++ b/src/Component/Formatter/Section.php @@ -29,10 +29,10 @@ class Section extends MessageFormatter { /** * @param string $title The title text - * @param string|array $body The section body message + * @param array|string $body The section body message * @param array $opts */ - public static function show(string $title, $body, array $opts = []): void + public static function show(string $title, array|string $body, array $opts = []): void { $opts = array_merge([ 'width' => 80, diff --git a/src/Component/Formatter/SingleList.php b/src/Component/Formatter/SingleList.php index 4e22720e..8e729323 100644 --- a/src/Component/Formatter/SingleList.php +++ b/src/Component/Formatter/SingleList.php @@ -42,7 +42,7 @@ class SingleList extends MessageFormatter * * @return int|string */ - public static function show($data, string $title = 'Information', array $opts = []) + public static function show(mixed $data, string $title = 'Information', array $opts = []): int|string { $string = ''; $opts = array_merge([ diff --git a/src/Component/Formatter/Table.php b/src/Component/Formatter/Table.php index 1ce8d1b0..0cb01f61 100644 --- a/src/Component/Formatter/Table.php +++ b/src/Component/Formatter/Table.php @@ -30,25 +30,25 @@ class Table extends MessageFormatter { /** @var array */ - public $data = []; + public array $data = []; /** @var array */ - public $columns = []; + public array $columns = []; /** @var string|array */ - public $body; + public string|array $body; /** @var string */ - public $title = ''; + public string $title = ''; /** @var string */ - public $titleBorder = '-'; + public string $titleBorder = '-'; /** @var string */ - public $titleStyle = '-'; + public string $titleStyle = '-'; /** @var string */ - public $titleAlign = self::ALIGN_LEFT; + public string $titleAlign = self::ALIGN_LEFT; /** * Tabular data display @@ -169,7 +169,7 @@ public static function show(array $data, string $title = 'Data Table', array $op $title = Str::ucwords(trim($title)); $titleLength = Str::utf8Len($title, 'UTF-8'); $indentSpace = Str::pad(' ', ceil($tableWidth / 2) - ceil($titleLength / 2) + ($columnCount * 2), ' '); - $buf->write(" {$indentSpace}<$tStyle>{$title}\n"); + $buf->write(" $indentSpace<$tStyle>$title\n"); } $border = $leftIndent . Str::pad($rowBorderChar, $tableWidth + ($columnCount * 3) + 2, $rowBorderChar); @@ -183,7 +183,7 @@ public static function show(array $data, string $title = 'Data Table', array $op // output table head if ($hasHead) { - $headStr = "{$leftIndent}{$colBorderChar} "; + $headStr = "$leftIndent$colBorderChar "; foreach ($head as $index => $name) { $colMaxWidth = $info['columnMaxWidth'][$index]; @@ -193,7 +193,7 @@ public static function show(array $data, string $title = 'Data Table', array $op $name = Str::padByWidth($name, $colMaxWidth, ' '); $name = ColorTag::wrap($name, $opts['headStyle']); // join string - $headStr .= " {$name} {$colBorderChar}"; + $headStr .= " $name $colBorderChar"; } $buf->write($headStr . "\n"); @@ -227,7 +227,7 @@ public static function show(array $data, string $title = 'Data Table', array $op // use Str::padByWidth support zh-CN words $value = Str::padByWidth($value, $colMaxWidth, ' '); $value = ColorTag::wrap($value, $opts['bodyStyle']); - $rowStr .= " {$value} {$colBorderChar}"; + $rowStr .= " $value $colBorderChar"; $colIndex++; } diff --git a/src/Component/Formatter/Tree.php b/src/Component/Formatter/Tree.php index e30433c6..0f586d5b 100644 --- a/src/Component/Formatter/Tree.php +++ b/src/Component/Formatter/Tree.php @@ -11,8 +11,8 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; -use Inhere\Console\Util\FormatUtil; use Toolkit\Cli\Cli; +use Toolkit\Stdlib\Std; use function array_merge; use function is_array; use function is_scalar; @@ -26,10 +26,10 @@ class Tree extends MessageFormatter { /** @var int */ - private $counter = 0; + private int $counter = 0; /** @var bool */ - private $started = false; + private bool $started = false; /** * Render data like tree @@ -64,7 +64,7 @@ public static function show(array $data, array $opts = []): void $counter++; $leftString = $opts['leftPadding'] . str_pad($opts['prefix'], $opts['_level'] + 1, $opts['char']); - Console::write($leftString . ' ' . FormatUtil::typeToString($value)); + Console::write($leftString . ' ' . Std::toString($value)); } elseif (is_array($value)) { $newOpts = $opts; $newOpts['_is_main'] = false; diff --git a/src/Component/Interact/AbstractQuestion.php b/src/Component/Interact/AbstractQuestion.php index 0891befb..f893d2c3 100644 --- a/src/Component/Interact/AbstractQuestion.php +++ b/src/Component/Interact/AbstractQuestion.php @@ -10,7 +10,7 @@ namespace Inhere\Console\Component\Interact; use Inhere\Console\Component\InteractiveHandle; -use Toolkit\Stdlib\Str\StrObject; +use Toolkit\Stdlib\Str\StrValue; /** * Class AbstractQuestion @@ -20,22 +20,22 @@ abstract class AbstractQuestion extends InteractiveHandle { /** - * @var StrObject + * @var StrValue|null */ - protected $answer; + protected ?StrValue $answer = null; /** * @param string $str */ protected function createAnswer(string $str): void { - $this->answer = StrObject::new($str)->trim(); + $this->answer = StrValue::new($str)->trim(); } /** - * @return StrObject + * @return StrValue */ - public function getAnswer(): StrObject + public function getAnswer(): StrValue { return $this->answer; } diff --git a/src/Component/Interact/AbstractSelect.php b/src/Component/Interact/AbstractSelect.php index dad4cbb0..914127b6 100644 --- a/src/Component/Interact/AbstractSelect.php +++ b/src/Component/Interact/AbstractSelect.php @@ -19,12 +19,12 @@ abstract class AbstractSelect extends InteractiveHandle /** * @var array */ - protected $data = []; + protected array $data = []; /** * @var bool */ - protected $allowExit = true; + protected bool $allowExit = true; /** * @return bool diff --git a/src/Component/Interact/Checkbox.php b/src/Component/Interact/Checkbox.php index 8727806f..74230872 100644 --- a/src/Component/Interact/Checkbox.php +++ b/src/Component/Interact/Checkbox.php @@ -29,13 +29,13 @@ class Checkbox extends MultiSelect * List multiple options and allow multiple selections * * @param string $description - * @param string|array $options - * @param null|mixed $default + * @param array|string $options + * @param mixed|null $default * @param bool $allowExit * * @return array */ - public static function select(string $description, $options, $default = null, bool $allowExit = true): array + public static function select(string $description, array|string $options, mixed $default = null, bool $allowExit = true): array { if (!$description = trim($description)) { Show::error('Please provide a description text!', 1); @@ -59,13 +59,13 @@ public static function select(string $description, $options, $default = null, bo } Console::write($text); - $defText = $default !== null ? "[default:{$default}]" : ''; - $filter = function ($val) use ($options) { + $defText = $default !== null ? "[default:$default]" : ''; + $filter = static function ($val) use ($options) { return $val !== 'q' && isset($options[$val]); }; beginChoice: - $r = Console::readln("Your choice{$defText} : "); + $r = Console::readln("Your choice$defText : "); $r = $r !== '' ? str_replace(' ', '', trim($r, $sep)) : ''; // empty diff --git a/src/Component/Interact/Choose.php b/src/Component/Interact/Choose.php index 85b6f97e..f4d34aec 100644 --- a/src/Component/Interact/Choose.php +++ b/src/Component/Interact/Choose.php @@ -30,21 +30,22 @@ class Choose extends SingleSelect * Choose one of several options * * @param string $description - * @param string|array $options Option data + * @param array|string $options Option data * e.g * [ * // option => value * '1' => 'chengdu', * '2' => 'beijing' * ] - * @param null|int|string $default Default option + * @param int|string|null $default Default option * @param bool $allowExit * @param array $opts + * * @psalm-param array{returnVal: bool, retFilter: callable} $opts * * @return string */ - public static function one(string $description, $options, $default = null, bool $allowExit = true, array $opts = []): string + public static function one(string $description, array|string $options, int|string $default = null, bool $allowExit = true, array $opts = []): string { if (!$description = trim($description)) { Show::error('Please provide a description text!', 1); diff --git a/src/Component/Interact/IShell.php b/src/Component/Interact/IShell.php index 665b4243..3ebb764b 100644 --- a/src/Component/Interact/IShell.php +++ b/src/Component/Interact/IShell.php @@ -24,7 +24,6 @@ use function explode; use function stripos; use function strlen; -use function strpos; use function substr; use function trim; @@ -44,17 +43,17 @@ class IShell extends InteractiveHandle /** * @var bool */ - private $debug = false; + private bool $debug = false; /** * @var string */ - private $prefix = 'CMD'; + private string $prefix = 'CMD'; /** * @var string */ - private $title = 'Welcome interactive shell environment'; + private string $title = 'Welcome interactive shell environment'; /** * the main logic handler @@ -76,7 +75,7 @@ class IShell extends InteractiveHandle /** * @var array */ - private $exitKeys = [ + private array $exitKeys = [ 'q' => 1, 'quit' => 1, 'exit' => 1, @@ -85,22 +84,22 @@ class IShell extends InteractiveHandle /** * @var bool */ - private $hasPcntl = false; + private bool $hasPcntl = false; /** * @var bool */ - private $hasReadline = false; + private bool $hasReadline = false; /** * @var string */ - private $historyFile = ''; + private string $historyFile = ''; /** * @var int */ - private $historySize = 1024; + private int $historySize = 1024; /** * Auto complete handler @@ -268,7 +267,7 @@ protected function dispatch(string $line, callable $handler): int // display help $hasMoreKey = false; - if ($line === self::HELP || ($hasMoreKey = strpos($line, 'help ') === 0)) { + if ($line === self::HELP || ($hasMoreKey = str_starts_with($line, 'help '))) { // help CMD $moreKeys = $hasMoreKey ? explode(' ', $line) : []; $this->handleHelp($moreKeys); @@ -301,7 +300,7 @@ protected function dispatch(string $line, callable $handler): int * * @return array|bool */ - public function autoCompleteHandle(string $input, int $index) + public function autoCompleteHandle(string $input, int $index): array|bool { // custom auto-completer if ($fn = $this->autoCompleter) { @@ -323,7 +322,7 @@ public function autoCompleteHandle(string $input, int $index) $founded = []; // $line=$input completion for top command name prefix. - if (strpos($line, ' ') === false) { + if (!str_contains($line, ' ')) { foreach ($commands as $name) { if (stripos($name, $input)) { $founded[] = $name; diff --git a/src/Component/Interact/LimitedAsk.php b/src/Component/Interact/LimitedAsk.php index 55b529b2..0d50e9eb 100644 --- a/src/Component/Interact/LimitedAsk.php +++ b/src/Component/Interact/LimitedAsk.php @@ -77,9 +77,9 @@ public static function ask( $hasDefault = '' !== $default; if ($hasDefault) { - $message = "{$question}(default: $default) "; + $message = "$question(default: $default) "; } else { - $message = "{$question}"; + $message = "$question"; Console::write($message); } diff --git a/src/Component/Interact/MultiSelect.php b/src/Component/Interact/MultiSelect.php index 1cc74335..6c53933f 100644 --- a/src/Component/Interact/MultiSelect.php +++ b/src/Component/Interact/MultiSelect.php @@ -19,19 +19,19 @@ class MultiSelect extends AbstractSelect * * @var string */ - protected $defaults; + protected string $defaults; /** * The selected key * * @var string[] */ - protected $selected; + protected array $selected; /** * @var string[] */ - protected $selectedVals; + protected array $selectedVals; /** * @return string[] diff --git a/src/Component/Interact/SingleSelect.php b/src/Component/Interact/SingleSelect.php index 69603f55..801a3cfc 100644 --- a/src/Component/Interact/SingleSelect.php +++ b/src/Component/Interact/SingleSelect.php @@ -19,19 +19,19 @@ class SingleSelect extends AbstractSelect * * @var string */ - protected $default; + protected string $default; /** * The selected key * * @var string */ - protected $selected; + protected string $selected; /** * @var string */ - protected $selectedVal; + protected string $selectedVal; /** * @return string diff --git a/src/Component/MessageFormatter.php b/src/Component/MessageFormatter.php index f799f6f7..d41ae18c 100644 --- a/src/Component/MessageFormatter.php +++ b/src/Component/MessageFormatter.php @@ -31,7 +31,7 @@ abstract class MessageFormatter implements FormatterInterface /** * @var array */ - protected $config = []; + protected array $config = []; /** * @var callable diff --git a/src/Component/NotifyMessage.php b/src/Component/NotifyMessage.php index b55a179a..a80784f9 100644 --- a/src/Component/NotifyMessage.php +++ b/src/Component/NotifyMessage.php @@ -20,7 +20,7 @@ class NotifyMessage { /** @var int Speed value. allow 1 - 10 */ - protected $speed = 2; + protected int $speed = 2; /** * @return int @@ -33,7 +33,7 @@ public function getSpeed(): int /** * @param int $speed */ - public function setSpeed($speed): void + public function setSpeed(int $speed): void { $this->speed = (int)$speed; } diff --git a/src/Component/PharCompiler.php b/src/Component/PharCompiler.php index ddd695f7..489c534a 100644 --- a/src/Component/PharCompiler.php +++ b/src/Component/PharCompiler.php @@ -79,7 +79,7 @@ class PharCompiler public const ADD_WEB_INDEX = 'add.index.web'; /** @var array */ - private static $supportedSignatureTypes = [ + private static array $supportedSignatureTypes = [ Phar::SHA512 => 1, Phar::SHA256 => 1, Phar::SHA1 => 1 @@ -89,42 +89,42 @@ class PharCompiler private $key; /** @var int */ - private $signatureType; + private int $signatureType; /** * @var int compress Mode @see \Phar::NONE, \Phar::GZ, \Phar::BZ2 */ - private $compressMode = 0; + private int $compressMode = 0; /** * @var string The want to packaged project path */ - private $basePath; + private string $basePath; /** * @var string */ - private $cliIndex = ''; + private string $cliIndex = ''; /** * @var string */ - private $webIndex = ''; + private string $webIndex = ''; /** * @var string|bool Set the shebang. eg '#!/usr/bin/env php' */ - private $shebang; + private string|bool $shebang; /** * @var array Want to added files. (It is relative the $basePath) */ - private $files = []; + private array $files = []; /** * @var array Want to include files suffix name list */ - private $suffixes = ['.php']; + private array $suffixes = ['.php']; /** * Want to exclude directory/file name list @@ -137,53 +137,53 @@ class PharCompiler * * @var array */ - private $excludes = []; + private array $excludes = []; /** * The directory paths, will collect files in there. * * @var array */ - private $directories = []; + private array $directories = []; /** * Some events. if you want to get some info on packing. * * @var Closure[] */ - private $events = []; + private array $events = []; /** * @var Closure Maybe you not want strip all files. */ - private $stripFilter; + private Closure $stripFilter; /** * @var bool Whether strip comments */ - private $stripComments = true; + private bool $stripComments = true; /** * @var bool Whether auto collect version info by git log. */ - private $collectVersionInfo = true; + private bool $collectVersionInfo = true; // -------------------- project version(by git) -------------------- /** * @var string The latest commit id */ - private $lastCommit = ''; + private string $lastCommit = ''; /** * @var string The latest tag name */ - private $lastVersion = ''; + private string $lastVersion = ''; /** * @var DateTime */ - private $versionDate; + private DateTime $versionDate; /** * 记录上面三个信息的文件, 相对于basePath @@ -196,42 +196,42 @@ class PharCompiler * * @var string */ - private $versionFile = ''; + private string $versionFile = ''; /** * @var string */ - private $versionFileContent = ''; + private string $versionFileContent = ''; // -------------------- internal properties -------------------- /** @var int */ - private $counter = 0; + private int $counter = 0; /** * @var string Phar file path. e.g '/some/path/app.phar' */ - private $pharFile; + private string $pharFile; /** * @var string Phar file name. eg 'app.phar' */ - private $pharName; + private string $pharName; /** * @var Closure File filter */ - private $fileFilter; + private Closure $fileFilter; /** * @var array|Iterator The modifies files list. if not empty, will skip find dirs. */ - private $modifies; + private array|Iterator $modifies; /** * @var SplQueue */ - private $fileQueue; + private SplQueue $fileQueue; /** * @throws RuntimeException @@ -252,14 +252,14 @@ private static function checkEnv(): void /** * @param string $pharFile * @param string $extractTo - * @param string|array|null $files Only fetch the listed files + * @param array|string|null $files Only fetch the listed files * @param bool $overwrite * * @return bool * @throws BadMethodCallException * @throws RuntimeException */ - public static function unpack(string $pharFile, string $extractTo, $files = null, bool $overwrite = false): bool + public static function unpack(string $pharFile, string $extractTo, array|string $files = null, bool $overwrite = false): bool { self::checkEnv(); @@ -283,16 +283,16 @@ public function __construct(string $basePath) $this->fileQueue = new SplQueue(); if (!is_dir($this->basePath)) { - throw new RuntimeException("The inputted path is not exists. PATH: {$this->basePath}"); + throw new RuntimeException("The inputted path is not exists. PATH: $this->basePath"); } } /** - * @param string|array $files + * @param array|string $files * * @return $this */ - public function addFile($files): self + public function addFile(array|string $files): self { $this->files = array_merge($this->files, (array)$files); return $this; @@ -310,33 +310,33 @@ public function setFiles(array $files): self } /** - * @param string|array $suffixes + * @param array|string $suffixes * * @return $this */ - public function addSuffix($suffixes): self + public function addSuffix(array|string $suffixes): self { $this->suffixes = array_merge($this->suffixes, (array)$suffixes); return $this; } /** - * @param string|array $patterns + * @param array|string $patterns * * @return $this */ - public function addExclude($patterns): self + public function addExclude(array|string $patterns): self { $this->excludes = array_merge($this->excludes, (array)$patterns); return $this; } /** - * @param string|array $patterns + * @param array|string $patterns * * @return $this */ - public function addExcludeDir($patterns): self + public function addExcludeDir(array|string $patterns): self { $list = []; foreach ((array)$patterns as $pattern) { @@ -348,11 +348,11 @@ public function addExcludeDir($patterns): self } /** - * @param string|array $patterns + * @param array|string $patterns * * @return $this */ - public function addExcludeFile($patterns): self + public function addExcludeFile(array|string $patterns): self { $list = []; foreach ((array)$patterns as $pattern) { @@ -375,22 +375,22 @@ public function setExcludes(array $excludes): self } /** - * @param bool|string|int $value + * @param bool|int|string $value * * @return PharCompiler */ - public function stripComments($value): self + public function stripComments(bool|int|string $value): self { $this->stripComments = (bool)$value; return $this; } /** - * @param bool|string|int $value + * @param bool|int|string $value * * @return PharCompiler */ - public function collectVersion($value): self + public function collectVersion(bool|int|string $value): self { $this->collectVersionInfo = (bool)$value; return $this; @@ -412,29 +412,29 @@ public function setStripFilter(Closure $stripFilter): self * * @return PharCompiler */ - public function setShebang($shebang): self + public function setShebang(bool|string $shebang): self { $this->shebang = $shebang; return $this; } /** - * @param string|array $dirs + * @param array|string $dirs * * @return PharCompiler */ - public function in($dirs): self + public function in(array|string $dirs): self { $this->directories = array_merge($this->directories, (array)$dirs); return $this; } /** - * @param array|Iterator $modifies + * @param Iterator|array $modifies * * @return PharCompiler */ - public function setModifies($modifies): self + public function setModifies(Iterator|array $modifies): self { $this->modifies = $modifies; return $this; @@ -619,11 +619,11 @@ public function findChangedByGit(): ?Generator } // modified files - if (strpos($file, 'M ') === 0) { + if (str_starts_with($file, 'M ')) { yield substr($file, 2); // new files - } elseif (strpos($file, '?? ') === 0) { + } elseif (str_starts_with($file, '?? ')) { yield substr($file, 3); } } @@ -768,7 +768,7 @@ private function getIteratorFilter(): Closure $path = $file->getPathname(); // Skip hidden files and directories. - if (strpos($name, '.') === 0) { + if (str_starts_with($name, '.')) { return false; } @@ -802,18 +802,6 @@ private function getIteratorFilter(): Closure return $this->fileFilter; } - /** - * Removes whitespace from a PHP source string while preserving line numbers. - * - * @param string $source A PHP string - * - * @return string The PHP string with the whitespace removed - */ - private function stripWhitespace(string $source): string - { - return File::stripPhpCode($source); - } - /** * Auto collect project information by git log * @@ -880,7 +868,7 @@ private function getRelativeFilePath(SplFileInfo $file): string * * @return mixed|null */ - private function fire(string $event, ...$args) + private function fire(string $event, ...$args): mixed { if (isset($this->events[$event])) { $cb = $this->events[$event]; diff --git a/src/Component/Progress/SimpleBar.php b/src/Component/Progress/SimpleBar.php index ebf9a017..edea7d69 100644 --- a/src/Component/Progress/SimpleBar.php +++ b/src/Component/Progress/SimpleBar.php @@ -71,7 +71,6 @@ public static function gen(int $total, array $opts = []): Generator } $step = yield; - if ((int)$step <= 0) { $step = 1; } @@ -92,7 +91,8 @@ public static function gen(int $total, array $opts = []): Generator */ // printf("\r[%'--100s] %d%% %s", // printf("\x0D\x1B[2K[%'{$waitChar}-100s] %d%% %s", printf( - "{$tplPrefix}[%'{$waitChar}-100s] %' 3d%% %s", + "%s[%'$waitChar-100s] %' 3d%% %s", + $tplPrefix, str_repeat($opts['doneChar'], $percent) . ($finished ? '' : $opts['signChar']), $percent, $msg diff --git a/src/Component/ReadlineCompleter.php b/src/Component/ReadlineCompleter.php index 26b1a7bc..26d1bc3f 100644 --- a/src/Component/ReadlineCompleter.php +++ b/src/Component/ReadlineCompleter.php @@ -22,12 +22,12 @@ class ReadlineCompleter extends AbstractObj /** * @var string */ - private $historyFile = ''; + private string $historyFile = ''; /** * @var int */ - private $historySize = 1024; + private int $historySize = 1024; /** * @var callable diff --git a/src/Component/Symbol/ArtFont.php b/src/Component/Symbol/ArtFont.php index 52954ac9..8c59a13a 100644 --- a/src/Component/Symbol/ArtFont.php +++ b/src/Component/Symbol/ArtFont.php @@ -28,15 +28,15 @@ class ArtFont public const INTERNAL_GROUP = '_internal'; /** @var self */ - private static $instance; + private static ArtFont $instance; /** @var array */ - private static $internalFonts = ['404', '500', 'error', 'no', 'ok', 'success', 'yes']; + private static array $internalFonts = ['404', '500', 'error', 'no', 'ok', 'success', 'yes']; /** * @var array */ - private $groups = []; + private array $groups = []; /** * @var array @@ -44,7 +44,7 @@ class ArtFont * group => [ name => path ] * ] */ - private $fonts = []; + private array $fonts = []; /** * @var array @@ -52,7 +52,7 @@ class ArtFont * name => content * ] */ - private $fontContents = []; + private array $fontContents = []; /** * @return self @@ -261,9 +261,9 @@ public function addFontContent(string $name, string $content): self * * @return bool */ - public static function isInternalFont($name): bool + public static function isInternalFont(string $name): bool { - return in_array((string)$name, self::$internalFonts, true); + return in_array($name, self::$internalFonts, true); } /** diff --git a/src/Component/Symbol/Char.php b/src/Component/Symbol/Char.php index ea75aeeb..31c7af5f 100644 --- a/src/Component/Symbol/Char.php +++ b/src/Component/Symbol/Char.php @@ -98,7 +98,7 @@ final class Char * ... * ] */ - private static $constants; + private static array $constants; /** * @return array diff --git a/src/Component/Symbol/Emoji.php b/src/Component/Symbol/Emoji.php index 00e40414..53a7c411 100644 --- a/src/Component/Symbol/Emoji.php +++ b/src/Component/Symbol/Emoji.php @@ -131,7 +131,7 @@ final class Emoji * ... * ] */ - private static $constants; + private static array $constants; /** * @return array From 8195fbd60ae5d567abc41ba4fecef88a59da927c Mon Sep 17 00:00:00 2001 From: Inhere Date: Mon, 29 Nov 2021 23:01:22 +0800 Subject: [PATCH 198/258] style: run code inspect for src/IO src/Concern --- src/Concern/ApplicationHelpTrait.php | 2 +- src/Concern/AttachApplicationTrait.php | 7 +++---- src/Concern/CommandHelpTrait.php | 13 ++++++------ src/Concern/ControllerHelpTrait.php | 1 - src/Concern/FormatOutputAwareTrait.php | 26 ++++++++++++----------- src/Concern/InputOutputAwareTrait.php | 27 +++++++++++------------- src/Concern/RuntimeProfileTrait.php | 10 ++++----- src/Concern/SimpleEventAwareTrait.php | 6 +++--- src/Concern/StyledOutputAwareTrait.php | 19 ++++++++--------- src/Concern/SubCommandsWareTrait.php | 9 ++++---- src/Concern/UserInteractAwareTrait.php | 22 +++++++++---------- src/IO/AbstractInput.php | 29 ++++++++++++-------------- src/IO/Input.php | 2 +- src/IO/Input/ArrayInput.php | 1 - src/IO/Input/StreamInput.php | 5 ----- src/IO/Output.php | 10 ++++----- src/IO/Output/BufferedOutput.php | 7 ++++--- src/IO/Output/StreamOutput.php | 22 +++++++++++++++++-- src/IO/TempStream.php | 2 +- 19 files changed, 113 insertions(+), 107 deletions(-) diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index 5d28d47c..aff1dd10 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -50,7 +50,7 @@ trait ApplicationHelpTrait /** * @var string|array */ - protected $moreHelpInfo = ''; + protected string|array $moreHelpInfo = ''; /*************************************************************************** * Show information for the application diff --git a/src/Concern/AttachApplicationTrait.php b/src/Concern/AttachApplicationTrait.php index 5cabaf3a..58d14608 100644 --- a/src/Concern/AttachApplicationTrait.php +++ b/src/Concern/AttachApplicationTrait.php @@ -12,7 +12,6 @@ use Inhere\Console\AbstractApplication; use Inhere\Console\Application; use Inhere\Console\Console; -use Inhere\Console\GlobalOption; use Toolkit\Stdlib\OS; /** @@ -28,16 +27,16 @@ trait AttachApplicationTrait } /** - * @var Application + * @var Application|null */ - protected $app; + protected ?Application $app = null; /** * Mark the command/controller is attached in console application. * * @var bool */ - private $attached = false; + private bool $attached = false; /** * @return AbstractApplication diff --git a/src/Concern/CommandHelpTrait.php b/src/Concern/CommandHelpTrait.php index f3ec185c..75f55b5d 100644 --- a/src/Concern/CommandHelpTrait.php +++ b/src/Concern/CommandHelpTrait.php @@ -15,7 +15,6 @@ use Toolkit\PFlag\FlagsParser; use function implode; use function sprintf; -use function strpos; use function strtr; use function ucfirst; @@ -30,14 +29,14 @@ trait CommandHelpTrait * @var array [name => value] * @see AbstractHandler::annotationVars() */ - private $commentsVars; + private array $commentsVars = []; /** * on display command help * * @var bool */ - protected $renderGlobalOption = false; + protected bool $renderGlobalOption = false; /** * @return array @@ -57,9 +56,9 @@ public function setCommentsVars(array $commentsVars): void /** * @param string $name - * @param string|array $value + * @param array|string $value */ - protected function addCommentsVar(string $name, $value): void + protected function addCommentsVar(string $name, array|string $value): void { if (!isset($this->commentsVars[$name])) { $this->setCommentsVar($name, $value); @@ -78,9 +77,9 @@ protected function addCommentsVars(array $map): void /** * @param string $name - * @param string|array $value + * @param array|string $value */ - protected function setCommentsVar(string $name, $value): void + protected function setCommentsVar(string $name, array|string $value): void { $this->commentsVars[$name] = is_array($value) ? implode(',', $value) : (string)$value; } diff --git a/src/Concern/ControllerHelpTrait.php b/src/Concern/ControllerHelpTrait.php index f1d33b66..a0eb7f0d 100644 --- a/src/Concern/ControllerHelpTrait.php +++ b/src/Concern/ControllerHelpTrait.php @@ -21,7 +21,6 @@ use function ksort; use function lcfirst; use function sprintf; -use function strpos; use function ucfirst; use const PHP_EOL; diff --git a/src/Concern/FormatOutputAwareTrait.php b/src/Concern/FormatOutputAwareTrait.php index 97867097..e2858ed2 100644 --- a/src/Concern/FormatOutputAwareTrait.php +++ b/src/Concern/FormatOutputAwareTrait.php @@ -66,11 +66,13 @@ public function echoln(...$args): void /** * @param string $format - * @param mixed ...$args + * @param mixed ...$args + * + * @return int */ - public function writef(string $format, ...$args): void + public function writef(string $format, ...$args): int { - Console::printf($format, ...$args); + return Console::printf($format, ...$args); } /** @@ -84,12 +86,12 @@ public function printf(string $format, ...$args): void /** * @param string|mixed $text - * @param bool|int $quit - * @param array $opts + * @param bool|int $quit + * @param array $opts * * @return int */ - public function writeln($text, $quit = false, array $opts = []): int + public function writeln($text, bool $quit = false, array $opts = []): int { return Console::writeln($text, $quit, $opts); } @@ -101,7 +103,7 @@ public function writeln($text, $quit = false, array $opts = []): int * * @return int */ - public function println($text, bool $quit = false, array $opts = []): int + public function println(mixed $text, bool $quit = false, array $opts = []): int { return Console::writeln($text, $quit, $opts); } @@ -109,12 +111,12 @@ public function println($text, bool $quit = false, array $opts = []): int /** * @param string|mixed $text * @param bool $nl - * @param bool|int $quit + * @param bool|int $quit * @param array $opts * * @return int */ - public function writeRaw($text, bool $nl = true, $quit = false, array $opts = []): int + public function writeRaw(mixed $text, bool $nl = true, bool $quit = false, array $opts = []): int { return Console::writeRaw($text, $nl, $quit, $opts); } @@ -127,10 +129,10 @@ public function writeRaw($text, bool $nl = true, $quit = false, array $opts = [] * @return int|string */ public function json( - $data, + mixed $data, bool $echo = true, int $flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES - ) { + ): int|string { $string = json_encode($data, $flags); if ($echo) { @@ -144,7 +146,7 @@ public function json( * @param mixed $data * @param string $title */ - public function prettyJSON($data, string $title = 'JSON:'): void + public function prettyJSON(mixed $data, string $title = 'JSON:'): void { if ($title) { Console::colored($title, 'ylw0'); diff --git a/src/Concern/InputOutputAwareTrait.php b/src/Concern/InputOutputAwareTrait.php index 5e9722b7..9501b007 100644 --- a/src/Concern/InputOutputAwareTrait.php +++ b/src/Concern/InputOutputAwareTrait.php @@ -24,19 +24,19 @@ trait InputOutputAwareTrait { /** - * @var SFlags|FlagsParser + * @var FlagsParser|null */ - protected $flags; + protected ?FlagsParser $flags; /** - * @var Input|InputInterface + * @var InputInterface|null */ - protected $input; + protected ?InputInterface $input; /** - * @var Output|OutputInterface + * @var OutputInterface|null */ - protected $output; + protected ?OutputInterface $output; /** * @return string @@ -74,26 +74,23 @@ public function readln(string $question = '', bool $nl = false): string } /** - * @param mixed $message - * @param bool $nl - * @param bool|int $quit + * @param mixed $message * * @return int */ - public function write($message, bool $nl = true, $quit = false): int + public function write(mixed $message): int { - return $this->output->write($message, $nl, $quit); + return $this->output->write($message); } /** - * @param mixed $message - * @param bool|int $quit + * @param mixed $message * * @return int */ - public function writeln($message, $quit = false): int + public function writeln(mixed $message): int { - return $this->output->write($message, true, $quit); + return $this->output->writeln($message); } /** diff --git a/src/Concern/RuntimeProfileTrait.php b/src/Concern/RuntimeProfileTrait.php index cd4831a4..7cb36092 100644 --- a/src/Concern/RuntimeProfileTrait.php +++ b/src/Concern/RuntimeProfileTrait.php @@ -29,7 +29,7 @@ trait RuntimeProfileTrait * * @var array */ - private static $profiles = []; + private static array $profiles = []; /** * @var array @@ -40,7 +40,7 @@ trait RuntimeProfileTrait * ... * ] */ - private static $keyQueue = []; + private static array $keyQueue = []; /** * mark data analysis start @@ -51,7 +51,7 @@ trait RuntimeProfileTrait * * @throws InvalidArgumentException */ - public static function profile($name, array $context = [], $category = 'application'): void + public static function profile($name, array $context = [], string $category = 'application'): void { $data = [ '_profile_stats' => [ @@ -65,7 +65,7 @@ public static function profile($name, array $context = [], $category = 'applicat $profileKey = $category . '|' . $name; - if (in_array($profileKey, self::$keyQueue, 1)) { + if (in_array($profileKey, self::$keyQueue, true)) { throw new InvalidArgumentException("Your added profile name [$name] have been exists!"); } @@ -81,7 +81,7 @@ public static function profile($name, array $context = [], $category = 'applicat * * @return bool|array */ - public static function profileEnd(string $msg = null, array $context = []) + public static function profileEnd(string $msg = null, array $context = []): bool|array { if (!$latestKey = array_pop(self::$keyQueue)) { return false; diff --git a/src/Concern/SimpleEventAwareTrait.php b/src/Concern/SimpleEventAwareTrait.php index e97d32a3..3bec85aa 100644 --- a/src/Concern/SimpleEventAwareTrait.php +++ b/src/Concern/SimpleEventAwareTrait.php @@ -25,7 +25,7 @@ trait SimpleEventAwareTrait * * @var array */ - protected static $supportedEvents = []; + protected static array $supportedEvents = []; /** * registered Events @@ -35,7 +35,7 @@ trait SimpleEventAwareTrait * 'event' => bool, // is once event * ] */ - private static $events = []; + private static array $events = []; /** * events and handlers @@ -45,7 +45,7 @@ trait SimpleEventAwareTrait * 'event' => callable, // event handler * ] */ - private static $eventHandlers = []; + private static array $eventHandlers = []; /** * register a event handler diff --git a/src/Concern/StyledOutputAwareTrait.php b/src/Concern/StyledOutputAwareTrait.php index 9bc85e91..d1e6fa7e 100644 --- a/src/Concern/StyledOutputAwareTrait.php +++ b/src/Concern/StyledOutputAwareTrait.php @@ -24,7 +24,6 @@ use Toolkit\Cli\Style; use function method_exists; use function sprintf; -use function strpos; use function substr; /** @@ -85,11 +84,11 @@ public function colored(string $text, string $tag = 'info'): int * @param array|mixed $messages * @param string $type * @param string $style - * @param bool $quit + * @param bool $quit * * @return int */ - public function block($messages, string $type = 'MESSAGE', string $style = Style::NORMAL, $quit = false): int + public function block(mixed $messages, string $type = 'MESSAGE', string $style = Style::NORMAL, bool $quit = false): int { return Show::block($messages, $type, $style, $quit); } @@ -98,11 +97,11 @@ public function block($messages, string $type = 'MESSAGE', string $style = Style * @param array|mixed $messages * @param string $type * @param string $style - * @param bool $quit + * @param bool $quit * * @return int */ - public function liteBlock($messages, string $type = 'MESSAGE', string $style = Style::NORMAL, $quit = false): int + public function liteBlock(mixed $messages, string $type = 'MESSAGE', string $style = Style::NORMAL, bool $quit = false): int { return Show::liteBlock($messages, $type, $style, $quit); } @@ -118,10 +117,10 @@ public function title(string $title, array $opts = []): void /** * @param string $title - * @param string|array $body The section body message + * @param array|string $body The section body message * @param array $opts */ - public function section(string $title, $body, array $opts = []): void + public function section(string $title, array|string $body, array $opts = []): void { Section::show($title, $body, $opts); } @@ -131,7 +130,7 @@ public function section(string $title, $body, array $opts = []): void * @param string $title * @param array $opts */ - public function aList($data, string $title = 'Information', array $opts = []): void + public function aList(mixed $data, string $title = 'Information', array $opts = []): void { SingleList::show($data, $title, $opts); } @@ -201,7 +200,7 @@ public function progressTxt(int $total, string $msg, string $doneMsg = ''): Gene * @return Generator * @see Show::progressBar() */ - public function progressBar($total, array $opts = []): Generator + public function progressBar(int $total, array $opts = []): Generator { return Show::progressBar($total, $opts); } @@ -222,7 +221,7 @@ public function __call(string $method, array $args = []) $quit = $args[1] ?? false; $style = $map[$method]; - if (0 === strpos($method, 'lite')) { + if (str_starts_with($method, 'lite')) { $type = substr($method, 4); return Show::liteBlock($msg, $type === 'Primary' ? 'IMPORTANT' : $type, $style, $quit); } diff --git a/src/Concern/SubCommandsWareTrait.php b/src/Concern/SubCommandsWareTrait.php index f2c4885f..f0cec8e4 100644 --- a/src/Concern/SubCommandsWareTrait.php +++ b/src/Concern/SubCommandsWareTrait.php @@ -15,6 +15,7 @@ use Inhere\Console\Contract\CommandInterface; use Inhere\Console\Util\Helper; use InvalidArgumentException; +use Toolkit\Stdlib\Obj\Traits\NameAliasTrait; use function array_keys; use function array_merge; use function class_exists; @@ -33,12 +34,12 @@ */ trait SubCommandsWareTrait { - use \Toolkit\Stdlib\Obj\Traits\NameAliasTrait; + use NameAliasTrait; /** * @var array */ - private $blocked = ['help', 'version']; + private array $blocked = ['help', 'version']; /** * The sub-commands of the command @@ -51,7 +52,7 @@ trait SubCommandsWareTrait * ] * ] */ - private $commands = []; + private array $commands = []; /** * Can attach sub-commands @@ -89,7 +90,7 @@ protected function dispatchCommand(string $name): void * * @throws InvalidArgumentException */ - public function addSub(string $name, $handler = null, array $options = []): void + public function addSub(string $name, string|Closure|CommandInterface $handler = null, array $options = []): void { if (!$handler && class_exists($name)) { /** @var Command $name name is an command class */ diff --git a/src/Concern/UserInteractAwareTrait.php b/src/Concern/UserInteractAwareTrait.php index 132cd543..3f8782cd 100644 --- a/src/Concern/UserInteractAwareTrait.php +++ b/src/Concern/UserInteractAwareTrait.php @@ -34,27 +34,27 @@ trait UserInteractAwareTrait { /** * @param string $description - * @param string|array $options Option data - * @param string|int|null $default Default option - * @param bool $allowExit + * @param array|string $options Option data + * @param int|string|null $default Default option + * @param bool $allowExit * * @return string * @see Interact::choice() */ - public function select(string $description, $options, $default = null, $allowExit = true): string + public function select(string $description, array|string $options, int|string $default = null, bool $allowExit = true): string { return $this->choice($description, $options, $default, $allowExit); } /** * @param string $description - * @param string|array $options Option data - * @param string|int|null $default Default option - * @param bool $allowExit + * @param array|string $options Option data + * @param int|string|null $default Default option + * @param bool $allowExit * * @return string */ - public function choice(string $description, $options, $default = null, $allowExit = true): string + public function choice(string $description, array|string $options, int|string $default = null, bool $allowExit = true): string { return Interact::choice($description, $options, $default, $allowExit); } @@ -109,12 +109,12 @@ public function question(string $question, string $default = '', Closure $valida * @param string $question * @param string $default * @param Closure|null $validator - * @param int $times + * @param int $times * * @return string|null * @see Interact::limitedAsk() */ - public function limitedAsk(string $question, string $default = '', Closure $validator = null, $times = 3): ?string + public function limitedAsk(string $question, string $default = '', Closure $validator = null, int $times = 3): ?string { return Interact::limitedAsk($question, $default, $validator, $times); } @@ -126,7 +126,7 @@ public function limitedAsk(string $question, string $default = '', Closure $vali * @return int * @throws LogicException */ - public function __call($method, array $args = []) + public function __call(string $method, array $args = []) { if (method_exists(Interact::class, $method)) { return Interact::$method(...$args); diff --git a/src/IO/AbstractInput.php b/src/IO/AbstractInput.php index df1f421b..39b098d3 100644 --- a/src/IO/AbstractInput.php +++ b/src/IO/AbstractInput.php @@ -12,16 +12,13 @@ use Inhere\Console\Contract\InputInterface; use Toolkit\Cli\Helper\FlagHelper; use Toolkit\PFlag\FlagsParser; -use Toolkit\PFlag\SFlags; use function array_map; use function array_shift; use function basename; use function getcwd; use function implode; -use function is_int; use function is_string; use function preg_match; -use function trim; /** * Class AbstractInput @@ -33,21 +30,21 @@ abstract class AbstractInput implements InputInterface /** * Global flags parser * - * @var FlagsParser|SFlags + * @var FlagsParser|null */ - protected $gfs; + protected ?FlagsParser $gfs = null; /** * Command flags parser * - * @var FlagsParser|SFlags + * @var FlagsParser|null */ - protected $fs; + protected ?FlagsParser $fs = null; /** * @var string */ - protected $pwd = ''; + protected string $pwd = ''; /** * The bin script file @@ -55,7 +52,7 @@ abstract class AbstractInput implements InputInterface * * @var string */ - protected $scriptFile = ''; + protected string $scriptFile = ''; /** * The bin script name @@ -63,7 +60,7 @@ abstract class AbstractInput implements InputInterface * * @var string */ - protected $scriptName = ''; + protected string $scriptName = ''; /** * the command name(Is first argument) @@ -71,22 +68,22 @@ abstract class AbstractInput implements InputInterface * * @var string */ - protected $command = ''; + protected string $command = ''; /** * the command name(Is first argument) - * e.g `subcmd` in the `./app group subcmd` + * e.g `subcommand` in the `./app group subcommand` * * @var string */ - protected $subCommand = ''; + protected string $subCommand = ''; /** * eg `./examples/app home:useArg status=2 name=john arg0 -s=test --page=23` * * @var string */ - protected $fullScript; + protected string $fullScript; /** * Raw input argv data. @@ -94,14 +91,14 @@ abstract class AbstractInput implements InputInterface * * @var array */ - protected $tokens; + protected array $tokens; /** * Same the $tokens but no $script * * @var array */ - protected $flags = []; + protected array $flags = []; /** * @return string diff --git a/src/IO/Input.php b/src/IO/Input.php index 84f930f9..55e2eed7 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -25,7 +25,7 @@ class Input extends StreamInput * * @var string */ - protected $commandId = ''; + protected string $commandId = ''; /** * Input constructor. diff --git a/src/IO/Input/ArrayInput.php b/src/IO/Input/ArrayInput.php index 8d372d3a..da0b5165 100644 --- a/src/IO/Input/ArrayInput.php +++ b/src/IO/Input/ArrayInput.php @@ -10,7 +10,6 @@ namespace Inhere\Console\IO\Input; use Inhere\Console\IO\Input; -use Toolkit\Cli\Flags; use function is_int; /** diff --git a/src/IO/Input/StreamInput.php b/src/IO/Input/StreamInput.php index a624667b..f752c9e7 100644 --- a/src/IO/Input/StreamInput.php +++ b/src/IO/Input/StreamInput.php @@ -13,13 +13,8 @@ use InvalidArgumentException; use Toolkit\FsUtil\File; use Toolkit\Stdlib\OS; -use function fclose; -use function fopen; use function fwrite; -use function stream_get_contents; use function stream_get_meta_data; -use function stream_set_blocking; -use function strpos; use const STDIN; /** diff --git a/src/IO/Output.php b/src/IO/Output.php index 9aabb7f7..7d4da49b 100644 --- a/src/IO/Output.php +++ b/src/IO/Output.php @@ -35,9 +35,9 @@ class Output extends StreamOutput * 控制台窗口(字体/背景)颜色添加处理 * window colors * - * @var Style + * @var Style|null */ - protected $style; + protected ?Style $style = null; /** * Output constructor. @@ -79,7 +79,7 @@ public function clearBuffer(): void * * @see Console::stopBuffer() */ - public function stopBuffer(bool $flush = true, bool $nl = false, $quit = false, array $opts = []): void + public function stopBuffer(bool $flush = true, bool $nl = false, bool $quit = false, array $opts = []): void { Console::stopBuffer($flush, $nl, $quit, $opts); } @@ -88,10 +88,10 @@ public function stopBuffer(bool $flush = true, bool $nl = false, $quit = false, * stop buffering and flush buffer text * * @param bool $nl - * @param bool|int $quit + * @param bool $quit * @param array $opts */ - public function flush(bool $nl = false, $quit = false, array $opts = []): void + public function flush(bool $nl = false, bool $quit = false, array $opts = []): void { Console::flushBuffer($nl, $quit, $opts); } diff --git a/src/IO/Output/BufferedOutput.php b/src/IO/Output/BufferedOutput.php index 851c3ab9..86e01e55 100644 --- a/src/IO/Output/BufferedOutput.php +++ b/src/IO/Output/BufferedOutput.php @@ -22,7 +22,7 @@ class BufferedOutput extends AbstractOutput /** * @var string */ - private $buffer = ''; + private string $buffer = ''; /** * @param bool $reset @@ -58,13 +58,14 @@ public function write(string $content): int /** * @param string $content + * @param bool $quit + * @param array $opts * * @return int */ - public function writeln(string $content): int + public function writeln($content, bool $quit = false, array $opts = []): int { $this->buffer .= $content . PHP_EOL; - return strlen($content) + 1; } diff --git a/src/IO/Output/StreamOutput.php b/src/IO/Output/StreamOutput.php index 23143714..8478f8aa 100644 --- a/src/IO/Output/StreamOutput.php +++ b/src/IO/Output/StreamOutput.php @@ -12,9 +12,10 @@ use Inhere\Console\IO\AbstractOutput; use InvalidArgumentException; use Toolkit\FsUtil\File; +use Toolkit\Stdlib\Helper\DataHelper; use Toolkit\Stdlib\OS; +use function sprintf; use function stream_get_meta_data; -use function strpos; use const PHP_EOL; use const STDOUT; @@ -51,13 +52,30 @@ public function write(string $content): int return File::streamWrite($this->stream, $content); } + /** + * @param string $format + * @param mixed ...$args + * + * @return int + */ + public function writef(string $format, ...$args): int + { + $content = sprintf($format, ...$args); + + return File::streamWrite($this->stream, $content); + } + /** * @param string $content + * @param bool $quit + * @param array $opts * * @return int */ - public function writeln(string $content): int + public function writeln($content, bool $quit = false, array $opts = []): int { + $content = DataHelper::toString($content); + return File::streamWrite($this->stream, $content . PHP_EOL); } diff --git a/src/IO/TempStream.php b/src/IO/TempStream.php index e535bdd7..4a57d2ec 100644 --- a/src/IO/TempStream.php +++ b/src/IO/TempStream.php @@ -68,7 +68,7 @@ public static function close(): void /** * @param string|int|mixed $string */ - public static function write($string): void + public static function write(mixed $string): void { fwrite(self::$tempFd, (string)$string); } From 45f0a1a632bc2caaf65b96306e468d64f50b0ed1 Mon Sep 17 00:00:00 2001 From: Inhere Date: Mon, 29 Nov 2021 23:01:54 +0800 Subject: [PATCH 199/258] style: run code inspect for src/BuiltIn Util --- src/BuiltIn/DevServerCommand.php | 7 +- src/BuiltIn/PharController.php | 8 +- src/BuiltIn/SelfUpdateCommand.php | 12 +-- src/Util/FormatUtil.php | 7 +- src/Util/Helper.php | 12 +-- src/Util/Interact.php | 32 +++--- src/Util/PhpDevServe.php | 17 +-- src/Util/ProgressBar.php | 58 +++++----- src/Util/Show.php | 174 +++++++----------------------- 9 files changed, 109 insertions(+), 218 deletions(-) diff --git a/src/BuiltIn/DevServerCommand.php b/src/BuiltIn/DevServerCommand.php index 410d5ba8..6acd2db1 100644 --- a/src/BuiltIn/DevServerCommand.php +++ b/src/BuiltIn/DevServerCommand.php @@ -15,7 +15,6 @@ use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; use Inhere\Console\Util\PhpDevServe; -use function strpos; /** * Class DevServerCommand @@ -24,9 +23,9 @@ */ class DevServerCommand extends Command { - protected static $name = 'dev:server'; + protected static string $name = 'dev:server'; - protected static $description = 'Start a php built-in http server for development'; + protected static string $description = 'Start a php built-in http server for development'; public static function aliases(): array { @@ -68,7 +67,7 @@ public function execute(Input $input, Output $output): void } $port = $this->flags->getOpt('port'); - if ($port && strpos($serveAddr, ':') === false) { + if ($port && !str_contains($serveAddr, ':')) { $serveAddr .= ':' . $port; } diff --git a/src/BuiltIn/PharController.php b/src/BuiltIn/PharController.php index acb324bd..9b1b46a4 100644 --- a/src/BuiltIn/PharController.php +++ b/src/BuiltIn/PharController.php @@ -34,19 +34,19 @@ */ class PharController extends Controller { - protected static $name = 'phar'; + protected static string $name = 'phar'; - protected static $description = 'Pack a project directory to phar or unpack phar to directory'; + protected static string $description = 'Pack a project directory to phar or unpack phar to directory'; /** * @var Closure */ - private $compilerConfiger; + private Closure $compilerConfiger; /** * @var string */ - private $defPkgName; + private string $defPkgName; protected static function commandAliases(): array { diff --git a/src/BuiltIn/SelfUpdateCommand.php b/src/BuiltIn/SelfUpdateCommand.php index 3706628d..758c4525 100644 --- a/src/BuiltIn/SelfUpdateCommand.php +++ b/src/BuiltIn/SelfUpdateCommand.php @@ -35,14 +35,14 @@ class SelfUpdateCommand extends Command public const FILE_NAME = 'console.phar'; - protected static $name = 'self-update'; + protected static string $name = 'self-update'; - protected static $description = 'Update phar package to most recent stable, pre-release or development build.'; + protected static string $description = 'Update phar package to most recent stable, pre-release or development build.'; /** * @var string */ - protected $version; + protected string $version; /** * Execute the command. @@ -118,7 +118,7 @@ protected function execute(Input $input, Output $output): void $this->updateToMostRecentNonDevRemote(); } - protected function getStableUpdater() + protected function getStableUpdater(): Updater { $updater = new Updater; $updater->setStrategy(Updater::STRATEGY_GITHUB); @@ -126,7 +126,7 @@ protected function getStableUpdater() return $this->getGithubReleasesUpdater($updater); } - protected function getPreReleaseUpdater() + protected function getPreReleaseUpdater(): Updater { $updater = new Updater; $updater->setStrategy(Updater::STRATEGY_GITHUB); @@ -135,7 +135,7 @@ protected function getPreReleaseUpdater() return $this->getGithubReleasesUpdater($updater); } - protected function getMostRecentNonDevUpdater() + protected function getMostRecentNonDevUpdater(): Updater { $updater = new Updater; $updater->setStrategy(Updater::STRATEGY_GITHUB); diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index de0f97f1..159728a1 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -19,13 +19,11 @@ use function array_merge; use function array_shift; use function explode; -use function get_class; use function implode; use function is_array; use function is_bool; use function is_int; use function is_numeric; -use function is_object; use function is_scalar; use function rtrim; use function str_repeat; @@ -33,7 +31,6 @@ use function strpos; use function trim; use function ucfirst; -use function vdump; use function wordwrap; use const STR_PAD_RIGHT; @@ -49,7 +46,7 @@ final class FormatUtil * * @return string */ - public static function typeToString($val): string + public static function typeToString(mixed $val): string { if (null === $val) { return '(Null)'; @@ -184,7 +181,7 @@ public static function alignOptions(array $options): array * @return string * @deprecated use Format::memory($secs); */ - public static function memoryUsage($memory): string + public static function memoryUsage(float|int $memory): string { return Format::memory($memory); } diff --git a/src/Util/Helper.php b/src/Util/Helper.php index 371ad4bb..4c009e00 100644 --- a/src/Util/Helper.php +++ b/src/Util/Helper.php @@ -20,6 +20,7 @@ use RuntimeException; use Swoole\Coroutine; use Toolkit\Stdlib\Arr\ArrayHelper; +use Traversable; use function class_exists; use function file_exists; use function is_dir; @@ -28,7 +29,6 @@ use function similar_text; use function sprintf; use function strlen; -use function strpos; /** * Class Helper @@ -77,7 +77,7 @@ public static function hasMode(int $haystack, int $value): bool */ public static function isAbsPath(string $path): bool { - return strpos($path, '/') === 0 || 1 === preg_match('#^[a-z]:[\/|\\\]{1}.+#i', $path); + return str_starts_with($path, '/') || 1 === preg_match('#^[a-z]:[\/|\\\]{1}.+#i', $path); } /** @@ -126,7 +126,7 @@ public static function mkdir(string $dir, int $mode = 0775): void /** * @param string $srcDir * @param callable $filter - * @param int $flags + * @param int $flags * * @return RecursiveIteratorIterator * @throws InvalidArgumentException @@ -134,7 +134,7 @@ public static function mkdir(string $dir, int $mode = 0775): void public static function directoryIterator( string $srcDir, callable $filter, - $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO + int $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO ): RecursiveIteratorIterator { if (!$srcDir || !file_exists($srcDir)) { throw new InvalidArgumentException('Please provide a exists source directory.'); @@ -158,12 +158,12 @@ public static function commandSearch(string $command, array $map): void * find similar text from an array|Iterator * * @param string $need - * @param Iterator|array $iterator + * @param Traversable|array $iterator * @param int $similarPercent * * @return array */ - public static function findSimilar(string $need, $iterator, int $similarPercent = 45): array + public static function findSimilar(string $need, Traversable|array $iterator, int $similarPercent = 45): array { if (!$need) { return []; diff --git a/src/Util/Interact.php b/src/Util/Interact.php index fc5f432c..13fc30dd 100644 --- a/src/Util/Interact.php +++ b/src/Util/Interact.php @@ -32,7 +32,7 @@ class Interact extends Show /** * read line from CLI input * - * @param mixed $message + * @param mixed|null $message * @param bool $nl * @param array $opts * [ @@ -41,7 +41,7 @@ class Interact extends Show * * @return string */ - public static function readln($message = null, bool $nl = false, array $opts = []): string + public static function readln(mixed $message = null, bool $nl = false, array $opts = []): string { return Cli::readln($message, $nl, $opts); } @@ -49,23 +49,23 @@ public static function readln($message = null, bool $nl = false, array $opts = [ /** * 读取输入信息 * - * @param mixed $message 若不为空,则先输出文本 + * @param mixed|null $message 若不为空,则先输出文本 * @param bool $nl true 会添加换行符 false 原样输出,不添加换行符 * * @return string */ - public static function readRow($message = null, bool $nl = false): string + public static function readRow(mixed $message = null, bool $nl = false): string { return Cli::readln($message, $nl); } /** - * @param null|mixed $message + * @param mixed|null $message * @param bool $nl * * @return string */ - public static function readFirst($message = null, bool $nl = false): string + public static function readFirst(mixed $message = null, bool $nl = false): string { return Cli::readFirst($message, $nl); } @@ -78,14 +78,14 @@ public static function readFirst($message = null, bool $nl = false): string * alias of the `select()` * * @param string $description 说明 - * @param string|array $options 选项数据 + * @param array|string $options 选项数据 * @param null $default 默认选项 * @param bool $allowExit 有退出选项 默认 true * @param array $opts * * @return string */ - public static function select(string $description, $options, $default = null, bool $allowExit = true, array $opts = []): string + public static function select(string $description, array|string $options, $default = null, bool $allowExit = true, array $opts = []): string { return self::choice($description, $options, $default, $allowExit, $opts); } @@ -94,7 +94,7 @@ public static function select(string $description, $options, $default = null, bo * Choose one of several options * * @param string $description - * @param string|array $options Option data + * @param array|string $options Option data * e.g * [ * // option => value @@ -107,7 +107,7 @@ public static function select(string $description, $options, $default = null, bo * * @return string */ - public static function choice(string $description, $options, $default = null, bool $allowExit = true, array $opts = []): string + public static function choice(string $description, array|string $options, $default = null, bool $allowExit = true, array $opts = []): string { return Choose::one($description, $options, $default, $allowExit, $opts); } @@ -116,13 +116,13 @@ public static function choice(string $description, $options, $default = null, bo * alias of the `multiSelect()` * * @param string $description - * @param string|array $options - * @param null|mixed $default + * @param array|string $options + * @param mixed|null $default * @param bool $allowExit * * @return array */ - public static function checkbox(string $description, $options, $default = null, bool $allowExit = true): array + public static function checkbox(string $description, array|string $options, mixed $default = null, bool $allowExit = true): array { return self::multiSelect($description, $options, $default, $allowExit); } @@ -131,13 +131,13 @@ public static function checkbox(string $description, $options, $default = null, * List multiple options and allow multiple selections * * @param string $description - * @param string|array $options - * @param null|mixed $default + * @param array|string $options + * @param mixed|null $default * @param bool $allowExit * * @return array */ - public static function multiSelect(string $description, $options, $default = null, bool $allowExit = true): array + public static function multiSelect(string $description, array|string $options, mixed $default = null, bool $allowExit = true): array { return Checkbox::select($description, $options, $default, $allowExit); } diff --git a/src/Util/PhpDevServe.php b/src/Util/PhpDevServe.php index 7bd7b444..8629fef9 100644 --- a/src/Util/PhpDevServe.php +++ b/src/Util/PhpDevServe.php @@ -42,14 +42,14 @@ class PhpDevServe * * @var string */ - protected $phpBin = self::PHP_BIN; + protected string $phpBin = self::PHP_BIN; /** * The document root dir for server * * @var string */ - protected $docRoot = ''; + protected string $docRoot = ''; /** * The entry file for server. e.g web/index.php @@ -57,14 +57,14 @@ class PhpDevServe * * @var string */ - protected $entryFile = ''; + protected string $entryFile = ''; /** * The http server address. e.g 127.0.0.1:8552 * * @var string */ - protected $serveAddr = ''; + protected string $serveAddr = ''; /** * Can custom message for print before start server. @@ -79,17 +79,17 @@ class PhpDevServe * * @var array */ - private $hceData = []; + private array $hceData = []; /** * @var string */ - private $hceEnv = ''; + private string $hceEnv = ''; /** * @var array */ - private $envVars = []; + private array $envVars = []; /** * @param string $serveAddr @@ -243,9 +243,10 @@ public function getCommand(bool $checkEnv = true): string /** * @param string $hceFile - * @param bool $mustLoad + * @param bool $mustLoad * * @return bool + * @throws \JsonException */ public function loadHceFile(string $hceFile, bool $mustLoad = false): bool { diff --git a/src/Util/ProgressBar.php b/src/Util/ProgressBar.php index b5ce4967..bca6f6b0 100644 --- a/src/Util/ProgressBar.php +++ b/src/Util/ProgressBar.php @@ -37,39 +37,39 @@ class ProgressBar /** * @var int */ - private $barWidth = 30; + private int $barWidth = 30; /** * @var string 已完成的显示字符 */ - private $completeChar = '='; + private string $completeChar = '='; /** * @var string 当前进度的显示字符 */ - private $progressChar = '>'; + private string $progressChar = '>'; /** * @var string 剩下的的显示字符 */ - private $remainingChar = '-'; + private string $remainingChar = '-'; /** * @var int */ - private $redrawFreq = 1; + private int $redrawFreq = 1; /** * @var string */ - private $format; + private string $format; /** * 已完成百分比 * * @var float */ - private $percent = 0.0; + private float $percent = 0.0; /** * maximal steps. @@ -77,7 +77,7 @@ class ProgressBar * * @var int */ - private $step = 0; + private int $step = 0; /** * maximal steps. @@ -85,7 +85,7 @@ class ProgressBar * * @var int */ - private $maxSteps; + private int $maxSteps; /** * step Width @@ -93,51 +93,51 @@ class ProgressBar * * @var int */ - private $stepWidth; + private int $stepWidth; /** * @var int */ - private $startTime = 0; + private int $startTime = 0; /** * @var int */ - private $finishTime = 0; + private int $finishTime = 0; /** * @var bool */ - private $overwrite = true; + private bool $overwrite = true; /** * @var bool */ - private $started = false; + private bool $started = false; /** * @var bool */ - private $firstRun = true; + private bool $firstRun = true; /** * @var OutputInterface */ - private $output; + private Output|OutputInterface $output; /** * messages * * @var array */ - private $messages = []; + private array $messages = []; /** * section parsers * * @var Closure[] */ - private static $parsers = [// 'precent' => function () { ... }, + private static array $parsers = [// 'precent' => function () { ... }, ]; public const DEFAULT_FORMAT = '[{@bar}] {@percent:3s}%({@current}/{@max}) {@elapsed:6s}/{@estimated:-6s} {@memory:6s}'; @@ -309,7 +309,7 @@ public function render(string $text): void * @return mixed * @throws RuntimeException */ - protected function buildLine() + protected function buildLine(): mixed { // $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; return preg_replace_callback('/{@([\w]+)(?:\:([\w-]+))?}/i', function ($matches) { @@ -349,7 +349,7 @@ public function setParser(string $section, callable $handler): void * @return mixed * @throws RuntimeException */ - public function getParser(string $section, bool $throwException = false) + public function getParser(string $section, bool $throwException = false): mixed { if (!self::$parsers) { self::$parsers = self::loadDefaultParsers(); @@ -388,7 +388,7 @@ public function setMessages(array $messages): void * @param string $message The text to associate with the placeholder * @param string $name The name of the placeholder */ - public function setMessage($message, string $name = 'message'): void + public function setMessage(string $message, string $name = 'message'): void { $this->messages[$name] = $message; } @@ -421,9 +421,9 @@ public function getStep(): int /** * Sets the redraw frequency. * - * @param int|float $freq The frequency in steps + * @param float|int $freq The frequency in steps */ - public function setRedrawFreq($freq): void + public function setRedrawFreq(float|int $freq): void { $this->redrawFreq = max((int)$freq, 1); } @@ -499,11 +499,7 @@ public function setCompleteChar(string $completeChar): void */ public function getCompleteChar(): string { - if (null === $this->completeChar) { - return $this->maxSteps ? '=' : $this->completeChar; - } - - return $this->completeChar; + return $this->completeChar ?? ($this->maxSteps ? '=' : $this->completeChar); } /** @@ -549,7 +545,7 @@ public function getPercent(): float /** * @return mixed */ - public function getStartTime() + public function getStartTime(): mixed { return $this->startTime; } @@ -557,7 +553,7 @@ public function getStartTime() /** * @return mixed */ - public function getFinishTime() + public function getFinishTime(): mixed { return $this->finishTime; } @@ -639,7 +635,7 @@ private static function loadDefaultParsers(): array return $bar->getMaxSteps(); }, 'percent' => static function (self $bar) { - return (float)floor($bar->getPercent() * 100); + return floor($bar->getPercent() * 100); }, ]; } diff --git a/src/Util/Show.php b/src/Util/Show.php index 5ddc22de..9cd1a067 100644 --- a/src/Util/Show.php +++ b/src/Util/Show.php @@ -41,7 +41,6 @@ use function microtime; use function sprintf; use function strlen; -use function strpos; use function strtoupper; use function substr; use function ucwords; @@ -70,14 +69,8 @@ */ class Show { - /** @var string */ - private static $buffer; - - /** @var bool */ - private static $buffering = false; - /** @var array */ - public static $defaultBlocks = [ + public static array $defaultBlocks = [ 'block', 'primary', 'info', @@ -96,11 +89,11 @@ class Show * @param mixed $messages * @param string $type * @param string $style - * @param int|boolean $quit If is int, setting it is exit code. + * @param bool|int $quit If is int, setting it is exit code. * * @return int */ - public static function block($messages, string $type = 'MESSAGE', string $style = Style::NORMAL, $quit = false): int + public static function block(mixed $messages, string $type = 'MESSAGE', string $style = Style::NORMAL, bool|int $quit = false): int { $messages = is_array($messages) ? array_values($messages) : [$messages]; @@ -123,15 +116,15 @@ public static function block($messages, string $type = 'MESSAGE', string $style * @param mixed $messages * @param string $type * @param string $style - * @param int|boolean $quit If is int, setting it is exit code. + * @param boolean|int $quit If is int, setting it is exit code. * * @return int */ public static function liteBlock( - $messages, + mixed $messages, string $type = 'MESSAGE', string $style = Style::NORMAL, - $quit = false + bool|int $quit = false ): int { $fmtType = ''; $messages = is_array($messages) ? array_values($messages) : [$messages]; @@ -156,7 +149,7 @@ public static function liteBlock( /** * @var array */ - private static $blockMethods = [ + private static array $blockMethods = [ // method => style 'info' => 'info', 'note' => 'note', @@ -192,7 +185,7 @@ public static function __callStatic(string $method, array $args = []) $quit = $args[1] ?? false; $style = self::$blockMethods[$method]; - if (0 === strpos($method, 'lite')) { + if (str_starts_with($method, 'lite')) { $type = substr($method, 4); return self::liteBlock($msg, $type === 'primary' ? 'IMPORTANT' : $type, $style, $quit); @@ -214,7 +207,7 @@ public static function __callStatic(string $method, array $args = []) * @param mixed $data * @param string $title */ - public static function prettyJSON($data, string $title = 'JSON:'): void + public static function prettyJSON(mixed $data, string $title = 'JSON:'): void { if ($title) { Console::colored($title, 'ylw0'); @@ -258,10 +251,10 @@ public static function title(string $title, array $opts = []): void /** * @param string $title The title text - * @param string|array $body The section body message + * @param array|string $body The section body message * @param array $opts */ - public static function section(string $title, $body, array $opts = []): void + public static function section(string $title, array|string $body, array $opts = []): void { Section::show($title, $body, $opts); } @@ -295,13 +288,13 @@ public static function padding(array $data, string $title = '', array $opts = [] * ]; * ``` * - * @param array|object $data + * @param object|array $data * @param string $title * @param array $opts More {@see FormatUtil::spliceKeyValue()} * * @return int|string */ - public static function aList($data, string $title = '', array $opts = []) + public static function aList(object|array $data, string $title = '', array $opts = []): int|string { return SingleList::show($data, $title, $opts); } @@ -313,7 +306,7 @@ public static function aList($data, string $title = '', array $opts = []) * * @return int|string */ - public static function sList($data, string $title = '', array $opts = []) + public static function sList(mixed $data, string $title = '', array $opts = []): int|string { return SingleList::show($data, $title, $opts); } @@ -375,7 +368,7 @@ public static function helpPanel(array $config): void * * @return int */ - public static function panel($data, string $title = 'Information Panel', array $opts = []): int + public static function panel(mixed $data, string $title = 'Information Panel', array $opts = []): int { return Panel::show($data, $title, $opts); } @@ -593,7 +586,7 @@ public static function progressTxt(int $total, string $msg, string $doneMsg = '' * @param int $total * @param array $opts * - * @return Generator + * @return Generator|null * @internal int $current */ public static function progressBar(int $total, array $opts = []): ?Generator @@ -614,13 +607,13 @@ public static function progressBar(int $total, array $opts = []): ?Generator * $bar->finish(); * ``` * - * @param int $max + * @param int $max * @param bool $start * * @return ProgressBar * @throws LogicException */ - public static function createProgressBar($max = 0, $start = true): ProgressBar + public static function createProgressBar(int $max = 0, bool $start = true): ProgressBar { $bar = new ProgressBar(null, $max); @@ -631,103 +624,6 @@ public static function createProgressBar($max = 0, $start = true): ProgressBar return $bar; } - /*********************************************************************************** - * Output buffer - ***********************************************************************************/ - - /** - * @return bool - * @deprecated Please use \Inhere\Console\Console method instead it. - */ - public static function isBuffering(): bool - { - return self::$buffering; - } - - /** - * @return string - * @deprecated Please use \Inhere\Console\Console method instead it. - */ - public static function getBuffer(): string - { - return self::$buffer; - } - - /** - * @param string $buffer - * - * @deprecated Please use \Inhere\Console\Console method instead it. - */ - public static function setBuffer(string $buffer): void - { - self::$buffer = $buffer; - } - - /** - * start buffering - * - * @deprecated Please use \Inhere\Console\Console method instead it. - */ - public static function startBuffer(): void - { - self::$buffering = true; - } - - /** - * start buffering - * - * @deprecated Please use \Inhere\Console\Console method instead it. - */ - public static function clearBuffer(): void - { - self::$buffer = null; - } - - /** - * stop buffering - * - * @param bool $flush Whether flush buffer to output stream - * @param bool $nl Default is False, because the last write() have been added "\n" - * @param bool|int $quit - * @param array $opts - * - * @return null|string If flush = False, will return all buffer text. - * @see Show::write() - * @deprecated Please use \Inhere\Console\Console method instead it. - */ - public static function stopBuffer(bool $flush = true, bool $nl = false, $quit = false, array $opts = []): ?string - { - self::$buffering = false; - - if ($flush && self::$buffer) { - // all text have been rendered by Style::render() in every write(); - $opts['color'] = false; - - // flush to stream - self::write(self::$buffer, $nl, $quit, $opts); - - // clear buffer - self::$buffer = null; - } - - return self::$buffer; - } - - /** - * stop buffering and flush buffer text - * - * @param bool $nl - * @param bool $quit - * @param array $opts - * - * @see Show::write() - * @deprecated Please use \Inhere\Console\Console method instead it. - */ - public static function flushBuffer(bool $nl = false, $quit = false, array $opts = []): void - { - self::stopBuffer(true, $nl, $quit, $opts); - } - /*********************************************************************************** * Helper methods ***********************************************************************************/ @@ -748,10 +644,10 @@ public static function writef(string $format, ...$args): int /** * Write a message to standard output stream. * - * @param string|array $messages Output message + * @param array|string $messages Output message * @param boolean $nl True 会添加换行符, False 原样输出,不添加换行符 - * @param int|bool $quit If is int, setting it is exit code. 'True' translate as code 0 and exit, 'False' will not exit. - * @param array $opts Some options for write + * @param bool|int $quit If is int, setting it is exit code. 'True' translate as code 0 and exit, 'False' will not exit. + * @param array{color:bool,stream:resource,flush:bool,quit:bool,quitCode:int} $opts Some options for write * refer: * [ * 'color' => bool, // whether render color, default is: True. @@ -761,22 +657,24 @@ public static function writef(string $format, ...$args): int * * @return int */ - public static function write($messages, bool $nl = true, $quit = false, array $opts = []): int + public static function write(array|string $messages, bool $nl = true, bool $quit = false, array $opts = []): int { + $qCode = $opts['quitCode'] ?? 0; + return Console::write($messages, $nl, $quit, $opts); } /** * Write raw data to stdout, will disable color render. * - * @param string|array $message - * @param bool $nl - * @param bool|int $quit - * @param array $opts + * @param array|string $message + * @param bool $nl + * @param bool $quit + * @param array{color:bool,stream:resource,flush:bool,quit:bool,quitCode:int} $opts * * @return int */ - public static function writeRaw($message, bool $nl = true, $quit = false, array $opts = []): int + public static function writeRaw(array|string $message, bool $nl = true, bool $quit = false, array $opts = []): int { $opts['color'] = false; return Console::write($message, $nl, $quit, $opts); @@ -785,26 +683,26 @@ public static function writeRaw($message, bool $nl = true, $quit = false, array /** * Write data to stdout with newline. * - * @param string|array $message - * @param array $opts - * @param bool|int $quit + * @param array|string $message + * @param bool $quit + * @param array{color:bool,stream:resource,flush:bool,quit:bool,quitCode:int} $opts * * @return int */ - public static function writeln($message, $quit = false, array $opts = []): int + public static function writeln(array|string $message, bool $quit = false, array $opts = []): int { return Console::write($message, true, $quit, $opts); } /** - * @param string|array $message + * @param array|string $message * @param string $style * @param bool $nl - * @param array $opts + * @param array{quit:bool,quitCode:int} $opts * * @return int */ - public static function colored($message, string $style = 'info', bool $nl = true, array $opts = []): int + public static function colored(array|string $message, string $style = 'info', bool $nl = true, array $opts = []): int { $quit = isset($opts['quit']) ? (bool)$opts['quit'] : false; From c5bc385621fb7941b1932102b6c9adc87a078bb9 Mon Sep 17 00:00:00 2001 From: Inhere Date: Mon, 29 Nov 2021 23:02:50 +0800 Subject: [PATCH 200/258] style: run code inspect for test example resource --- examples/Command/CorCommand.php | 6 +-- examples/Command/DemoCommand.php | 4 +- examples/Command/TestCommand.php | 4 +- examples/Controller/HomeController.php | 6 ++- examples/Controller/InteractController.php | 4 +- examples/Controller/ProcessController.php | 4 +- examples/Controller/ShowController.php | 4 +- examples/alone | 4 +- examples/alone-app | 4 +- examples/demo/cli-spinner.php | 15 +++--- examples/demo/constants.php | 22 ++++----- examples/demo/progress_bar3.php | 3 +- examples/demo/sf2_color.php | 44 ++++++++--------- examples/demo/spinner.php | 6 +-- resource/deprecated/AbstractHandlerOld.php | 4 +- resource/deprecated/AbstractInput.php | 20 ++++---- resource/deprecated/InputArgumentsTrait.php | 34 ++++++------- resource/deprecated/InputDefinition.php | 54 ++++++++++----------- resource/deprecated/InputOptionsTrait.php | 22 ++++----- test/TestCommand.php | 6 +-- test/TestController.php | 6 +-- test/bootstrap.php | 10 ++-- 22 files changed, 146 insertions(+), 140 deletions(-) diff --git a/examples/Command/CorCommand.php b/examples/Command/CorCommand.php index 0c530dfc..4a2bfa92 100644 --- a/examples/Command/CorCommand.php +++ b/examples/Command/CorCommand.php @@ -20,11 +20,11 @@ */ class CorCommand extends Command { - protected static $name = 'cor'; + protected static string $name = 'cor'; - protected static $description = 'a coroutine test command'; + protected static string $description = 'a coroutine test command'; - protected static $coroutine = true; + protected static bool $coroutine = true; /** * @return array diff --git a/examples/Command/DemoCommand.php b/examples/Command/DemoCommand.php index b820c031..28c948e6 100644 --- a/examples/Command/DemoCommand.php +++ b/examples/Command/DemoCommand.php @@ -21,9 +21,9 @@ */ class DemoCommand extends Command { - protected static $name = 'demo'; + protected static string $name = 'demo'; - protected static $description = 'this is a demo alone command. but use Definition instead of annotations'; + protected static string $description = 'this is a demo alone command. but use Definition instead of annotations'; /** * {@inheritDoc} diff --git a/examples/Command/TestCommand.php b/examples/Command/TestCommand.php index bf9496e4..8b1e4430 100644 --- a/examples/Command/TestCommand.php +++ b/examples/Command/TestCommand.php @@ -19,9 +19,9 @@ */ class TestCommand extends Command { - protected static $name = 'test'; + protected static string $name = 'test'; - protected static $description = 'this is a test independent command'; + protected static string $description = 'this is a test independent command'; protected function commands(): array { diff --git a/examples/Controller/HomeController.php b/examples/Controller/HomeController.php index df316dcc..676a5b6c 100644 --- a/examples/Controller/HomeController.php +++ b/examples/Controller/HomeController.php @@ -14,6 +14,7 @@ use Inhere\Console\Util\Interact; use Inhere\Console\Util\ProgressBar; use Inhere\Console\Util\Show; +use JetBrains\PhpStorm\NoReturn; use LogicException; use RuntimeException; use Toolkit\Cli\Cli; @@ -31,9 +32,9 @@ */ class HomeController extends Controller { - protected static $name = 'home'; + protected static string $name = 'home'; - protected static $description = 'This is a demo command controller. there are some command usage examples(2)'; + protected static string $description = 'This is a demo command controller. there are some command usage examples(2)'; /** * @return array @@ -151,6 +152,7 @@ public function exCommand(): void /** * a command for test trigger error */ + #[NoReturn] public function errorCommand(): void { trigger_error('oo, this is a runtime error!', E_USER_ERROR); diff --git a/examples/Controller/InteractController.php b/examples/Controller/InteractController.php index 90f1de3a..04e797b2 100644 --- a/examples/Controller/InteractController.php +++ b/examples/Controller/InteractController.php @@ -22,9 +22,9 @@ */ class InteractController extends Controller { - protected static $name = 'interact'; + protected static string $name = 'interact'; - protected static $description = 'there are some demo commands for use interactive method'; + protected static string $description = 'there are some demo commands for use interactive method'; public static function aliases(): array { diff --git a/examples/Controller/ProcessController.php b/examples/Controller/ProcessController.php index 670894d5..2e2ea66e 100644 --- a/examples/Controller/ProcessController.php +++ b/examples/Controller/ProcessController.php @@ -21,9 +21,9 @@ */ class ProcessController extends Controller { - protected static $name = 'process'; + protected static string $name = 'process'; - protected static $description = 'Some simple process to create and use examples'; + protected static string $description = 'Some simple process to create and use examples'; protected static function commandAliases(): array { diff --git a/examples/Controller/ShowController.php b/examples/Controller/ShowController.php index 5a5930e2..f5b0b924 100644 --- a/examples/Controller/ShowController.php +++ b/examples/Controller/ShowController.php @@ -27,9 +27,9 @@ */ class ShowController extends Controller { - protected static $name = 'show'; + protected static string $name = 'show'; - protected static $description = 'there are some demo commands for show format data'; + protected static string $description = 'there are some demo commands for show format data'; public static function commandAliases(): array { diff --git a/examples/alone b/examples/alone index 44c6dbc3..7d6e1679 100644 --- a/examples/alone +++ b/examples/alone @@ -19,10 +19,10 @@ $ctrl->setDetached(); try { exit($ctrl->run([$input->getCommand()])); -} catch (\Exception $e) { +} catch (Exception $e) { $message = Color::apply('error', $e->getMessage()); - echo \sprintf("%s\nFile %s:%d\nTrace:\n%s\n", + echo sprintf("%s\nFile %s:%d\nTrace:\n%s\n", $message, $e->getFile(), $e->getLine(), $e->getTraceAsString() ); } diff --git a/examples/alone-app b/examples/alone-app index 3c2ec1b7..605d07ff 100644 --- a/examples/alone-app +++ b/examples/alone-app @@ -23,10 +23,10 @@ try { $app->controller('home', HomeController::class); $subCmd = $input->getCommand(); - $result = $app->dispatch('home:' . $subCmd, true); + $result = $app->dispatch('home:' . $subCmd, []); exit((int)$result); -} catch (\Exception $e) { +} catch (Exception $e) { $message = Color::apply('error', $e->getMessage()); echo sprintf("%s\nFile %s:%d\nTrace:\n%s\n", diff --git a/examples/demo/cli-spinner.php b/examples/demo/cli-spinner.php index b143e3d9..ed688fab 100644 --- a/examples/demo/cli-spinner.php +++ b/examples/demo/cli-spinner.php @@ -43,9 +43,10 @@ public static function spinner(): void /** * Uses `stty` to hide input/output completely. + * * @param boolean $hidden Will hide/show the next data. Defaults to true. */ - public static function hide($hidden = true): void + public static function hide(bool $hidden = true): void { system('stty ' . ($hidden? '-echo' : 'echo')); } @@ -53,12 +54,13 @@ public static function hide($hidden = true): void /** * Prompts the user for input. Optionally masking it. * - * @param string $prompt The prompt to show the user - * @param bool $masked If true, the users input will not be shown. e.g. password input - * @param int $limit The maximum amount of input to accept + * @param string $prompt The prompt to show the user + * @param bool $masked If true, the users input will not be shown. e.g. password input + * @param int $limit The maximum amount of input to accept + * * @return string */ - public static function prompt($prompt, $masked=false, $limit=100) + public static function prompt(string $prompt, bool $masked, int $limit=100): string { echo "$prompt: "; if ($masked) { @@ -66,7 +68,7 @@ public static function prompt($prompt, $masked=false, $limit=100) } $buffer = ''; $char = ''; - $f = fopen('php://stdin', 'r'); + $f = fopen('php://stdin', 'rb'); while (strlen($buffer) < $limit) { $char = fread($f, 1); if ($char === "\n" || $char === "\r") { @@ -89,6 +91,7 @@ public static function prompt($prompt, $masked=false, $limit=100) echo $input; die; +/** @noinspection PhpUnreachableStatementInspection */ $total = random_int(5000, 10000); for ($x=1; $x<=$total; $x++) { Status::spinner(); diff --git a/examples/demo/constants.php b/examples/demo/constants.php index 8346c921..382028e0 100644 --- a/examples/demo/constants.php +++ b/examples/demo/constants.php @@ -8,19 +8,19 @@ */ // Boolean true constants -define('yes', true); -define('ok', true); -define('okay', true); -define('✔', true); -define('correct', true); -define('👍', true); +const yes = true; +const ok = true; +const okay = true; +const ✔ = true; +const correct = true; +const 👍 = true; // Boolean false constants -define('no', false); -define('not', false); -define('✘', false); -define('wrong', false); -define('👎', false); +const no = false; +const not = false; +const ✘ = false; +const wrong = false; +const 👎 = false; // Constants with a random boolean value define('maybe', (bool)random_int(0, 1)); diff --git a/examples/demo/progress_bar3.php b/examples/demo/progress_bar3.php index c6720f00..696ad942 100644 --- a/examples/demo/progress_bar3.php +++ b/examples/demo/progress_bar3.php @@ -11,9 +11,10 @@ * @param int $total * @param string $msg * @param string $char + * * @return Generator */ -function progress_bar($total, $msg, $char = '=') +function progress_bar(int $total, string $msg, string $char = '='): Generator { $finished = false; diff --git a/examples/demo/sf2_color.php b/examples/demo/sf2_color.php index 25cc4623..f3449ec3 100644 --- a/examples/demo/sf2_color.php +++ b/examples/demo/sf2_color.php @@ -14,7 +14,7 @@ */ class OutputFormatterStyle { - private static $availableForegroundColors = [ + private static array $availableForegroundColors = [ 'black' => ['set' => 30, 'unset' => 39], 'red' => ['set' => 31, 'unset' => 39], 'green' => ['set' => 32, 'unset' => 39], @@ -26,7 +26,7 @@ class OutputFormatterStyle 'default' => ['set' => 39, 'unset' => 39], ]; - private static $availableBackgroundColors = [ + private static array $availableBackgroundColors = [ 'black' => ['set' => 40, 'unset' => 49], 'red' => ['set' => 41, 'unset' => 49], 'green' => ['set' => 42, 'unset' => 49], @@ -38,7 +38,7 @@ class OutputFormatterStyle 'default' => ['set' => 49, 'unset' => 49], ]; - private static $availableOptions = [ + private static array $availableOptions = [ 'bold' => ['set' => 1, 'unset' => 22], 'underscore' => ['set' => 4, 'unset' => 24], 'blink' => ['set' => 5, 'unset' => 25], @@ -46,11 +46,11 @@ class OutputFormatterStyle 'conceal' => ['set' => 8, 'unset' => 28], ]; - private $foreground; + private array $foreground = []; - private $background; + private array $background = []; - private $options = []; + private array $options = []; /** * Initializes output formatter style. @@ -59,7 +59,7 @@ class OutputFormatterStyle * @param string|null $background The style background color name * @param array $options The style options */ - public function __construct($foreground = null, $background = null, array $options = []) + public function __construct(string $foreground = null, string $background = null, array $options = []) { if (null !== $foreground) { $this->setForeground($foreground); @@ -77,16 +77,16 @@ public function __construct($foreground = null, $background = null, array $optio * * @param string|null $color The color name * - * @throws \InvalidArgumentException When the color name isn't defined + * @throws InvalidArgumentException When the color name isn't defined */ - public function setForeground($color = null): void + public function setForeground(string $color = null): void { if (null === $color) { $this->foreground = null; return; } if (!isset(static::$availableForegroundColors[$color])) { - throw new \InvalidArgumentException(sprintf( + throw new InvalidArgumentException(sprintf( 'Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors)) @@ -100,16 +100,16 @@ public function setForeground($color = null): void * * @param string|null $color The color name * - * @throws \InvalidArgumentException When the color name isn't defined + * @throws InvalidArgumentException When the color name isn't defined */ - public function setBackground($color = null): void + public function setBackground(string $color = null): void { if (null === $color) { $this->background = null; return; } if (!isset(static::$availableBackgroundColors[$color])) { - throw new \InvalidArgumentException(sprintf( + throw new InvalidArgumentException(sprintf( 'Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors)) @@ -123,12 +123,12 @@ public function setBackground($color = null): void * * @param string $option The option name * - * @throws \InvalidArgumentException When the option name isn't defined + * @throws InvalidArgumentException When the option name isn't defined */ - public function setOption($option): void + public function setOption(string $option): void { if (!isset(static::$availableOptions[$option])) { - throw new \InvalidArgumentException(sprintf( + throw new InvalidArgumentException(sprintf( 'Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)) @@ -144,12 +144,12 @@ public function setOption($option): void * * @param string $option The option name * - * @throws \InvalidArgumentException When the option name isn't defined + * @throws InvalidArgumentException When the option name isn't defined */ - public function unsetOption($option): void + public function unsetOption(string $option): void { if (!isset(static::$availableOptions[$option])) { - throw new \InvalidArgumentException(sprintf( + throw new InvalidArgumentException(sprintf( 'Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)) @@ -181,15 +181,15 @@ public function setOptions(array $options): void * * @return string */ - public function apply($text) + public function apply(string $text): string { $setCodes = []; $unsetCodes = []; - if (null !== $this->foreground) { + if ($this->foreground) { $setCodes[] = $this->foreground['set']; $unsetCodes[] = $this->foreground['unset']; } - if (null !== $this->background) { + if ($this->background) { $setCodes[] = $this->background['set']; $unsetCodes[] = $this->background['unset']; } diff --git a/examples/demo/spinner.php b/examples/demo/spinner.php index 7b027812..44b6997a 100644 --- a/examples/demo/spinner.php +++ b/examples/demo/spinner.php @@ -10,7 +10,7 @@ class Spinner { /** @var int $speed ms */ - public $speed = 100; + public int $speed = 100; public static function create($speed): void { @@ -19,12 +19,12 @@ public static function create($speed): void Swoole\Runtime::enableCoroutine(); -function spinner() +function spinner(): Generator { $chars = '-\|/'; $index = 0; - yield function () use ($chars, $index): void { + yield static function () use ($chars, $index): void { while (1) { printf("\x0D\x1B[2K %s handling ...", $chars[$index]); diff --git a/resource/deprecated/AbstractHandlerOld.php b/resource/deprecated/AbstractHandlerOld.php index 3b0a6ee2..0f967dbc 100644 --- a/resource/deprecated/AbstractHandlerOld.php +++ b/resource/deprecated/AbstractHandlerOld.php @@ -23,7 +23,7 @@ class AbstractHandlerOld /** * @var InputDefinition|null */ - protected $definition; + protected ?InputDefinition $definition; /** * @return InputDefinition @@ -123,7 +123,7 @@ public function validateInput(): bool } elseif ($conf['default'] !== null) { $opts[$name] = $conf['default']; } elseif ($conf['required']) { - $missingOpts[] = "--{$name}" . ($srt ? "|-{$srt}" : ''); + $missingOpts[] = "--$name" . ($srt ? "|-$srt" : ''); } } } diff --git a/resource/deprecated/AbstractInput.php b/resource/deprecated/AbstractInput.php index 43873da9..47d65de3 100644 --- a/resource/deprecated/AbstractInput.php +++ b/resource/deprecated/AbstractInput.php @@ -36,19 +36,19 @@ abstract class AbstractInput implements InputInterface * * @var FlagsParser|SFlags */ - protected $gfs; + protected FlagsParser|SFlags $gfs; /** * Command flags parser * * @var FlagsParser|SFlags */ - protected $fs; + protected FlagsParser|SFlags $fs; /** * @var string */ - protected $pwd = ''; + protected string $pwd = ''; /** * The bin script file @@ -56,7 +56,7 @@ abstract class AbstractInput implements InputInterface * * @var string */ - protected $scriptFile = ''; + protected string $scriptFile = ''; /** * The bin script name @@ -64,7 +64,7 @@ abstract class AbstractInput implements InputInterface * * @var string */ - protected $scriptName = ''; + protected string $scriptName = ''; /** * the command name(Is first argument) @@ -72,7 +72,7 @@ abstract class AbstractInput implements InputInterface * * @var string */ - protected $command = ''; + protected string $command = ''; /** * the command name(Is first argument) @@ -80,14 +80,14 @@ abstract class AbstractInput implements InputInterface * * @var string */ - protected $subCommand = ''; + protected string $subCommand = ''; /** * eg `./examples/app home:useArg status=2 name=john arg0 -s=test --page=23` * * @var string */ - protected $fullScript; + protected string $fullScript; /** * Raw input argv data. @@ -95,14 +95,14 @@ abstract class AbstractInput implements InputInterface * * @var array */ - protected $tokens; + protected array $tokens; /** * Same the $tokens but no $script * * @var array */ - protected $flags = []; + protected array $flags = []; /** * @return string diff --git a/resource/deprecated/InputArgumentsTrait.php b/resource/deprecated/InputArgumentsTrait.php index 03ec7b1f..e59f5858 100644 --- a/resource/deprecated/InputArgumentsTrait.php +++ b/resource/deprecated/InputArgumentsTrait.php @@ -25,7 +25,7 @@ trait InputArgumentsTrait * * @var array */ - protected $args = []; + protected array $args = []; /** * Bind an name for argument index @@ -37,14 +37,14 @@ trait InputArgumentsTrait * * @var array */ - protected $binds = []; + protected array $binds = []; /** * @param string $name * @param int $index * @return self */ - public function bindArgument(string $name, int $index) + public function bindArgument(string $name, int $index): static { $this->binds[$name] = $index; return $this; @@ -95,11 +95,11 @@ public function setArgs(array $args, bool $replace = false): void } /** - * @param string|int $name + * @param int|string $name * * @return bool */ - public function hasArg($name): bool + public function hasArg(int|string $name): bool { // get real index key $key = $this->binds[$name] ?? $name; @@ -110,12 +110,12 @@ public function hasArg($name): bool /** * get Argument * - * @param null|int|string $name - * @param mixed $default + * @param int|string|null $name + * @param mixed|null $default * * @return mixed */ - public function getArgument($name, $default = null) + public function getArgument(int|string|null $name, mixed $default = null): mixed { return $this->get($name, $default); } @@ -123,12 +123,12 @@ public function getArgument($name, $default = null) /** * get argument * - * @param null|int|string $name - * @param mixed $default + * @param int|string|null $name + * @param mixed|null $default * * @return mixed */ - public function getArg($name, $default = null) + public function getArg(int|string|null $name, mixed $default = null): mixed { return $this->get($name, $default); } @@ -136,12 +136,12 @@ public function getArg($name, $default = null) /** * get Argument * - * @param null|int|string $name - * @param mixed $default + * @param int|string|null $name + * @param mixed|null $default * * @return mixed */ - public function get($name, $default = null) + public function get(int|string|null $name, mixed $default = null): mixed { // get real index key $key = $this->binds[$name] ?? $name; @@ -157,7 +157,7 @@ public function get($name, $default = null) * * @return mixed */ - public function getRequiredArg($name, string $errMsg = '') + public function getRequiredArg(int|string $name, string $errMsg = ''): mixed { // get real index key $key = $this->binds[$name] ?? $name; @@ -166,8 +166,8 @@ public function getRequiredArg($name, string $errMsg = '') } if (!$errMsg) { - $errName = is_int($key) ? "'{$name}'(position#{$key})" : "'{$name}'"; - $errMsg = "The argument {$errName} is required"; + $errName = is_int($key) ? "'$name'(position#$key)" : "'$name'"; + $errMsg = "The argument $errName is required"; } throw new PromptException($errMsg); diff --git a/resource/deprecated/InputDefinition.php b/resource/deprecated/InputDefinition.php index 8074785d..cad2b2ab 100644 --- a/resource/deprecated/InputDefinition.php +++ b/resource/deprecated/InputDefinition.php @@ -33,7 +33,7 @@ class InputDefinition { /** @var array */ - private static $defaultArgOptConfig = [ + private static array $defaultArgOptConfig = [ 'mode' => null, 'default' => null, 'description' => '', @@ -42,42 +42,42 @@ class InputDefinition /** * @var string|array */ - private $example; + private string|array $example; /** * @var string */ - private $description; + private string $description; /** * @var array[] */ - private $arguments = []; + private array $arguments = []; /** * @var int */ - private $requiredCount = 0; + private int $requiredCount = 0; /** * @var bool */ - private $hasOptionalArgument = false; + private bool $hasOptionalArgument = false; /** * @var bool */ - private $hasAnArrayArgument = false; + private bool $hasAnArrayArgument = false; /** * @var array[] */ - private $options; + private array $options; /** * @var array */ - private $shortcuts; + private array $shortcuts; /** * @param array $arguments @@ -160,12 +160,12 @@ public function addArg(string $name, int $mode = null, string $description = '', * @param int $mode The argument mode flags. eg: Input::ARG_REQUIRED, Input::ARG_OPTIONAL * allow more flags, eg: Input::ARG_REQUIRED|Input::ARG_IS_ARRAY * @param string $description A description text - * @param mixed $default The default value (for Input::ARG_OPTIONAL mode only) + * @param mixed|null $default The default value (for Input::ARG_OPTIONAL mode only) * * @return $this * @throws LogicException */ - public function addArgument(string $name, int $mode = 0, string $description = '', $default = null): self + public function addArgument(string $name, int $mode = 0, string $description = '', mixed $default = null): self { if (0 === $mode) { $mode = Input::ARG_OPTIONAL; @@ -231,7 +231,7 @@ public function addArgument(string $name, int $mode = 0, string $description = ' * * @return string|int|null */ - public function getArgument($name, $default = null) + public function getArgument(int|string $name, $default = null): array|int|string|null { $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; @@ -244,11 +244,11 @@ public function getArgument($name, $default = null) } /** - * @param string|int $name The argument name or position + * @param int|string $name The argument name or position * * @return bool true if the InputArgument object exists, false otherwise */ - public function hasArgument($name): bool + public function hasArgument(int|string $name): bool { $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; @@ -324,7 +324,7 @@ public function addOptions(array $options = []): void * @param string|null $shortcut * @param int|null $mode * @param string $description - * @param null|mixed $default + * @param mixed|null $default * * @return InputDefinition */ @@ -333,7 +333,7 @@ public function addOpt( string $shortcut = null, int $mode = null, string $description = '', - $default = null + mixed $default = null ): self { return $this->addOption($name, $shortcut, $mode, $description, $default); } @@ -342,12 +342,12 @@ public function addOpt( * Adds an option. * * @param string|bool $name The option name, must is a string - * @param string|array|null $shortcut The shortcut (can be null) + * @param array|string|null $shortcut The shortcut (can be null) * - array: [a, b] * - string: 'a|b' * @param int $mode The option mode: One of the Input::OPT_* constants * @param string $description A description text - * @param mixed $default The default value (must be null for InputOption::OPT_BOOL) + * @param mixed|null $default The default value (must be null for InputOption::OPT_BOOL) * * @return $this * @throws InvalidArgumentException @@ -355,10 +355,10 @@ public function addOpt( */ public function addOption( string $name, - $shortcut = '', + array|string|null $shortcut = '', int $mode = 0, string $description = '', - $default = null + mixed $default = null ): self { $name = trim($name, '-'); if (empty($name)) { @@ -510,7 +510,7 @@ public function getOptionByShortcut(string $shortcut): array * @return mixed * @throws InvalidArgumentException */ - private function shortcutToName(string $shortcut) + private function shortcutToName(string $shortcut): mixed { if (!isset($this->shortcuts[$shortcut])) { throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); @@ -554,7 +554,7 @@ public function getSynopsis(bool $short = false): array $shortcut = $option['shortcut'] ? sprintf('-%s, ', $option['shortcut']) : ' '; $elements[] = sprintf('[%s--%s%s]', $shortcut, $name, $value); - $key = "{$shortcut}--{$name}"; + $key = "$shortcut--$name"; $opts[$key] = ($option['required'] ? '*' : '') . $option['description']; } } @@ -597,11 +597,11 @@ public function getSynopsis(bool $short = false): array } /** - * @param string|int $name + * @param int|string $name * * @return bool */ - public function argumentIsRequired($name): bool + public function argumentIsRequired(int|string $name): bool { if (isset($this->arguments[$name])) { return $this->arguments[$name]['mode'] === Input::ARG_REQUIRED; @@ -633,17 +633,17 @@ protected function optionIsAcceptValue(int $mode): bool /** * @return string|array */ - public function getExample() + public function getExample(): array|string { return $this->example; } /** - * @param string|array $example + * @param array|string $example * * @return $this */ - public function setExample($example): self + public function setExample(array|string $example): self { $this->example = $example; return $this; diff --git a/resource/deprecated/InputOptionsTrait.php b/resource/deprecated/InputOptionsTrait.php index 563de77d..f8733157 100644 --- a/resource/deprecated/InputOptionsTrait.php +++ b/resource/deprecated/InputOptionsTrait.php @@ -24,14 +24,14 @@ trait InputOptionsTrait * * @var array */ - protected $sOpts = []; + protected array $sOpts = []; /** * Input long-opts data * * @var array */ - protected $lOpts = []; + protected array $lOpts = []; /*********************************************************************************** * long/short options (eg: -d --help) @@ -46,7 +46,7 @@ trait InputOptionsTrait * * @return bool|mixed|null */ - public function getOpt(string $name, $default = null) + public function getOpt(string $name, $default = null): mixed { // It's long-opt if (isset($name[1])) { @@ -60,11 +60,11 @@ public function getOpt(string $name, $default = null) * Alias of the getOpt() * * @param string $name - * @param mixed $default + * @param mixed|null $default * * @return mixed */ - public function getOption(string $name, $default = null) + public function getOption(string $name, mixed $default = null): mixed { return $this->getOpt($name, $default); } @@ -78,7 +78,7 @@ public function getOption(string $name, $default = null) * * @return mixed */ - public function getRequiredOpt(string $name, string $errMsg = '') + public function getRequiredOpt(string $name, string $errMsg = ''): mixed { if (null !== ($val = $this->getOpt($name))) { return $val; @@ -134,7 +134,7 @@ public function clearOpts(): void * * @return mixed|null */ - public function getShortOpt(string $name, $default = null) + public function getShortOpt(string $name, $default = null): mixed { return $this->sOpts[$name] ?? $default; } @@ -163,7 +163,7 @@ public function getShortOpts(): array * @param string $name * @param mixed $value */ - public function setSOpt(string $name, $value): void + public function setSOpt(string $name, mixed $value): void { $this->sOpts[$name] = $value; } @@ -203,7 +203,7 @@ public function clearSOpts(): void * * @return mixed|null */ - public function lOpt(string $name, $default = null) + public function lOpt(string $name, $default = null): mixed { return $this->lOpts[$name] ?? $default; } @@ -216,7 +216,7 @@ public function lOpt(string $name, $default = null) * * @return mixed|null */ - public function getLongOpt(string $name, $default = null) + public function getLongOpt(string $name, $default = null): mixed { return $this->lOpts[$name] ?? $default; } @@ -245,7 +245,7 @@ public function getLongOpts(): array * @param string $name * @param mixed $value */ - public function setLOpt(string $name, $value): void + public function setLOpt(string $name, mixed $value): void { $this->lOpts[$name] = $value; } diff --git a/test/TestCommand.php b/test/TestCommand.php index 3eb4c5c2..f8277ab9 100644 --- a/test/TestCommand.php +++ b/test/TestCommand.php @@ -20,9 +20,9 @@ */ class TestCommand extends Command { - protected static $name = 'test1'; + protected static string $name = 'test1'; - protected static $description = 'command description message'; + protected static string $description = 'command description message'; /** * do execute command @@ -32,7 +32,7 @@ class TestCommand extends Command * * @return int|mixed */ - protected function execute(Input $input, Output $output) + protected function execute(Input $input, Output $output): mixed { return __METHOD__; } diff --git a/test/TestController.php b/test/TestController.php index 2f212dfc..5025568e 100644 --- a/test/TestController.php +++ b/test/TestController.php @@ -18,16 +18,16 @@ */ class TestController extends Controller { - protected static $name = 'test'; + protected static string $name = 'test'; - protected static $description = 'controller description message'; + protected static string $description = 'controller description message'; /** * this is an demo command in test * * @return mixed */ - public function demoCommand() + public function demoCommand(): mixed { return __METHOD__; } diff --git a/test/bootstrap.php b/test/bootstrap.php index 8b298d16..0c6634d2 100644 --- a/test/bootstrap.php +++ b/test/bootstrap.php @@ -10,18 +10,18 @@ error_reporting(E_ALL | E_STRICT); date_default_timezone_set('Asia/Shanghai'); -spl_autoload_register(function ($class): void { +spl_autoload_register(static function ($class): void { $file = null; - if (0 === strpos($class, 'Inhere\Console\Examples\\')) { + if (str_starts_with($class, 'Inhere\Console\Examples\\')) { $path = str_replace('\\', '/', substr($class, strlen('Inhere\Console\Examples\\'))); $file = dirname(__DIR__) . "/examples/$path.php"; - } elseif (0 === strpos($class, 'Inhere\ConsoleTest\\')) { + } elseif (str_starts_with($class, 'Inhere\ConsoleTest\\')) { $path = str_replace('\\', '/', substr($class, strlen('Inhere\ConsoleTest\\'))); $file = __DIR__ . "/$path.php"; - } elseif (0 === strpos($class, 'Inhere\Console\\')) { + } elseif (str_starts_with($class, 'Inhere\Console\\')) { $path = str_replace('\\', '/', substr($class, strlen('Inhere\Console\\'))); - $file = dirname(__DIR__) . "/src/{$path}.php"; + $file = dirname(__DIR__) . "/src/$path.php"; } if ($file && is_file($file)) { From 7611abac7c4d7e9c777c8f68aebe42a7e290c828 Mon Sep 17 00:00:00 2001 From: Inhere Date: Mon, 29 Nov 2021 23:03:30 +0800 Subject: [PATCH 201/258] style: run code inspect for some class --- phar.build.inc | 2 +- src/AbstractApplication.php | 53 ++++++++++++------------ src/Annotate/AnnotateRules.php | 2 +- src/Annotate/Attr/RuleArg.php | 4 +- src/Annotate/Attr/RuleOpt.php | 4 +- src/Annotate/DocblockRules.php | 8 ++-- src/Application.php | 25 +++++------ src/Command.php | 14 +++---- src/Console.php | 8 ++-- src/Contract/ApplicationInterface.php | 16 ++++--- src/Contract/CommandHandlerInterface.php | 2 +- src/Contract/OutputInterface.php | 17 +++++++- src/Contract/RouterInterface.php | 12 +++--- src/Controller.php | 32 +++++++------- src/GlobalOption.php | 8 ++-- src/Handler/AbstractHandler.php | 28 ++++++------- src/Handler/CallableCommand.php | 2 +- src/Router.php | 12 +++--- 18 files changed, 131 insertions(+), 118 deletions(-) diff --git a/phar.build.inc b/phar.build.inc index 652f6855..416e64ef 100644 --- a/phar.build.inc +++ b/phar.build.inc @@ -36,5 +36,5 @@ $compiler->setStripFilter(static function ($file) { /** @var SplFileInfo $file */ $name = $file->getFilename(); - return false === strpos($name, 'Command.php') && false === strpos($name, 'Controller.php'); + return !str_contains($name, 'Command.php') && !str_contains($name, 'Controller.php'); }); diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 8bf42898..02323dc3 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -25,6 +25,7 @@ use Inhere\Console\Util\Helper; use Inhere\Console\Util\Interact; use InvalidArgumentException; +use JetBrains\PhpStorm\NoReturn; use Throwable; use Toolkit\Cli\Helper\FlagHelper; use Toolkit\Cli\Style; @@ -66,14 +67,14 @@ abstract class AbstractApplication implements ApplicationInterface use SimpleEventAwareTrait; /** @var array */ - protected static $internalCommands = [ + protected static array $internalCommands = [ 'version' => 'Show application version information', 'help' => 'Show application help information', 'list' => 'List all group and alone commands', ]; /** @var array Application runtime stats */ - protected $stats = [ + protected array $stats = [ 'startTime' => 0, 'endTime' => 0, 'startMemory' => 0, @@ -83,19 +84,19 @@ abstract class AbstractApplication implements ApplicationInterface /** * @var string */ - public $delimiter = ':'; // '/' ':' + public string $delimiter = ':'; // '/' ':' /** * @var string */ - protected $commandName = ''; + protected string $commandName = ''; /** * @var string Command delimiter char. e.g dev:serve */ /** @var array Application config data */ - protected $config = [ + protected array $config = [ 'name' => 'My Console Application', 'desc' => 'This is my console application', 'version' => '0.5.1', @@ -124,17 +125,17 @@ abstract class AbstractApplication implements ApplicationInterface /** * @var Router */ - protected $router; + protected Router $router; /** * @var ErrorHandlerInterface Can custom error handler */ - protected $errorHandler; + protected ErrorHandlerInterface $errorHandler; /** * @var Controller[] */ - protected $groupObjects = []; + protected array $groupObjects = []; /** * Class constructor. @@ -169,12 +170,10 @@ protected function init(): void 'endMemory' => 0, ]; - if (!$this->errorHandler) { - $this->errorHandler = new ErrorHandler([ - 'rootPath' => $this->config['rootPath'], - 'hideRootPath' => (bool)$this->config['hideRootPath'], - ]); - } + $this->errorHandler = new ErrorHandler([ + 'rootPath' => $this->config['rootPath'], + 'hideRootPath' => (bool)$this->config['hideRootPath'], + ]); $this->registerErrorHandle(); @@ -288,10 +287,10 @@ protected function beforeRun(): bool * * @param bool $exit * - * @return int|mixed + * @return mixed * @throws InvalidArgumentException */ - public function run(bool $exit = true) + public function run(bool $exit = true): mixed { try { // init @@ -334,6 +333,7 @@ protected function afterRun(): void /** * @param int $code */ + #[NoReturn] public function stop(int $code = 0): void { // call 'onAppStop' event, if it is registered. @@ -355,9 +355,9 @@ public function stop(int $code = 0): void /** * @param array $args * - * @return int|mixed + * @return mixed */ - public function runWithArgs(array $args) + public function runWithArgs(array $args): mixed { $this->input->setFlags($args); return $this->run(false); @@ -382,9 +382,9 @@ public function runWithIO(InputInterface $input, OutputInterface $output): void * @param InputInterface $input * @param OutputInterface $output * - * @return int|mixed + * @return mixed */ - public function subRun(string $command, InputInterface $input, OutputInterface $output) + public function subRun(string $command, InputInterface $input, OutputInterface $output): mixed { $app = $this->copy(); $app->setInput($input); @@ -449,6 +449,7 @@ protected function registerErrorHandle(): void * * @throws InvalidArgumentException */ + #[NoReturn] public function handleError(int $num, string $str, string $file, int $line): void { $this->handleException(new ErrorException($str, 0, $num, $file, $line)); @@ -591,11 +592,11 @@ protected function startInteractiveShell(): void /** * @param string $name - * @param string|array $aliases + * @param array|string $aliases * * @return $this */ - public function addAliases(string $name, $aliases): self + public function addAliases(string $name, array|string $aliases): self { if ($name && $aliases) { $this->router->setAlias($name, $aliases, true); @@ -768,12 +769,12 @@ public function getArrayParam(string $name, array $default = []): array /** * Get config param value * - * @param string $name - * @param null|string|mixed $default + * @param string $name + * @param mixed|null $default * - * @return array|string + * @return mixed */ - public function getParam(string $name, $default = null) + public function getParam(string $name, mixed $default = null): mixed { return $this->config[$name] ?? $default; } diff --git a/src/Annotate/AnnotateRules.php b/src/Annotate/AnnotateRules.php index 26675b78..c815ed76 100644 --- a/src/Annotate/AnnotateRules.php +++ b/src/Annotate/AnnotateRules.php @@ -19,7 +19,7 @@ class AnnotateRules * * @var array */ - protected static $allowedTags = [ + protected static array $allowedTags = [ // tag name => allow multi tags 'desc' => false, 'usage' => false, diff --git a/src/Annotate/Attr/RuleArg.php b/src/Annotate/Attr/RuleArg.php index 451067b5..400fe273 100644 --- a/src/Annotate/Attr/RuleArg.php +++ b/src/Annotate/Attr/RuleArg.php @@ -21,11 +21,11 @@ class RuleArg /** * @var string */ - public $name; + public string $name; /** * @var string * @see FlagsParser::$argRules */ - public $rule; + public string $rule; } diff --git a/src/Annotate/Attr/RuleOpt.php b/src/Annotate/Attr/RuleOpt.php index bd731fbb..6d40cac6 100644 --- a/src/Annotate/Attr/RuleOpt.php +++ b/src/Annotate/Attr/RuleOpt.php @@ -21,11 +21,11 @@ class RuleOpt /** * @var string */ - public $name; + public string $name; /** * @var string * @see FlagsParser::$optRules */ - public $rule; + public string $rule; } diff --git a/src/Annotate/DocblockRules.php b/src/Annotate/DocblockRules.php index 1b51be57..7d56f10b 100644 --- a/src/Annotate/DocblockRules.php +++ b/src/Annotate/DocblockRules.php @@ -29,7 +29,7 @@ class DocblockRules * * @var array */ - protected static $allowedTags = [ + protected static array $allowedTags = [ // tag name => multi line align 'desc' => false, 'usage' => false, @@ -46,17 +46,17 @@ class DocblockRules * @see $allowedTags for keys * @psalm-var array */ - private $docTags; + private array $docTags; /** * @var array */ - private $argRules = []; + private array $argRules = []; /** * @var array */ - private $optRules = []; + private array $optRules = []; /** * @param string $doc diff --git a/src/Application.php b/src/Application.php index b8a55e6c..629abed7 100644 --- a/src/Application.php +++ b/src/Application.php @@ -11,6 +11,7 @@ use Closure; use Inhere\Console\Contract\ApplicationInterface; +use Inhere\Console\Contract\CommandInterface; use Inhere\Console\Contract\ControllerInterface; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; @@ -61,7 +62,7 @@ public function __construct(array $config = [], Input $input = null, Output $out /** * {@inheritdoc} */ - public function controller(string $name, $class = null, array $config = []): ApplicationInterface + public function controller(string $name, ControllerInterface|string $class = null, array $config = []): ApplicationInterface { $this->logf(Console::VERB_CRAZY, 'register group controller: %s', $name); $this->router->addGroup($name, $class, $config); @@ -79,7 +80,7 @@ public function controller(string $name, $class = null, array $config = []): App * @return Application|Contract\ApplicationInterface * @see controller() */ - public function addGroup(string $name, $class = null, array $config = []): ApplicationInterface + public function addGroup(string $name, ControllerInterface|string $class = null, array $config = []): ApplicationInterface { return $this->controller($name, $class, $config); } @@ -92,7 +93,7 @@ public function addGroup(string $name, $class = null, array $config = []): Appli * @return Application|Contract\ApplicationInterface * @see controller() */ - public function addController(string $name, $class = null, array $config = []): ApplicationInterface + public function addController(string $name, ControllerInterface|string $class = null, array $config = []): ApplicationInterface { return $this->controller($name, $class, $config); } @@ -131,7 +132,7 @@ public function addControllers(array $controllers): void /** * {@inheritdoc} */ - public function command(string $name, $handler = null, array $config = []) + public function command(string $name, string|Closure|CommandInterface $handler = null, array $config = []): static { $this->logf(Console::VERB_CRAZY, 'register alone command: %s', $name); $this->router->addCommand($name, $handler, $config); @@ -143,13 +144,13 @@ public function command(string $name, $handler = null, array $config = []) * add command * * @param string $name - * @param null|mixed $handler + * @param mixed|null $handler * @param array $config * * @return Application * @see command() */ - public function addCommand(string $name, $handler = null, array $config = []): self + public function addCommand(string $name, mixed $handler = null, array $config = []): self { return $this->command($name, $handler, $config); } @@ -231,7 +232,7 @@ protected function getFileFilter(): callable $name = $f->getFilename(); // Skip hidden files and directories. - if (strpos($name, '.') === 0) { + if (str_starts_with($name, '.')) { return false; } @@ -241,7 +242,7 @@ protected function getFileFilter(): callable } // php file - return $f->isFile() && substr($name, -4) === '.php'; + return $f->isFile() && str_ends_with($name, '.php'); }; } @@ -253,10 +254,10 @@ protected function getFileFilter(): callable * @param string $name command name or command ID or command path. * @param array $args * - * @return int|mixed + * @return mixed * @throws Throwable */ - public function dispatch(string $name, array $args = []) + public function dispatch(string $name, array $args = []): mixed { if (!$name = trim($name)) { throw new InvalidArgumentException('cannot dispatch an empty command'); @@ -320,7 +321,7 @@ public function dispatch(string $name, array $args = []) * @return mixed * @throws Throwable */ - protected function runCommand(array $info, array $options, array $args) + protected function runCommand(array $info, array $options, array $args): mixed { /** @var Closure|string $handler Command class or handler func */ $handler = $info['handler']; @@ -373,7 +374,7 @@ protected function runCommand(array $info, array $options, array $args) * @return mixed * @throws Throwable */ - protected function runAction(array $info, array $options, array $args, bool $detachedRun = false) + protected function runAction(array $info, array $options, array $args, bool $detachedRun = false): mixed { $controller = $this->createController($info); $controller::setDesc($options['desc'] ?? ''); diff --git a/src/Command.php b/src/Command.php index 6036add9..1759f8cd 100644 --- a/src/Command.php +++ b/src/Command.php @@ -34,14 +34,14 @@ abstract class Command extends AbstractHandler implements CommandInterface public const METHOD = 'execute'; /** - * @var Controller + * @var Controller|null */ - protected $group; + protected ?Controller $group = null; /** - * @var Command + * @var Command|null */ - protected $parent; + protected ?Command $parent = null; protected function init(): void { @@ -140,8 +140,8 @@ public function getRealCName(): string * Get the group * * @return Controller - */ - public function getGroup() + */ + public function getGroup(): ?Controller { return $this->group; } @@ -150,7 +150,7 @@ public function getGroup() * Set the value of group * * @param Controller $group - */ + */ public function setGroup(Controller $group): void { $this->group = $group; diff --git a/src/Console.php b/src/Console.php index c697dcdd..9a456028 100644 --- a/src/Console.php +++ b/src/Console.php @@ -72,9 +72,9 @@ class Console extends Cli public const DEBUG_ENV_KEY = 'CONSOLE_DEBUG'; /** - * @var Application + * @var Application|null */ - private static $app; + private static ?Application $app; /** * @return Application @@ -126,7 +126,7 @@ public static function newApp( /** * @var int */ - public static $traceIndex = 1; + public static int $traceIndex = 1; /** * @param int $level @@ -171,7 +171,7 @@ public static function log(int $level, string $msg, array $data = [], array $opt $userOpts = []; $datetime = date('Y/m/d H:i:s'); foreach ($opts as $n => $v) { - if (is_numeric($n) || strpos($n, '_') === 0) { + if (is_numeric($n) || str_starts_with($n, '_')) { $userOpts[] = "[$v]"; } else { $userOpts[] = "[$n:$v]"; diff --git a/src/Contract/ApplicationInterface.php b/src/Contract/ApplicationInterface.php index ee3903e3..7c4b40c9 100644 --- a/src/Contract/ApplicationInterface.php +++ b/src/Contract/ApplicationInterface.php @@ -35,7 +35,7 @@ interface ApplicationInterface * * @return int|mixed */ - public function run(bool $exit = true); + public function run(bool $exit = true): mixed; /** * Dispatch input command, exec found command handler. @@ -48,20 +48,18 @@ public function run(bool $exit = true); * * @return int|mixed */ - public function dispatch(string $name, array $args = []); + public function dispatch(string $name, array $args = []): mixed; /** * @param int $code - * - * @return mixed */ - public function stop(int $code = 0); + public function stop(int $code = 0): void; /** * Register a app group command(by controller) * * @param string $name The controller name - * @param string|ControllerInterface $class The controller class + * @param string|ControllerInterface|null $class The controller class * @param array $config config the controller * - aliases The command aliases * - desc The description message @@ -69,13 +67,13 @@ public function stop(int $code = 0); * @return static * @throws InvalidArgumentException */ - public function controller(string $name, $class = null, array $config = []): ApplicationInterface; + public function controller(string $name, ControllerInterface|string $class = null, array $config = []): ApplicationInterface; /** * Register a app independent console command * * @param string|CommandInterface $name - * @param string|Closure|CommandInterface $handler + * @param string|Closure|CommandInterface|null $handler * @param array $config config the command * - aliases The command aliases * - desc The description message @@ -83,7 +81,7 @@ public function controller(string $name, $class = null, array $config = []): App * @return mixed * @throws InvalidArgumentException */ - public function command(string $name, $handler = null, array $config = []); + public function command(string $name, string|Closure|CommandInterface $handler = null, array $config = []): static; public function showCommandList(); diff --git a/src/Contract/CommandHandlerInterface.php b/src/Contract/CommandHandlerInterface.php index eefd7bd8..349318ed 100644 --- a/src/Contract/CommandHandlerInterface.php +++ b/src/Contract/CommandHandlerInterface.php @@ -37,7 +37,7 @@ interface CommandHandlerInterface * * @return int|mixed return int is exit code. other is command exec result. */ - public function run(array $args); + public function run(array $args): mixed; /** * @return AbstractApplication|ApplicationInterface diff --git a/src/Contract/OutputInterface.php b/src/Contract/OutputInterface.php index b70e5c0c..089a9765 100644 --- a/src/Contract/OutputInterface.php +++ b/src/Contract/OutputInterface.php @@ -13,6 +13,7 @@ * Class OutputInterface * * @package Inhere\Console\Contract + * @method error(string $string) */ interface OutputInterface { @@ -28,11 +29,23 @@ public function write(string $content): int; /** * Write a message to output with newline * - * @param string $content + * @param string|int|array|mixed $content + * @param bool $quit + * @param array $opts + * + * @return int + */ + public function writeln(mixed $content, bool $quit = false, array $opts = []): int; + + /** + * Write a message to output with format. + * + * @param string $format + * @param mixed ...$args * * @return int */ - public function writeln(string $content): int; + public function writef(string $format, ...$args): int; /** * Whether the stream is an interactive terminal diff --git a/src/Contract/RouterInterface.php b/src/Contract/RouterInterface.php index fbafc5b7..e9a66446 100644 --- a/src/Contract/RouterInterface.php +++ b/src/Contract/RouterInterface.php @@ -31,29 +31,29 @@ interface RouterInterface * Register a app group command(by controller) * * @param string $name The controller name - * @param string|ControllerInterface $class The controller class - * @param array{aliases: array, desc: string} $options The options + * @param string|ControllerInterface|null $class The controller class + * @param array{aliases: array, desc: string} $options The options * - aliases The command aliases * - desc The description message * * @return static * @throws InvalidArgumentException */ - public function addGroup(string $name, $class = null, array $options = []): self; + public function addGroup(string $name, ControllerInterface|string $class = null, array $options = []): self; /** * Register a app independent console command * * @param string|CommandInterface $name - * @param string|Closure|CommandInterface $handler - * @param array{aliases: array, desc: string} $options The options + * @param string|Closure|CommandInterface|null $handler + * @param array{aliases: array, desc: string} $options The options * - aliases The command aliases * - desc The description message * * @return static * @throws InvalidArgumentException */ - public function addCommand(string $name, $handler = null, array $options = []): self; + public function addCommand(string $name, string|Closure|CommandInterface $handler = null, array $options = []): self; /** * @param string $name The input command name diff --git a/src/Controller.php b/src/Controller.php index 7e006d56..4aa3a80a 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -61,12 +61,12 @@ abstract class Controller extends AbstractHandler implements ControllerInterface * * @var array */ - private static $commandAliases = []; + private static array $commandAliases = []; /** * @var array global options for the group command */ - protected static $globalOptions = [ + protected static array $globalOptions = [ '--show-disabled' => 'Whether display disabled commands', ]; @@ -77,38 +77,38 @@ abstract class Controller extends AbstractHandler implements ControllerInterface * * @var string */ - private $action = ''; + private string $action = ''; /** * The input group name. * * @var string */ - private $groupName = ''; + private string $groupName = ''; /** * The delimiter. eg: '/' ':' * * @var string */ - private $delimiter = ':'; + private string $delimiter = ':'; /** * @var string */ - private $defaultAction = ''; + private string $defaultAction = ''; /** * The action method name on the controller. * * @var string */ - private $actionMethod = ''; + private string $actionMethod = ''; /** * @var string */ - private $actionSuffix = self::COMMAND_SUFFIX; + private string $actionSuffix = self::COMMAND_SUFFIX; /** * Flags for all action commands @@ -123,19 +123,19 @@ abstract class Controller extends AbstractHandler implements ControllerInterface * @var FlagsParser[] * @psalm-var array */ - private $subFss = []; + private array $subFss = []; /** * @var array From disabledCommands() */ - private $disabledCommands = []; + private array $disabledCommands = []; /** * TODO ... * * @var array */ - private $attachedCommands = []; + private array $attachedCommands = []; /** * Metadata for sub-commands. such as: desc, alias @@ -150,7 +150,7 @@ abstract class Controller extends AbstractHandler implements ControllerInterface * * @var array */ - protected $commandMetas = []; + protected array $commandMetas = []; /** * Define command alias mapping. please rewrite it on sub-class. @@ -243,7 +243,7 @@ protected function afterInitFlagsParser(FlagsParser $fs): void * @return bool|int|mixed * @throws Throwable */ - public function runActionWithArgs(string $cmd, array $args) + public function runActionWithArgs(string $cmd, array $args): mixed { $args[0] = $cmd; return $this->doRun($args); @@ -259,7 +259,7 @@ protected function beforeRun(): void * @return int|mixed * @throws Throwable */ - public function doRun(array $args) + public function doRun(array $args): mixed { $name = self::getName(); if (!$args) { @@ -374,7 +374,7 @@ protected function afterAction(): void * @return mixed * @throws ReflectionException */ - final public function execute(Input $input, Output $output) + final public function execute(Input $input, Output $output): mixed { $action = $this->action; $group = static::getName(); @@ -807,7 +807,7 @@ public function setCommandMeta(string $command, array $meta): void * * @return mixed|null */ - public function getCommandMeta(string $key, $default = null, string $command = '') + public function getCommandMeta(string $key, $default = null, string $command = ''): mixed { $action = $command ?: $this->action; diff --git a/src/GlobalOption.php b/src/GlobalOption.php index b6ebc100..835cff24 100644 --- a/src/GlobalOption.php +++ b/src/GlobalOption.php @@ -49,7 +49,7 @@ class GlobalOption * @var array * @psalm-var array */ - private static $options = [ + private static array $options = [ 'debug' => [ 'type' => FlagType::INT, 'desc' => 'Setting the runtime log debug level, quiet 0 - crazy 5', @@ -93,7 +93,7 @@ class GlobalOption /** * @var array built-in options for the alone command */ - protected static $aloneOptions = [ + protected static array $aloneOptions = [ // '--help' => 'bool;Display this help message;;;h', // '--show-disabled' => 'string;Whether display disabled commands', ]; @@ -103,7 +103,7 @@ class GlobalOption /** * @var array built-in options for the group command */ - protected static $groupOptions = [ + protected static array $groupOptions = [ // '--help' => 'bool;Display this help message;;;h', self::SHOW_DISABLED => [ 'hidden' => true, @@ -114,7 +114,7 @@ class GlobalOption /** * @var array common options for the group/command */ - protected static $commonOptions = [ + protected static array $commonOptions = [ self::HELP => 'bool;Display command help message;;;h', ]; diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index f8b90f4b..514ac388 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -58,7 +58,7 @@ abstract class AbstractHandler implements CommandHandlerInterface * * @var string */ - protected static $name = ''; + protected static string $name = ''; /** * command/controller description message @@ -66,7 +66,7 @@ abstract class AbstractHandler implements CommandHandlerInterface * * @var string */ - protected static $desc = ''; + protected static string $desc = ''; /** * command/controller description message @@ -74,46 +74,46 @@ abstract class AbstractHandler implements CommandHandlerInterface * @var string * @deprecated please use {@see $desc} */ - protected static $description = ''; + protected static string $description = ''; /** * @var bool Whether enable coroutine. It is require swoole extension. */ - protected static $coroutine = false; + protected static bool $coroutine = false; /** * @var bool */ - private $initialized = false; + private bool $initialized = false; /** * Compatible mode run command. * * @var bool */ - protected $compatible = true; + protected bool $compatible = true; /** * @var string */ - protected $processTitle = ''; + protected string $processTitle = ''; /** * @var DataObject */ - protected $params; + protected DataObject $params; /** * The input command name. maybe is an alias name. * * @var string */ - protected $commandName = ''; + protected string $commandName = ''; /** * @var array Command options */ - protected $commandOptions = []; + protected array $commandOptions = []; /** * Whether enabled @@ -293,7 +293,7 @@ protected function afterInitFlagsParser(FlagsParser $fs): void * @return bool|int|mixed * @throws Throwable */ - public function run(array $args) + public function run(array $args): mixed { $name = self::getName(); @@ -329,7 +329,7 @@ public function run(array $args) * * @return int|mixed */ - protected function doRun(array $args) + protected function doRun(array $args): mixed { if (isset($args[0])) { $first = $args[0]; @@ -411,7 +411,7 @@ protected function beforeExecute(): bool * @param Input $input * @param Output $output * - * @return int|mixed + * @return void|mixed */ abstract protected function execute(Input $input, Output $output); @@ -662,7 +662,7 @@ public static function isCoroutine(): bool /** * @param bool|mixed $coroutine */ - public static function setCoroutine($coroutine): void + public static function setCoroutine(mixed $coroutine): void { static::$coroutine = (bool)$coroutine; } diff --git a/src/Handler/CallableCommand.php b/src/Handler/CallableCommand.php index cc85c182..e4542dfc 100644 --- a/src/Handler/CallableCommand.php +++ b/src/Handler/CallableCommand.php @@ -64,7 +64,7 @@ public function setCallable(callable $callable): self * * @return int|mixed */ - protected function execute(Input $input, Output $output) + protected function execute(Input $input, Output $output): mixed { if (!$call = $this->callable) { throw new BadMethodCallException('The callable property is empty'); diff --git a/src/Router.php b/src/Router.php index 041acab3..20e8c88e 100644 --- a/src/Router.php +++ b/src/Router.php @@ -42,14 +42,14 @@ class Router implements RouterInterface /** * @var array */ - private $blocked = ['help', 'version']; + private array $blocked = ['help', 'version']; /** * Command delimiter char. e.g dev:serve * * @var string */ - private $delimiter = ':'; // '/' ':' + private string $delimiter = ':'; // '/' ':' /** * The independent commands @@ -62,7 +62,7 @@ class Router implements RouterInterface * ] * ] */ - private $commands = []; + private array $commands = []; /** * The group commands(controller) @@ -75,7 +75,7 @@ class Router implements RouterInterface * ] * ] */ - private $controllers = []; + private array $controllers = []; /********************************************************** * register command/group methods @@ -94,7 +94,7 @@ class Router implements RouterInterface * @return Router * @throws InvalidArgumentException */ - public function addGroup(string $name, $class = null, array $options = []): RouterInterface + public function addGroup(string $name, ControllerInterface|string $class = null, array $options = []): RouterInterface { /** * @var Controller $class name is an controller class @@ -161,7 +161,7 @@ public function addGroup(string $name, $class = null, array $options = []): Rout * @return Router|RouterInterface * @throws InvalidArgumentException */ - public function addCommand(string $name, $handler = null, array $options = []): RouterInterface + public function addCommand(string $name, string|Closure|CommandInterface $handler = null, array $options = []): RouterInterface { if (!$handler && class_exists($name)) { $handler = $name; From 2c35c386c80ea034eed085de49a0a38998790218 Mon Sep 17 00:00:00 2001 From: Inhere Date: Tue, 30 Nov 2021 10:40:13 +0800 Subject: [PATCH 202/258] fix: phar package build error, add test build on ci tests --- .github/workflows/php.yml | 8 +++++++- src/BuiltIn/PharController.php | 4 ++-- src/Component/PharCompiler.php | 32 ++++++++++++++++++-------------- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 8bd00c57..c99dccaf 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -53,4 +53,10 @@ jobs: # Docs: https://getcomposer.org/doc/articles/scripts.md - name: Run unit tests - run: phpunit -v --debug + run: | + phpunit -v --debug + + - name: Test build PHAR + run: | + php -d phar.readonly=0 examples/app phar pack -o=myapp.phar + php myapp.phar -h \ No newline at end of file diff --git a/src/BuiltIn/PharController.php b/src/BuiltIn/PharController.php index 9b1b46a4..a7732bfe 100644 --- a/src/BuiltIn/PharController.php +++ b/src/BuiltIn/PharController.php @@ -39,9 +39,9 @@ class PharController extends Controller protected static string $description = 'Pack a project directory to phar or unpack phar to directory'; /** - * @var Closure + * @var Closure|null */ - private Closure $compilerConfiger; + private ?Closure $compilerConfiger = null; /** * @var string diff --git a/src/Component/PharCompiler.php b/src/Component/PharCompiler.php index 489c534a..5d7947ff 100644 --- a/src/Component/PharCompiler.php +++ b/src/Component/PharCompiler.php @@ -26,6 +26,7 @@ use SplQueue; use Toolkit\FsUtil\File; use Toolkit\Sys\Sys; +use Traversable; use UnexpectedValueException; use function array_merge; use function basename; @@ -89,10 +90,12 @@ class PharCompiler private $key; /** @var int */ - private int $signatureType; + private int $signatureType = Phar::SHA256; /** - * @var int compress Mode @see \Phar::NONE, \Phar::GZ, \Phar::BZ2 + * compress Mode @see \Phar::NONE, \Phar::GZ, \Phar::BZ2 + * + * @var int */ private int $compressMode = 0; @@ -154,9 +157,9 @@ class PharCompiler private array $events = []; /** - * @var Closure Maybe you not want strip all files. + * @var Closure|null Maybe you not want strip all files. */ - private Closure $stripFilter; + private ?Closure $stripFilter = null; /** * @var bool Whether strip comments @@ -181,9 +184,9 @@ class PharCompiler private string $lastVersion = ''; /** - * @var DateTime + * @var DateTime|null */ - private DateTime $versionDate; + private ?DateTime $versionDate = null; /** * 记录上面三个信息的文件, 相对于basePath @@ -211,22 +214,23 @@ class PharCompiler /** * @var string Phar file path. e.g '/some/path/app.phar' */ - private string $pharFile; + private string $pharFile = ''; /** * @var string Phar file name. eg 'app.phar' */ - private string $pharName; + private string $pharName = ''; /** - * @var Closure File filter + * @var Closure|null File filter */ - private Closure $fileFilter; + private ?Closure $fileFilter = null; /** - * @var array|Iterator The modifies files list. if not empty, will skip find dirs. + * The modifies files list. if not empty, will skip find dirs. + * @var array|Traversable */ - private array|Iterator $modifies; + private array|Traversable $modifies; /** * @var SplQueue @@ -430,11 +434,11 @@ public function in(array|string $dirs): self } /** - * @param Iterator|array $modifies + * @param Traversable|array $modifies * * @return PharCompiler */ - public function setModifies(Iterator|array $modifies): self + public function setModifies(Traversable|array $modifies): self { $this->modifies = $modifies; return $this; From ee9ce7f08ffeeda7ca9f9087cc50443883bf9b2f Mon Sep 17 00:00:00 2001 From: Inhere Date: Sun, 5 Dec 2021 15:54:39 +0800 Subject: [PATCH 203/258] breaking: update some toolkit deps to 2.0 --- composer.json | 10 +++++----- examples/Controller/ShowController.php | 2 +- src/AbstractApplication.php | 4 ++-- src/Controller.php | 3 ++- src/Util/Show.php | 13 ++----------- 5 files changed, 12 insertions(+), 20 deletions(-) diff --git a/composer.json b/composer.json index 365f2be1..23e28730 100644 --- a/composer.json +++ b/composer.json @@ -22,12 +22,12 @@ } ], "require": { - "php": ">=8.0", - "toolkit/cli-utils": "~1.0", - "toolkit/fsutil": "~1.0", + "php": ">=8.0.1", + "toolkit/cli-utils": "~2.0", + "toolkit/fsutil": "~2.0", "toolkit/pflag": "~1.0", - "toolkit/stdlib": "^1.0", - "toolkit/sys-utils": "~1.0" + "toolkit/stdlib": "^2.0", + "toolkit/sys-utils": "~2.0" }, "require-dev": { "phpunit/phpunit": "^9.1" diff --git a/examples/Controller/ShowController.php b/examples/Controller/ShowController.php index f5b0b924..3ea1a70c 100644 --- a/examples/Controller/ShowController.php +++ b/examples/Controller/ShowController.php @@ -17,7 +17,7 @@ use Inhere\Console\Util\Show; use ReflectionException; use Toolkit\Cli\Color; -use Toolkit\Cli\Highlighter; +use Toolkit\Cli\Util\Highlighter; use Toolkit\PFlag\FlagsParser; use function file_get_contents; diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 02323dc3..efef96f2 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -27,9 +27,9 @@ use InvalidArgumentException; use JetBrains\PhpStorm\NoReturn; use Throwable; -use Toolkit\Cli\Helper\FlagHelper; use Toolkit\Cli\Style; use Toolkit\Cli\Util\LineParser; +use Toolkit\PFlag\FlagUtil; use Toolkit\PFlag\SFlags; use Toolkit\Stdlib\Helper\DataHelper; use Toolkit\Stdlib\Helper\PhpHelper; @@ -485,7 +485,7 @@ protected function handleGlobalCommand(string $command): bool case 'help': $cmd = ''; $args = $this->flags->getRawArgs(); - if ($args && FlagHelper::isValidName($args[0])) { + if ($args && FlagUtil::isValidName($args[0])) { $cmd = $args[0]; } diff --git a/src/Controller.php b/src/Controller.php index 4aa3a80a..559f0761 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -26,6 +26,7 @@ use Throwable; use Toolkit\Cli\Helper\FlagHelper; use Toolkit\PFlag\FlagsParser; +use Toolkit\PFlag\FlagUtil; use Toolkit\PFlag\SFlags; use Toolkit\Stdlib\Obj\ObjectHelper; use Toolkit\Stdlib\Str; @@ -272,7 +273,7 @@ public function doRun(array $args): mixed } } else { $first = $args[0]; - if (!FlagHelper::isValidName($first)) { + if (!FlagUtil::isValidName($first)) { $this->debugf('not input subcommand, display help for the group: %s', $name); return $this->showHelp(); } diff --git a/src/Util/Show.php b/src/Util/Show.php index 9cd1a067..73cb3d65 100644 --- a/src/Util/Show.php +++ b/src/Util/Show.php @@ -646,21 +646,12 @@ public static function writef(string $format, ...$args): int * * @param array|string $messages Output message * @param boolean $nl True 会添加换行符, False 原样输出,不添加换行符 - * @param bool|int $quit If is int, setting it is exit code. 'True' translate as code 0 and exit, 'False' will not exit. + * @param bool $quit If is int, setting it is exit code. 'True' translate as code 0 and exit, 'False' will not exit. * @param array{color:bool,stream:resource,flush:bool,quit:bool,quitCode:int} $opts Some options for write - * refer: - * [ - * 'color' => bool, // whether render color, default is: True. - * 'stream' => resource, // the stream resource, default is: STDOUT - * 'flush' => bool, // flush the stream data, default is: True - * ] - * * @return int */ public static function write(array|string $messages, bool $nl = true, bool $quit = false, array $opts = []): int { - $qCode = $opts['quitCode'] ?? 0; - return Console::write($messages, $nl, $quit, $opts); } @@ -704,7 +695,7 @@ public static function writeln(array|string $message, bool $quit = false, array */ public static function colored(array|string $message, string $style = 'info', bool $nl = true, array $opts = []): int { - $quit = isset($opts['quit']) ? (bool)$opts['quit'] : false; + $quit = isset($opts['quit']) && $opts['quit']; if (is_array($message)) { $message = implode($nl ? PHP_EOL : '', $message); From be119333a81cf3db12bb497293915b75efe81f48 Mon Sep 17 00:00:00 2001 From: Inhere Date: Mon, 6 Dec 2021 00:30:33 +0800 Subject: [PATCH 204/258] up: remove some util methods --- src/Util/FormatUtil.php | 39 +++++++-------------------------------- src/Util/Helper.php | 1 - src/Util/ProgressBar.php | 6 +++--- 3 files changed, 10 insertions(+), 36 deletions(-) diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index 159728a1..de11ac65 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -11,7 +11,6 @@ use Toolkit\Cli\ColorTag; use Toolkit\Stdlib\Arr\ArrayHelper; -use Toolkit\Stdlib\Helper\Format; use Toolkit\Stdlib\Helper\JsonHelper; use Toolkit\Stdlib\Str; use Toolkit\Sys\Sys; @@ -25,6 +24,7 @@ use function is_int; use function is_numeric; use function is_scalar; +use function preg_match; use function rtrim; use function str_repeat; use function str_replace; @@ -136,9 +136,12 @@ public static function wrapText(string $text, int $indent = 0, int $width = 0): } /** + * Align command option names. + * * @param array $options * * @return array + * @deprecated Please use {@see FlagUtil::alignOptions()} */ public static function alignOptions(array $options): array { @@ -146,9 +149,9 @@ public static function alignOptions(array $options): array return []; } - // e.g '-h, --help' - $hasShort = (bool)strpos(implode('', array_keys($options)), ','); - if (!$hasShort) { + // check has short option. e.g '-h, --help' + $nameString = implode('|', array_keys($options)); + if (preg_match('/\|-\w/', $nameString) !== 1) { return $options; } @@ -171,34 +174,6 @@ public static function alignOptions(array $options): array return $formatted; } - /** - * ``` - * FormatUtil::memoryUsage(memory_get_usage(true)); - * ``` - * - * @param float|int $memory - * - * @return string - * @deprecated use Format::memory($secs); - */ - public static function memoryUsage(float|int $memory): string - { - return Format::memory($memory); - } - - /** - * format timestamp to how long ago - * - * @param int $secs - * - * @return string - * @deprecated use Format::howLongAgo($secs); - */ - public static function howLongAgo(int $secs): string - { - return Format::howLongAgo($secs); - } - /** * Splice array * diff --git a/src/Util/Helper.php b/src/Util/Helper.php index 4c009e00..216a3f91 100644 --- a/src/Util/Helper.php +++ b/src/Util/Helper.php @@ -13,7 +13,6 @@ use Inhere\Console\Concern\RuntimeProfileTrait; use Inhere\Console\ConsoleConst; use InvalidArgumentException; -use Iterator; use RecursiveCallbackFilterIterator; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; diff --git a/src/Util/ProgressBar.php b/src/Util/ProgressBar.php index bca6f6b0..ec29ee3a 100644 --- a/src/Util/ProgressBar.php +++ b/src/Util/ProgressBar.php @@ -595,7 +595,7 @@ private static function loadDefaultParsers(): array return $display; }, 'elapsed' => static function (self $bar) { - return FormatUtil::howLongAgo(time() - $bar->getStartTime()); + return Format::howLongAgo(time() - $bar->getStartTime()); }, 'remaining' => static function (self $bar) { if (!$bar->getMaxSteps()) { @@ -609,7 +609,7 @@ private static function loadDefaultParsers(): array $remaining = (int)round((time() - $bar->getStartTime()) / $progress * ($bar->getMaxSteps() - $progress)); } - return FormatUtil::howLongAgo($remaining); + return Format::howLongAgo($remaining); }, 'estimated' => static function (self $bar) { if (!$bar->getMaxSteps()) { @@ -623,7 +623,7 @@ private static function loadDefaultParsers(): array $estimated = (int)round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps()); } - return FormatUtil::howLongAgo($estimated); + return Format::howLongAgo($estimated); }, 'memory' => static function () { return Format::memory(memory_get_usage(true)); From ae9e9f615b2c5de2e3b05f61b7d09c7bcb85fc54 Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 9 Dec 2021 14:41:19 +0800 Subject: [PATCH 205/258] up: update dep toolkit/pflag to 2.0 --- composer.json | 4 ++-- src/AbstractApplication.php | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 23e28730..a1f57a53 100644 --- a/composer.json +++ b/composer.json @@ -22,10 +22,10 @@ } ], "require": { - "php": ">=8.0.1", + "php": ">8.0.1", "toolkit/cli-utils": "~2.0", "toolkit/fsutil": "~2.0", - "toolkit/pflag": "~1.0", + "toolkit/pflag": "~2.0", "toolkit/stdlib": "^2.0", "toolkit/sys-utils": "~2.0" }, diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index efef96f2..a415c388 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -261,8 +261,14 @@ protected function beforeRun(): bool $firstArg = array_shift($remainArgs); if (!Helper::isValidCmdPath($firstArg)) { - $this->output->liteError('input an invalid command name'); - $this->showCommandList(); + $evtName = ConsoleEvent::ON_NOT_FOUND; + if (true === $this->fire($evtName, $firstArg, $this)) { + $this->debugf('user custom handle the invalid command: %s, event: %s', $firstArg, $evtName); + } else { + $this->output->liteError("input an invalid command name: $firstArg"); + $this->showCommandList(); + } + return false; } From 07a1e51e10a43b84f9aeecafbf5c6cccd0efa139 Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 16 Dec 2021 10:35:27 +0800 Subject: [PATCH 206/258] refacting: update all use cli color tag class --- src/Component/Formatter/JSONPretty.php | 35 +++++++++++++++++++++++--- src/Component/Formatter/Padding.php | 2 +- src/Component/Formatter/SingleList.php | 2 +- src/Component/Formatter/Table.php | 2 +- src/Component/Formatter/Title.php | 2 +- src/Component/Symbol/ArtFont.php | 2 +- src/Concern/ApplicationHelpTrait.php | 2 +- src/Concern/ControllerHelpTrait.php | 2 +- src/Concern/FormatOutputAwareTrait.php | 8 +++--- src/Console.php | 2 +- src/Util/FormatUtil.php | 2 +- src/Util/PhpDevServe.php | 1 - src/Util/Show.php | 2 +- 13 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/Component/Formatter/JSONPretty.php b/src/Component/Formatter/JSONPretty.php index 2b22021c..460f438b 100644 --- a/src/Component/Formatter/JSONPretty.php +++ b/src/Component/Formatter/JSONPretty.php @@ -65,11 +65,40 @@ class JSONPretty extends AbstractObj * @param string $json * * @return string - * @throws JsonException + */ + public static function prettyJSON(string $json): string + { + return (new self)->render($json); + } + + /** + * @param mixed $data + * + * @return string + */ + public static function pretty(mixed $data): string + { + return (new self)->renderData($data); + } + + /** + * @param mixed $data + * + * @return string + */ + public static function prettyData(mixed $data): string + { + return (new self)->renderData($data); + } + + /** + * @param string $json + * + * @return string */ public function render(string $json): string { - $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR); + $data = JsonHelper::decode($json, true); return $this->renderData($data); } @@ -79,7 +108,7 @@ public function render(string $json): string * * @return string */ - public function renderData(array $data): string + public function renderData(mixed $data): string { $buf = StrBuffer::new(); $json = JsonHelper::prettyJSON($data); diff --git a/src/Component/Formatter/Padding.php b/src/Component/Formatter/Padding.php index 074b2ee3..879326ea 100644 --- a/src/Component/Formatter/Padding.php +++ b/src/Component/Formatter/Padding.php @@ -11,7 +11,7 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; -use Toolkit\Cli\ColorTag; +use Toolkit\Cli\Color\ColorTag; use Toolkit\Stdlib\Arr\ArrayHelper; use Toolkit\Stdlib\Str; use function array_merge; diff --git a/src/Component/Formatter/SingleList.php b/src/Component/Formatter/SingleList.php index 8e729323..42fe67a4 100644 --- a/src/Component/Formatter/SingleList.php +++ b/src/Component/Formatter/SingleList.php @@ -12,7 +12,7 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; use Inhere\Console\Util\FormatUtil; -use Toolkit\Cli\ColorTag; +use Toolkit\Cli\Color\ColorTag; use Toolkit\Stdlib\Str; use function array_merge; use function trim; diff --git a/src/Component/Formatter/Table.php b/src/Component/Formatter/Table.php index 0cb01f61..70df1b33 100644 --- a/src/Component/Formatter/Table.php +++ b/src/Component/Formatter/Table.php @@ -11,7 +11,7 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; -use Toolkit\Cli\ColorTag; +use Toolkit\Cli\Color\ColorTag; use Toolkit\Stdlib\Str; use Toolkit\Stdlib\Str\StrBuffer; use function array_keys; diff --git a/src/Component/Formatter/Title.php b/src/Component/Formatter/Title.php index dc6b86f5..084fedc9 100644 --- a/src/Component/Formatter/Title.php +++ b/src/Component/Formatter/Title.php @@ -11,7 +11,7 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; -use Toolkit\Cli\ColorTag; +use Toolkit\Cli\Color\ColorTag; use Toolkit\Stdlib\Str; use Toolkit\Sys\Sys; use function array_merge; diff --git a/src/Component/Symbol/ArtFont.php b/src/Component/Symbol/ArtFont.php index 8c59a13a..a4632f42 100644 --- a/src/Component/Symbol/ArtFont.php +++ b/src/Component/Symbol/ArtFont.php @@ -10,7 +10,7 @@ namespace Inhere\Console\Component\Symbol; use Inhere\Console\Console; -use Toolkit\Cli\ColorTag; +use Toolkit\Cli\Color\ColorTag; use function dirname; use function file_get_contents; use function in_array; diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index aff1dd10..bb7eb02a 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -17,7 +17,7 @@ use Inhere\Console\IO\Output; use Inhere\Console\Util\FormatUtil; use Inhere\Console\Util\Show; -use Toolkit\Cli\ColorTag; +use Toolkit\Cli\Color\ColorTag; use Toolkit\Cli\Style; use function array_merge; use function basename; diff --git a/src/Concern/ControllerHelpTrait.php b/src/Concern/ControllerHelpTrait.php index a0eb7f0d..58be9004 100644 --- a/src/Concern/ControllerHelpTrait.php +++ b/src/Concern/ControllerHelpTrait.php @@ -13,7 +13,7 @@ use Inhere\Console\GlobalOption; use Inhere\Console\Util\FormatUtil; use ReflectionClass; -use Toolkit\Cli\ColorTag; +use Toolkit\Cli\Color\ColorTag; use Toolkit\Stdlib\Str; use Toolkit\Stdlib\Util\PhpDoc; use function array_merge; diff --git a/src/Concern/FormatOutputAwareTrait.php b/src/Concern/FormatOutputAwareTrait.php index e2858ed2..75a0286e 100644 --- a/src/Concern/FormatOutputAwareTrait.php +++ b/src/Concern/FormatOutputAwareTrait.php @@ -9,13 +9,13 @@ namespace Inhere\Console\Concern; +use Inhere\Console\Component\Formatter\JSONPretty; use Inhere\Console\Console; -use Toolkit\Stdlib\Helper\JsonHelper; +use Toolkit\Stdlib\Json; use Toolkit\Stdlib\Php; use function array_merge; use function count; use function implode; -use function json_encode; use const PHP_EOL; /** @@ -133,7 +133,7 @@ public function json( bool $echo = true, int $flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ): int|string { - $string = json_encode($data, $flags); + $string = Json::encode($data, $flags); if ($echo) { return Console::write($string); @@ -152,7 +152,7 @@ public function prettyJSON(mixed $data, string $title = 'JSON:'): void Console::colored($title, 'ylw0'); } - Console::write(JsonHelper::prettyJSON($data)); + Console::write(JSONPretty::pretty($data)); } /** diff --git a/src/Console.php b/src/Console.php index 9a456028..4921093b 100644 --- a/src/Console.php +++ b/src/Console.php @@ -12,7 +12,7 @@ use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; use Toolkit\Cli\Cli; -use Toolkit\Cli\ColorTag; +use Toolkit\Cli\Color\ColorTag; use function date; use function debug_backtrace; use function implode; diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index de11ac65..737f747a 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -9,7 +9,7 @@ namespace Inhere\Console\Util; -use Toolkit\Cli\ColorTag; +use Toolkit\Cli\Color\ColorTag; use Toolkit\Stdlib\Arr\ArrayHelper; use Toolkit\Stdlib\Helper\JsonHelper; use Toolkit\Stdlib\Str; diff --git a/src/Util/PhpDevServe.php b/src/Util/PhpDevServe.php index 8629fef9..5411597b 100644 --- a/src/Util/PhpDevServe.php +++ b/src/Util/PhpDevServe.php @@ -246,7 +246,6 @@ public function getCommand(bool $checkEnv = true): string * @param bool $mustLoad * * @return bool - * @throws \JsonException */ public function loadHceFile(string $hceFile, bool $mustLoad = false): bool { diff --git a/src/Util/Show.php b/src/Util/Show.php index 73cb3d65..e4599159 100644 --- a/src/Util/Show.php +++ b/src/Util/Show.php @@ -26,7 +26,7 @@ use Inhere\Console\Console; use LogicException; use Toolkit\Cli\Cli; -use Toolkit\Cli\ColorTag; +use Toolkit\Cli\Color\ColorTag; use Toolkit\Cli\Style; use Toolkit\Stdlib\Helper\JsonHelper; use Toolkit\Stdlib\Math; From 5b887a4483bb2ba7233cd5f2060e3329c73a28d4 Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 17 Dec 2021 10:17:20 +0800 Subject: [PATCH 207/258] perf(io): update some use io class logic --- resource/deprecated/AbstractInput.php | 2 +- src/AbstractApplication.php | 16 ++++++------ src/Concern/FormatOutputAwareTrait.php | 9 ------- src/Concern/InputOutputAwareTrait.php | 33 +++++++++++------------- src/Contract/OutputInterface.php | 1 - src/IO/Output.php | 18 ++----------- src/Util/ProgressBar.php | 35 +++++++++++++------------- 7 files changed, 42 insertions(+), 72 deletions(-) diff --git a/resource/deprecated/AbstractInput.php b/resource/deprecated/AbstractInput.php index 47d65de3..9e664625 100644 --- a/resource/deprecated/AbstractInput.php +++ b/resource/deprecated/AbstractInput.php @@ -27,7 +27,7 @@ * * @package Inhere\Console\IO */ -abstract class AbstractInput implements InputInterface +abstract class AbstractInput // implements InputInterface { use InputArgumentsTrait, InputOptionsTrait; diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index a415c388..36757f51 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -18,8 +18,6 @@ use Inhere\Console\Concern\StyledOutputAwareTrait; use Inhere\Console\Contract\ApplicationInterface; use Inhere\Console\Contract\ErrorHandlerInterface; -use Inhere\Console\Contract\InputInterface; -use Inhere\Console\Contract\OutputInterface; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; use Inhere\Console\Util\Helper; @@ -370,10 +368,10 @@ public function runWithArgs(array $args): mixed } /** - * @param InputInterface $input - * @param OutputInterface $output + * @param Input $input + * @param Output $output */ - public function runWithIO(InputInterface $input, OutputInterface $output): void + public function runWithIO(Input $input, Output $output): void { $app = $this->copy(); $app->setInput($input); @@ -384,13 +382,13 @@ public function runWithIO(InputInterface $input, OutputInterface $output): void } /** - * @param string $command - * @param InputInterface $input - * @param OutputInterface $output + * @param string $command + * @param Input $input + * @param Output $output * * @return mixed */ - public function subRun(string $command, InputInterface $input, OutputInterface $output): mixed + public function subRun(string $command, Input $input, Output $output): mixed { $app = $this->copy(); $app->setInput($input); diff --git a/src/Concern/FormatOutputAwareTrait.php b/src/Concern/FormatOutputAwareTrait.php index 75a0286e..08652f59 100644 --- a/src/Concern/FormatOutputAwareTrait.php +++ b/src/Concern/FormatOutputAwareTrait.php @@ -39,15 +39,6 @@ public function write($messages, $nl = true, $quit = false, array $opts = []): i ], $opts)); } - // public function print(...$args): void - // { - // if (count($args) > 1) { - // echo ; - // } - // - // - // } - /** * @param ...$args */ diff --git a/src/Concern/InputOutputAwareTrait.php b/src/Concern/InputOutputAwareTrait.php index 9501b007..f5134f69 100644 --- a/src/Concern/InputOutputAwareTrait.php +++ b/src/Concern/InputOutputAwareTrait.php @@ -10,11 +10,8 @@ namespace Inhere\Console\Concern; use Inhere\Console\IO\Input; -use Inhere\Console\Contract\InputInterface; use Inhere\Console\IO\Output; -use Inhere\Console\Contract\OutputInterface; use Toolkit\PFlag\FlagsParser; -use Toolkit\PFlag\SFlags; /** * Class InputOutputAwareTrait @@ -29,14 +26,14 @@ trait InputOutputAwareTrait protected ?FlagsParser $flags; /** - * @var InputInterface|null + * @var Input|null */ - protected ?InputInterface $input; + protected ?Input $input; /** - * @var OutputInterface|null + * @var Output|null */ - protected ?OutputInterface $output; + protected ?Output $output; /** * @return string @@ -64,7 +61,7 @@ public function getScriptName(): string /** * @param string $question - * @param bool $nl + * @param bool $nl * * @return string */ @@ -94,39 +91,39 @@ public function writeln(mixed $message): int } /** - * @return Input|InputInterface + * @return Input */ - public function getInput(): InputInterface + public function getInput(): Input { return $this->input; } /** - * @param InputInterface $input + * @param Input $input */ - public function setInput(InputInterface $input): void + public function setInput(Input $input): void { $this->input = $input; } /** - * @return Output|OutputInterface + * @return Output */ - public function getOutput(): OutputInterface + public function getOutput(): Output { return $this->output; } /** - * @param Output|OutputInterface $output + * @param Output $output */ - public function setOutput(OutputInterface $output): void + public function setOutput(Output $output): void { $this->output = $output; } /** - * @return FlagsParser|SFlags + * @return FlagsParser */ public function getFlags(): FlagsParser { @@ -134,7 +131,7 @@ public function getFlags(): FlagsParser } /** - * @param FlagsParser|SFlags $flags + * @param FlagsParser $flags */ public function setFlags(FlagsParser $flags): void { diff --git a/src/Contract/OutputInterface.php b/src/Contract/OutputInterface.php index 089a9765..19846b0e 100644 --- a/src/Contract/OutputInterface.php +++ b/src/Contract/OutputInterface.php @@ -13,7 +13,6 @@ * Class OutputInterface * * @package Inhere\Console\Contract - * @method error(string $string) */ interface OutputInterface { diff --git a/src/IO/Output.php b/src/IO/Output.php index 7d4da49b..87b5bffe 100644 --- a/src/IO/Output.php +++ b/src/IO/Output.php @@ -31,22 +31,12 @@ class Output extends StreamOutput */ protected $errorStream; - /** - * 控制台窗口(字体/背景)颜色添加处理 - * window colors - * - * @var Style|null - */ - protected ?Style $style = null; - /** * Output constructor. */ public function __construct() { parent::__construct(Cli::getOutputStream()); - - $this->getStyle(); } /*************************************************************************** @@ -136,7 +126,7 @@ public function readln(string $question = '', bool $nl = false): string */ public function stderr(string $text = '', bool $nl = true): int { - return Console::write($text, $nl, [ + return Console::write($text, $nl, false, [ 'steam' => $this->errorStream, ]); } @@ -150,11 +140,7 @@ public function stderr(string $text = '', bool $nl = true): int */ public function getStyle(): Style { - if (!$this->style) { - $this->style = Style::instance(); - } - - return $this->style; + return Style::global(); } /** diff --git a/src/Util/ProgressBar.php b/src/Util/ProgressBar.php index ec29ee3a..fa9fb2be 100644 --- a/src/Util/ProgressBar.php +++ b/src/Util/ProgressBar.php @@ -11,7 +11,6 @@ use Closure; use Inhere\Console\IO\Output; -use Inhere\Console\Contract\OutputInterface; use LogicException; use RuntimeException; use Toolkit\Stdlib\Helper\Format; @@ -24,7 +23,7 @@ * @package Inhere\Console\Util * @form \Symfony\Component\Console\Helper\ProgressBar * - * ``` + * ```bash * 1 [->--------------------------] * 3 [■■■>------------------------] * 25/50 [==============>-------------] 50% @@ -121,9 +120,9 @@ class ProgressBar private bool $firstRun = true; /** - * @var OutputInterface + * @var Output */ - private Output|OutputInterface $output; + private Output $output; /** * messages @@ -143,21 +142,21 @@ class ProgressBar public const DEFAULT_FORMAT = '[{@bar}] {@percent:3s}%({@current}/{@max}) {@elapsed:6s}/{@estimated:-6s} {@memory:6s}'; /** - * @param OutputInterface|null $output - * @param int $maxSteps + * @param Output|null $output + * @param int $maxSteps * * @return ProgressBar */ - public static function create(OutputInterface $output = null, int $maxSteps = 0): ProgressBar + public static function create(Output $output = null, int $maxSteps = 0): ProgressBar { return new self($output, $maxSteps); } /** - * @param OutputInterface|null $output + * @param Output|null $output * @param int $maxSteps */ - public function __construct(OutputInterface $output = null, int $maxSteps = 0) + public function __construct(Output $output = null, int $maxSteps = 0) { $this->output = $output ?: new Output; @@ -168,11 +167,11 @@ public function __construct(OutputInterface $output = null, int $maxSteps = 0) /** * 开始 * - * @param null $maxSteps + * @param int|null $maxSteps * * @throws LogicException */ - public function start($maxSteps = null): void + public function start(int $maxSteps = null): void { if ($this->started) { throw new LogicException('Progress bar already started.'); @@ -260,7 +259,7 @@ public function finish(): void */ public function display(): void { - if (null === $this->format) { + if (!$this->format) { $this->format = self::DEFAULT_FORMAT; } @@ -306,10 +305,10 @@ public function render(string $text): void } /** - * @return mixed + * @return string * @throws RuntimeException */ - protected function buildLine(): mixed + protected function buildLine(): string { // $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; return preg_replace_callback('/{@([\w]+)(?:\:([\w-]+))?}/i', function ($matches) { @@ -543,17 +542,17 @@ public function getPercent(): float } /** - * @return mixed + * @return int */ - public function getStartTime(): mixed + public function getStartTime(): int { return $this->startTime; } /** - * @return mixed + * @return int */ - public function getFinishTime(): mixed + public function getFinishTime(): int { return $this->finishTime; } From c1d43564083420aff9850461044a62e86bca1338 Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 17 Dec 2021 10:36:40 +0800 Subject: [PATCH 208/258] :necktie: refactor(router): move router class to component dir --- src/AbstractApplication.php | 1 + src/Application.php | 14 +++++++------- src/{ => Component}/Router.php | 28 +++++++++++++-------------- src/Console.php | 4 ++-- src/Contract/ApplicationInterface.php | 12 ++++++------ src/Contract/RouterInterface.php | 23 ++++++++++++---------- test/ApplicationTest.php | 2 +- 7 files changed, 43 insertions(+), 41 deletions(-) rename src/{ => Component}/Router.php (94%) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 36757f51..adf959d6 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -11,6 +11,7 @@ use ErrorException; use Inhere\Console\Component\ErrorHandler; +use Inhere\Console\Component\Router; use Inhere\Console\Component\Formatter\Title; use Inhere\Console\Concern\ApplicationHelpTrait; use Inhere\Console\Concern\InputOutputAwareTrait; diff --git a/src/Application.php b/src/Application.php index 629abed7..73c9ea83 100644 --- a/src/Application.php +++ b/src/Application.php @@ -10,7 +10,7 @@ namespace Inhere\Console; use Closure; -use Inhere\Console\Contract\ApplicationInterface; +use Inhere\Console\Component\Router; use Inhere\Console\Contract\CommandInterface; use Inhere\Console\Contract\ControllerInterface; use Inhere\Console\IO\Input; @@ -62,7 +62,7 @@ public function __construct(array $config = [], Input $input = null, Output $out /** * {@inheritdoc} */ - public function controller(string $name, ControllerInterface|string $class = null, array $config = []): ApplicationInterface + public function controller(string $name, ControllerInterface|string $class = null, array $config = []): static { $this->logf(Console::VERB_CRAZY, 'register group controller: %s', $name); $this->router->addGroup($name, $class, $config); @@ -73,14 +73,14 @@ public function controller(string $name, ControllerInterface|string $class = nul /** * Add group/controller * - * @param string $name + * @param string|class-string $name * @param string|ControllerInterface|null $class The controller class * @param array $config * - * @return Application|Contract\ApplicationInterface + * @return static * @see controller() */ - public function addGroup(string $name, ControllerInterface|string $class = null, array $config = []): ApplicationInterface + public function addGroup(string $name, ControllerInterface|string $class = null, array $config = []): static { return $this->controller($name, $class, $config); } @@ -90,10 +90,10 @@ public function addGroup(string $name, ControllerInterface|string $class = null, * @param string|ControllerInterface|null $class The controller class * @param array $config * - * @return Application|Contract\ApplicationInterface + * @return $this * @see controller() */ - public function addController(string $name, ControllerInterface|string $class = null, array $config = []): ApplicationInterface + public function addController(string $name, ControllerInterface|string $class = null, array $config = []): static { return $this->controller($name, $class, $config); } diff --git a/src/Router.php b/src/Component/Router.php similarity index 94% rename from src/Router.php rename to src/Component/Router.php index 20e8c88e..e4eb1f69 100644 --- a/src/Router.php +++ b/src/Component/Router.php @@ -7,12 +7,14 @@ * @license https://github.com/inhere/php-console/blob/master/LICENSE */ -namespace Inhere\Console; +namespace Inhere\Console\Component; use Closure; +use Inhere\Console\Command; use Inhere\Console\Contract\CommandInterface; use Inhere\Console\Contract\ControllerInterface; use Inhere\Console\Contract\RouterInterface; +use Inhere\Console\Controller; use Inhere\Console\Util\Helper; use InvalidArgumentException; use Toolkit\Stdlib\Obj\Traits\NameAliasTrait; @@ -86,15 +88,15 @@ class Router implements RouterInterface * * @param string $name The controller name * @param string|ControllerInterface|null $class The controller class - * @param array $options + * @param array{aliases: array, desc: string} $config config for group. * array: * - aliases The command aliases - * - description The description message + * - desc The description message * - * @return Router + * @return static * @throws InvalidArgumentException */ - public function addGroup(string $name, ControllerInterface|string $class = null, array $options = []): RouterInterface + public function addGroup(string $name, ControllerInterface|string $class = null, array $config = []): static { /** * @var Controller $class name is an controller class @@ -141,7 +143,7 @@ public function addGroup(string $name, ControllerInterface|string $class = null, ]; // has alias option - if (isset($options['aliases'])) { + if ($options['aliases']) { $this->setAlias($name, $options['aliases'], true); } @@ -151,17 +153,13 @@ public function addGroup(string $name, ControllerInterface|string $class = null, /** * Register a app independent console command * - * @param string|CommandInterface $name + * @param string|class-string $name * @param string|Closure|CommandInterface|null $handler - * @param array $options - * array: - * - aliases The command aliases - * - description The description message + * @param array{aliases: array, desc: string} $config * - * @return Router|RouterInterface - * @throws InvalidArgumentException + * @return static */ - public function addCommand(string $name, string|Closure|CommandInterface $handler = null, array $options = []): RouterInterface + public function addCommand(string $name, string|Closure|CommandInterface $handler = null, array $config = []): static { if (!$handler && class_exists($name)) { $handler = $name; @@ -217,7 +215,7 @@ public function addCommand(string $name, string|Closure|CommandInterface $handle ]; // has alias option - if (isset($options['aliases'])) { + if ($options['aliases']) { $this->setAlias($name, $options['aliases'], true); } diff --git a/src/Console.php b/src/Console.php index 4921093b..46db9224 100644 --- a/src/Console.php +++ b/src/Console.php @@ -13,11 +13,11 @@ use Inhere\Console\IO\Output; use Toolkit\Cli\Cli; use Toolkit\Cli\Color\ColorTag; +use Toolkit\Stdlib\Json; use function date; use function debug_backtrace; use function implode; use function is_numeric; -use function json_encode; use function sprintf; use function strpos; use function trim; @@ -179,7 +179,7 @@ public static function log(int $level, string $msg, array $data = [], array $opt } $optString = $userOpts ? ' ' . implode(' ', $userOpts) : ''; - $dataString = $data ? json_encode($data, JSON_UNESCAPED_SLASHES) : ''; + $dataString = $data ? Json::encode($data, JSON_UNESCAPED_SLASHES) : ''; $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, self::$traceIndex + 2); $position = self::formatBacktrace($backtrace, self::$traceIndex); diff --git a/src/Contract/ApplicationInterface.php b/src/Contract/ApplicationInterface.php index 7c4b40c9..ffdc126f 100644 --- a/src/Contract/ApplicationInterface.php +++ b/src/Contract/ApplicationInterface.php @@ -33,7 +33,7 @@ interface ApplicationInterface /** * @param bool $exit * - * @return int|mixed + * @return mixed */ public function run(bool $exit = true): mixed; @@ -46,7 +46,7 @@ public function run(bool $exit = true): mixed; * - 'group action' * @param array $args * - * @return int|mixed + * @return mixed */ public function dispatch(string $name, array $args = []): mixed; @@ -60,21 +60,21 @@ public function stop(int $code = 0): void; * * @param string $name The controller name * @param string|ControllerInterface|null $class The controller class - * @param array $config config the controller + * @param array{desc: string, aliases: array} $config config the controller. * - aliases The command aliases * - desc The description message * * @return static * @throws InvalidArgumentException */ - public function controller(string $name, ControllerInterface|string $class = null, array $config = []): ApplicationInterface; + public function controller(string $name, ControllerInterface|string $class = null, array $config = []): static; /** * Register a app independent console command * - * @param string|CommandInterface $name + * @param string|class-string $name * @param string|Closure|CommandInterface|null $handler - * @param array $config config the command + * @param array{desc: string, aliases: array} $config config the command. * - aliases The command aliases * - desc The description message * diff --git a/src/Contract/RouterInterface.php b/src/Contract/RouterInterface.php index e9a66446..5eb8d1da 100644 --- a/src/Contract/RouterInterface.php +++ b/src/Contract/RouterInterface.php @@ -30,36 +30,34 @@ interface RouterInterface /** * Register a app group command(by controller) * - * @param string $name The controller name + * @param string|class-string $name The controller name * @param string|ControllerInterface|null $class The controller class - * @param array{aliases: array, desc: string} $options The options + * @param array{aliases: array, desc: string} $config The options config. * - aliases The command aliases * - desc The description message * * @return static * @throws InvalidArgumentException */ - public function addGroup(string $name, ControllerInterface|string $class = null, array $options = []): self; + public function addGroup(string $name, ControllerInterface|string $class = null, array $config = []): static; /** * Register a app independent console command * - * @param string|CommandInterface $name + * @param string|class-string $name * @param string|Closure|CommandInterface|null $handler - * @param array{aliases: array, desc: string} $options The options + * @param array{aliases: array, desc: string} $config The options config. * - aliases The command aliases * - desc The description message * * @return static * @throws InvalidArgumentException */ - public function addCommand(string $name, string|Closure|CommandInterface $handler = null, array $options = []): self; + public function addCommand(string $name, string|Closure|CommandInterface $handler = null, array $config = []): static; /** - * @param string $name The input command name - * - * @return array return route info array. If not found, will return empty array. - * [ + * ```php + * return [ * type => 1, // 1 group 2 command * handler => handler class/object/func ... * options => [ @@ -67,6 +65,11 @@ public function addCommand(string $name, string|Closure|CommandInterface $handle * description => '', * ], * ] + * ``` + * + * @param string $name The input command name + * + * @return array return route info array. If not found, will return empty array. */ public function match(string $name): array; } diff --git a/test/ApplicationTest.php b/test/ApplicationTest.php index fade4fa7..7fb51edc 100644 --- a/test/ApplicationTest.php +++ b/test/ApplicationTest.php @@ -13,7 +13,7 @@ use Inhere\Console\Console; use Inhere\Console\IO\Input; use Inhere\Console\Contract\InputInterface; -use Inhere\Console\Router; +use Inhere\Console\Component\Router; use InvalidArgumentException; use PHPUnit\Framework\TestCase; use Throwable; From ee2d8a1d59c797b2cb61a8d9408d719ecc6a49de Mon Sep 17 00:00:00 2001 From: Inhere Date: Tue, 21 Dec 2021 10:18:55 +0800 Subject: [PATCH 209/258] chore: delete deprecated class files --- .github/workflows/php.yml | 2 + examples/alone | 4 +- examples/alone-app | 6 +- resource/deprecated/AbstractHandlerOld.php | 170 ----- resource/deprecated/AbstractInput.php | 397 ------------ resource/deprecated/InputArgumentsTrait.php | 183 ------ resource/deprecated/InputDefinition.php | 678 -------------------- resource/deprecated/InputOptionsTrait.php | 277 -------- src/Concern/ControllerHelpTrait.php | 6 +- 9 files changed, 9 insertions(+), 1714 deletions(-) delete mode 100644 resource/deprecated/AbstractHandlerOld.php delete mode 100644 resource/deprecated/AbstractInput.php delete mode 100644 resource/deprecated/InputArgumentsTrait.php delete mode 100644 resource/deprecated/InputDefinition.php delete mode 100644 resource/deprecated/InputOptionsTrait.php diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index c99dccaf..88020de6 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -55,6 +55,8 @@ jobs: - name: Run unit tests run: | phpunit -v --debug + php examples/alone -h + php examples/app --help - name: Test build PHAR run: | diff --git a/examples/alone b/examples/alone index 7d6e1679..faa3cccc 100644 --- a/examples/alone +++ b/examples/alone @@ -18,8 +18,8 @@ $ctrl = new HomeController($input, new Output()); $ctrl->setDetached(); try { - exit($ctrl->run([$input->getCommand()])); -} catch (Exception $e) { + $ctrl->run([$input->getCommand()]); +} catch (Throwable $e) { $message = Color::apply('error', $e->getMessage()); echo sprintf("%s\nFile %s:%d\nTrace:\n%s\n", diff --git a/examples/alone-app b/examples/alone-app index 605d07ff..68d43919 100644 --- a/examples/alone-app +++ b/examples/alone-app @@ -23,10 +23,8 @@ try { $app->controller('home', HomeController::class); $subCmd = $input->getCommand(); - $result = $app->dispatch('home:' . $subCmd, []); - - exit((int)$result); -} catch (Exception $e) { + $app->dispatch('home:' . $subCmd, []); +} catch (Throwable $e) { $message = Color::apply('error', $e->getMessage()); echo sprintf("%s\nFile %s:%d\nTrace:\n%s\n", diff --git a/resource/deprecated/AbstractHandlerOld.php b/resource/deprecated/AbstractHandlerOld.php deleted file mode 100644 index 0f967dbc..00000000 --- a/resource/deprecated/AbstractHandlerOld.php +++ /dev/null @@ -1,170 +0,0 @@ -definition) { - $this->definition = new InputDefinition(); - $this->definition->setDescription(self::getDescription()); - } - - return $this->definition; - } - - /** - * @return InputDefinition - */ - protected function createDefinition2(): InputDefinition - { - if (!$this->definition) { - $this->definition = new InputDefinition(); - - // if have been set desc for the sub-command - $cmdDesc = $this->commandMetas[$this->action]['desc'] ?? ''; - if ($cmdDesc) { - $this->definition->setDescription($cmdDesc); - } - } - - return $this->definition; - } - - /** - * validate input arguments and options - * - * @return bool - * @throws InvalidArgumentException - */ - public function validateInput(): bool - { - if (!$def = $this->definition) { - return true; - } - - $this->logf(Console::VERB_DEBUG, 'validate the input arguments and options by Definition'); - - $in = $this->input; - $out = $this->output; - - $givenArgs = $errArgs = []; - foreach ($in->getArgs() as $key => $value) { - if (is_int($key)) { - $givenArgs[$key] = $value; - } else { - $errArgs[] = $key; - } - } - - if (count($errArgs) > 0) { - $out->liteError(sprintf('Unknown arguments (error: "%s").', implode(', ', $errArgs))); - return false; - } - - $defArgs = $def->getArguments(); - $missingArgs = array_filter(array_keys($defArgs), static function ($name, $key) use ($def, $givenArgs) { - return !array_key_exists($key, $givenArgs) && $def->argumentIsRequired($name); - }, ARRAY_FILTER_USE_BOTH); - - if (count($missingArgs) > 0) { - $out->liteError(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArgs))); - return false; - } - - $index = 0; - $args = []; - - foreach ($defArgs as $name => $conf) { - $args[$name] = $givenArgs[$index] ?? $conf['default']; - $index++; - } - - $in->setArgs($args); - $this->checkNotExistsOptions($def); - - // check options - $opts = $missingOpts = []; - - $defOpts = $def->getOptions(); - foreach ($defOpts as $name => $conf) { - if (!$in->hasLOpt($name)) { - // support multi short: 'a|b|c' - $shortNames = $conf['shortcut'] ? explode('|', $conf['shortcut']) : []; - if ($srt = $in->findOneShortOpts($shortNames)) { - $opts[$name] = $in->sOpt($srt); - } elseif ($conf['default'] !== null) { - $opts[$name] = $conf['default']; - } elseif ($conf['required']) { - $missingOpts[] = "--$name" . ($srt ? "|-$srt" : ''); - } - } - } - - if (count($missingOpts) > 0) { - $out->liteError(sprintf('Not enough options parameters (missing: "%s").', implode(', ', $missingOpts))); - return false; - } - - if ($opts) { - $in->setLOpts($opts); - } - - return true; - } - - private function checkNotExistsOptions(InputDefinition $def): void - { - $givenOpts = $this->input->getOptions(); - $allDefOpts = $def->getAllOptionNames(); - - // check unknown options - if ($unknown = array_diff_key($givenOpts, $allDefOpts)) { - $names = array_keys($unknown); - - // $first = array_shift($names); - $first = ''; - foreach ($names as $name) { - if (!GlobalOption::isExists($name)) { - $first = $name; - break; - } - } - - if (!$first) { - return; - } - - $errMsg = sprintf('Input option is not exists (unknown: "%s").', (isset($first[1]) ? '--' : '-') . $first); - throw new InvalidArgumentException($errMsg); - } - } - -} diff --git a/resource/deprecated/AbstractInput.php b/resource/deprecated/AbstractInput.php deleted file mode 100644 index 9e664625..00000000 --- a/resource/deprecated/AbstractInput.php +++ /dev/null @@ -1,397 +0,0 @@ -toString(); - } - - /** - * @return string - */ - abstract public function toString(): string; - - /** - * @param array $rawFlags - */ - protected function collectInfo(array $rawFlags): void - { - $this->getPwd(); - if (!$rawFlags) { - return; - } - - $this->tokens = $rawFlags; - - // first is bin file - if (isset($rawFlags[0]) && is_string($rawFlags[0])) { - $this->scriptFile = array_shift($rawFlags); - - // bin name - $this->scriptName = basename($this->scriptFile); - } - - $this->flags = $rawFlags; // no script - - // full script - $this->fullScript = implode(' ', $rawFlags); - } - - /** - * find command name, it is first argument. - * TIP: will reset args data after founded. - */ - public function findCommandName(): string - { - if (!isset($this->args[0])) { - return ''; - } - - $command = ''; - $newArgs = []; - foreach ($this->args as $key => $value) { - if ($key === 0) { - $command = trim($value); - } elseif (is_int($key)) { - $newArgs[] = $value; - } else { - $newArgs[$key] = $value; - } - } - - if ($command) { - $this->args = $newArgs; - } - - return $command; - } - - public function popFirstArg() - { - return array_shift($this->args); - } - - /** - * @return string - */ - public function getCommandPath(): string - { - $path = $this->command; - if ($this->subCommand) { - $path .= ' ' . $this->subCommand; - } - - return $path; - } - - /** - * @return bool - */ - public function isInteractive(): bool - { - return false; - } - - /*********************************************************************************** - * getter/setter - ***********************************************************************************/ - - /** - * @return string - */ - public function getPwd(): string - { - if (!$this->pwd) { - $this->pwd = (string)getcwd(); - } - - return $this->pwd; - } - - /** - * @return string - */ - public function getWorkDir(): string - { - return $this->getPwd(); - } - - /** - * @param string $pwd - */ - public function setPwd(string $pwd): void - { - $this->pwd = $pwd; - } - - /** - * @return string - */ - public function getScriptFile(): string - { - return $this->scriptFile; - } - - /** - * @return string - */ - public function getScriptPath(): string - { - return $this->scriptFile; - } - - /** - * @param string $scriptFile - */ - public function setScriptFile(string $scriptFile): void - { - if ($scriptFile) { - $this->scriptFile = $scriptFile; - // update scriptName - $this->scriptName = basename($scriptFile); - } - } - - /** - * @return string - */ - public function getScriptName(): string - { - return $this->scriptName; - } - - /** - * @return string - */ - public function getBinName(): string - { - return $this->scriptName; - } - - /** - * @return string - */ - public function getCommand(): string - { - return $this->command; - } - - /** - * @param string $command - */ - public function setCommand(string $command): void - { - $this->command = $command; - } - - /** - * @return string - */ - public function getFullScript(): string - { - return $this->fullScript; - } - - /** - * @param string $fullScript - */ - public function setFullScript(string $fullScript): void - { - $this->fullScript = $fullScript; - } - - /** - * @return array - */ - public function getFlags(): array - { - return $this->flags; - } - - /** - * @param array $flags - */ - public function setFlags(array $flags): void - { - $this->flags = $flags; - } - - /** - * @return array - */ - public function getRawFlags(): array - { - return $this->tokens; - } - - /** - * @return array - */ - public function getTokens(): array - { - return $this->tokens; - } - - /** - * @param array $tokens - */ - public function setTokens(array $tokens): void - { - $this->tokens = $tokens; - $this->collectInfo($tokens); - } - - /** - * @return string - */ - public function getSubCommand(): string - { - return $this->subCommand; - } - - /** - * @param string $subCommand - */ - public function setSubCommand(string $subCommand): void - { - $this->subCommand = $subCommand; - } - - /** - * @return FlagsParser - */ - public function getGfs(): FlagsParser - { - return $this->gfs; - } - - /** - * @param FlagsParser $gfs - */ - public function setGfs(FlagsParser $gfs): void - { - $this->gfs = $gfs; - } - - /** - * @return FlagsParser - */ - public function getFs(): FlagsParser - { - return $this->fs; - } - - /** - * @param FlagsParser $fs - */ - public function setFs(FlagsParser $fs): void - { - $this->fs = $fs; - } -} diff --git a/resource/deprecated/InputArgumentsTrait.php b/resource/deprecated/InputArgumentsTrait.php deleted file mode 100644 index e59f5858..00000000 --- a/resource/deprecated/InputArgumentsTrait.php +++ /dev/null @@ -1,183 +0,0 @@ - 0, - * 'name2' => 1, - * ] - * - * @var array - */ - protected array $binds = []; - - /** - * @param string $name - * @param int $index - * @return self - */ - public function bindArgument(string $name, int $index): static - { - $this->binds[$name] = $index; - return $this; - } - - /** - * @param array $map [ argName => index, ] - * @param bool $replace - */ - public function bindArguments(array $map, bool $replace = false): void - { - if ($replace) { - $this->binds = []; - } - - foreach ($map as $name => $index) { - $this->bindArgument($name, (int)$index); - } - } - - /*********************************************************************************** - * arguments (eg: arg0 name=john city=chengdu) - ***********************************************************************************/ - - /** - * @return array - */ - public function getArgs(): array - { - return $this->args; - } - - /** - * @return array - */ - public function getArguments(): array - { - return $this->getArgs(); - } - - /** - * @param array $args - * @param bool $replace - */ - public function setArgs(array $args, bool $replace = false): void - { - $this->args = $replace ? $args : array_merge($this->args, $args); - } - - /** - * @param int|string $name - * - * @return bool - */ - public function hasArg(int|string $name): bool - { - // get real index key - $key = $this->binds[$name] ?? $name; - - return isset($this->args[$key]); - } - - /** - * get Argument - * - * @param int|string|null $name - * @param mixed|null $default - * - * @return mixed - */ - public function getArgument(int|string|null $name, mixed $default = null): mixed - { - return $this->get($name, $default); - } - - /** - * get argument - * - * @param int|string|null $name - * @param mixed|null $default - * - * @return mixed - */ - public function getArg(int|string|null $name, mixed $default = null): mixed - { - return $this->get($name, $default); - } - - /** - * get Argument - * - * @param int|string|null $name - * @param mixed|null $default - * - * @return mixed - */ - public function get(int|string|null $name, mixed $default = null): mixed - { - // get real index key - $key = $this->binds[$name] ?? $name; - - return $this->args[$key] ?? $default; - } - - /** - * Get a required argument - * - * @param int|string $name argument index or name - * @param string $errMsg - * - * @return mixed - */ - public function getRequiredArg(int|string $name, string $errMsg = ''): mixed - { - // get real index key - $key = $this->binds[$name] ?? $name; - if (isset($this->args[$key])) { - return $this->args[$key]; - } - - if (!$errMsg) { - $errName = is_int($key) ? "'$name'(position#$key)" : "'$name'"; - $errMsg = "The argument $errName is required"; - } - - throw new PromptException($errMsg); - } - - /** - * clear args - */ - public function clearArgs(): void - { - $this->args = []; - } -} diff --git a/resource/deprecated/InputDefinition.php b/resource/deprecated/InputDefinition.php deleted file mode 100644 index cad2b2ab..00000000 --- a/resource/deprecated/InputDefinition.php +++ /dev/null @@ -1,678 +0,0 @@ - null, - 'default' => null, - 'description' => '', - ]; - - /** - * @var string|array - */ - private string|array $example; - - /** - * @var string - */ - private string $description; - - /** - * @var array[] - */ - private array $arguments = []; - - /** - * @var int - */ - private int $requiredCount = 0; - - /** - * @var bool - */ - private bool $hasOptionalArgument = false; - - /** - * @var bool - */ - private bool $hasAnArrayArgument = false; - - /** - * @var array[] - */ - private array $options; - - /** - * @var array - */ - private array $shortcuts; - - /** - * @param array $arguments - * @param array $options - * - * @return InputDefinition - */ - public static function make(array $arguments = [], array $options = []): InputDefinition - { - return new self($arguments, $options); - } - - /** - * Constructor. - * - * @param array $arguments - * @param array $options - * - * @throws LogicException - * @throws InvalidArgumentException - */ - public function __construct(array $arguments = [], array $options = []) - { - $this->setArguments($arguments); - $this->setOptions($options); - } - - /*************************************************************************** - * some methods for the arguments - ***************************************************************************/ - - /** - * @param array $arguments - * - * @return InputDefinition - * @throws LogicException - */ - public function setArguments(array $arguments): InputDefinition - { - $this->arguments = []; - - return $this->addArguments($arguments); - } - - /** - * @param array $arguments - * - * @return $this - * @throws LogicException - */ - public function addArguments(array $arguments): self - { - foreach ($arguments as $name => $arg) { - $arg = $this->mergeArgOptConfig($arg); - $this->addArgument($name, $arg['mode'], $arg['description'], $arg['default']); - } - - return $this; - } - - /** - * alias of the addArgument - * - * @param string $name - * @param int|null $mode - * @param string $description - * @param null $default - * - * @return InputDefinition - */ - public function addArg(string $name, int $mode = null, string $description = '', $default = null): self - { - return $this->addArgument($name, $mode, $description, $default); - } - - /** - * Adds an argument. - * - * @param string $name The argument name - * @param int $mode The argument mode flags. eg: Input::ARG_REQUIRED, Input::ARG_OPTIONAL - * allow more flags, eg: Input::ARG_REQUIRED|Input::ARG_IS_ARRAY - * @param string $description A description text - * @param mixed|null $default The default value (for Input::ARG_OPTIONAL mode only) - * - * @return $this - * @throws LogicException - */ - public function addArgument(string $name, int $mode = 0, string $description = '', mixed $default = null): self - { - if (0 === $mode) { - $mode = Input::ARG_OPTIONAL; - } elseif (!is_int($mode) || $mode > 7 || $mode < 1) { - throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); - } - - if (isset($this->arguments[$name])) { - throw new LogicException(sprintf('An argument with name "%s" already exists.', $name)); - } - - if ($this->hasAnArrayArgument) { - throw new LogicException('Cannot add an argument after an array argument.'); - } - - $required = ($mode & Input::ARG_REQUIRED) > 0; - if ($required && $this->hasOptionalArgument) { - throw new LogicException('Cannot add a required argument after an optional one.'); - } - - $isArray = ($mode & Input::ARG_IS_ARRAY) > 0; - if ($isArray) { - // if (false === $this->argumentIsAcceptValue($mode)) { - // throw new InvalidArgumentException('Impossible to have an option mode ARG_IS_ARRAY if the option does not accept a value.'); - // } - - $this->hasAnArrayArgument = true; - - if (null === $default) { - $default = []; - } elseif (!is_array($default)) { - throw new LogicException('A default value for an array argument must be an array.'); - } - } - - if ($required) { - if (null !== $default) { - throw new LogicException('Cannot set a default value except for OPTIONAL-ARGUMENT mode.'); - } - - ++$this->requiredCount; - } else { - $this->hasOptionalArgument = true; - } - - $index = count($this->arguments); - - $this->arguments[$name] = [ - 'mode' => $mode, - 'index' => $index, - 'required' => $required, - 'isArray' => $isArray, - 'description' => $description, - 'default' => $default, - ]; - - return $this; - } - - /** - * @param int|string $name - * @param null $default - * - * @return string|int|null - */ - public function getArgument(int|string $name, $default = null): array|int|string|null - { - $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; - - if (!isset($arguments[$name])) { - return $default; - // throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); - } - - return $arguments[$name]; - } - - /** - * @param int|string $name The argument name or position - * - * @return bool true if the InputArgument object exists, false otherwise - */ - public function hasArgument(int|string $name): bool - { - $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; - - return isset($arguments[$name]); - } - - /** - * @return array[] - */ - public function getArguments(): array - { - return $this->arguments; - } - - /** - * Returns the number of arguments. - * - * @return int - */ - public function getArgumentCount(): int - { - return count($this->arguments); - } - - /** - * get count of required arguments - * - * @return int - */ - public function getArgumentRequiredCount(): int - { - return $this->requiredCount; - } - - /*************************************************************************** - * some methods for the options - ***************************************************************************/ - - /** - * Sets the options - * - * @param array[] $options An array of InputOption objects - * - * @throws LogicException - * @throws InvalidArgumentException - */ - public function setOptions(array $options = []): void - { - $this->options = $this->shortcuts = []; - $this->addOptions($options); - } - - /** - * Adds an array of option - * - * @param array - * - * @throws LogicException - * @throws InvalidArgumentException - */ - public function addOptions(array $options = []): void - { - foreach ($options as $name => $opt) { - $opt = $this->mergeArgOptConfig($opt); - $this->addOption($name, $opt['mode'], $opt['description'], $opt['default']); - } - } - - /** - * alias of the addOption - * - * @param string $name - * @param string|null $shortcut - * @param int|null $mode - * @param string $description - * @param mixed|null $default - * - * @return InputDefinition - */ - public function addOpt( - string $name, - string $shortcut = null, - int $mode = null, - string $description = '', - mixed $default = null - ): self { - return $this->addOption($name, $shortcut, $mode, $description, $default); - } - - /** - * Adds an option. - * - * @param string|bool $name The option name, must is a string - * @param array|string|null $shortcut The shortcut (can be null) - * - array: [a, b] - * - string: 'a|b' - * @param int $mode The option mode: One of the Input::OPT_* constants - * @param string $description A description text - * @param mixed|null $default The default value (must be null for InputOption::OPT_BOOL) - * - * @return $this - * @throws InvalidArgumentException - * @throws LogicException - */ - public function addOption( - string $name, - array|string|null $shortcut = '', - int $mode = 0, - string $description = '', - mixed $default = null - ): self { - $name = trim($name, '-'); - if (empty($name)) { - throw new InvalidArgumentException('An option name cannot be empty.'); - } - - if (isset($this->options[$name])) { - throw new LogicException(sprintf('An option named "%s" already exists.', $name)); - } - - if ($mode <= 0) { - $mode = Input::OPT_BOOLEAN; - } elseif (!is_int($mode) || $mode > 15 || $mode < 1) { - throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); - } - - $isArray = ($mode & Input::OPT_IS_ARRAY) > 0; - if ($isArray && !$this->optionIsAcceptValue($mode)) { - throw new InvalidArgumentException('Impossible to have an option mode OPT_IS_ARRAY if the option does not accept a value.'); - } - - // set default value - $isBoolean = Input::OPT_BOOLEAN === (Input::OPT_BOOLEAN & $mode); - if ($isBoolean) { - if (null !== $default) { - throw new LogicException('Cannot set a default value when using OPT_BOOLEAN mode.'); - } - - $default = false; - } elseif ($isArray) { - if (null === $default) { - $default = []; - } elseif (!is_array($default)) { - throw new LogicException('A default value for an array option must be an array.'); - } - } - - $default = $this->optionIsAcceptValue($mode) ? $default : false; - - if ($shortcut) { - if (is_array($shortcut)) { - $shortcut = implode('|', $shortcut); - } - - $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); - $shortcuts = array_filter($shortcuts); - $shortcut = implode('|', $shortcuts); - - foreach ($shortcuts as $srt) { - if (isset($this->shortcuts[$srt])) { - throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $srt)); - } - - $this->shortcuts[$srt] = $name; - } - } - - $this->options[$name] = [ - 'mode' => $mode, - 'isArray' => $isArray, - 'shortcut' => $shortcut, // 允许数组 - 'required' => Helper::hasMode($mode, Input::OPT_REQUIRED), - 'optional' => Helper::hasMode($mode, Input::OPT_OPTIONAL), - 'description' => $description, - 'default' => $default, - ]; - - return $this; - } - - /** - * @param string $name - * - * @return array - * @throws InvalidArgumentException - */ - public function getOption(string $name): array - { - if (!$this->hasOption($name)) { - throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); - } - - return $this->options[$name]; - } - - /** - * @param string $name - * - * @return bool - */ - public function hasOption(string $name): bool - { - return isset($this->options[$name]); - } - - /** - * Gets the array of options - * - * @return array[] - */ - public function getOptions(): array - { - return $this->options; - } - - /** - * Gets the name of options, contains short-name - * - * @return array[] key is name - */ - public function getAllOptionNames(): array - { - $allNames = $this->shortcuts; - $longNames = array_keys($this->options); - - foreach ($longNames as $name) { - $allNames[$name] = 1; - } - - return $allNames; - } - - /** - * @param string $name The InputOption shortcut - * - * @return bool - */ - public function hasShortcut(string $name): bool - { - return isset($this->shortcuts[$name]); - } - - /** - * Gets an option info array - * - * @param string $shortcut the Shortcut name - * - * @return array - * @throws InvalidArgumentException - */ - public function getOptionByShortcut(string $shortcut): array - { - return $this->getOption($this->shortcutToName($shortcut)); - } - - /** - * @param string $shortcut - * - * @return mixed - * @throws InvalidArgumentException - */ - private function shortcutToName(string $shortcut): mixed - { - if (!isset($this->shortcuts[$shortcut])) { - throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); - } - - return $this->shortcuts[$shortcut]; - } - - /** - * @param array $map - * - * @return array - */ - private function mergeArgOptConfig(array $map): array - { - return $map ? array_merge(self::$defaultArgOptConfig, $map) : self::$defaultArgOptConfig; - } - - /** - * Gets the synopsis. - * - * @param bool $short 简化版显示 - * - * @return array - */ - public function getSynopsis(bool $short = false): array - { - $elements = $args = $opts = []; - - if ($short && $this->options) { - $elements[] = '[options]'; - } elseif (!$short) { - foreach ($this->options as $name => $option) { - $value = ''; - - if ($this->optionIsAcceptValue($option['mode'])) { - $value = sprintf(' %s%s%s', $option['optional'] ? '[' : '', strtoupper($name), - $option['optional'] ? ']' : ''); - } - - $shortcut = $option['shortcut'] ? sprintf('-%s, ', $option['shortcut']) : ' '; - $elements[] = sprintf('[%s--%s%s]', $shortcut, $name, $value); - - $key = "$shortcut--$name"; - $opts[$key] = ($option['required'] ? '*' : '') . $option['description']; - } - } - - if ($this->arguments && count($elements)) { - $elements[] = '[--]'; - } - - foreach ($this->arguments as $name => $argument) { - $des = $argument['required'] ? '*' . $argument['description'] : $argument['description']; - - $element = '<' . $name . '>'; - if (!$argument['required']) { - $element = '[' . $element . ']'; - } elseif ($argument['isArray']) { - $element .= ' (' . $element . ')'; - } - - if ($argument['isArray']) { - $element .= '...'; - } - - $elements[] = $element; - $args[$name] = $des; - } - - $example = $this->example; - if ($this->example) { - - } - - return [ - $this->description, - 'usage:' => implode(' ', $elements), - 'options:' => $opts, - 'arguments:' => $args, - 'example:' => $example, - 'global options:' => '', - ]; - } - - /** - * @param int|string $name - * - * @return bool - */ - public function argumentIsRequired(int|string $name): bool - { - if (isset($this->arguments[$name])) { - return $this->arguments[$name]['mode'] === Input::ARG_REQUIRED; - } - - return false; - } - - /** - * @param int $mode - * - * @return bool - */ - protected function argumentIsAcceptValue(int $mode): bool - { - return $mode & Input::ARG_REQUIRED || $mode & Input::ARG_OPTIONAL; - } - - /** - * @param int $mode - * - * @return bool - */ - protected function optionIsAcceptValue(int $mode): bool - { - return $mode & Input::OPT_REQUIRED || $mode & Input::OPT_OPTIONAL; - } - - /** - * @return string|array - */ - public function getExample(): array|string - { - return $this->example; - } - - /** - * @param array|string $example - * - * @return $this - */ - public function setExample(array|string $example): self - { - $this->example = $example; - return $this; - } - - /** - * @return string|null - */ - public function getDescription(): ?string - { - return $this->description; - } - - /** - * @param string $description - * - * @return $this - */ - public function setDescription(string $description): self - { - $this->description = $description; - return $this; - } - - /** - * @return array - */ - public function getShortcuts(): array - { - return $this->shortcuts; - } -} diff --git a/resource/deprecated/InputOptionsTrait.php b/resource/deprecated/InputOptionsTrait.php deleted file mode 100644 index f8733157..00000000 --- a/resource/deprecated/InputOptionsTrait.php +++ /dev/null @@ -1,277 +0,0 @@ -getLongOpt($name, $default); - } - - return $this->getShortOpt($name, $default); - } - - /** - * Alias of the getOpt() - * - * @param string $name - * @param mixed|null $default - * - * @return mixed - */ - public function getOption(string $name, mixed $default = null): mixed - { - return $this->getOpt($name, $default); - } - - /** - * Get a required option value - * - * @param string $name - * - * @param string $errMsg - * - * @return mixed - */ - public function getRequiredOpt(string $name, string $errMsg = ''): mixed - { - if (null !== ($val = $this->getOpt($name))) { - return $val; - } - - $errMsg = $errMsg ?: "The option '$name' is required"; - throw new PromptException($errMsg); - } - - /** - * check option exists - * - * @param string $name - * - * @return bool - */ - public function hasOpt(string $name): bool - { - return isset($this->sOpts[$name]) || isset($this->lOpts[$name]); - } - - /** - * @return array - */ - public function getOpts(): array - { - return array_merge($this->sOpts, $this->lOpts); - } - - /** - * @return array - */ - public function getOptions(): array - { - return $this->getOpts(); - } - - /** - * clear (l/s)opts - */ - public function clearOpts(): void - { - $this->sOpts = $this->lOpts = []; - } - - /************************** short-opts **********************/ - - /** - * Alias of the sOpt() - * - * @param string $name - * @param null $default - * - * @return mixed|null - */ - public function getShortOpt(string $name, $default = null): mixed - { - return $this->sOpts[$name] ?? $default; - } - - /** - * Check short-opt exists - * - * @param string $name - * - * @return bool - */ - public function hasSOpt(string $name): bool - { - return isset($this->sOpts[$name]); - } - - /** - * @return array - */ - public function getShortOpts(): array - { - return $this->sOpts; - } - - /** - * @param string $name - * @param mixed $value - */ - public function setSOpt(string $name, mixed $value): void - { - $this->sOpts[$name] = $value; - } - - /** - * @return array - */ - public function getSOpts(): array - { - return $this->sOpts; - } - - /** - * @param array $sOpts - * @param bool $replace - */ - public function setSOpts(array $sOpts, bool $replace = false): void - { - $this->sOpts = $replace ? $sOpts : array_merge($this->sOpts, $sOpts); - } - - /** - * clear s-opts - */ - public function clearSOpts(): void - { - $this->sOpts = []; - } - - /************************** long-opts **********************/ - - /** - * Alias of the getLongOpt() - * - * @param string $name - * @param null $default - * - * @return mixed|null - */ - public function lOpt(string $name, $default = null): mixed - { - return $this->lOpts[$name] ?? $default; - } - - /** - * Get long-opt value - * - * @param string $name - * @param null $default - * - * @return mixed|null - */ - public function getLongOpt(string $name, $default = null): mixed - { - return $this->lOpts[$name] ?? $default; - } - - /** - * check long-opt exists - * - * @param string $name - * - * @return bool - */ - public function hasLOpt(string $name): bool - { - return isset($this->lOpts[$name]); - } - - /** - * @return array - */ - public function getLongOpts(): array - { - return $this->lOpts; - } - - /** - * @param string $name - * @param mixed $value - */ - public function setLOpt(string $name, mixed $value): void - { - $this->lOpts[$name] = $value; - } - - /** - * @return array - */ - public function getLOpts(): array - { - return $this->lOpts; - } - - /** - * @param array $lOpts - * @param bool $replace - */ - public function setLOpts(array $lOpts, bool $replace = false): void - { - $this->lOpts = $replace ? $lOpts : array_merge($this->lOpts, $lOpts); - } - - /** - * clear lang opts - */ - public function clearLOpts(): void - { - $this->lOpts = []; - } -} diff --git a/src/Concern/ControllerHelpTrait.php b/src/Concern/ControllerHelpTrait.php index 58be9004..60092a52 100644 --- a/src/Concern/ControllerHelpTrait.php +++ b/src/Concern/ControllerHelpTrait.php @@ -163,7 +163,7 @@ public function showCommandList(): void } $globalOptions = []; - if ($app = $this->getApp()) { + if ($app = $this->app) { $globalOptions = $app->getFlags()->getOptsHelpLines(); } @@ -185,8 +185,8 @@ public function showCommandList(): void 'sepChar' => ' ', ]); - $msgTpl = 'More information about a command, please see: %s %s COMMAND -h'; - $this->output->write(sprintf($msgTpl, $script, $detached ? '' : $sName)); + $msgTpl = 'More information about a command, please see: %s%s COMMAND -h'; + $this->output->writeln(sprintf($msgTpl, $script, $detached ? '' : ' ' . $sName)); $this->output->flush(); } } From 045e0ddedd57b132ea8d3c198238c166c1732930 Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 23 Dec 2021 11:40:35 +0800 Subject: [PATCH 210/258] up: update group command help render --- examples/Controller/HomeController.php | 5 +++++ examples/alone | 5 +++-- src/Concern/ControllerHelpTrait.php | 23 ++++++++++++----------- src/Controller.php | 1 - src/Handler/AbstractHandler.php | 5 +++-- src/Util/FormatUtil.php | 2 +- 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/examples/Controller/HomeController.php b/examples/Controller/HomeController.php index 676a5b6c..e5b24f2c 100644 --- a/examples/Controller/HomeController.php +++ b/examples/Controller/HomeController.php @@ -36,6 +36,11 @@ class HomeController extends Controller protected static string $description = 'This is a demo command controller. there are some command usage examples(2)'; + public static function aliases(): array + { + return ['h']; + } + /** * @return array */ diff --git a/examples/alone b/examples/alone index faa3cccc..ac473101 100644 --- a/examples/alone +++ b/examples/alone @@ -13,12 +13,13 @@ define('BASE_PATH', dirname(__DIR__)); require dirname(__DIR__) . '/test/bootstrap.php'; +// run: php examples/alone $input = new Input(); $ctrl = new HomeController($input, new Output()); -$ctrl->setDetached(); try { - $ctrl->run([$input->getCommand()]); + $ctrl->setDetached(); + $ctrl->run($input->getFlags()); } catch (Throwable $e) { $message = Color::apply('error', $e->getMessage()); diff --git a/src/Concern/ControllerHelpTrait.php b/src/Concern/ControllerHelpTrait.php index 60092a52..7ad87904 100644 --- a/src/Concern/ControllerHelpTrait.php +++ b/src/Concern/ControllerHelpTrait.php @@ -11,9 +11,9 @@ use Inhere\Console\Console; use Inhere\Console\GlobalOption; -use Inhere\Console\Util\FormatUtil; use ReflectionClass; use Toolkit\Cli\Color\ColorTag; +use Toolkit\PFlag\FlagUtil; use Toolkit\Stdlib\Str; use Toolkit\Stdlib\Util\PhpDoc; use function array_merge; @@ -96,7 +96,7 @@ public function showCommandList(): void $this->beforeShowCommandList(); $ref = new ReflectionClass($this); - $sName = lcfirst(self::getName() ?: $ref->getShortName()); + $sName = self::getName() ?: lcfirst($ref->getShortName()); if (!($classDes = self::getDesc())) { $classDes = PhpDoc::description($ref->getDocComment()) ?: 'No description for the command group'; @@ -124,6 +124,7 @@ public function showCommandList(): void $desc = $defaultDes; } + $desc = ucfirst($desc); if ($this->isDisabled($cmd)) { if (!$showDisabled) { continue; @@ -151,7 +152,6 @@ public function showCommandList(): void // if is alone running. if ($detached = $this->isDetached()) { - // $name = $sName . ' '; $usage = "$script COMMAND [--options ...] [arguments ...]"; } else { $name = $sName . $this->delimiter; @@ -162,24 +162,25 @@ public function showCommandList(): void ]; } - $globalOptions = []; - if ($app = $this->app) { - $globalOptions = $app->getFlags()->getOptsHelpLines(); - } + // $globalOptions = []; + // if ($app = $this->app) { + // $globalOptions = $app->getFlags()->getOptsHelpLines(); + // } $this->output->startBuffer(); $this->output->write(ucfirst($classDes) . PHP_EOL); + $alias = ''; if ($aliases = $this->getAliases()) { - $this->output->writef("Alias: %s\n", implode(',', $aliases)); + $alias = ' (alias: ' . implode(',', $aliases) . ')'; } + $this->output->writef("Name : %s%s\n", $sName, $alias); $groupOptions = $this->flags->getOptsHelpLines(); $this->output->mList([ 'Usage:' => $usage, - //'Group Name:' => "$sName", - 'Group Options:' => FormatUtil::alignOptions($groupOptions), - 'Global Options:' => FormatUtil::alignOptions($globalOptions), + 'Group Options:' => FlagUtil::alignOptions($groupOptions), + // 'Global Options:' => FlagUtil::alignOptions($globalOptions), 'Available Commands:' => $commands, ], [ 'sepChar' => ' ', diff --git a/src/Controller.php b/src/Controller.php index 559f0761..d3ffad55 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -24,7 +24,6 @@ use ReflectionObject; use RuntimeException; use Throwable; -use Toolkit\Cli\Helper\FlagHelper; use Toolkit\PFlag\FlagsParser; use Toolkit\PFlag\FlagUtil; use Toolkit\PFlag\SFlags; diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index 514ac388..ab5776b5 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -290,7 +290,7 @@ protected function afterInitFlagsParser(FlagsParser $fs): void /** * @param array $args * - * @return bool|int|mixed + * @return mixed * @throws Throwable */ public function run(array $args): mixed @@ -593,9 +593,10 @@ public function getCommandId(bool $useReal = true): string */ public function getAliases(): array { - $aliases = []; if ($this->app) { $aliases = $this->app->getAliases(self::getName()); + } else { + $aliases = static::aliases(); } return $aliases; diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index 737f747a..e4da452d 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -141,7 +141,7 @@ public static function wrapText(string $text, int $indent = 0, int $width = 0): * @param array $options * * @return array - * @deprecated Please use {@see FlagUtil::alignOptions()} + * @deprecated Please use FlagUtil::alignOptions {@see FlagUtil::alignOptions()} */ public static function alignOptions(array $options): array { From d94eeeee703853da774f05d5bc15b4674fe1af7d Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 24 Dec 2021 10:45:37 +0800 Subject: [PATCH 211/258] breaking: update command register method sign --- src/Concern/ApplicationHelpTrait.php | 18 +++++++++--------- src/Concern/CommandHelpTrait.php | 6 +++--- src/Concern/SubCommandsWareTrait.php | 16 ++++++++-------- src/Contract/ApplicationInterface.php | 10 +++------- src/Contract/RouterInterface.php | 17 +++++------------ 5 files changed, 28 insertions(+), 39 deletions(-) diff --git a/src/Concern/ApplicationHelpTrait.php b/src/Concern/ApplicationHelpTrait.php index bb7eb02a..e922b939 100644 --- a/src/Concern/ApplicationHelpTrait.php +++ b/src/Concern/ApplicationHelpTrait.php @@ -15,10 +15,10 @@ use Inhere\Console\Contract\CommandInterface; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; -use Inhere\Console\Util\FormatUtil; use Inhere\Console\Util\Show; use Toolkit\Cli\Color\ColorTag; use Toolkit\Cli\Style; +use Toolkit\PFlag\FlagUtil; use function array_merge; use function basename; use function date; @@ -138,7 +138,7 @@ public function showHelpInfo(string $command = ''): void $binName = $in->getScriptName(); $helpInfo = [ 'Usage' => "$binName {command} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", - 'Options' => FormatUtil::alignOptions($globalOptions), + 'Options' => FlagUtil::alignOptions($globalOptions), 'Example' => [ '- run a command/subcommand:', "$binName test run a independent command", @@ -215,11 +215,11 @@ public function showCommandList(): void $placeholder = 'No description of the command'; foreach ($groups as $name => $info) { - $options = $info['options']; $controller = $info['handler']; /** @var AbstractHandler $controller */ $desc = $controller::getDesc() ?: $placeholder; - $aliases = $options['aliases']; + $config = $info['config']; + $aliases = $config['aliases']; $extra = $aliases ? ColorTag::wrap(' (alias: ' . implode(',', $aliases) . ')', 'info') : ''; // collect @@ -231,14 +231,14 @@ public function showCommandList(): void } foreach ($commands as $name => $info) { - $desc = $placeholder; - $options = $info['options']; + $desc = $placeholder; + $config = $info['config']; $command = $info['handler']; /** @var AbstractHandler $command */ if (is_subclass_of($command, CommandInterface::class)) { $desc = $command::getDesc() ?: $placeholder; - } elseif ($msg = $options['desc'] ?? '') { + } elseif ($msg = $config['desc'] ?? '') { $desc = $msg; } elseif (is_string($command)) { $desc = 'A handler : ' . $command; @@ -246,7 +246,7 @@ public function showCommandList(): void $desc = 'A handler by ' . get_class($command); } - $aliases = $options['aliases']; + $aliases = $config['aliases']; $extra = $aliases ? ColorTag::wrap(' (alias: ' . implode(',', $aliases) . ')', 'info') : ''; $commandArr[$name] = $desc . $extra; @@ -281,7 +281,7 @@ public function showCommandList(): void Show::mList([ 'Usage:' => "$scriptName {COMMAND} [--opt -v -h ...] [arg0 arg1 arg2=value2 ...]", - 'Options:' => FormatUtil::alignOptions($globOpts), + 'Options:' => FlagUtil::alignOptions($globOpts), 'Internal Commands:' => $internalCommands, 'Available Commands:' => array_merge($groupArr, $commandArr), ], [ diff --git a/src/Concern/CommandHelpTrait.php b/src/Concern/CommandHelpTrait.php index 75f55b5d..e6534739 100644 --- a/src/Concern/CommandHelpTrait.php +++ b/src/Concern/CommandHelpTrait.php @@ -11,8 +11,8 @@ use Inhere\Console\Handler\AbstractHandler; use Inhere\Console\Console; -use Inhere\Console\Util\FormatUtil; use Toolkit\PFlag\FlagsParser; +use Toolkit\PFlag\FlagUtil; use function implode; use function sprintf; use function strtr; @@ -146,7 +146,7 @@ public function showHelpByFlagsParser(FlagsParser $fs, array $aliases = [], stri $help['Usage:'] = "$path [--options ...] [arguments ...]"; - $help['Options:'] = FormatUtil::alignOptions($fs->getOptsHelpLines()); + $help['Options:'] = FlagUtil::alignOptions($fs->getOptsHelpLines()); $help['Argument:'] = $fs->getArgsHelpLines(); $help['Example:'] = $fs->getExampleHelp(); @@ -158,7 +158,7 @@ public function showHelpByFlagsParser(FlagsParser $fs, array $aliases = [], stri // attached to console app if ($this->renderGlobalOption && ($app = $this->getApp())) { - $help['Global Options:'] = FormatUtil::alignOptions($app->getFlags()->getOptsHelpLines()); + $help['Global Options:'] = FlagUtil::alignOptions($app->getFlags()->getOptsHelpLines()); } $this->output->mList($help, [ diff --git a/src/Concern/SubCommandsWareTrait.php b/src/Concern/SubCommandsWareTrait.php index f0cec8e4..da73f184 100644 --- a/src/Concern/SubCommandsWareTrait.php +++ b/src/Concern/SubCommandsWareTrait.php @@ -48,7 +48,7 @@ trait SubCommandsWareTrait * [ * 'name' => [ * 'handler' => MyCommand::class, // allow: string|Closure|CommandInterface - * 'options' => [] + * 'config' => [] * ] * ] */ @@ -83,14 +83,14 @@ protected function dispatchCommand(string $name): void * * @param string|CommandInterface $name * @param string|Closure|CommandInterface|null $handler - * @param array $options + * @param array $config * array: * - aliases The command aliases * - description The description message * * @throws InvalidArgumentException */ - public function addSub(string $name, string|Closure|CommandInterface $handler = null, array $options = []): void + public function addSub(string $name, string|Closure|CommandInterface $handler = null, array $config = []): void { if (!$handler && class_exists($name)) { /** @var Command $name name is an command class */ @@ -108,7 +108,7 @@ public function addSub(string $name, string|Closure|CommandInterface $handler = Helper::throwInvalidArgument("Command '$name' have been registered!"); } - $options['aliases'] = isset($options['aliases']) ? (array)$options['aliases'] : []; + $config['aliases'] = isset($config['aliases']) ? (array)$config['aliases'] : []; if (is_string($handler)) { if (!class_exists($handler)) { @@ -127,7 +127,7 @@ public function addSub(string $name, string|Closure|CommandInterface $handler = // allow define aliases in Command class by Command::aliases() if ($aliases = $handler::aliases()) { - $options['aliases'] = array_merge($options['aliases'], $aliases); + $config['aliases'] = array_merge($config['aliases'], $aliases); } } elseif (!is_object($handler) || !method_exists($handler, '__invoke')) { Helper::throwInvalidArgument( @@ -140,12 +140,12 @@ public function addSub(string $name, string|Closure|CommandInterface $handler = $this->commands[$name] = [ 'type' => Console::CMD_SINGLE, 'handler' => $handler, - 'options' => $options, + 'config' => $config, ]; // has alias option - if (isset($options['aliases'])) { - $this->setAlias($name, $options['aliases'], true); + if (isset($config['aliases'])) { + $this->setAlias($name, $config['aliases'], true); } } diff --git a/src/Contract/ApplicationInterface.php b/src/Contract/ApplicationInterface.php index ffdc126f..7240bb2e 100644 --- a/src/Contract/ApplicationInterface.php +++ b/src/Contract/ApplicationInterface.php @@ -59,10 +59,8 @@ public function stop(int $code = 0): void; * Register a app group command(by controller) * * @param string $name The controller name - * @param string|ControllerInterface|null $class The controller class + * @param class-string|ControllerInterface|null $class The controller class * @param array{desc: string, aliases: array} $config config the controller. - * - aliases The command aliases - * - desc The description message * * @return static * @throws InvalidArgumentException @@ -72,11 +70,9 @@ public function controller(string $name, ControllerInterface|string $class = nul /** * Register a app independent console command * - * @param string|class-string $name - * @param string|Closure|CommandInterface|null $handler + * @param string $name + * @param string|CommandInterface|null|Closure():void $handler * @param array{desc: string, aliases: array} $config config the command. - * - aliases The command aliases - * - desc The description message * * @return mixed * @throws InvalidArgumentException diff --git a/src/Contract/RouterInterface.php b/src/Contract/RouterInterface.php index 5eb8d1da..e337a231 100644 --- a/src/Contract/RouterInterface.php +++ b/src/Contract/RouterInterface.php @@ -10,7 +10,6 @@ namespace Inhere\Console\Contract; use Closure; -use InvalidArgumentException; /** * Interface RouterInterface @@ -33,11 +32,8 @@ interface RouterInterface * @param string|class-string $name The controller name * @param string|ControllerInterface|null $class The controller class * @param array{aliases: array, desc: string} $config The options config. - * - aliases The command aliases - * - desc The description message * * @return static - * @throws InvalidArgumentException */ public function addGroup(string $name, ControllerInterface|string $class = null, array $config = []): static; @@ -45,13 +41,10 @@ public function addGroup(string $name, ControllerInterface|string $class = null, * Register a app independent console command * * @param string|class-string $name - * @param string|Closure|CommandInterface|null $handler - * @param array{aliases: array, desc: string} $config The options config. - * - aliases The command aliases - * - desc The description message + * @param string|CommandInterface|null|Closure():void $handler + * @param array{aliases: array, desc: string, options: array, arguments: array} $config The config. * * @return static - * @throws InvalidArgumentException */ public function addCommand(string $name, string|Closure|CommandInterface $handler = null, array $config = []): static; @@ -60,16 +53,16 @@ public function addCommand(string $name, string|Closure|CommandInterface $handle * return [ * type => 1, // 1 group 2 command * handler => handler class/object/func ... - * options => [ + * config => [ + * desc => '', * aliases => [], - * description => '', * ], * ] * ``` * * @param string $name The input command name * - * @return array return route info array. If not found, will return empty array. + * @return array{name:string, cmdId: string, config: array, handler: mixed} return route info. If not found, will return empty array. */ public function match(string $name): array; } From 6a4b640267442a8a1ad2c3758cf36b7f326dff04 Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 24 Dec 2021 10:46:43 +0800 Subject: [PATCH 212/258] feat: support add flags config for add alone Closure command --- src/Application.php | 139 ++++++++++---------------- src/Component/Formatter/HelpPanel.php | 10 +- src/Component/PharCompiler.php | 4 +- src/Component/Router.php | 115 +++++++++++++-------- src/Controller.php | 3 +- src/Util/FormatUtil.php | 32 +----- 6 files changed, 138 insertions(+), 165 deletions(-) diff --git a/src/Application.php b/src/Application.php index 73c9ea83..5888d04a 100644 --- a/src/Application.php +++ b/src/Application.php @@ -18,9 +18,10 @@ use Inhere\Console\Util\Helper; use InvalidArgumentException; use RuntimeException; -use SplFileInfo; use Throwable; +use Toolkit\FsUtil\Dir; use Toolkit\PFlag\SFlags; +use Toolkit\Stdlib\Helper\Assert; use Toolkit\Stdlib\Helper\DataHelper; use function array_unshift; use function class_exists; @@ -60,7 +61,11 @@ public function __construct(array $config = [], Input $input = null, Output $out ****************************************************************************/ /** - * {@inheritdoc} + * @param string $name + * @param ControllerInterface|string|null $class + * @param array $config + * + * @return $this */ public function controller(string $name, ControllerInterface|string $class = null, array $config = []): static { @@ -100,8 +105,6 @@ public function addController(string $name, ControllerInterface|string $class = /** * @param array $controllers - * - * @throws InvalidArgumentException */ public function controllers(array $controllers): void { @@ -110,19 +113,6 @@ public function controllers(array $controllers): void /** * @param array $controllers - * - * @throws InvalidArgumentException - * @deprecated please use addControllers() instead it. - */ - public function setControllers(array $controllers): void - { - $this->addControllers($controllers); - } - - /** - * @param array $controllers - * - * @throws InvalidArgumentException */ public function addControllers(array $controllers): void { @@ -130,7 +120,11 @@ public function addControllers(array $controllers): void } /** - * {@inheritdoc} + * @param string $name + * @param class-string|CommandInterface|null|Closure():void $handler + * @param array{desc: string, aliases: array, options: array, arguments: array} $config config the command. + * + * @return Application */ public function command(string $name, string|Closure|CommandInterface $handler = null, array $config = []): static { @@ -144,21 +138,19 @@ public function command(string $name, string|Closure|CommandInterface $handler = * add command * * @param string $name - * @param mixed|null $handler - * @param array $config + * @param class-string|CommandInterface|null|Closure():void $handler + * @param array{desc: string, aliases: array, options: array, arguments: array} $config config the command. * * @return Application * @see command() */ - public function addCommand(string $name, mixed $handler = null, array $config = []): self + public function addCommand(string $name, string|Closure|CommandInterface $handler = null, array $config = []): static { return $this->command($name, $handler, $config); } /** - * @param array $commands - * - * @throws InvalidArgumentException + * @param array{string, mixed} $commands */ public function addCommands(array $commands): void { @@ -166,9 +158,7 @@ public function addCommands(array $commands): void } /** - * @param array $commands - * - * @throws InvalidArgumentException + * @param array{string, mixed} $commands */ public function commands(array $commands): void { @@ -186,14 +176,14 @@ public function commands(array $commands): void * @param string $basePath * * @return $this - * @throws InvalidArgumentException */ - public function registerCommands(string $namespace, string $basePath): self + public function registerCommands(string $namespace, string $basePath): static { - $length = strlen($basePath) + 1; - $iterator = Helper::directoryIterator($basePath, $this->getFileFilter()); + $length = strlen($basePath) + 1; + // $iterator = Helper::directoryIterator($basePath, $this->getFileFilter()); + $iter = Dir::getIterator($basePath, Dir::getPhpFileFilter()); - foreach ($iterator as $file) { + foreach ($iter as $file) { $class = $namespace . '\\' . substr($file->getPathName(), $length, -4); $this->addCommand($class); } @@ -212,10 +202,11 @@ public function registerCommands(string $namespace, string $basePath): self */ public function registerGroups(string $namespace, string $basePath): self { - $length = strlen($basePath) + 1; - $iterator = Helper::directoryIterator($basePath, $this->getFileFilter()); + $length = strlen($basePath) + 1; + // $iterator = Helper::directoryIterator($basePath, $this->getFileFilter()); + $iter = Dir::getIterator($basePath, Dir::getPhpFileFilter()); - foreach ($iterator as $file) { + foreach ($iter as $file) { $class = $namespace . '\\' . substr($file->getPathName(), $length, -4); $this->addController($class); } @@ -223,29 +214,6 @@ public function registerGroups(string $namespace, string $basePath): self return $this; } - /** - * @return Closure - */ - protected function getFileFilter(): callable - { - return static function (SplFileInfo $f) { - $name = $f->getFilename(); - - // Skip hidden files and directories. - if (str_starts_with($name, '.')) { - return false; - } - - // go on read sub-dir - if ($f->isDir()) { - return true; - } - - // php file - return $f->isFile() && str_ends_with($name, '.php'); - }; - } - /**************************************************************************** * Dispatch and run console controller/command ****************************************************************************/ @@ -298,39 +266,49 @@ public function dispatch(string $name, array $args = []): mixed } // save command ID - $cmdOptions = $info['options']; - unset($info['options']); + $cmdConf = $info['config']; + unset($info['config']); $this->input->setCommandId($info['cmdId']); // is command if ($info['type'] === Router::TYPE_SINGLE) { - return $this->runCommand($info, $cmdOptions, $args); + return $this->runCommand($info, $cmdConf, $args); } // is controller/group - return $this->runAction($info, $cmdOptions, $args); + return $this->runAction($info, $cmdConf, $args); } /** * run a independent command * * @param array{name: string, handler: mixed, realName: string} $info - * @param array $options + * @param array{aliases: array, desc: string, options: array, arguments: array} $config The config. * @param array $args * * @return mixed * @throws Throwable */ - protected function runCommand(array $info, array $options, array $args): mixed + protected function runCommand(array $info, array $config, array $args): mixed { - /** @var Closure|string $handler Command class or handler func */ + /** @var Closure|class-string{Command} $handler Command class or handler func */ $handler = $info['handler']; + $iptName = $info['name']; if (is_object($handler) && method_exists($handler, '__invoke')) { $fs = SFlags::new(); + $fs->setName($iptName); $fs->addOptsByRules(GlobalOption::getAloneOptions()); - $desc = $options['desc'] ?? 'No command description message'; - $fs->setDesc($desc); + + // command flags load + if ($cmdOpts = $config['options'] ?? null) { + $fs->addOptsByRules($cmdOpts); + } + if ($cmdArgs = $config['arguments'] ?? null) { + $fs->addArgsByRules($cmdArgs); + } + + $fs->setDesc($config['desc'] ?? 'No command description message'); // save to input object $this->input->setFs($fs); @@ -339,21 +317,17 @@ protected function runCommand(array $info, array $options, array $args): mixed return 0; // render help } - $result = $handler($this->input, $this->output); + $result = $handler($fs, $this->output); } else { - if (!class_exists($handler)) { - Helper::throwInvalidArgument("The console command class [$handler] not exists!"); - } + Assert::isTrue(class_exists($handler), "The console command class [$handler] not exists!"); - /** @var Command $object */ $object = new $handler($this->input, $this->output); - if (!($object instanceof Command)) { - Helper::throwInvalidArgument("The console command class [$handler] must instanceof the " . Command::class); - } + Assert::isTrue($object instanceof Command, "Command class [$handler] must instanceof the " . Command::class); + /** @var Command $object */ $object::setName($info['cmdId']); // real command name. $object->setApp($this); - $object->setCommandName($info['name']); + $object->setCommandName($iptName); $result = $object->run($args); } @@ -363,21 +337,18 @@ protected function runCommand(array $info, array $options, array $args): mixed /** * Execute an action in a group command(controller) * - * @param array $info Matched route info - * - * @psalm-param array{action: string} $info Matched route info - * - * @param array $options + * @param array{action: string} $info Matched route info + * @param array $config * @param array $args * @param bool $detachedRun * * @return mixed * @throws Throwable */ - protected function runAction(array $info, array $options, array $args, bool $detachedRun = false): mixed + protected function runAction(array $info, array $config, array $args, bool $detachedRun = false): mixed { $controller = $this->createController($info); - $controller::setDesc($options['desc'] ?? ''); + $controller::setDesc($config['desc'] ?? ''); if ($detachedRun) { $controller->setDetached(); @@ -408,7 +379,7 @@ public function getController(string $name): Controller } /** - * @param array $info + * @param array{name: string, group: string, handler: mixed} $info * * @return Controller */ diff --git a/src/Component/Formatter/HelpPanel.php b/src/Component/Formatter/HelpPanel.php index d2dc9fe1..531244ff 100644 --- a/src/Component/Formatter/HelpPanel.php +++ b/src/Component/Formatter/HelpPanel.php @@ -89,17 +89,13 @@ public static function show(array $config): void $config = array_merge([ 'desc' => '', 'usage' => '', - 'commands' => [], 'arguments' => [], 'options' => [], - - 'examples' => [], - + 'examples' => [], // extra - 'extras' => [], - - '_opts' => [], + 'extras' => [], + '_opts' => [], ], $config); // some option for show. diff --git a/src/Component/PharCompiler.php b/src/Component/PharCompiler.php index 5d7947ff..58653b05 100644 --- a/src/Component/PharCompiler.php +++ b/src/Component/PharCompiler.php @@ -24,6 +24,7 @@ use Seld\PharUtils\Timestamps; use SplFileInfo; use SplQueue; +use Toolkit\FsUtil\Dir; use Toolkit\FsUtil\File; use Toolkit\Sys\Sys; use Traversable; @@ -637,11 +638,10 @@ public function findChangedByGit(): ?Generator * @param string $directory * * @return Iterator - * @throws InvalidArgumentException */ protected function findFiles(string $directory): Iterator { - return Helper::directoryIterator( + return Dir::getIterator( $directory, $this->getIteratorFilter(), FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS diff --git a/src/Component/Router.php b/src/Component/Router.php index e4eb1f69..5cbe1d33 100644 --- a/src/Component/Router.php +++ b/src/Component/Router.php @@ -15,8 +15,11 @@ use Inhere\Console\Contract\ControllerInterface; use Inhere\Console\Contract\RouterInterface; use Inhere\Console\Controller; +use Inhere\Console\IO\Output; use Inhere\Console\Util\Helper; use InvalidArgumentException; +use Toolkit\PFlag\FlagsParser; +use Toolkit\Stdlib\Helper\Assert; use Toolkit\Stdlib\Obj\Traits\NameAliasTrait; use function array_keys; use function array_merge; @@ -56,26 +59,41 @@ class Router implements RouterInterface /** * The independent commands * - * @var array + * ```php * [ * 'name' => [ + * 'type' => 2, * 'handler' => MyCommand::class, - * 'options' => [] + * 'config' => [ + * 'desc' => 'string', + * 'options' => [], + * 'arguments' => [], + * ] * ] * ] + * ``` + * + * + * @var array */ private array $commands = []; /** * The group commands(controller) * - * @var array + * ```php * [ * 'name' => [ + * 'type' => 1, * 'handler' => MyController::class, - * 'options' => [] + * 'config' => [ + * 'desc' => 'string', + * ] * ] * ] + * ``` + * + * @var array */ private array $controllers = []; @@ -129,24 +147,25 @@ public function addGroup(string $name, ControllerInterface|string $class = null, return $this; } - $options['aliases'] = isset($options['aliases']) ? (array)$options['aliases'] : []; + $config['aliases'] = isset($config['aliases']) ? (array)$config['aliases'] : []; // allow define aliases in group class by Controller::aliases() if ($aliases = $class::aliases()) { - $options['aliases'] = array_merge($options['aliases'], $aliases); + $config['aliases'] = array_merge($config['aliases'], $aliases); + } + + // has alias option + if ($config['aliases']) { + $this->setAlias($name, $config['aliases'], true); } + // $this->controllers[$name] = $config; $this->controllers[$name] = [ 'type' => self::TYPE_GROUP, 'handler' => $class, - 'options' => $options, + 'config' => $config, ]; - // has alias option - if ($options['aliases']) { - $this->setAlias($name, $options['aliases'], true); - } - return $this; } @@ -154,8 +173,8 @@ public function addGroup(string $name, ControllerInterface|string $class = null, * Register a app independent console command * * @param string|class-string $name - * @param string|Closure|CommandInterface|null $handler - * @param array{aliases: array, desc: string} $config + * @param string|CommandInterface|null|Closure(FlagsParser, Output):void $handler + * @param array{aliases: array, desc: string, options: array, arguments: array} $config * * @return static */ @@ -163,23 +182,16 @@ public function addCommand(string $name, string|Closure|CommandInterface $handle { if (!$handler && class_exists($name)) { $handler = $name; - $name = $name::getName(); + /** @var Command $name name is an command class */ + $name = $name::getName(); } - /** - * @var Command $name name is an command class - */ - if (!$name || !$handler) { - Helper::throwInvalidArgument("Command 'name' and 'handler' cannot be empty! name: $name"); - } + Assert::isFalse(!$name || !$handler, "Command 'name' and 'handler' cannot be empty! name: $name"); + Assert::isFalse(isset($this->commands[$name]), "Command '$name' have been registered!"); $this->validateName($name); - if (isset($this->commands[$name])) { - Helper::throwInvalidArgument("Command '$name' have been registered!"); - } - - $options['aliases'] = isset($options['aliases']) ? (array)$options['aliases'] : []; + $config['aliases'] = isset($config['aliases']) ? (array)$config['aliases'] : []; if (is_string($handler)) { if (!class_exists($handler)) { @@ -198,7 +210,7 @@ public function addCommand(string $name, string|Closure|CommandInterface $handle // allow define aliases in Command class by Command::aliases() if ($aliases = $handler::aliases()) { - $options['aliases'] = array_merge($options['aliases'], $aliases); + $config['aliases'] = array_merge($config['aliases'], $aliases); } } elseif (!is_object($handler) || !method_exists($handler, '__invoke')) { Helper::throwInvalidArgument( @@ -207,18 +219,19 @@ public function addCommand(string $name, string|Closure|CommandInterface $handle ); } + // has alias option + if ($config['aliases']) { + $this->setAlias($name, $config['aliases'], true); + } + // is an class name string + // $this->commands[$name] = $config; $this->commands[$name] = [ 'type' => self::TYPE_SINGLE, 'handler' => $handler, - 'options' => $options, + 'config' => $config, ]; - // has alias option - if ($options['aliases']) { - $this->setAlias($name, $options['aliases'], true); - } - return $this; } @@ -259,25 +272,47 @@ public function addControllers(array $controllers): void **********************************************************/ /** + * match a command or group + * + * returns examples: + * + * - Command + * + * ```php + * return [ + * type => 2, // 1 group 2 command + * name => '', // input command name. + * cmdId => '', // format and resolved $name + * // common info + * handler => handler class/object/func ... + * aliases => [], + * desc => '', + * ] + * ``` + * + * - Group/Controller + * * ```php * return [ * type => 1, // 1 group 2 command - * name => '', // input group/command name. + * name => 'git', // input command name. * cmdId => '', // format and resolved $name * // for group - * group => '', // group name. - * sub => '', // input subcommand name. on name is group. + * group => 'git', // group name. + * sub => 'clone', // input subcommand name. on name is group. * // common info * handler => handler class/object/func ... - * options => [ - * aliases => [], - * description => '', + * aliases => [], + * config => [ + * desc => '', * ], * ] * ``` + * * @param string $name The input command name * - * @return array return route info array. If not found, will return empty array. + * @return array{type: string, name:string, cmdId: string, config: array, handler: mixed} return route info. + * If not found, will return empty array. */ public function match(string $name): array { diff --git a/src/Controller.php b/src/Controller.php index d3ffad55..95c6b1ad 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -16,7 +16,6 @@ use Inhere\Console\Handler\AbstractHandler; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; -use Inhere\Console\Util\FormatUtil; use Inhere\Console\Util\Helper; use ReflectionClass; use ReflectionException; @@ -520,7 +519,7 @@ protected function showHelp(): bool */ protected function beforeRenderCommandHelp(array &$help): void { - $help['Group Options:'] = FormatUtil::alignOptions($this->flags->getOptsHelpLines()); + $help['Group Options:'] = FlagUtil::alignOptions($this->flags->getOptsHelpLines()); } /** diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index e4da452d..2e9ed2c8 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -10,11 +10,11 @@ namespace Inhere\Console\Util; use Toolkit\Cli\Color\ColorTag; +use Toolkit\PFlag\FlagUtil; use Toolkit\Stdlib\Arr\ArrayHelper; use Toolkit\Stdlib\Helper\JsonHelper; use Toolkit\Stdlib\Str; use Toolkit\Sys\Sys; -use function array_keys; use function array_merge; use function array_shift; use function explode; @@ -24,10 +24,8 @@ use function is_int; use function is_numeric; use function is_scalar; -use function preg_match; use function rtrim; use function str_repeat; -use function str_replace; use function strpos; use function trim; use function ucfirst; @@ -145,33 +143,7 @@ public static function wrapText(string $text, int $indent = 0, int $width = 0): */ public static function alignOptions(array $options): array { - if (!$options) { - return []; - } - - // check has short option. e.g '-h, --help' - $nameString = implode('|', array_keys($options)); - if (preg_match('/\|-\w/', $nameString) !== 1) { - return $options; - } - - $formatted = []; - foreach ($options as $name => $des) { - if (!$name = trim($name, ', ')) { - continue; - } - - // start with '--', padding length equals to '-h, ' - if (isset($name[1]) && $name[1] === '-') { - $name = ' ' . $name; - } else { - $name = str_replace([',-'], [', -'], $name); - } - - $formatted[$name] = $des; - } - - return $formatted; + return FlagUtil::alignOptions($options); } /** From ef50e5c0115befd4c9c19186219f8bd0efffaec4 Mon Sep 17 00:00:00 2001 From: Inhere Date: Sat, 25 Dec 2021 11:47:32 +0800 Subject: [PATCH 213/258] chore: replace all description prop to desc --- examples/Command/CorCommand.php | 2 +- examples/Command/DemoCommand.php | 3 +-- examples/Command/TestCommand.php | 4 ++-- examples/Controller/HomeController.php | 2 +- examples/Controller/InteractController.php | 2 +- examples/Controller/ProcessController.php | 2 +- examples/Controller/ShowController.php | 2 +- src/BuiltIn/DevServerCommand.php | 2 +- src/BuiltIn/PharController.php | 2 +- src/BuiltIn/SelfUpdateCommand.php | 2 +- 10 files changed, 11 insertions(+), 12 deletions(-) diff --git a/examples/Command/CorCommand.php b/examples/Command/CorCommand.php index 4a2bfa92..0a6a4236 100644 --- a/examples/Command/CorCommand.php +++ b/examples/Command/CorCommand.php @@ -22,7 +22,7 @@ class CorCommand extends Command { protected static string $name = 'cor'; - protected static string $description = 'a coroutine test command'; + protected static string $desc = 'a coroutine test command'; protected static bool $coroutine = true; diff --git a/examples/Command/DemoCommand.php b/examples/Command/DemoCommand.php index 28c948e6..5cad48da 100644 --- a/examples/Command/DemoCommand.php +++ b/examples/Command/DemoCommand.php @@ -23,11 +23,10 @@ class DemoCommand extends Command { protected static string $name = 'demo'; - protected static string $description = 'this is a demo alone command. but use Definition instead of annotations'; + protected static string $desc = 'this is a demo alone command. but use Definition instead of annotations'; /** * {@inheritDoc} - * @throws LogicException */ protected function configure(): void { diff --git a/examples/Command/TestCommand.php b/examples/Command/TestCommand.php index 8b1e4430..6725dc9e 100644 --- a/examples/Command/TestCommand.php +++ b/examples/Command/TestCommand.php @@ -21,12 +21,12 @@ class TestCommand extends Command { protected static string $name = 'test'; - protected static string $description = 'this is a test independent command'; + protected static string $desc = 'this is a test independent command'; protected function commands(): array { return [ - 'sub' => static function ($in, $out): void { + 'sub' => static function ($fs, $out): void { $out->println('hello, this is an sub command of test.'); }, ]; diff --git a/examples/Controller/HomeController.php b/examples/Controller/HomeController.php index e5b24f2c..05cc45b3 100644 --- a/examples/Controller/HomeController.php +++ b/examples/Controller/HomeController.php @@ -34,7 +34,7 @@ class HomeController extends Controller { protected static string $name = 'home'; - protected static string $description = 'This is a demo command controller. there are some command usage examples(2)'; + protected static string $desc = 'This is a demo command controller. there are some command usage examples(2)'; public static function aliases(): array { diff --git a/examples/Controller/InteractController.php b/examples/Controller/InteractController.php index 04e797b2..26044b2a 100644 --- a/examples/Controller/InteractController.php +++ b/examples/Controller/InteractController.php @@ -24,7 +24,7 @@ class InteractController extends Controller { protected static string $name = 'interact'; - protected static string $description = 'there are some demo commands for use interactive method'; + protected static string $desc = 'there are some demo commands for use interactive method'; public static function aliases(): array { diff --git a/examples/Controller/ProcessController.php b/examples/Controller/ProcessController.php index 2e2ea66e..e046746c 100644 --- a/examples/Controller/ProcessController.php +++ b/examples/Controller/ProcessController.php @@ -23,7 +23,7 @@ class ProcessController extends Controller { protected static string $name = 'process'; - protected static string $description = 'Some simple process to create and use examples'; + protected static string $desc = 'Some simple process to create and use examples'; protected static function commandAliases(): array { diff --git a/examples/Controller/ShowController.php b/examples/Controller/ShowController.php index 3ea1a70c..9ed5b8ae 100644 --- a/examples/Controller/ShowController.php +++ b/examples/Controller/ShowController.php @@ -29,7 +29,7 @@ class ShowController extends Controller { protected static string $name = 'show'; - protected static string $description = 'there are some demo commands for show format data'; + protected static string $desc = 'there are some demo commands for show format data'; public static function commandAliases(): array { diff --git a/src/BuiltIn/DevServerCommand.php b/src/BuiltIn/DevServerCommand.php index 6acd2db1..7bea21d4 100644 --- a/src/BuiltIn/DevServerCommand.php +++ b/src/BuiltIn/DevServerCommand.php @@ -25,7 +25,7 @@ class DevServerCommand extends Command { protected static string $name = 'dev:server'; - protected static string $description = 'Start a php built-in http server for development'; + protected static string $desc = 'Start a php built-in http server for development'; public static function aliases(): array { diff --git a/src/BuiltIn/PharController.php b/src/BuiltIn/PharController.php index a7732bfe..20a6f4c4 100644 --- a/src/BuiltIn/PharController.php +++ b/src/BuiltIn/PharController.php @@ -36,7 +36,7 @@ class PharController extends Controller { protected static string $name = 'phar'; - protected static string $description = 'Pack a project directory to phar or unpack phar to directory'; + protected static string $desc = 'Pack a project directory to phar or unpack phar to directory'; /** * @var Closure|null diff --git a/src/BuiltIn/SelfUpdateCommand.php b/src/BuiltIn/SelfUpdateCommand.php index 758c4525..0d5f0e7e 100644 --- a/src/BuiltIn/SelfUpdateCommand.php +++ b/src/BuiltIn/SelfUpdateCommand.php @@ -37,7 +37,7 @@ class SelfUpdateCommand extends Command protected static string $name = 'self-update'; - protected static string $description = 'Update phar package to most recent stable, pre-release or development build.'; + protected static string $desc = 'Update phar package to most recent stable, pre-release or development build.'; /** * @var string From c913803666a688e4102aad63f3254eb6607443d2 Mon Sep 17 00:00:00 2001 From: Inhere Date: Sat, 25 Dec 2021 11:50:39 +0800 Subject: [PATCH 214/258] refactor: move some trait class to Decorate dir --- src/AbstractApplication.php | 4 ++-- src/Application.php | 5 +++-- src/Concern/AdvancedFormatOutputTrait.php | 19 ------------------- src/Controller.php | 4 ++-- .../ApplicationHelpTrait.php | 4 ++-- .../CommandHelpTrait.php | 4 ++-- .../ControllerHelpTrait.php | 16 ++++++++-------- .../FormatOutputAwareTrait.php | 4 ++-- .../RuntimeProfileTrait.php | 4 ++-- .../StyledOutputAwareTrait.php | 4 ++-- .../SubCommandsWareTrait.php | 4 ++-- .../UserInteractAwareTrait.php | 4 ++-- src/Handler/AbstractHandler.php | 17 +++++++++-------- src/IO/Output.php | 2 +- src/Util/Helper.php | 12 +----------- src/Util/PhpDevServe.php | 3 ++- test/TestCommand.php | 2 +- test/TestController.php | 2 +- 18 files changed, 44 insertions(+), 70 deletions(-) delete mode 100644 src/Concern/AdvancedFormatOutputTrait.php rename src/{Concern => Decorate}/ApplicationHelpTrait.php (99%) rename src/{Concern => Decorate}/CommandHelpTrait.php (98%) rename src/{Concern => Decorate}/ControllerHelpTrait.php (93%) rename src/{Concern => Decorate}/FormatOutputAwareTrait.php (98%) rename src/{Concern => Decorate}/RuntimeProfileTrait.php (97%) rename src/{Concern => Decorate}/StyledOutputAwareTrait.php (98%) rename src/{Concern => Decorate}/SubCommandsWareTrait.php (98%) rename src/{Concern => Decorate}/UserInteractAwareTrait.php (98%) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index adf959d6..371dd678 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -13,12 +13,12 @@ use Inhere\Console\Component\ErrorHandler; use Inhere\Console\Component\Router; use Inhere\Console\Component\Formatter\Title; -use Inhere\Console\Concern\ApplicationHelpTrait; use Inhere\Console\Concern\InputOutputAwareTrait; use Inhere\Console\Concern\SimpleEventAwareTrait; -use Inhere\Console\Concern\StyledOutputAwareTrait; use Inhere\Console\Contract\ApplicationInterface; use Inhere\Console\Contract\ErrorHandlerInterface; +use Inhere\Console\Decorate\ApplicationHelpTrait; +use Inhere\Console\Decorate\StyledOutputAwareTrait; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; use Inhere\Console\Util\Helper; diff --git a/src/Application.php b/src/Application.php index 5888d04a..eaad0332 100644 --- a/src/Application.php +++ b/src/Application.php @@ -20,6 +20,7 @@ use RuntimeException; use Throwable; use Toolkit\FsUtil\Dir; +use Toolkit\PFlag\FlagsParser; use Toolkit\PFlag\SFlags; use Toolkit\Stdlib\Helper\Assert; use Toolkit\Stdlib\Helper\DataHelper; @@ -121,7 +122,7 @@ public function addControllers(array $controllers): void /** * @param string $name - * @param class-string|CommandInterface|null|Closure():void $handler + * @param class-string|CommandInterface|null|Closure(FlagsParser, Output):void $handler * @param array{desc: string, aliases: array, options: array, arguments: array} $config config the command. * * @return Application @@ -138,7 +139,7 @@ public function command(string $name, string|Closure|CommandInterface $handler = * add command * * @param string $name - * @param class-string|CommandInterface|null|Closure():void $handler + * @param class-string|CommandInterface|null|Closure(FlagsParser, Output):void $handler * @param array{desc: string, aliases: array, options: array, arguments: array} $config config the command. * * @return Application diff --git a/src/Concern/AdvancedFormatOutputTrait.php b/src/Concern/AdvancedFormatOutputTrait.php deleted file mode 100644 index 75ce23c9..00000000 --- a/src/Concern/AdvancedFormatOutputTrait.php +++ /dev/null @@ -1,19 +0,0 @@ -logf(Console::VERB_DEBUG, 'display all sub-commands list of the group: %s', static::$name); - + $name = self::getName(); + $this->logf(Console::VERB_DEBUG, 'display all sub-commands list of the group: %s', $name); $this->beforeShowCommandList(); - $ref = new ReflectionClass($this); - $sName = self::getName() ?: lcfirst($ref->getShortName()); + $refCls = new ReflectionClass($this); + $sName = $name ?: lcfirst($refCls->getShortName()); if (!($classDes = self::getDesc())) { - $classDes = PhpDoc::description($ref->getDocComment()) ?: 'No description for the command group'; + $classDes = PhpDoc::description($refCls->getDocComment()) ?: 'No description for the command group'; } $commands = []; @@ -109,7 +109,7 @@ public function showCommandList(): void /** * @var $cmd string The command name. */ - foreach ($this->getAllCommandMethods($ref) as $cmd => $m) { + foreach ($this->getAllCommandMethods($refCls) as $cmd => $m) { if (!$cmd) { continue; } diff --git a/src/Concern/FormatOutputAwareTrait.php b/src/Decorate/FormatOutputAwareTrait.php similarity index 98% rename from src/Concern/FormatOutputAwareTrait.php rename to src/Decorate/FormatOutputAwareTrait.php index 08652f59..62ab5ce6 100644 --- a/src/Concern/FormatOutputAwareTrait.php +++ b/src/Decorate/FormatOutputAwareTrait.php @@ -7,7 +7,7 @@ * @license https://github.com/inhere/php-console/blob/master/LICENSE */ -namespace Inhere\Console\Concern; +namespace Inhere\Console\Decorate; use Inhere\Console\Component\Formatter\JSONPretty; use Inhere\Console\Console; @@ -21,7 +21,7 @@ /** * Class FormatOutputAwareTrait * - * @package Inhere\Console\Traits + * @package Inhere\Console\Decorate */ trait FormatOutputAwareTrait { diff --git a/src/Concern/RuntimeProfileTrait.php b/src/Decorate/RuntimeProfileTrait.php similarity index 97% rename from src/Concern/RuntimeProfileTrait.php rename to src/Decorate/RuntimeProfileTrait.php index 7cb36092..3dd3524f 100644 --- a/src/Concern/RuntimeProfileTrait.php +++ b/src/Decorate/RuntimeProfileTrait.php @@ -7,7 +7,7 @@ * @license https://github.com/inhere/php-console/blob/master/LICENSE */ -namespace Inhere\Console\Concern; +namespace Inhere\Console\Decorate; use InvalidArgumentException; use Toolkit\Stdlib\Helper\PhpHelper; @@ -20,7 +20,7 @@ /** * Trait RuntimeProfileTrait * - * @package Inhere\Library\Concern + * @package Inhere\Console\Decorate */ trait RuntimeProfileTrait { diff --git a/src/Concern/StyledOutputAwareTrait.php b/src/Decorate/StyledOutputAwareTrait.php similarity index 98% rename from src/Concern/StyledOutputAwareTrait.php rename to src/Decorate/StyledOutputAwareTrait.php index d1e6fa7e..99b47604 100644 --- a/src/Concern/StyledOutputAwareTrait.php +++ b/src/Decorate/StyledOutputAwareTrait.php @@ -7,7 +7,7 @@ * @license https://github.com/inhere/php-console/blob/master/LICENSE */ -namespace Inhere\Console\Concern; +namespace Inhere\Console\Decorate; use Closure; use Generator; @@ -29,7 +29,7 @@ /** * Trait StyledOutputAwareTrait * - * @package Inhere\Console\Concern + * @package Inhere\Console\Decorate * * @method int info($messages, $quit = false) * @method int note($messages, $quit = false) diff --git a/src/Concern/SubCommandsWareTrait.php b/src/Decorate/SubCommandsWareTrait.php similarity index 98% rename from src/Concern/SubCommandsWareTrait.php rename to src/Decorate/SubCommandsWareTrait.php index da73f184..818bb2a0 100644 --- a/src/Concern/SubCommandsWareTrait.php +++ b/src/Decorate/SubCommandsWareTrait.php @@ -7,7 +7,7 @@ * @license https://github.com/inhere/php-console/blob/master/LICENSE */ -namespace Inhere\Console\Concern; +namespace Inhere\Console\Decorate; use Closure; use Inhere\Console\Command; @@ -30,7 +30,7 @@ /** * Trait SubCommandsWareTrait * - * @package Inhere\Console\Concern + * @package Inhere\Console\Decorate */ trait SubCommandsWareTrait { diff --git a/src/Concern/UserInteractAwareTrait.php b/src/Decorate/UserInteractAwareTrait.php similarity index 98% rename from src/Concern/UserInteractAwareTrait.php rename to src/Decorate/UserInteractAwareTrait.php index 3f8782cd..c1c438a7 100644 --- a/src/Concern/UserInteractAwareTrait.php +++ b/src/Decorate/UserInteractAwareTrait.php @@ -7,7 +7,7 @@ * @license https://github.com/inhere/php-console/blob/master/LICENSE */ -namespace Inhere\Console\Concern; +namespace Inhere\Console\Decorate; use Closure; use Inhere\Console\Util\Interact; @@ -17,7 +17,7 @@ /** * Class UserInteractAwareTrait * - * @package Inhere\Console\Concern + * @package Inhere\Console\Decorate * @see Interact * * @method string readRow($message = null, $nl = false) diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index ab5776b5..45cc345f 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -12,14 +12,14 @@ use Inhere\Console\Annotate\DocblockRules; use Inhere\Console\Component\ErrorHandler; use Inhere\Console\Concern\AttachApplicationTrait; -use Inhere\Console\Concern\CommandHelpTrait; use Inhere\Console\Concern\InputOutputAwareTrait; -use Inhere\Console\Concern\SubCommandsWareTrait; -use Inhere\Console\Concern\UserInteractAwareTrait; +use Inhere\Console\Decorate\UserInteractAwareTrait; use Inhere\Console\Console; use Inhere\Console\ConsoleEvent; use Inhere\Console\Contract\CommandHandlerInterface; use Inhere\Console\Contract\CommandInterface; +use Inhere\Console\Decorate\CommandHelpTrait; +use Inhere\Console\Decorate\SubCommandsWareTrait; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; use Inhere\Console\Util\Helper; @@ -54,22 +54,23 @@ abstract class AbstractHandler implements CommandHandlerInterface use SubCommandsWareTrait; /** - * group/command name e.g 'test' 'test:one' + * The group/command name e.g 'test' 'test:one' * * @var string */ protected static string $name = ''; /** - * command/controller description message - * please use the property setting current controller/command description + * The command/controller description message. + * + * TIP: please use the property setting current controller/command description * * @var string */ protected static string $desc = ''; /** - * command/controller description message + * The command/controller description message * * @var string * @deprecated please use {@see $desc} @@ -327,7 +328,7 @@ public function run(array $args): mixed * * @param array $args * - * @return int|mixed + * @return mixed */ protected function doRun(array $args): mixed { diff --git a/src/IO/Output.php b/src/IO/Output.php index 87b5bffe..a06eca34 100644 --- a/src/IO/Output.php +++ b/src/IO/Output.php @@ -9,7 +9,7 @@ namespace Inhere\Console\IO; -use Inhere\Console\Concern\FormatOutputAwareTrait; +use Inhere\Console\Decorate\FormatOutputAwareTrait; use Inhere\Console\Console; use Inhere\Console\IO\Output\StreamOutput; use Toolkit\Cli\Cli; diff --git a/src/Util/Helper.php b/src/Util/Helper.php index 216a3f91..4d0a758a 100644 --- a/src/Util/Helper.php +++ b/src/Util/Helper.php @@ -10,7 +10,7 @@ namespace Inhere\Console\Util; use FilesystemIterator; -use Inhere\Console\Concern\RuntimeProfileTrait; +use Inhere\Console\Decorate\RuntimeProfileTrait; use Inhere\Console\ConsoleConst; use InvalidArgumentException; use RecursiveCallbackFilterIterator; @@ -69,16 +69,6 @@ public static function hasMode(int $haystack, int $value): bool return ($haystack & $value) > 0; } - /** - * @param string $path - * - * @return bool - */ - public static function isAbsPath(string $path): bool - { - return str_starts_with($path, '/') || 1 === preg_match('#^[a-z]:[\/|\\\]{1}.+#i', $path); - } - /** * @param string $name */ diff --git a/src/Util/PhpDevServe.php b/src/Util/PhpDevServe.php index 5411597b..da8890db 100644 --- a/src/Util/PhpDevServe.php +++ b/src/Util/PhpDevServe.php @@ -13,6 +13,7 @@ use Exception; use RuntimeException; use Toolkit\Cli\Cli; +use Toolkit\FsUtil\Path; use Toolkit\Stdlib\Json; use Toolkit\Sys\Sys; use function explode; @@ -189,7 +190,7 @@ protected function printDefaultMessage(): void $docRoot = $workDir; if ($this->docRoot) { $docRoot = $this->docRoot; - $docRoot = Helper::isAbsPath($docRoot) ? $docRoot : $workDir . '/' . $docRoot; + $docRoot = Path::isAbsPath($docRoot) ? $docRoot : $workDir . '/' . $docRoot; } Cli::writeln([ diff --git a/test/TestCommand.php b/test/TestCommand.php index f8277ab9..f854461d 100644 --- a/test/TestCommand.php +++ b/test/TestCommand.php @@ -22,7 +22,7 @@ class TestCommand extends Command { protected static string $name = 'test1'; - protected static string $description = 'command description message'; + protected static string $desc = 'command description message'; /** * do execute command diff --git a/test/TestController.php b/test/TestController.php index 5025568e..01a24300 100644 --- a/test/TestController.php +++ b/test/TestController.php @@ -20,7 +20,7 @@ class TestController extends Controller { protected static string $name = 'test'; - protected static string $description = 'controller description message'; + protected static string $desc = 'controller description message'; /** * this is an demo command in test From bc61390c2066225f37da0f0b93bb0dab0331fc4d Mon Sep 17 00:00:00 2001 From: Inhere Date: Sat, 25 Dec 2021 11:55:20 +0800 Subject: [PATCH 215/258] refactor: move io abstract class to Concern dir --- src/{IO => Concern}/AbstractInput.php | 4 ++-- src/{IO => Concern}/AbstractOutput.php | 5 +++-- src/IO/Input/StreamInput.php | 3 ++- src/IO/Output/BufferedOutput.php | 2 +- src/IO/Output/StreamOutput.php | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) rename src/{IO => Concern}/AbstractInput.php (99%) rename src/{IO => Concern}/AbstractOutput.php (87%) diff --git a/src/IO/AbstractInput.php b/src/Concern/AbstractInput.php similarity index 99% rename from src/IO/AbstractInput.php rename to src/Concern/AbstractInput.php index 39b098d3..9087d75f 100644 --- a/src/IO/AbstractInput.php +++ b/src/Concern/AbstractInput.php @@ -7,7 +7,7 @@ * @license https://github.com/inhere/php-console/blob/master/LICENSE */ -namespace Inhere\Console\IO; +namespace Inhere\Console\Concern; use Inhere\Console\Contract\InputInterface; use Toolkit\Cli\Helper\FlagHelper; @@ -23,7 +23,7 @@ /** * Class AbstractInput * - * @package Inhere\Console\IO + * @package Inhere\Console\Concern */ abstract class AbstractInput implements InputInterface { diff --git a/src/IO/AbstractOutput.php b/src/Concern/AbstractOutput.php similarity index 87% rename from src/IO/AbstractOutput.php rename to src/Concern/AbstractOutput.php index 71905d0d..f55f163c 100644 --- a/src/IO/AbstractOutput.php +++ b/src/Concern/AbstractOutput.php @@ -7,13 +7,14 @@ * @license https://github.com/inhere/php-console/blob/master/LICENSE */ -namespace Inhere\Console\IO; +namespace Inhere\Console\Concern; use Inhere\Console\Contract\OutputInterface; /** * Class AbstractOutput - * @package Inhere\Console\IO + * + * @package Inhere\Console\Concern */ abstract class AbstractOutput implements OutputInterface { diff --git a/src/IO/Input/StreamInput.php b/src/IO/Input/StreamInput.php index f752c9e7..69860247 100644 --- a/src/IO/Input/StreamInput.php +++ b/src/IO/Input/StreamInput.php @@ -9,7 +9,7 @@ namespace Inhere\Console\IO\Input; -use Inhere\Console\IO\AbstractInput; +use Inhere\Console\Concern\AbstractInput; use InvalidArgumentException; use Toolkit\FsUtil\File; use Toolkit\Stdlib\OS; @@ -19,6 +19,7 @@ /** * Class StreamInput + * * @package Inhere\Console\IO\Input */ class StreamInput extends AbstractInput diff --git a/src/IO/Output/BufferedOutput.php b/src/IO/Output/BufferedOutput.php index 86e01e55..f93def42 100644 --- a/src/IO/Output/BufferedOutput.php +++ b/src/IO/Output/BufferedOutput.php @@ -9,7 +9,7 @@ namespace Inhere\Console\IO\Output; -use Inhere\Console\IO\AbstractOutput; +use Inhere\Console\Concern\AbstractOutput; use function strlen; use const PHP_EOL; diff --git a/src/IO/Output/StreamOutput.php b/src/IO/Output/StreamOutput.php index 8478f8aa..c5e86dca 100644 --- a/src/IO/Output/StreamOutput.php +++ b/src/IO/Output/StreamOutput.php @@ -9,7 +9,7 @@ namespace Inhere\Console\IO\Output; -use Inhere\Console\IO\AbstractOutput; +use Inhere\Console\Concern\AbstractOutput; use InvalidArgumentException; use Toolkit\FsUtil\File; use Toolkit\Stdlib\Helper\DataHelper; From 7259524fc0a13e8b142ac509446900caba221110 Mon Sep 17 00:00:00 2001 From: Inhere Date: Sat, 25 Dec 2021 12:06:06 +0800 Subject: [PATCH 216/258] refactor: move some abstract class to Concern dir --- src/AbstractApplication.php | 6 +++--- src/Component/Interact/AbstractSelect.php | 2 +- src/Component/Interact/Confirm.php | 2 +- src/Component/Interact/IShell.php | 2 +- src/Component/Interact/LimitedAsk.php | 2 +- src/Component/Interact/Password.php | 2 +- src/Component/Interact/Question.php | 2 +- src/Component/Interact/Terminal.php | 2 +- src/Component/Symbol/GitEmoji.php | 16 ++++++++++++++++ .../Interact => Concern}/AbstractQuestion.php | 3 +-- src/{Component => Concern}/InteractiveHandle.php | 4 ++-- .../AttachApplicationTrait.php | 9 ++++----- .../InputOutputAwareTrait.php | 4 ++-- .../SimpleEventAwareTrait.php | 4 ++-- src/Handler/AbstractHandler.php | 4 ++-- 15 files changed, 39 insertions(+), 25 deletions(-) create mode 100644 src/Component/Symbol/GitEmoji.php rename src/{Component/Interact => Concern}/AbstractQuestion.php (90%) rename src/{Component => Concern}/InteractiveHandle.php (95%) rename src/{Concern => Decorate}/AttachApplicationTrait.php (96%) rename src/{Concern => Decorate}/InputOutputAwareTrait.php (97%) rename src/{Concern => Decorate}/SimpleEventAwareTrait.php (98%) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 371dd678..9040b122 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -13,10 +13,10 @@ use Inhere\Console\Component\ErrorHandler; use Inhere\Console\Component\Router; use Inhere\Console\Component\Formatter\Title; -use Inhere\Console\Concern\InputOutputAwareTrait; -use Inhere\Console\Concern\SimpleEventAwareTrait; +use Inhere\Console\Decorate\SimpleEventAwareTrait; use Inhere\Console\Contract\ApplicationInterface; use Inhere\Console\Contract\ErrorHandlerInterface; +use Inhere\Console\Decorate\InputOutputAwareTrait; use Inhere\Console\Decorate\ApplicationHelpTrait; use Inhere\Console\Decorate\StyledOutputAwareTrait; use Inhere\Console\IO\Input; @@ -90,7 +90,7 @@ abstract class AbstractApplication implements ApplicationInterface */ protected string $commandName = ''; - /** + /* * @var string Command delimiter char. e.g dev:serve */ diff --git a/src/Component/Interact/AbstractSelect.php b/src/Component/Interact/AbstractSelect.php index 914127b6..df46050f 100644 --- a/src/Component/Interact/AbstractSelect.php +++ b/src/Component/Interact/AbstractSelect.php @@ -9,7 +9,7 @@ namespace Inhere\Console\Component\Interact; -use Inhere\Console\Component\InteractiveHandle; +use Inhere\Console\Concern\InteractiveHandle; /** * class AbstractSelect diff --git a/src/Component/Interact/Confirm.php b/src/Component/Interact/Confirm.php index e1ffdb3c..dd1d0e33 100644 --- a/src/Component/Interact/Confirm.php +++ b/src/Component/Interact/Confirm.php @@ -9,7 +9,7 @@ namespace Inhere\Console\Component\Interact; -use Inhere\Console\Component\InteractiveHandle; +use Inhere\Console\Concern\InteractiveHandle; use Inhere\Console\Console; use Inhere\Console\Util\Show; use function stripos; diff --git a/src/Component/Interact/IShell.php b/src/Component/Interact/IShell.php index 3ebb764b..d04b96c9 100644 --- a/src/Component/Interact/IShell.php +++ b/src/Component/Interact/IShell.php @@ -11,7 +11,7 @@ use Closure; use Inhere\Console\Component\Formatter\Title; -use Inhere\Console\Component\InteractiveHandle; +use Inhere\Console\Concern\InteractiveHandle; use Inhere\Console\Console; use Inhere\Console\Util\Interact; use Inhere\Console\Util\Show; diff --git a/src/Component/Interact/LimitedAsk.php b/src/Component/Interact/LimitedAsk.php index 0d50e9eb..8e0f66e1 100644 --- a/src/Component/Interact/LimitedAsk.php +++ b/src/Component/Interact/LimitedAsk.php @@ -10,7 +10,7 @@ namespace Inhere\Console\Component\Interact; use Closure; -use Inhere\Console\Component\InteractiveHandle; +use Inhere\Console\Concern\InteractiveHandle; use Inhere\Console\Console; use Inhere\Console\Util\Show; use function sprintf; diff --git a/src/Component/Interact/Password.php b/src/Component/Interact/Password.php index 1e09c627..ae83209a 100644 --- a/src/Component/Interact/Password.php +++ b/src/Component/Interact/Password.php @@ -9,7 +9,7 @@ namespace Inhere\Console\Component\Interact; -use Inhere\Console\Component\InteractiveHandle; +use Inhere\Console\Concern\InteractiveHandle; use RuntimeException; use Toolkit\Sys\Sys; use Toolkit\Sys\Util\ShellUtil; diff --git a/src/Component/Interact/Question.php b/src/Component/Interact/Question.php index 21848a6f..ca65bf4a 100644 --- a/src/Component/Interact/Question.php +++ b/src/Component/Interact/Question.php @@ -10,7 +10,7 @@ namespace Inhere\Console\Component\Interact; use Closure; -use Inhere\Console\Component\InteractiveHandle; +use Inhere\Console\Concern\InteractiveHandle; use Inhere\Console\Console; use Inhere\Console\Util\Show; use function trim; diff --git a/src/Component/Interact/Terminal.php b/src/Component/Interact/Terminal.php index 44576579..d6aa5e39 100644 --- a/src/Component/Interact/Terminal.php +++ b/src/Component/Interact/Terminal.php @@ -9,7 +9,7 @@ namespace Inhere\Console\Component\Interact; -use Inhere\Console\Component\InteractiveHandle; +use Inhere\Console\Concern\InteractiveHandle; /** * Class Terminal diff --git a/src/Component/Symbol/GitEmoji.php b/src/Component/Symbol/GitEmoji.php new file mode 100644 index 00000000..4dc77015 --- /dev/null +++ b/src/Component/Symbol/GitEmoji.php @@ -0,0 +1,16 @@ +app = $app; diff --git a/src/Concern/InputOutputAwareTrait.php b/src/Decorate/InputOutputAwareTrait.php similarity index 97% rename from src/Concern/InputOutputAwareTrait.php rename to src/Decorate/InputOutputAwareTrait.php index f5134f69..f99dcae8 100644 --- a/src/Concern/InputOutputAwareTrait.php +++ b/src/Decorate/InputOutputAwareTrait.php @@ -7,7 +7,7 @@ * @license https://github.com/inhere/php-console/blob/master/LICENSE */ -namespace Inhere\Console\Concern; +namespace Inhere\Console\Decorate; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; @@ -16,7 +16,7 @@ /** * Class InputOutputAwareTrait * - * @package Inhere\Console\Concern + * @package Inhere\Console\Decorate */ trait InputOutputAwareTrait { diff --git a/src/Concern/SimpleEventAwareTrait.php b/src/Decorate/SimpleEventAwareTrait.php similarity index 98% rename from src/Concern/SimpleEventAwareTrait.php rename to src/Decorate/SimpleEventAwareTrait.php index 3bec85aa..707a72b9 100644 --- a/src/Concern/SimpleEventAwareTrait.php +++ b/src/Decorate/SimpleEventAwareTrait.php @@ -7,7 +7,7 @@ * @license https://github.com/inhere/php-console/blob/master/LICENSE */ -namespace Inhere\Console\Concern; +namespace Inhere\Console\Decorate; use function count; use function in_array; @@ -15,7 +15,7 @@ /** * Class SimpleEventStaticTrait * - * @package Inhere\Console\Concern + * @package Inhere\Console\Decorate */ trait SimpleEventAwareTrait { diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index 45cc345f..e11fd24f 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -11,8 +11,8 @@ use Inhere\Console\Annotate\DocblockRules; use Inhere\Console\Component\ErrorHandler; -use Inhere\Console\Concern\AttachApplicationTrait; -use Inhere\Console\Concern\InputOutputAwareTrait; +use Inhere\Console\Decorate\AttachApplicationTrait; +use Inhere\Console\Decorate\InputOutputAwareTrait; use Inhere\Console\Decorate\UserInteractAwareTrait; use Inhere\Console\Console; use Inhere\Console\ConsoleEvent; From 4dfe19fe6f9c989b17e2a183e188338df65b3dfd Mon Sep 17 00:00:00 2001 From: Inhere Date: Sat, 25 Dec 2021 12:14:00 +0800 Subject: [PATCH 217/258] fix: uint tests error --- examples/commands.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/commands.php b/examples/commands.php index 2baf7f7b..a348b3fe 100644 --- a/examples/commands.php +++ b/examples/commands.php @@ -40,9 +40,7 @@ $app->command(CorCommand::class); -$app->controller('home', HomeController::class, [ - 'aliases' => ['h'] -]); +$app->controller('home', HomeController::class); $app->controller(ProcessController::class, null, [ 'aliases' => 'prc' From 1119e7fa59c50a1f96e96a94ea0e6f4866b080a5 Mon Sep 17 00:00:00 2001 From: Inhere Date: Sat, 25 Dec 2021 20:10:36 +0800 Subject: [PATCH 218/258] refactor: update handler construct sign, add some tests --- src/Application.php | 64 ++++++++++---------- src/Command.php | 10 +++- src/Component/Router.php | 33 +++++++---- src/Contract/CommandHandlerInterface.php | 7 ++- src/Decorate/AttachApplicationTrait.php | 5 +- src/Decorate/InputOutputAwareTrait.php | 13 +++++ src/Handler/AbstractHandler.php | 22 ++++--- src/Handler/CallableCommand.php | 74 +++++++++++++++++++++++- test/ApplicationTest.php | 15 ++++- test/TestCommand.php | 2 +- 10 files changed, 185 insertions(+), 60 deletions(-) diff --git a/src/Application.php b/src/Application.php index eaad0332..1752bae9 100644 --- a/src/Application.php +++ b/src/Application.php @@ -296,40 +296,46 @@ protected function runCommand(array $info, array $config, array $args): mixed $handler = $info['handler']; $iptName = $info['name']; - if (is_object($handler) && method_exists($handler, '__invoke')) { - $fs = SFlags::new(); - $fs->setName($iptName); - $fs->addOptsByRules(GlobalOption::getAloneOptions()); - - // command flags load - if ($cmdOpts = $config['options'] ?? null) { - $fs->addOptsByRules($cmdOpts); + if (is_object($handler)) { + // Command object + if ($handler instanceof Command) { + $handler->setInputOutput($this->input, $this->output); + $result = $handler->run($args); + } else { // Closure + $fs = SFlags::new(); + $fs->setName($iptName); + $fs->addOptsByRules(GlobalOption::getAloneOptions()); + + // command flags load + if ($cmdOpts = $config['options'] ?? null) { + $fs->addOptsByRules($cmdOpts); + } + if ($cmdArgs = $config['arguments'] ?? null) { + $fs->addArgsByRules($cmdArgs); + } + + $fs->setDesc($config['desc'] ?? 'No command description message'); + + // save to input object + $this->input->setFs($fs); + + if (!$fs->parse($args)) { + return 0; // render help + } + + $result = $handler($fs, $this->output); } - if ($cmdArgs = $config['arguments'] ?? null) { - $fs->addArgsByRules($cmdArgs); - } - - $fs->setDesc($config['desc'] ?? 'No command description message'); - - // save to input object - $this->input->setFs($fs); - - if (!$fs->parse($args)) { - return 0; // render help - } - - $result = $handler($fs, $this->output); } else { Assert::isTrue(class_exists($handler), "The console command class [$handler] not exists!"); - $object = new $handler($this->input, $this->output); - Assert::isTrue($object instanceof Command, "Command class [$handler] must instanceof the " . Command::class); + /** @var $cmd Command */ + $cmd = new $handler($this->input, $this->output); + Assert::isTrue($cmd instanceof Command, "Command class [$handler] must instanceof the " . Command::class); - /** @var Command $object */ - $object::setName($info['cmdId']); // real command name. - $object->setApp($this); - $object->setCommandName($iptName); - $result = $object->run($args); + $cmd::setName($info['cmdId']); // real command name. + $cmd->setApp($this); + $cmd->setCommandName($iptName); + $result = $cmd->run($args); } return $result; diff --git a/src/Command.php b/src/Command.php index 1759f8cd..55263799 100644 --- a/src/Command.php +++ b/src/Command.php @@ -50,6 +50,14 @@ protected function init(): void parent::init(); } + /** + * @return array + */ + public function getArguments(): array + { + return []; + } + /** * @param FlagsParser $fs */ @@ -139,7 +147,7 @@ public function getRealCName(): string /** * Get the group * - * @return Controller + * @return Controller|null */ public function getGroup(): ?Controller { diff --git a/src/Component/Router.php b/src/Component/Router.php index 5cbe1d33..500eebea 100644 --- a/src/Component/Router.php +++ b/src/Component/Router.php @@ -194,16 +194,10 @@ public function addCommand(string $name, string|Closure|CommandInterface $handle $config['aliases'] = isset($config['aliases']) ? (array)$config['aliases'] : []; if (is_string($handler)) { - if (!class_exists($handler)) { - Helper::throwInvalidArgument("The console command class [$handler] not exists!"); - } - - if (!is_subclass_of($handler, Command::class)) { - Helper::throwInvalidArgument('The console command class must is subclass of the: ' . Command::class); - } + Assert::isTrue(class_exists($handler), "The console command class '$handler' not exists!"); + Assert::isTrue(is_subclass_of($handler, Command::class), 'The command class must be subclass of the: ' . Command::class); - // not enable - /** @var Command $handler */ + /** @var Command $handler not enable */ if (!$handler::isEnabled()) { return $this; } @@ -212,10 +206,11 @@ public function addCommand(string $name, string|Closure|CommandInterface $handle if ($aliases = $handler::aliases()) { $config['aliases'] = array_merge($config['aliases'], $aliases); } - } elseif (!is_object($handler) || !method_exists($handler, '__invoke')) { + } elseif (!is_object($handler) || !$this->isValidCmdObject($handler)) { Helper::throwInvalidArgument( - 'The console command handler must is an subclass of %s OR a Closure OR a object have method __invoke()', - Command::class + 'The command handler must is an subclass of %s OR a Closure OR a sub-object of %s', + Command::class, + Command::class, ); } @@ -235,6 +230,20 @@ public function addCommand(string $name, string|Closure|CommandInterface $handle return $this; } + /** + * @param object $handler + * + * @return bool + */ + private function isValidCmdObject(object $handler): bool + { + if ($handler instanceof Command) { + return true; + } + + return method_exists($handler, '__invoke'); + } + /** * @param array $commands * diff --git a/src/Contract/CommandHandlerInterface.php b/src/Contract/CommandHandlerInterface.php index 349318ed..264f7dc5 100644 --- a/src/Contract/CommandHandlerInterface.php +++ b/src/Contract/CommandHandlerInterface.php @@ -10,6 +10,7 @@ namespace Inhere\Console\Contract; use Inhere\Console\AbstractApplication; +use Inhere\Console\Application; /** * Interface CommandHandlerInterface @@ -35,14 +36,14 @@ interface CommandHandlerInterface * * @param array $args * - * @return int|mixed return int is exit code. other is command exec result. + * @return mixed return int is exit code. other is command exec result. */ public function run(array $args): mixed; /** - * @return AbstractApplication|ApplicationInterface + * @return Application */ - public function getApp(): AbstractApplication; + public function getApp(): Application; /** * The input group name. diff --git a/src/Decorate/AttachApplicationTrait.php b/src/Decorate/AttachApplicationTrait.php index e285386d..83b76362 100644 --- a/src/Decorate/AttachApplicationTrait.php +++ b/src/Decorate/AttachApplicationTrait.php @@ -9,7 +9,6 @@ namespace Inhere\Console\Decorate; -use Inhere\Console\AbstractApplication; use Inhere\Console\Application; use Inhere\Console\Console; use Toolkit\Stdlib\OS; @@ -38,9 +37,9 @@ trait AttachApplicationTrait private bool $attached = false; /** - * @return AbstractApplication + * @return Application */ - public function getApp(): AbstractApplication + public function getApp(): Application { return $this->app; } diff --git a/src/Decorate/InputOutputAwareTrait.php b/src/Decorate/InputOutputAwareTrait.php index f99dcae8..7a47bc6c 100644 --- a/src/Decorate/InputOutputAwareTrait.php +++ b/src/Decorate/InputOutputAwareTrait.php @@ -122,6 +122,19 @@ public function setOutput(Output $output): void $this->output = $output; } + /** + * @param Input $input + * @param Output $output + * + * @return static + */ + public function setInputOutput(Input $input, Output $output): static + { + $this->input = $input; + $this->output = $output; + return $this; + } + /** * @return FlagsParser */ diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index e11fd24f..7ecc6d36 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -34,6 +34,7 @@ use function cli_set_process_title; use function error_get_last; use function function_exists; +use function vdump; use const PHP_OS; /** @@ -140,11 +141,11 @@ public static function aliases(): array /** * Command constructor. * - * @param Input $input - * @param Output $output + * @param Input|null $input + * @param Output|null $output */ - // TODO public function __construct(Input $input = null, Output $output = null) - public function __construct(Input $input, Output $output) + // public function __construct(Input $input, Output $output) + public function __construct(Input $input = null, Output $output = null) { $this->input = $input; $this->output = $output; @@ -157,8 +158,7 @@ public function __construct(Input $input, Output $output) protected function init(): void { - $this->commentsVars = $this->annotationVars(); - + // $this->commentsVars = $this->annotationVars(); $this->afterInit(); $this->debugf('attach inner subcommands to "%s"', self::getName()); $this->addCommands($this->commands()); @@ -240,6 +240,12 @@ protected function annotationVars(): array * running a command **************************************************************************/ + protected function initForRun(): void + { + $this->commentsVars = $this->annotationVars(); + + } + /** * @param Input $input */ @@ -299,6 +305,8 @@ public function run(array $args): mixed $name = self::getName(); try { + $this->initForRun(); + $this->initFlagsParser($this->input); $this->log(Console::VERB_DEBUG, "begin run '$name' - parse options", ['args' => $args]); @@ -335,7 +343,7 @@ protected function doRun(array $args): mixed if (isset($args[0])) { $first = $args[0]; $rName = $this->resolveAlias($first); - +// vdump($first, $rName); // TODO // if ($this->isSub($rName)) { // } diff --git a/src/Handler/CallableCommand.php b/src/Handler/CallableCommand.php index e4542dfc..9037c7c1 100644 --- a/src/Handler/CallableCommand.php +++ b/src/Handler/CallableCommand.php @@ -13,6 +13,7 @@ use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; use BadMethodCallException; +use Toolkit\PFlag\FlagsParser; /** * Class CallableCommand - wrap an callable as Command @@ -22,10 +23,15 @@ class CallableCommand extends Command { /** - * @var callable + * @var callable(FlagsParser, Output): void */ private $callable; + /** + * @var array{options: array, arguments: array} + */ + protected array $config = []; + // public function new(callable $callable): self // { // } @@ -45,6 +51,60 @@ class CallableCommand extends Command // return (new self())->setCallable($cb); // } + /** + * @param callable $fn + * + * @return $this + */ + public function withFunc(callable $fn): self + { + return $this->setCallable($fn); + } + + /** + * @param callable $fn + * + * @return $this + */ + public function withCustom(callable $fn): self + { + $fn($this); + return $this; + } + + /** + * @param array $config + * + * @return $this + */ + public function withConfig(array $config): self + { + $this->config = $config; + return $this; + } + + /** + * @param array $options + * + * @return $this + */ + public function withOptions(array $options): self + { + $this->config['options'] = $options; + return $this; + } + + /** + * @param array $arguments + * + * @return $this + */ + public function withArguments(array $arguments): self + { + $this->config['arguments'] = $arguments; + return $this; + } + /** * @param callable $callable * @@ -56,13 +116,21 @@ public function setCallable(callable $callable): self return $this; } + /** + * @return array + */ + public function getConfig(): array + { + return $this->config; + } + /** * Do execute command * * @param Input $input * @param Output $output * - * @return int|mixed + * @return mixed */ protected function execute(Input $input, Output $output): mixed { @@ -71,6 +139,6 @@ protected function execute(Input $input, Output $output): mixed } // call custom callable - return $call($input, $output); + return $call($this->flags, $output); } } diff --git a/test/ApplicationTest.php b/test/ApplicationTest.php index 7fb51edc..94a99a6e 100644 --- a/test/ApplicationTest.php +++ b/test/ApplicationTest.php @@ -85,7 +85,7 @@ public function testAddCommandError(): void $app->addCommand('test', 'invalid'); } catch (Throwable $e) { self::assertSame(get_class($e), InvalidArgumentException::class); - self::assertSame($e->getMessage(), 'The console command class [invalid] not exists!'); + self::assertSame($e->getMessage(), "The console command class 'invalid' not exists!"); } } @@ -116,6 +116,19 @@ public function testRunCommand_callback(): void self::assertSame('hello', $ret); } + public function testRun_Command_object(): void + { + $app = $this->newApp([ + './app', + 'test' + ]); + + $app->addCommand('test', new TestCommand()); + + $ret = $app->run(false); + self::assertSame('Inhere\ConsoleTest\TestCommand::execute', $ret); + } + public function testAddController(): void { $app = $this->newApp(); diff --git a/test/TestCommand.php b/test/TestCommand.php index f854461d..67c8eb4c 100644 --- a/test/TestCommand.php +++ b/test/TestCommand.php @@ -30,7 +30,7 @@ class TestCommand extends Command * @param Input $input * @param Output $output * - * @return int|mixed + * @return mixed */ protected function execute(Input $input, Output $output): mixed { From cce98504a0ad71681c5750ffc41e2cfce6e08ab4 Mon Sep 17 00:00:00 2001 From: Inhere Date: Sat, 25 Dec 2021 20:18:43 +0800 Subject: [PATCH 219/258] chore: add more tests for run group commadn --- src/Application.php | 11 +++++------ test/ApplicationTest.php | 23 ++++++++++++++++++----- test/TestController.php | 4 ++-- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/Application.php b/src/Application.php index 1752bae9..26b5afca 100644 --- a/src/Application.php +++ b/src/Application.php @@ -29,7 +29,6 @@ use function implode; use function is_object; use function is_string; -use function method_exists; use function str_replace; use function strlen; use function strpos; @@ -404,14 +403,13 @@ protected function createController(array $info): Controller $handler = $info['handler']; // The controller class or object if (is_string($handler)) { $class = $handler; - if (!class_exists($class)) { - Helper::throwInvalidArgument('The console controller class [%s] not exists!', $class); - } + Assert::isTrue(class_exists($class), "The console controller class '$class' not exists!"); - $handler = new $class($this->input, $this->output); + // create group object + $handler = new $class(); } - if (!($handler instanceof Controller)) { + if (!$handler instanceof Controller) { Helper::throwInvalidArgument( 'The console controller class [%s] must instanceof the %s', $handler, @@ -422,6 +420,7 @@ protected function createController(array $info): Controller // force set name and description $handler::setName($group); $handler->setApp($this); + $handler->setInputOutput($this->input, $this->output); // set input name if ($inputName = $info['name'] ?? '') { diff --git a/test/ApplicationTest.php b/test/ApplicationTest.php index 94a99a6e..62d5bcde 100644 --- a/test/ApplicationTest.php +++ b/test/ApplicationTest.php @@ -89,7 +89,7 @@ public function testAddCommandError(): void } } - public function testRunCommand_class(): void + public function testAddCommand_class_run(): void { $app = $this->newApp([ './app', @@ -101,7 +101,7 @@ public function testRunCommand_class(): void self::assertSame('Inhere\ConsoleTest\TestCommand::execute', $ret); } - public function testRunCommand_callback(): void + public function testAddCommand_callback_run(): void { $app = $this->newApp([ './app', @@ -116,14 +116,14 @@ public function testRunCommand_callback(): void self::assertSame('hello', $ret); } - public function testRun_Command_object(): void + public function testAddCommand_object_run(): void { $app = $this->newApp([ './app', 'test' ]); - $app->addCommand('test', new TestCommand()); + $app->command('test', new TestCommand()); $ret = $app->run(false); self::assertSame('Inhere\ConsoleTest\TestCommand::execute', $ret); @@ -165,7 +165,7 @@ public function testAddControllerError(): void } } - public function testRunController(): void + public function testAdd_Controller_class_Run(): void { $app = $this->newApp([ './app', @@ -178,6 +178,19 @@ public function testRunController(): void self::assertSame('Inhere\ConsoleTest\TestController::demoCommand', $ret); } + public function testAdd_Controller_object_Run(): void + { + $app = $this->newApp([ + './app', + 'test:demo' + ]); + + $app->controller('test', new TestController); + + $ret = $app->run(false); + self::assertSame('Inhere\ConsoleTest\TestController::demoCommand', $ret); + } + public function testTriggerEvent(): void { $app = $this->newApp([ diff --git a/test/TestController.php b/test/TestController.php index 01a24300..1a321c5a 100644 --- a/test/TestController.php +++ b/test/TestController.php @@ -25,9 +25,9 @@ class TestController extends Controller /** * this is an demo command in test * - * @return mixed + * @return string */ - public function demoCommand(): mixed + public function demoCommand(): string { return __METHOD__; } From d1f9829db9b0d331fdf556bb2b95e1040050b63d Mon Sep 17 00:00:00 2001 From: Inhere Date: Sun, 26 Dec 2021 20:57:09 +0800 Subject: [PATCH 220/258] feat: allow attch multi level sub-commands to command,group --- examples/Command/TestCommand.php | 2 +- examples/Controller/Attach/DemoSubCommand.php | 40 ++++ examples/Controller/HomeController.php | 25 ++- examples/Controller/ShowController.php | 2 +- src/Command.php | 52 +++-- src/Component/PharCompiler.php | 1 - src/Component/ReadlineCompleter.php | 13 +- src/Component/Router.php | 17 +- src/Contract/CommandHandlerInterface.php | 5 + src/Controller.php | 15 +- src/Decorate/SubCommandsWareTrait.php | 161 ++++++++++++---- src/Handler/AbstractHandler.php | 86 +++------ src/Handler/CallableCommand.php | 126 +------------ src/Handler/CommandWrapper.php | 178 ++++++++++++++++++ src/Util/ConsoleUtil.php | 29 +++ test/CommandTest.php | 22 ++- test/ControllerTest.php | 2 +- test/TestCommand.php | 13 ++ 18 files changed, 510 insertions(+), 279 deletions(-) create mode 100644 examples/Controller/Attach/DemoSubCommand.php create mode 100644 src/Handler/CommandWrapper.php create mode 100644 src/Util/ConsoleUtil.php diff --git a/examples/Command/TestCommand.php b/examples/Command/TestCommand.php index 6725dc9e..6239cb78 100644 --- a/examples/Command/TestCommand.php +++ b/examples/Command/TestCommand.php @@ -23,7 +23,7 @@ class TestCommand extends Command protected static string $desc = 'this is a test independent command'; - protected function commands(): array + protected function subCommands(): array { return [ 'sub' => static function ($fs, $out): void { diff --git a/examples/Controller/Attach/DemoSubCommand.php b/examples/Controller/Attach/DemoSubCommand.php new file mode 100644 index 00000000..c6c79c2e --- /dev/null +++ b/examples/Controller/Attach/DemoSubCommand.php @@ -0,0 +1,40 @@ + 'string option1', + 's2,str2' => 'string option2', + ]; + } + + /** + * Do execute command + * + * @param Input $input + * @param Output $output + * + * @return void|mixed + */ + protected function execute(Input $input, Output $output) + { + vdump(__METHOD__); + } +} diff --git a/examples/Controller/HomeController.php b/examples/Controller/HomeController.php index 05cc45b3..0cbc71eb 100644 --- a/examples/Controller/HomeController.php +++ b/examples/Controller/HomeController.php @@ -11,6 +11,8 @@ use Inhere\Console\Component\Symbol\ArtFont; use Inhere\Console\Controller; +use Inhere\Console\Examples\Controller\Attach\DemoSubCommand; +use Inhere\Console\Handler\CommandWrapper; use Inhere\Console\Util\Interact; use Inhere\Console\Util\ProgressBar; use Inhere\Console\Util\Show; @@ -23,6 +25,7 @@ use Toolkit\Stdlib\Php; use function sleep; use function trigger_error; +use function vdump; /** * default command controller. there are some command usage examples(1) @@ -74,13 +77,30 @@ protected function init(): void /** * @return array */ - protected function options(): array + protected function getOptions(): array { return [ '-c, --common' => 'This is a common option for all sub-commands', ]; } + protected function subCommands(): array + { + return [ + DemoSubCommand::class, + CommandWrapper::wrap(static function ($fs) { + vdump(__METHOD__, $fs->getOpts()); + }, [ + 'name' => 'sub2', + 'desc' => 'an sub command in group controller', + 'options' => [ + 'int1' => 'int;an int option1', + 'i2, int2' => 'int;an int option2', + ] + ]), + ]; + } + protected function disabledCommands(): array { return ['disabled']; @@ -207,6 +227,9 @@ public function colorCheckCommand(): void * --font Set the art font name(allow: {internalFonts}). * --italic bool;Set the art font type is italic. * --style Set the art font style. + * + * @param FlagsParser $fs + * * @return int */ public function artFontCommand(FlagsParser $fs): int diff --git a/examples/Controller/ShowController.php b/examples/Controller/ShowController.php index 9ed5b8ae..dec3b548 100644 --- a/examples/Controller/ShowController.php +++ b/examples/Controller/ShowController.php @@ -43,7 +43,7 @@ public static function commandAliases(): array /** * @return array */ - protected function options(): array + protected function getOptions(): array { return [ '-c, --common' => 'This is a common option for all sub-commands', diff --git a/src/Command.php b/src/Command.php index 55263799..a3cd4f56 100644 --- a/src/Command.php +++ b/src/Command.php @@ -13,6 +13,7 @@ use Inhere\Console\Handler\AbstractHandler; use ReflectionException; use Toolkit\PFlag\FlagsParser; +use function array_shift; /** * Class Command @@ -38,14 +39,9 @@ abstract class Command extends AbstractHandler implements CommandInterface */ protected ?Controller $group = null; - /** - * @var Command|null - */ - protected ?Command $parent = null; - protected function init(): void { - $this->commandName = self::getName(); + $this->commandName = $this->getRealName(); parent::init(); } @@ -53,7 +49,7 @@ protected function init(): void /** * @return array */ - public function getArguments(): array + protected function getArguments(): array { return []; } @@ -63,12 +59,13 @@ public function getArguments(): array */ protected function beforeInitFlagsParser(FlagsParser $fs): void { + $fs->addArgsByRules($this->getArguments()); $fs->setStopOnFistArg(false); // old mode: options and arguments at method annotations - if ($this->compatible) { - $fs->setSkipOnUndefined(true); - } + // if ($this->compatible) { + // $fs->setSkipOnUndefined(true); + // } } /** @@ -93,31 +90,24 @@ protected function afterInitFlagsParser(FlagsParser $fs): void } /** - * @param Command $parent - */ - public function setParent(Command $parent): void - { - $this->parent = $parent; - } - - /** - * @return $this + * @param array $args + * + * @return mixed */ - public function getRoot(): Command + protected function doRun(array $args): mixed { - if ($this->parent) { - return $this->parent->getRoot(); + // if input sub-command name + if (isset($args[0])) { + $first = $args[0]; + $rName = $this->resolveAlias($first); + + if ($this->isSub($rName)) { + array_shift($args); + return $this->dispatchSub($rName, $args); + } } - return $this; - } - - /** - * @return Command|null - */ - public function getParent(): ?Command - { - return $this->parent; + return parent::doRun($args); } /** diff --git a/src/Component/PharCompiler.php b/src/Component/PharCompiler.php index 58653b05..984900a0 100644 --- a/src/Component/PharCompiler.php +++ b/src/Component/PharCompiler.php @@ -16,7 +16,6 @@ use Exception; use FilesystemIterator; use Generator; -use Inhere\Console\Util\Helper; use InvalidArgumentException; use Iterator; use Phar; diff --git a/src/Component/ReadlineCompleter.php b/src/Component/ReadlineCompleter.php index 26d1bc3f..2555836b 100644 --- a/src/Component/ReadlineCompleter.php +++ b/src/Component/ReadlineCompleter.php @@ -44,10 +44,13 @@ public function isSupported(): bool /** * @param callable $completer + * + * @return ReadlineCompleter */ - public function setCompleter(callable $completer): void + public function setCompleter(callable $completer): static { $this->completer = $completer; + return $this; } /** @@ -113,4 +116,12 @@ public function setHistorySize(int $historySize): void { $this->historySize = $historySize; } + + /** + * @return callable + */ + public function getCompleter(): callable + { + return $this->completer; + } } diff --git a/src/Component/Router.php b/src/Component/Router.php index 500eebea..38d9966a 100644 --- a/src/Component/Router.php +++ b/src/Component/Router.php @@ -16,6 +16,7 @@ use Inhere\Console\Contract\RouterInterface; use Inhere\Console\Controller; use Inhere\Console\IO\Output; +use Inhere\Console\Util\ConsoleUtil; use Inhere\Console\Util\Helper; use InvalidArgumentException; use Toolkit\PFlag\FlagsParser; @@ -206,7 +207,7 @@ public function addCommand(string $name, string|Closure|CommandInterface $handle if ($aliases = $handler::aliases()) { $config['aliases'] = array_merge($config['aliases'], $aliases); } - } elseif (!is_object($handler) || !$this->isValidCmdObject($handler)) { + } elseif (!is_object($handler) || !ConsoleUtil::isValidCmdObject($handler)) { Helper::throwInvalidArgument( 'The command handler must is an subclass of %s OR a Closure OR a sub-object of %s', Command::class, @@ -230,20 +231,6 @@ public function addCommand(string $name, string|Closure|CommandInterface $handle return $this; } - /** - * @param object $handler - * - * @return bool - */ - private function isValidCmdObject(object $handler): bool - { - if ($handler instanceof Command) { - return true; - } - - return method_exists($handler, '__invoke'); - } - /** * @param array $commands * diff --git a/src/Contract/CommandHandlerInterface.php b/src/Contract/CommandHandlerInterface.php index 264f7dc5..3e0e8d21 100644 --- a/src/Contract/CommandHandlerInterface.php +++ b/src/Contract/CommandHandlerInterface.php @@ -59,6 +59,11 @@ public function getGroupName(): string; */ public function getRealName(): string; + /** + * @return string + */ + public function getRealDesc(): string; + /** * The real group name. * diff --git a/src/Controller.php b/src/Controller.php index 964aec33..724c522e 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -300,21 +300,26 @@ public function doRun(array $args): mixed // convert 'boo-foo' to 'booFoo' $this->action = $action = Str::camelCase($command); $this->debugf("will run the '%s' group action: %s, subcommand: %s", $name, $action, $command); - $this->actionMethod = $method = $this->getMethodName($action); + $method = $this->getMethodName($action); // fire event $this->fire(ConsoleEvent::COMMAND_RUN_BEFORE, $this); $this->beforeRun(); // check method not exist - // - if command method not exists. if (!method_exists($this, $method)) { + if ($this->isSub($command)) { + return $this->dispatchSub($command, $args); + } + + // if command not exists. return $this->handleNotFound($name, $action, $args); } // init flags for subcommand $fs = $this->newActionFlags(); + $this->actionMethod = $method; $this->input->setFs($fs); $this->debugf('load flags by configure method, subcommand: %s', $command); $this->configure(); @@ -485,9 +490,9 @@ protected function newActionFlags(string $action = ''): FlagsParser }); // old mode: options and arguments at method annotations - if ($this->compatible) { - $fs->setSkipOnUndefined(true); - } + // if ($this->compatible) { + // $fs->setSkipOnUndefined(true); + // } // save $this->subFss[$action] = $fs; diff --git a/src/Decorate/SubCommandsWareTrait.php b/src/Decorate/SubCommandsWareTrait.php index 818bb2a0..b042c704 100644 --- a/src/Decorate/SubCommandsWareTrait.php +++ b/src/Decorate/SubCommandsWareTrait.php @@ -13,8 +13,12 @@ use Inhere\Console\Command; use Inhere\Console\Console; use Inhere\Console\Contract\CommandInterface; +use Inhere\Console\Handler\AbstractHandler; +use Inhere\Console\Handler\CommandWrapper; +use Inhere\Console\Util\ConsoleUtil; use Inhere\Console\Util\Helper; use InvalidArgumentException; +use Toolkit\Stdlib\Helper\Assert; use Toolkit\Stdlib\Obj\Traits\NameAliasTrait; use function array_keys; use function array_merge; @@ -24,7 +28,6 @@ use function is_object; use function is_string; use function is_subclass_of; -use function method_exists; use function preg_match; /** @@ -36,6 +39,11 @@ trait SubCommandsWareTrait { use NameAliasTrait; + /** + * @var AbstractHandler|null + */ + protected ?AbstractHandler $parent = null; + /** * @var array */ @@ -44,28 +52,38 @@ trait SubCommandsWareTrait /** * The sub-commands of the command * - * @var array + * ```php * [ * 'name' => [ - * 'handler' => MyCommand::class, // allow: string|Closure|CommandInterface - * 'config' => [] + * 'handler' => MyCommand::class, + * 'config' => [ + * 'name' => 'string', + * 'desc' => 'string', + * 'options' => [], + * 'arguments' => [], + * ] * ] * ] + * ``` + * + * @var array */ private array $commands = []; /** - * Can attach sub-commands + * Can attach sub-commands to current command * * @return array */ - protected function commands(): array + protected function subCommands(): array { // [ // 'cmd1' => function(){}, + // // class name // MySubCommand::class, // 'cmd2' => MySubCommand2::class, - // new FooCommand, + // // no key + // new FooCommand(), // 'cmd3' => new FooCommand2(), // ] return []; @@ -73,22 +91,49 @@ protected function commands(): array /** * @param string $name + * @param array $args + * + * @return mixed */ - protected function dispatchCommand(string $name): void + protected function dispatchSub(string $name, array $args): mixed { + $subInfo = $this->commands[$name]; + $this->debugf('dispatch the attached subcommand: %s', $name); + + // create and init sub-command + $subCmd = $this->createSubCommand($subInfo); + $subCmd->setParent($this); + $subCmd->setInputOutput($this->input, $this->output); + + return $subCmd->run($args); + } + + /** + * @param array{name: string, desc: string, options: array, arguments: array} $subInfo + * + * @return Command + */ + protected function createSubCommand(array $subInfo): Command + { + $handler = $subInfo['handler']; + if (is_object($handler)) { + if ($handler instanceof Command) { + return $handler; + } + + return CommandWrapper::wrap($handler, $subInfo['config']); + } + + // class-string of Command + return new $handler; } /** * Register a app independent console command * - * @param string|CommandInterface $name + * @param string|class-string $name * @param string|Closure|CommandInterface|null $handler - * @param array $config - * array: - * - aliases The command aliases - * - description The description message - * - * @throws InvalidArgumentException + * @param array $config */ public function addSub(string $name, string|Closure|CommandInterface $handler = null, array $config = []): void { @@ -96,24 +141,21 @@ public function addSub(string $name, string|Closure|CommandInterface $handler = /** @var Command $name name is an command class */ $handler = $name; $name = $name::getName(); + } elseif (!$name && $handler instanceof Command) { + $name = $handler->getRealName(); + } elseif (!$name && class_exists($handler)) { + $name = $handler::getName(); } - if (!$name || !$handler) { - Helper::throwInvalidArgument("Command 'name' and 'handler' cannot be empty! name: $name"); - } + Assert::isFalse(!$name || !$handler, "Command 'name' and 'handler' cannot be empty! name: $name"); + Assert::isFalse(isset($this->commands[$name]), "Command '$name' have been registered!"); $this->validateName($name); - if (isset($this->commands[$name])) { - Helper::throwInvalidArgument("Command '$name' have been registered!"); - } - $config['aliases'] = isset($config['aliases']) ? (array)$config['aliases'] : []; if (is_string($handler)) { - if (!class_exists($handler)) { - Helper::throwInvalidArgument("The command handler class [$handler] not exists!"); - } + Assert::isTrue(class_exists($handler), "The console command class '$handler' not exists!"); if (!is_subclass_of($handler, Command::class)) { Helper::throwInvalidArgument('The command handler class must is subclass of the: ' . Command::class); @@ -129,42 +171,89 @@ public function addSub(string $name, string|Closure|CommandInterface $handler = if ($aliases = $handler::aliases()) { $config['aliases'] = array_merge($config['aliases'], $aliases); } - } elseif (!is_object($handler) || !method_exists($handler, '__invoke')) { + } elseif (!is_object($handler) || !ConsoleUtil::isValidCmdObject($handler)) { Helper::throwInvalidArgument( - 'The console command handler must is an subclass of %s OR a Closure OR a object have method __invoke()', - Command::class + 'The command handler must is an subclass of %s OR a Closure OR a sub-object of %s', + Command::class, + Command::class, ); } + // has alias option + if ($config['aliases']) { + $this->setAlias($name, $config['aliases'], true); + } + + $config['name'] = $name; + // save + $this->commands[$name] = [ + 'type' => Console::CMD_SINGLE, + 'handler' => $handler, + 'config' => $config, + ]; + } + + /** + * @param CommandInterface $handler + * + * @return $this + */ + public function addSubHandler(CommandInterface $handler): static + { + $name = $handler->getRealName(); + // is an class name string $this->commands[$name] = [ 'type' => Console::CMD_SINGLE, 'handler' => $handler, - 'config' => $config, + 'config' => [], ]; - // has alias option - if (isset($config['aliases'])) { - $this->setAlias($name, $config['aliases'], true); - } + return $this; } /** * @param array $commands - * - * @throws InvalidArgumentException */ public function addCommands(array $commands): void { foreach ($commands as $name => $handler) { if (is_int($name)) { - $this->addSub($handler); + $this->addSub('', $handler); } else { $this->addSub($name, $handler); } } } + /** + * @param AbstractHandler $parent + */ + public function setParent(AbstractHandler $parent): void + { + $this->parent = $parent; + } + + /** + * @return $this + */ + public function getRoot(): static + { + if ($this->parent) { + return $this->parent->getRoot(); + } + + return $this; + } + + /** + * @return static|null + */ + public function getParent(): ?static + { + return $this->parent; + } + /********************************************************** * helper methods **********************************************************/ diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index 7ecc6d36..8c68359a 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -23,7 +23,6 @@ use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; use Inhere\Console\Util\Helper; -use InvalidArgumentException; use ReflectionException; use RuntimeException; use Throwable; @@ -34,7 +33,6 @@ use function cli_set_process_title; use function error_get_last; use function function_exists; -use function vdump; use const PHP_OS; /** @@ -70,14 +68,6 @@ abstract class AbstractHandler implements CommandHandlerInterface */ protected static string $desc = ''; - /** - * The command/controller description message - * - * @var string - * @deprecated please use {@see $desc} - */ - protected static string $description = ''; - /** * @var bool Whether enable coroutine. It is require swoole extension. */ @@ -101,9 +91,9 @@ abstract class AbstractHandler implements CommandHandlerInterface protected string $processTitle = ''; /** - * @var DataObject + * @var DataObject|null */ - protected DataObject $params; + protected ?DataObject $params = null; /** * The input command name. maybe is an alias name. @@ -160,8 +150,8 @@ protected function init(): void { // $this->commentsVars = $this->annotationVars(); $this->afterInit(); - $this->debugf('attach inner subcommands to "%s"', self::getName()); - $this->addCommands($this->commands()); + $this->debugf('attach inner subcommands to "%s"', $this->getRealName()); + $this->addCommands($this->subCommands()); } protected function afterInit(): void @@ -170,7 +160,7 @@ protected function afterInit(): void } /** - * command options + * get command options * * **Alone Command** * @@ -183,7 +173,7 @@ protected function afterInit(): void * * @return array */ - protected function options(): array + protected function getOptions(): array { // ['--skip-invalid' => 'Whether ignore invalid arguments and options, when use input definition',] return []; @@ -257,13 +247,13 @@ protected function initFlagsParser(Input $input): void } $input->setFs($this->flags); - $this->flags->setDesc(self::getDesc()); - $this->flags->setScriptName(self::getName()); + $this->flags->setDesc($this->getRealDesc()); + $this->flags->setScriptName($this->getRealName()); $this->beforeInitFlagsParser($this->flags); // set options by options() - $optRules = $this->options(); + $optRules = $this->getOptions(); $this->flags->addOptsByRules($optRules); // for render help @@ -298,11 +288,10 @@ protected function afterInitFlagsParser(FlagsParser $fs): void * @param array $args * * @return mixed - * @throws Throwable */ public function run(array $args): mixed { - $name = self::getName(); + $name = $this->getRealName(); try { $this->initForRun(); @@ -314,7 +303,7 @@ public function run(array $args): mixed // parse options $this->flags->lock(); if (!$this->flags->parse($args)) { - return 0; // on error, help + return 0; // on error OR help } $args = $this->flags->getRawArgs(); @@ -324,7 +313,7 @@ public function run(array $args): mixed if ($this->isDetached()) { ErrorHandler::new()->handle($e); } else { - throw $e; + throw new RuntimeException('Run error - ' . $e->getMessage(), $e->getCode(), $e); } } @@ -340,23 +329,11 @@ public function run(array $args): mixed */ protected function doRun(array $args): mixed { - if (isset($args[0])) { - $first = $args[0]; - $rName = $this->resolveAlias($first); -// vdump($first, $rName); - // TODO - // if ($this->isSub($rName)) { - // } - } - // some prepare check - // - validate input arguments if (true !== $this->prepare()) { return -1; } - // $this->dispatchCommand($name); - // return False to deny goon run. if (false === $this->beforeExecute()) { return -1; @@ -378,6 +355,11 @@ protected function doRun(array $args): mixed return $result; } + protected function doExecute(): mixed + { + return ''; + } + /** * coroutine run by swoole go() * @@ -435,9 +417,6 @@ protected function afterExecute(): void /** * prepare run - * - * @throws InvalidArgumentException - * @throws RuntimeException */ protected function prepare(): bool { @@ -491,13 +470,10 @@ public function getParams(): DataObject /** * @param array $params - * - * @return DataObject */ - public function initParams(array $params): DataObject + public function initParams(array $params): void { $this->params = DataObject::new($params); - return $this->params; } /** @@ -587,6 +563,14 @@ public function getRealName(): string return self::getName(); } + /** + * @return string + */ + public function getRealDesc(): string + { + return self::getDesc(); + } + /** * @param bool $useReal * @@ -632,7 +616,7 @@ final public static function getName(): string */ public static function getDesc(): string { - return static::$desc ?: static::$description; + return static::$desc; } /** @@ -645,22 +629,6 @@ public static function setDesc(string $desc): void } } - /** - * @return string - */ - public static function getDescription(): string - { - return self::getDesc(); - } - - /** - * @param string $desc - */ - public static function setDescription(string $desc): void - { - self::setDesc($desc); - } - /** * @return bool */ diff --git a/src/Handler/CallableCommand.php b/src/Handler/CallableCommand.php index 9037c7c1..561f85ea 100644 --- a/src/Handler/CallableCommand.php +++ b/src/Handler/CallableCommand.php @@ -9,136 +9,12 @@ namespace Inhere\Console\Handler; -use Inhere\Console\Command; -use Inhere\Console\IO\Input; -use Inhere\Console\IO\Output; -use BadMethodCallException; -use Toolkit\PFlag\FlagsParser; - /** * Class CallableCommand - wrap an callable as Command * * @package Inhere\Console\Handler */ -class CallableCommand extends Command +class CallableCommand extends CommandWrapper { - /** - * @var callable(FlagsParser, Output): void - */ - private $callable; - - /** - * @var array{options: array, arguments: array} - */ - protected array $config = []; - - // public function new(callable $callable): self - // { - // } - - // public function __construct(Input $input, Output $output, InputDefinition $definition = null) - // { - // parent::__construct($input, $output, $definition); - // } - - // /** - // * @param callable $cb - // * - // * @return static - // */ - // public static function wrap(callable $cb): self - // { - // return (new self())->setCallable($cb); - // } - - /** - * @param callable $fn - * - * @return $this - */ - public function withFunc(callable $fn): self - { - return $this->setCallable($fn); - } - - /** - * @param callable $fn - * - * @return $this - */ - public function withCustom(callable $fn): self - { - $fn($this); - return $this; - } - - /** - * @param array $config - * - * @return $this - */ - public function withConfig(array $config): self - { - $this->config = $config; - return $this; - } - - /** - * @param array $options - * - * @return $this - */ - public function withOptions(array $options): self - { - $this->config['options'] = $options; - return $this; - } - - /** - * @param array $arguments - * - * @return $this - */ - public function withArguments(array $arguments): self - { - $this->config['arguments'] = $arguments; - return $this; - } - - /** - * @param callable $callable - * - * @return CallableCommand - */ - public function setCallable(callable $callable): self - { - $this->callable = $callable; - return $this; - } - - /** - * @return array - */ - public function getConfig(): array - { - return $this->config; - } - - /** - * Do execute command - * - * @param Input $input - * @param Output $output - * - * @return mixed - */ - protected function execute(Input $input, Output $output): mixed - { - if (!$call = $this->callable) { - throw new BadMethodCallException('The callable property is empty'); - } - // call custom callable - return $call($this->flags, $output); - } } diff --git a/src/Handler/CommandWrapper.php b/src/Handler/CommandWrapper.php new file mode 100644 index 00000000..b23a6072 --- /dev/null +++ b/src/Handler/CommandWrapper.php @@ -0,0 +1,178 @@ +withConfig($config)->setCallable($handleFn); + } + + /** + * @param callable(FlagsParser, Output):mixed $handleFn + * @param array $config + * + * @return static + */ + public static function wrap(callable $handleFn, array $config = []): self + { + return (new self())->withConfig($config)->setCallable($handleFn); + } + + /** + * @param callable $fn + * + * @return $this + */ + public function withCustom(callable $fn): self + { + $fn($this); + return $this; + } + + /** + * @param callable(FlagsParser, Output):mixed $fn + * + * @return $this + */ + public function withFunc(callable $fn): self + { + return $this->setCallable($fn); + } + + /** + * @param array $config + * + * @return $this + */ + public function withConfig(array $config): self + { + $this->config = $config; + return $this; + } + + /** + * @param array $options + * + * @return $this + */ + public function withOptions(array $options): self + { + $this->config['options'] = $options; + return $this; + } + + /** + * @param array $arguments + * + * @return $this + */ + public function withArguments(array $arguments): self + { + $this->config['arguments'] = $arguments; + return $this; + } + + /** + * @param callable(FlagsParser, Output):mixed $callable + * + * @return static + */ + public function setCallable(callable $callable): self + { + $this->callable = $callable; + return $this; + } + + /** + * @return array + */ + protected function getOptions(): array + { + return $this->config['options'] ?? []; + } + + /** + * @return array + */ + protected function getArguments(): array + { + return $this->config['arguments'] ?? []; + } + + /** + * @return string + */ + public function getRealDesc(): string + { + return $this->config['desc'] ?? ''; + } + + /** + * @return string + */ + public function getRealName(): string + { + return $this->config['name'] ?? ''; + } + + /** + * @return array + */ + public function getConfig(): array + { + return $this->config; + } + + /** + * Do execute command + * + * @param Input $input + * @param Output $output + * + * @return mixed + */ + protected function execute(Input $input, Output $output): mixed + { + if (!$call = $this->callable) { + throw new BadMethodCallException('The command handler property is empty'); + } + + // call custom callable + return $call($this->flags, $output); + } +} diff --git a/src/Util/ConsoleUtil.php b/src/Util/ConsoleUtil.php new file mode 100644 index 00000000..fc3dc9f1 --- /dev/null +++ b/src/Util/ConsoleUtil.php @@ -0,0 +1,29 @@ +assertSame('test1', $c::getName()); - $this->assertStringContainsString('desc', $c::getDesc()); + $this->assertSame('test1', $c->getRealName()); + $this->assertStringContainsString('description', $c::getDesc()); + $this->assertStringContainsString('description', $c->getRealDesc()); + } + + public function testCommand_alone_run(): void + { + $c = new TestCommand(new Input(), new Output()); + + $str = $c->run([]); + $this->assertEquals('Inhere\ConsoleTest\TestCommand::execute', $str); + } + + public function testCommand_alone_run_sub(): void + { + $c = new TestCommand(new Input(), new Output()); + + $str = $c->run(['sub1']); + $this->assertEquals('Inhere\ConsoleTest\{closure}', $str); } } diff --git a/test/ControllerTest.php b/test/ControllerTest.php index 5b091533..22a70de1 100644 --- a/test/ControllerTest.php +++ b/test/ControllerTest.php @@ -23,6 +23,6 @@ public function testBasic(): void $c = new TestController(new Input(), new Output()); $this->assertSame('test', $c::getName()); - $this->assertStringContainsString('desc', $c::getDescription()); + $this->assertStringContainsString('desc', $c::getDesc()); } } diff --git a/test/TestCommand.php b/test/TestCommand.php index 67c8eb4c..1264e75b 100644 --- a/test/TestCommand.php +++ b/test/TestCommand.php @@ -10,6 +10,7 @@ namespace Inhere\ConsoleTest; use Inhere\Console\Command; +use Inhere\Console\Handler\CommandWrapper; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; @@ -24,6 +25,18 @@ class TestCommand extends Command protected static string $desc = 'command description message'; + protected function subCommands(): array + { + return [ + CommandWrapper::new(static function () { + return __METHOD__; + })->withConfig([ + 'name' => 'sub1', + 'desc' => 'desc for sub1 in test1', + ]), + ]; + } + /** * do execute command * From 43e04954b413805ed7d5a9b8fcef01d719ec790c Mon Sep 17 00:00:00 2001 From: Inhere Date: Sun, 26 Dec 2021 22:54:16 +0800 Subject: [PATCH 221/258] feat: support render attached subcommand help --- examples/Command/TestCommand.php | 7 +++-- src/Decorate/CommandHelpTrait.php | 11 ++++++-- src/Decorate/ControllerHelpTrait.php | 4 +++ src/Decorate/SubCommandsWareTrait.php | 40 +++++++++++++++++++++++---- 4 files changed, 53 insertions(+), 9 deletions(-) diff --git a/examples/Command/TestCommand.php b/examples/Command/TestCommand.php index 6239cb78..a51157ef 100644 --- a/examples/Command/TestCommand.php +++ b/examples/Command/TestCommand.php @@ -10,6 +10,7 @@ namespace Inhere\Console\Examples\Command; use Inhere\Console\Command; +use Inhere\Console\Handler\CommandWrapper; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; @@ -26,9 +27,11 @@ class TestCommand extends Command protected function subCommands(): array { return [ - 'sub' => static function ($fs, $out): void { + 'sub' => CommandWrapper::new(static function ($fs, $out): void { $out->println('hello, this is an sub command of test.'); - }, + }, [ + 'desc' => 'sub command of test command' + ]), ]; } diff --git a/src/Decorate/CommandHelpTrait.php b/src/Decorate/CommandHelpTrait.php index f73fd860..79e53f92 100644 --- a/src/Decorate/CommandHelpTrait.php +++ b/src/Decorate/CommandHelpTrait.php @@ -14,6 +14,7 @@ use Toolkit\PFlag\FlagsParser; use Toolkit\PFlag\FlagUtil; use function implode; +use function ksort; use function sprintf; use function strtr; use function ucfirst; @@ -81,7 +82,7 @@ protected function addCommentsVars(array $map): void */ protected function setCommentsVar(string $name, array|string $value): void { - $this->commentsVars[$name] = is_array($value) ? implode(',', $value) : (string)$value; + $this->commentsVars[$name] = is_array($value) ? implode(',', $value) : $value; } /** @@ -148,8 +149,14 @@ public function showHelpByFlagsParser(FlagsParser $fs, array $aliases = [], stri $help['Options:'] = FlagUtil::alignOptions($fs->getOptsHelpLines()); $help['Argument:'] = $fs->getArgsHelpLines(); - $help['Example:'] = $fs->getExampleHelp(); + if ($subCmds = $this->getSubsForHelp()) { + // sort commands + ksort($subCmds); + $help['Commands:'] = $subCmds; + } + + $help['Example:'] = $fs->getExampleHelp(); $help['More Help:'] = $fs->getMoreHelp(); // no group options. only set key position. diff --git a/src/Decorate/ControllerHelpTrait.php b/src/Decorate/ControllerHelpTrait.php index af6c87ca..7fb0553a 100644 --- a/src/Decorate/ControllerHelpTrait.php +++ b/src/Decorate/ControllerHelpTrait.php @@ -139,6 +139,10 @@ public function showCommandList(): void $commands[$cmd] = $desc; } + if ($subCmds = $this->getSubsForHelp()) { + $commands = array_merge($commands, $subCmds); + } + // sort commands ksort($commands); diff --git a/src/Decorate/SubCommandsWareTrait.php b/src/Decorate/SubCommandsWareTrait.php index b042c704..ce4655ee 100644 --- a/src/Decorate/SubCommandsWareTrait.php +++ b/src/Decorate/SubCommandsWareTrait.php @@ -132,10 +132,10 @@ protected function createSubCommand(array $subInfo): Command * Register a app independent console command * * @param string|class-string $name - * @param string|Closure|CommandInterface|null $handler + * @param class-string|CommandInterface|null $handler * @param array $config */ - public function addSub(string $name, string|Closure|CommandInterface $handler = null, array $config = []): void + public function addSub(string $name, string|CommandInterface $handler = null, array $config = []): void { if (!$handler && class_exists($name)) { /** @var Command $name name is an command class */ @@ -171,9 +171,9 @@ public function addSub(string $name, string|Closure|CommandInterface $handler = if ($aliases = $handler::aliases()) { $config['aliases'] = array_merge($config['aliases'], $aliases); } - } elseif (!is_object($handler) || !ConsoleUtil::isValidCmdObject($handler)) { + } elseif (!is_object($handler) || !$handler instanceof Command) { Helper::throwInvalidArgument( - 'The command handler must is an subclass of %s OR a Closure OR a sub-object of %s', + 'The subcommand handler must be an subclass of %s OR a sub-object of %s', Command::class, Command::class, ); @@ -317,8 +317,38 @@ public function setBlocked(array $blocked): void /** * @return array */ - public function getCommandNames(): array + public function getSubNames(): array { return array_keys($this->commands); } + + /** + * @return array + */ + public function getCommands(): array + { + return $this->commands; + } + + /** + * @return array + */ + public function getSubsForHelp(): array + { + $subs = []; + foreach ($this->commands as $name => $subInfo) { + $sub = $subInfo['handler']; + if ($sub instanceof Command) { + $subs[$name] = $sub->getRealDesc(); + } elseif (is_string($sub)) { + $subs[$name] = $sub::getDesc(); + } else { + $subConf = $subInfo['config']; + + $subs[$name] = $subConf['desc'] ?? 'no description'; + } + } + + return $subs; + } } From 472997957cceca98db8c738546a0a94551b70b50 Mon Sep 17 00:00:00 2001 From: Inhere Date: Sun, 2 Jan 2022 21:35:48 +0800 Subject: [PATCH 222/258] up: update some for output class --- CHANGELOG.md | 3 + TODO.md | 2 +- src/Concern/AbstractOutput.php | 3 + src/Handler/AbstractHandler.php | 4 +- src/IO/Output.php | 12 ++-- src/IO/Output/BufferedOutput.php | 104 +++++++++++++++++++++++++++---- src/IO/Output/MemoryOutput.php | 24 ++++++- src/IO/Output/StreamOutput.php | 16 +---- src/IO/Output/TempOutput.php | 54 ++++++++++++++++ src/IO/TempStream.php | 98 ----------------------------- test/CommandTest.php | 14 ++++- 11 files changed, 198 insertions(+), 136 deletions(-) create mode 100644 src/IO/Output/TempOutput.php delete mode 100644 src/IO/TempStream.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dbfbd33..ca50b653 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ > require php 8.0+ +- feat: allow attach multi level sub-commands to command,group +- feat: support add flags config for add alone Closure command + ## v4.0.x > require php 7.3+ diff --git a/TODO.md b/TODO.md index dd9fa863..e7ce729d 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,5 @@ # TODO - [x] Add more events supports -- [ ] Add nested command supports +- [x] Add nested command supports - [ ] Simpler bash command completion support diff --git a/src/Concern/AbstractOutput.php b/src/Concern/AbstractOutput.php index f55f163c..aff92af6 100644 --- a/src/Concern/AbstractOutput.php +++ b/src/Concern/AbstractOutput.php @@ -10,6 +10,7 @@ namespace Inhere\Console\Concern; use Inhere\Console\Contract\OutputInterface; +use Toolkit\Stdlib\Obj\Traits\AutoConfigTrait; /** * Class AbstractOutput @@ -18,6 +19,8 @@ */ abstract class AbstractOutput implements OutputInterface { + use AutoConfigTrait; + /** * @return bool */ diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index 8c68359a..975a6a08 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -134,7 +134,6 @@ public static function aliases(): array * @param Input|null $input * @param Output|null $output */ - // public function __construct(Input $input, Output $output) public function __construct(Input $input = null, Output $output = null) { $this->input = $input; @@ -148,7 +147,6 @@ public function __construct(Input $input = null, Output $output = null) protected function init(): void { - // $this->commentsVars = $this->annotationVars(); $this->afterInit(); $this->debugf('attach inner subcommands to "%s"', $this->getRealName()); $this->addCommands($this->subCommands()); @@ -402,7 +400,7 @@ protected function beforeExecute(): bool * @param Input $input * @param Output $output * - * @return void|mixed + * @return mixed|void */ abstract protected function execute(Input $input, Output $output); diff --git a/src/IO/Output.php b/src/IO/Output.php index a06eca34..018500d2 100644 --- a/src/IO/Output.php +++ b/src/IO/Output.php @@ -34,9 +34,13 @@ class Output extends StreamOutput /** * Output constructor. */ - public function __construct() + public function __construct(array $config = []) { - parent::__construct(Cli::getOutputStream()); + if (!isset($config['stream'])) { + $config['stream'] = Cli::getOutputStream(); + } + + parent::__construct($config); } /*************************************************************************** @@ -64,8 +68,8 @@ public function clearBuffer(): void * * @param bool $flush * @param bool $nl - * @param bool|int $quit - * @param array $opts + * @param bool $quit + * @param array{quitCode:int} $opts * * @see Console::stopBuffer() */ diff --git a/src/IO/Output/BufferedOutput.php b/src/IO/Output/BufferedOutput.php index f93def42..5b61867f 100644 --- a/src/IO/Output/BufferedOutput.php +++ b/src/IO/Output/BufferedOutput.php @@ -9,7 +9,10 @@ namespace Inhere\Console\IO\Output; -use Inhere\Console\Concern\AbstractOutput; +use Inhere\Console\IO\Output; +use function implode; +use function is_array; +use function sprintf; use function strlen; use const PHP_EOL; @@ -17,7 +20,7 @@ * Class BufferedOutput * @package Inhere\Console\IO\Output */ -class BufferedOutput extends AbstractOutput +class BufferedOutput extends Output { /** * @var string @@ -40,37 +43,114 @@ public function fetch(bool $reset = true): string return $str; } + /** + * @return string + */ + public function toString(): string + { + return $this->fetch(); + } + + public function reset(): void + { + $this->buffer = ''; + } + + /** + * @return string + */ public function __toString(): string { return $this->fetch(); } /** - * @param string $content + * @param mixed $messages + * @param bool $nl + * @param bool $quit + * @param array $opts * * @return int */ - public function write(string $content): int + public function write($messages, $nl = true, $quit = false, array $opts = []): int { - $this->buffer .= $content; - return strlen($content); + if (is_array($messages)) { + $str = implode($nl ? PHP_EOL : '', $messages); + } else { + $str = (string)$messages; + } + + if ($nl) { + $str .= PHP_EOL; + } + + $this->buffer .= $str; + return strlen($str); } /** - * @param string $content + * @param mixed $messages * @param bool $quit * @param array $opts * * @return int */ - public function writeln($content, bool $quit = false, array $opts = []): int + public function writeln($messages, bool $quit = false, array $opts = []): int { - $this->buffer .= $content . PHP_EOL; - return strlen($content) + 1; + return $this->write($messages, true, $quit, $opts); } - public function reset(): void + /** + * Write a message to output with format. + * + * @param string $format + * @param mixed ...$args + * + * @return int + */ + public function writef(string $format, ...$args): int + { + return $this->write(sprintf($format, ...$args)); + } + + /** + * start buffering + */ + public function startBuffer(): void + { + } + + /** + * clear buffering + */ + public function clearBuffer(): void + { + $this->reset(); + } + + /** + * stop buffering and flush buffer text + * + * @param bool $flush + * @param bool $nl + * @param bool $quit + * @param array{quitCode:int} $opts + * + * @see Console::stopBuffer() + */ + public function stopBuffer(bool $flush = true, bool $nl = false, bool $quit = false, array $opts = []): void + { + + } + + /** + * stop buffering and flush buffer text + * + * @param bool $nl + * @param bool $quit + * @param array $opts + */ + public function flush(bool $nl = false, bool $quit = false, array $opts = []): void { - $this->buffer = ''; } } diff --git a/src/IO/Output/MemoryOutput.php b/src/IO/Output/MemoryOutput.php index ded3af00..894e5b7e 100644 --- a/src/IO/Output/MemoryOutput.php +++ b/src/IO/Output/MemoryOutput.php @@ -9,6 +9,7 @@ namespace Inhere\Console\IO\Output; +use Toolkit\FsUtil\File; use function fopen; /** @@ -18,9 +19,26 @@ */ class MemoryOutput extends StreamOutput { - public function __construct() + /** + * Class constructor. + * + * @param array{stream: resource} $config + */ + public function __construct(array $config = []) + { + if (!isset($config['stream'])) { + $config['stream'] = fopen('php://memory', 'rwb'); + } + + parent::__construct($config); + } + + /** + * @return string + */ + public function fetch(): string { - parent::__construct(fopen('php://memory', 'rwb')); + return File::streamReadAll($this->stream); } /** @@ -28,6 +46,6 @@ public function __construct() */ public function getBuffer(): string { - return ''; + return $this->fetch(); } } diff --git a/src/IO/Output/StreamOutput.php b/src/IO/Output/StreamOutput.php index c5e86dca..55429bdd 100644 --- a/src/IO/Output/StreamOutput.php +++ b/src/IO/Output/StreamOutput.php @@ -30,17 +30,7 @@ class StreamOutput extends AbstractOutput * * @var resource */ - protected $stream; - - /** - * StreamInput constructor. - * - * @param resource $stream - */ - public function __construct($stream = STDOUT) - { - $this->setStream($stream); - } + protected $stream = STDOUT; /** * @param string $content @@ -90,13 +80,13 @@ public function getStream() /** * @param resource $stream */ - protected function setStream($stream): void + public function setStream($stream): void { File::assertStream($stream); $meta = stream_get_meta_data($stream); if (!str_contains($meta['mode'], 'w') && !str_contains($meta['mode'], '+')) { - throw new InvalidArgumentException('Expected a readable stream'); + throw new InvalidArgumentException('Expected a writeable stream'); } $this->stream = $stream; diff --git a/src/IO/Output/TempOutput.php b/src/IO/Output/TempOutput.php new file mode 100644 index 00000000..7f2df719 --- /dev/null +++ b/src/IO/Output/TempOutput.php @@ -0,0 +1,54 @@ +stream); + } + + /** + * @return string + */ + public function getBuffer(): string + { + return $this->fetch(); + } +} diff --git a/src/IO/TempStream.php b/src/IO/TempStream.php deleted file mode 100644 index 4a57d2ec..00000000 --- a/src/IO/TempStream.php +++ /dev/null @@ -1,98 +0,0 @@ -assertEquals('Inhere\ConsoleTest\TestCommand::execute', $str); } - public function testCommand_alone_run_sub(): void + public function testCommand_sub_run(): void { - $c = new TestCommand(new Input(), new Output()); + $c = new TestCommand(new Input(), Output::new()); $str = $c->run(['sub1']); $this->assertEquals('Inhere\ConsoleTest\{closure}', $str); } + + public function testCommand_sub_help(): void + { + $c = new TestCommand(new Input(), $buf = Output\BufferedOutput::new()); + $this->assertNotEmpty($c); + + $c->run(['sub1', '-h']); + vdump($buf->toString()); + } } From 71c0b4a6e0db98ffb4a0891c65fc6f7bd9c15e41 Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 7 Jan 2022 11:09:30 +0800 Subject: [PATCH 223/258] up: remove some depercated methods, modify command flag option --- src/Command.php | 15 ++---------- src/Controller.php | 34 ++------------------------- src/Decorate/SubCommandsWareTrait.php | 30 +++++++++++++++++++++-- src/Handler/AbstractHandler.php | 25 +++++++++++++------- 4 files changed, 48 insertions(+), 56 deletions(-) diff --git a/src/Command.php b/src/Command.php index a3cd4f56..121829d1 100644 --- a/src/Command.php +++ b/src/Command.php @@ -39,13 +39,6 @@ abstract class Command extends AbstractHandler implements CommandInterface */ protected ?Controller $group = null; - protected function init(): void - { - $this->commandName = $this->getRealName(); - - parent::init(); - } - /** * @return array */ @@ -60,12 +53,7 @@ protected function getArguments(): array protected function beforeInitFlagsParser(FlagsParser $fs): void { $fs->addArgsByRules($this->getArguments()); - $fs->setStopOnFistArg(false); - - // old mode: options and arguments at method annotations - // if ($this->compatible) { - // $fs->setSkipOnUndefined(true); - // } + // $fs->setStopOnFistArg(false); } /** @@ -77,6 +65,7 @@ protected function afterInitFlagsParser(FlagsParser $fs): void { $this->debugf('load flags configure for command: %s', $this->getRealCName()); $this->configure(); + $this->configFlags($fs); $isEmpty = $this->flags->isEmpty(); diff --git a/src/Controller.php b/src/Controller.php index 724c522e..f5bd4dd5 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -21,7 +21,6 @@ use ReflectionException; use ReflectionMethod; use ReflectionObject; -use RuntimeException; use Throwable; use Toolkit\PFlag\FlagsParser; use Toolkit\PFlag\FlagUtil; @@ -278,7 +277,6 @@ public function doRun(array $args): mixed $command = $first; array_shift($args); - // $this->input->popFirstArg(); } // update subcommand @@ -531,7 +529,7 @@ protected function beforeRenderCommandHelp(array &$help): void * @param ReflectionClass|null $ref * @param bool $onlyName * - * @return Generator + * @return ?Generator */ protected function getAllCommandMethods(ReflectionClass $ref = null, bool $onlyName = false): ?Generator { @@ -556,17 +554,6 @@ protected function getAllCommandMethods(ReflectionClass $ref = null, bool $onlyN } } - /** - * @param string $name - * - * @return string - * @deprecated please use resolveAlias() - */ - public function getRealCommandName(string $name): string - { - return $this->resolveAlias($name); - } - /** * @param string $alias * @@ -631,7 +618,7 @@ public function getDisabledCommands(): array } /** - * @param string|null $name + * @param string $name * * @return array */ @@ -752,23 +739,6 @@ public function setActionSuffix(string $actionSuffix): void $this->actionSuffix = $actionSuffix; } - /** - * @return bool - * @deprecated - */ - public function isExecutionAlone(): bool - { - throw new RuntimeException('please call isAttached() instead'); - } - - /** - * @deprecated - */ - public function setExecutionAlone(): void - { - throw new RuntimeException('please call setAttached() instead'); - } - /** * @return string */ diff --git a/src/Decorate/SubCommandsWareTrait.php b/src/Decorate/SubCommandsWareTrait.php index ce4655ee..443bae0b 100644 --- a/src/Decorate/SubCommandsWareTrait.php +++ b/src/Decorate/SubCommandsWareTrait.php @@ -9,13 +9,11 @@ namespace Inhere\Console\Decorate; -use Closure; use Inhere\Console\Command; use Inhere\Console\Console; use Inhere\Console\Contract\CommandInterface; use Inhere\Console\Handler\AbstractHandler; use Inhere\Console\Handler\CommandWrapper; -use Inhere\Console\Util\ConsoleUtil; use Inhere\Console\Util\Helper; use InvalidArgumentException; use Toolkit\Stdlib\Helper\Assert; @@ -49,6 +47,13 @@ trait SubCommandsWareTrait */ private array $blocked = ['help', 'version']; + /** + * Command full path. eg: 'git remote set-url' + * + * @var string + */ + protected string $path = ''; + /** * The sub-commands of the command * @@ -268,6 +273,26 @@ public function isSub(string $name): bool return isset($this->commands[$name]); } + /** + * @param string $path + */ + public function setPath(string $path): void + { + $this->path = $path; + } + + /** + * @param string $name + */ + public function addPath(string $name): void + { + if ($this->path) { + $this->path .= ' ' . $name; + } else { + $this->path = $name; + } + } + /** * @param string $name * @@ -341,6 +366,7 @@ public function getSubsForHelp(): array if ($sub instanceof Command) { $subs[$name] = $sub->getRealDesc(); } elseif (is_string($sub)) { + /** @var Command $sub */ $subs[$name] = $sub::getDesc(); } else { $subConf = $subInfo['config']; diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index 975a6a08..57c0c338 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -78,13 +78,6 @@ abstract class AbstractHandler implements CommandHandlerInterface */ private bool $initialized = false; - /** - * Compatible mode run command. - * - * @var bool - */ - protected bool $compatible = true; - /** * @var string */ @@ -96,7 +89,7 @@ abstract class AbstractHandler implements CommandHandlerInterface protected ?DataObject $params = null; /** - * The input command name. maybe is an alias name. + * The user input command name. maybe is an alias name. * * @var string */ @@ -184,6 +177,15 @@ protected function configure(): void { } + /** + * Config flags for the command/controller. + * + * @param FlagsParser $fs + */ + protected function configFlags(FlagsParser $fs): void + { + } + /** * Provides parsable substitution variables for command annotations. Can be used in comments in commands * 为命令注解提供可解析的替换变量. 可以在命令的注释中使用 @@ -232,6 +234,11 @@ protected function initForRun(): void { $this->commentsVars = $this->annotationVars(); + if (!$this->commandName) { + $this->commandName = $this->getRealName(); + } + + $this->addPath($this->commandName); } /** @@ -296,7 +303,7 @@ public function run(array $args): mixed $this->initFlagsParser($this->input); - $this->log(Console::VERB_DEBUG, "begin run '$name' - parse options", ['args' => $args]); + $this->log(Console::VERB_DEBUG, "cmd: $name - parse flag options", ['args' => $args]); // parse options $this->flags->lock(); From bf25644c849be9a590d36ea83000f7a6c6c274d1 Mon Sep 17 00:00:00 2001 From: Inhere Date: Sat, 29 Jan 2022 16:37:02 +0800 Subject: [PATCH 224/258] fix: phar compiler prop: modifies not init --- src/Component/PharCompiler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Component/PharCompiler.php b/src/Component/PharCompiler.php index 984900a0..fd55d9eb 100644 --- a/src/Component/PharCompiler.php +++ b/src/Component/PharCompiler.php @@ -230,7 +230,7 @@ class PharCompiler * The modifies files list. if not empty, will skip find dirs. * @var array|Traversable */ - private array|Traversable $modifies; + private array|Traversable $modifies = []; /** * @var SplQueue From a3e0851b3d9180040a8989ab91e3487e12cb68f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Mar 2022 11:21:59 +0000 Subject: [PATCH 225/258] chore(deps): bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/php.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 88020de6..408c03e9 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set ENV vars # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f5a3c690..eae93734 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set ENV for github-release # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable From 40b4cc8b33537848f6c43701a2b4b274caa270ee Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 25 Mar 2022 14:09:13 +0800 Subject: [PATCH 226/258] up: update some logic for run sub commands --- src/AbstractApplication.php | 8 ++-- src/Application.php | 2 +- src/Command.php | 2 +- src/Concern/AbstractInput.php | 37 ----------------- src/Controller.php | 11 +++-- src/Decorate/CommandHelpTrait.php | 21 ++++++---- src/Decorate/ControllerHelpTrait.php | 2 +- src/Decorate/SubCommandsWareTrait.php | 60 +++++++++++++++++++++++---- src/Handler/AbstractHandler.php | 27 ++++++------ src/IO/Input.php | 25 ++++++++++- 10 files changed, 115 insertions(+), 80 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 9040b122..f311df9f 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -11,13 +11,13 @@ use ErrorException; use Inhere\Console\Component\ErrorHandler; -use Inhere\Console\Component\Router; use Inhere\Console\Component\Formatter\Title; -use Inhere\Console\Decorate\SimpleEventAwareTrait; +use Inhere\Console\Component\Router; use Inhere\Console\Contract\ApplicationInterface; use Inhere\Console\Contract\ErrorHandlerInterface; -use Inhere\Console\Decorate\InputOutputAwareTrait; use Inhere\Console\Decorate\ApplicationHelpTrait; +use Inhere\Console\Decorate\InputOutputAwareTrait; +use Inhere\Console\Decorate\SimpleEventAwareTrait; use Inhere\Console\Decorate\StyledOutputAwareTrait; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; @@ -276,7 +276,7 @@ protected function beforeRun(): bool $this->commandName = $command; $this->flags->popFirstRawArg(); $this->input->setCommand($command); - $this->logf(Console::VERB_DEBUG, 'found the application command: %s', $command); + $this->logf(Console::VERB_DEBUG, 'app - match and found the command: %s', $command); // like: help, version, list if (!$this->handleGlobalCommand($command)) { diff --git a/src/Application.php b/src/Application.php index 26b5afca..a98bde64 100644 --- a/src/Application.php +++ b/src/Application.php @@ -232,7 +232,7 @@ public function dispatch(string $name, array $args = []): mixed } $cmdId = $name; - $this->debugf('begin dispatch the input command: %s, args: %s', $name, DataHelper::toString($args)); + $this->debugf('app - begin dispatch the input command: %s, args: %s', $name, DataHelper::toString($args)); // format is: `group action` if (strpos($name, ' ') > 0) { diff --git a/src/Command.php b/src/Command.php index 121829d1..192677ae 100644 --- a/src/Command.php +++ b/src/Command.php @@ -63,7 +63,7 @@ protected function beforeInitFlagsParser(FlagsParser $fs): void */ protected function afterInitFlagsParser(FlagsParser $fs): void { - $this->debugf('load flags configure for command: %s', $this->getRealCName()); + $this->debugf('cmd: %s - load command flags configure', $this->getRealCName()); $this->configure(); $this->configFlags($fs); diff --git a/src/Concern/AbstractInput.php b/src/Concern/AbstractInput.php index 9087d75f..55ce8e50 100644 --- a/src/Concern/AbstractInput.php +++ b/src/Concern/AbstractInput.php @@ -70,14 +70,6 @@ abstract class AbstractInput implements InputInterface */ protected string $command = ''; - /** - * the command name(Is first argument) - * e.g `subcommand` in the `./app group subcommand` - * - * @var string - */ - protected string $subCommand = ''; - /** * eg `./examples/app home:useArg status=2 name=john arg0 -s=test --page=23` * @@ -165,19 +157,6 @@ protected function collectInfo(array $rawFlags): void $this->fullScript = implode(' ', $rawFlags); } - /** - * @return string - */ - public function getCommandPath(): string - { - $path = $this->command; - if ($this->subCommand) { - $path .= ' ' . $this->subCommand; - } - - return $path; - } - /** * @return bool */ @@ -335,22 +314,6 @@ public function setTokens(array $tokens): void $this->collectInfo($tokens); } - /** - * @return string - */ - public function getSubCommand(): string - { - return $this->subCommand; - } - - /** - * @param string $subCommand - */ - public function setSubCommand(string $subCommand): void - { - $this->subCommand = $subCommand; - } - /** * @return FlagsParser */ diff --git a/src/Controller.php b/src/Controller.php index f5bd4dd5..dc4a7aad 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -265,13 +265,13 @@ public function doRun(array $args): mixed // and not default command if (!$command) { - $this->debugf('run args is empty, display help for the group: %s', $name); + $this->debugf('cmd: %s - run args is empty, display help for the group', $name); return $this->showHelp(); } } else { $first = $args[0]; if (!FlagUtil::isValidName($first)) { - $this->debugf('not input subcommand, display help for the group: %s', $name); + $this->debugf('cmd: %s - not input subcommand, display help for the group', $name); return $this->showHelp(); } @@ -281,13 +281,12 @@ public function doRun(array $args): mixed // update subcommand $this->commandName = $command; - $this->input->setSubCommand($command); // update some comment vars - $fullCmd = $this->input->getFullCommand(); + $fullCmd = $this->input->buildFullCmd($name, $command); $this->setCommentsVar('fullCmd', $fullCmd); $this->setCommentsVar('fullCommand', $fullCmd); - $this->setCommentsVar('binWithCmd', $this->input->getBinWithCommand()); + $this->setCommentsVar('binWithCmd', $this->input->buildCmdPath($name, $command)); // get real sub-command name $command = $this->resolveAlias($command); @@ -297,7 +296,7 @@ public function doRun(array $args): mixed // convert 'boo-foo' to 'booFoo' $this->action = $action = Str::camelCase($command); - $this->debugf("will run the '%s' group action: %s, subcommand: %s", $name, $action, $command); + $this->debugf("cmd: %s - will run the subcommand: %s(action: %s)", $name, $command, $action); $method = $this->getMethodName($action); // fire event diff --git a/src/Decorate/CommandHelpTrait.php b/src/Decorate/CommandHelpTrait.php index 79e53f92..bb5ebad1 100644 --- a/src/Decorate/CommandHelpTrait.php +++ b/src/Decorate/CommandHelpTrait.php @@ -9,8 +9,8 @@ namespace Inhere\Console\Decorate; -use Inhere\Console\Handler\AbstractHandler; use Inhere\Console\Console; +use Inhere\Console\Handler\AbstractHandler; use Toolkit\PFlag\FlagsParser; use Toolkit\PFlag\FlagUtil; use function implode; @@ -125,8 +125,8 @@ public function showHelpByFlagsParser(FlagsParser $fs, array $aliases = [], stri $name = $this->getCommandName(); // $isCommand = $this->isCommand(); - $commandId = $this->input->getCommandId(); - $this->logf(Console::VERB_DEBUG, 'render help for the command: %s', $commandId); + // $commandId = $this->input->getCommandId(); + $this->logf(Console::VERB_DEBUG, 'cmd: %s - begin render help for the command', $name); if ($aliases) { $realName = $action ?: $this->getRealName(); @@ -135,17 +135,20 @@ public function showHelpByFlagsParser(FlagsParser $fs, array $aliases = [], stri } $binName = $this->input->getBinName(); - - $path = $binName . ' ' . $name; - if ($action) { - $group = $this->getGroupName(); - $path = "$binName $group $action"; + $cmdPath = $binName . ' ' . $this->getPath(); + // if ($action) { + // $group = $this->getGroupName(); + // $cmdPath = "$binName $group $action"; + // } + + if ($this->hasSubs()) { + $cmdPath .= ' SUBCOMMAND'; } $desc = $fs->getDesc(); $this->writeln(ucfirst($this->parseCommentsVars($desc))); - $help['Usage:'] = "$path [--options ...] [arguments ...]"; + $help['Usage:'] = "$cmdPath [--options ...] [arguments ...]"; $help['Options:'] = FlagUtil::alignOptions($fs->getOptsHelpLines()); $help['Argument:'] = $fs->getArgsHelpLines(); diff --git a/src/Decorate/ControllerHelpTrait.php b/src/Decorate/ControllerHelpTrait.php index 7fb0553a..ad368302 100644 --- a/src/Decorate/ControllerHelpTrait.php +++ b/src/Decorate/ControllerHelpTrait.php @@ -92,7 +92,7 @@ protected function beforeShowCommandList(): void public function showCommandList(): void { $name = self::getName(); - $this->logf(Console::VERB_DEBUG, 'display all sub-commands list of the group: %s', $name); + $this->logf(Console::VERB_DEBUG, 'cmd: %s - display all sub-commands list of the group', $name); $this->beforeShowCommandList(); $refCls = new ReflectionClass($this); diff --git a/src/Decorate/SubCommandsWareTrait.php b/src/Decorate/SubCommandsWareTrait.php index 443bae0b..2bf1493f 100644 --- a/src/Decorate/SubCommandsWareTrait.php +++ b/src/Decorate/SubCommandsWareTrait.php @@ -16,11 +16,15 @@ use Inhere\Console\Handler\CommandWrapper; use Inhere\Console\Util\Helper; use InvalidArgumentException; +use Toolkit\Cli\Color\ColorTag; use Toolkit\Stdlib\Helper\Assert; use Toolkit\Stdlib\Obj\Traits\NameAliasTrait; use function array_keys; use function array_merge; use function class_exists; +use function count; +use function explode; +use function implode; use function in_array; use function is_int; use function is_object; @@ -54,6 +58,13 @@ trait SubCommandsWareTrait */ protected string $path = ''; + /** + * Command full path nodes. eg: ['git', 'remote', 'set-url'] + * + * @var string[] + */ + protected array $pathNodes = []; + /** * The sub-commands of the command * @@ -64,6 +75,7 @@ trait SubCommandsWareTrait * 'config' => [ * 'name' => 'string', * 'desc' => 'string', + * 'aliases' => [], * 'options' => [], * 'arguments' => [], * ] @@ -103,11 +115,12 @@ protected function subCommands(): array protected function dispatchSub(string $name, array $args): mixed { $subInfo = $this->commands[$name]; - $this->debugf('dispatch the attached subcommand: %s', $name); + $this->debugf('cmd: %s - dispatch the attached subcommand: %s', $this->getRealName(), $name); // create and init sub-command $subCmd = $this->createSubCommand($subInfo); $subCmd->setParent($this); + $subCmd->setPath($this->path); $subCmd->setInputOutput($this->input, $this->output); return $subCmd->run($args); @@ -273,23 +286,39 @@ public function isSub(string $name): bool return isset($this->commands[$name]); } + /** + * @param string $sep + * + * @return string + */ + public function getPath(string $sep = ''): string + { + return $sep ? implode($sep, $this->pathNodes) : $this->path; + } + /** * @param string $path */ public function setPath(string $path): void { $this->path = $path; + // set path nodes + $this->pathNodes = explode(' ', $path); } /** * @param string $name */ - public function addPath(string $name): void + public function addPathNode(string $name): void { if ($this->path) { $this->path .= ' ' . $name; + // add path nodes + $this->pathNodes[] = $name; } else { $this->path = $name; + // set path nodes + $this->pathNodes = [$name]; } } @@ -339,6 +368,14 @@ public function setBlocked(array $blocked): void $this->blocked = $blocked; } + /** + * @return bool + */ + public function hasSubs(): bool + { + return count($this->commands) > 0; + } + /** * @return array */ @@ -364,15 +401,24 @@ public function getSubsForHelp(): array foreach ($this->commands as $name => $subInfo) { $sub = $subInfo['handler']; if ($sub instanceof Command) { - $subs[$name] = $sub->getRealDesc(); + $desc = $sub->getRealDesc(); + // alias names + $aliases = $sub::aliases(); } elseif (is_string($sub)) { /** @var Command $sub */ - $subs[$name] = $sub::getDesc(); + $desc = $sub::getDesc(); + // alias names + $aliases = $sub::aliases(); } else { - $subConf = $subInfo['config']; - - $subs[$name] = $subConf['desc'] ?? 'no description'; + $conf = $subInfo['config']; + $desc = $conf['desc'] ?? 'no description'; + // alias names + $aliases = $conf['aliases'] ?? []; } + + $extra = $aliases ? ColorTag::wrap(' (alias: ' . implode(',', $aliases) . ')', 'info') : ''; + // add help desc + $subs[$name] = $desc . $extra; } return $subs; diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index 57c0c338..75f78996 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -11,15 +11,15 @@ use Inhere\Console\Annotate\DocblockRules; use Inhere\Console\Component\ErrorHandler; -use Inhere\Console\Decorate\AttachApplicationTrait; -use Inhere\Console\Decorate\InputOutputAwareTrait; -use Inhere\Console\Decorate\UserInteractAwareTrait; use Inhere\Console\Console; use Inhere\Console\ConsoleEvent; use Inhere\Console\Contract\CommandHandlerInterface; use Inhere\Console\Contract\CommandInterface; +use Inhere\Console\Decorate\AttachApplicationTrait; use Inhere\Console\Decorate\CommandHelpTrait; +use Inhere\Console\Decorate\InputOutputAwareTrait; use Inhere\Console\Decorate\SubCommandsWareTrait; +use Inhere\Console\Decorate\UserInteractAwareTrait; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; use Inhere\Console\Util\Helper; @@ -33,6 +33,7 @@ use function cli_set_process_title; use function error_get_last; use function function_exists; +use function implode; use const PHP_OS; /** @@ -141,8 +142,11 @@ public function __construct(Input $input = null, Output $output = null) protected function init(): void { $this->afterInit(); - $this->debugf('attach inner subcommands to "%s"', $this->getRealName()); - $this->addCommands($this->subCommands()); + + if ($subCmds = $this->subCommands()) { + $this->addCommands($subCmds); + $this->debugf('cmd: %s - load and attach subcommands: %s', $this->getRealName(), implode(',', $this->getSubNames())); + } } protected function afterInit(): void @@ -205,10 +209,10 @@ protected function configFlags(FlagsParser $fs): void */ protected function annotationVars(): array { - $fullCmd = $this->input->getFullCommand(); $binFile = $this->input->getScriptFile(); // bin/app $binName = $this->input->getScriptName(); $command = $this->input->getCommand(); + $fullCmd = $this->input->buildFullCmd($command); // e.g: `more info see {name}:index` return [ @@ -238,7 +242,7 @@ protected function initForRun(): void $this->commandName = $this->getRealName(); } - $this->addPath($this->commandName); + $this->addPathNode($this->commandName); } /** @@ -262,11 +266,10 @@ protected function initFlagsParser(Input $input): void $this->flags->addOptsByRules($optRules); // for render help - $this->flags->setBeforePrintHelp(function (string $text) { - return $this->parseCommentsVars($text); - }); + $this->flags->setBeforePrintHelp(fn(string $text): string => $this->parseCommentsVars($text)); $this->flags->setHelpRenderer(function (): void { - $this->logf(Console::VERB_DEBUG, 'show help message by input flags: -h, --help'); + $name = $this->getRealName(); + $this->logf(Console::VERB_DEBUG, "cmd: $name - show help by input flags: -h, --help"); $this->showHelp(); }); @@ -489,7 +492,7 @@ public function initParams(array $params): void */ public function loadRulesByDocblock(string $method, FlagsParser $fs): void { - $this->debugf('not config flags, load flag rules by docblock, method: %s', $method); + $this->debugf('cmd: %s - not config flags, load flag rules by method %s() docblock', $this->getRealCName(), $method); $rftMth = PhpHelper::reflectMethod($this, $method); // parse doc for get flag rules diff --git a/src/IO/Input.php b/src/IO/Input.php index 55e2eed7..187fe9bc 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -11,6 +11,7 @@ use Inhere\Console\IO\Input\StreamInput; use Toolkit\Cli\Cli; +use function implode; /** * Class Input - The std input. @@ -64,7 +65,7 @@ public function resetInputStream(): void */ public function getBinWithCommand(): string { - return $this->scriptName . ' ' . $this->getCommandPath(); + return $this->scriptName . ' ' . $this->command; } /** @@ -72,7 +73,27 @@ public function getBinWithCommand(): string */ public function getFullCommand(): string { - return $this->scriptFile . ' ' . $this->getCommandPath(); + return $this->scriptFile . ' ' . $this->command; + } + + /** + * @param string ...$names + * + * @return string + */ + public function buildCmdPath(string... $names): string + { + return $this->scriptName . ' ' . implode(' ', $names); + } + + /** + * @param string ...$names + * + * @return string + */ + public function buildFullCmd(string... $names): string + { + return $this->scriptFile . ' ' . implode(' ', $names); } /** From 7720ce7579898d727ad83d985f68b69ccae77c22 Mon Sep 17 00:00:00 2001 From: Inhere Date: Wed, 13 Apr 2022 19:11:53 +0800 Subject: [PATCH 227/258] up: update some for error handler render exception --- src/Component/ErrorHandler.php | 3 ++- src/Concern/AbstractInput.php | 36 +++++++++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/Component/ErrorHandler.php b/src/Component/ErrorHandler.php index d10fd1e8..1b5eebb0 100644 --- a/src/Component/ErrorHandler.php +++ b/src/Component/ErrorHandler.php @@ -86,6 +86,7 @@ public function handle(Throwable $e): void ERR; $line = $e->getLine(); $file = $e->getFile(); + $prev = $e->getPrevious(); $snippet = Highlighter::create()->snippet(file_get_contents($file), $line, 3, 3); $message = sprintf( @@ -94,7 +95,7 @@ public function handle(Throwable $e): void $file, $line, // __METHOD__, $snippet, - $e->getTraceAsString()// \str_replace('):', '): -', $e->getTraceAsString()) + $e->getTraceAsString() . ($prev ? "\nPrevious: {$prev->getMessage()}\n" . $prev->getTraceAsString() : '') ); if ($this->hideRootPath && ($rootPath = $this->rootPath)) { diff --git a/src/Concern/AbstractInput.php b/src/Concern/AbstractInput.php index 55ce8e50..3111a8c9 100644 --- a/src/Concern/AbstractInput.php +++ b/src/Concern/AbstractInput.php @@ -104,13 +104,37 @@ public function __toString(): string * @return string */ public function toString(): string + { + return $this->getTokenString(); + } + + /** + * @param bool $escape + * + * @return string + */ + public function getTokenString(bool $escape = true): string { if (!$this->tokens) { return ''; } - $tokens = array_map([$this, 'tokenEscape'], $this->tokens); - return implode(' ', $tokens); + if ($escape) { + $tokens = array_map([$this, 'tokenEscape'], $this->tokens); + return implode(' ', $tokens); + } + + return implode(' ', $this->tokens); + } + + /** + * @param bool $escape + * + * @return string + */ + public function getInputUri(bool $escape = true): string + { + return $this->getTokenString($escape); } /** @@ -258,10 +282,16 @@ public function setCommand(string $command): void } /** + * @param bool $withBinName + * * @return string */ - public function getFullScript(): string + public function getFullScript(bool $withBinName = false): string { + if ($withBinName) { + return $this->getBinName() . ' ' . $this->fullScript; + } + return $this->fullScript; } From 41067a5e0181379efe4ebe291320d259887681f8 Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 14 Apr 2022 10:21:17 +0800 Subject: [PATCH 228/258] up: update some for add global option --- src/Application.php | 14 ++++++++++---- src/Concern/AbstractInput.php | 26 ++++++++++++++++++++++---- src/GlobalOption.php | 33 ++++++++++++++++++++++----------- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/src/Application.php b/src/Application.php index a98bde64..c6e9153b 100644 --- a/src/Application.php +++ b/src/Application.php @@ -179,13 +179,16 @@ public function commands(array $commands): void */ public function registerCommands(string $namespace, string $basePath): static { + $this->debugf('register commands from the namespace: %s', $namespace); + $length = strlen($basePath) + 1; // $iterator = Helper::directoryIterator($basePath, $this->getFileFilter()); $iter = Dir::getIterator($basePath, Dir::getPhpFileFilter()); foreach ($iter as $file) { - $class = $namespace . '\\' . substr($file->getPathName(), $length, -4); - $this->addCommand($class); + $subPath = substr($file->getPathName(), $length, -4); + $fullClass = $namespace . '\\' . str_replace('/', '\\', $subPath); + $this->addCommand($fullClass); } return $this; @@ -202,13 +205,16 @@ public function registerCommands(string $namespace, string $basePath): static */ public function registerGroups(string $namespace, string $basePath): self { + $this->debugf('register groups from the namespace: %s', $namespace); + $length = strlen($basePath) + 1; // $iterator = Helper::directoryIterator($basePath, $this->getFileFilter()); $iter = Dir::getIterator($basePath, Dir::getPhpFileFilter()); foreach ($iter as $file) { - $class = $namespace . '\\' . substr($file->getPathName(), $length, -4); - $this->addController($class); + $subPath = substr($file->getPathName(), $length, -4); + $fullClass = $namespace . '\\' . str_replace('/', '\\', $subPath); + $this->addController($fullClass); } return $this; diff --git a/src/Concern/AbstractInput.php b/src/Concern/AbstractInput.php index 3111a8c9..d4afe2b0 100644 --- a/src/Concern/AbstractInput.php +++ b/src/Concern/AbstractInput.php @@ -15,6 +15,7 @@ use function array_map; use function array_shift; use function basename; +use function chdir; use function getcwd; use function implode; use function is_string; @@ -194,11 +195,13 @@ public function isInteractive(): bool ***********************************************************************************/ /** + * @param bool $refresh + * * @return string */ - public function getPwd(): string + public function getPwd(bool $refresh = false): string { - if (!$this->pwd) { + if (!$this->pwd || $refresh) { $this->pwd = (string)getcwd(); } @@ -206,11 +209,26 @@ public function getPwd(): string } /** + * @param bool $refresh + * * @return string */ - public function getWorkDir(): string + public function getWorkDir(bool $refresh = false): string { - return $this->getPwd(); + return $this->getPwd($refresh); + } + + /** + * @param string $workdir + * + * @return void + */ + public function chWorkDir(string $workdir): void + { + if ($workdir) { + chdir($workdir); + $this->getPwd(true); + } } /** diff --git a/src/GlobalOption.php b/src/GlobalOption.php index 835cff24..20180099 100644 --- a/src/GlobalOption.php +++ b/src/GlobalOption.php @@ -46,10 +46,12 @@ class GlobalOption ]; /** + * Global options config + * * @var array * @psalm-var array */ - private static array $options = [ + private static array $globalOptions = [ 'debug' => [ 'type' => FlagType::INT, 'desc' => 'Setting the runtime log debug level, quiet 0 - crazy 5', @@ -125,26 +127,35 @@ class GlobalOption */ public static function isExists(string $name): bool { - return isset(self::KEY_MAP[$name]); + return isset(self::$globalOptions[$name]); + } + + /** + * @param string $name + * @param array $rule + */ + public static function setOption(string $name, array $rule): void + { + self::$globalOptions[$name] = $rule; } /** - * @param array $options + * @param array $globalOptions */ - public function setOptions(array $options): void + public static function setOptions(array $globalOptions): void { - if ($options) { - self::$options = $options; + if ($globalOptions) { + self::$globalOptions = $globalOptions; } } /** - * @param array $options + * @param array $globalOptions */ - public function addOptions(array $options): void + public static function addOptions(array $globalOptions): void { - if ($options) { - self::$options = array_merge(self::$options, $options); + if ($globalOptions) { + self::$globalOptions = array_merge(self::$globalOptions, $globalOptions); } } @@ -153,7 +164,7 @@ public function addOptions(array $options): void */ public static function getOptions(): array { - return self::$options; + return self::$globalOptions; } /** From a09383d06c16a989695824d13f2504745fd3df49 Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 14 Apr 2022 13:37:50 +0800 Subject: [PATCH 229/258] fix: use 'top sub' access command error --- src/Application.php | 339 ++++++++++++++++---------------- src/Handler/AbstractHandler.php | 4 +- 2 files changed, 174 insertions(+), 169 deletions(-) diff --git a/src/Application.php b/src/Application.php index c6e9153b..80ddd6bf 100644 --- a/src/Application.php +++ b/src/Application.php @@ -24,6 +24,8 @@ use Toolkit\PFlag\SFlags; use Toolkit\Stdlib\Helper\Assert; use Toolkit\Stdlib\Helper\DataHelper; +use Toolkit\Stdlib\Str; +use function array_shift; use function array_unshift; use function class_exists; use function implode; @@ -56,170 +58,6 @@ public function __construct(array $config = [], Input $input = null, Output $out parent::__construct($config, $input, $output); } - /**************************************************************************** - * register console controller/command - ****************************************************************************/ - - /** - * @param string $name - * @param ControllerInterface|string|null $class - * @param array $config - * - * @return $this - */ - public function controller(string $name, ControllerInterface|string $class = null, array $config = []): static - { - $this->logf(Console::VERB_CRAZY, 'register group controller: %s', $name); - $this->router->addGroup($name, $class, $config); - - return $this; - } - - /** - * Add group/controller - * - * @param string|class-string $name - * @param string|ControllerInterface|null $class The controller class - * @param array $config - * - * @return static - * @see controller() - */ - public function addGroup(string $name, ControllerInterface|string $class = null, array $config = []): static - { - return $this->controller($name, $class, $config); - } - - /** - * @param string $name - * @param string|ControllerInterface|null $class The controller class - * @param array $config - * - * @return $this - * @see controller() - */ - public function addController(string $name, ControllerInterface|string $class = null, array $config = []): static - { - return $this->controller($name, $class, $config); - } - - /** - * @param array $controllers - */ - public function controllers(array $controllers): void - { - $this->addControllers($controllers); - } - - /** - * @param array $controllers - */ - public function addControllers(array $controllers): void - { - $this->router->addControllers($controllers); - } - - /** - * @param string $name - * @param class-string|CommandInterface|null|Closure(FlagsParser, Output):void $handler - * @param array{desc: string, aliases: array, options: array, arguments: array} $config config the command. - * - * @return Application - */ - public function command(string $name, string|Closure|CommandInterface $handler = null, array $config = []): static - { - $this->logf(Console::VERB_CRAZY, 'register alone command: %s', $name); - $this->router->addCommand($name, $handler, $config); - - return $this; - } - - /** - * add command - * - * @param string $name - * @param class-string|CommandInterface|null|Closure(FlagsParser, Output):void $handler - * @param array{desc: string, aliases: array, options: array, arguments: array} $config config the command. - * - * @return Application - * @see command() - */ - public function addCommand(string $name, string|Closure|CommandInterface $handler = null, array $config = []): static - { - return $this->command($name, $handler, $config); - } - - /** - * @param array{string, mixed} $commands - */ - public function addCommands(array $commands): void - { - $this->router->addCommands($commands); - } - - /** - * @param array{string, mixed} $commands - */ - public function commands(array $commands): void - { - $this->addCommands($commands); - } - - /** - * auto register commands from a dir. - * - * ```php - * $app->registerCommands('SwagPhp\Command', dirname(__DIR__) . '/src/Command'); - * ``` - * - * @param string $namespace - * @param string $basePath - * - * @return $this - */ - public function registerCommands(string $namespace, string $basePath): static - { - $this->debugf('register commands from the namespace: %s', $namespace); - - $length = strlen($basePath) + 1; - // $iterator = Helper::directoryIterator($basePath, $this->getFileFilter()); - $iter = Dir::getIterator($basePath, Dir::getPhpFileFilter()); - - foreach ($iter as $file) { - $subPath = substr($file->getPathName(), $length, -4); - $fullClass = $namespace . '\\' . str_replace('/', '\\', $subPath); - $this->addCommand($fullClass); - } - - return $this; - } - - /** - * auto register controllers from a dir. - * - * @param string $namespace - * @param string $basePath - * - * @return $this - * @throws InvalidArgumentException - */ - public function registerGroups(string $namespace, string $basePath): self - { - $this->debugf('register groups from the namespace: %s', $namespace); - - $length = strlen($basePath) + 1; - // $iterator = Helper::directoryIterator($basePath, $this->getFileFilter()); - $iter = Dir::getIterator($basePath, Dir::getPhpFileFilter()); - - foreach ($iter as $file) { - $subPath = substr($file->getPathName(), $length, -4); - $fullClass = $namespace . '\\' . str_replace('/', '\\', $subPath); - $this->addController($fullClass); - } - - return $this; - } - /**************************************************************************** * Dispatch and run console controller/command ****************************************************************************/ @@ -238,11 +76,15 @@ public function dispatch(string $name, array $args = []): mixed } $cmdId = $name; - $this->debugf('app - begin dispatch the input command: %s, args: %s', $name, DataHelper::toString($args)); + $this->debugf('app - begin dispatch the input command: "%s", args: %s', $name, DataHelper::toString($args)); - // format is: `group action` + // format is: `group action` or `top sub sub2` if (strpos($name, ' ') > 0) { - $cmdId = str_replace(' ', $this->delimiter, $name); + $names = Str::splitTrimmed($name, ' '); + $cmdId = array_shift($names); + + // prepend elements to the beginning of $args + array_unshift($args, ...$names); } // match handler by input name @@ -439,4 +281,167 @@ protected function createController(array $info): Controller $this->groupObjects[$group] = $handler; return $handler; } + + /**************************************************************************** + * register console controller/command + ****************************************************************************/ + + /** + * @param string $name + * @param ControllerInterface|string|null $class + * @param array $config + * + * @return $this + */ + public function controller(string $name, ControllerInterface|string $class = null, array $config = []): static + { + $this->logf(Console::VERB_CRAZY, 'register group controller: %s', $name); + $this->router->addGroup($name, $class, $config); + + return $this; + } + + /** + * Add group/controller + * + * @param string|class-string $name + * @param string|ControllerInterface|null $class The controller class + * @param array $config + * + * @return static + * @see controller() + */ + public function addGroup(string $name, ControllerInterface|string $class = null, array $config = []): static + { + return $this->controller($name, $class, $config); + } + + /** + * @param string $name + * @param string|ControllerInterface|null $class The controller class + * @param array $config + * + * @return $this + * @see controller() + */ + public function addController(string $name, ControllerInterface|string $class = null, array $config = []): static + { + return $this->controller($name, $class, $config); + } + + /** + * @param array $controllers + */ + public function controllers(array $controllers): void + { + $this->addControllers($controllers); + } + + /** + * @param array $controllers + */ + public function addControllers(array $controllers): void + { + $this->router->addControllers($controllers); + } + + /** + * @param string $name + * @param class-string|CommandInterface|null|Closure(FlagsParser, Output):void $handler + * @param array{desc: string, aliases: array, options: array, arguments: array} $config config the command. + * + * @return Application + */ + public function command(string $name, string|Closure|CommandInterface $handler = null, array $config = []): static + { + $this->logf(Console::VERB_CRAZY, 'register alone command: %s', $name); + $this->router->addCommand($name, $handler, $config); + + return $this; + } + + /** + * add command + * + * @param string $name + * @param class-string|CommandInterface|null|Closure(FlagsParser, Output):void $handler + * @param array{desc: string, aliases: array, options: array, arguments: array} $config config the command. + * + * @return Application + * @see command() + */ + public function addCommand(string $name, string|Closure|CommandInterface $handler = null, array $config = []): static + { + return $this->command($name, $handler, $config); + } + + /** + * @param array{string, mixed} $commands + */ + public function addCommands(array $commands): void + { + $this->router->addCommands($commands); + } + + /** + * @param array{string, mixed} $commands + */ + public function commands(array $commands): void + { + $this->addCommands($commands); + } + + /** + * auto register commands from a dir. + * + * ```php + * $app->registerCommands('SwagPhp\Command', dirname(__DIR__) . '/src/Command'); + * ``` + * + * @param string $namespace + * @param string $basePath + * + * @return $this + */ + public function registerCommands(string $namespace, string $basePath): static + { + $this->debugf('register commands from the namespace: %s', $namespace); + + $length = strlen($basePath) + 1; + // $iterator = Helper::directoryIterator($basePath, $this->getFileFilter()); + $iter = Dir::getIterator($basePath, Dir::getPhpFileFilter()); + + foreach ($iter as $file) { + $subPath = substr($file->getPathName(), $length, -4); + $fullClass = $namespace . '\\' . str_replace('/', '\\', $subPath); + $this->addCommand($fullClass); + } + + return $this; + } + + /** + * auto register controllers from a dir. + * + * @param string $namespace + * @param string $basePath + * + * @return $this + */ + public function registerGroups(string $namespace, string $basePath): self + { + $this->debugf('register groups from the namespace: %s', $namespace); + + $length = strlen($basePath) + 1; + // $iterator = Helper::directoryIterator($basePath, $this->getFileFilter()); + $iter = Dir::getIterator($basePath, Dir::getPhpFileFilter()); + + foreach ($iter as $file) { + $subPath = substr($file->getPathName(), $length, -4); + $fullClass = $namespace . '\\' . str_replace('/', '\\', $subPath); + $this->addController($fullClass); + } + + return $this; + } } diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index 75f78996..1e7d3d2d 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -135,7 +135,6 @@ public function __construct(Input $input = null, Output $output = null) // init an flags object $this->flags = new SFlags(); - $this->init(); } @@ -296,6 +295,7 @@ protected function afterInitFlagsParser(FlagsParser $fs): void * @param array $args * * @return mixed + * @throws Throwable */ public function run(array $args): mixed { @@ -321,7 +321,7 @@ public function run(array $args): mixed if ($this->isDetached()) { ErrorHandler::new()->handle($e); } else { - throw new RuntimeException('Run error - ' . $e->getMessage(), $e->getCode(), $e); + throw $e; } } From fdcf1c678a2e04d2ac2bcc3e60601401fe6084bb Mon Sep 17 00:00:00 2001 From: Inhere Date: Tue, 10 May 2022 13:24:30 +0800 Subject: [PATCH 230/258] fix: fix some error for run sub cmd --- src/Decorate/CommandHelpTrait.php | 12 ++++++------ src/Decorate/SubCommandsWareTrait.php | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Decorate/CommandHelpTrait.php b/src/Decorate/CommandHelpTrait.php index bb5ebad1..db90e1b7 100644 --- a/src/Decorate/CommandHelpTrait.php +++ b/src/Decorate/CommandHelpTrait.php @@ -136,12 +136,11 @@ public function showHelpByFlagsParser(FlagsParser $fs, array $aliases = [], stri $binName = $this->input->getBinName(); $cmdPath = $binName . ' ' . $this->getPath(); - // if ($action) { - // $group = $this->getGroupName(); - // $cmdPath = "$binName $group $action"; - // } - if ($this->hasSubs()) { + if ($action) { + // $group = $this->getGroupName(); + $cmdPath .= " $action"; + } elseif ($this->hasSubs()) { $cmdPath .= ' SUBCOMMAND'; } @@ -153,7 +152,8 @@ public function showHelpByFlagsParser(FlagsParser $fs, array $aliases = [], stri $help['Options:'] = FlagUtil::alignOptions($fs->getOptsHelpLines()); $help['Argument:'] = $fs->getArgsHelpLines(); - if ($subCmds = $this->getSubsForHelp()) { + // fix: action should not have sub-commands + if (!$action && ($subCmds = $this->getSubsForHelp())) { // sort commands ksort($subCmds); $help['Commands:'] = $subCmds; diff --git a/src/Decorate/SubCommandsWareTrait.php b/src/Decorate/SubCommandsWareTrait.php index 2bf1493f..8b8b39a5 100644 --- a/src/Decorate/SubCommandsWareTrait.php +++ b/src/Decorate/SubCommandsWareTrait.php @@ -265,9 +265,9 @@ public function getRoot(): static } /** - * @return static|null + * @return AbstractHandler|null */ - public function getParent(): ?static + public function getParent(): ?AbstractHandler { return $this->parent; } From b57d62f62f039e083bf5c6312efceb9a630d1161 Mon Sep 17 00:00:00 2001 From: Inhere Date: Wed, 11 May 2022 21:52:38 +0800 Subject: [PATCH 231/258] update: update some logic for show simple list --- src/Decorate/SubCommandsWareTrait.php | 14 ++++++++++ src/Util/FormatUtil.php | 40 +++++++++++++++++---------- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/Decorate/SubCommandsWareTrait.php b/src/Decorate/SubCommandsWareTrait.php index 8b8b39a5..ad961a35 100644 --- a/src/Decorate/SubCommandsWareTrait.php +++ b/src/Decorate/SubCommandsWareTrait.php @@ -16,7 +16,9 @@ use Inhere\Console\Handler\CommandWrapper; use Inhere\Console\Util\Helper; use InvalidArgumentException; +use RuntimeException; use Toolkit\Cli\Color\ColorTag; +use Toolkit\PFlag\FlagsParser; use Toolkit\Stdlib\Helper\Assert; use Toolkit\Stdlib\Obj\Traits\NameAliasTrait; use function array_keys; @@ -272,6 +274,18 @@ public function getParent(): ?AbstractHandler return $this->parent; } + /** + * @return FlagsParser + */ + public function getParentFlags(): FlagsParser + { + if (!$this->parent) { + throw new RuntimeException('no parent command of the: ' . $this->getCommandName()); + } + + return $this->parent->getFlags(); + } + /********************************************************** * helper methods **********************************************************/ diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index 2e9ed2c8..e91b6f6e 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -17,6 +17,7 @@ use Toolkit\Sys\Sys; use function array_merge; use function array_shift; +use function count; use function explode; use function implode; use function is_array; @@ -24,7 +25,6 @@ use function is_int; use function is_numeric; use function is_scalar; -use function rtrim; use function str_repeat; use function strpos; use function trim; @@ -204,20 +204,9 @@ public static function spliceKeyValue(array $data, array $opts = []): string $lines = []; - // if value is array, translate array to string + // if value is array, convert array to string if (is_array($value)) { - $temp = '['; - foreach ($value as $k => $val) { - if (is_bool($val)) { - $val = $val ? '(True)' : '(False)'; - } else { - $val = is_scalar($val) ? (string)$val : JsonHelper::unescaped($val); - } - - $temp .= (!is_numeric($k) ? "$k: " : '') . "$val, "; - } - - $value = rtrim($temp, ' ,') . ']'; + $value = self::arr2str($value); // } elseif (is_object($value)) { // $value = get_class($value); } elseif (is_bool($value)) { @@ -242,7 +231,7 @@ public static function spliceKeyValue(array $data, array $opts = []): string // value has multi line if ($lines) { - $linePrefix = $opts['leftChar'] . Str::repeat(' ', $keyWidth + 1) . $opts['sepChar']; + $linePrefix = $opts['leftChar'] . Str::repeat(' ', $keyWidth) . $opts['sepChar']; foreach ($lines as $line) { $fmtLines[] = $linePrefix . $line; } @@ -255,4 +244,25 @@ public static function spliceKeyValue(array $data, array $opts = []): string return implode("\n", $fmtLines); } + + public static function arr2str(array $arr): string + { + if (count($arr) === 0) { + return '[]'; + } + + $temp = "[\n"; + foreach ($arr as $k => $val) { + if (is_bool($val)) { + $val = $val ? '(True)' : '(False)'; + } else { + $val = is_scalar($val) ? (string)$val : JsonHelper::unescaped($val); + } + + $temp .= (!is_numeric($k) ? " $k: " : '') . "$val,\n"; + } + + $temp .= " ]"; + return $temp; + } } From 59d0b61443a2ae2a7bfca37513a7509f16565f54 Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 12 May 2022 11:36:28 +0800 Subject: [PATCH 232/258] up: update indent space for list data format --- src/Util/FormatUtil.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index e91b6f6e..a16f34b1 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -259,7 +259,7 @@ public static function arr2str(array $arr): string $val = is_scalar($val) ? (string)$val : JsonHelper::unescaped($val); } - $temp .= (!is_numeric($k) ? " $k: " : '') . "$val,\n"; + $temp .= (!is_numeric($k) ? " $k: " : ' ') . "$val,\n"; } $temp .= " ]"; From d5fec40398ffe9a5c9cf601304a28cab329be772 Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 12 May 2022 12:55:39 +0800 Subject: [PATCH 233/258] up: dont split show group and alone commands --- src/Decorate/ApplicationHelpTrait.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Decorate/ApplicationHelpTrait.php b/src/Decorate/ApplicationHelpTrait.php index ed7482d4..f1e7adf6 100644 --- a/src/Decorate/ApplicationHelpTrait.php +++ b/src/Decorate/ApplicationHelpTrait.php @@ -9,10 +9,10 @@ namespace Inhere\Console\Decorate; -use Inhere\Console\Handler\AbstractHandler; use Inhere\Console\Console; use Inhere\Console\ConsoleEvent; use Inhere\Console\Contract\CommandInterface; +use Inhere\Console\Handler\AbstractHandler; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; use Inhere\Console\Util\Show; @@ -208,10 +208,10 @@ public function showCommandList(): void } // add split title on both exists. - if (!$autoComp && $hasCommand && $hasGroup) { - $groupArr[] = PHP_EOL . '- Group Commands'; - $commandArr[] = PHP_EOL . '- Alone Commands'; - } + // if (!$autoComp && $hasCommand && $hasGroup) { + // $groupArr[] = PHP_EOL . '- Group Commands'; + // $commandArr[] = PHP_EOL . '- Alone Commands'; + // } $placeholder = 'No description of the command'; foreach ($groups as $name => $info) { From 5e3d2d3de2bcb37eb210c2755c117ed07cfc4904 Mon Sep 17 00:00:00 2001 From: Inhere Date: Tue, 17 May 2022 20:08:15 +0800 Subject: [PATCH 234/258] up: update some for format and not found handle --- src/AbstractApplication.php | 25 +++++++++++++++++++++++++ src/Controller.php | 22 +++++++++++----------- src/Util/FormatUtil.php | 6 ++++++ 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index f311df9f..95d5b908 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -80,6 +80,11 @@ abstract class AbstractApplication implements ApplicationInterface 'endMemory' => 0, ]; + /** + * @var int + */ + private int $exitCode = 0; + /** * @var string */ @@ -341,6 +346,10 @@ protected function afterRun(): void #[NoReturn] public function stop(int $code = 0): void { + if ($code === 0) { + $code = $this->exitCode; + } + // call 'onAppStop' event, if it is registered. $this->fire(self::ON_STOP_RUN, $this); @@ -880,4 +889,20 @@ public function getCommandName(): string { return $this->commandName; } + + /** + * @return int + */ + public function getExitCode(): int + { + return $this->exitCode; + } + + /** + * @param int $exitCode + */ + public function setExitCode(int $exitCode): void + { + $this->exitCode = $exitCode; + } } diff --git a/src/Controller.php b/src/Controller.php index dc4a7aad..193d9987 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -202,14 +202,14 @@ protected function disabledCommands(): array } /** - * Will call it on action(sub-command) not found on the group. + * Will call it on subcommand not found on the group. * - * @param string $action + * @param string $command * @param array $args * * @return bool if return True, will stop goon render group help. */ - protected function onNotFound(string $action, array $args): bool + protected function onNotFound(string $command, array $args): bool { // TIP: you can add custom logic on sub-command not found. return false; @@ -310,7 +310,7 @@ public function doRun(array $args): mixed } // if command not exists. - return $this->handleNotFound($name, $action, $args); + return $this->handleNotFound($name, $command, $args); } // init flags for subcommand @@ -434,29 +434,29 @@ final public function execute(Input $input, Output $output): mixed /** * @param string $group - * @param string $action + * @param string $command * @param array $args * * @return int */ - protected function handleNotFound(string $group, string $action, array $args): int + protected function handleNotFound(string $group, string $command, array $args): int { // if user custom handle not found logic. - if ($this->onNotFound($action, $args)) { - $this->debugf('user custom handle the "%s" action "%s" not found', $group, $action); + if ($this->onNotFound($command, $args)) { + $this->debugf('group: %s - user custom handle the subcommand "%s" not found', $group, $command); return 0; } - $this->debugf('action "%s" not found on the group controller "%s"', $action, $group); + $this->debugf('group: %s - command "%s" is not found on the group', $group, $command); // if you defined the method '$this->notFoundCallback' , will call it // if (($notFoundCallback = $this->notFoundCallback) && method_exists($this, $notFoundCallback)) { // $result = $this->{$notFoundCallback}($action); // } else { - $this->output->liteError("Sorry, The command '$action' not exist of the group '$group'!"); + $this->output->liteError("Sorry, The command '$command' not exist of the group '$group'!"); // find similar command names - $similar = Helper::findSimilar($action, $this->getAllCommandMethods(null, true)); + $similar = Helper::findSimilar($command, $this->getAllCommandMethods(null, true)); if ($similar) { $this->output->writef("\nMaybe what you mean is:\n %s", implode(', ', $similar)); diff --git a/src/Util/FormatUtil.php b/src/Util/FormatUtil.php index a16f34b1..21b2ca9f 100644 --- a/src/Util/FormatUtil.php +++ b/src/Util/FormatUtil.php @@ -173,6 +173,7 @@ public static function spliceKeyValue(array $data, array $opts = []): string 'keyMaxWidth' => 0, // if not set, will automatic calculation 'ucFirst' => true, // upper first char for value 'endNewline' => true, // with newline on end. + 'filterEmpty' => false, ], $opts); if ($opts['keyMaxWidth'] < 1) { @@ -188,8 +189,13 @@ public static function spliceKeyValue(array $data, array $opts = []): string $keyStyle = trim($opts['keyStyle']); $keyPadPos = (int)$opts['keyPadPos']; + $filter = (bool)$opts['filterEmpty']; $fmtLines = []; foreach ($data as $key => $value) { + if ($filter && empty($value)) { + continue; + } + $hasKey = !is_int($key); $fmtLine = $opts['leftChar']; From 202cef46c8198e244e94fd87b1af50cc61020f61 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 29 May 2022 13:36:04 +0800 Subject: [PATCH 235/258] fix: title formatter var type maybe not fixed --- src/Component/Formatter/Title.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Component/Formatter/Title.php b/src/Component/Formatter/Title.php index 084fedc9..c4d600aa 100644 --- a/src/Component/Formatter/Title.php +++ b/src/Component/Formatter/Title.php @@ -43,7 +43,7 @@ public static function show(string $title, array $opts = []): void // list($sW, $sH) = Helper::getScreenSize(); $width = (int)$opts['width']; $char = trim($opts['char']); - $indent = (int)$opts['indent'] >= 0 ? $opts['indent'] : 0; + $indent = (int)$opts['indent'] >= 0 ? (int)$opts['indent'] : 0; $indentStr = ''; if ($indent > 0) { From 54102f0cbb3d379aee07d70d02b26fc182d368f2 Mon Sep 17 00:00:00 2001 From: lihq1403 Date: Thu, 14 Jul 2022 17:44:12 +0800 Subject: [PATCH 236/258] feat: support micro sapi mode --- src/AbstractApplication.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index 95d5b908..afe6c41d 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -431,7 +431,7 @@ public function copy(): self protected function runtimeCheck(): void { // check env - if (!in_array(PHP_SAPI, ['cli', 'phpdbg', 'cli-server'], true)) { + if (!in_array(PHP_SAPI, ['cli', 'phpdbg', 'cli-server', 'micro'], true)) { header('HTTP/1.1 403 Forbidden'); exit(" 403 Forbidden \n\n" . " current environment is CLI. \n" . " :( Sorry! Run this script is only allowed in the terminal environment!\n,You are not allowed to access this file.\n"); } From 2c2fc3306c6b2af18b45a0f4dd986a72350cd9ce Mon Sep 17 00:00:00 2001 From: nelsonsun Date: Sun, 7 Aug 2022 15:06:21 +0800 Subject: [PATCH 237/258] fix: type error on issues #29 --- src/Component/Progress/SimpleBar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Component/Progress/SimpleBar.php b/src/Component/Progress/SimpleBar.php index edea7d69..1ce3b428 100644 --- a/src/Component/Progress/SimpleBar.php +++ b/src/Component/Progress/SimpleBar.php @@ -76,7 +76,7 @@ public static function gen(int $total, array $opts = []): Generator } $current += $step; - $percent = ceil(($current / $total) * 100); + $percent = (int)ceil(($current / $total) * 100); if ($percent >= 100) { $msg = $doneMsg ?: $msg; From a272ae3eceaf8d1084041138ba2f56193c23e286 Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 3 Nov 2022 00:23:02 +0800 Subject: [PATCH 238/258] up: support fire event on sub-cmd and set app to sub-cmd --- src/Application.php | 8 +++++ src/Command.php | 10 +++++- src/Decorate/AttachApplicationTrait.php | 37 ++++---------------- src/Decorate/SubCommandsWareTrait.php | 5 +-- src/Handler/AbstractHandler.php | 46 +++++++++++++++++++++---- 5 files changed, 66 insertions(+), 40 deletions(-) diff --git a/src/Application.php b/src/Application.php index 80ddd6bf..b8c26854 100644 --- a/src/Application.php +++ b/src/Application.php @@ -29,6 +29,7 @@ use function array_unshift; use function class_exists; use function implode; +use function is_dir; use function is_object; use function is_string; use function str_replace; @@ -405,6 +406,10 @@ public function commands(array $commands): void */ public function registerCommands(string $namespace, string $basePath): static { + if (!is_dir($basePath)) { + return $this; + } + $this->debugf('register commands from the namespace: %s', $namespace); $length = strlen($basePath) + 1; @@ -430,6 +435,9 @@ public function registerCommands(string $namespace, string $basePath): static */ public function registerGroups(string $namespace, string $basePath): self { + if (!is_dir($basePath)) { + return $this; + } $this->debugf('register groups from the namespace: %s', $namespace); $length = strlen($basePath) + 1; diff --git a/src/Command.php b/src/Command.php index 192677ae..f13bcf87 100644 --- a/src/Command.php +++ b/src/Command.php @@ -40,6 +40,14 @@ abstract class Command extends AbstractHandler implements CommandInterface protected ?Controller $group = null; /** + * command argument rules + * + * eg: + * + * [ + * 'arg1' => 'type;desc', + * ] + * * @return array */ protected function getArguments(): array @@ -63,7 +71,7 @@ protected function beforeInitFlagsParser(FlagsParser $fs): void */ protected function afterInitFlagsParser(FlagsParser $fs): void { - $this->debugf('cmd: %s - load command flags configure', $this->getRealCName()); + $this->debugf('cmd: %s - load command flags configure, class: %s', $this->getRealCName(), static::class); $this->configure(); $this->configFlags($fs); diff --git a/src/Decorate/AttachApplicationTrait.php b/src/Decorate/AttachApplicationTrait.php index 83b76362..a28befc4 100644 --- a/src/Decorate/AttachApplicationTrait.php +++ b/src/Decorate/AttachApplicationTrait.php @@ -45,14 +45,16 @@ public function getApp(): Application } /** - * @param Application $app + * @param Application|null $app */ - public function setApp(Application $app): void + public function setApp(Application|null $app): void { - $this->app = $app; + if ($app !== null) { + $this->app = $app; - // auto setting $attached - $this->attached = true; + // auto setting $attached + $this->attached = true; + } } /** @@ -168,29 +170,4 @@ public function log(int $level, string $message, array $extra = []): void Console::log($level, $message, $extra); } - - /************************************************************************** - * wrap trigger events - **************************************************************************/ - - /** - * @param string $event - * @param mixed ...$args - * - * @return bool - */ - public function fire(string $event, ...$args): bool - { - $this->debugf("fire event: $event"); - - // if has application instance - if ($this->attached) { - $stop = $this->app->fire($event, ...$args); - if ($stop === false) { - return false; - } - } - - return $this->parentFire($event, ...$args); - } } diff --git a/src/Decorate/SubCommandsWareTrait.php b/src/Decorate/SubCommandsWareTrait.php index ad961a35..e2ce58e6 100644 --- a/src/Decorate/SubCommandsWareTrait.php +++ b/src/Decorate/SubCommandsWareTrait.php @@ -117,11 +117,12 @@ protected function subCommands(): array protected function dispatchSub(string $name, array $args): mixed { $subInfo = $this->commands[$name]; - $this->debugf('cmd: %s - dispatch the attached subcommand: %s', $this->getRealName(), $name); + $this->debugf('cmd: %s - dispatch the subcommand: %s', $this->getRealName(), $name); // create and init sub-command $subCmd = $this->createSubCommand($subInfo); $subCmd->setParent($this); + $subCmd->setApp($this->getApp()); $subCmd->setPath($this->path); $subCmd->setInputOutput($this->input, $this->output); @@ -167,7 +168,7 @@ public function addSub(string $name, string|CommandInterface $handler = null, ar $name = $handler::getName(); } - Assert::isFalse(!$name || !$handler, "Command 'name' and 'handler' cannot be empty! name: $name"); + Assert::isFalse(!$name || !$handler, "Command 'name' and 'handler' cannot be empty! name: $name, handler: $handler"); Assert::isFalse(isset($this->commands[$name]), "Command '$name' have been registered!"); $this->validateName($name); diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index 1e7d3d2d..af865c0a 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -130,11 +130,12 @@ public static function aliases(): array */ public function __construct(Input $input = null, Output $output = null) { - $this->input = $input; - $this->output = $output; + // init io stream + $input && $this->setInput($input); + $output && $this->setOutput($output); // init an flags object - $this->flags = new SFlags(); + $this->setFlags(new SFlags()); $this->init(); } @@ -318,6 +319,8 @@ public function run(array $args): mixed return $this->doRun($args); } catch (Throwable $e) { + $this->log(Console::VERB_DEBUG, "cmd: $name - run error: " . $e->getMessage(), ['args' => $args]); + if ($this->isDetached()) { ErrorHandler::new()->handle($e); } else { @@ -348,7 +351,7 @@ protected function doRun(array $args): mixed } // only fire for alone command run. - if ($this->isAlone()) { + if ($this->isAloneCmd()) { $this->fire(ConsoleEvent::COMMAND_RUN_BEFORE, $this); } @@ -375,6 +378,8 @@ protected function doExecute(): mixed */ public function coExecute(): int { + /** @noinspection PhpFullyQualifiedNameUsageInspection */ + /** @noinspection PhpUndefinedNamespaceInspection */ $cid = \Swoole\Coroutine\run(function (): void { $this->execute($this->input, $this->output); }); @@ -432,6 +437,7 @@ protected function prepare(): bool if (function_exists('cli_set_process_title')) { cli_set_process_title($this->processTitle); } elseif (function_exists('setproctitle')) { + /** @noinspection PhpUndefinedFunctionInspection */ setproctitle($this->processTitle); } @@ -444,6 +450,32 @@ protected function prepare(): bool return true; } + /************************************************************************** + * wrap trigger events + **************************************************************************/ + + /** + * @param string $event + * @param mixed ...$args + * + * @return bool + */ + public function fire(string $event, ...$args): bool + { + $this->debugf("fire event: $event"); + + // if has application instance + if ($this->attached) { + $stop = $this->app->fire($event, ...$args); + if ($stop === false) { + return false; + } + } + // TODO pop fire event to parent and app + + return $this->parentFire($event, ...$args); + } + /************************************************************************** * helper methods **************************************************************************/ @@ -496,7 +528,7 @@ public function loadRulesByDocblock(string $method, FlagsParser $fs): void $rftMth = PhpHelper::reflectMethod($this, $method); // parse doc for get flag rules - $dr = DocblockRules::newByDocblock($rftMth->getDocComment()); + $dr = DocblockRules::newByDocblock((string)$rftMth->getDocComment()); $dr->parse(); $fs->addArgsByRules($dr->getArgRules()); @@ -582,11 +614,11 @@ public function getRealDesc(): string /** * @param bool $useReal * - * @return string + * @return string top:sub */ public function getCommandId(bool $useReal = true): string { - return $useReal ? self::getName() : $this->commandName; + return $this->getPath(':'); } /** From 04e5fdbebcc65889d15e32e1993e52fa477b67b9 Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 3 Nov 2022 10:39:17 +0800 Subject: [PATCH 239/258] fix: unit test error on subcommands --- src/Component/Router.php | 9 ++++++--- src/Decorate/SubCommandsWareTrait.php | 10 +++++++--- src/Handler/AbstractHandler.php | 8 ++------ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Component/Router.php b/src/Component/Router.php index 38d9966a..911e6e8e 100644 --- a/src/Component/Router.php +++ b/src/Component/Router.php @@ -26,12 +26,12 @@ use function array_merge; use function class_exists; use function explode; +use function get_class; use function in_array; use function is_int; use function is_object; use function is_string; use function is_subclass_of; -use function method_exists; use function preg_match; use function strpos; use function trim; @@ -187,9 +187,12 @@ public function addCommand(string $name, string|Closure|CommandInterface $handle $name = $name::getName(); } - Assert::isFalse(!$name || !$handler, "Command 'name' and 'handler' cannot be empty! name: $name"); - Assert::isFalse(isset($this->commands[$name]), "Command '$name' have been registered!"); + if (!$name || !$handler) { + $handlerClass = is_object($handler) ? get_class($handler) : $handler; + throw new InvalidArgumentException("Command 'name' and 'handler' cannot be empty! name: $name, handler: $handlerClass"); + } + Assert::isFalse(isset($this->commands[$name]), "Command '$name' have been registered!"); $this->validateName($name); $config['aliases'] = isset($config['aliases']) ? (array)$config['aliases'] : []; diff --git a/src/Decorate/SubCommandsWareTrait.php b/src/Decorate/SubCommandsWareTrait.php index e2ce58e6..278268a4 100644 --- a/src/Decorate/SubCommandsWareTrait.php +++ b/src/Decorate/SubCommandsWareTrait.php @@ -26,6 +26,7 @@ use function class_exists; use function count; use function explode; +use function get_class; use function implode; use function in_array; use function is_int; @@ -122,7 +123,7 @@ protected function dispatchSub(string $name, array $args): mixed // create and init sub-command $subCmd = $this->createSubCommand($subInfo); $subCmd->setParent($this); - $subCmd->setApp($this->getApp()); + $subCmd->setApp($this->app); $subCmd->setPath($this->path); $subCmd->setInputOutput($this->input, $this->output); @@ -168,9 +169,12 @@ public function addSub(string $name, string|CommandInterface $handler = null, ar $name = $handler::getName(); } - Assert::isFalse(!$name || !$handler, "Command 'name' and 'handler' cannot be empty! name: $name, handler: $handler"); - Assert::isFalse(isset($this->commands[$name]), "Command '$name' have been registered!"); + if (!$name || !$handler) { + $handlerClass = is_object($handler) ? get_class($handler) : $handler; + throw new InvalidArgumentException("Command 'name' and 'handler' cannot be empty! name: $name, handler: $handlerClass"); + } + Assert::isFalse(isset($this->commands[$name]), "Command '$name' have been registered!"); $this->validateName($name); $config['aliases'] = isset($config['aliases']) ? (array)$config['aliases'] : []; diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index af865c0a..f06af81e 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -43,13 +43,9 @@ */ abstract class AbstractHandler implements CommandHandlerInterface { - use AttachApplicationTrait; + use AttachApplicationTrait, CommandHelpTrait; - use CommandHelpTrait; - - use InputOutputAwareTrait; - - use UserInteractAwareTrait; + use InputOutputAwareTrait, UserInteractAwareTrait; use SubCommandsWareTrait; From 6c444034809ab80cd530fab556dcec17d9efeead Mon Sep 17 00:00:00 2001 From: Inhere Date: Wed, 7 Dec 2022 14:49:42 +0800 Subject: [PATCH 240/258] up: update table and json format, add new interact tool: value collector --- src/Component/Formatter/JSONPretty.php | 146 +++- src/Component/Formatter/Table.php | 38 +- .../ParamDefinition/AbstractParam.php | 50 ++ .../Interact/ParamDefinition/ChoiceParam.php | 14 + .../Interact/ParamDefinition/StringParam.php | 14 + src/Component/Interact/ValuesCollector.php | 74 ++ src/Component/Symbol/GitEmoji.php | 705 +++++++++++++++++- 7 files changed, 1017 insertions(+), 24 deletions(-) create mode 100644 src/Component/Interact/ParamDefinition/AbstractParam.php create mode 100644 src/Component/Interact/ParamDefinition/ChoiceParam.php create mode 100644 src/Component/Interact/ParamDefinition/StringParam.php create mode 100644 src/Component/Interact/ValuesCollector.php diff --git a/src/Component/Formatter/JSONPretty.php b/src/Component/Formatter/JSONPretty.php index 460f438b..5c12c003 100644 --- a/src/Component/Formatter/JSONPretty.php +++ b/src/Component/Formatter/JSONPretty.php @@ -9,7 +9,6 @@ namespace Inhere\Console\Component\Formatter; -use JsonException; use Toolkit\Cli\Color\ColorTag; use Toolkit\Stdlib\Helper\JsonHelper; use Toolkit\Stdlib\Obj\AbstractObj; @@ -17,11 +16,11 @@ use function array_merge; use function explode; use function is_numeric; -use function json_decode; use function preg_replace_callback; use function rtrim; use function str_contains; use function str_ends_with; +use function str_replace; use function trim; /** @@ -34,6 +33,7 @@ class JSONPretty extends AbstractObj 'strVal' => 'info', 'intVal' => 'cyan', 'boolVal' => 'red', + 'matched' => 'red1', ]; public const THEME_ONE = [ @@ -41,6 +41,7 @@ class JSONPretty extends AbstractObj 'strVal' => 'cyan', 'intVal' => 'red', 'boolVal' => 'green', + 'matched' => 'yellow', ]; // json.cn @@ -49,10 +50,11 @@ class JSONPretty extends AbstractObj 'strVal' => 'info', 'intVal' => 'hiBlue', 'boolVal' => 'red', + 'matched' => 'yellow', ]; /** - * @var array{keyName: string, strVal: string, intVal: string, boolVal: string} + * @var array = DEFAULT_THEME */ protected array $theme = self::DEFAULT_THEME; @@ -61,6 +63,14 @@ class JSONPretty extends AbstractObj */ public int $maxDepth = 10; + public bool $noColor = false; + + public array $includes = []; + + public array $excludes = []; + + public array $matches = []; + /** * @param string $json * @@ -110,13 +120,43 @@ public function render(string $json): string */ public function renderData(mixed $data): string { - $buf = StrBuffer::new(); $json = JsonHelper::prettyJSON($data); + if ($this->noColor && !$this->includes && !$this->excludes) { + return $json; + } + + $buf = StrBuffer::new(); + foreach (explode("\n", $json) as $line) { $trimmed = trim($line); // start or end chars. eg: {} [] if (!str_contains($trimmed, ': ')) { + if ($this->noColor) { + $buf->writeln($line); + } else { + $buf->writeln(ColorTag::wrap($line, $this->theme['strVal'])); + } + continue; + } + + if ($this->includes && !$this->includeFilter($trimmed)) { + continue; + } + + if ($this->excludes && !$this->excludeFilter($trimmed)) { + continue; + } + + if ($this->noColor) { + $buf->writeln($line); + continue; + } + + if ($ms = $this->matchKeywords($line)) { + foreach ($ms as $m) { + $line = str_replace($m, ColorTag::wrap($m, $this->theme['matched']), $line); + } $buf->writeln($line); continue; } @@ -150,6 +190,60 @@ public function renderData(mixed $data): string return $buf->getAndClear(); } + /** + * @param string $line + * + * @return bool return false to exclude + */ + protected function includeFilter(string $line): bool + { + if (!$this->includes) { + return true; + } + + foreach ($this->includes as $kw) { + if (str_contains($line, $kw)) { + return true; + } + } + return false; + } + + /** + * @param string $line + * + * @return bool return false to exclude + */ + protected function excludeFilter(string $line): bool + { + if (!$this->excludes) { + return true; + } + + foreach ($this->excludes as $kw) { + if (str_contains($line, $kw)) { + return false; + } + } + return true; + } + + /** + * @param string $line + * + * @return array + */ + protected function matchKeywords(string $line): array + { + $matched = []; + foreach ($this->matches as $kw) { + if (str_contains($line, $kw)) { + $matched[] = $kw; + } + } + return $matched; + } + /** * @param array $theme */ @@ -157,5 +251,49 @@ public function setTheme(array $theme): void { $this->theme = array_merge($this->theme, $theme); } + + /** + * @param array|string $includes + * + * @return JSONPretty + */ + public function setIncludes(array|string $includes): self + { + $this->includes = (array)$includes; + return $this; + } + + /** + * @param array|string $excludes + * + * @return JSONPretty + */ + public function setExcludes(array|string $excludes): self + { + $this->excludes = (array)$excludes; + return $this; + } + + /** + * @param bool $noColor + * + * @return JSONPretty + */ + public function setNoColor(bool $noColor): self + { + $this->noColor = $noColor; + return $this; + } + + /** + * @param array $matches + * + * @return JSONPretty + */ + public function setMatches(array $matches): self + { + $this->matches = $matches; + return $this; + } } diff --git a/src/Component/Formatter/Table.php b/src/Component/Formatter/Table.php index 70df1b33..620f164a 100644 --- a/src/Component/Formatter/Table.php +++ b/src/Component/Formatter/Table.php @@ -12,6 +12,7 @@ use Inhere\Console\Component\MessageFormatter; use Inhere\Console\Console; use Toolkit\Cli\Color\ColorTag; +use Toolkit\Stdlib\Helper\DataHelper; use Toolkit\Stdlib\Str; use Toolkit\Stdlib\Str\StrBuffer; use function array_keys; @@ -112,18 +113,18 @@ public static function show(array $data, string $title = 'Data Table', array $op $colBorderChar = $opts['colBorderChar']; $info = [ - 'rowCount' => count($data), + // 'rowCount' => count($data), 'columnCount' => 0, // how many column in the table. 'columnMaxWidth' => [], // table column max width - 'tableWidth' => 0, // table width. equals to all max column width's sum. + // 'tableWidth' => 0, // table width. equals to all max column width's sum. ]; // parse table data - foreach ($data as $row) { + foreach ($data as &$row) { // collection all field name if ($rowIndex === 0) { $head = $tableHead ?: array_keys($row); - // + // column count $info['columnCount'] = count($row); foreach ($head as $index => $name) { @@ -131,44 +132,47 @@ public static function show(array $data, string $title = 'Data Table', array $op $hasHead = true; } - $info['columnMaxWidth'][$index] = Str::utf8Len($name, 'UTF-8'); + $info['columnMaxWidth'][$index] = Str::utf8Len($name); } } $colIndex = 0; + $rowData = (array)$row; + + foreach ($rowData as &$value) { + // always convert to string + $value = DataHelper::toString($value); - foreach ((array)$row as $value) { // collection column max width if (isset($info['columnMaxWidth'][$colIndex])) { - if (is_bool($value)) { - $colWidth = $value ? 4 : 5; - } else { - $colWidth = Str::utf8Len($value, 'UTF-8'); - } + $colWidth = Str::utf8Len($value); // If current column width gt old column width. override old width. if ($colWidth > $info['columnMaxWidth'][$colIndex]) { $info['columnMaxWidth'][$colIndex] = $colWidth; } } else { - $info['columnMaxWidth'][$colIndex] = Str::utf8Len($value, 'UTF-8'); + $info['columnMaxWidth'][$colIndex] = Str::utf8Len($value); } $colIndex++; } + unset($value); $rowIndex++; + $row = $rowData; } + unset($row); - $tableWidth = $info['tableWidth'] = array_sum($info['columnMaxWidth']); $columnCount = $info['columnCount']; + $tableWidth = (int)array_sum($info['columnMaxWidth']); // output title if ($title) { $tStyle = $opts['titleStyle'] ?: 'bold'; $title = Str::ucwords(trim($title)); - $titleLength = Str::utf8Len($title, 'UTF-8'); - $indentSpace = Str::pad(' ', ceil($tableWidth / 2) - ceil($titleLength / 2) + ($columnCount * 2), ' '); + $titleLength = Str::utf8Len($title); + $indentSpace = Str::pad(' ', ceil($tableWidth / 2) - ceil($titleLength / 2) + ($columnCount * 2)); $buf->write(" $indentSpace<$tStyle>$title\n"); } @@ -190,7 +194,7 @@ public static function show(array $data, string $title = 'Data Table', array $op // format head title // $name = Str::pad($name, $colMaxWidth, ' '); // use Str::padByWidth support zh-CN words - $name = Str::padByWidth($name, $colMaxWidth, ' '); + $name = Str::padByWidth($name, $colMaxWidth); $name = ColorTag::wrap($name, $opts['headStyle']); // join string $headStr .= " $name $colBorderChar"; @@ -225,7 +229,7 @@ public static function show(array $data, string $title = 'Data Table', array $op // $value = Str::pad($value, $colMaxWidth, ' '); // use Str::padByWidth support zh-CN words - $value = Str::padByWidth($value, $colMaxWidth, ' '); + $value = Str::padByWidth($value, $colMaxWidth); $value = ColorTag::wrap($value, $opts['bodyStyle']); $rowStr .= " $value $colBorderChar"; $colIndex++; diff --git a/src/Component/Interact/ParamDefinition/AbstractParam.php b/src/Component/Interact/ParamDefinition/AbstractParam.php new file mode 100644 index 00000000..5432a9fd --- /dev/null +++ b/src/Component/Interact/ParamDefinition/AbstractParam.php @@ -0,0 +1,50 @@ +validator = $validator; + return $this; + } +} diff --git a/src/Component/Interact/ParamDefinition/ChoiceParam.php b/src/Component/Interact/ParamDefinition/ChoiceParam.php new file mode 100644 index 00000000..706640e9 --- /dev/null +++ b/src/Component/Interact/ParamDefinition/ChoiceParam.php @@ -0,0 +1,14 @@ + 'title', // param name + * 'type' => 'StringParam', // String Parameter Definition + * 'desc' => 'description', + * 'default' => null, // default value + * ], + * [ + * 'name' => 'projects', + * 'type' => 'ChoiceParam', // Choice Parameter Definition + * 'desc' => 'description', + * 'default' => null, // default value + * ], + * ] + */ + public array $propDefinitions; + + /** + * @param array $propDefinitions + * + * @return $this + */ + public function new(array $propDefinitions = []): self + { + return new self($propDefinitions); + } + + /** + * Class constructor. + * + * @param array $propDefinitions + */ + public function __construct(array $propDefinitions = []) + { + $this->propDefinitions = $propDefinitions; + } + + /** + * @param FlagsParser $fs + */ + public function collect(FlagsParser $fs): void + { + // for ($fs->getOptDefine($name)) + Assert::notEmpty($this->propDefinitions); + foreach ($this->propDefinitions as $definition) { + + } + } + + /** + * @param array $propDefinitions + * + * @return ValuesCollector + */ + public function setPropDefinitions(array $propDefinitions): self + { + $this->propDefinitions = $propDefinitions; + return $this; + } +} diff --git a/src/Component/Symbol/GitEmoji.php b/src/Component/Symbol/GitEmoji.php index 4dc77015..37a9888d 100644 --- a/src/Component/Symbol/GitEmoji.php +++ b/src/Component/Symbol/GitEmoji.php @@ -2,15 +2,714 @@ namespace Inhere\Console\Component\Symbol; +use Toolkit\Stdlib\Json; +use Toolkit\Stdlib\Obj\AbstractObj; +use function count; +use function str_replace; +use function stripos; + /** * class GitEmoji * * @author inhere */ -class GitEmoji +class GitEmoji extends AbstractObj { - public static function search(string $key): array + /** + * @var string + * @link https://github.com/carloscuesta/gitmoji/blob/master/src/data/gitmojis.json + * @link https://github.com/hooj0/git-emoji-guide/blob/master/git-emoji-list.md see zh-CN translated + */ + public string $emojiJson = << '', + * 'entity' => '', + * 'code' => '', + * 'name' => '', + * 'description' => '', + * ], + * ] + */ + protected array $emojis = []; + + /** + * @param string $keywords + * @param int $limit + * + * @return array + */ + public static function quickSearch(string $keywords, int $limit = 5): array + { + return self::new()->search($keywords, $limit); + } + + + /** + * @param string $str + * + * @return string + */ + public function quickRender(string $str): string + { + return self::new()->render($str); + } + + protected function initLoadMap(): void + { + if (!$this->loaded) { + $this->loaded = true; + $this->emojis = Json::decode($this->emojiJson, true); + } + } + + /** + * @param string $keywords + * @param int $limit + * + * @return array + */ + public function search(string $keywords, int $limit = 5): array + { + if (!$keywords) { + return []; + } + + $this->initLoadMap(); + + $matched = []; + foreach ($this->emojis as $item) { + if (stripos($item['name'], $keywords) !== false || stripos($item['description'], $keywords) !== false) { + unset($item['semver']); + $matched[] = $item; + + if (count($matched) >= $limit) { + break; + } + } + } + + return $matched; + } + + /** + * @param string $str + * + * @return string + */ + public function render(string $str): string + { + $this->initLoadMap(); + + foreach ($this->emojis as $emoji) { + $str = str_replace($emoji['code'], $emoji['emoji'], $str); + } + return $str; + } + + /** + * @return array + */ + public function getEmojis(): array + { + $this->initLoadMap(); + + return $this->emojis; } } From 3674b03aff6211ff34b5b81e753a9ab794c70cea Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 13 Jan 2023 13:29:00 +0800 Subject: [PATCH 241/258] refactor: update the sub command alias logic for controller --- src/Controller.php | 62 ++++----------------------- src/Decorate/ControllerHelpTrait.php | 4 +- src/Decorate/SubCommandsWareTrait.php | 8 ++++ src/Handler/AbstractHandler.php | 7 +-- 4 files changed, 23 insertions(+), 58 deletions(-) diff --git a/src/Controller.php b/src/Controller.php index 193d9987..5df13c80 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -28,7 +28,6 @@ use Toolkit\Stdlib\Obj\ObjectHelper; use Toolkit\Stdlib\Str; use function array_flip; -use function array_keys; use function array_shift; use function implode; use function is_array; @@ -48,19 +47,6 @@ abstract class Controller extends AbstractHandler implements ControllerInterface { use ControllerHelpTrait; - /** - * The sub-command aliases mapping - * - * eg: [ - * alias => command, - * alias1 => command1, - * alias2 => command1, - * ] - * - * @var array - */ - private static array $commandAliases = []; - /** * @var array global options for the group command */ @@ -153,7 +139,9 @@ abstract class Controller extends AbstractHandler implements ControllerInterface /** * Define command alias mapping. please rewrite it on sub-class. * - * @return array + * - key is command name, value is aliases. + * + * @return array{string: list} */ protected static function commandAliases(): array { @@ -177,7 +165,8 @@ protected function init(): void { parent::init(); - self::loadCommandAliases(); + // up: load sub-commands alias + $this->loadCommandAliases(); $list = $this->disabledCommands(); @@ -475,6 +464,7 @@ protected function handleNotFound(string $group, string $command, array $args): protected function newActionFlags(string $action = ''): FlagsParser { $action = $action ?: $this->action; + if (!$fs = $this->getActionFlags($action)) { $fs = new SFlags(['name' => $action]); $fs->setStopOnFistArg(false); @@ -553,21 +543,6 @@ protected function getAllCommandMethods(ReflectionClass $ref = null, bool $onlyN } } - /** - * @param string $alias - * - * @return string - */ - public function resolveAlias(string $alias): string - { - if (!$alias) { - return ''; - } - - $map = $this->getCommandAliases(); - return $map[$alias] ?? $alias; - } - /** * @param string $name * @@ -581,27 +556,22 @@ public function isDisabled(string $name): bool /** * load sub-commands aliases from sub-class::commandAliases() */ - public static function loadCommandAliases(): void + public function loadCommandAliases(): void { $cmdAliases = static::commandAliases(); if (!$cmdAliases) { return; } - $fmtAliases = []; foreach ($cmdAliases as $name => $item) { // $name is command, $item is alias list // eg: ['command1' => ['alias1', 'alias2']] if (is_array($item)) { - foreach ($item as $alias) { - $fmtAliases[$alias] = $name; - } + $this->setAlias($name, $item, true); } elseif (is_string($item)) { // $item is command, $name is alias name - $fmtAliases[$name] = $item; + $this->setAlias($item, $name, true); } } - - self::$commandAliases = $fmtAliases; } /************************************************************************** @@ -616,20 +586,6 @@ public function getDisabledCommands(): array return $this->disabledCommands; } - /** - * @param string $name - * - * @return array - */ - public function getCommandAliases(string $name = ''): array - { - if ($name) { - return self::$commandAliases ? array_keys(self::$commandAliases, $name, true) : []; - } - - return self::$commandAliases; - } - /** * @return string */ diff --git a/src/Decorate/ControllerHelpTrait.php b/src/Decorate/ControllerHelpTrait.php index ad368302..e5f92899 100644 --- a/src/Decorate/ControllerHelpTrait.php +++ b/src/Decorate/ControllerHelpTrait.php @@ -59,7 +59,7 @@ public function helpCommand(): int $action = Str::camelCase($action); $method = $this->actionSuffix ? $action . ucfirst($this->actionSuffix) : $action; - $aliases = $this->getCommandAliases($action); + $aliases = $this->getNameAliases($action); // up: find global aliases from app if ($this->app) { @@ -133,7 +133,7 @@ public function showCommandList(): void $desc .= '(DISABLED)'; } - $aliases = $this->getCommandAliases($cmd); + $aliases = $this->getNameAliases($cmd); $desc .= $aliases ? ColorTag::wrap(' (alias: ' . implode(',', $aliases) . ')', 'info') : ''; $commands[$cmd] = $desc; diff --git a/src/Decorate/SubCommandsWareTrait.php b/src/Decorate/SubCommandsWareTrait.php index 278268a4..a4178449 100644 --- a/src/Decorate/SubCommandsWareTrait.php +++ b/src/Decorate/SubCommandsWareTrait.php @@ -411,6 +411,14 @@ public function getCommands(): array return $this->commands; } + /** + * @return array + */ + public function getSubAliasMap(): array + { + return $this->aliases; + } + /** * @return array */ diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index f06af81e..46a5b85d 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -239,6 +239,7 @@ protected function initForRun(): void } $this->addPathNode($this->commandName); + $this->commentsVars['binWithCmd'] = $this->getPath(); } /** @@ -337,12 +338,12 @@ public function run(array $args): mixed protected function doRun(array $args): mixed { // some prepare check - if (true !== $this->prepare()) { + if (!$this->prepare()) { return -1; } // return False to deny goon run. - if (false === $this->beforeExecute()) { + if (!$this->beforeExecute()) { return -1; } @@ -425,7 +426,7 @@ protected function afterExecute(): void } /** - * prepare run + * prepare run, return false to stop run. */ protected function prepare(): bool { From 57f7fbb7eaf1a1758db7a0f2f9888300ab48e265 Mon Sep 17 00:00:00 2001 From: Inhere Date: Fri, 13 Jan 2023 13:29:42 +0800 Subject: [PATCH 242/258] ci: update the release action script --- .github/changelog.yml | 50 +++++++++++++++++++++-------------- .github/workflows/php.yml | 2 +- .github/workflows/release.yml | 26 ++++-------------- 3 files changed, 36 insertions(+), 42 deletions(-) diff --git a/.github/changelog.yml b/.github/changelog.yml index f5df9693..734c3805 100644 --- a/.github/changelog.yml +++ b/.github/changelog.yml @@ -1,27 +1,37 @@ -options: - title: '## Change Log' - style: gh-release +title: '## Change Log' +# style allow: simple, markdown(mkdown), ghr(gh-release) +style: gh-release +# group names +names: [Refactor, Fixed, Feature, Update, Other] +# if empty will auto fetch by git remote +#repo_url: https://github.com/gookit/gitw + filters: - # message length >= 12 - - name: msgLen - minLen: 12 - # message words >= 3 - - name: wordsLen - minLen: 3 + # message length should >= 12 + - name: msg_len + min_len: 12 + # message words should >= 3 + - name: words_len + min_len: 3 + - name: keyword + keyword: format code + exclude: true - name: keywords - keywords: ['format code'] + keywords: format code, action test exclude: true +# group match rules # not matched will use 'Other' group. -groups: - - name: New - keywords: [add, new] +rules: + - name: Refactor + start_withs: [refactor, break] + contains: ['refactor:'] - name: Fixed - startWiths: [add, new] - keywords: [add, new] - - name: Feat - startWiths: [feat] - keywords: [feature] + start_withs: [fix] + contains: ['fix:'] + - name: Feature + start_withs: [feat, new] + contains: ['feat:'] - name: Update - startWiths: [update, 'up:'] - keywords: [update] + start_withs: [update] + contains: ['update:', 'up:'] diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 408c03e9..30925b8c 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: true matrix: - php: [8.0, 8.1] # 7.3, 7.4, + php: [8.0, 8.1, 8.2] # 7.3, 7.4, # os: [ubuntu-latest] # windows-latest, # include: # - os: 'ubuntu-latest' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eae93734..879c7ad0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,13 +7,9 @@ on: jobs: release: - name: Test on php ${{ matrix.php}} + name: New tag release runs-on: ubuntu-latest timeout-minutes: 10 - strategy: - fail-fast: true - matrix: - php: [8.0] steps: - name: Checkout @@ -25,23 +21,11 @@ jobs: echo "RELEASE_TAG=${GITHUB_REF:10}" >> $GITHUB_ENV echo "RELEASE_NAME=$GITHUB_WORKFLOW" >> $GITHUB_ENV - # usage refer https://github.com/shivammathur/setup-php - - name: Setup PHP - timeout-minutes: 5 - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php}} - tools: php-cs-fixer, phpunit - extensions: mbstring, dom, fileinfo, openssl # , swoole-4.4.19 #optional, setup extensions - ini-values: post_max_size=56M, short_open_tag=On #optional, setup php.ini configuration - coverage: none #optional, setup coverage driver: xdebug, none - - - name: Generate changelog file - id: changelog + - name: Generate changelog run: | - wget -c -q https://github.com/inhere/kite/releases/latest/download/kite.phar - php kite.phar git cl prev last --style gh-release --no-merges --fetch-tags --unshallow --file changelog.md - cat changelog.md + curl https://github.com/gookit/gitw/releases/latest/download/chlog-linux-amd64 -L -o /usr/local/bin/chlog + chmod a+x /usr/local/bin/chlog + chlog -c .github/changelog.yml -o changelog.md prev last # https://github.com/softprops/action-gh-release - name: Create release and upload assets From 2742340c74efcdaf0687db532ff5193b198ef2d2 Mon Sep 17 00:00:00 2001 From: Inhere Date: Sat, 14 Jan 2023 10:58:51 +0800 Subject: [PATCH 243/258] fix: help var binWithCmd not with bin name --- src/Handler/AbstractHandler.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index 46a5b85d..765ad72e 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -209,6 +209,7 @@ protected function annotationVars(): array $binName = $this->input->getScriptName(); $command = $this->input->getCommand(); $fullCmd = $this->input->buildFullCmd($command); + $cmdPath = $binName . ' ' . $command; // e.g: `more info see {name}:index` return [ @@ -222,7 +223,8 @@ protected function annotationVars(): array 'command' => $command, // demo OR home:test 'fullCmd' => $fullCmd, // bin/app demo OR bin/app home:test 'fullCommand' => $fullCmd, - 'binWithCmd' => $binName . ' ' . $command, + 'cmdPath' => $cmdPath, + 'binWithCmd' => $cmdPath, ]; } @@ -239,7 +241,11 @@ protected function initForRun(): void } $this->addPathNode($this->commandName); - $this->commentsVars['binWithCmd'] = $this->getPath(); + + $binName = $this->input->getScriptName(); + $cmdPath = $binName . ' ' . $this->getPath(); + + $this->commentsVars['cmdPath'] = $this->commentsVars['binWithCmd'] = $cmdPath; } /** From d3a93d8d0b418e0cc3a5f951c4b190d332338278 Mon Sep 17 00:00:00 2001 From: Inhere Date: Sat, 14 Jan 2023 13:40:29 +0800 Subject: [PATCH 244/258] fix: route command error on name is one char --- src/ConsoleConst.php | 2 +- src/Handler/AbstractHandler.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ConsoleConst.php b/src/ConsoleConst.php index 13b7908e..10309c6c 100644 --- a/src/ConsoleConst.php +++ b/src/ConsoleConst.php @@ -18,7 +18,7 @@ class ConsoleConst public const CMD_NAME_MAX_LEN = 48; - public const REGEX_CMD_PATH = '/^[a-zA-Z][\w:-]+$/'; + public const REGEX_CMD_PATH = '/^[a-zA-Z][\w:-]*$/'; public const REGEX_CMD_NAME = '/^[a-zA-z][\w-]+$/'; } diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index 765ad72e..4e6ec014 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -369,10 +369,10 @@ protected function doRun(array $args): mixed return $result; } - protected function doExecute(): mixed - { - return ''; - } + // protected function doExecute(): mixed + // { + // return ''; + // } /** * coroutine run by swoole go() From bbb7aed23f721b7d5bf3c34a02bc0d27f6ff9565 Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 18 Jan 2024 10:50:48 +0800 Subject: [PATCH 245/258] up: command name allow use one char, eg: a --- src/Component/Router.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Component/Router.php b/src/Component/Router.php index 911e6e8e..5da9e30f 100644 --- a/src/Component/Router.php +++ b/src/Component/Router.php @@ -358,6 +358,12 @@ public function match(string $name): array * helper methods **********************************************************/ + /** + * command name pattern. + * old: '/^[a-z][\w-]*:?([a-z][\w-]+)?$/' + */ + public const NAME_PATTERN = '/^[a-z][\w:-]*$/'; + /** * @param string $name * @@ -365,16 +371,14 @@ public function match(string $name): array */ protected function validateName(string $name): void { - // '/^[a-z][\w-]*:?([a-z][\w-]+)?$/' - $pattern = '/^[a-z][\w:-]+$/'; - + $pattern = self::NAME_PATTERN; if (1 !== preg_match($pattern, $name)) { throw new InvalidArgumentException("The command name '$name' is must match: $pattern"); } - // cannot be override. like: help, version + // cannot be overridden. like: help, version if ($this->isBlocked($name)) { - throw new InvalidArgumentException("The command name '$name' is not allowed. It is a built in command."); + throw new InvalidArgumentException("Command name '$name' is not allowed. It is a built in command."); } } From db130269f6f510858528702fed85b954198f4643 Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 7 Feb 2024 17:58:31 +0800 Subject: [PATCH 246/258] :memo: chore: update some comments message --- src/Decorate/SubCommandsWareTrait.php | 2 +- src/Handler/CommandWrapper.php | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Decorate/SubCommandsWareTrait.php b/src/Decorate/SubCommandsWareTrait.php index a4178449..dce5f969 100644 --- a/src/Decorate/SubCommandsWareTrait.php +++ b/src/Decorate/SubCommandsWareTrait.php @@ -74,7 +74,7 @@ trait SubCommandsWareTrait * ```php * [ * 'name' => [ - * 'handler' => MyCommand::class, + * 'handler' => MyCommand::class, // class or object * 'config' => [ * 'name' => 'string', * 'desc' => 'string', diff --git a/src/Handler/CommandWrapper.php b/src/Handler/CommandWrapper.php index b23a6072..d5a7d236 100644 --- a/src/Handler/CommandWrapper.php +++ b/src/Handler/CommandWrapper.php @@ -28,7 +28,13 @@ class CommandWrapper extends Command private $callable; /** - * @var array{name: string, desc: string, options: array, arguments: array} + * @var array{name: string, desc: string, options: array, arguments: array} = [ + * 'name' => '', + * 'desc' => '', + * 'aliases' => [], + * 'options' => [], + * 'arguments' => [] + * ] */ protected array $config = []; From f2538724f77aa08b2e67e76b98e32bf4f1f0e825 Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 5 Dec 2024 18:47:43 +0800 Subject: [PATCH 247/258] up: upgrade support php8.4 --- .github/workflows/php.yml | 4 ++-- src/AbstractApplication.php | 8 ++++++-- src/Application.php | 12 ++++++------ src/Component/Router.php | 4 ++-- src/Console.php | 4 ++-- src/Contract/ApplicationInterface.php | 7 +++++-- src/Contract/RouterInterface.php | 4 ++-- src/Controller.php | 2 +- src/Decorate/SubCommandsWareTrait.php | 2 +- src/Decorate/UserInteractAwareTrait.php | 12 ++++++------ src/Handler/AbstractHandler.php | 2 +- src/IO/Input.php | 2 +- 12 files changed, 35 insertions(+), 28 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 30925b8c..44df80fd 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: true matrix: - php: [8.0, 8.1, 8.2] # 7.3, 7.4, + php: [8.1, 8.2, 8.3, 8.4] # 7.3, 7.4, # os: [ubuntu-latest] # windows-latest, # include: # - os: 'ubuntu-latest' @@ -61,4 +61,4 @@ jobs: - name: Test build PHAR run: | php -d phar.readonly=0 examples/app phar pack -o=myapp.phar - php myapp.phar -h \ No newline at end of file + php myapp.phar -h diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index afe6c41d..d467e52a 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -148,7 +148,7 @@ abstract class AbstractApplication implements ApplicationInterface * @param Input|null $input * @param Output|null $output */ - public function __construct(array $config = [], Input $input = null, Output $output = null) + public function __construct(array $config = [], ?Input $input = null, ?Output $output = null) { $this->runtimeCheck(); $this->setConfig($config); @@ -672,7 +672,7 @@ public function getLogoText(): ?string * @param string $logoTxt * @param string|null $style */ - public function setLogo(string $logoTxt, string $style = null): void + public function setLogo(string $logoTxt, ?string $style = null): void { $this->config['logoText'] = $logoTxt; @@ -848,6 +848,10 @@ public function isProfile(): bool $optKey = GlobalOption::PROFILE; $default = (bool)$this->getParam($optKey, false); + if (!$this->flags->hasOpt($optKey)) { + return $default; + } + // return $this->input->getBoolOpt($key, $def); return $this->flags->getOpt($optKey, $default); } diff --git a/src/Application.php b/src/Application.php index b8c26854..6eeb8225 100644 --- a/src/Application.php +++ b/src/Application.php @@ -52,7 +52,7 @@ class Application extends AbstractApplication * @param Input|null $input * @param Output|null $output */ - public function __construct(array $config = [], Input $input = null, Output $output = null) + public function __construct(array $config = [], ?Input $input = null, ?Output $output = null) { Console::setApp($this); @@ -294,7 +294,7 @@ protected function createController(array $info): Controller * * @return $this */ - public function controller(string $name, ControllerInterface|string $class = null, array $config = []): static + public function controller(string $name, ControllerInterface|string|null $class = null, array $config = []): static { $this->logf(Console::VERB_CRAZY, 'register group controller: %s', $name); $this->router->addGroup($name, $class, $config); @@ -312,7 +312,7 @@ public function controller(string $name, ControllerInterface|string $class = nul * @return static * @see controller() */ - public function addGroup(string $name, ControllerInterface|string $class = null, array $config = []): static + public function addGroup(string $name, ControllerInterface|string|null $class = null, array $config = []): static { return $this->controller($name, $class, $config); } @@ -325,7 +325,7 @@ public function addGroup(string $name, ControllerInterface|string $class = null, * @return $this * @see controller() */ - public function addController(string $name, ControllerInterface|string $class = null, array $config = []): static + public function addController(string $name, ControllerInterface|string|null $class = null, array $config = []): static { return $this->controller($name, $class, $config); } @@ -353,7 +353,7 @@ public function addControllers(array $controllers): void * * @return Application */ - public function command(string $name, string|Closure|CommandInterface $handler = null, array $config = []): static + public function command(string $name, string|Closure|CommandInterface|null $handler = null, array $config = []): static { $this->logf(Console::VERB_CRAZY, 'register alone command: %s', $name); $this->router->addCommand($name, $handler, $config); @@ -371,7 +371,7 @@ public function command(string $name, string|Closure|CommandInterface $handler = * @return Application * @see command() */ - public function addCommand(string $name, string|Closure|CommandInterface $handler = null, array $config = []): static + public function addCommand(string $name, string|Closure|CommandInterface|null $handler = null, array $config = []): static { return $this->command($name, $handler, $config); } diff --git a/src/Component/Router.php b/src/Component/Router.php index 5da9e30f..bdc8760f 100644 --- a/src/Component/Router.php +++ b/src/Component/Router.php @@ -115,7 +115,7 @@ class Router implements RouterInterface * @return static * @throws InvalidArgumentException */ - public function addGroup(string $name, ControllerInterface|string $class = null, array $config = []): static + public function addGroup(string $name, ControllerInterface|string|null $class = null, array $config = []): static { /** * @var Controller $class name is an controller class @@ -179,7 +179,7 @@ public function addGroup(string $name, ControllerInterface|string $class = null, * * @return static */ - public function addCommand(string $name, string|Closure|CommandInterface $handler = null, array $config = []): static + public function addCommand(string $name, string|Closure|CommandInterface|null $handler = null, array $config = []): static { if (!$handler && class_exists($name)) { $handler = $name; diff --git a/src/Console.php b/src/Console.php index 46db9224..104f1f74 100644 --- a/src/Console.php +++ b/src/Console.php @@ -117,8 +117,8 @@ public static function getOutput(): Output */ public static function newApp( array $config = [], - Input $input = null, - Output $output = null + ?Input $input = null, + ?Output $output = null ): Application { return new Application($config, $input, $output); } diff --git a/src/Contract/ApplicationInterface.php b/src/Contract/ApplicationInterface.php index 7240bb2e..97bc515a 100644 --- a/src/Contract/ApplicationInterface.php +++ b/src/Contract/ApplicationInterface.php @@ -65,7 +65,7 @@ public function stop(int $code = 0): void; * @return static * @throws InvalidArgumentException */ - public function controller(string $name, ControllerInterface|string $class = null, array $config = []): static; + public function controller(string $name, ControllerInterface|string|null $class = null, array $config = []): ApplicationInterface; /** * Register a app independent console command @@ -77,8 +77,11 @@ public function controller(string $name, ControllerInterface|string $class = nul * @return mixed * @throws InvalidArgumentException */ - public function command(string $name, string|Closure|CommandInterface $handler = null, array $config = []): static; + public function command(string $name, string|Closure|CommandInterface|null $handler = null, array $config = []): ApplicationInterface; + /** + * @return void + */ public function showCommandList(); /** diff --git a/src/Contract/RouterInterface.php b/src/Contract/RouterInterface.php index e337a231..d6879af7 100644 --- a/src/Contract/RouterInterface.php +++ b/src/Contract/RouterInterface.php @@ -35,7 +35,7 @@ interface RouterInterface * * @return static */ - public function addGroup(string $name, ControllerInterface|string $class = null, array $config = []): static; + public function addGroup(string $name, ControllerInterface|string|null $class = null, array $config = []): static; /** * Register a app independent console command @@ -46,7 +46,7 @@ public function addGroup(string $name, ControllerInterface|string $class = null, * * @return static */ - public function addCommand(string $name, string|Closure|CommandInterface $handler = null, array $config = []): static; + public function addCommand(string $name, string|Closure|CommandInterface|null $handler = null, array $config = []): static; /** * ```php diff --git a/src/Controller.php b/src/Controller.php index 5df13c80..d9cecda6 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -520,7 +520,7 @@ protected function beforeRenderCommandHelp(array &$help): void * * @return ?Generator */ - protected function getAllCommandMethods(ReflectionClass $ref = null, bool $onlyName = false): ?Generator + protected function getAllCommandMethods(?ReflectionClass $ref = null, bool $onlyName = false): ?Generator { $ref = $ref ?: new ReflectionObject($this); diff --git a/src/Decorate/SubCommandsWareTrait.php b/src/Decorate/SubCommandsWareTrait.php index dce5f969..513510b7 100644 --- a/src/Decorate/SubCommandsWareTrait.php +++ b/src/Decorate/SubCommandsWareTrait.php @@ -157,7 +157,7 @@ protected function createSubCommand(array $subInfo): Command * @param class-string|CommandInterface|null $handler * @param array $config */ - public function addSub(string $name, string|CommandInterface $handler = null, array $config = []): void + public function addSub(string $name, string|CommandInterface|null $handler = null, array $config = []): void { if (!$handler && class_exists($name)) { /** @var Command $name name is an command class */ diff --git a/src/Decorate/UserInteractAwareTrait.php b/src/Decorate/UserInteractAwareTrait.php index c1c438a7..b58f5a31 100644 --- a/src/Decorate/UserInteractAwareTrait.php +++ b/src/Decorate/UserInteractAwareTrait.php @@ -41,7 +41,7 @@ trait UserInteractAwareTrait * @return string * @see Interact::choice() */ - public function select(string $description, array|string $options, int|string $default = null, bool $allowExit = true): string + public function select(string $description, array|string $options, int|string|null $default = null, bool $allowExit = true): string { return $this->choice($description, $options, $default, $allowExit); } @@ -54,7 +54,7 @@ public function select(string $description, array|string $options, int|string $d * * @return string */ - public function choice(string $description, array|string $options, int|string $default = null, bool $allowExit = true): string + public function choice(string $description, array|string $options, int|string|null $default = null, bool $allowExit = true): string { return Interact::choice($description, $options, $default, $allowExit); } @@ -88,7 +88,7 @@ public function unConfirm(string $question, bool $default = true): bool * * @return string|null */ - public function ask(string $question, string $default = '', Closure $validator = null): ?string + public function ask(string $question, string $default = '', ?Closure $validator = null): ?string { return $this->question($question, $default, $validator); } @@ -100,7 +100,7 @@ public function ask(string $question, string $default = '', Closure $validator = * * @return string|null */ - public function question(string $question, string $default = '', Closure $validator = null): ?string + public function question(string $question, string $default = '', ?Closure $validator = null): ?string { return Interact::question($question, $default, $validator); } @@ -114,7 +114,7 @@ public function question(string $question, string $default = '', Closure $valida * @return string|null * @see Interact::limitedAsk() */ - public function limitedAsk(string $question, string $default = '', Closure $validator = null, int $times = 3): ?string + public function limitedAsk(string $question, string $default = '', ?Closure $validator = null, int $times = 3): ?string { return Interact::limitedAsk($question, $default, $validator, $times); } @@ -123,7 +123,7 @@ public function limitedAsk(string $question, string $default = '', Closure $vali * @param string $method * @param array $args * - * @return int + * @return mixed * @throws LogicException */ public function __call(string $method, array $args = []) diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index 4e6ec014..6037e93b 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -124,7 +124,7 @@ public static function aliases(): array * @param Input|null $input * @param Output|null $output */ - public function __construct(Input $input = null, Output $output = null) + public function __construct(?Input $input = null, ?Output $output = null) { // init io stream $input && $this->setInput($input); diff --git a/src/IO/Input.php b/src/IO/Input.php index 187fe9bc..828d8a62 100644 --- a/src/IO/Input.php +++ b/src/IO/Input.php @@ -33,7 +33,7 @@ class Input extends StreamInput * * @param null|array $args */ - public function __construct(array $args = null) + public function __construct(?array $args = null) { if (null === $args) { $args = $_SERVER['argv']; From c2b9d9e740815ff02f68183d2ae0d78e8fb7b95d Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 20 Apr 2025 00:03:20 +0800 Subject: [PATCH 248/258] up: fix some code syntax error on php 8.4 --- .github/workflows/php.yml | 2 +- README.en.md | 2 +- README.md | 2 +- composer.json | 2 +- src/Component/ErrorHandler.php | 1 + src/Component/Interact/LimitedAsk.php | 2 +- src/Component/Interact/Question.php | 2 +- src/Component/Symbol/ArtFont.php | 8 ++++---- src/Decorate/RuntimeProfileTrait.php | 4 ++-- src/Util/Interact.php | 8 ++++---- src/Util/ProgressBar.php | 6 +++--- test/bootstrap.php | 2 +- 12 files changed, 21 insertions(+), 20 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 44df80fd..2cedb4c9 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -54,7 +54,7 @@ jobs: - name: Run unit tests run: | - phpunit -v --debug + phpunit --debug php examples/alone -h php examples/app --help diff --git a/README.en.md b/README.en.md index 3e25fc20..2d430431 100644 --- a/README.en.md +++ b/README.en.md @@ -1,7 +1,7 @@ # PHP Console [![License](https://img.shields.io/packagist/l/inhere/console.svg?style=flat-square)](LICENSE) -[![Php Version](https://img.shields.io/badge/php-%3E=8.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) +[![Php Version](https://img.shields.io/packagist/php-v/inhere/console?maxAge=2592000)](https://packagist.org/packages/inhere/console) [![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console) [![Github Actions Status](https://github.com/inhere/php-console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/php-console/actions) [![zh-CN readme](https://img.shields.io/badge/Readme-中文-brightgreen.svg?maxAge=2592000)](README.md) diff --git a/README.md b/README.md index 2b7aa417..98c5a81d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # PHP Console [![License](https://img.shields.io/packagist/l/inhere/console.svg?style=flat-square)](LICENSE) -[![Php Version](https://img.shields.io/badge/php-%3E=8.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console) +[![Php Version](https://img.shields.io/packagist/php-v/inhere/console?maxAge=2592000)](https://packagist.org/packages/inhere/console) [![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console) [![Github Actions Status](https://github.com/inhere/php-console/workflows/Unit-tests/badge.svg)](https://github.com/inhere/php-console/actions) [![English](https://img.shields.io/badge/Readme-English-brightgreen.svg?maxAge=2592000)](README.en.md) diff --git a/composer.json b/composer.json index a1f57a53..1e9b3f79 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ } ], "require": { - "php": ">8.0.1", + "php": ">8.1", "toolkit/cli-utils": "~2.0", "toolkit/fsutil": "~2.0", "toolkit/pflag": "~2.0", diff --git a/src/Component/ErrorHandler.php b/src/Component/ErrorHandler.php index 1b5eebb0..898cf06b 100644 --- a/src/Component/ErrorHandler.php +++ b/src/Component/ErrorHandler.php @@ -88,6 +88,7 @@ public function handle(Throwable $e): void $file = $e->getFile(); $prev = $e->getPrevious(); + // var_dump($e); $snippet = Highlighter::create()->snippet(file_get_contents($file), $line, 3, 3); $message = sprintf( $tpl, // $e->getCode(), diff --git a/src/Component/Interact/LimitedAsk.php b/src/Component/Interact/LimitedAsk.php index 8e0f66e1..ec51a7ac 100644 --- a/src/Component/Interact/LimitedAsk.php +++ b/src/Component/Interact/LimitedAsk.php @@ -63,7 +63,7 @@ class LimitedAsk extends InteractiveHandle public static function ask( string $question, string $default = '', - Closure $validator = null, + ?Closure $validator = null, int $times = 3 ): string { if (!$question = trim($question)) { diff --git a/src/Component/Interact/Question.php b/src/Component/Interact/Question.php index ca65bf4a..114b72c6 100644 --- a/src/Component/Interact/Question.php +++ b/src/Component/Interact/Question.php @@ -62,7 +62,7 @@ class Question extends InteractiveHandle * echo "Your input: $answer"; * ``` */ - public static function ask(string $question, string $default = '', Closure $validator = null): string + public static function ask(string $question, string $default = '', ?Closure $validator = null): string { if (!$question = trim($question)) { Show::error('Please provide a question text!', 1); diff --git a/src/Component/Symbol/ArtFont.php b/src/Component/Symbol/ArtFont.php index a4632f42..12cdfb6a 100644 --- a/src/Component/Symbol/ArtFont.php +++ b/src/Component/Symbol/ArtFont.php @@ -109,7 +109,7 @@ public function showInternal(string $name, array $opts = []): int * * @return int */ - public function showItalic(string $name, string $group = null, array $opts = []): int + public function showItalic(string $name, ?string $group = null, array $opts = []): int { $opts['type'] = 'italic'; @@ -129,7 +129,7 @@ public function showItalic(string $name, string $group = null, array $opts = []) * * @return int */ - public function show(string $name, string $group = null, array $opts = []): int + public function show(string $name, ?string $group = null, array $opts = []): int { $opts = array_merge([ 'type' => '', @@ -175,7 +175,7 @@ public function show(string $name, string $group = null, array $opts = []): int * * @return string */ - public function font(string $name, string $group = null): string + public function font(string $name, ?string $group = null): string { return ''; } @@ -227,7 +227,7 @@ public function setGroup(string $group, string $path): self * * @return $this */ - public function addFont(string $name, string $file, string $group = null): self + public function addFont(string $name, string $file, ?string $group = null): self { $group = $group ?: self::DEFAULT_GROUP; diff --git a/src/Decorate/RuntimeProfileTrait.php b/src/Decorate/RuntimeProfileTrait.php index 3dd3524f..027e8cf3 100644 --- a/src/Decorate/RuntimeProfileTrait.php +++ b/src/Decorate/RuntimeProfileTrait.php @@ -81,7 +81,7 @@ public static function profile($name, array $context = [], string $category = 'a * * @return bool|array */ - public static function profileEnd(string $msg = null, array $context = []): bool|array + public static function profileEnd(?string $msg = null, array $context = []): bool|array { if (!$latestKey = array_pop(self::$keyQueue)) { return false; @@ -114,7 +114,7 @@ public static function profileEnd(string $msg = null, array $context = []): bool * * @return array */ - public static function getProfileData(string $name = null, string $category = 'application'): array + public static function getProfileData(?string $name = null, string $category = 'application'): array { if ($name) { return self::$profiles[$category][$name] ?? []; diff --git a/src/Util/Interact.php b/src/Util/Interact.php index 13fc30dd..d918efea 100644 --- a/src/Util/Interact.php +++ b/src/Util/Interact.php @@ -180,7 +180,7 @@ public static function unConfirm(string $question, bool $default = true): bool * * @return bool */ - public static function answerIsYes(bool $default = null): bool + public static function answerIsYes(?bool $default = null): bool { $mark = ' [yes|no]: '; @@ -216,7 +216,7 @@ public static function answerIsYes(bool $default = null): bool * * @return string|null */ - public static function ask(string $question, string $default = '', Closure $validator = null): ?string + public static function ask(string $question, string $default = '', ?Closure $validator = null): ?string { return self::question($question, $default, $validator); } @@ -231,7 +231,7 @@ public static function ask(string $question, string $default = '', Closure $vali * @return string * @see Question::ask() */ - public static function question(string $question, string $default = '', Closure $validator = null): string + public static function question(string $question, string $default = '', ?Closure $validator = null): string { return Question::ask($question, $default, $validator); } @@ -250,7 +250,7 @@ public static function question(string $question, string $default = '', Closure public static function limitedAsk( string $question, string $default = '', - Closure $validator = null, + ?Closure $validator = null, int $times = 3 ): string { return LimitedAsk::ask($question, $default, $validator, $times); diff --git a/src/Util/ProgressBar.php b/src/Util/ProgressBar.php index fa9fb2be..6580d4ea 100644 --- a/src/Util/ProgressBar.php +++ b/src/Util/ProgressBar.php @@ -147,7 +147,7 @@ class ProgressBar * * @return ProgressBar */ - public static function create(Output $output = null, int $maxSteps = 0): ProgressBar + public static function create(?Output $output = null, int $maxSteps = 0): ProgressBar { return new self($output, $maxSteps); } @@ -156,7 +156,7 @@ public static function create(Output $output = null, int $maxSteps = 0): Progres * @param Output|null $output * @param int $maxSteps */ - public function __construct(Output $output = null, int $maxSteps = 0) + public function __construct(?Output $output = null, int $maxSteps = 0) { $this->output = $output ?: new Output; @@ -171,7 +171,7 @@ public function __construct(Output $output = null, int $maxSteps = 0) * * @throws LogicException */ - public function start(int $maxSteps = null): void + public function start(?int $maxSteps = null): void { if ($this->started) { throw new LogicException('Progress bar already started.'); diff --git a/test/bootstrap.php b/test/bootstrap.php index 0c6634d2..43ed9d86 100644 --- a/test/bootstrap.php +++ b/test/bootstrap.php @@ -7,7 +7,7 @@ * @license https://github.com/inhere/php-console/blob/master/LICENSE */ -error_reporting(E_ALL | E_STRICT); +error_reporting(E_ALL); date_default_timezone_set('Asia/Shanghai'); spl_autoload_register(static function ($class): void { From a4181a758a452988cf218f89d24b98af78029821 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 20 Apr 2025 00:05:37 +0800 Subject: [PATCH 249/258] dep: update required phpunit version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1e9b3f79..068ea758 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "toolkit/sys-utils": "~2.0" }, "require-dev": { - "phpunit/phpunit": "^9.1" + "phpunit/phpunit": "^10.0" }, "autoload": { "psr-4": { From e3ecd9bc23ad91d62fe63602582a5235ae9554cd Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 20 Apr 2025 00:19:33 +0800 Subject: [PATCH 250/258] test: fix some error on use phpunit 12 --- .github/workflows/php.yml | 2 +- composer.json | 2 +- phpunit.xml | 34 +++++++++++----------------------- src/AbstractApplication.php | 6 ++++++ test/ApplicationTest.php | 2 +- test/BaseTestCase.php | 5 +++++ test/CommandTest.php | 2 +- test/IO/InputTest.php | 5 ++++- test/TestCommand.php | 2 +- test/bootstrap.php | 2 ++ 10 files changed, 33 insertions(+), 29 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 2cedb4c9..f43fdbfd 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -54,7 +54,7 @@ jobs: - name: Run unit tests run: | - phpunit --debug + phpunit php examples/alone -h php examples/app --help diff --git a/composer.json b/composer.json index 068ea758..071ff719 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "toolkit/sys-utils": "~2.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^12.0" }, "autoload": { "psr-4": { diff --git a/phpunit.xml b/phpunit.xml index d615c158..cf45ea0c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,25 +1,13 @@ - - - - - test - - - - - - src - - + + + + test + + + + + src + + diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index d467e52a..f1c9d3ac 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -444,6 +444,12 @@ protected function runtimeCheck(): void */ protected function registerErrorHandle(): void { + // not register error handle in unit test + if (defined('UNIT_TESTING') && UNIT_TESTING) { + $this->debugf('skip register error handle in unit test'); + return; + } + set_error_handler([$this, 'handleError']); set_exception_handler([$this, 'handleException']); register_shutdown_function(function (): void { diff --git a/test/ApplicationTest.php b/test/ApplicationTest.php index 62d5bcde..a940bd7a 100644 --- a/test/ApplicationTest.php +++ b/test/ApplicationTest.php @@ -32,7 +32,7 @@ protected function assertStringContains(string $string, string $contains): void self::assertNotFalse(strpos($string, $contains), "string \"$string\" not contains: $contains"); } - private function newApp(array $args = null): Application + private function newApp(?array $args = null): Application { $input = new Input($args); diff --git a/test/BaseTestCase.php b/test/BaseTestCase.php index 631f5be6..fa725b34 100644 --- a/test/BaseTestCase.php +++ b/test/BaseTestCase.php @@ -17,4 +17,9 @@ */ abstract class BaseTestCase extends TestCase { + protected function assertStringContains(string $string, string $contains): void + { + self::assertNotFalse(strpos($string, $contains), "string \"$string\" not contains: $contains"); + } + } diff --git a/test/CommandTest.php b/test/CommandTest.php index b7242f0f..059ab2a3 100644 --- a/test/CommandTest.php +++ b/test/CommandTest.php @@ -43,7 +43,7 @@ public function testCommand_sub_run(): void $c = new TestCommand(new Input(), Output::new()); $str = $c->run(['sub1']); - $this->assertEquals('Inhere\ConsoleTest\{closure}', $str); + $this->assertEquals('at TestCommand::sub1', $str); } public function testCommand_sub_help(): void diff --git a/test/IO/InputTest.php b/test/IO/InputTest.php index b647980d..a584018a 100644 --- a/test/IO/InputTest.php +++ b/test/IO/InputTest.php @@ -27,6 +27,9 @@ public function testBasic(): void $this->assertSame('app', $in->getScriptName()); // $this->assertSame('cmd', $in->getCommand()); $this->assertEquals('cmd val0 val1', $in->getFullScript()); - $this->assertEquals("'./bin/app' cmd val0 val1", $in->toString()); + $s = $in->toString(); + $this->assertStringContainsString('bin/app', $s); + $this->assertStringContainsString('cmd val0 val1', $s); + // $this->assertEquals("'./bin/app' cmd val0 val1", $in->toString()); } } diff --git a/test/TestCommand.php b/test/TestCommand.php index 1264e75b..4dd4dcba 100644 --- a/test/TestCommand.php +++ b/test/TestCommand.php @@ -29,7 +29,7 @@ protected function subCommands(): array { return [ CommandWrapper::new(static function () { - return __METHOD__; + return 'at TestCommand::sub1'; })->withConfig([ 'name' => 'sub1', 'desc' => 'desc for sub1 in test1', diff --git a/test/bootstrap.php b/test/bootstrap.php index 43ed9d86..58202195 100644 --- a/test/bootstrap.php +++ b/test/bootstrap.php @@ -10,6 +10,8 @@ error_reporting(E_ALL); date_default_timezone_set('Asia/Shanghai'); +define('UNIT_TESTING', true); + spl_autoload_register(static function ($class): void { $file = null; From eae695427ae3ba580625c077484d55972a8359a1 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 20 Apr 2025 00:30:38 +0800 Subject: [PATCH 251/258] fix: build example phar fail on ci --- .github/workflows/php.yml | 2 +- phar.build.inc | 1 + src/Component/PharCompiler.php | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index f43fdbfd..1b7759ee 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -60,5 +60,5 @@ jobs: - name: Test build PHAR run: | - php -d phar.readonly=0 examples/app phar pack -o=myapp.phar + php -d phar.readonly=0 examples/app phar pack --no-progress -o=myapp.phar php myapp.phar -h diff --git a/phar.build.inc b/phar.build.inc index 416e64ef..5bf387cc 100644 --- a/phar.build.inc +++ b/phar.build.inc @@ -25,6 +25,7 @@ $compiler 'composer.json', 'README.md', 'test/bootstrap.php', + 'examples/commands.php' ]) ->setCliIndex('examples/app') // ->setWebIndex('web/index.php') diff --git a/src/Component/PharCompiler.php b/src/Component/PharCompiler.php index fd55d9eb..e958eac3 100644 --- a/src/Component/PharCompiler.php +++ b/src/Component/PharCompiler.php @@ -263,7 +263,7 @@ private static function checkEnv(): void * @throws BadMethodCallException * @throws RuntimeException */ - public static function unpack(string $pharFile, string $extractTo, array|string $files = null, bool $overwrite = false): bool + public static function unpack(string $pharFile, string $extractTo, array|string|null $files = null, bool $overwrite = false): bool { self::checkEnv(); From 27c1048e12b7ca6d2623b90eff704a7392f41b68 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 20 Apr 2025 00:32:38 +0800 Subject: [PATCH 252/258] dep: remove dev package phpunit --- composer.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 071ff719..f19d420d 100644 --- a/composer.json +++ b/composer.json @@ -29,9 +29,7 @@ "toolkit/stdlib": "^2.0", "toolkit/sys-utils": "~2.0" }, - "require-dev": { - "phpunit/phpunit": "^12.0" - }, + "require-dev": {}, "autoload": { "psr-4": { "Inhere\\Console\\": "src/" From 17928ce2522138a8853e4c620558f44908f4b8d2 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 20 Apr 2025 12:55:51 +0800 Subject: [PATCH 253/258] up: update some logic for phar build and verb env handle --- phar.build.inc | 15 +++--- src/AbstractApplication.php | 6 +-- src/BuiltIn/PharController.php | 20 ++++---- src/Component/PharCompiler.php | 32 ++++++++++--- src/Component/Router.php | 2 +- src/Console.php | 63 ++++++++++++++++++++++++- src/Decorate/AttachApplicationTrait.php | 10 +--- 7 files changed, 111 insertions(+), 37 deletions(-) diff --git a/phar.build.inc b/phar.build.inc index 5bf387cc..bf681f30 100644 --- a/phar.build.inc +++ b/phar.build.inc @@ -13,19 +13,20 @@ $compiler // ->stripComments(false) ->setShebang(true) ->addExclude([ - 'demo', - 'example', - 'runtime', - 'node_modules', - 'test', - 'tmp', + 'demo/', + 'docs/', + 'example/', + 'runtime/', + 'node_modules/', + 'test/', + 'tmp/', + '/console/resource/', ]) ->addFile([ 'LICENSE', 'composer.json', 'README.md', 'test/bootstrap.php', - 'examples/commands.php' ]) ->setCliIndex('examples/app') // ->setWebIndex('web/index.php') diff --git a/src/AbstractApplication.php b/src/AbstractApplication.php index f1c9d3ac..84801011 100644 --- a/src/AbstractApplication.php +++ b/src/AbstractApplication.php @@ -829,10 +829,8 @@ public function getVerbLevel(): int $optKey = GlobalOption::DEBUG; // feat: support set debug level by ENV var: CONSOLE_DEBUG - $envVal = OS::getEnvStrVal(Console::DEBUG_ENV_KEY); - if ($envVal !== '') { - $setVal = (int)$envVal; - } else { + $setVal = Console::getLevelFromENV(-1); + if ($setVal < 0) { $setVal = (int)$this->config[$optKey]; } diff --git a/src/BuiltIn/PharController.php b/src/BuiltIn/PharController.php index 20a6f4c4..b5078e0a 100644 --- a/src/BuiltIn/PharController.php +++ b/src/BuiltIn/PharController.php @@ -76,8 +76,9 @@ protected function packConfigure(Input $input): void * -o, --output Setting the output file name({defaultPkgName}) * --fast bool;Fast build. only add modified files by git status -s * --refresh bool;Whether build vendor folder files on phar file exists(False) - * --files Only pack the list files to the exist phar, multi use ',' split - * --no-progress bool;Disable output progress on the runtime + * --files Only pack the list files to exist phar, multi use ',' split + * --no-progress bool;Disable output progress on the runtime. + * --debug bool;Show debug information for collect files * * @param Input $input * @param Output $output @@ -137,9 +138,8 @@ public function packCommand(Input $input, Output $output, FlagsParser $fs): int $output->colored('Collect Pack files', 'comment'); - if (!$fs->getOpt('no-progress')) { - $this->outputProgress($cpr); - } + $showProgress = !$fs->getOpt('no-progress'); + $this->outputProgress($cpr, $fs->getOpt('debug'), $showProgress); // packing ... $cpr->pack($pharFile, $refresh); @@ -186,22 +186,24 @@ protected function configCompiler(string $dir, string $confFile): PharCompiler /** * @param PharCompiler $cpr + * @param bool $debug + * @param bool $showProgress * * @return void */ - private function outputProgress(PharCompiler $cpr): void + private function outputProgress(PharCompiler $cpr, bool $debug, bool $showProgress): void { - if ($this->isDebug()) { + if ($debug || $this->isDebug()) { // $output->info('Pack file to Phar ... ...'); $cpr->onAdd(function (string $path): void { $this->writeln(" + $path"); }); - $cpr->on('skip', function (string $path, bool $isFile): void { + $cpr->on(PharCompiler::ON_SKIP, function (string $path, bool $isFile): void { $mark = $isFile ? '[F]' : '[D]'; $this->writeln(" - $path $mark"); }); - } else { + } else if ($showProgress) { $counter = Show::counterTxt('Collecting ...', 'Done.'); $cpr->onAdd(static function () use ($counter): void { $counter->send(1); diff --git a/src/Component/PharCompiler.php b/src/Component/PharCompiler.php index e958eac3..dc5c7108 100644 --- a/src/Component/PharCompiler.php +++ b/src/Component/PharCompiler.php @@ -63,6 +63,10 @@ class PharCompiler { public const ON_ADD = 'add'; + /** + * skip dir or file. + * - fn: `function(string $path, bool $isFile) {}` + */ public const ON_SKIP = 'skip'; public const ON_ERROR = 'error'; @@ -283,7 +287,7 @@ public function __construct(string $basePath) { self::checkEnv(); - $this->basePath = File::realpath($basePath); + $this->basePath = File::pathFormat(File::realpath($basePath), false); $this->fileQueue = new SplQueue(); if (!is_dir($this->basePath)) { @@ -768,7 +772,13 @@ private function getIteratorFilter(): Closure if (!$this->fileFilter) { $this->fileFilter = function (SplFileInfo $file) { $name = $file->getFilename(); - $path = $file->getPathname(); + $path = File::pathFormat($file->getPathname(), false); + // remove basePath prefix + if (str_starts_with($path, $this->basePath)) { + $noFullPath = substr($path, strlen($this->basePath)+1); + } else { + $noFullPath = $path; + } // Skip hidden files and directories. if (str_starts_with($name, '.')) { @@ -779,13 +789,23 @@ private function getIteratorFilter(): Closure if ($file->isDir()) { foreach ($this->excludes as $exclude) { if (strpos($path . '/', $exclude) > 0) { - $this->fire(self::ON_SKIP, $path, false); + $this->fire(self::ON_SKIP, $noFullPath . '/', false); return false; } } return true; } + // Exclude file check + if ($this->excludes) { + foreach ($this->excludes as $exclude) { + if (strpos($path, $exclude) > 0) { + $this->fire(self::ON_SKIP, $noFullPath, true); + return false; + } + } + } + // File ext check if ($this->suffixes) { foreach ($this->suffixes as $suffix) { @@ -794,7 +814,7 @@ private function getIteratorFilter(): Closure } } - $this->fire(self::ON_SKIP, $path, true); + $this->fire(self::ON_SKIP, $noFullPath, true); return false; } @@ -856,8 +876,8 @@ private function collectInformation(): void */ private function getRelativeFilePath(SplFileInfo $file): string { - $realPath = $file->getRealPath(); - $pathPrefix = $this->basePath . DIRECTORY_SEPARATOR; + $realPath = File::pathFormat($file->getRealPath(), false); + $pathPrefix = $this->basePath . '/'; $pos = strpos($realPath, $pathPrefix); $path = $pos !== false ? substr_replace($realPath, '', $pos, strlen($pathPrefix)) : $realPath; diff --git a/src/Component/Router.php b/src/Component/Router.php index bdc8760f..4d2f964c 100644 --- a/src/Component/Router.php +++ b/src/Component/Router.php @@ -189,7 +189,7 @@ public function addCommand(string $name, string|Closure|CommandInterface|null $h if (!$name || !$handler) { $handlerClass = is_object($handler) ? get_class($handler) : $handler; - throw new InvalidArgumentException("Command 'name' and 'handler' cannot be empty! name: $name, handler: $handlerClass"); + throw new InvalidArgumentException("Command 'name' and 'handler' cannot be empty! (name: $name, handler: $handlerClass)"); } Assert::isFalse(isset($this->commands[$name]), "Command '$name' have been registered!"); diff --git a/src/Console.php b/src/Console.php index 104f1f74..ba234655 100644 --- a/src/Console.php +++ b/src/Console.php @@ -14,6 +14,8 @@ use Toolkit\Cli\Cli; use Toolkit\Cli\Color\ColorTag; use Toolkit\Stdlib\Json; +use Toolkit\Stdlib\OS; +use Toolkit\Stdlib\Str; use function date; use function debug_backtrace; use function implode; @@ -55,6 +57,19 @@ class Console extends Cli self::VERB_CRAZY => 'CRAZY', ]; + // name => level + public const NAME2LEVEL = [ + 'QUIET' => self::VERB_QUIET, + 'ERR' => self::VERB_ERROR, // alias + 'ERROR' => self::VERB_ERROR, + 'WARN' => self::VERB_WARN, + 'WARNING' => self::VERB_WARN, // alias + 'INFO' => self::VERB_INFO, + 'DEBUG' => self::VERB_DEBUG, + 'CRAZY' => self::VERB_CRAZY, + ]; + + // level => color name public const LEVEL2TAG = [ self::VERB_QUIET => 'normal', self::VERB_ERROR => 'error', @@ -64,7 +79,7 @@ class Console extends Cli self::VERB_CRAZY => 'magenta', ]; - public const CMD_GROUP = 1; + public const CMD_GROUP = 1; public const CMD_SINGLE = 2; @@ -76,6 +91,49 @@ class Console extends Cli */ private static ?Application $app; + /** + * get debug level from ENV var: `CONSOLE_DEBUG`. if not set, return `$defaultLevel` + * + * @param int $defaultLevel + * + * @return integer + */ + public static function getLevelFromENV(int $defaultLevel = self::VERB_ERROR): int + { + // feat: support set debug level by ENV var: CONSOLE_DEBUG + $level = $defaultLevel; + $envVal = OS::getEnvStrVal(Console::DEBUG_ENV_KEY); + + if ($envVal !== '') { + if (is_numeric($envVal)) { + $level = (int)$envVal; + } else { + $level = self::nameToLevel($envVal, $defaultLevel); + } + } + + return $level; + } + + /** + * level name to level number + * + * @param string $levelName + * @param int $defaultLevel + * + * @return int + */ + public static function nameToLevel(string $levelName, int $defaultLevel = self::VERB_INFO): int + { + $levelName = Str::upper($levelName); + + if (isset(self::NAME2LEVEL[$levelName])) { + return self::NAME2LEVEL[$levelName]; + } + + return $defaultLevel; + } + /** * @return Application */ @@ -184,7 +242,8 @@ public static function log(int $level, string $msg, array $data = [], array $opt $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, self::$traceIndex + 2); $position = self::formatBacktrace($backtrace, self::$traceIndex); - self::writef('%s [%s] [%s]%s %s %s' . PHP_EOL, $datetime, $taggedName, $position, $optString, trim($msg), $dataString); + self::writef('%s [%s] [%s]%s %s %s' . PHP_EOL, $datetime, $taggedName, $position, $optString, trim($msg), + $dataString); } /** diff --git a/src/Decorate/AttachApplicationTrait.php b/src/Decorate/AttachApplicationTrait.php index a28befc4..bd5e7a0d 100644 --- a/src/Decorate/AttachApplicationTrait.php +++ b/src/Decorate/AttachApplicationTrait.php @@ -106,8 +106,7 @@ public function getVerbLevel(): int } // return (int)$this->input->getLongOpt('debug', Console::VERB_ERROR); - $envVal = OS::getEnvStrVal(Console::DEBUG_ENV_KEY); - return $envVal !== '' ? (int)$envVal : Console::VERB_ERROR; + return Console::getLevelFromENV(); } /** @@ -121,12 +120,7 @@ public function isDebug(int $level = Console::VERB_DEBUG): bool return $this->app->isDebug(); } - $setVal = Console::VERB_ERROR; - $envVal = OS::getEnvStrVal(Console::DEBUG_ENV_KEY); - if ($envVal !== '') { - $setVal = (int)$envVal; - } - + $setVal = Console::getLevelFromENV(); return $level <= $setVal; } From 5cec9539ac68a727bdcd58efd542e161f4756464 Mon Sep 17 00:00:00 2001 From: inhere Date: Sun, 20 Apr 2025 13:19:59 +0800 Subject: [PATCH 254/258] ci: update the release action on checkout repo --- .github/workflows/release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 879c7ad0..6548c87f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,9 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set ENV for github-release # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable From bd18ec0ea1853e3383bfa24e3eb600d2be85eec8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 12:00:10 +0000 Subject: [PATCH 255/258] chore(deps): bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 1b7759ee..4a4acce3 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set ENV vars # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable From b2dbb00ea4cf333de9b771f2613cc395a2c900f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 12:36:26 +0000 Subject: [PATCH 256/258] chore(deps): bump softprops/action-gh-release from 1 to 2 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: '2' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6548c87f..82491007 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: # https://github.com/softprops/action-gh-release - name: Create release and upload assets - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 # if: startsWith(github.ref, 'refs/tags/') with: name: ${{ env.RELEASE_TAG }} From d8be9b366efb369e5148ef0f649f6346605e36e6 Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 29 Apr 2025 17:19:10 +0800 Subject: [PATCH 257/258] style: fix Choose.php code sytle in php8.4 --- src/Component/Formatter/Padding.php | 2 +- src/Component/Interact/Choose.php | 19 ++++++++++--------- src/Component/Interact/LimitedAsk.php | 2 +- src/Contract/ApplicationInterface.php | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Component/Formatter/Padding.php b/src/Component/Formatter/Padding.php index 879326ea..8300b334 100644 --- a/src/Component/Formatter/Padding.php +++ b/src/Component/Formatter/Padding.php @@ -53,7 +53,7 @@ public static function show(array $data, string $title = '', array $opts = []): ], $opts); $keyMaxLen = ArrayHelper::getKeyMaxWidth($data); - $paddingLen = $keyMaxLen > $opts['padding'] ? $keyMaxLen : $opts['padding']; + $paddingLen = max($keyMaxLen, $opts['padding']); foreach ($data as $label => $value) { $value = ColorTag::wrap((string)$value, $opts['valueStyle']); diff --git a/src/Component/Interact/Choose.php b/src/Component/Interact/Choose.php index f4d34aec..4a043a66 100644 --- a/src/Component/Interact/Choose.php +++ b/src/Component/Interact/Choose.php @@ -29,23 +29,24 @@ class Choose extends SingleSelect /** * Choose one of several options * - * @param string $description + * Options: + * e.g: [ + * // option => value + * '1' => 'chengdu', + * '2' => 'beijing' + * ] + * + * @param string $description * @param array|string $options Option data - * e.g - * [ - * // option => value - * '1' => 'chengdu', - * '2' => 'beijing' - * ] * @param int|string|null $default Default option * @param bool $allowExit * @param array $opts * - * @psalm-param array{returnVal: bool, retFilter: callable} $opts + * @psalm-param array{returnVal: bool, retFilter: callable} $opts * * @return string */ - public static function one(string $description, array|string $options, int|string $default = null, bool $allowExit = true, array $opts = []): string + public static function one(string $description, array|string $options, int|string|null $default = null, bool $allowExit = true, array $opts = []): string { if (!$description = trim($description)) { Show::error('Please provide a description text!', 1); diff --git a/src/Component/Interact/LimitedAsk.php b/src/Component/Interact/LimitedAsk.php index ec51a7ac..410eec80 100644 --- a/src/Component/Interact/LimitedAsk.php +++ b/src/Component/Interact/LimitedAsk.php @@ -115,7 +115,7 @@ public static function ask( return $default; } - Console::write("\n You've entered incorrectly $back times in a row. exit!", true, 1); + Console::write("\n You've entered incorrectly $back times in a row. exit!", true, true); return ''; } } diff --git a/src/Contract/ApplicationInterface.php b/src/Contract/ApplicationInterface.php index 97bc515a..f0952a60 100644 --- a/src/Contract/ApplicationInterface.php +++ b/src/Contract/ApplicationInterface.php @@ -82,7 +82,7 @@ public function command(string $name, string|Closure|CommandInterface|null $hand /** * @return void */ - public function showCommandList(); + public function showCommandList(): void; /** * @return string From 1c370b40796196593c3de81004d95764806fa3ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 20:02:38 +0000 Subject: [PATCH 258/258] chore(deps): bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/php.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 4a4acce3..1cc5ce27 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set ENV vars # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 82491007..d36a5a18 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0