diff --git a/src/Bean/Annotation/Command.php b/src/Bean/Annotation/Command.php index 04054ee..f175998 100644 --- a/src/Bean/Annotation/Command.php +++ b/src/Bean/Annotation/Command.php @@ -15,6 +15,11 @@ class Command */ private $name = ''; + /** + * @var bool + */ + private $enabled = true; + /** * @var bool */ @@ -35,12 +40,19 @@ public function __construct(array $values) if (isset($values['value'])) { $this->name = $values['value']; } + if (isset($values['name'])) { $this->name = $values['name']; } + if (isset($values['coroutine'])) { $this->coroutine = $values['coroutine']; } + + if (isset($values['enabled'])) { + $this->enabled = (bool)$values['enabled']; + } + if (isset($values['server'])) { $this->server = $values['server']; } @@ -69,4 +81,12 @@ public function isServer(): bool { return $this->server; } -} \ No newline at end of file + + /** + * @return bool + */ + public function isEnabled(): bool + { + return $this->enabled; + } +} diff --git a/src/Bean/Collector/CommandCollector.php b/src/Bean/Collector/CommandCollector.php index d9e9b90..19b3b82 100644 --- a/src/Bean/Collector/CommandCollector.php +++ b/src/Bean/Collector/CommandCollector.php @@ -20,7 +20,7 @@ class CommandCollector implements CollectorInterface * collect * * @param string $className - * @param object $objectAnnotation + * @param mixed $objectAnnotation * @param string $propertyName * @param string $methodName * @param null $propertyValue @@ -54,6 +54,7 @@ private static function collectCommand(string $className, Command $objectAnnotat $server = $objectAnnotation->isServer(); self::$commandMapping[$className]['name'] = $commandName; + self::$commandMapping[$className]['enabled'] = $objectAnnotation->isEnabled(); self::$commandMapping[$className]['coroutine'] = $coroutine; self::$commandMapping[$className]['server'] = $server; } @@ -94,4 +95,4 @@ public static function getCollector(): array { return self::$commandMapping; } -} \ No newline at end of file +} diff --git a/src/Bean/Parser/CommandParser.php b/src/Bean/Parser/CommandParser.php index 8b923a3..ba731cb 100644 --- a/src/Bean/Parser/CommandParser.php +++ b/src/Bean/Parser/CommandParser.php @@ -19,20 +19,21 @@ class CommandParser extends AbstractParser { /** - * @param string $className + * @param string $className * @param Command $objectAnnotation - * @param string $propertyName - * @param string $methodName + * @param string $propertyName + * @param string $methodName * + * @param null $propertyValue * @return mixed */ - public function parser(string $className, $objectAnnotation = null, string $propertyName = "", string $methodName = "", $propertyValue = null) + public function parser(string $className, $objectAnnotation = null, string $propertyName = '', string $methodName = '', $propertyValue = null) { $beanName = $className; $scope = Scope::SINGLETON; CommandCollector::collect($className, $objectAnnotation, $propertyName, $methodName, $propertyValue); - return [$beanName, $scope, ""]; + return [$beanName, $scope, '']; } -} \ No newline at end of file +} diff --git a/src/Bean/Parser/MappingParser.php b/src/Bean/Parser/MappingParser.php index 1860e40..7135745 100644 --- a/src/Bean/Parser/MappingParser.php +++ b/src/Bean/Parser/MappingParser.php @@ -22,15 +22,16 @@ class MappingParser extends AbstractParser * @param Mapping $objectAnnotation * @param string $propertyName * @param string $methodName + * @param null $propertyValue * @return void */ public function parser( string $className, $objectAnnotation = null, - string $propertyName = "", - string $methodName = "", + string $propertyName = '', + string $methodName = '', $propertyValue = null ) { CommandCollector::collect($className, $objectAnnotation, $propertyName, $methodName, $propertyValue); } -} \ No newline at end of file +} diff --git a/src/Command.php b/src/Command.php index e18ced2..1b5120d 100644 --- a/src/Command.php +++ b/src/Command.php @@ -6,7 +6,6 @@ use Swoft\Bean\Annotation\Bean; use Swoft\Console\Bean\Collector\CommandCollector; use Swoft\Console\Helper\DocBlockHelper; -use Swoft\Helper\DocumentHelper; use Swoft\Console\Router\HandlerAdapter; use Swoft\Console\Router\HandlerMapping; @@ -37,6 +36,7 @@ public function annotationVars(): array /** * @return void + * @throws \InvalidArgumentException * @throws \ReflectionException */ public function run() @@ -55,7 +55,7 @@ public function run() list($className, $method) = $handler; if ($router->isDefaultCommand($method)) { - $this->indexComamnd($className); + $this->indexCommand($className); return; } @@ -77,7 +77,7 @@ public function run() * @throws \ReflectionException * @return void */ - private function indexComamnd(string $className) + private function indexCommand(string $className) { /* @var HandlerMapping $router */ $router = App::getBean('commandRoute'); @@ -87,7 +87,7 @@ private function indexComamnd(string $className) $reflectionClass = new \ReflectionClass($className); $classDocument = $reflectionClass->getDocComment(); - $classDocAry = DocumentHelper::tagList($classDocument); + $classDocAry = DocBlockHelper::getTags($classDocument); $classDesc = $classDocAry['Description']; $methodCommands = []; @@ -106,7 +106,7 @@ private function indexComamnd(string $className) } $reflectionMethod = $reflectionClass->getMethod($methodName); $methodDocument = $reflectionMethod->getDocComment(); - $methodDocAry = DocumentHelper::tagList($methodDocument); + $methodDocAry = DocBlockHelper::getTags($methodDocument); $methodCommands[$mappedName] = $methodDocAry['Description']; } @@ -137,7 +137,6 @@ private function showCommandHelp(string $controllerClass, string $commandMethod) $reflectionMethod = $reflectionClass->getMethod($commandMethod); $document = $reflectionMethod->getDocComment(); $document = $this->parseAnnotationVars($document, $this->annotationVars()); - // $docs = DocumentHelper::tagList($document); $docs = DocBlockHelper::getTags($document); $commands = []; @@ -173,7 +172,7 @@ private function showCommandHelp(string $controllerClass, string $commandMethod) } /** - * help list + * show all commands for the console app * * @throws \ReflectionException */ @@ -182,19 +181,19 @@ private function showCommandList() $commands = $this->parserCmdAndDesc(); $commandList = []; - $script = input()->getFullScript(); - $commandList['Usage:'] = ["php $script"]; + $script = \input()->getFullScript(); + $commandList['Usage:'] = ["php $script {command} [arguments] [options]"]; $commandList['Commands:'] = $commands; $commandList['Options:'] = [ - '-h, --help' => 'show help information', - '-v, --version' => 'show version', + '-h, --help' => 'Display help information', + '-v, --version' => 'Display version information', ]; // show logo - output()->writeLogo(); + \output()->writeLogo(); // output list - output()->writeList($commandList, 'comment', 'info'); + \output()->writeList($commandList, 'comment', 'info'); } /** @@ -208,9 +207,11 @@ private function showVersion() $swooleVersion = SWOOLE_VERSION; // 显示面板 - output()->writeLogo(); - output()->writeln("swoft: $swoftVersion, php: $phpVersion, swoole: $swooleVersion", true); - output()->writeln(''); + \output()->writeLogo(); + \output()->writeln( + "swoft: $swoftVersion, php: $phpVersion, swoole: $swooleVersion\n", + true + ); } /** @@ -227,17 +228,23 @@ private function parserCmdAndDesc(): array /* @var \Swoft\Console\Router\HandlerMapping $route */ $route = App::getBean('commandRoute'); - foreach ($collector as $className => $comamnd) { + foreach ($collector as $className => $command) { + if (!$command['enabled']) { + continue; + } + $rc = new \ReflectionClass($className); $docComment = $rc->getDocComment(); - $docAry = DocumentHelper::tagList($docComment); - $desc = $docAry['Description']; + $docAry = DocBlockHelper::getTags($docComment); - $prefix = $comamnd['name']; + $prefix = $command['name']; $prefix = $route->getPrefix($prefix, $className); - $commands[$prefix] = $desc; + $commands[$prefix] = \ucfirst($docAry['Description']); } + // sort commands + ksort($commands); + return $commands; } @@ -258,26 +265,6 @@ private function baseCommand() $this->showCommandList(); } - /** - * 解析命令key和描述 - * - * @param string $document 注解文档 - * @return array - */ - private function parserKeyAndDesc(string $document): array - { - $keyAndDesc = []; - $items = explode("\n", $document); - foreach ($items as $item) { - $pos = strpos($item, ' '); - $key = substr($item, 0, $pos); - $desc = substr($item, $pos + 1); - $keyAndDesc[$key] = $desc; - } - - return $keyAndDesc; - } - /** * 替换注解中的变量为对应的值 * @param string $str diff --git a/src/Console.php b/src/Console.php index 1d9e374..0b592ce 100644 --- a/src/Console.php +++ b/src/Console.php @@ -30,8 +30,8 @@ public function run() $command = App::getBean('command'); $command->run(); } catch (\Throwable $e) { - output()->writeln(sprintf('%s', $e->getMessage()), true, false); - output()->writeln(sprintf('%s', $e->getTraceAsString()), true, true); + \output()->writeln(sprintf('%s', $e->getMessage()), true, false); + \output()->writeln(sprintf("Trace:\n%s", $e->getTraceAsString()), true, true); } } diff --git a/src/Helper/CommandHelper.php b/src/Helper/CommandHelper.php index 4d18fd0..b68f3b6 100644 --- a/src/Helper/CommandHelper.php +++ b/src/Helper/CommandHelper.php @@ -178,4 +178,49 @@ public static function nextIsValue($val): bool // it isn't option or named argument return $val{0} !== '-' && false === strpos($val, '='); } + + + /** + * Returns true if STDOUT supports colorization. + * This code has been copied and adapted from + * \Symfony\Component\Console\Output\OutputStream. + * @return boolean + */ + public static function supportColor(): bool + { + if (DIRECTORY_SEPARATOR === '\\') { + return + '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD || + // 0 == strpos(PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . PHP_WINDOWS_VERSION_BUILD, '10.') || + false !== getenv('ANSICON') || + 'ON' === getenv('ConEmuANSI') || + 'xterm' === getenv('TERM')// || 'cygwin' === getenv('TERM') + ; + } + + if (!\defined('STDOUT')) { + return false; + } + + return self::isInteractive(STDOUT); + } + + /** + * @return bool + */ + public static function isSupport256Color(): bool + { + return DIRECTORY_SEPARATOR === '/' && strpos(getenv('TERM'), '256color') !== false; + } + + /** + * Returns if the file descriptor is an interactive terminal or not. + * @param int|resource $fileDescriptor + * @return boolean + */ + public static function isInteractive($fileDescriptor): bool + { + return \function_exists('posix_isatty') && @posix_isatty($fileDescriptor); + } + } diff --git a/src/Helper/ConsoleUtil.php b/src/Helper/ConsoleUtil.php index 8ec323d..7f354cd 100644 --- a/src/Helper/ConsoleUtil.php +++ b/src/Helper/ConsoleUtil.php @@ -14,6 +14,79 @@ */ class ConsoleUtil { + /** + * 与文本进度条相比,没有 total + * + * ```php + * $total = 120; + * $ctt = ConsoleUtil::counterTxt('doing ...', 'completed.'); + * $this->write('Counter:'); + * while ($total - 1) { + * $ctt->send(1); + * usleep(30000); + * $total--; + * } + * // end of the counter. + * $ctt->send(-1); + * ``` + * @param string $msg + * @param string|null $doneMsg + * @return \Generator + */ + public static function counterTxt(string $msg, $doneMsg = null) + { + $counter = 0; + $finished = false; + $tpl = (CommandHelper::supportColor() ? "\x0D\x1B[2K" : "\x0D\r") . '%d %s'; + $msg = style()->t($msg); + $doneMsg = $doneMsg ? style()->t($doneMsg) : null; + while (true) { + if ($finished) { + return; + } + + $step = yield; + + if ((int)$step <= 0) { + $counter++; + $finished = true; + $msg = $doneMsg ?: $msg; + } else { + $counter += $step; + } + + printf($tpl, $counter, $msg); + + if ($finished) { + echo "\n"; + break; + } + } + + yield false; + } + + /** + * read CLI input + * @param mixed $message + * @param bool $nl + * @param array $opts + * [ + * 'stream' => \STDIN + * ] + * @return string + */ + public static function read($message = null, $nl = false, array $opts = []): string + { + if ($message) { + \output()->writeln($message, $nl); + } + + $stream = $opts['stream'] ?? \STDIN; + + return trim(fgets($stream)); + } + /** * 确认, 发出信息要求确认 * @param string $question 发出的信息 diff --git a/src/Output/Output.php b/src/Output/Output.php index 2163560..dea714f 100644 --- a/src/Output/Output.php +++ b/src/Output/Output.php @@ -23,14 +23,18 @@ class Output implements OutputInterface /** * 输出一行数据 * - * @param string $messages 信息 + * @param string|array $messages 信息 * @param bool $newline 是否换行 * @param bool $quit 是否退出 */ public function writeln($messages = '', $newline = true, $quit = false) { + if (\is_array($messages)) { + $messages = \implode($newline ? PHP_EOL : '', $messages); + } + // 文字里面颜色标签翻译 - $messages = \style()->t($messages); + $messages = \style()->t((string)$messages); // 输出文字 echo $messages; @@ -40,7 +44,7 @@ public function writeln($messages = '', $newline = true, $quit = false) // 是否退出 if ($quit) { - exit(); + exit; } } @@ -49,14 +53,23 @@ public function writeln($messages = '', $newline = true, $quit = false) */ public function writeLogo() { - $logo = " + $logo = " ____ __ _ / ___|_ _____ / _| |_ \___ \ \ /\ / / _ \| |_| __| ___) \ V V / (_) | _| |_ |____/ \_/\_/ \___/|_| \__| -"; - $this->writeln($logo); +"; + $this->colored(' ' . \ltrim($logo)); + } + + /** + * @param string $text + * @param string $tag + */ + public function colored(string $text, string $tag = 'info') + { + $this->writeln(\sprintf('<%s>%s', $tag, $text, $tag)); } /** @@ -98,7 +111,7 @@ private function writeItems(array $items, string $cmdStyle) // 命令和描述 $maxLength = $this->getCmdMaxLength(array_keys($items)); - $cmd = str_pad($cmd, $maxLength, ' '); + $cmd = \str_pad($cmd, $maxLength, ' '); $cmd = "<$cmdStyle>$cmd"; $message = self::LEFT_CHAR . $cmd . self::GAP_CHAR . $desc; diff --git a/src/Router/HandlerAdapter.php b/src/Router/HandlerAdapter.php index bc064bd..2bf021f 100644 --- a/src/Router/HandlerAdapter.php +++ b/src/Router/HandlerAdapter.php @@ -84,7 +84,7 @@ private function getBindParams(string $className, string $methodName): array /** * execute command by coroutine * - * @param object $class + * @param mixed $class * @param string $method * @param bool $server * @param array $bindParams @@ -92,7 +92,7 @@ private function getBindParams(string $className, string $methodName): array private function executeCommandByCoroutine($class, string $method, bool $server, $bindParams) { Coroutine::create(function () use ($class, $method, $server, $bindParams) { - $this->beforeCommand(get_parent_class($class), $method, $server); + $this->beforeCommand(\get_parent_class($class), $method, $server); PhpHelper::call([$class, $method], $bindParams); $this->afterCommand($method, $server); }); @@ -101,14 +101,14 @@ private function executeCommandByCoroutine($class, string $method, bool $server, /** * execute command * - * @param object $class + * @param mixed $class * @param string $method * @param bool $server * @param array $bindParams */ private function executeCommand($class, string $method, bool $server, $bindParams) { - $this->beforeCommand(get_parent_class($class), $method, $server); + $this->beforeCommand(\get_parent_class($class), $method, $server); PhpHelper::call([$class, $method], $bindParams); $this->afterCommand($method, $server); } @@ -177,4 +177,4 @@ private function bootstrap() } } } -} \ No newline at end of file +} diff --git a/src/Router/HandlerMapping.php b/src/Router/HandlerMapping.php index 2c25087..5679156 100644 --- a/src/Router/HandlerMapping.php +++ b/src/Router/HandlerMapping.php @@ -32,7 +32,7 @@ class HandlerMapping implements HandlerMappingInterface /** * the default command */ - private $deaultCommand = 'index'; + private $defaultCommand = 'index'; /** * the delimiter @@ -83,7 +83,7 @@ public function register(array $commandMapping) */ public function isDefaultCommand(string $comamnd): bool { - return $comamnd === $this->deaultCommand; + return $comamnd === $this->defaultCommand; } /** @@ -109,7 +109,7 @@ private function getGroupAndCommand(): array } if (empty($command)) { - $command = $this->deaultCommand; + $command = $this->defaultCommand; } return [$group, $command]; @@ -153,8 +153,8 @@ private function registerRoute(string $className, array $routes, string $prefix, $this->routes[$commandKey] = [$className, $methodName, $coroutine, $server]; } - $commandKey = $this->getCommandString($prefix, $this->deaultCommand); - $this->routes[$commandKey] = [$className, $this->deaultCommand]; + $commandKey = $this->getCommandString($prefix, $this->defaultCommand); + $this->routes[$commandKey] = [$className, $this->defaultCommand]; } /** @@ -191,4 +191,4 @@ private function getCommandString(string $group, string $command): string { return sprintf('%s%s%s', $group, $this->delimiter, $command); } -} \ No newline at end of file +} diff --git a/src/Style/Color.php b/src/Style/Color.php index c5f5be1..157c596 100644 --- a/src/Style/Color.php +++ b/src/Style/Color.php @@ -93,7 +93,7 @@ final class Color * @return Color * @throws \InvalidArgumentException */ - public static function make(string $fg = '', string $bg = '', array $options = []) + public static function make(string $fg = '', string $bg = '', array $options = []): Color { return new self($fg, $bg, $options); } @@ -112,7 +112,7 @@ private function __construct(string $fg = '', string $bg = '', array $options = $fgNotExist = ! empty($fg) && ! array_key_exists($fg, self::COLORS); $bgNotExist = ! empty($bg) && ! array_key_exists($bg, self::COLORS); if ($fgNotExist || $bgNotExist) { - throw new \InvalidArgumentException("Foreground和Background参数值,不存在,检查后再试!"); + throw new \InvalidArgumentException('Foreground和Background参数值,不存在,检查后再试!'); } // 前景色 @@ -128,7 +128,7 @@ private function __construct(string $fg = '', string $bg = '', array $options = foreach ($options as $option) { // 选项不存在 if (! array_key_exists($option, self::OPTIONS)) { - throw new \InvalidArgumentException("选项参数不存在,option=" . $option); + throw new \InvalidArgumentException('选项参数不存在,option=' . $option); } $this->options[] = self::OPTIONS[$option]; }