初始化项目

This commit is contained in:
2016-06-21 17:12:08 +08:00
commit 7ea154d684
903 changed files with 226100 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
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<bg=blue;fg=white> %s </> <fg=blue>%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<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix);
$this->started[$id]['err'] = true;
}
$message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %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<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix);
$this->started[$id]['out'] = true;
}
$message .= str_replace("\n", sprintf("\n%s<bg=green;fg=white> %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<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
}
$message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%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('<bg=%s> </>', $this->colors[$this->started[$id]['border']]);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'debug_formatter';
}
}

View File

@@ -0,0 +1,54 @@
<?php
// +----------------------------------------------------------------------
// | TopThink [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2015 http://www.topthink.com All rights reserved.
// +----------------------------------------------------------------------
// | Author: zhangyajun <448901948@qq.com>
// +----------------------------------------------------------------------
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';
}
}

View File

@@ -0,0 +1,74 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
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> %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</%s>', $style, $messages[$i], $style);
}
return implode("\n", $messages);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'formatter';
}
}

View File

@@ -0,0 +1,121 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
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);
}
}

View File

@@ -0,0 +1,118 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
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('<error>%s</error>', $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';
}
}

View File

@@ -0,0 +1,394 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
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(" [<info>%-${width}s</info>] %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>' . $error->getMessage() . '</error>';
}
$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('<hl>' . substr($matches[$ofs], $i) . '</hl>');
$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;
}
}

View File

@@ -0,0 +1,99 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
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);
}
}

View File

@@ -0,0 +1,150 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\helper\descriptor;
use think\console\command\Command;
use think\Console as ThinkConsole;
class Console
{
const GLOBAL_NAMESPACE = '_global';
/**
* @var ThinkConsole
*/
private $console;
/**
* @var null|string
*/
private $namespace;
/**
* @var array
*/
private $namespaces;
/**
* @var Command[]
*/
private $commands;
/**
* @var Command[]
*/
private $aliases;
/**
* 构造方法
* @param ThinkConsole $console
* @param string|null $namespace
*/
public function __construct(ThinkConsole $console, $namespace = null)
{
$this->console = $console;
$this->namespace = $namespace;
}
/**
* @return array
*/
public function getNamespaces()
{
if (null === $this->namespaces) {
$this->inspectConsole();
}
return $this->namespaces;
}
/**
* @return Command[]
*/
public function getCommands()
{
if (null === $this->commands) {
$this->inspectConsole();
}
return $this->commands;
}
/**
* @param string $name
* @return Command
* @throws \InvalidArgumentException
*/
public function getCommand($name)
{
if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name));
}
return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name];
}
private function inspectConsole()
{
$this->commands = [];
$this->namespaces = [];
$all = $this->console->all($this->namespace ? $this->console->findNamespace($this->namespace) : null);
foreach ($this->sortCommands($all) as $namespace => $commands) {
$names = [];
/** @var Command $command */
foreach ($commands as $name => $command) {
if (!$command->getName()) {
continue;
}
if ($command->getName() === $name) {
$this->commands[$name] = $command;
} else {
$this->aliases[$name] = $command;
}
$names[] = $name;
}
$this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names];
}
}
/**
* @param array $commands
* @return array
*/
private function sortCommands(array $commands)
{
$namespacedCommands = [];
foreach ($commands as $name => $command) {
$key = $this->console->extractNamespace($name, 1);
if (!$key) {
$key = '_global';
}
$namespacedCommands[$key][$name] = $command;
}
ksort($namespacedCommands);
foreach ($namespacedCommands as &$commandsSet) {
ksort($commandsSet);
}
// unset reference to keep scope clear
unset($commandsSet);
return $namespacedCommands;
}
}

View File

