diff --git a/library/think/Console.php b/library/think/Console.php index 7859974f..b0700bd8 100644 --- a/library/think/Console.php +++ b/library/think/Console.php @@ -9,20 +9,14 @@ namespace think; -use think\console\command\Command; +use think\console\Command; use think\console\command\Help as HelpCommand; -use think\console\helper\Debug as DebugFormatterHelper; -use think\console\helper\Formatter as FormatterHelper; -use think\console\helper\Process as ProcessHelper; -use think\console\helper\Question as QuestionHelper; -use think\console\helper\Set as HelperSet; use think\console\Input; use think\console\input\Argument as InputArgument; use think\console\input\Definition as InputDefinition; use think\console\input\Option as InputOption; use think\console\Output; -use think\console\output\Nothing; -use think\console\output\Stream; +use think\console\output\driver\Buffer; class Console { @@ -35,14 +29,9 @@ class Console private $wantHelps = false; - /** @var Command */ - private $runningCommand; - private $catchExceptions = true; private $autoExit = true; private $definition; - private $helperSet; - private $terminalDimensions; private $defaultCommand; private static $defaultCommands = [ @@ -63,7 +52,6 @@ class Console $this->version = $version; $this->defaultCommand = 'list'; - $this->helperSet = $this->getDefaultHelperSet(); $this->definition = $this->getDefaultInputDefinition(); foreach ($this->getDefaultCommands() as $command) { @@ -98,15 +86,24 @@ class Console } } + /** + * @param $command + * @param array $parameters + * @return Output|Buffer + */ public static function call($command, array $parameters = []) { $console = self::init(false); array_unshift($parameters, $command); - $input = new Input($parameters); + $input = new Input($parameters); + $output = new Output('buffer'); - $console->find($command)->run($input, new Nothing()); + $console->setCatchExceptions(false); + $console->find($command)->run($input, $output); + + return $output; } /** @@ -129,11 +126,11 @@ class Console throw $e; } - $this->renderException($e, $output->getErrorOutput()); + $output->renderException($e); $exitCode = $e->getCode(); if (is_numeric($exitCode)) { - $exitCode = (int) $exitCode; + $exitCode = (int)$exitCode; if (0 === $exitCode) { $exitCode = 1; } @@ -155,8 +152,8 @@ class Console /** * 执行指令 - * @param Input $input - * @param Output $output + * @param Input $input + * @param Output $output * @return int */ public function doRun(Input $input, Output $output) @@ -185,31 +182,11 @@ class Console $command = $this->find($name); - $this->runningCommand = $command; - $exitCode = $this->doRunCommand($command, $input, $output); - $this->runningCommand = null; + $exitCode = $this->doRunCommand($command, $input, $output); return $exitCode; } - /** - * 设置助手集 - * @param HelperSet $helperSet - */ - public function setHelperSet(HelperSet $helperSet) - { - $this->helperSet = $helperSet; - } - - /** - * 获取助手集 - * @return HelperSet - */ - public function getHelperSet() - { - return $this->helperSet; - } - /** * 设置输入参数定义 * @param InputDefinition $definition @@ -244,7 +221,7 @@ class Console */ public function setCatchExceptions($boolean) { - $this->catchExceptions = (bool) $boolean; + $this->catchExceptions = (bool)$boolean; } /** @@ -254,7 +231,7 @@ class Console */ public function setAutoExit($boolean) { - $this->autoExit = (bool) $boolean; + $this->autoExit = (bool)$boolean; } /** @@ -422,7 +399,7 @@ class Console $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]) . '[^:]*'; }, $namespace); - $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces); + $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces); if (empty($namespaces)) { $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); @@ -460,7 +437,7 @@ class Console $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]) . '[^:]*'; }, $name); - $commands = preg_grep('{^' . $expr . '}', $allCommands); + $commands = preg_grep('{^' . $expr . '}', $allCommands); if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) { if (false !== $pos = strrpos($name, ':')) { @@ -547,144 +524,14 @@ class Console */ public function renderException(\Exception $e, Stream $output) { - do { - $title = sprintf(' [%s] ', get_class($e)); - $len = $this->stringWidth($title); - - $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX; - - if (defined('HHVM_VERSION') && $width > 1 << 31) { - $width = 1 << 31; - } - $formatter = $output->getFormatter(); - $lines = []; - foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { - foreach ($this->splitStringByWidth($line, $width - 4) as $line) { - - $lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $formatter->format($line))) + 4; - $lines[] = [$line, $lineLength]; - - $len = max($lineLength, $len); - } - } - - $messages = ['', '']; - $messages[] = $emptyLine = $formatter->format(sprintf('%s', str_repeat(' ', $len))); - $messages[] = $formatter->format(sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title))))); - foreach ($lines as $line) { - $messages[] = $formatter->format(sprintf(' %s %s', $line[0], str_repeat(' ', $len - $line[1]))); - } - $messages[] = $emptyLine; - $messages[] = ''; - $messages[] = ''; - - $output->writeln($messages, Output::OUTPUT_RAW); - - if (Output::VERBOSITY_VERBOSE <= $output->getVerbosity()) { - $output->writeln('Exception trace:'); - - // exception related properties - $trace = $e->getTrace(); - array_unshift($trace, [ - 'function' => '', - 'file' => $e->getFile() !== null ? $e->getFile() : 'n/a', - 'line' => $e->getLine() !== null ? $e->getLine() : 'n/a', - 'args' => [], - ]); - - for ($i = 0, $count = count($trace); $i < $count; ++$i) { - $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; - $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; - $function = $trace[$i]['function']; - $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; - $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; - - $output->writeln(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line)); - } - - $output->writeln(''); - $output->writeln(''); - } - } while ($e = $e->getPrevious()); - - if (null !== $this->runningCommand) { - $output->writeln(sprintf('%s', sprintf($this->runningCommand->getSynopsis(), $this->getName()))); - $output->writeln(''); - $output->writeln(''); - } } - /** - * 获取终端宽度 - * @return int|null - */ - protected function getTerminalWidth() - { - $dimensions = $this->getTerminalDimensions(); - - return $dimensions[0]; - } - - /** - * 获取终端高度 - * @return int|null - */ - protected function getTerminalHeight() - { - $dimensions = $this->getTerminalDimensions(); - - return $dimensions[1]; - } - - /** - * 获取当前终端的尺寸 - * @return array - */ - public function getTerminalDimensions() - { - if ($this->terminalDimensions) { - return $this->terminalDimensions; - } - - if ('\\' === DS) { - if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) { - return [(int) $matches[1], (int) $matches[2]]; - } - if (preg_match('/^(\d+)x(\d+)$/', $this->getConsoleMode(), $matches)) { - return [(int) $matches[1], (int) $matches[2]]; - } - } - - if ($sttyString = $this->getSttyColumns()) { - if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { - return [(int) $matches[2], (int) $matches[1]]; - } - if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { - return [(int) $matches[2], (int) $matches[1]]; - } - } - - return [null, null]; - } - - /** - * 设置终端尺寸 - * @param int $width - * @param int $height - * @return Console - */ - public function setTerminalDimensions($width, $height) - { - $this->terminalDimensions = [$width, $height]; - - return $this; - } /** * 配置基于用户的参数和选项的输入和输出实例。 - * @param Input $input 输入实例 - * @param Output $output 输出实例 + * @param Input $input 输入实例 + * @param Output $output 输出实例 */ protected function configureIO(Input $input, Output $output) { @@ -718,9 +565,9 @@ class Console /** * 执行指令 - * @param Command $command 指令实例 - * @param Input $input 输入实例 - * @param Output $output 输出实例 + * @param Command $command 指令实例 + * @param Input $input 输入实例 + * @param Output $output 输出实例 * @return int * @throws \Exception */ @@ -779,68 +626,6 @@ class Console self::$defaultCommands = array_merge(self::$defaultCommands, $classnames); } - /** - * 设置默认助手 - * @return HelperSet - */ - protected function getDefaultHelperSet() - { - return new HelperSet([ - new FormatterHelper(), - new DebugFormatterHelper(), - new ProcessHelper(), - new QuestionHelper(), - ]); - } - - /** - * 获取stty列数 - * @return string - */ - private function getSttyColumns() - { - if (!function_exists('proc_open')) { - return null; - } - - $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; - $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); - if (is_resource($process)) { - $info = stream_get_contents($pipes[1]); - fclose($pipes[1]); - fclose($pipes[2]); - proc_close($process); - - return $info; - } - return null; - } - - /** - * 获取终端模式 - * @return string x 或 null - */ - private function getConsoleMode() - { - if (!function_exists('proc_open')) { - return null; - } - - $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; - $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); - if (is_resource($process)) { - $info = stream_get_contents($pipes[1]); - fclose($pipes[1]); - fclose($pipes[2]); - proc_close($process); - - if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { - return $matches[2] . 'x' . $matches[1]; - } - } - return null; - } - /** * 获取可能的建议 * @param array $abbrevs @@ -924,49 +709,6 @@ class Console $this->defaultCommand = $commandName; } - private function stringWidth($string) - { - if (!function_exists('mb_strwidth')) { - return strlen($string); - } - - if (false === $encoding = mb_detect_encoding($string)) { - return strlen($string); - } - - return mb_strwidth($string, $encoding); - } - - private function splitStringByWidth($string, $width) - { - if (!function_exists('mb_strwidth')) { - return str_split($string, $width); - } - - if (false === $encoding = mb_detect_encoding($string)) { - return str_split($string, $width); - } - - $utf8String = mb_convert_encoding($string, 'utf8', $encoding); - $lines = []; - $line = ''; - foreach (preg_split('//u', $utf8String) as $char) { - if (mb_strwidth($line . $char, 'utf8') <= $width) { - $line .= $char; - continue; - } - $lines[] = str_pad($line, $width); - $line = $char; - } - if (strlen($line)) { - $lines[] = count($lines) ? str_pad($line, $width) : $line; - } - - mb_convert_variables($encoding, 'utf8', $lines); - - return $lines; - } - /** * 返回所有的命名空间 * @param string $name diff --git a/library/think/console/command/Command.php b/library/think/console/Command.php similarity index 92% rename from library/think/console/command/Command.php rename to library/think/console/Command.php index 82237593..fd3a16d5 100644 --- a/library/think/console/command/Command.php +++ b/library/think/console/Command.php @@ -9,15 +9,12 @@ // | Author: yunwuxin <448901948@qq.com> // +---------------------------------------------------------------------- -namespace think\console\command; +namespace think\console; use think\Console; -use think\console\Input; use think\console\input\Argument; use think\console\input\Definition; -use think\console\helper\Set as HelperSet; use think\console\input\Option; -use think\console\Output; class Command { @@ -36,8 +33,11 @@ class Command private $synopsis = []; private $usages = []; - /** @var HelperSet */ - private $helperSet; + /** @var Input */ + protected $input; + + /** @var Output */ + protected $output; /** * 构造方法 @@ -75,29 +75,6 @@ class Command public function setConsole(Console $console = null) { $this->console = $console; - if ($console) { - $this->setHelperSet($console->getHelperSet()); - } else { - $this->helperSet = null; - } - } - - /** - * 设置帮助集 - * @param HelperSet $helperSet - */ - public function setHelperSet(HelperSet $helperSet) - { - $this->helperSet = $helperSet; - } - - /** - * 获取帮助集 - * @return HelperSet - */ - public function getHelperSet() - { - return $this->helperSet; } /** @@ -455,6 +432,7 @@ class Command /** * 添加用法介绍 * @param string $usage + * @return $this */ public function addUsage($usage) { @@ -476,17 +454,6 @@ class Command return $this->usages; } - /** - * 获取助手 - * @param string $name - * @return mixed - * @throws \InvalidArgumentException - */ - public function getHelper($name) - { - return $this->helperSet->get($name); - } - /** * 验证指令名称 * @param string $name diff --git a/library/think/console/Output.php b/library/think/console/Output.php index 44eba7a5..38fe9051 100644 --- a/library/think/console/Output.php +++ b/library/think/console/Output.php @@ -11,43 +11,52 @@ namespace think\console; -use think\console\output\Formatter; -use think\console\output\Stream; +use Exception; +use think\console\output\Descriptor; +use think\console\output\driver\Buffer; +use think\console\output\driver\Console; +use think\console\output\driver\Nothing; -class Output extends Stream +class Output { + const VERBOSITY_QUIET = 0; + const VERBOSITY_NORMAL = 1; + const VERBOSITY_VERBOSE = 2; + const VERBOSITY_VERY_VERBOSE = 3; + const VERBOSITY_DEBUG = 4; - /** @var Stream */ - private $stderr; + const OUTPUT_NORMAL = 0; + const OUTPUT_RAW = 1; + const OUTPUT_PLAIN = 2; - public function __construct() + private $verbosity = self::VERBOSITY_NORMAL; + + /** @var Buffer|Console|Nothing */ + private $handle = null; + + public function __construct($driver = 'console') { - $outputStream = 'php://stdout'; - if (!$this->hasStdoutSupport()) { - $outputStream = 'php://output'; - } + $class = '\\think\\console\\output\\driver\\' . ucwords($driver); - parent::__construct(fopen($outputStream, 'w')); + $this->handle = new $class($this); + } - $this->stderr = new Stream(fopen('php://stderr', 'w'), $this->getFormatter()); + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + $this->write($messages, true, $type); } /** * {@inheritdoc} */ - public function setDecorated($decorated) + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) { - parent::setDecorated($decorated); - $this->stderr->setDecorated($decorated); + $this->handle->write($messages, $newline, $type); } - /** - * {@inheritdoc} - */ - public function setFormatter(Formatter $formatter) + public function renderException(\Exception $e) { - parent::setFormatter($formatter); - $this->stderr->setFormatter($formatter); + $this->handle->renderException($e); } /** @@ -55,32 +64,54 @@ class Output extends Stream */ public function setVerbosity($level) { - parent::setVerbosity($level); - $this->stderr->setVerbosity($level); + $this->verbosity = (int)$level; } /** * {@inheritdoc} */ - public function getErrorOutput() + public function getVerbosity() { - return $this->stderr; + return $this->verbosity; } - /** - * {@inheritdoc} - */ - public function setErrorOutput(Output $error) + public function isQuiet() { - $this->stderr = $error; + return self::VERBOSITY_QUIET === $this->verbosity; } - /** - * 检查当前环境是否支持控制台输出写入标准输出。 - * @return bool - */ - protected function hasStdoutSupport() + public function isVerbose() { - return ('OS400' != php_uname('s')); + return self::VERBOSITY_VERBOSE <= $this->verbosity; } + + public function isVeryVerbose() + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + public function isDebug() + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + public function describe($object, array $options = []) + { + $descriptor = new Descriptor(); + $options = array_merge([ + 'raw_text' => false + ], $options); + + $descriptor->describe($this, $object, $options); + } + + public function __call($method, $args) + { + if ($this->handle && method_exists($this->handle, $method)) { + return call_user_func_array([$this->handle, $method], $args); + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } + } \ No newline at end of file diff --git a/library/think/console/command/Build.php b/library/think/console/command/Build.php index 9c86060c..0523ac93 100644 --- a/library/think/console/command/Build.php +++ b/library/think/console/command/Build.php @@ -11,6 +11,7 @@ namespace think\console\command; +use think\console\Command; use think\console\Input; use think\console\input\Option; use think\console\Output; diff --git a/library/think/console/command/Clear.php b/library/think/console/command/Clear.php index 072ce6ec..60f3a478 100644 --- a/library/think/console/command/Clear.php +++ b/library/think/console/command/Clear.php @@ -10,7 +10,7 @@ // +---------------------------------------------------------------------- namespace think\console\command; -use think\console\command\Command; +use think\console\Command; use think\console\Input; use think\console\input\Option; use think\console\Output; diff --git a/library/think/console/command/Help.php b/library/think/console/command/Help.php index a7bcaade..eb0858a3 100644 --- a/library/think/console/command/Help.php +++ b/library/think/console/command/Help.php @@ -11,6 +11,7 @@ namespace think\console\command; +use think\console\Command; use think\console\Input; use think\console\input\Argument as InputArgument; use think\console\input\Option as InputOption; @@ -60,9 +61,7 @@ EOF $this->command = $this->getConsole()->find($input->getArgument('command_name')); } - - $helper = new DescriptorHelper(); - $helper->describe($output, $this->command, [ + $output->describe($this->command, [ 'raw_text' => $input->getOption('raw'), ]); diff --git a/library/think/console/command/Lists.php b/library/think/console/command/Lists.php index 0eac10d5..ffbee07c 100644 --- a/library/think/console/command/Lists.php +++ b/library/think/console/command/Lists.php @@ -11,13 +11,12 @@ namespace think\console\command; - +use think\console\Command; use think\console\Input; use think\console\Output; use think\console\input\Argument as InputArgument; use think\console\input\Option as InputOption; use think\console\input\Definition as InputDefinition; -use think\console\helper\Descriptor as DescriptorHelper; class Lists extends Command { @@ -56,9 +55,7 @@ EOF */ protected function execute(Input $input, Output $output) { - - $helper = new DescriptorHelper(); - $helper->describe($output, $this->getConsole(), [ + $output->describe($this->getConsole(), [ 'raw_text' => $input->getOption('raw'), 'namespace' => $input->getArgument('namespace'), ]); diff --git a/library/think/console/command/Make.php b/library/think/console/command/Make.php index 2829ee3c..74b06053 100644 --- a/library/think/console/command/Make.php +++ b/library/think/console/command/Make.php @@ -12,6 +12,7 @@ namespace think\console\command; use think\Config; +use think\console\Command; use think\console\Input; use think\console\input\Argument; use think\console\Output; diff --git a/library/think/console/command/optimize/Autoload.php b/library/think/console/command/optimize/Autoload.php index 8079c1b4..1fa28a44 100644 --- a/library/think/console/command/optimize/Autoload.php +++ b/library/think/console/command/optimize/Autoload.php @@ -11,14 +11,12 @@ namespace think\console\command\optimize; use think\App; -use think\console\command\Command; +use think\console\Command; use think\console\Input; use think\console\Output; class Autoload extends Command { - /** @var Output */ - protected $output; protected function configure() { @@ -28,7 +26,6 @@ class Autoload extends Command protected function execute(Input $input, Output $output) { - $this->output = $output; $classmapFile = << -// +---------------------------------------------------------------------- - -namespace think\console\helper; - -class Debug extends Helper -{ - - private $colors = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white']; - private $started = []; - private $count = -1; - - /** - * 启动调试会话的格式 - * @param string $id 会话的 id - * @param string $message 要显示的消息 - * @param string $prefix 要使用的前缀 - * @return string - */ - public function start($id, $message, $prefix = 'RUN') - { - $this->started[$id] = ['border' => ++$this->count % count($this->colors)]; - - return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); - } - - /** - * 添加设置会话的进度条格式 - * @param string $id 会话的 id - * @param string $buffer 要显示的消息 - * @param bool $error 是否输出错误 - * @param string $prefix 输出的前缀 - * @param string $errorPrefix 输出错误的前缀 - * @return string - */ - public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR') - { - $message = ''; - - if ($error) { - if (isset($this->started[$id]['out'])) { - $message .= "\n"; - unset($this->started[$id]['out']); - } - if (!isset($this->started[$id]['err'])) { - $message .= sprintf("%s %s ", $this->getBorder($id), $errorPrefix); - $this->started[$id]['err'] = true; - } - - $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $errorPrefix), $buffer); - } else { - if (isset($this->started[$id]['err'])) { - $message .= "\n"; - unset($this->started[$id]['err']); - } - if (!isset($this->started[$id]['out'])) { - $message .= sprintf("%s %s ", $this->getBorder($id), $prefix); - $this->started[$id]['out'] = true; - } - - $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $prefix), $buffer); - } - - return $message; - } - - /** - * 停止一个会话 - * @param string $id 会话的 id - * @param string $message 要显示的消息 - * @param bool $successful 是否显示成功消息 - * @param string $prefix 前缀 - * @return string - */ - public function stop($id, $message, $successful, $prefix = 'RES') - { - $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; - - if ($successful) { - return sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); - } - - $message = sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); - - unset($this->started[$id]['out'], $this->started[$id]['err']); - - return $message; - } - - /** - * @param string $id - * @return string - */ - private function getBorder($id) - { - return sprintf(' ', $this->colors[$this->started[$id]['border']]); - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'debug_formatter'; - } -} \ No newline at end of file diff --git a/library/think/console/helper/Descriptor.php b/library/think/console/helper/Descriptor.php deleted file mode 100644 index 18c0eab8..00000000 --- a/library/think/console/helper/Descriptor.php +++ /dev/null @@ -1,54 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\helper; - -use think\console\helper\descriptor\Descriptor as OutputDescriptor; -use think\console\Output; - -class Descriptor extends Helper -{ - - /** - * @var OutputDescriptor - */ - private $descriptor; - - /** - * 构造方法 - */ - public function __construct() - { - $this->descriptor = new OutputDescriptor(); - } - - /** - * 描述 - * @param Output $output - * @param object $object - * @param array $options - * @throws \InvalidArgumentException - */ - public function describe(Output $output, $object, array $options = []) - { - $options = array_merge([ - 'raw_text' => false - ], $options); - - $this->descriptor->describe($output, $object, $options); - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'descriptor'; - } -} diff --git a/library/think/console/helper/Formatter.php b/library/think/console/helper/Formatter.php deleted file mode 100644 index 18ce5742..00000000 --- a/library/think/console/helper/Formatter.php +++ /dev/null @@ -1,74 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\helper; - -use think\console\output\Formatter as OutputFormatter; - -class Formatter extends Helper -{ - - /** - * 设置消息在某一节的格式 - * @param string $section 节名称 - * @param string $message 消息 - * @param string $style 样式 - * @return string - */ - public function formatSection($section, $message, $style = 'info') - { - return sprintf('<%s>[%s] %s', $style, $section, $style, $message); - } - - /** - * 设置消息作为文本块的格式 - * @param string|array $messages 消息 - * @param string $style 样式 - * @param bool $large 是否返回一个大段文本 - * @return string The formatter message - */ - public function formatBlock($messages, $style, $large = false) - { - if (!is_array($messages)) { - $messages = [$messages]; - } - - $len = 0; - $lines = []; - foreach ($messages as $message) { - $message = OutputFormatter::escape($message); - $lines[] = sprintf($large ? ' %s ' : ' %s ', $message); - $len = max($this->strlen($message) + ($large ? 4 : 2), $len); - } - - $messages = $large ? [str_repeat(' ', $len)] : []; - for ($i = 0; isset($lines[$i]); ++$i) { - $messages[] = $lines[$i] . str_repeat(' ', $len - $this->strlen($lines[$i])); - } - if ($large) { - $messages[] = str_repeat(' ', $len); - } - - for ($i = 0; isset($messages[$i]); ++$i) { - $messages[$i] = sprintf('<%s>%s', $style, $messages[$i], $style); - } - - return implode("\n", $messages); - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'formatter'; - } -} \ No newline at end of file diff --git a/library/think/console/helper/Helper.php b/library/think/console/helper/Helper.php deleted file mode 100644 index 7b327bdd..00000000 --- a/library/think/console/helper/Helper.php +++ /dev/null @@ -1,121 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\helper; - -use think\console\helper\Set as HelperSet; -use think\console\output\Formatter; - -abstract class Helper -{ - - protected $helperSet = null; - - /** - * 设置与此助手关联的助手集。 - * @param HelperSet $helperSet - */ - public function setHelperSet(HelperSet $helperSet = null) - { - $this->helperSet = $helperSet; - } - - /** - * 获取与此助手关联的助手集。 - * @return HelperSet - */ - public function getHelperSet() - { - return $this->helperSet; - } - - /** - * 获取名称 - * @return string - */ - abstract public function getName(); - - /** - * 返回字符串的长度 - * @param string $string - * @return int - */ - public static function strlen($string) - { - if (!function_exists('mb_strwidth')) { - return strlen($string); - } - - if (false === $encoding = mb_detect_encoding($string)) { - return strlen($string); - } - - return mb_strwidth($string, $encoding); - } - - public static function formatTime($secs) - { - static $timeFormats = [ - [0, '< 1 sec'], - [2, '1 sec'], - [59, 'secs', 1], - [60, '1 min'], - [3600, 'mins', 60], - [5400, '1 hr'], - [86400, 'hrs', 3600], - [129600, '1 day'], - [604800, 'days', 86400], - ]; - - foreach ($timeFormats as $format) { - if ($secs >= $format[0]) { - continue; - } - - if (2 == count($format)) { - return $format[1]; - } - - return ceil($secs / $format[2]) . ' ' . $format[1]; - } - return null; - } - - public static function formatMemory($memory) - { - if ($memory >= 1024 * 1024 * 1024) { - return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024); - } - - if ($memory >= 1024 * 1024) { - return sprintf('%.1f MiB', $memory / 1024 / 1024); - } - - if ($memory >= 1024) { - return sprintf('%d KiB', $memory / 1024); - } - - return sprintf('%d B', $memory); - } - - public static function strlenWithoutDecoration(Formatter $formatter, $string) - { - $isDecorated = $formatter->isDecorated(); - $formatter->setDecorated(false); - // remove <...> formatting - $string = $formatter->format($string); - // remove already formatted characters - $string = preg_replace("/\033\[[^m]*m/", '', $string); - $formatter->setDecorated($isDecorated); - - return self::strlen($string); - } -} diff --git a/library/think/console/helper/Process.php b/library/think/console/helper/Process.php deleted file mode 100644 index 4252bfe3..00000000 --- a/library/think/console/helper/Process.php +++ /dev/null @@ -1,118 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\helper; - -use think\console\Output; -use think\Process as ThinkProcess; -use think\process\Builder as ProcessBuilder; -use think\process\exception\Failed as ProcessFailedException; - -class Process extends Helper -{ - - /** - * 运行一个外部进程。 - * @param Output $output 一个Output实例 - * @param string|array|ThinkProcess $cmd 指令 - * @param string|null $error 错误信息 - * @param callable|null $callback 回调 - * @param int $verbosity - * @return ThinkProcess - */ - public function run(Output $output, $cmd, $error = null, $callback = null, $verbosity = Output::VERBOSITY_VERY_VERBOSE) - { - /** @var Debug $formatter */ - $formatter = $this->getHelperSet()->get('debug_formatter'); - - if (is_array($cmd)) { - $process = ProcessBuilder::create($cmd)->getProcess(); - } elseif ($cmd instanceof ThinkProcess) { - $process = $cmd; - } else { - $process = new ThinkProcess($cmd); - } - - if ($verbosity <= $output->getVerbosity()) { - $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine()))); - } - - if ($output->isDebug()) { - $callback = $this->wrapCallback($output, $process, $callback); - } - - $process->run($callback); - - if ($verbosity <= $output->getVerbosity()) { - $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode()); - $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful())); - } - - if (!$process->isSuccessful() && null !== $error) { - $output->writeln(sprintf('%s', $this->escapeString($error))); - } - - return $process; - } - - /** - * 运行指令 - * @param Output $output - * @param string|ThinkProcess $cmd - * @param string|null $error - * @param callable|null $callback - * @return ThinkProcess - */ - public function mustRun(Output $output, $cmd, $error = null, $callback = null) - { - $process = $this->run($output, $cmd, $error, $callback); - - if (!$process->isSuccessful()) { - throw new ProcessFailedException($process); - } - - return $process; - } - - /** - * 包装过程回调来添加调试输出 - * @param Output $output - * @param ThinkProcess $process - * @param callable|null $callback - * @return callable - */ - public function wrapCallback(Output $output, ThinkProcess $process, $callback = null) - { - /** @var Debug $formatter */ - $formatter = $this->getHelperSet()->get('debug_formatter'); - - return function ($type, $buffer) use ($output, $process, $callback, $formatter) { - $output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), ThinkProcess::ERR === $type)); - - if (null !== $callback) { - call_user_func($callback, $type, $buffer); - } - }; - } - - private function escapeString($str) - { - return str_replace('<', '\\<', $str); - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'process'; - } -} diff --git a/library/think/console/helper/Question.php b/library/think/console/helper/Question.php deleted file mode 100644 index e1820834..00000000 --- a/library/think/console/helper/Question.php +++ /dev/null @@ -1,394 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\helper; - -use think\console\helper\question\Choice as ChoiceQuestion; -use think\console\helper\question\Question as OutputQuestion; -use think\console\Input; -use think\console\Output; -use think\console\output\formatter\Style as OutputFormatterStyle; - -class Question extends Helper -{ - - private $inputStream; - private static $shell; - private static $stty; - - /** - * 向用户提问 - * @param Input $input - * @param Output $output - * @param OutputQuestion $question - * @return string - */ - public function ask(Input $input, Output $output, OutputQuestion $question) - { - if (!$input->isInteractive()) { - return $question->getDefault(); - } - - if (!$question->getValidator()) { - return $this->doAsk($output, $question); - } - - $interviewer = function () use ($output, $question) { - return $this->doAsk($output, $question); - }; - - return $this->validateAttempts($interviewer, $output, $question); - } - - /** - * 设置输入流 - * @param resource $stream - * @throws \InvalidArgumentException - */ - public function setInputStream($stream) - { - if (!is_resource($stream)) { - throw new \InvalidArgumentException('Input stream must be a valid resource.'); - } - - $this->inputStream = $stream; - } - - /** - * 获取输入流 - * @return resource - */ - public function getInputStream() - { - return $this->inputStream; - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'question'; - } - - /** - * 提问 - * @param Output $output - * @param OutputQuestion $question - * @return bool|mixed|null|string - * @throws \Exception - * @throws \RuntimeException - */ - private function doAsk(Output $output, OutputQuestion $question) - { - $this->writePrompt($output, $question); - - $inputStream = $this->inputStream ?: STDIN; - $autocomplete = $question->getAutocompleterValues(); - - if (null === $autocomplete || !$this->hasSttyAvailable()) { - $ret = false; - if ($question->isHidden()) { - try { - $ret = trim($this->getHiddenResponse($output, $inputStream)); - } catch (\RuntimeException $e) { - if (!$question->isHiddenFallback()) { - throw $e; - } - } - } - - if (false === $ret) { - $ret = fgets($inputStream, 4096); - if (false === $ret) { - throw new \RuntimeException('Aborted'); - } - $ret = trim($ret); - } - } else { - $ret = trim($this->autocomplete($output, $question, $inputStream)); - } - - $ret = strlen($ret) > 0 ? $ret : $question->getDefault(); - - if ($normalizer = $question->getNormalizer()) { - return $normalizer($ret); - } - - return $ret; - } - - /** - * 显示提示 - * @param Output $output - * @param OutputQuestion $question - */ - protected function writePrompt(Output $output, OutputQuestion $question) - { - $message = $question->getQuestion(); - - if ($question instanceof ChoiceQuestion) { - $width = max(array_map('strlen', array_keys($question->getChoices()))); - - $messages = (array) $question->getQuestion(); - foreach ($question->getChoices() as $key => $value) { - $messages[] = sprintf(" [%-${width}s] %s", $key, $value); - } - - $output->writeln($messages); - - $message = $question->getPrompt(); - } - - $output->write($message); - } - - /** - * 输出错误 - * @param Output $output - * @param \Exception $error - */ - protected function writeError(Output $output, \Exception $error) - { - if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) { - $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'); - } else { - $message = '' . $error->getMessage() . ''; - } - - $output->writeln($message); - } - - /** - * 自动完成问题 - * @param Output $output - * @param OutputQuestion $question - * @param $inputStream - * @return string - */ - private function autocomplete(Output $output, OutputQuestion $question, $inputStream) - { - $autocomplete = $question->getAutocompleterValues(); - $ret = ''; - - $i = 0; - $ofs = -1; - $matches = $autocomplete; - $numMatches = count($matches); - - $sttyMode = shell_exec('stty -g'); - - shell_exec('stty -icanon -echo'); - - $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); - - while (!feof($inputStream)) { - $c = fread($inputStream, 1); - - if ("\177" === $c) { - if (0 === $numMatches && 0 !== $i) { - $i--; - $output->write("\033[1D"); - } - - if (0 === $i) { - $ofs = -1; - $matches = $autocomplete; - $numMatches = count($matches); - } else { - $numMatches = 0; - } - - $ret = substr($ret, 0, $i); - } elseif ("\033" === $c) { - $c .= fread($inputStream, 2); - - if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { - if ('A' === $c[2] && -1 === $ofs) { - $ofs = 0; - } - - if (0 === $numMatches) { - continue; - } - - $ofs += ('A' === $c[2]) ? -1 : 1; - $ofs = ($numMatches + $ofs) % $numMatches; - } - } elseif (ord($c) < 32) { - if ("\t" === $c || "\n" === $c) { - if ($numMatches > 0 && -1 !== $ofs) { - $ret = $matches[$ofs]; - $output->write(substr($ret, $i)); - $i = strlen($ret); - } - - if ("\n" === $c) { - $output->write($c); - break; - } - - $numMatches = 0; - } - - continue; - } else { - $output->write($c); - $ret .= $c; - $i++; - - $numMatches = 0; - $ofs = 0; - - foreach ($autocomplete as $value) { - if (0 === strpos($value, $ret) && strlen($value) !== $i) { - $matches[$numMatches++] = $value; - } - } - } - - $output->write("\033[K"); - - if ($numMatches > 0 && -1 !== $ofs) { - $output->write("\0337"); - $output->write('' . substr($matches[$ofs], $i) . ''); - $output->write("\0338"); - } - } - - shell_exec(sprintf('stty %s', $sttyMode)); - - return $ret; - } - - /** - * 从用户获取隐藏的响应 - * @param Output $output - * @return string - * @throws \RuntimeException - */ - private function getHiddenResponse(Output $output, $inputStream) - { - if ('\\' === DS) { - $exe = __DIR__ . '/../bin/hiddeninput.exe'; - - if ('phar:' === substr(__FILE__, 0, 5)) { - $tmpExe = sys_get_temp_dir() . '/hiddeninput.exe'; - copy($exe, $tmpExe); - $exe = $tmpExe; - } - - $value = rtrim(shell_exec($exe)); - $output->writeln(''); - - if (isset($tmpExe)) { - unlink($tmpExe); - } - - return $value; - } - - if ($this->hasSttyAvailable()) { - $sttyMode = shell_exec('stty -g'); - - shell_exec('stty -echo'); - $value = fgets($inputStream, 4096); - shell_exec(sprintf('stty %s', $sttyMode)); - - if (false === $value) { - throw new \RuntimeException('Aborted'); - } - - $value = trim($value); - $output->writeln(''); - - return $value; - } - - if (false !== $shell = $this->getShell()) { - $readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword'; - $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); - $value = rtrim(shell_exec($command)); - $output->writeln(''); - - return $value; - } - - throw new \RuntimeException('Unable to hide the response.'); - } - - /** - * 验证重试次数 - * @param callable $interviewer - * @param Output $output - * @param OutputQuestion $question - * @return string - * @throws null - */ - private function validateAttempts($interviewer, Output $output, OutputQuestion $question) - { - $error = null; - $attempts = $question->getMaxAttempts(); - while (null === $attempts || $attempts--) { - if (null !== $error) { - $this->writeError($output, $error); - } - - try { - return call_user_func($question->getValidator(), $interviewer()); - } catch (\Exception $error) { - } - } - - throw $error; - } - - /** - * 获取一个有效的 unix 终端。 - * @return string|bool - */ - private function getShell() - { - if (null !== self::$shell) { - return self::$shell; - } - - self::$shell = false; - - if (file_exists('/usr/bin/env')) { - // handle other OSs with bash/zsh/ksh/csh if available to hide the answer - $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; - foreach (['bash', 'zsh', 'ksh', 'csh'] as $sh) { - if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { - self::$shell = $sh; - break; - } - } - } - - return self::$shell; - } - - /** - * 检查有用的stty - * @return bool - */ - private function hasSttyAvailable() - { - if (null !== self::$stty) { - return self::$stty; - } - - exec('stty 2>&1', $output, $exitcode); - - return self::$stty = 0 === $exitcode; - } -} diff --git a/library/think/console/helper/Set.php b/library/think/console/helper/Set.php deleted file mode 100644 index 63ea58e4..00000000 --- a/library/think/console/helper/Set.php +++ /dev/null @@ -1,99 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\helper; - -use think\console\command\Command; - -class Set implements \IteratorAggregate -{ - - private $helpers = []; - private $command; - - /** - * 构造方法 - * @param Helper[] $helpers 助手实例数组 - */ - public function __construct(array $helpers = []) - { - /** - * @var int|string $alias - * @var Helper $helper - */ - foreach ($helpers as $alias => $helper) { - $this->set($helper, is_int($alias) ? null : $alias); - } - } - - /** - * 添加一个助手 - * @param Helper $helper 助手实例 - * @param string $alias 别名 - */ - public function set(Helper $helper, $alias = null) - { - $this->helpers[$helper->getName()] = $helper; - if (null !== $alias) { - $this->helpers[$alias] = $helper; - } - - $helper->setHelperSet($this); - } - - /** - * 是否有某个助手 - * @param string $name 助手名称 - * @return bool - */ - public function has($name) - { - return isset($this->helpers[$name]); - } - - /** - * 获取助手 - * @param string $name 助手名称 - * @return Helper - * @throws \InvalidArgumentException - */ - public function get($name) - { - if (!$this->has($name)) { - throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); - } - - return $this->helpers[$name]; - } - - /** - * 设置与这个助手关联的命令集 - * @param Command $command - */ - public function setCommand(Command $command = null) - { - $this->command = $command; - } - - /** - * 获取与这个助手关联的命令集 - * @return Command - */ - public function getCommand() - { - return $this->command; - } - - public function getIterator() - { - return new \ArrayIterator($this->helpers); - } -} diff --git a/library/think/console/helper/descriptor/Descriptor.php b/library/think/console/output/Descriptor.php similarity index 98% rename from library/think/console/helper/descriptor/Descriptor.php rename to library/think/console/output/Descriptor.php index b9a96d87..fff31e6b 100644 --- a/library/think/console/helper/descriptor/Descriptor.php +++ b/library/think/console/output/Descriptor.php @@ -9,15 +9,15 @@ // | Author: yunwuxin <448901948@qq.com> // +---------------------------------------------------------------------- -namespace think\console\helper\descriptor; +namespace think\console\output; use think\console\Output; use think\console\input\Argument as InputArgument; use think\console\input\Option as InputOption; use think\console\input\Definition as InputDefinition; -use think\console\command\Command; +use think\console\Command; use think\Console; -use think\console\helper\descriptor\Console as ConsoleDescription; +use think\console\output\descriptor\Console as ConsoleDescription; class Descriptor { diff --git a/library/think/console/output/Nothing.php b/library/think/console/output/Nothing.php deleted file mode 100644 index c1550270..00000000 --- a/library/think/console/output/Nothing.php +++ /dev/null @@ -1,108 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\output; - -use think\console\Output; - -class Nothing extends Output -{ - /** @noinspection PhpMissingParentConstructorInspection */ - public function __construct() - { - - } - - /** - * {@inheritdoc} - */ - public function setFormatter(Formatter $formatter) - { - // do nothing - } - - /** - * {@inheritdoc} - */ - public function getFormatter() - { - // to comply with the interface we must return a OutputFormatterInterface - return new Formatter(); - } - - /** - * {@inheritdoc} - */ - public function setDecorated($decorated) - { - // do nothing - } - - /** - * {@inheritdoc} - */ - public function isDecorated() - { - return false; - } - - /** - * {@inheritdoc} - */ - public function setVerbosity($level) - { - // do nothing - } - - /** - * {@inheritdoc} - */ - public function getVerbosity() - { - return self::VERBOSITY_QUIET; - } - - public function isQuiet() - { - return true; - } - - public function isVerbose() - { - return false; - } - - public function isVeryVerbose() - { - return false; - } - - public function isDebug() - { - return false; - } - - /** - * {@inheritdoc} - */ - public function writeln($messages, $options = self::OUTPUT_NORMAL) - { - // do nothing - } - - /** - * {@inheritdoc} - */ - public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL) - { - // do nothing - } -} \ No newline at end of file diff --git a/library/think/console/output/Stream.php b/library/think/console/output/Stream.php deleted file mode 100644 index fec94bdf..00000000 --- a/library/think/console/output/Stream.php +++ /dev/null @@ -1,195 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\output; - -class Stream -{ - - const VERBOSITY_QUIET = 0; - const VERBOSITY_NORMAL = 1; - const VERBOSITY_VERBOSE = 2; - const VERBOSITY_VERY_VERBOSE = 3; - const VERBOSITY_DEBUG = 4; - - const OUTPUT_NORMAL = 0; - const OUTPUT_RAW = 1; - const OUTPUT_PLAIN = 2; - - private $verbosity = self::VERBOSITY_NORMAL; - private $formatter; - - - private $stream; - - /** - * 构造方法 - * @param $stream - * @param Formatter $formatter - */ - public function __construct($stream, Formatter $formatter = null) - { - if (!is_resource($stream) || 'stream' !== get_resource_type($stream)) { - throw new \InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); - } - - $this->stream = $stream; - - $decorated = $this->hasColorSupport(); - - $this->formatter = $formatter ?: new Formatter(); - $this->formatter->setDecorated($decorated); - } - - /** - * {@inheritdoc} - */ - public function setFormatter(Formatter $formatter) - { - $this->formatter = $formatter; - } - - /** - * {@inheritdoc} - */ - public function getFormatter() - { - return $this->formatter; - } - - /** - * {@inheritdoc} - */ - public function setDecorated($decorated) - { - $this->formatter->setDecorated($decorated); - } - - /** - * {@inheritdoc} - */ - public function isDecorated() - { - return $this->formatter->isDecorated(); - } - - /** - * {@inheritdoc} - */ - public function setVerbosity($level) - { - $this->verbosity = (int)$level; - } - - /** - * {@inheritdoc} - */ - public function getVerbosity() - { - return $this->verbosity; - } - - public function isQuiet() - { - return self::VERBOSITY_QUIET === $this->verbosity; - } - - public function isVerbose() - { - return self::VERBOSITY_VERBOSE <= $this->verbosity; - } - - public function isVeryVerbose() - { - return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; - } - - public function isDebug() - { - return self::VERBOSITY_DEBUG <= $this->verbosity; - } - - /** - * {@inheritdoc} - */ - public function writeln($messages, $type = self::OUTPUT_NORMAL) - { - $this->write($messages, true, $type); - } - - /** - * {@inheritdoc} - */ - public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) - { - if (self::VERBOSITY_QUIET === $this->verbosity) { - return; - } - - $messages = (array)$messages; - - foreach ($messages as $message) { - switch ($type) { - case self::OUTPUT_NORMAL: - $message = $this->formatter->format($message); - break; - case self::OUTPUT_RAW: - break; - case self::OUTPUT_PLAIN: - $message = strip_tags($this->formatter->format($message)); - break; - default: - throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type)); - } - - $this->doWrite($message, $newline); - } - } - - /** - * 将消息写入到输出。 - * @param string $message 消息 - * @param bool $newline 是否另起一行 - */ - protected function doWrite($message, $newline) - { - if (false === @fwrite($this->stream, $message . ($newline ? PHP_EOL : ''))) { - throw new \RuntimeException('Unable to write output.'); - } - - fflush($this->stream); - } - - /** - * @return resource - */ - public function getStream() - { - return $this->stream; - } - - /** - * 是否支持着色 - * @return bool - */ - protected function hasColorSupport() - { - if (DIRECTORY_SEPARATOR === '\\') { - return - 0 >= version_compare('10.0.10586', PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD) - || false !== getenv('ANSICON') - || 'ON' === getenv('ConEmuANSI') - || 'xterm' === getenv('TERM'); - } - - return function_exists('posix_isatty') && @posix_isatty($this->stream); - } -} \ No newline at end of file diff --git a/library/think/console/helper/descriptor/Console.php b/library/think/console/output/descriptor/Console.php similarity index 96% rename from library/think/console/helper/descriptor/Console.php rename to library/think/console/output/descriptor/Console.php index 168c31fd..60867ec7 100644 --- a/library/think/console/helper/descriptor/Console.php +++ b/library/think/console/output/descriptor/Console.php @@ -9,10 +9,10 @@ // | Author: yunwuxin <448901948@qq.com> // +---------------------------------------------------------------------- -namespace think\console\helper\descriptor; +namespace think\console\output\descriptor; -use think\console\command\Command; +use think\console\Command; use think\Console as ThinkConsole; class Console @@ -132,7 +132,7 @@ class Console foreach ($commands as $name => $command) { $key = $this->console->extractNamespace($name, 1); if (!$key) { - $key = '_global'; + $key = self::GLOBAL_NAMESPACE; } $namespacedCommands[$key][$name] = $command; diff --git a/library/think/console/output/driver/Buffer.php b/library/think/console/output/driver/Buffer.php new file mode 100644 index 00000000..a720732a --- /dev/null +++ b/library/think/console/output/driver/Buffer.php @@ -0,0 +1,53 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + + +use think\console\Output; + +class Buffer +{ + /** + * @var string + */ + private $buffer = ''; + + public function __construct(Output $output) + { + // do nothing + } + + public function fetch() + { + $content = $this->buffer; + $this->buffer = ''; + return $content; + } + + public function write($messages, $newline = false, $options = Output::OUTPUT_NORMAL) + { + $messages = (array)$messages; + + foreach ($messages as $message) { + $this->buffer .= $message; + } + if ($newline) { + $this->buffer .= "\n"; + } + } + + public function renderException(\Exception $e) + { + // do nothing + } + +} \ No newline at end of file diff --git a/library/think/console/output/driver/Console.php b/library/think/console/output/driver/Console.php new file mode 100644 index 00000000..7134b3f1 --- /dev/null +++ b/library/think/console/output/driver/Console.php @@ -0,0 +1,366 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; +use think\console\output\Formatter; + +class Console +{ + + /** @var Resource */ + private $stdout; + + /** @var Formatter */ + private $formatter; + + private $terminalDimensions; + + /** @var Output */ + private $output; + + + public function __construct(Output $output) + { + $this->output = $output; + $this->formatter = new Formatter(); + $this->stdout = $this->openOutputStream(); + $decorated = $this->hasColorSupport($this->stdout); + $this->formatter->setDecorated($decorated); + } + + public function write($messages, $newline = false, $type = Output::OUTPUT_NORMAL, $stream = null) + { + if (Output::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + $messages = (array)$messages; + + foreach ($messages as $message) { + switch ($type) { + case Output::OUTPUT_NORMAL: + $message = $this->formatter->format($message); + break; + case Output::OUTPUT_RAW: + break; + case Output::OUTPUT_PLAIN: + $message = strip_tags($this->formatter->format($message)); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type)); + } + + $this->doWrite($message, $newline, $stream); + } + } + + public function renderException(\Exception $e) + { + $stderr = $this->openErrorStream(); + $decorated = $this->hasColorSupport($stderr); + $this->formatter->setDecorated($decorated); + + do { + $title = sprintf(' [%s] ', get_class($e)); + + $len = $this->stringWidth($title); + + $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX; + + if (defined('HHVM_VERSION') && $width > 1 << 31) { + $width = 1 << 31; + } + $lines = []; + foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { + foreach ($this->splitStringByWidth($line, $width - 4) as $line) { + + $lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $line)) + 4; + $lines[] = [$line, $lineLength]; + + $len = max($lineLength, $len); + } + } + + $messages = ['', '']; + $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); + $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title)))); + foreach ($lines as $line) { + $messages[] = sprintf(' %s %s', $line[0], str_repeat(' ', $len - $line[1])); + } + $messages[] = $emptyLine; + $messages[] = ''; + $messages[] = ''; + + $this->write($messages, true, Output::OUTPUT_NORMAL, $stderr); + + if (Output::VERBOSITY_VERBOSE <= $this->output->getVerbosity()) { + $this->write('Exception trace:', true, Output::OUTPUT_NORMAL, $stderr); + + // exception related properties + $trace = $e->getTrace(); + array_unshift($trace, [ + 'function' => '', + 'file' => $e->getFile() !== null ? $e->getFile() : 'n/a', + 'line' => $e->getLine() !== null ? $e->getLine() : 'n/a', + 'args' => [], + ]); + + for ($i = 0, $count = count($trace); $i < $count; ++$i) { + $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; + $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; + $function = $trace[$i]['function']; + $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; + $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; + + $this->write(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line), true, Output::OUTPUT_NORMAL, $stderr); + } + + $this->write('', true, Output::OUTPUT_NORMAL, $stderr); + $this->write('', true, Output::OUTPUT_NORMAL, $stderr); + } + } while ($e = $e->getPrevious()); + + } + + + /** + * 获取终端宽度 + * @return int|null + */ + protected function getTerminalWidth() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[0]; + } + + /** + * 获取终端高度 + * @return int|null + */ + protected function getTerminalHeight() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[1]; + } + + /** + * 获取当前终端的尺寸 + * @return array + */ + public function getTerminalDimensions() + { + if ($this->terminalDimensions) { + return $this->terminalDimensions; + } + + if ('\\' === DS) { + if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) { + return [(int)$matches[1], (int)$matches[2]]; + } + if (preg_match('/^(\d+)x(\d+)$/', $this->getMode(), $matches)) { + return [(int)$matches[1], (int)$matches[2]]; + } + } + + if ($sttyString = $this->getSttyColumns()) { + if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { + return [(int)$matches[2], (int)$matches[1]]; + } + if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { + return [(int)$matches[2], (int)$matches[1]]; + } + } + + return [null, null]; + } + + /** + * 获取stty列数 + * @return string + */ + private function getSttyColumns() + { + if (!function_exists('proc_open')) { + return null; + } + + $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + return $info; + } + return null; + } + + /** + * 获取终端模式 + * @return string x 或 null + */ + private function getMode() + { + if (!function_exists('proc_open')) { + return null; + } + + $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { + return $matches[2] . 'x' . $matches[1]; + } + } + return null; + } + + private function stringWidth($string) + { + if (!function_exists('mb_strwidth')) { + return strlen($string); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + private function splitStringByWidth($string, $width) + { + if (!function_exists('mb_strwidth')) { + return str_split($string, $width); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return str_split($string, $width); + } + + $utf8String = mb_convert_encoding($string, 'utf8', $encoding); + $lines = []; + $line = ''; + foreach (preg_split('//u', $utf8String) as $char) { + if (mb_strwidth($line . $char, 'utf8') <= $width) { + $line .= $char; + continue; + } + $lines[] = str_pad($line, $width); + $line = $char; + } + if (strlen($line)) { + $lines[] = count($lines) ? str_pad($line, $width) : $line; + } + + mb_convert_variables($encoding, 'utf8', $lines); + + return $lines; + } + + private function isRunningOS400() + { + $checks = [ + function_exists('php_uname') ? php_uname('s') : '', + getenv('OSTYPE'), + PHP_OS, + ]; + return false !== stripos(implode(';', $checks), 'OS400'); + } + + /** + * 当前环境是否支持写入控制台输出到stdout. + * + * @return bool + */ + protected function hasStdoutSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * 当前环境是否支持写入控制台输出到stderr. + * + * @return bool + */ + protected function hasStderrSupport() + { + return false === $this->isRunningOS400(); + } + + + /** + * @return resource + */ + private function openOutputStream() + { + if (!$this->hasStdoutSupport()) { + return fopen('php://output', 'w'); + } + return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w'); + } + + /** + * @return resource + */ + private function openErrorStream() + { + return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w'); + } + + /** + * 将消息写入到输出。 + * @param string $message 消息 + * @param bool $newline 是否另起一行 + * @param null $stream + */ + protected function doWrite($message, $newline, $stream = null) + { + if (null === $stream) { + $stream = $this->stdout; + } + if (false === @fwrite($stream, $message . ($newline ? PHP_EOL : ''))) { + throw new \RuntimeException('Unable to write output.'); + } + + fflush($stream); + } + + /** + * 是否支持着色 + * @param $stream + * @return bool + */ + protected function hasColorSupport($stream) + { + if (DIRECTORY_SEPARATOR === '\\') { + return + 0 >= version_compare('10.0.10586', PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD) + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + return function_exists('posix_isatty') && @posix_isatty($stream); + } + +} \ No newline at end of file diff --git a/library/think/console/output/driver/Nothing.php b/library/think/console/output/driver/Nothing.php new file mode 100644 index 00000000..673521d7 --- /dev/null +++ b/library/think/console/output/driver/Nothing.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; + +class Nothing +{ + + public function __construct(Output $output) + { + // do nothing + } + + public function write($messages, $newline = false, $options = Output::OUTPUT_NORMAL) + { + // do nothing + } + + public function renderException(\Exception $e) + { + // do nothing + } +} \ No newline at end of file diff --git a/library/think/console/helper/question/Choice.php b/library/think/console/output/question/Choice.php similarity index 100% rename from library/think/console/helper/question/Choice.php rename to library/think/console/output/question/Choice.php diff --git a/library/think/console/helper/question/Confirmation.php b/library/think/console/output/question/Confirmation.php similarity index 100% rename from library/think/console/helper/question/Confirmation.php rename to library/think/console/output/question/Confirmation.php diff --git a/library/think/console/helper/question/Question.php b/library/think/console/output/question/Question.php similarity index 100% rename from library/think/console/helper/question/Question.php rename to library/think/console/output/question/Question.php