@@ -0,0 +1,319 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\helper\descriptor;
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;
use think\console\helper\descriptor\Console as ConsoleDescription;
class Descriptor
{
/**
* @var Output
*/
protected $output;
/**
* {@inheritdoc}
*/
public function describe(Output $output, $object, array $options = [])
{
$this->output = $output;
switch (true) {
case $object instanceof InputArgument:
$this->describeInputArgument($object, $options);
break;
case $object instanceof InputOption:
$this->describeInputOption($object, $options);
break;
case $object instanceof InputDefinition:
$this->describeInputDefinition($object, $options);
break;
case $object instanceof Command:
$this->describeCommand($object, $options);
break;
case $object instanceof Console:
$this->describeConsole($object, $options);
break;
default:
throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object)));
}
}
/**
* 输出内容
* @param string $content
* @param bool $decorated
*/
protected function write($content, $decorated = false)
{
$this->output->write($content, false, $decorated ? Output::OUTPUT_NORMAL : Output::OUTPUT_RAW);
}
/**
* 描述参数
* @param InputArgument $argument
* @param array $options
* @return string|mixed
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
if (null !== $argument->getDefault()
&& (!is_array($argument->getDefault())
|| count($argument->getDefault()))
) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
} else {
$default = '';
}
$totalWidth = isset($options['total_width']) ? $options['total_width'] : strlen($argument->getName());
$spacingWidth = $totalWidth - strlen($argument->getName()) + 2;
$this->writeText(sprintf(" <info>%s</info>%s%s%s", $argument->getName(), str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + <info> + </info> + 2 spaces
preg_replace('/\s*\R\s*/', PHP_EOL . str_repeat(' ', $totalWidth + 17), $argument->getDescription()), $default), $options);
}
/**
* 描述选项
* @param InputOption $option
* @param array $options
* @return string|mixed
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
if ($option->acceptValue() && null !== $option->getDefault()
&& (!is_array($option->getDefault())
|| count($option->getDefault()))
) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
} else {
$default = '';
}
$value = '';
if ($option->acceptValue()) {
$value = '=' . strtoupper($option->getName());
if ($option->isValueOptional()) {
$value = '[' . $value . ']';
}
}
$totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions([$option]);
$synopsis = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', sprintf('--%s%s', $option->getName(), $value));
$spacingWidth = $totalWidth - strlen($synopsis) + 2;
$this->writeText(sprintf(" <info>%s</info>%s%s%s%s", $synopsis, str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + <info> + </info> + 2 spaces
preg_replace('/\s*\R\s*/', "\n" . str_repeat(' ', $totalWidth + 17), $option->getDescription()), $default, $option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''), $options);
}
/**
* 描述输入
* @param InputDefinition $definition
* @param array $options
* @return string|mixed
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
$totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
foreach ($definition->getArguments() as $argument) {
$totalWidth = max($totalWidth, strlen($argument->getName()));
}
if ($definition->getArguments()) {
$this->writeText('<comment>Arguments:</comment>', $options);
$this->writeText("\n");
foreach ($definition->getArguments() as $argument) {
$this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
$this->writeText("\n");
}
}
if ($definition->getArguments() && $definition->getOptions()) {
$this->writeText("\n");
}
if ($definition->getOptions()) {
$laterOptions = [];
$this->writeText('<comment>Options:</comment>', $options);
foreach ($definition->getOptions() as $option) {
if (strlen($option->getShortcut()) > 1) {
$laterOptions[] = $option;
continue;
}
$this->writeText("\n");
$this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
}
foreach ($laterOptions as $option) {
$this->writeText("\n");
$this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
}
}
}
/**
* 描述指令
* @param Command $command
* @param array $options
* @return string|mixed
*/
protected function describeCommand(Command $command, array $options = [])
{
$command->getSynopsis(true);
$command->getSynopsis(false);
$command->mergeConsoleDefinition(false);
$this->writeText('<comment>Usage:</comment>', $options);
foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
$this->writeText("\n");
$this->writeText(' ' . $usage, $options);
}
$this->writeText("\n");
$definition = $command->getNativeDefinition();
if ($definition->getOptions() || $definition->getArguments()) {
$this->writeText("\n");
$this->describeInputDefinition($definition, $options);
$this->writeText("\n");
}
if ($help = $command->getProcessedHelp()) {
$this->writeText("\n");
$this->writeText('<comment>Help:</comment>', $options);
$this->writeText("\n");
$this->writeText(' ' . str_replace("\n", "\n ", $help), $options);
$this->writeText("\n");
}
}
/**
* 描述控制台
* @param Console $console
* @param array $options
* @return string|mixed
*/
protected function describeConsole(Console $console, array $options = [])
{
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$description = new ConsoleDescription($console, $describedNamespace);
if (isset($options['raw_text']) && $options['raw_text']) {
$width = $this->getColumnWidth($description->getCommands());
foreach ($description->getCommands() as $command) {
$this->writeText(sprintf("%-${width}s %s", $command->getName(), $command->getDescription()), $options);
$this->writeText("\n");
}
} else {
if ('' != $help = $console->getHelp()) {
$this->writeText("$help\n\n", $options);
}
$this->writeText("<comment>Usage:</comment>\n", $options);
$this->writeText(" command [options] [arguments]\n\n", $options);
$this->describeInputDefinition(new InputDefinition($console->getDefinition()->getOptions()), $options);
$this->writeText("\n");
$this->writeText("\n");
$width = $this->getColumnWidth($description->getCommands());
if ($describedNamespace) {
$this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
} else {
$this->writeText('<comment>Available commands:</comment>', $options);
}
// add commands by namespace
foreach ($description->getNamespaces() as $namespace) {
if (!$describedNamespace && ConsoleDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
$this->writeText("\n");
$this->writeText(' <comment>' . $namespace['id'] . '</comment>', $options);
}
foreach ($namespace['commands'] as $name) {
$this->writeText("\n");
$spacingWidth = $width - strlen($name);
$this->writeText(sprintf(" <info>%s</info>%s%s", $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)
->getDescription()), $options);
}
}
$this->writeText("\n");
}
}
/**
* {@inheritdoc}
*/
private function writeText($content, array $options = [])
{
$this->write(isset($options['raw_text'])
&& $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true);
}
/**
* 格式化
* @param mixed $default
* @return string
*/
private function formatDefaultValue($default)
{
return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
/**
* @param Command[] $commands
* @return int
*/
private function getColumnWidth(array $commands)
{
$width = 0;
foreach ($commands as $command) {
$width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width;
}
return $width + 2;
}
/**
* @param InputOption[] $options
* @return int
*/
private function calculateTotalWidthForOptions($options)
{
$totalWidth = 0;
foreach ($options as $option) {
$nameLength = 4 + strlen($option->getName()) + 2; // - + shortcut + , + whitespace + name + --
if ($option->acceptValue()) {
$valueLength = 1 + strlen($option->getName()); // = + value
$valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]
$nameLength += $valueLength;
}
$totalWidth = max($totalWidth, $nameLength);
}
return $totalWidth;
}
}

View File

@@ -0,0 +1,157 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\helper\question;
class Choice extends Question
{
private $choices;
private $multiselect = false;
private $prompt = ' > ';
private $errorMessage = 'Value "%s" is invalid';
/**
* 构造方法
* @param string $question 问题
* @param array $choices 选项
* @param mixed $default 默认答案
*/
public function __construct($question, array $choices, $default = null)
{
parent::__construct($question, $default);
$this->choices = $choices;
$this->setValidator($this->getDefaultValidator());
$this->setAutocompleterValues($choices);
}
/**
* 可选项
* @return array
*/
public function getChoices()
{
return $this->choices;
}
/**
* 设置可否多选
* @param bool $multiselect
* @return self
*/
public function setMultiselect($multiselect)
{
$this->multiselect = $multiselect;
$this->setValidator($this->getDefaultValidator());
return $this;
}
/**
* 获取提示
* @return string
*/
public function getPrompt()
{
return $this->prompt;
}
/**
* 设置提示
* @param string $prompt
* @return self
*/
public function setPrompt($prompt)
{
$this->prompt = $prompt;
return $this;
}
/**
* 设置错误提示信息
* @param string $errorMessage
* @return self
*/
public function setErrorMessage($errorMessage)
{
$this->errorMessage = $errorMessage;
$this->setValidator($this->getDefaultValidator());
return $this;
}
/**
* 获取默认的验证方法
* @return callable
*/
private function getDefaultValidator()
{
$choices = $this->choices;
$errorMessage = $this->errorMessage;
$multiselect = $this->multiselect;
$isAssoc = $this->isAssoc($choices);
return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) {
// Collapse all spaces.
$selectedChoices = str_replace(' ', '', $selected);
if ($multiselect) {
// Check for a separated comma values
if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) {
throw new \InvalidArgumentException(sprintf($errorMessage, $selected));
}
$selectedChoices = explode(',', $selectedChoices);
} else {
$selectedChoices = [$selected];
}
$multiselectChoices = [];
foreach ($selectedChoices as $value) {
$results = [];
foreach ($choices as $key => $choice) {
if ($choice === $value) {
$results[] = $key;
}
}
if (count($results) > 1) {
throw new \InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results)));
}
$result = array_search($value, $choices);
if (!$isAssoc) {
if (!empty($result)) {
$result = $choices[$result];
} elseif (isset($choices[$value])) {
$result = $choices[$value];
}
} elseif (empty($result) && array_key_exists($value, $choices)) {
$result = $value;
}
if (empty($result)) {
throw new \InvalidArgumentException(sprintf($errorMessage, $value));
}
array_push($multiselectChoices, $result);
}
if ($multiselect) {
return $multiselectChoices;
}
return current($multiselectChoices);
};
}
}

View File

@@ -0,0 +1,56 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\helper\question;
class Confirmation extends Question
{
private $trueAnswerRegex;
/**
* 构造方法
* @param string $question 问题
* @param bool $default 默认答案
* @param string $trueAnswerRegex 验证正则
*/
public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i')
{
parent::__construct($question, (bool)$default);
$this->trueAnswerRegex = $trueAnswerRegex;
$this->setNormalizer($this->getDefaultNormalizer());
}
/**
* 获取默认的答案回调
* @return callable
*/
private function getDefaultNormalizer()
{
$default = $this->getDefault();
$regex = $this->trueAnswerRegex;
return function ($answer) use ($default, $regex) {
if (is_bool($answer)) {
return $answer;
}
$answerIsTrue = (bool)preg_match($regex, $answer);
if (false === $default) {
return $answer && $answerIsTrue;
}
return !$answer || $answerIsTrue;
};
}
}

View File

@@ -0,0 +1,211 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\helper\question;
class Question
{
private $question;
private $attempts;
private $hidden = false;
private $hiddenFallback = true;
private $autocompleterValues;
private $validator;
private $default;
private $normalizer;
/**
* 构造方法
* @param string $question 问题
* @param mixed $default 默认答案
*/
public function __construct($question, $default = null)
{
$this->question = $question;
$this->default = $default;
}
/**
* 获取问题
* @return string
*/
public function getQuestion()
{
return $this->question;
}
/**
* 获取默认答案
* @return mixed
*/
public function getDefault()
{
return $this->default;
}
/**
* 是否隐藏答案
* @return bool
*/
public function isHidden()
{
return $this->hidden;
}
/**
* 隐藏答案
* @param bool $hidden
* @return Question
*/
public function setHidden($hidden)
{
if ($this->autocompleterValues) {
throw new \LogicException('A hidden question cannot use the autocompleter.');
}
$this->hidden = (bool)$hidden;
return $this;
}
/**
* 不能被隐藏是否撤销
* @return bool
*/
public function isHiddenFallback()
{
return $this->hiddenFallback;
}
/**
* 设置不能被隐藏的时候的操作
* @param bool $fallback
* @return Question
*/
public function setHiddenFallback($fallback)
{
$this->hiddenFallback = (bool)$fallback;
return $this;
}
/**
* 获取自动完成
* @return null|array|\Traversable
*/
public function getAutocompleterValues()
{
return $this->autocompleterValues;
}
/**
* 设置自动完成的值
* @param null|array|\Traversable $values
* @return Question
* @throws \InvalidArgumentException
* @throws \LogicException
*/
public function setAutocompleterValues($values)
{
if (is_array($values) && $this->isAssoc($values)) {
$values = array_merge(array_keys($values), array_values($values));
}
if (null !== $values && !is_array($values)) {
if (!$values instanceof \Traversable || $values instanceof \Countable) {
throw new \InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.');
}
}
if ($this->hidden) {
throw new \LogicException('A hidden question cannot use the autocompleter.');
}
$this->autocompleterValues = $values;
return $this;
}
/**
* 设置答案的验证器
* @param null|callable $validator
* @return Question The current instance
*/
public function setValidator($validator)
{
$this->validator = $validator;
return $this;
}
/**
* 获取验证器
* @return null|callable
*/
public function getValidator()
{
return $this->validator;
}
/**
* 设置最大重试次数
* @param null|int $attempts
* @return Question
* @throws \InvalidArgumentException
*/
public function setMaxAttempts($attempts)
{
if (null !== $attempts && $attempts < 1) {
throw new \InvalidArgumentException('Maximum number of attempts must be a positive value.');
}
$this->attempts = $attempts;
return $this;
}
/**
* 获取最大重试次数
* @return null|int
*/
public function getMaxAttempts()
{
return $this->attempts;
}
/**
* 设置响应的回调
* @param string|\Closure $normalizer
* @return Question
*/
public function setNormalizer($normalizer)
{
$this->normalizer = $normalizer;
return $this;
}
/**
* 获取相应回调
* The normalizer can ba a callable (a string), a closure or a class implementing __invoke.
* @return string|\Closure
*/
public function getNormalizer()
{
return $this->normalizer;
}
protected function isAssoc($array)
{
return (bool)count(array_filter(array_keys($array), 'is_string'));
}
}