diff --git a/README.md b/README.md
index 2637924..b2262f4 100644
--- a/README.md
+++ b/README.md
@@ -4,16 +4,16 @@
[![Php Version](https://img.shields.io/badge/php-%3E=5.6.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/console)
[![Latest Stable Version](http://img.shields.io/packagist/v/inhere/console.svg)](https://packagist.org/packages/inhere/console)
-简洁、功能全面的php命令行应用库。提供控制台参数解析, 颜色风格输出, 用户信息交互, 特殊格式信息显示。
+简洁、功能全面的php命令行应用库。提供控制台参数解析, 命令运行,颜色风格输出, 用户信息交互, 特殊格式信息显示。
> 无其他库依赖,可以方便的整合到任何已有项目中。
- 功能全面的命令行的选项参数解析(命名参数,短选项,长选项 ...)
-- 命令行应用, 命令行的 `controller`, `command` 解析运行
+- 命令行应用, 命令行的 `controller`, `command` 解析运行。(支持命令别名)
- 命令行中功能强大的 `input`, `output` 管理、使用
- 消息文本的多种颜色风格输出支持(`info`, `comment`, `success`, `danger`, `error` ... ...)
-- 丰富的特殊格式信息显示(`section`, `panel`, `padding`, `help-panel`, `table`, `title`, `list`, `progressBar`)
-- 常用的用户信息交互支持(`select`, `confirm`, `ask/question`)
+- 丰富的特殊格式信息显示(`section`, `panel`, `padding`, `help-panel`, `table`, `title`, `list`, `multiList`, `progressBar`)
+- 常用的用户信息交互支持(`select`, `multiSelect`, `confirm`, `ask/question`, `askPassword/askHiddenInput`)
- 命令方法注释自动解析(提取为参数 `arguments` 和 选项 `options` 等信息)
- 类似 `symfony/console` 的预定义参数定义支持(按位置赋予参数值)
- 输出是 windows,linux 兼容的,不支持颜色的环境会自动去除相关CODE
@@ -34,12 +34,20 @@
## 安装
-- 使用 composer
+- 使用 composer 命令
+
+```bash
+composer require inhere/console
+```
+
+- 使用 composer.json
编辑 `composer.json`,在 `require` 添加
```
"inhere/console": "dev-master",
+
+// "inhere/console": "^2.0", // 指定稳定版本
// "inhere/console": "dev-php5", // for php5
```
@@ -84,10 +92,12 @@ $app->run();
然后在命令行里执行 `php examples/app`, 立即就可以看到如下输出了:
-!['output-commands-info'](images/example-app.png)
+!['app-command-list'](docs/screenshots/app-command-list.png)
> `Independent Commands` 中的 demo 就是我们上面添加的命令
+- `[alias: ...]` 命令最后的alias 表明了此命令拥有的别名。
+
## 添加命令
添加命令的方式有三种
@@ -104,7 +114,7 @@ $app->command('demo', function (Input $in, Output $out) {
}, 'this is message for the command');
```
-### 继承 `Inhere\Console\Command`
+### 独立命令
通过继承 `Inhere\Console\Command` 添加独立命令
@@ -155,7 +165,7 @@ $app->command(TestCommand::class);
// $app->command('test1', TestCommand::class);
```
-### 继承 `Inhere\Console\Controller`
+### 命令组
通过继承 `Inhere\Console\Controller` 添加一组命令. 即是命令行的控制器
@@ -171,13 +181,29 @@ class HomeController extends Controller
protected static $description = 'default command controller. there are some command usage examples';
/**
- * this is a command's description message a color text
+ * this is a command's description message, color text
* the second line text
- * @usage usage message
+ * @usage {command} [arg ...] [--opt ...]
+ * @arguments
+ * arg1 argument description 1
+ * the second line
+ * a2,arg2 argument description 2
+ * the second line
+ * @options
+ * -s, --long option description 1
+ * --opt option description 2
* @example example text one
* the second line example
*/
- public function indexCommand()
+ public function testCommand()
+ {
+ $this->write('hello, welcome!! this is ' . __METHOD__);
+ }
+
+ /**
+ * a example for use color text output on command
+ */
+ public function otherCommand()
{
$this->write('hello, welcome!! this is ' . __METHOD__);
}
@@ -192,13 +218,14 @@ class HomeController extends Controller
- 支持的tag有 `@usage` `@arguments` `@options` `@example`
- 当你使用 `php examples/app home -h` 时,可以查看到 `HomeController` 的所有命令描述注释信息
-- 当使用 `php examples/app home:index -h` 时,可以查看到关于 `HomeController::indexCommand` 更详细的信息。包括描述注释文本、`@usage` 、`@example`
+
+ ![group-command-list](docs/screenshots/group-command-list.png)
+- 当使用 `php examples/app home:test -h` 时,可以查看到关于 `HomeController::testCommand` 更详细的信息。包括描述注释文本、`@usage` 、`@example`
-> 小提示:注释里面同样支持带颜色的文本输出 `eg: this is a command's description message`
+ ![group-command-list](docs/screenshots/group-command-help.png)
-- 运行效果(by `php examples/app home`):
+> 小提示:注释里面同样支持带颜色的文本输出 `eg: this is a command's description message`
-![command-group-example](./images/example-for-group.png)
更多请查看 [examples](./examples) 中的示例代码和在目录下运行示例 `php examples/app` 来查看效果
@@ -360,12 +387,24 @@ $output->write('hello world');
已经内置了常用的风格:
-![alt text](images/output-color-text.png "Title")
+![alt text](docs/screenshots/output-color-text.png "Title")
来自于类 `Inhere\Console\Utils\Show`。
> output 实例拥有 `Inhere\Console\Utils\Show` 的所有格式化输出方法。不过都是通过对象式访问的。
+- **单独使用颜色风格**
+
+```php
+$style = Inhere\Console\Style\Style::create();
+
+echo $style->render('no color color text');
+
+// 直接使用内置的风格
+echo $style->info('message');
+echo $style->error('message');
+```
+
### 标题文本输出
使用 `Show::title()/$output->title()`
@@ -403,13 +442,13 @@ echo "Progress:\n";
$i = 0;
while ($i <= $total) {
- $bar->send($i);
+ $bar->send(1);// 发送步进长度,通常是 1
usleep(50000);
$i++;
}
```
-![show-progress](images/show-progress.png)
+![show-progress](docs/screenshots/progress-demo.png)
### 列表数据展示输出
@@ -438,7 +477,9 @@ $data = [
Show::aList($data, $title);
```
-> 渲染效果请看下面的预览
+> 渲染效果
+
+![fmt-list](docs/screenshots/fmt-list.png)
### 多列表数据展示输出
@@ -467,7 +508,9 @@ $data = [
Show::mList($data);
```
-> 渲染效果请看下面的预览
+> 渲染效果
+
+![fmt-multi-list](docs/screenshots/fmt-multi-list.png)
### 面板展示信息输出
@@ -489,7 +532,9 @@ $data = [
Show::panel($data, 'panel show', '#');
```
-> 渲染效果请看下面的预览
+> 渲染效果
+
+![fmt-panel](docs/screenshots/fmt-panel.png)
### 数据表格信息输出
@@ -531,7 +576,7 @@ Show::table($data, 'a table', $opts);
> 渲染效果请看下面的预览
-![table-show](images/table-show.png)
+![table-show](docs/screenshots/table-show.png)
### 快速的渲染一个帮助信息面板
@@ -558,9 +603,9 @@ Show::helpPanel([
], false);
```
-### 渲染效果预览
+> 渲染效果预览
-![alt text](images/output-format-msg.png "Title")
+![alt text](docs/screenshots/fmt-help-panel.png "Title")
## 用户交互方法
diff --git a/README_en.md b/README_en.md
index 15352c3..d86d303 100644
--- a/README_en.md
+++ b/README_en.md
@@ -12,7 +12,7 @@ a php console application library.
- console color support, format message output
- console interactive
-[中文README](./README.md)
+> [中文README](./README.md)
## project
@@ -72,7 +72,7 @@ $app->run();
now, you can see:
-!['output-commands-info'](images/example-app.png)
+!['app-command-list'](docs/screenshots/app-command-list.png)
## input
@@ -174,7 +174,7 @@ $output->write('hello');
#### use color style
-![alt text](images/output-color-text.png "Title")
+![alt text](docs/screenshots/output-color-text.png "Title")
#### special format output
@@ -184,7 +184,7 @@ $output->write('hello');
- `$output->table()`
- `$output->helpPanel()`
-![alt text](images/output-format-msg.png "Title")
+![alt text](docs/screenshots/output-format-msg.png "Title")
## more interactive
diff --git a/docs/catelog.md b/docs/catelog.md
new file mode 100644
index 0000000..f9e9bef
--- /dev/null
+++ b/docs/catelog.md
@@ -0,0 +1,3 @@
+# php 命令行应用库
+
+[简介和安装](intro.md)
diff --git a/examples/baks/cli-color.md b/docs/cli-color.md
similarity index 100%
rename from examples/baks/cli-color.md
rename to docs/cli-color.md
diff --git a/docs/controller.md b/docs/controller.md
new file mode 100644
index 0000000..c9cb977
--- /dev/null
+++ b/docs/controller.md
@@ -0,0 +1,34 @@
+# 注册命令
+
+### 注册独立命令
+### 注册组命令
+### 设置命令名称
+### 设置命令描述
+
+## 独立命令
+
+## 组命令(controller)
+
+## 输入定义(InputDefinition)
+
+
+## 设置参数
+
+### 使用名称设置参数
+
+### 根据位置设置参数
+
+```
+$ php examples/app demo john male 43 --opt1 value1 -y
+hello, this in Inhere\Console\examples\DemoCommand::execute
+this is argument and option example:
+ the opt1's value
+ option: opt1 |
+ | |
+php examples/app demo john male 43 --opt1 value1 -y
+ | | | | | |
+ script command | | |______ option: yes, it use shortcat: y, and it is a Input::OPT_BOOLEAN, so no value.
+ | |___ |
+ argument: name | argument: age
+ argument: sex
+```
diff --git a/docs/input-output.md b/docs/input-output.md
new file mode 100644
index 0000000..6a66f06
--- /dev/null
+++ b/docs/input-output.md
@@ -0,0 +1,39 @@
+# input and output
+
+## input
+
+## output
+
+### output buffer
+
+how tu use
+
+- use `Output`
+
+```php
+ // open buffer
+ $this->output->startBuffer();
+
+ $this->output->write('message 0');
+ $this->output->write('message 1');
+ // ....
+ $this->output->write('message n');
+
+ // stop and output buffer
+ $this->output->stopBuffer();
+```
+
+- use `Show`
+
+```php
+ // open buffer
+ Show::startBuffer();
+
+ Show::write('message 0');
+ Show::write('message 1');
+ // ....
+ Show::write('message n');
+
+ // stop and output buffer
+ Show::stopBuffer();
+```
diff --git a/docs/intro.md b/docs/intro.md
new file mode 100644
index 0000000..5b78818
--- /dev/null
+++ b/docs/intro.md
@@ -0,0 +1,56 @@
+# 简介
+
+简洁、功能全面的php命令行应用库。提供控制台参数解析, 颜色风格输出, 用户信息交互, 特殊格式信息显示。
+
+> 无其他库依赖,可以方便的整合到任何已有项目中。
+
+- 功能全面的命令行的选项参数解析(命名参数,短选项,长选项 ...)
+- 命令行应用, 命令行的 `controller`, `command` 解析运行
+- 命令行中功能强大的 `input`, `output` 管理、使用
+- 消息文本的多种颜色风格输出支持(`info`, `comment`, `success`, `danger`, `error` ... ...)
+- 丰富的特殊格式信息显示(`section`, `panel`, `padding`, `help-panel`, `table`, `title`, `list`, `progressBar`)
+- 常用的用户信息交互支持(`select`, `confirm`, `ask/question`)
+- 命令方法注释自动解析(提取为参数 `arguments` 和 选项 `options` 等信息)
+- 类似 `symfony/console` 的预定义参数定义支持(按位置赋予参数值)
+- 输出是 windows,linux 兼容的,不支持颜色的环境会自动去除相关CODE
+
+> 下面所有的特性,效果都是运行 `examples/` 中的示例代码 `php examples/app` 展示出来的。下载后可以直接测试体验
+
+
+## 项目地址
+
+- **github** https://github.com/inhere/php-console.git
+- **git@osc** https://git.oschina.net/inhere/php-console.git
+
+**注意:**
+
+- master 分支是要求 `php >= 7` 的(推荐使用)。
+- php5 分支是支持 php5 `php >= 5.5` 的代码分支。
+
+## 安装
+
+- 使用 composer 命令
+
+```bash
+composer require inhere/console
+```
+
+- 使用 composer.json
+
+编辑 `composer.json`,在 `require` 添加
+
+```
+"inhere/console": "dev-master",
+
+// "inhere/console": "^2.0", // 指定稳定版本
+// "inhere/console": "dev-php5", // for php5
+```
+
+然后执行: `composer update`
+
+- 直接拉取
+
+```
+git clone https://git.oschina.net/inhere/php-console.git // git@osc
+git clone https://github.com/inhere/php-console.git // github
+```
diff --git a/docs/screenshots/app-command-list.png b/docs/screenshots/app-command-list.png
new file mode 100644
index 0000000..05e9d68
Binary files /dev/null and b/docs/screenshots/app-command-list.png differ
diff --git a/docs/screenshots/fmt-help-panel.png b/docs/screenshots/fmt-help-panel.png
new file mode 100644
index 0000000..660c068
Binary files /dev/null and b/docs/screenshots/fmt-help-panel.png differ
diff --git a/docs/screenshots/fmt-list.png b/docs/screenshots/fmt-list.png
new file mode 100644
index 0000000..a3658af
Binary files /dev/null and b/docs/screenshots/fmt-list.png differ
diff --git a/docs/screenshots/fmt-multi-list.png b/docs/screenshots/fmt-multi-list.png
new file mode 100644
index 0000000..5facf09
Binary files /dev/null and b/docs/screenshots/fmt-multi-list.png differ
diff --git a/docs/screenshots/fmt-panel.png b/docs/screenshots/fmt-panel.png
new file mode 100644
index 0000000..f89f37d
Binary files /dev/null and b/docs/screenshots/fmt-panel.png differ
diff --git a/docs/screenshots/group-command-help.png b/docs/screenshots/group-command-help.png
new file mode 100644
index 0000000..7bcd6b1
Binary files /dev/null and b/docs/screenshots/group-command-help.png differ
diff --git a/docs/screenshots/group-command-list.png b/docs/screenshots/group-command-list.png
new file mode 100644
index 0000000..c5c5216
Binary files /dev/null and b/docs/screenshots/group-command-list.png differ
diff --git a/docs/screenshots/interactive-ask.png b/docs/screenshots/interactive-ask.png
new file mode 100644
index 0000000..a988f8d
Binary files /dev/null and b/docs/screenshots/interactive-ask.png differ
diff --git a/docs/screenshots/interactive-limited-ask.png b/docs/screenshots/interactive-limited-ask.png
new file mode 100644
index 0000000..88599d6
Binary files /dev/null and b/docs/screenshots/interactive-limited-ask.png differ
diff --git a/images/output-color-text.png b/docs/screenshots/output-color-text.png
similarity index 100%
rename from images/output-color-text.png
rename to docs/screenshots/output-color-text.png
diff --git a/images/output-format-msg.png b/docs/screenshots/output-format-msg.png
similarity index 100%
rename from images/output-format-msg.png
rename to docs/screenshots/output-format-msg.png
diff --git a/docs/screenshots/progress-demo.png b/docs/screenshots/progress-demo.png
new file mode 100644
index 0000000..0a1cb72
Binary files /dev/null and b/docs/screenshots/progress-demo.png differ
diff --git a/images/table-show.png b/docs/screenshots/table-show.png
similarity index 100%
rename from images/table-show.png
rename to docs/screenshots/table-show.png
diff --git a/images/use-arg-position.png b/docs/screenshots/use-definition-args.png
similarity index 100%
rename from images/use-arg-position.png
rename to docs/screenshots/use-definition-args.png
diff --git a/docs/show-ascii-font.md b/docs/show-ascii-font.md
new file mode 100644
index 0000000..61b7f31
--- /dev/null
+++ b/docs/show-ascii-font.md
@@ -0,0 +1,11 @@
+# show cli font
+
+```php
+
+ $name = '404';
+ ArtFont::create()->show($name, ArtFont::INTERNAL_GROUP,[
+ 'type' => $this->input->getBoolOpt('italic') ? 'italic' : '',
+ 'style' => $this->input->getOpt('style'),
+ ]);
+
+```
diff --git a/docs/something.md b/docs/something.md
new file mode 100644
index 0000000..4bd4748
--- /dev/null
+++ b/docs/something.md
@@ -0,0 +1,41 @@
+# some idea
+
+## controller
+
+```php
+ protected function commandConfigure($definition)
+ {
+ // old: own create.
+ $this->createDefinition()->addArgument();
+
+ // maybe: get by argument.
+ $definition->addArgument();
+
+ // ....
+ }
+```
+
+```php
+ /**
+ * the group controller metadata. to define name, description
+ * @return array
+ */
+ public static function metadata()
+ {
+ return [
+ 'name' => 'model',
+ 'description' => 'some console command handle for model user data.',
+
+ // for command
+ 'aliases' => [
+ 'i', 'in',
+ ],
+
+ // for controller
+ 'aliases' => [
+ 'i' => 'install',
+ 'up' => 'update',
+ ]
+ ];
+ }
+```
\ No newline at end of file
diff --git a/examples/sprintf.md b/docs/sprintf.md
similarity index 100%
rename from examples/sprintf.md
rename to docs/sprintf.md
diff --git a/examples/DemoCommand.php b/examples/Commands/DemoCommand.php
similarity index 90%
rename from examples/DemoCommand.php
rename to examples/Commands/DemoCommand.php
index 9ae6ab5..83a9517 100644
--- a/examples/DemoCommand.php
+++ b/examples/Commands/DemoCommand.php
@@ -6,7 +6,7 @@
* Time: 18:58
*/
-namespace Inhere\Console\Examples;
+namespace Inhere\Console\Examples\Commands;
use Inhere\Console\Command;
use Inhere\Console\IO\Input;
@@ -14,7 +14,7 @@
/**
* Class DemoCommand
- * @package app\console\commands
+ * @package Inhere\Console\Examples\Commands
*/
class DemoCommand extends Command
{
@@ -23,12 +23,13 @@ class DemoCommand extends Command
/**
* {@inheritDoc}
+ * @throws \LogicException
*/
protected function configure()
{
$this->createDefinition()
->setDescription(self::getDescription())
- ->setExample($this->handleAnnotationVars('{script} {command} john male 43 --opt1 value1'))
+ ->setExample($this->parseAnnotationVars('{script} {command} john male 43 --opt1 value1'))
->addArgument('name', Input::ARG_REQUIRED, 'description for the argument [name], is required')
->addArgument('sex', Input::ARG_OPTIONAL, 'description for the argument [sex], is optional')
->addArgument('age', Input::ARG_OPTIONAL, 'description for the argument [age], is optional')
diff --git a/examples/TestCommand.php b/examples/Commands/TestCommand.php
similarity index 82%
rename from examples/TestCommand.php
rename to examples/Commands/TestCommand.php
index 03e93a8..76a4bf9 100644
--- a/examples/TestCommand.php
+++ b/examples/Commands/TestCommand.php
@@ -6,12 +6,13 @@
* Time: 18:58
*/
-namespace Inhere\Console\Examples;
+namespace Inhere\Console\Examples\Commands;
use Inhere\Console\Command;
/**
* Class Test
+ * @package Inhere\Console\Examples\Commands
*/
class TestCommand extends Command
{
@@ -26,6 +27,8 @@ class TestCommand extends Command
* @options
* --long,-s option description 1
* --opt option description 2
+ * @param $input
+ * @param $output
*/
public function execute($input, $output)
{
diff --git a/examples/HomeController.php b/examples/Controllers/HomeController.php
similarity index 53%
rename from examples/HomeController.php
rename to examples/Controllers/HomeController.php
index f9f56e1..113e9b5 100644
--- a/examples/HomeController.php
+++ b/examples/Controllers/HomeController.php
@@ -1,51 +1,96 @@
'index',
+ 'prg' => 'progress',
+ 'pgb' => 'progressBar',
+ 'pwd' => 'password',
+ 'l' => 'list',
+ 'h' => 'helpPanel',
+ 'hl' => 'highlight',
+ 'hp' => 'helpPanel',
+ 'af' => 'artFont',
+ 'ml' => 'multiList',
+ 'ms' => 'multiSelect',
+ ];
+ }
+
+ protected function init()
+ {
+ parent::init();
+
+ $this->addAnnotationVar('internalFonts', implode(',', ArtFont::getInternalFonts()));
+ }
+
+ protected function afterExecute()
+ {
+ $this->write('after command execute');
+ }
/**
* this is a command's description message
* the second line text
- * @usage usage message
+ * @usage {command} [arg ...] [--opt ...]
* @arguments
- * arg1 argument description 1
- * arg2 argument description 2
+ * arg1 argument description 1
+ * the second line
+ * a2,arg2 argument description 2
+ * the second line
* @options
- * --long,-s option description 1
- * --opt option description 2
+ * -s, --long option description 1
+ * --opt option description 2
* @example example text one
* the second line example
*/
- public function indexCommand()
+ public function testCommand()
{
$this->write('hello, welcome!! this is ' . __METHOD__);
}
/**
- * a example for input password on command line
- * @usage {fullCommand}
+ * a example for highlight code
+ * @options
+ * --ln With line number
+ * @param Input $in
*/
- public function passwdCommand()
+ public function highlightCommand($in)
{
- $pwd = $this->askPassword();
+ // $file = $this->app->getRootPath() . '/examples/routes.php';
+ $file = $this->app->getRootPath() . '/src/Utils/Show.php';
+ $src = file_get_contents($file);
- $this->write('Your input is:' . $pwd);
+ $hl = new Highlighter();
+ $code = $hl->highlight($src, $in->getBoolOpt('ln'));
+
+ $this->output->writeRaw($code);
}
/**
@@ -60,7 +105,7 @@ public function colorCommand()
return 0;
}
- $this->write('color text output:');
+ $this->write('color style text output:');
$styles = $this->output->getStyle()->getStyleNames();
foreach ($styles as $style) {
@@ -86,7 +131,31 @@ public function blockMsgCommand()
}
/**
- * a counter example show. It is like progress txt, but no max value.
+ * output art font text
+ * @options
+ * --font Set the art font name(allow: {internalFonts}).
+ * --italic Set the art font type is italic.
+ * --style Set the art font style.
+ * @return int
+ */
+ public function artFontCommand()
+ {
+ $name = $this->input->getLongOpt('font', '404');
+
+ if (!ArtFont::isInternalFont($name)) {
+ return $this->output->liteError("Your input font name: $name, is not exists. Please use '-h' see allowed.");
+ }
+
+ ArtFont::create()->show($name, ArtFont::INTERNAL_GROUP,[
+ 'type' => $this->input->getBoolOpt('italic') ? 'italic' : '',
+ 'style' => $this->input->getOpt('style'),
+ ]);
+
+ return 0;
+ }
+
+ /**
+ * dynamic notice message show: counterTxt. It is like progress txt, but no max value.
* @example
* {script} {command}
* @return int
@@ -110,7 +179,37 @@ public function counterCommand()
}
/**
- * a progress bar example show
+ * dynamic notice message show: spinner
+ */
+ public function spinnerCommand()
+ {
+ $total = 5000;
+
+ while ($total--) {
+ Show::spinner();
+ usleep(100);
+ }
+
+ Show::spinner('Done', true);
+ }
+
+ /**
+ * dynamic notice message show: pending
+ */
+ public function pendingCommand()
+ {
+ $total = 8000;
+
+ while ($total--) {
+ Show::pending();
+ usleep(200);
+ }
+
+ Show::pending('Done', true);
+ }
+
+ /**
+ * a progress bar example show, by Show::progressBar()
* @options
* --type the progress type, allow: bar,txt. txt
* --done-char the done show char. =
@@ -135,7 +234,7 @@ public function progressCommand($input)
'signChar' => $input->getOpt('sign-char', '>'),
]);
} else {
- $bar = $this->output->progressTxt($total, 'Doing gggg ...', 'Done');
+ $bar = $this->output->progressTxt($total, 'Doing go g...', 'Done');
}
$this->write('Progress:');
@@ -150,13 +249,40 @@ public function progressCommand($input)
}
/**
- * output more format message text
+ * a progress bar example show, by class ProgressBar
+ * @throws \LogicException
*/
- public function fmtMsgCommand()
+ public function progressBarCommand()
+ {
+ $i = 0;
+ $total = 120;
+ $bar = new ProgressBar();
+ $bar->start(120);
+
+ while ($i <= $total) {
+ $bar->advance();
+ usleep(50000);
+ $i++;
+ }
+
+ $bar->finish();
+ }
+
+ /**
+ * output format message: title
+ */
+ public function titleCommand()
{
$this->output->title('title show');
- echo "\n";
+ return 0;
+ }
+
+ /**
+ * output format message: section
+ */
+ public function sectionCommand()
+ {
$body = 'If screen size could not be detected, or the indentation is greater than the screen size, the text will not be wrapped.' .
'Word wrap text with indentation to fit the screen size,' .
'Word wrap text with indentation to fit the screen size,' .
@@ -167,17 +293,31 @@ public function fmtMsgCommand()
'pos' => 'l'
]);
+ return 0;
+ }
+
+ /**
+ * output format message: panel
+ */
+ public function panelCommand()
+ {
$data = [
'application version' => '1.2.0',
'system version' => '5.2.3',
'see help' => 'please use php bin/app -h',
'a only value message text',
];
+
Show::panel($data, 'panel show', [
- 'borderChar' => '#'
+ 'borderChar' => '*'
]);
+ }
- echo "\n";
+ /**
+ * output format message: helpPanel
+ */
+ public function helpPanelCommand()
+ {
Show::helpPanel([
Show::HELP_DES => 'a help panel description text. (help panel show)',
Show::HELP_USAGE => 'a usage text',
@@ -192,6 +332,21 @@ public function fmtMsgCommand()
'-h, --help' => 'Display this help message'
],
], false);
+ }
+
+ /**
+ * output format message: list
+ */
+ public function listCommand()
+ {
+ $list = [
+ 'The is a list line 0',
+ 'The is a list line 1',
+ 'The is a list line 2',
+ 'The is a list line 3',
+ ];
+
+ Show::aList($list, 'a List show(No key)');
$commands = [
'version' => 'Show application version information',
@@ -199,32 +354,39 @@ public function fmtMsgCommand()
'list' => 'List all group and independent commands',
'a only value message text'
];
- Show::aList($commands, 'a List show');
- Show::table([
- [
- 'id' => 1,
- 'name' => 'john',
- 'status' => 2,
- 'email' => 'john@email.com',
+ Show::aList($commands, 'a List show(Has key)');
+ }
+
+ /**
+ * output format message: multiList
+ */
+ public function multiListCommand()
+ {
+ Show::multiList([
+ 'list0' => [
+ 'value in the list 0',
+ 'key' => 'value in the list 0',
+ 'key1' => 'value1 in the list 0',
+ 'key2' => 'value2 in the list 0',
],
- [
- 'id' => 2,
- 'name' => 'tom',
- 'status' => 0,
- 'email' => 'tom@email.com',
+ 'list1' => [
+ 'key' => 'value in the list 1',
+ 'key1' => 'value1 in the list 1',
+ 'key2' => 'value2 in the list 1',
+ 'value in the list 1',
],
- [
- 'id' => 3,
- 'name' => 'jack',
- 'status' => 1,
- 'email' => 'jack-test@email.com',
+ 'list2' => [
+ 'key' => 'value in the list 2',
+ 'value in the list 2',
+ 'key1' => 'value1 in the list 2',
+ 'key2' => 'value2 in the list 2',
],
- ], 'table show');
+ ]);
}
/**
- * a example for display a table
+ * output format message: table
*/
public function tableCommand()
{
@@ -280,7 +442,7 @@ public function tableCommand()
}
/**
- * a example use padding() for show data
+ * output format message: padding
*/
public function paddingCommand()
{
@@ -294,7 +456,7 @@ public function paddingCommand()
}
/**
- * a example for dump, print, json data
+ * output format message: dump
*/
public function jsonCommand()
{
@@ -323,7 +485,7 @@ public function jsonCommand()
$this->output->dump($data);
$this->output->write('use print:');
- $this->output->print($data);
+ $this->output->prints($data);
$this->output->write('use json:');
$this->output->json($data);
@@ -350,6 +512,7 @@ public function useArgCommand()
/**
* command `defArgCommand` config
+ * @throws \LogicException
*/
protected function defArgConfigure()
{
@@ -369,24 +532,95 @@ public function defArgCommand()
}
/**
- * use Interact::confirm method
+ * This is a demo for use Interact::confirm method
*/
public function confirmCommand()
{
+ // can also: $this->confirm();
$a = Interact::confirm('continue');
- $this->write('you answer is: ' . ($a ? 'yes' : 'no'));
+ $this->write('Your answer is: ' . ($a ? 'yes' : 'no'));
}
/**
- * example for use Interact::select method
+ * This is a demo for use Interact::select() method
*/
public function selectCommand()
{
$opts = ['john', 'simon', 'rose'];
+ // can also: $this->select();
$a = Interact::select('you name is', $opts);
- $this->write('you answer is: ' . $opts[$a]);
+ $this->write('Your answer is: ' . $opts[$a]);
+ }
+
+ /**
+ * This is a demo for use Interact::multiSelect() method
+ */
+ public function multiSelectCommand()
+ {
+ $opts = ['john', 'simon', 'rose', 'tom'];
+
+ // can also: $a = Interact::multiSelect('Your friends are', $opts);
+ $a = $this->multiSelect('Your friends are', $opts);
+
+ $this->write('Your answer is: ' . json_encode($a));
+ }
+
+ /**
+ * This is a demo for use Interact::ask() method
+ */
+ public function askCommand()
+ {
+ $a = Interact::ask('you name is: ', null, function ($val, &$err) {
+ if (!preg_match('/^\w{2,}$/', $val)) {
+ $err = 'Your input must match /^\w{2,}$/';
+
+ return false;
+ }
+
+ return true;
+ });
+
+ $this->write('Your answer is: ' . $a);
+ }
+
+ /**
+ * This is a demo for use Interact::limitedAsk() method
+ * @options
+ * --nv Not use validator.
+ * --limit limit times.(default: 3)
+ */
+ public function limitedAskCommand()
+ {
+ $times = (int)$this->input->getOpt('limit', 3);
+
+ if ($this->input->getBoolOpt('nv')) {
+ $a = Interact::limitedAsk('you name is: ', null, null, $times);
+ } else {
+ $a = Interact::limitedAsk('you name is: ', null, function ($val) {
+ if (!preg_match('/^\w{2,}$/', $val)) {
+ Show::error('Your input must match /^\w{2,}$/');
+
+ return false;
+ }
+
+ return true;
+ }, $times);
+ }
+
+ $this->write('Your answer is: ' . $a);
+ }
+
+ /**
+ * This is a demo for input password. use: Interact::askPassword()
+ * @usage {fullCommand}
+ */
+ public function passwordCommand()
+ {
+ $pwd = $this->askPassword();
+
+ $this->write('Your input is: ' . $pwd);
}
/**
@@ -406,16 +640,16 @@ public function envCommand()
}
/**
- * download a file to local
+ * This is a demo for download a file to local
* @usage {command} url=url saveTo=[saveAs] type=[bar|text]
- * @example {command} url=https://github.com/inhere/php-librarys/archive/v2.0.1.zip type=bar
+ * @example {command} url=https://github.com/inhere/php-console/archive/master.zip type=bar
*/
public function downCommand()
{
$url = $this->input->getArg('url');
if (!$url) {
- Show::error('Please input you want to downloaded file url, use: url=[url]', 1);
+ $this->output->liteError('Please input you want to downloaded file url, use: url=[url]', 1);
}
$saveAs = $this->input->getArg('saveAs');
@@ -425,7 +659,7 @@ public function downCommand()
$saveAs = __DIR__ . '/' . basename($url);
}
- $goon = Interact::confirm("Now, will download $url to $saveAs, go on");
+ $goon = Interact::confirm("Now, will download $url \nto dir $saveAs, go on");
if (!$goon) {
Show::notice('Quit download, Bye!');
@@ -433,47 +667,44 @@ public function downCommand()
return 0;
}
- $d = Download::down($url, $saveAs, $type);
-
- echo Helper::dumpVars($d);
+ Download::down($url, $saveAs, $type);
+ // $d = Download::down($url, $saveAs, $type);
+ // echo Helper::dumpVars($d);
return 0;
}
/**
- * show cursor move on the screen
+ * This is a demo for show cursor move on the Terminal screen
*/
public function cursorCommand()
{
$this->write('hello, this in ' . __METHOD__);
-
- // $this->output->panel($_SERVER, 'Server information', '');
-
$this->write('this is a message text.', false);
sleep(1);
- AnsiCode::make()->cursor(AnsiCode::CURSOR_BACKWARD, 6);
+ Terminal::make()->cursor(Terminal::CURSOR_BACKWARD, 6);
sleep(1);
- AnsiCode::make()->cursor(AnsiCode::CURSOR_FORWARD, 3);
+ Terminal::make()->cursor(Terminal::CURSOR_FORWARD, 3);
sleep(1);
- AnsiCode::make()->cursor(AnsiCode::CURSOR_BACKWARD, 2);
+ Terminal::make()->cursor(Terminal::CURSOR_BACKWARD, 2);
sleep(2);
- AnsiCode::make()->screen(AnsiCode::CLEAR_LINE, 3);
+ Terminal::make()->screen(Terminal::CLEAR_LINE, 3);
$this->write('after 2s scroll down 3 row.');
sleep(2);
- AnsiCode::make()->screen(AnsiCode::SCROLL_DOWN, 3);
+ Terminal::make()->screen(Terminal::SCROLL_DOWN, 3);
$this->write('after 3s clear screen.');
sleep(3);
- AnsiCode::make()->screen(AnsiCode::CLEAR);
+ Terminal::make()->screen(Terminal::CLEAR);
}
}
diff --git a/examples/Controllers/ProcessController.php b/examples/Controllers/ProcessController.php
new file mode 100644
index 0000000..162c12f
--- /dev/null
+++ b/examples/Controllers/ProcessController.php
@@ -0,0 +1,130 @@
+ 'childProcess',
+ 'mpr' => 'multiProcess',
+ 'dr' => 'daemonRun',
+ 'rs' => 'runScript',
+ 'rb' => 'runInBackground',
+ ];
+ }
+
+ /**
+ * simple process example for child-process
+ */
+ public function runScriptCommand()
+ {
+ /*$script = '';*/
+ $script = '';
+
+ // $tmpDir = CliUtil::getTempDir();
+ // $tmpFile = $tmpDir . '/' . md5($script) . '.php';
+ // file_put_contents($tmpFile, $script);
+
+ $descriptorSpec = [
+ 0 => ['pipe', 'r'], // 标准输入,子进程从此管道中读取数据
+ 1 => ['pipe', 'w'], // 标准输出,子进程向此管道中写入数据
+ 2 => ['file', $this->app->getRootPath() . '/examples/tmp/error-output.log', 'a'] // 标准错误,写入到一个文件
+ ];
+
+ $process = proc_open('php', $descriptorSpec, $pipes);
+
+ if (\is_resource($process)) {
+ // $pipes 现在看起来是这样的:
+ // 0 => 可以向子进程标准输入写入的句柄
+ // 1 => 可以从子进程标准输出读取的句柄
+ // 错误输出将被追加到文件 error-output.txt
+
+ fwrite($pipes[0], $script);
+ fclose($pipes[0]);
+
+ $result = stream_get_contents($pipes[1]);
+
+ fclose($pipes[1]);
+
+ $this->write("RESULT:\n" . $result);
+
+ // 切记:在调用 proc_close 之前关闭所有的管道以避免死锁。
+ $retVal = proc_close($process);
+
+ echo "command returned $retVal\n";
+ }
+ }
+
+ /**
+ * simple process example for child-process
+ */
+ public function childProcessCommand()
+ {
+ $ret = ProcessUtil::create(function ($pid) {
+ echo "print in process $pid";
+
+ sleep(5);
+ });
+
+ if ($ret === false) {
+ $this->output->liteError('current env is not support process create.');
+ }
+ }
+
+ /**
+ * simple process example for daemon run
+ */
+ public function daemonRunCommand()
+ {
+ $ret = ProcessUtil::daemonRun(function ($pid){
+ $this->output->info("will running background by new process: $pid");
+ });
+
+ if ($ret === false) {
+ $this->output->liteError('current env is not support process create.');
+ }
+ }
+
+ /**
+ * simple process example for run In Background
+ */
+ public function runInBackgroundCommand()
+ {
+ $script = '';
+ $ret = ProcessUtil::runInBackground("php $script");
+
+ if ($ret === false) {
+ $this->output->liteError('current env is not support process create.');
+ }
+ }
+
+ /**
+ * simple process example for multi-process
+ * @options
+ *
+ */
+ public function multiProcessCommand()
+ {
+
+ }
+}
diff --git a/examples/app b/examples/app
index b174373..13ed035 100644
--- a/examples/app
+++ b/examples/app
@@ -3,14 +3,23 @@
define('BASE_PATH', dirname(__DIR__));
-require __DIR__ . '/s-autoload.php';
+require dirname(__DIR__) . '/tests/boot.php';
// create app instance
$app = new \Inhere\Console\Application([
'debug' => true,
- 'rootPath' => BASE_PATH,
+ 'rootPath' => dirname(__DIR__),
]);
+$app->setLogo("
+ ________ ____ ___ ___ __ _
+ / ____/ / / _/ / | ____ ____ / (_)________ _/ /_(_)___ ____
+ / / / / / / / /| | / __ \/ __ \/ / / ___/ __ `/ __/ / __ \/ __ \
+ / /___/ /____/ / / ___ |/ /_/ / /_/ / / / /__/ /_/ / /_/ / /_/ / / / /
+ \____/_____/___/ /_/ |_/ .___/ .___/_/_/\___/\__,_/\__/_/\____/_/ /_/
+ /_/ /_/
+", 'success');
+
// require dirname(__DIR__) . '/boot/cli-services.php';
require __DIR__ . '/routes.php';
diff --git a/examples/baks/OldInput.php b/examples/demo/OldInput.php
similarity index 96%
rename from examples/baks/OldInput.php
rename to examples/demo/OldInput.php
index fa34bd3..2461130 100644
--- a/examples/baks/OldInput.php
+++ b/examples/demo/OldInput.php
@@ -1,6 +1,6 @@
strlen($chars) - 1) {
+ $spinner = 0;
+ }
+ }
+ }
+
+ /**
+ * Uses `stty` to hide input/output completely.
+ * @param boolean $hidden Will hide/show the next data. Defaults to true.
+ */
+ public static function hide($hidden = true)
+ {
+ system( 'stty ' . ( $hidden? '-echo' : 'echo' ) );
+ }
+
+ /**
+ * Prompts the user for input. Optionally masking it.
+ *
+ * @param string $prompt The prompt to show the user
+ * @param bool $masked If true, the users input will not be shown. e.g. password input
+ * @param int $limit The maximum amount of input to accept
+ * @return string
+ */
+ public static function prompt($prompt, $masked=false, $limit=100)
+ {
+ echo "$prompt: ";
+ if ($masked) {
+ `stty -echo`; // disable shell echo
+ }
+ $buffer = "";
+ $char = "";
+ $f = fopen('php://stdin', 'r');
+ while (strlen($buffer) < $limit) {
+ $char = fread($f, 1);
+ if ($char === "\n" || $char === "\r") {
+ break;
+ }
+ $buffer.= $char;
+ }
+ if ($masked) {
+ `stty echo`; // enable shell echo
+ echo "\n";
+ }
+ return $buffer;
+ }
+}
+
+Status::hide();
+echo 'Password: ';
+$input = fgets(STDIN);
+Status::hide(false);
+echo $input;
+die;
+$total = random_int(5000, 10000);
+for ($x=1; $x<=$total; $x++) {
+ Status::spinner();
+ usleep(50);
+}
+
+Status::clearLine();
+
+//
+// $answer = Status::prompt("What is the secret word?", 0);
+// if ($answer == "secret") {
+// echo "Yay! You got it!";
+// } else {
+// echo "Boo! That is wrong!";
+// }
\ No newline at end of file
diff --git a/examples/baks/color.php b/examples/demo/color.php
similarity index 100%
rename from examples/baks/color.php
rename to examples/demo/color.php
diff --git a/examples/tests/phar.php b/examples/demo/phar.php
similarity index 100%
rename from examples/tests/phar.php
rename to examples/demo/phar.php
diff --git a/examples/baks/progress_bar.php b/examples/demo/progress_bar.php
similarity index 100%
rename from examples/baks/progress_bar.php
rename to examples/demo/progress_bar.php
diff --git a/examples/baks/progress_bar1.php b/examples/demo/progress_bar1.php
similarity index 100%
rename from examples/baks/progress_bar1.php
rename to examples/demo/progress_bar1.php
diff --git a/examples/baks/progress_bar3.php b/examples/demo/progress_bar3.php
similarity index 100%
rename from examples/baks/progress_bar3.php
rename to examples/demo/progress_bar3.php
diff --git a/examples/baks/readline.php b/examples/demo/readline.php
similarity index 100%
rename from examples/baks/readline.php
rename to examples/demo/readline.php
diff --git a/examples/baks/rl_callback.php b/examples/demo/rl_callback.php
similarity index 100%
rename from examples/baks/rl_callback.php
rename to examples/demo/rl_callback.php
diff --git a/examples/baks/rl_history.php b/examples/demo/rl_history.php
similarity index 100%
rename from examples/baks/rl_history.php
rename to examples/demo/rl_history.php
diff --git a/examples/baks/sf2_color.php b/examples/demo/sf2_color.php
similarity index 100%
rename from examples/baks/sf2_color.php
rename to examples/demo/sf2_color.php
diff --git a/examples/home b/examples/home
index 4c3db46..5e62115 100644
--- a/examples/home
+++ b/examples/home
@@ -1,19 +1,27 @@
#!/usr/env/php
true,
- 'rootPath' => BASE_PATH,
-]);
+$in = new \Inhere\Console\IO\Input();
+$ctrl = new HomeController($in, new \Inhere\Console\IO\Output());
+$ctrl->setStandAlone();
-$app->controller('home', HomeController::class);
+exit($ctrl->run($in->getCommand()));
-exit(
- (int)$app->runAction('home', $app->getInput()->getCommand(), false, true)
-);
+// can also:
+
+// $app = new \Inhere\Console\Application([
+// 'debug' => true,
+// 'rootPath' => BASE_PATH,
+// ]);
+//
+// $app->controller('home', HomeController::class);
+//
+// exit(
+// (int)$app->runAction('home', $app->getInput()->getCommand(), false, true)
+// );
diff --git a/examples/liteApp b/examples/liteApp
index e988fc2..95f6087 100644
--- a/examples/liteApp
+++ b/examples/liteApp
@@ -3,10 +3,10 @@
define('BASE_PATH', dirname(__DIR__));
-require __DIR__ . '/s-autoload.php';
+require dirname(__DIR__) . '/tests/boot.php';
// create app instance
-$app = new \Inhere\Console\LiteApplication;
+$app = new \Inhere\Console\LiteApp;
// register commands
$app->addCommand('test', function () {
diff --git a/examples/routes.php b/examples/routes.php
index 6b5f5cc..5d1fd80 100644
--- a/examples/routes.php
+++ b/examples/routes.php
@@ -4,40 +4,34 @@
* User: inhere
* Date: 2016/12/7
* Time: 12:46
- *
* @var Inhere\Console\Application $app
*/
use Inhere\Console\BuiltIn\PharController;
-use Inhere\Console\Examples\HomeController;
-use Inhere\Console\Examples\TestCommand;
+use Inhere\Console\Examples\Commands\DemoCommand;
+use Inhere\Console\Examples\Commands\TestCommand;
+use Inhere\Console\Examples\Controllers\HomeController;
+use Inhere\Console\Examples\Controllers\ProcessController;
use Inhere\Console\IO\Input;
use Inhere\Console\IO\Output;
-use Inhere\Console\Utils\ProgressBar;
-$app->command(\Inhere\Console\Examples\DemoCommand::class);
+$app->command(DemoCommand::class);
$app->command('exam', function (Input $in, Output $out) {
$cmd = $in->getCommand();
$out->info('hello, this is a test command: ' . $cmd);
-});
+}, 'a description message');
-$app->command('test', TestCommand::class);
-$app->command('prg', function () {
- $i = 0;
- $total = 120;
- $bar = new ProgressBar();
- $bar->start(120);
+$app->command('test', TestCommand::class, [
+ 'aliases' => ['t']
+]);
- while ($i <= $total) {
- $bar->advance();
- usleep(50000);
- $i++;
- }
+$app->controller(PharController::class);
- $bar->finish();
+$app->controller('home', HomeController::class, [
+ 'aliases' => ['h']
+]);
-}, 'a description message');
-
-$app->controller('home', HomeController::class);
-$app->controller(PharController::class);
+$app->controller(ProcessController::class, null, [
+ 'aliases' => 'prc'
+]);
diff --git a/examples/s-autoload.php b/examples/s-autoload.php
deleted file mode 100644
index 8f42815..0000000
--- a/examples/s-autoload.php
+++ /dev/null
@@ -1,31 +0,0 @@
-controllers[$name] = $class;
+ if (!$option) {
+ return $this;
+ }
+ // have option information
+ if (\is_string($option)) {
+ $this->addCommandMessage($name, $option);
+ } elseif (\is_array($option)) {
+ $this->addCommandAliases($name, isset($option['aliases']) ? $option['aliases'] : null);
+ $this->addCommandMessage($name, isset($option['description']) ? $option['description'] : null);
+ }
return $this;
}
@@ -62,6 +73,7 @@ public function addController($name, $class = null)
/**
* @param array $controllers
+ * @throws \InvalidArgumentException
*/
public function controllers(array $controllers)
{
@@ -72,11 +84,11 @@ public function controllers(array $controllers)
* Register a app independent console command
* @param string|Command $name
* @param string|\Closure|Command $handler
- * @param null|string $description
+ * @param null|array|string $option
* @return $this
* @throws \InvalidArgumentException
*/
- public function command($name, $handler = null, $description = null)
+ public function command($name, $handler = null, $option = null)
{
if (!$handler && class_exists($name)) {
/** @var Command $name */
@@ -102,8 +114,15 @@ public function command($name, $handler = null, $description = null)
}
// is an class name string
$this->commands[$name] = $handler;
- if ($description) {
- $this->addCommandMessage($name, $description);
+ if (!$option) {
+ return $this;
+ }
+ // have option information
+ if (\is_string($option)) {
+ $this->addCommandMessage($name, $option);
+ } elseif (\is_array($option)) {
+ $this->addCommandAliases($name, isset($option['aliases']) ? $option['aliases'] : null);
+ $this->addCommandMessage($name, isset($option['description']) ? $option['description'] : null);
}
return $this;
@@ -111,6 +130,7 @@ public function command($name, $handler = null, $description = null)
/**
* @param array $commands
+ * @throws \InvalidArgumentException
*/
public function commands(array $commands)
{
@@ -208,34 +228,29 @@ protected function getFileFilter()
*/
protected function dispatch($name)
{
- $sep = $this->delimiter ?: '/';
- //// is a command name
- if ($this->isCommand($name)) {
- return $this->runCommand($name, true);
+ $sep = $this->delimiter ?: ':';
+ // maybe is a command name
+ $realName = $this->getRealCommandName($name);
+ if ($this->isCommand($realName)) {
+ return $this->runCommand($realName, true);
}
- //// is a controller name
+ // maybe is a controller name
$action = '';
- // like 'home/index'
+ // like 'home:index'
if (strpos($name, $sep) > 0) {
- $input = array_filter(explode($sep, $name));
+ $input = array_values(array_filter(explode($sep, $name)));
list($name, $action) = \count($input) > 2 ? array_splice($input, 2) : $input;
}
- if ($this->isController($name)) {
- return $this->runAction($name, $action, true);
+ $realName = $this->getRealCommandName($name);
+ if ($this->isController($realName)) {
+ return $this->runAction($realName, $action, true);
}
// command not found
if (true !== self::fire(self::ON_NOT_FOUND, [$this])) {
- $this->output->liteError("The console command '{$name}' not exists!");
- // find similar command names by similar_text()
- $similar = [];
+ $this->output->liteError("The command '{$name}' is not exists in the console application!");
$commands = array_merge($this->getControllerNames(), $this->getCommandNames());
- foreach ($commands as $command) {
- similar_text($name, $command, $percent);
- if (45 <= (int)$percent) {
- $similar[] = $command;
- }
- }
- if ($similar) {
+ // find similar command names by similar_text()
+ if ($similar = Helper::findSimilar($name, $commands)) {
$this->write(sprintf("\nMaybe what you mean is:\n %s", implode(', ', $similar)));
} else {
$this->showCommandList(false);
@@ -261,6 +276,11 @@ public function runCommand($name, $believable = false)
/** @var \Closure|string $handler Command class */
$handler = $this->commands[$name];
if (\is_object($handler) && method_exists($handler, '__invoke')) {
+ if ($this->input->getSameOpt(['h', 'help'])) {
+ $des = $this->getCommandMessage($name, 'No command description message.');
+
+ return $this->output->write($des);
+ }
$status = $handler($this->input, $this->output);
} else {
if (!class_exists($handler)) {
diff --git a/src/Base/AbstractApplication.php b/src/Base/AbstractApplication.php
index 3bf531f..c4ced37 100644
--- a/src/Base/AbstractApplication.php
+++ b/src/Base/AbstractApplication.php
@@ -12,8 +12,9 @@
use Inhere\Console\IO\Input;
use Inhere\Console\IO\Output;
use Inhere\Console\Style\Style;
-use Inhere\Console\Traits\InputOutputTrait;
+use Inhere\Console\Traits\InputOutputAwareTrait;
use Inhere\Console\Traits\SimpleEventTrait;
+use Inhere\Console\Utils\FormatUtil;
use Inhere\Console\Utils\Helper;
/**
@@ -22,21 +23,17 @@
*/
abstract class AbstractApplication implements ApplicationInterface
{
- use InputOutputTrait, SimpleEventTrait;
- /**
- * @var array
- */
+ use InputOutputAwareTrait, SimpleEventTrait;
+ /** @var array */
protected static $internalCommands = ['version' => 'Show application version information', 'help' => 'Show application help information', 'list' => 'List all group and independent commands'];
- /**
- * @var array
- */
+ /** @var array */
protected static $internalOptions = ['--debug' => 'Setting the application runtime debug level', '--profile' => 'Display timing and memory usage information', '--no-color' => 'Disable color/ANSI for message output', '-h, --help' => 'Display this help message', '-V, --version' => 'Show application version information'];
/**
- * app meta config
+ * application meta info
* @var array
*/
private $meta = [
- 'name' => 'My Console',
+ 'name' => 'My Console Application',
'debug' => false,
'profile' => false,
'version' => '0.5.1',
@@ -47,20 +44,24 @@ abstract class AbstractApplication implements ApplicationInterface
// 'timeZone' => 'Asia/Shanghai',
// 'env' => 'pdt', // dev test pdt
// 'charset' => 'UTF-8',
+ 'logoText' => '',
+ 'logoStyle' => 'info',
// runtime stats
'_stats' => [],
];
/** @var string Command delimiter. e.g dev:serve */
public $delimiter = ':';
// '/' ':'
- /** @var array The group commands */
- protected $controllers = [];
+ /** @var string Current command name */
+ private $commandName;
+ /** @var array Some message for command */
+ private $commandMessages = [];
+ /** @var array Save command aliases */
+ private $commandAliases = [];
/** @var array The independent commands */
protected $commands = [];
- /** @var array */
- private $commandMessages = [];
- /** @var string */
- private $commandName;
+ /** @var array The group commands */
+ protected $controllers = [];
/**
* App constructor.
@@ -151,7 +152,7 @@ protected function afterRun()
if ($this->isProfile()) {
$title = '---------- Runtime Stats(profile=true) ----------';
$stats = $this->meta['_stats'];
- $this->meta['_stats'] = Helper::runtime($stats['startTime'], $stats['startMemory'], $stats);
+ $this->meta['_stats'] = FormatUtil::runtime($stats['startTime'], $stats['startMemory'], $stats);
$this->output->write('');
$this->output->aList($this->meta['_stats'], $title);
}
@@ -295,7 +296,7 @@ public function showHelpInfo($quit = true, $command = null)
}
$script = $this->input->getScript();
$sep = $this->delimiter;
- $this->output->helpPanel(['usage' => "{$script} {command} [arg0 arg1=value1 arg2=value2 ...] [--opt -v -h ...]", 'example' => ["{$script} test (run a independent command)", "{$script} home{$sep}index (run a command of the group)", "{$script} help {command} (see a command help information)", "{$script} home{$sep}index -h (see a command help of the group)"]], $quit);
+ $this->output->helpPanel(['usage' => "{$script} {command} [arg0 arg1=value1 arg2=value2 ...] [--opt -v -h ...]", 'example' => ["{$script} test (run a independent command)", "{$script} home{$sep}index (run a command of the group)", "{$script} help {command} (see a command help information)", "{$script} home{$sep}index -h (see a command help of the group)"]], $quit);
}
/**
@@ -304,14 +305,18 @@ public function showHelpInfo($quit = true, $command = null)
*/
public function showVersionInfo($quit = true)
{
+ $os = PHP_OS;
$date = date('Y.m.d');
+ $logo = '';
$name = $this->getMeta('name', 'Console Application');
$version = $this->getMeta('version', 'Unknown');
$publishAt = $this->getMeta('publishAt', 'Unknown');
$updateAt = $this->getMeta('updateAt', 'Unknown');
$phpVersion = PHP_VERSION;
- $os = PHP_OS;
- $this->output->aList(["\n {$name}, Version {$version}\n", 'System Info' => "PHP version {$phpVersion}, on {$os} system", 'Application Info' => "Update at {$updateAt}, publish at {$publishAt}(current {$date})"], null, ['leftChar' => '', 'sepChar' => ' : ']);
+ if ($logoTxt = $this->getLogoText()) {
+ $logo = Helper::wrapTag($logoTxt, $this->getLogoStyle());
+ }
+ $this->output->aList(["{$logo}\n {$name}, Version {$version}\n", 'System Info' => "PHP version {$phpVersion}, on {$os} system", 'Application Info' => "Update at {$updateAt}, publish at {$publishAt}(current {$date})"], null, ['leftChar' => '', 'sepChar' => ' : ']);
$quit && $this->stop();
}
@@ -326,20 +331,23 @@ public function showCommandList($quit = true)
$controllerArr = $commandArr = [];
$desPlaceholder = 'No description of the command';
// all console controllers
- $controllerArr[] = PHP_EOL . '- Group Commands';
+ $controllerArr[] = PHP_EOL . '- Group Commands';
$controllers = $this->controllers;
ksort($controllers);
foreach ($controllers as $name => $controller) {
$hasGroup = true;
/** @var AbstractCommand $controller */
- $controllerArr[$name] = $controller::getDescription() ?: $desPlaceholder;
+ $desc = $controller::getDescription() ?: $desPlaceholder;
+ $aliases = $this->getCommandAliases($name);
+ $extra = $aliases ? Helper::wrapTag(' [alias: ' . implode(',', $aliases) . ']', 'info') : '';
+ $controllerArr[$name] = $desc . $extra;
}
if (!$hasGroup) {
$controllerArr[] = '... No register any group command(controller)';
}
- // all independent commands
+ // all independent commands, Independent, Single, Alone
$commands = $this->commands;
- $commandArr[] = PHP_EOL . '- Independent Commands';
+ $commandArr[] = PHP_EOL . '- Alone Commands';
ksort($commands);
foreach ($commands as $name => $command) {
$desc = $desPlaceholder;
@@ -347,20 +355,16 @@ public function showCommandList($quit = true)
/** @var AbstractCommand $command */
if (is_subclass_of($command, CommandInterface::class)) {
$desc = $command::getDescription() ?: $desPlaceholder;
- } else {
- if ($msg = $this->getCommandMessage($name)) {
- $desc = $msg;
- } else {
- if (\is_string($command)) {
- $desc = 'A handler : ' . $command;
- } else {
- if (\is_object($command)) {
- $desc = 'A handler by ' . \get_class($command);
- }
- }
- }
+ } elseif ($msg = $this->getCommandMessage($name)) {
+ $desc = $msg;
+ } elseif (\is_string($command)) {
+ $desc = 'A handler : ' . $command;
+ } elseif (\is_object($command)) {
+ $desc = 'A handler by ' . \get_class($command);
}
- $commandArr[$name] = $desc;
+ $aliases = $this->getCommandAliases($name);
+ $extra = $aliases ? Helper::wrapTag(' [alias: ' . implode(',', $aliases) . ']', 'info') : '';
+ $commandArr[$name] = $desc . $extra;
}
if (!$hasCommand) {
$commandArr[] = '... No register any group command(controller)';
@@ -368,21 +372,9 @@ public function showCommandList($quit = true)
// built in commands
$internalCommands = static::$internalCommands;
ksort($internalCommands);
- array_unshift($internalCommands, "\n- Internal Commands");
- $this->output->mList([
- //'There are all console controllers and independent commands.',
- 'Usage:' => "{$script} {command} [arg0 arg1=value1 arg2=value2 ...] [--opt -v -h ...]",
- 'Options:' => self::$internalOptions,
- 'Available Commands:' => array_merge($controllerArr, $commandArr, $internalCommands),
- ]);
- // $this->output->mList([
- // //'There are all console controllers and independent commands.',
- // 'Usage:' => "$script {command} [arg0 arg1=value1 arg2=value2 ...] [--opt -v -h ...]",
- // 'Options:' => self::$internalOptions,
- // 'Group Commands:' => $controllerArr ?: '... No register any group command(controller)',
- // 'Independent Commands:' => $commandArr ?: '... No register any independent command',
- // 'Internal Commands:' => $internalCommands,
- // ]);
+ // built in options
+ $internalOptions = FormatUtil::alignmentOptions(self::$internalOptions);
+ $this->output->mList(['Usage:' => "{$script} {command} [arg0 arg1=value1 arg2=value2 ...] [--opt -v -h ...]", 'Options:' => $internalOptions, 'Internal Commands:' => $internalCommands, 'Available Commands:' => array_merge($controllerArr, $commandArr)], ['sepChar' => ' ']);
unset($controllerArr, $commandArr, $internalCommands);
$this->output->write("More command information, please use: {$script} {command} -h");
$quit && $this->stop();
@@ -401,11 +393,43 @@ public function getCommandMessage($name, $default = null)
/**
* @param string $name The command name
* @param string $message
- * @return string
+ * @return $this
*/
public function addCommandMessage($name, $message)
{
- return $this->commandMessages[$name] = $message;
+ if ($name && $message) {
+ $this->commandMessages[$name] = $message;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * @param string|array $aliases
+ * @return $this
+ */
+ public function addCommandAliases($name, $aliases)
+ {
+ if (!$name || !$aliases) {
+ return $this;
+ }
+ foreach ((array)$aliases as $alias) {
+ if ($alias = trim($alias)) {
+ $this->commandAliases[$alias] = $name;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * @return string
+ */
+ protected function getRealCommandName($name)
+ {
+ return isset($this->commandAliases[$name]) ? $this->commandAliases[$name] : $name;
}
/**********************************************************
* getter/setter methods
@@ -428,6 +452,7 @@ public function getCommandNames()
/**
* @param array $controllers
+ * @throws \InvalidArgumentException
*/
public function setControllers(array $controllers)
{
@@ -459,6 +484,7 @@ public function isController($name)
/**
* @param array $commands
+ * @throws \InvalidArgumentException
*/
public function setCommands(array $commands)
{
@@ -488,6 +514,50 @@ public function isCommand($name)
return isset($this->commands[$name]);
}
+ /**
+ * @return string|null
+ */
+ public function getLogoText()
+ {
+ return isset($this->meta['logoText']) ? $this->meta['logoText'] : null;
+ }
+
+ /**
+ * @param string $logoTxt
+ * @param string|null $style
+ */
+ public function setLogo($logoTxt, $style = null)
+ {
+ $this->meta['logoText'] = $logoTxt;
+ if ($style) {
+ $this->meta['logoStyle'] = $style;
+ }
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getLogoStyle()
+ {
+ return isset($this->meta['logoStyle']) ? $this->meta['logoStyle'] : 'info';
+ }
+
+ /**
+ * @param string $style
+ */
+ public function setLogoStyle($style)
+ {
+ $this->meta['logoStyle'] = $style;
+ }
+
+ /**
+ * @return string
+ */
+ public function getRootPath()
+ {
+ return $this->getMeta('rootPath');
+ }
+
/**
* @return array
*/
@@ -572,4 +642,25 @@ public function setCommandMessages(array $commandMessages)
{
$this->commandMessages = $commandMessages;
}
+
+ /**
+ * @param null|string $name
+ * @return array
+ */
+ public function getCommandAliases($name = null)
+ {
+ if (!$name) {
+ return $this->commandAliases;
+ }
+
+ return array_keys($this->commandAliases, $name, true);
+ }
+
+ /**
+ * @param array $commandAliases
+ */
+ public function setCommandAliases(array $commandAliases)
+ {
+ $this->commandAliases = $commandAliases;
+ }
}
\ No newline at end of file
diff --git a/src/Base/AbstractCommand.php b/src/Base/AbstractCommand.php
index aab4def..ff7347c 100644
--- a/src/Base/AbstractCommand.php
+++ b/src/Base/AbstractCommand.php
@@ -13,18 +13,19 @@
use Inhere\Console\IO\Input;
use Inhere\Console\IO\InputDefinition;
use Inhere\Console\IO\Output;
-use Inhere\Console\Traits\InputOutputTrait;
-use Inhere\Console\Traits\UserInteractTrait;
+use Inhere\Console\Traits\InputOutputAwareTrait;
+use Inhere\Console\Traits\UserInteractAwareTrait;
use Inhere\Console\Utils\Annotation;
/**
* Class AbstractCommand
* @package Inhere\Console
*/
-abstract class AbstractCommand implements CommandInterface
+abstract class AbstractCommand implements BaseCommandInterface
{
- use InputOutputTrait, UserInteractTrait;
- // name -> {$name}
+ use InputOutputAwareTrait, UserInteractAwareTrait;
+ const OK = 0;
+ // name -> {name}
const ANNOTATION_VAR = '{%s}';
// '{$%s}';
/**
@@ -56,6 +57,8 @@ abstract class AbstractCommand implements CommandInterface
private $definition;
/** @var string */
private $processTitle;
+ /** @var array */
+ private $annotationVars;
/**
* Command constructor.
@@ -71,6 +74,7 @@ public function __construct(Input $input, Output $output, InputDefinition $defin
$this->definition = $definition;
}
$this->init();
+ $this->annotationVars = $this->annotationVars();
}
protected function init()
@@ -104,8 +108,16 @@ protected function createDefinition()
*/
public function annotationVars()
{
- // e.g: `more info see {name}/index`
- return ['script' => $this->input->getScript(), 'command' => $this->input->getCommand(), 'fullCommand' => $this->input->getScript() . ' ' . $this->input->getCommand(), 'name' => self::getName()];
+ // e.g: `more info see {name}:index`
+ return [
+ 'name' => self::getName(),
+ 'group' => self::getName(),
+ 'script' => $this->input->getScript(),
+ // bin/app
+ 'command' => $this->input->getCommand(),
+ // demo OR home:test
+ 'fullCommand' => $this->input->getScript() . ' ' . $this->input->getCommand(),
+ ];
}
/**************************************************************************
* running a command
@@ -122,6 +134,7 @@ public function run($command = '')
if ($this->input->sameOpt(['h', 'help'])) {
return $this->showHelp();
}
+ // some prepare check
if (true !== $this->prepare()) {
return -1;
}
@@ -164,10 +177,13 @@ protected function afterExecute()
*/
protected function showHelp()
{
- // 创建了 InputDefinition , 则使用它的信息。
- // 不会再解析和使用命令的注释。
+ // 创建了 InputDefinition , 则使用它的信息。此时不会再解析和使用命令的注释。
if ($def = $this->getDefinition()) {
- $this->output->mList($def->getSynopsis());
+ $cmd = $this->input->getCommand();
+ $spt = $this->input->getScript();
+ $info = $def->getSynopsis();
+ $info['usage'] = "{$spt} {$cmd} " . $info['usage'];
+ $this->output->mList($info);
return true;
}
@@ -177,17 +193,16 @@ protected function showHelp()
/**
* prepare run
+ * @throws \RuntimeException
*/
protected function prepare()
{
if ($this->processTitle) {
if (\function_exists('cli_set_process_title')) {
if (false === @cli_set_process_title($this->processTitle)) {
- if ('Darwin' === PHP_OS) {
- $this->output->writeln('Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.');
- } else {
- $error = error_get_last();
- trigger_error($error['message'], E_USER_WARNING);
+ $error = error_get_last();
+ if ($error && 'Darwin' !== PHP_OS) {
+ throw new \RuntimeException($error['message']);
}
}
} elseif (\function_exists('setproctitle')) {
@@ -268,29 +283,65 @@ public function validateInput()
* helper methods
**************************************************************************/
/**
- * 为命令注解提供可解析解析变量. 可以在命令的注释中使用
+ * @param string $name
+ * @param string $value
+ */
+ protected function addAnnotationVar($name, $value)
+ {
+ if (!isset($this->annotationVars[$name])) {
+ $this->annotationVars[$name] = (string)$value;
+ }
+ }
+
+ /**
+ * @param array $map
+ */
+ protected function addAnnotationVars(array $map)
+ {
+ foreach ($map as $name => $value) {
+ $this->addAnnotationVar($name, $value);
+ }
+ }
+
+ /**
+ * @param string $name
+ * @param string $value
+ */
+ protected function setAnnotationVar($name, $value)
+ {
+ $this->annotationVars[$name] = (string)$value;
+ }
+
+ /**
+ * 替换注解中的变量为对应的值
* @param string $str
* @return string
*/
- protected function handleAnnotationVars($str)
+ protected function parseAnnotationVars($str)
{
- $map = [];
- foreach ($this->annotationVars() as $key => $value) {
- $key = sprintf(self::ANNOTATION_VAR, $key);
- $map[$key] = $value;
+ static $map;
+ if ($map === null) {
+ foreach ($this->annotationVars as $key => $value) {
+ $key = sprintf(self::ANNOTATION_VAR, $key);
+ $map[$key] = $value;
+ }
+ }
+ // not use vars
+ if (false === strpos($str, '{')) {
+ return $str;
}
return $map ? strtr($str, $map) : $str;
}
/**
- * show help by parse method annotation
+ * show help by parse method annotations
* @param string $method
* @param null|string $action
+ * @param array $aliases
* @return int
- * @throws \ReflectionException
*/
- protected function showHelpByMethodAnnotation($method, $action = null)
+ protected function showHelpByMethodAnnotations($method, $action = null, array $aliases = [])
{
$ref = new \ReflectionClass($this);
$name = $this->input->getCommand();
@@ -306,25 +357,20 @@ protected function showHelpByMethodAnnotation($method, $action = null)
return 0;
}
$doc = $ref->getMethod($method)->getDocComment();
- $tags = Annotation::tagList($this->handleAnnotationVars($doc));
- foreach ($tags as $tag => $msg) {
- if (!$msg || !\is_string($msg)) {
+ $tags = Annotation::getTags($this->parseAnnotationVars($doc));
+ $help = [];
+ if ($aliases) {
+ $help[] = sprintf("Alias Name: %s\n", implode(',', $aliases));
+ }
+ foreach (array_keys(self::$annotationTags) as $tag) {
+ if (empty($tags[$tag]) || !\is_string($tags[$tag])) {
continue;
}
- if (isset(self::$annotationTags[$tag])) {
- $msg = trim($msg);
- // need multi align
- // if (self::$annotationTags[$tag]) {
- // $lines = array_map(function ($line) {
- // // return trim($line);
- // return $line;
- // }, explode("\n", $msg));
- // $msg = implode("\n", array_filter($lines, 'trim'));
- // }
- $tag = ucfirst($tag);
- $this->write("{$tag}:\n {$msg}\n");
- }
+ $msg = trim($tags[$tag]);
+ $tag = ucfirst($tag);
+ $help[] = "{$tag}:\n {$msg}\n";
}
+ $this->output->write(implode("\n", $help), false);
return 0;
}
@@ -371,6 +417,16 @@ public static function getAnnotationTags()
return self::$annotationTags;
}
+ /**
+ * @param string $name
+ */
+ public static function addAnnotationTag($name)
+ {
+ if (!isset(self::$annotationTags[$name])) {
+ self::$annotationTags[$name] = true;
+ }
+ }
+
/**
* @param array $annotationTags
* @param bool $replace
@@ -396,6 +452,14 @@ public function setDefinition(InputDefinition $definition)
$this->definition = $definition;
}
+ /**
+ * @return array
+ */
+ public function getAnnotationVars()
+ {
+ return $this->annotationVars;
+ }
+
/**
* @return ApplicationInterface
*/
diff --git a/src/Base/ApplicationInterface.php b/src/Base/ApplicationInterface.php
index fe7cea8..ed6be6a 100644
--- a/src/Base/ApplicationInterface.php
+++ b/src/Base/ApplicationInterface.php
@@ -47,9 +47,33 @@ public function runCommand($name, $believable = false);
*/
public function runAction($name, $action, $believable = false, $standAlone = false);
- public function controller($name, $controller = null);
+ /**
+ * Register a app group command(by controller)
+ * @param string $name The controller name
+ * @param string $class The controller class
+ * @param null|array|string $option
+ * string: define the description message.
+ * array:
+ * - aliases The command aliases
+ * - description The description message
+ * @return static
+ * @throws \InvalidArgumentException
+ */
+ public function controller($name, $class = null, $option = null);
- public function command($name, $handler = null, $description = null);
+ /**
+ * Register a app independent console command
+ * @param string|CommandInterface $name
+ * @param string|\Closure|CommandInterface $handler
+ * @param null|array|string $option
+ * string: define the description message.
+ * array:
+ * - aliases The command aliases
+ * - description The description message
+ * @return $this
+ * @throws \InvalidArgumentException
+ */
+ public function command($name, $handler = null, $option = null);
public function showCommandList($quit = true);
}
\ No newline at end of file
diff --git a/src/Base/BaseCommandInterface.php b/src/Base/BaseCommandInterface.php
new file mode 100644
index 0000000..13535cd
--- /dev/null
+++ b/src/Base/BaseCommandInterface.php
@@ -0,0 +1,46 @@
+showHelpByMethodAnnotation('execute');
+ return $this->showHelpByMethodAnnotations('execute');
}
}
\ No newline at end of file
diff --git a/src/Components/ArtFont.php b/src/Components/ArtFont.php
new file mode 100644
index 0000000..a546a84
--- /dev/null
+++ b/src/Components/ArtFont.php
@@ -0,0 +1,269 @@
+
+ */
+ private $groups = [];
+ /**
+ * @var array
+ * [
+ * group => [ name => path ]
+ * ]
+ */
+ private $fonts = [];
+ /**
+ * @var array
+ * [
+ * name => content
+ * ]
+ */
+ private $fontContents = [];
+
+ /**
+ * @return self
+ */
+ public static function create()
+ {
+ if (!self::$instance) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * ArtFont constructor.
+ */
+ public function __construct()
+ {
+ $this->loadInternalFonts();
+ }
+
+ /**
+ * load Internal Fonts
+ */
+ protected function loadInternalFonts()
+ {
+ $path = \dirname(__DIR__) . '/BuiltIn/Resources/art-fonts/';
+ $group = self::INTERNAL_GROUP;
+ foreach (self::$internalFonts as $font) {
+ $this->fonts[$group][$font] = $path . $font . '%s.txt';
+ }
+ $this->groups[$group] = $path;
+ }
+
+ /**
+ * display the internal art font
+ * @param string $name
+ * @param array $opts
+ * @return bool
+ */
+ public function showInternal($name, array $opts = [])
+ {
+ return $this->show($name, self::INTERNAL_GROUP, $opts);
+ }
+
+ /**
+ * @param string $name
+ * @param string $group
+ * @return string
+ */
+ public function showItalic($name, $group = null, array $opts = [])
+ {
+ $opts['type'] = 'italic';
+
+ return $this->show($name . '_italic', $group, $opts);
+ }
+
+ /**
+ * display the art font
+ * @param string $name
+ * @param string $group
+ * @param array $opts
+ * contains:
+ * - type => '', // 'italic'
+ * - indent => 2,
+ * - style => '', // 'info' 'error'
+ * @return bool
+ */
+ public function show($name, $group = null, array $opts = [])
+ {
+ $opts = array_merge(['type' => '', 'indent' => 2, 'style' => ''], $opts);
+ $type = $opts['type'];
+ $pfxType = $type ? '_' . $type : '';
+ $txt = '';
+ $group = trim($group);
+ $group = $group ?: self::DEFAULT_GROUP;
+ $longKey = $group . '.' . $name . $pfxType;
+ if (isset($this->fontContents[$longKey])) {
+ $txt = $this->fontContents[$longKey];
+ } elseif (isset($this->fonts[$group][$name])) {
+ $font = sprintf($this->fonts[$group][$name], $pfxType);
+ if (is_file($font)) {
+ $txt = file_get_contents($font);
+ }
+ } elseif (isset($this->groups[$group])) {
+ $font = $this->groups[$group] . $name . $pfxType . '.txt';
+ if (is_file($font)) {
+ $txt = file_get_contents($font);
+ }
+ }
+ // var_dump($txt, $this);
+ if ($txt) {
+ return Show::write(Helper::wrapTag($txt, $opts['style']));
+ }
+
+ return false;
+ }
+
+ /**
+ * @param string $name
+ * @param string $group
+ * @return string
+ */
+ public function font($name, $group = null)
+ {
+ return '';
+ }
+
+ /**
+ * @param string $group
+ * @param string $path
+ * @return $this
+ */
+ public function addGroup($group, $path)
+ {
+ $group = trim($group, '_');
+ if (!$group || !is_dir($path)) {
+ return $this;
+ }
+ if (!isset($this->groups[$group])) {
+ $this->groups[$group] = $path;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $group
+ * @param string $path
+ * @return $this
+ */
+ public function setGroup($group, $path)
+ {
+ $group = trim($group, '_');
+ if (!$group || !is_dir($path)) {
+ return $this;
+ }
+ $this->groups[$group] = $path;
+
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * @param string $file font file path
+ * @param string|null $group
+ * @return $this
+ */
+ public function addFont($name, $file, $group = null)
+ {
+ $group = $group ?: self::DEFAULT_GROUP;
+ if (is_file($file)) {
+ $info = pathinfo($file);
+ $ext = !empty($info['extension']) ? $info['extension'] : 'txt';
+ $this->fonts[$group][$name] = $info['dirname'] . '/' . $info['filename'] . '.' . $ext;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * @param string $content
+ * @return $this
+ */
+ public function addFontContent($name, $content)
+ {
+ if ($name && ($content = trim($content))) {
+ $this->fontContents[$name] = $content;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * @return bool
+ */
+ public static function isInternalFont($name)
+ {
+ return \in_array((string)$name, self::$internalFonts, true);
+ }
+
+ /**
+ * @return array
+ */
+ public static function getInternalFonts()
+ {
+ return self::$internalFonts;
+ }
+
+ /**
+ * @return array
+ */
+ public function getGroups()
+ {
+ return $this->groups;
+ }
+
+ /**
+ * @param array $groups
+ */
+ public function setGroups(array $groups)
+ {
+ $this->groups = array_merge($this->groups, $groups);
+ }
+
+ /**
+ * @return array
+ */
+ public function getFonts()
+ {
+ return $this->fonts;
+ }
+
+ /**
+ * @param array $fonts
+ */
+ public function setFonts(array $fonts)
+ {
+ foreach ($fonts as $name => $font) {
+ $this->addFont($name, $font);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Components/AryBuffer.php b/src/Components/AryBuffer.php
new file mode 100644
index 0000000..a6b85aa
--- /dev/null
+++ b/src/Components/AryBuffer.php
@@ -0,0 +1,116 @@
+body[] = $content;
+ }
+ }
+
+ /**
+ * @param string $content
+ */
+ public function write($content)
+ {
+ $this->body[] = $content;
+ }
+
+ /**
+ * @param string $content
+ */
+ public function append($content)
+ {
+ $this->write($content);
+ }
+
+ /**
+ * @param string $content
+ */
+ public function prepend($content)
+ {
+ array_unshift($this->body, $content);
+ }
+
+ /**
+ * clear
+ */
+ public function clear()
+ {
+ $this->body = [];
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getBody()
+ {
+ return $this->body;
+ }
+
+ /**
+ * @param string[] $body
+ */
+ public function setBody(array $body)
+ {
+ $this->body = $body;
+ }
+
+ /**
+ * @return string
+ */
+ public function toString()
+ {
+ return implode($this->delimiter, $this->body);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * @return string
+ */
+ public function getDelimiter()
+ {
+ return $this->delimiter;
+ }
+
+ /**
+ * @param string $delimiter
+ */
+ public function setDelimiter($delimiter)
+ {
+ $this->delimiter = $delimiter;
+ }
+}
\ No newline at end of file
diff --git a/src/Components/AutoCompletion.php b/src/Components/AutoComplete/AutoCompletion.php
similarity index 90%
rename from src/Components/AutoCompletion.php
rename to src/Components/AutoComplete/AutoCompletion.php
index 2add048..363de33 100644
--- a/src/Components/AutoCompletion.php
+++ b/src/Components/AutoComplete/AutoCompletion.php
@@ -7,12 +7,12 @@
* Time: 17:56
*/
-namespace Inhere\Console\Components;
+namespace Inhere\Console\Components\AutoComplete;
/**
* Class AutoCompletion - a simple command auto-completion tool
* @todo not available
- * @package Inhere\Console\Components
+ * @package Inhere\Console\Components\AutoComplete
*/
class AutoCompletion
{
@@ -54,9 +54,9 @@ public function register()
*/
public function completionHandler($input, $index)
{
- $info = readline_info();
- $line = substr($info['line_buffer'], 0, $info['end']);
- $tokens = token_get_all('data;
diff --git a/src/Components/AutoComplete/ScriptGenerator.php b/src/Components/AutoComplete/ScriptGenerator.php
new file mode 100644
index 0000000..c8ed78f
--- /dev/null
+++ b/src/Components/AutoComplete/ScriptGenerator.php
@@ -0,0 +1,126 @@
+getRenderer();
+
+ return $tt->render(file_get_contents($tplFile), $vars, $dstFile);
+ }
+
+ /**
+ * @return int
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * @param int $type
+ */
+ public function setType($type)
+ {
+ if (\in_array($type, self::typeList(), 1)) {
+ $this->type = $type;
+ }
+ }
+
+ /**
+ * @return int
+ */
+ public function getMode()
+ {
+ return $this->mode;
+ }
+
+ /**
+ * @param int $mode
+ */
+ public function setMode($mode)
+ {
+ if (\in_array($mode, self::modeList(), 1)) {
+ $this->mode = $mode;
+ }
+ }
+
+ /**
+ * @return TextTemplate
+ */
+ public function getRenderer()
+ {
+ if (!$this->renderer) {
+ $this->renderer = new TextTemplate();
+ }
+
+ return $this->renderer;
+ }
+
+ /**
+ * @param TextTemplate $renderer
+ */
+ public function setRenderer(TextTemplate $renderer)
+ {
+ $this->renderer = $renderer;
+ }
+}
\ No newline at end of file
diff --git a/src/Utils/Download.php b/src/Components/Download.php
similarity index 86%
rename from src/Utils/Download.php
rename to src/Components/Download.php
index a04dc2b..e23444f 100644
--- a/src/Utils/Download.php
+++ b/src/Components/Download.php
@@ -7,13 +7,15 @@
* Time: 19:08
*/
-namespace Inhere\Console\Utils;
+namespace Inhere\Console\Components;
+
+use Inhere\Console\Utils\Show;
/**
* Class Download
- * @package Inhere\Console\Utils
+ * @package Inhere\Console\Components
*/
-class Download
+final class Download
{
const PROGRESS_TEXT = 'text';
const PROGRESS_BAR = 'bar';
@@ -61,10 +63,14 @@ public function __construct($url, $saveAs, $type = self::PROGRESS_TEXT)
$this->showType = $type === self::PROGRESS_BAR ? self::PROGRESS_BAR : self::PROGRESS_TEXT;
}
+ /**
+ * start download
+ * @return $this
+ */
public function start()
{
if (!$this->url || !$this->saveAs) {
- Show::error("Please the property 'url' and 'saveAs'.", 1);
+ Show::liteError("Please the property 'url' and 'saveAs'.", 1);
}
$ctx = stream_context_create();
// register stream notification callback
@@ -75,22 +81,13 @@ public function start()
Show::write("\nDone!");
} else {
$err = error_get_last();
- Show::error("\nErr.rrr..orr...\n {$err['message']}\n", 1);
+ Show::liteError("\nErr.rrr..orr...\n {$err['message']}\n", 1);
}
$this->fileSize = null;
return $this;
}
- /*
- progressBar() OUT:
- Connected...
- Mime-type: text/html; charset=utf-8
- Being redirected to: http://no2.php.net/distributions/php-5.2.5.tar.bz2
- Connected...
- FileSize: 7773024
- Mime-type: application/octet-stream
- [========================================> ] 40% (3076/7590 kb)
- */
+
/**
* @param int $notifyCode stream notify code
* @param int $severity severity code
@@ -99,7 +96,7 @@ public function start()
* @param int $transferredBytes Have been transferred bytes
* @param int $maxBytes Target max length bytes
*/
- protected function progressShow($notifyCode, $severity, $message, $messageCode, $transferredBytes, $maxBytes)
+ public function progressShow($notifyCode, $severity, $message, $messageCode, $transferredBytes, $maxBytes)
{
$msg = '';
switch ($notifyCode) {
@@ -138,7 +135,7 @@ protected function progressShow($notifyCode, $severity, $message, $messageCode,
* @param $transferredBytes
* @return string
*/
- protected function showProgressByType($transferredBytes)
+ public function showProgressByType($transferredBytes)
{
if ($transferredBytes <= 0) {
return '';
diff --git a/src/Components/ExecComparator.php b/src/Components/ExecComparator.php
new file mode 100644
index 0000000..4ff1dcc
--- /dev/null
+++ b/src/Components/ExecComparator.php
@@ -0,0 +1,42 @@
+vars;
+ }
+
+ /**
+ * @param array $vars
+ */
+ public function setVars(array $vars)
+ {
+ $this->vars = $vars;
+ }
+}
\ No newline at end of file
diff --git a/src/Components/Formatter/Formatter.php b/src/Components/Formatter/Formatter.php
new file mode 100644
index 0000000..5cd197a
--- /dev/null
+++ b/src/Components/Formatter/Formatter.php
@@ -0,0 +1,18 @@
+speed;
+ }
+
+ /**
+ * @param int $speed
+ */
+ public function setSpeed($speed)
+ {
+ $this->speed = (int)$speed;
+ }
+}
\ No newline at end of file
diff --git a/src/Components/Progress/Bar.php b/src/Components/Progress/Bar.php
new file mode 100644
index 0000000..0379887
--- /dev/null
+++ b/src/Components/Progress/Bar.php
@@ -0,0 +1,18 @@
+body;
}
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
}
\ No newline at end of file
diff --git a/src/Utils/AnsiCode.php b/src/Components/Terminal.php
similarity index 95%
rename from src/Utils/AnsiCode.php
rename to src/Components/Terminal.php
index 9a9c723..ab22be3 100644
--- a/src/Utils/AnsiCode.php
+++ b/src/Components/Terminal.php
@@ -7,16 +7,18 @@
* Time: 9:35
*/
-namespace Inhere\Console\Utils;
+namespace Inhere\Console\Components;
+
+use Inhere\Console\Utils\Show;
/**
- * Class AnsiCode terminal
- * @package Inhere\Console\Utils
+ * Class Terminal - terminal control by ansiCode
+ * @package Inhere\Console\Components
* 2K 清除本行
* \x0D = \r = 13 回车,回到行首
* ESC = \x1B = 27
*/
-final class AnsiCode
+final class Terminal
{
const BEGIN_CHAR = "\33[";
const END_CHAR = "\33[0m";
@@ -109,8 +111,8 @@ public static function make()
/**
* build ansi code string
* ```
- * AnsiCode::build(null, 'u'); // "\033[s" Saves the current cursor position
- * AnsiCode::build(0); // "\033[0m" Build end char, Resets any ANSI format
+ * Terminal::build(null, 'u'); // "\033[s" Saves the current cursor position
+ * Terminal::build(0); // "\033[0m" Build end char, Resets any ANSI format
* ```
* @param mixed $format
* @param string $type
diff --git a/src/Components/TextTemplate.php b/src/Components/TextTemplate.php
new file mode 100644
index 0000000..8b5feed
--- /dev/null
+++ b/src/Components/TextTemplate.php
@@ -0,0 +1,191 @@
+setVars($vars);
+ }
+ }
+
+ /**
+ * @param string $tplFile
+ * @param array $vars
+ * @param null|string $saveAs
+ * @return string|bool
+ */
+ public function renderFile($tplFile, array $vars = [], $saveAs = null)
+ {
+ if (!\is_file($tplFile)) {
+ throw new \InvalidArgumentException("Template file not exists. FILE: {$tplFile}");
+ }
+
+ return $this->render(file_get_contents($tplFile), $vars, $saveAs);
+ }
+
+ /**
+ * @param string $template
+ * @param array $vars
+ * @param null|string $saveAs
+ * @return string
+ */
+ public function render($template, array $vars = [], $saveAs = null)
+ {
+ if (!$template || false === strpos($template, $this->openChar)) {
+ return $template;
+ }
+ if ($this->vars) {
+ $vars = array_merge($this->vars, $vars);
+ }
+ $pairs = $map = [];
+ $this->expandVars($vars, $map);
+ foreach ($map as $name => $value) {
+ $key = $this->openChar . $name . $this->closeChar;
+ $pairs[$key] = $value;
+ }
+ // replace vars to values.
+ $rendered = strtr($template, $pairs);
+ if (!$saveAs) {
+ return $rendered;
+ }
+ $dstDir = \dirname($saveAs);
+ if (!is_dir($dstDir) && !mkdir($dstDir, 0775, true) && !is_dir($dstDir)) {
+ throw new \RuntimeException(sprintf('Directory "%s" was not created', $dstDir));
+ }
+
+ return (bool)file_put_contents($saveAs, $rendered);
+ }
+
+ /**
+ * Multidimensional array expansion to one dimension array
+ * @param array $vars
+ * @param null|string $prefix
+ * @param array $map
+ */
+ protected function expandVars(array $vars, array &$map = [], $prefix = null)
+ {
+ foreach ($vars as $name => $value) {
+ $key = $prefix !== null ? $prefix . '.' . $name : $name;
+ if (is_scalar($value)) {
+ $map[$key] = $value;
+ } elseif (\is_array($value)) {
+ $this->expandVars($value, $map, (string)$key);
+ }
+ }
+ }
+
+ /**
+ * @param string $name
+ * @param null $default
+ * @return mixed
+ */
+ public function getVar($name, $default = null)
+ {
+ return isset($this->vars[$name]) ? $this->vars[$name] : $default;
+ }
+
+ /**
+ * @param string $name
+ * @param mixed $value
+ */
+ public function addVar($name, $value)
+ {
+ if (!isset($this->vars[$name])) {
+ $this->vars[$name] = $value;
+ }
+ }
+
+ /**
+ * @param string $name
+ * @param mixed $value
+ */
+ public function setVar($name, $value)
+ {
+ $this->vars[$name] = $value;
+ }
+
+ /**
+ * @param array $vars
+ */
+ public function addVars(array $vars)
+ {
+ $this->vars = array_merge($this->vars, $vars);
+ }
+
+ /**
+ * @return array
+ */
+ public function getVars()
+ {
+ return $this->vars;
+ }
+
+ /**
+ * @param array $vars
+ */
+ public function setVars(array $vars)
+ {
+ $this->vars = $vars;
+ }
+
+ /**
+ * @return string
+ */
+ public function getOpenChar()
+ {
+ return $this->openChar;
+ }
+
+ /**
+ * @param string $openChar
+ */
+ public function setOpenChar($openChar)
+ {
+ if ($openChar) {
+ $this->openChar = $openChar;
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getCloseChar()
+ {
+ return $this->closeChar;
+ }
+
+ /**
+ * @param string $closeChar
+ */
+ public function setCloseChar($closeChar)
+ {
+ if ($closeChar) {
+ $this->closeChar = $closeChar;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Controller.php b/src/Controller.php
index f57ab1f..07ab70f 100644
--- a/src/Controller.php
+++ b/src/Controller.php
@@ -14,6 +14,7 @@
use Inhere\Console\IO\Input;
use Inhere\Console\IO\Output;
use Inhere\Console\Utils\Annotation;
+use Inhere\Console\Utils\FormatUtil;
use Inhere\Console\Utils\Helper;
/**
@@ -22,19 +23,30 @@
*/
abstract class Controller extends AbstractCommand implements ControllerInterface
{
+ /** @var array */
+ private static $aliases;
/** @var string */
private $action;
/** @var string */
+ private $delimiter = ':';
+ // '/' ':'
+ /** @var bool */
+ private $standAlone = false;
+ /** @var string */
private $defaultAction = 'help';
/** @var string */
private $actionSuffix = 'Command';
/** @var string */
protected $notFoundCallback = 'notFound';
- /** @var string */
- protected $delimiter = ':';
- // '/' ':'
- /** @var bool */
- private $standAlone = false;
+
+ /**
+ * define command alias map
+ * @return array
+ */
+ protected static function commandAliases()
+ {
+ return [];
+ }
/**
* @param string $command
@@ -42,7 +54,8 @@ abstract class Controller extends AbstractCommand implements ControllerInterface
*/
public function run($command = '')
{
- if (!($this->action = trim($command))) {
+ $this->action = $this->getRealCommandName(trim($command, $this->delimiter));
+ if (!$this->action) {
return $this->showHelp();
}
@@ -52,7 +65,7 @@ public function run($command = '')
/**
* load command configure
*/
- protected function configure()
+ protected final function configure()
{
if ($action = $this->action) {
$method = $action . 'Configure';
@@ -67,11 +80,10 @@ protected function configure()
* @param Input $input
* @param Output $output
* @return mixed
- * @throws \ReflectionException
*/
protected function execute($input, $output)
{
- $action = Helper::camelCase(trim($this->action ?: $this->defaultAction, $this->delimiter));
+ $action = FormatUtil::camelCase(trim($this->action ?: $this->defaultAction, $this->delimiter));
$method = $this->actionSuffix ? $action . ucfirst($this->actionSuffix) : $action;
// the action method exists and only allow access public method.
if (method_exists($this, $method) && (($rfm = new \ReflectionMethod($this, $method)) && $rfm->isPublic())) {
@@ -84,14 +96,8 @@ protected function execute($input, $output)
$group = static::getName();
$status = -1;
$output->liteError("Sorry, The command '{$action}' not exist of the group '{$group}'!");
- // find similar command names by similar_text()
- $similar = [];
- foreach ($this->getAllCommandMethods() as $cmd => $refM) {
- similar_text($action, $cmd, $percent);
- if (45 <= (int)$percent) {
- $similar[] = $cmd;
- }
- }
+ // find similar command names
+ $similar = Helper::findSimilar($action, $this->getAllCommandMethods(null, true));
if ($similar) {
$output->write(sprintf("\nMaybe what you mean is:\n %s", implode(', ', $similar)));
} else {
@@ -104,7 +110,6 @@ protected function execute($input, $output)
/**
* @return int
- * @throws \ReflectionException
*/
protected function showHelp()
{
@@ -117,15 +122,17 @@ protected function showHelp()
/**
* Show help of the controller command group or specified command action
- * @usage {name}/[command] -h OR {command} [command] OR {name} [command] -h
+ * @usage {name}:[command] -h OR {command} [command] OR {name} [command] -h
+ * @options
+ * -s, --search Search command by input keywords
+ * --format Set the help information dump format(raw, xml, json, markdown)
* @example
* {script} {name} -h
- * {script} {name}/help
- * {script} {name}/help index
- * {script} {name}/index -h
+ * {script} {name}:help
+ * {script} {name}:help index
+ * {script} {name}:index -h
* {script} {name} index
* @return int
- * @throws \ReflectionException
*/
public final function helpCommand()
{
@@ -136,29 +143,36 @@ public final function helpCommand()
return 0;
}
- $action = Helper::camelCase($action);
+ $action = FormatUtil::camelCase($action);
$method = $this->actionSuffix ? $action . ucfirst($this->actionSuffix) : $action;
+ $aliases = self::getCommandAliases($action);
// show help info for a command.
- return $this->showHelpByMethodAnnotation($method, $action);
+ return $this->showHelpByMethodAnnotations($method, $action, $aliases);
}
/**
* show command list of the controller class
- * @throws \ReflectionException
*/
public final function showCommandList()
{
$ref = new \ReflectionClass($this);
$sName = lcfirst(self::getName() ?: $ref->getShortName());
if (!($classDes = self::getDescription())) {
- $classDes = Annotation::description($ref->getDocComment()) ?: 'No Description for the console controller';
+ $classDes = Annotation::description($ref->getDocComment()) ?: 'No description for the console controller';
}
$commands = [];
+ $defCommandDes = 'No description message';
foreach ($this->getAllCommandMethods($ref) as $cmd => $m) {
- $desc = Annotation::firstLine($m->getDocComment());
+ $desc = Annotation::firstLine($m->getDocComment()) ?: $defCommandDes;
+ // is a annotation tag
+ if ($desc[0] === '@') {
+ $desc = $defCommandDes;
+ }
if ($cmd) {
- $commands[$cmd] = $desc;
+ $aliases = self::getCommandAliases($cmd);
+ $extra = $aliases ? Helper::wrapTag(' [alias: ' . implode(',', $aliases) . ']', 'info') : '';
+ $commands[$cmd] = $desc . $extra;
}
}
// sort commands
@@ -171,26 +185,29 @@ public final function showCommandList()
$script = $this->getScriptName();
if ($this->standAlone) {
$name = $sName . ' ';
- $usage = "{$script} {command} [arguments] [options]";
+ $usage = "{$script} {command} [arguments ...] [options ...]";
} else {
$name = $sName . $this->delimiter;
- $usage = "{$script} {$name}{command} [arguments] [options]";
+ $usage = "{$script} {$name}{command} [arguments ...] [options ...]";
}
+ $this->output->startBuffer();
+ $this->output->write(ucfirst($classDes) . PHP_EOL);
$this->output->mList([
- 'Description:' => $classDes,
'Usage:' => $usage,
//'Group Name:' => "$sName",
+ 'Options:' => ['-h, --help' => 'Show help of the command group or specified command action'],
'Commands:' => $commands,
- 'Options:' => ['-h,--help' => 'Show help of the command group or specified command action'],
- ]);
+ ], ['sepChar' => ' ']);
$this->write(sprintf("More information about a command, please use: {$script} {$name}{command} -h", $this->standAlone ? ' ' . $name : ''));
+ $this->output->flush();
}
/**
* @param \ReflectionClass|null $ref
+ * @param bool $onlyName
* @return \Generator
*/
- protected function getAllCommandMethods(\ReflectionClass $ref = null)
+ protected function getAllCommandMethods(\ReflectionClass $ref = null, $onlyName = false)
{
$ref = $ref ?: new \ReflectionObject($this);
$suffix = $this->actionSuffix;
@@ -200,13 +217,47 @@ protected function getAllCommandMethods(\ReflectionClass $ref = null)
if ($m->isPublic() && substr($mName, -$suffixLen) === $suffix) {
// suffix is empty ?
$cmd = $suffix ? substr($mName, 0, -$suffixLen) : $mName;
- (yield $cmd => $m);
+ if ($onlyName) {
+ (yield $cmd);
+ } else {
+ (yield $cmd => $m);
+ }
}
}
}
+
+ /**
+ * @param string $name
+ * @return mixed|string
+ */
+ protected function getRealCommandName($name)
+ {
+ if (!$name) {
+ return $name;
+ }
+ $map = self::getCommandAliases();
+
+ return isset($map[$name]) ? $map[$name] : $name;
+ }
/**************************************************************************
* getter/setter methods
**************************************************************************/
+ /**
+ * @param string|null $name
+ * @return array
+ */
+ public static function getCommandAliases($name = null)
+ {
+ if (null === self::$aliases) {
+ self::$aliases = static::commandAliases();
+ }
+ if ($name) {
+ return self::$aliases ? array_keys(self::$aliases, $name, true) : [];
+ }
+
+ return self::$aliases;
+ }
+
/**
* @return string
*/
@@ -222,7 +273,7 @@ public function getAction()
public function setAction($action)
{
if ($action) {
- $this->action = Helper::camelCase($action);
+ $this->action = FormatUtil::camelCase($action);
}
return $this;
diff --git a/src/IO/FixedInput.php b/src/IO/FixedInput.php
new file mode 100644
index 0000000..9b87201
--- /dev/null
+++ b/src/IO/FixedInput.php
@@ -0,0 +1,101 @@
+ has value
+ 'h' => false,
+ 'V' => false,
+ 'help' => false,
+ 'debug' => true,
+ 'profile' => false,
+ 'version' => false,
+ ];
+ /** @var array */
+ private $cleanedTokens;
+
+ /**
+ * FixedInput constructor.
+ * @param null|array $argv
+ */
+ public function __construct($argv = null)
+ {
+ if (null === $argv) {
+ $argv = $_SERVER['argv'];
+ }
+ parent::__construct($argv, false);
+ $copy = $argv;
+ // command name
+ if (!empty($copy[1]) && $copy[1][0] !== '-' && false === strpos($copy[1], '=')) {
+ $this->setCommand($copy[1]);
+ // unset command
+ unset($copy[1]);
+ }
+ // pop script name
+ array_shift($copy);
+ $this->cleanedTokens = $copy;
+ $this->collectPreParsed($copy);
+ }
+
+ private function collectPreParsed(array $tokens)
+ {
+ foreach ($this->preParsed as $name => $hasVal) {
+ }
+ }
+
+ /**
+ * @param array $allowArray
+ * @param array $noValues
+ */
+ public function parseTokens(array $allowArray = [], array $noValues = [])
+ {
+ $params = $this->getTokens();
+ array_shift($params);
+ // pop script name
+ }
+
+ /**
+ * @return array
+ */
+ public function getPreParsed()
+ {
+ return $this->preParsed;
+ }
+
+ /**
+ * @param array $preParsed
+ */
+ public function setPreParsed(array $preParsed)
+ {
+ $this->preParsed = $preParsed;
+ }
+
+ /**
+ * @return array|null
+ */
+ public function getCleanedTokens()
+ {
+ return $this->cleanedTokens;
+ }
+}
\ No newline at end of file
diff --git a/src/IO/Input.php b/src/IO/Input.php
index a9b5ba6..d407de9 100644
--- a/src/IO/Input.php
+++ b/src/IO/Input.php
@@ -9,23 +9,24 @@
namespace Inhere\Console\IO;
-use Inhere\Console\Utils\ArgumentOptionParse;
+use Inhere\Console\Utils\CommandLine;
/**
- * Class Input
+ * Class Input - the input information. by parse global var $argv.
* @package Inhere\Console\IO
*/
class Input implements InputInterface
{
/**
- * @var @resource
+ * @var resource
*/
- protected $inputStream = STDIN;
+ protected $inputStream = \STDIN;
/**
- * @var
+ * @var string
*/
private $pwd;
/**
+ * eg `./examples/app home:useArg status=2 name=john arg0 -s=test --page=23`
* @var string
*/
private $fullScript;
@@ -65,19 +66,22 @@ class Input implements InputInterface
/**
* Input constructor.
* @param null|array $argv
+ * @param bool $parsing
*/
- public function __construct($argv = null)
+ public function __construct($argv = null, $parsing = true)
{
if (null === $argv) {
$argv = $_SERVER['argv'];
}
$this->pwd = $this->getPwd();
+ $this->tokens = $argv;
$this->fullScript = implode(' ', $argv);
$this->script = array_shift($argv);
- $this->tokens = $argv;
- list($this->args, $this->sOpts, $this->lOpts) = ArgumentOptionParse::byArgv($argv);
- // collect command `server`
- $this->command = isset($this->args[0]) ? array_shift($this->args) : null;
+ if ($parsing) {
+ list($this->args, $this->sOpts, $this->lOpts) = CommandLine::parseByArgv($argv);
+ // collect command. it is first argument.
+ $this->command = isset($this->args[0]) ? array_shift($this->args) : null;
+ }
}
/**
@@ -87,10 +91,10 @@ public function __toString()
{
$tokens = array_map(function ($token) {
if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
- return $match[1] . ArgumentOptionParse::escapeToken($match[2]);
+ return $match[1] . CommandLine::escapeToken($match[2]);
}
if ($token && $token[0] !== '-') {
- return ArgumentOptionParse::escapeToken($token);
+ return CommandLine::escapeToken($token);
}
return $token;
@@ -107,13 +111,15 @@ public function __toString()
*/
public function read($question = null, $nl = false)
{
- fwrite(STDOUT, $question . ($nl ? "\n" : ''));
+ if ($question) {
+ fwrite(\STDOUT, $question . ($nl ? "\n" : ''));
+ }
return trim(fgets($this->inputStream));
}
- /////////////////////////////////////////////////////////////////////////////////////////
- /// arguments (eg: name=john city=chengdu)
- /////////////////////////////////////////////////////////////////////////////////////////
+ /***************************************************************************
+ * arguments (eg: arg0 name=john city=chengdu)
+ ***************************************************************************/
/**
* @return array
*/
@@ -612,4 +618,12 @@ public function getPwd()
return $this->pwd;
}
+
+ /**
+ * @return array
+ */
+ public function getTokens()
+ {
+ return $this->tokens;
+ }
}
\ No newline at end of file
diff --git a/src/IO/InputDefinition.php b/src/IO/InputDefinition.php
index 197956c..b1bdd7e 100644
--- a/src/IO/InputDefinition.php
+++ b/src/IO/InputDefinition.php
@@ -16,6 +16,8 @@
*/
class InputDefinition
{
+ /** @var array */
+ private static $defaultArgOptConfig = ['mode' => null, 'description' => '', 'default' => null];
private $example;
private $description;
/**
@@ -23,17 +25,20 @@ class InputDefinition
*/
private $arguments;
private $requiredCount = 0;
- private $hasAnArrayArgument = false;
private $hasOptional = false;
+ private $hasAnArrayArgument = false;
/**
* @var array[]
*/
private $options;
- /**
- * @var array
- */
+ /** @var array */
private $shortcuts;
+ /**
+ * @param array $arguments
+ * @param array $options
+ * @return InputDefinition
+ */
public static function make(array $arguments = [], array $options = [])
{
return new self($arguments, $options);
@@ -68,9 +73,8 @@ public function setArguments(array $arguments)
*/
public function addArguments(array $arguments)
{
- $def = ['mode' => null, 'description' => '', 'default' => null];
foreach ($arguments as $name => $arg) {
- $arg = array_merge($def, $arg);
+ $arg = $this->mergeArgOptConfig($arg);
$this->addArgument($name, $arg['mode'], $arg['description'], $arg['default']);
}
@@ -197,9 +201,8 @@ public function setOptions(array $options = [])
*/
public function addOptions(array $options = [])
{
- $def = ['mode' => null, 'description' => '', 'default' => null];
foreach ($options as $name => $opt) {
- $opt = array_merge($def, $opt);
+ $opt = $this->mergeArgOptConfig($opt);
$this->addOption($name, $opt['mode'], $opt['description'], $opt['default']);
}
}
@@ -341,6 +344,15 @@ private function shortcutToName($shortcut)
return $this->shortcuts[$shortcut];
}
+ /**
+ * @param array $map
+ * @return array
+ */
+ private function mergeArgOptConfig(array $map)
+ {
+ return array_merge(self::$defaultArgOptConfig, $map);
+ }
+
/**
* Gets the synopsis.
* @param bool $short 简化版显示
@@ -357,7 +369,7 @@ public function getSynopsis($short = false)
if ($this->optionIsAcceptValue($option['mode'])) {
$value = sprintf(' %s%s%s', $option['optional'] ? '[' : '', strtoupper($name), $option['optional'] ? ']' : '');
}
- $shortcut = $option['shortcut'] ? sprintf('-%s|', $option['shortcut']) : '';
+ $shortcut = $option['shortcut'] ? sprintf('-%s, ', $option['shortcut']) : '';
$elements[] = sprintf('[%s--%s%s]', $shortcut, $name, $value);
$key = "{$shortcut}--{$name}";
$opts[$key] = ($option['required'] ? '*' : '') . $option['description'];
@@ -380,7 +392,7 @@ public function getSynopsis($short = false)
$elements[] = $element;
$args[$name] = $des;
}
- $opts['-h|--help'] = 'Show help information for the command';
+ $opts['-h, --help'] = 'Show help information for the command';
return ['description' => $this->description, 'usage' => implode(' ', $elements), 'arguments' => $args, 'options' => $opts, 'example' => $this->example];
}
diff --git a/src/IO/Output.php b/src/IO/Output.php
index 41b2aa0..797629f 100644
--- a/src/IO/Output.php
+++ b/src/IO/Output.php
@@ -10,7 +10,7 @@
namespace Inhere\Console\IO;
use Inhere\Console\Style\Style;
-use Inhere\Console\Traits\FormatOutputTrait;
+use Inhere\Console\Traits\FormatOutputAwareTrait;
use Inhere\Console\Utils\Helper;
use Inhere\Console\Utils\Show;
@@ -20,17 +20,17 @@
*/
class Output implements OutputInterface
{
- use FormatOutputTrait;
+ use FormatOutputAwareTrait;
/**
* 正常输出流
* Property outStream.
*/
- protected $outputStream = STDOUT;
+ protected $outputStream = \STDOUT;
/**
* 错误输出流
* Property errorStream.
*/
- protected $errorStream = STDERR;
+ protected $errorStream = \STDERR;
/**
* 控制台窗口(字体/背景)颜色添加处理
* window colors
@@ -49,9 +49,46 @@ public function __construct($outputStream = null)
}
$this->getStyle();
}
- /////////////////////////////////////////////////////////////////
- /// Output Message
- /////////////////////////////////////////////////////////////////
+ /***************************************************************************
+ * Output buffer
+ ***************************************************************************/
+ /**
+ * start buffering
+ */
+ public function startBuffer()
+ {
+ Show::startBuffer();
+ }
+
+ /**
+ * clear buffering
+ */
+ public function clearBuffer()
+ {
+ Show::clearBuffer();
+ }
+
+ /**
+ * stop buffering and flush buffer text
+ * {@inheritdoc}
+ * @see Show::stopBuffer()
+ */
+ public function stopBuffer($flush = true, $nl = false, $quit = false, array $opts = [])
+ {
+ Show::stopBuffer($flush, $nl, $quit, $opts);
+ }
+
+ /**
+ * stop buffering and flush buffer text
+ * {@inheritdoc}
+ */
+ public function flush($nl = false, $quit = false, array $opts = [])
+ {
+ $this->stopBuffer(true, $nl, $quit, $opts);
+ }
+ /***************************************************************************
+ * Output Message
+ ***************************************************************************/
/**
* 读取输入信息
* @param string $question 若不为空,则先输出文本
@@ -64,7 +101,7 @@ public function read($question = null, $nl = false)
$this->write($question, $nl);
}
- return trim(fgets(STDIN));
+ return trim(fgets(\STDIN));
}
/**
@@ -80,9 +117,9 @@ public function stderr($text = '', $nl = true)
return $this;
}
- /////////////////////////////////////////////////////////////////
- /// Getter/Setter
- /////////////////////////////////////////////////////////////////
+ /***************************************************************************
+ * Getter/Setter
+ ***************************************************************************/
/**
* @return Style
*/
diff --git a/src/LiteApplication.php b/src/LiteApp.php
similarity index 92%
rename from src/LiteApplication.php
rename to src/LiteApp.php
index 6f74b4c..3c35258 100644
--- a/src/LiteApplication.php
+++ b/src/LiteApp.php
@@ -12,14 +12,14 @@
use Inhere\Console\Style\LiteStyle;
/**
- * Class LiteApplication
+ * Class LiteApp - Lite Application
* @package Inhere\Console
*/
-class LiteApplication
+class LiteApp
{
- ///////////////////////////////////////////////////////////////////
- /// simple cli support
- ///////////////////////////////////////////////////////////////////
+ /****************************************************************************
+ * simple cli support
+ ****************************************************************************/
/**
* parse from `name=val var2=val2`
* @var array
@@ -191,9 +191,9 @@ public function commands(array $commands)
$this->addCommand($command, $handler, $des);
}
}
- ///////////////////////////////////////////////////////////////////////////////////
- /// helper methods
- ///////////////////////////////////////////////////////////////////////////////////
+ /****************************************************************************
+ * helper methods
+ ****************************************************************************/
/**
* @param string $err
*/
@@ -232,9 +232,9 @@ public function getOpt($name, $default = null)
{
return isset($this->opts[$name]) ? $this->opts[$name] : $default;
}
- ///////////////////////////////////////////////////////////////////////////////////
- /// getter/setter methods
- ///////////////////////////////////////////////////////////////////////////////////
+ /****************************************************************************
+ * getter/setter methods
+ ****************************************************************************/
/**
* @return array
*/
diff --git a/src/Style/Color.php b/src/Style/Color.php
index 8139470..782cf2d 100644
--- a/src/Style/Color.php
+++ b/src/Style/Color.php
@@ -14,14 +14,14 @@
*/
final class Color
{
- /**
- * Foreground base value
- */
+ /** Foreground base value */
const FG_BASE = 30;
- /**
- * Background base value
- */
+ /** Background base value */
const BG_BASE = 40;
+ /** Extra Foreground base value */
+ const FG_EXTRA = 90;
+ /** Extra Background base value */
+ const BG_EXTRA = 100;
// color
const BLACK = 'black';
const RED = 'red';
@@ -48,9 +48,7 @@ final class Color
// 颠倒的 交换背景色与前景色
const CONCEALED = 'concealed';
// 隐匿的
- /**
- * Known color list
- */
+ /** @var array Known color list */
private static $knownColors = array(
'black' => 0,
'red' => 1,
@@ -64,10 +62,7 @@ final class Color
'white' => 7,
'normal' => 9,
);
- /**
- * Known style option
- * @var array
- */
+ /** @var array Known style option */
private static $knownOptions = [
'bold' => 1,
// 22 加粗
@@ -83,41 +78,37 @@ final class Color
// 27 颠倒的 交换背景色与前景色
'concealed' => 8,
];
- /**
- * Foreground color
- */
+ /** @var int Foreground color */
private $fgColor = 0;
- /**
- * Background color
- */
+ /** @var int Background color */
private $bgColor = 0;
- /**
- * Array of style options
- */
+ /** @var array Array of style options */
private $options = [];
/**
* @param string $fg
* @param string $bg
* @param array $options
+ * @param bool $extra
* @return Color
*/
- public static function make($fg = '', $bg = '', array $options = [])
+ public static function make($fg = '', $bg = '', array $options = [], $extra = false)
{
- return new self($fg, $bg, $options);
+ return new self($fg, $bg, $options, $extra);
}
/**
* Create a color style from a parameter string.
- * @param string $string e.g 'fg=white;bg=black;options=bold,underscore'
+ * @param string $string e.g 'fg=white;bg=black;options=bold,underscore;extra=1'
* @return static
* @throws \RuntimeException
*/
public static function makeByString($string)
{
$fg = $bg = '';
+ $extra = false;
$options = [];
- $parts = explode(';', $string);
+ $parts = explode(';', str_replace(' ', '', $string));
foreach ($parts as $part) {
$subParts = explode('=', $part);
if (\count($subParts) < 2) {
@@ -130,6 +121,9 @@ public static function makeByString($string)
case 'bg':
$bg = $subParts[1];
break;
+ case 'extra':
+ $extra = $subParts[1];
+ break;
case 'options':
$options = explode(',', $subParts[1]);
break;
@@ -139,7 +133,7 @@ public static function makeByString($string)
}
}
- return new self($fg, $bg, $options);
+ return new self($fg, $bg, $options, $extra);
}
/**
@@ -147,21 +141,21 @@ public static function makeByString($string)
* @param string $fg Foreground color. e.g 'white'
* @param string $bg Background color. e.g 'black'
* @param array $options Style options. e.g ['bold', 'underscore']
- * @throws \InvalidArgumentException
+ * @param bool $extra
*/
- public function __construct($fg = '', $bg = '', array $options = [])
+ public function __construct($fg = '', $bg = '', array $options = [], $extra = false)
{
if ($fg) {
if (false === array_key_exists($fg, static::$knownColors)) {
throw new \InvalidArgumentException(sprintf('Invalid foreground color "%1$s" [%2$s]', $fg, implode(', ', $this->getKnownColors())));
}
- $this->fgColor = self::FG_BASE + static::$knownColors[$fg];
+ $this->fgColor = ($extra ? self::FG_EXTRA : self::FG_BASE) + static::$knownColors[$fg];
}
if ($bg) {
if (false === array_key_exists($bg, static::$knownColors)) {
throw new \InvalidArgumentException(sprintf('Invalid background color "%1$s" [%2$s]', $bg, implode(', ', $this->getKnownColors())));
}
- $this->bgColor = self::BG_BASE + static::$knownColors[$bg];
+ $this->bgColor = ($extra ? self::BG_EXTRA : self::BG_BASE) + static::$knownColors[$bg];
}
foreach ($options as $option) {
if (false === array_key_exists($option, static::$knownOptions)) {
diff --git a/src/Style/Highlighter.php b/src/Style/Highlighter.php
index f61e99d..c190eb7 100644
--- a/src/Style/Highlighter.php
+++ b/src/Style/Highlighter.php
@@ -12,8 +12,263 @@
/**
* Class Highlighter
* @package Inhere\Console\Style
- * @referrer jakub-onderka/php-console-highlighter
+ * @see jakub-onderka/php-console-highlighter
+ * @link https://github.com/JakubOnderka/PHP-Console-Highlighter/blob/master/src/Highlighter.php
*/
class Highlighter
{
+ const TOKEN_DEFAULT = 'token_default';
+ const TOKEN_COMMENT = 'token_comment';
+ const TOKEN_STRING = 'token_string';
+ const TOKEN_HTML = 'token_html';
+ const TOKEN_KEYWORD = 'token_keyword';
+ const ACTUAL_LINE_MARK = 'actual_line_mark';
+ const LINE_NUMBER = 'line_number';
+ /** @var Style */
+ private $color;
+ /** @var array */
+ private $defaultTheme = [self::TOKEN_STRING => 'red', self::TOKEN_COMMENT => 'yellow', self::TOKEN_KEYWORD => 'info', self::TOKEN_DEFAULT => 'normal', self::TOKEN_HTML => 'cyan', self::ACTUAL_LINE_MARK => 'red', self::LINE_NUMBER => 'darkGray'];
+
+ /**
+ * @param Style $color
+ */
+ public function __construct(Style $color = null)
+ {
+ $this->color = $color ?: Style::create();
+ }
+
+ /**
+ * @param string $source
+ * @param bool $withLn with line number
+ * @return string
+ */
+ public function highlight($source, $withLn = false)
+ {
+ $tokenLines = $this->getHighlightedLines($source);
+ $lines = $this->colorLines($tokenLines);
+ if ($withLn) {
+ return $this->lineNumbers($lines);
+ }
+
+ return implode(PHP_EOL, $lines);
+ }
+
+ /**
+ * @param string $source
+ * @param int $lineNumber
+ * @param int $linesBefore
+ * @param int $linesAfter
+ * @return string
+ * @throws \InvalidArgumentException
+ */
+ public function getCodeSnippet($source, $lineNumber, $linesBefore = 2, $linesAfter = 2)
+ {
+ $tokenLines = $this->getHighlightedLines($source);
+ $offset = $lineNumber - $linesBefore - 1;
+ $offset = max($offset, 0);
+ $length = $linesAfter + $linesBefore + 1;
+ $tokenLines = \array_slice($tokenLines, $offset, $length, $preserveKeys = true);
+ $lines = $this->colorLines($tokenLines);
+
+ return $this->lineNumbers($lines, $lineNumber);
+ }
+
+ /**
+ * @param string $source
+ * @return string
+ * @throws \InvalidArgumentException
+ */
+ public function getWholeFile($source)
+ {
+ $tokenLines = $this->getHighlightedLines($source);
+ $lines = $this->colorLines($tokenLines);
+
+ return implode(PHP_EOL, $lines);
+ }
+
+ /**
+ * @param string $source
+ * @return string
+ * @throws \InvalidArgumentException
+ */
+ public function getWholeFileWithLineNumbers($source)
+ {
+ $tokenLines = $this->getHighlightedLines($source);
+ $lines = $this->colorLines($tokenLines);
+
+ return $this->lineNumbers($lines);
+ }
+
+ /**
+ * @param string $source
+ * @return array
+ */
+ private function getHighlightedLines($source)
+ {
+ $source = str_replace(array("\r\n", "\r"), "\n", $source);
+ $tokens = $this->tokenize($source);
+
+ return $this->splitToLines($tokens);
+ }
+
+ /**
+ * @param string $source
+ * @return array
+ */
+ private function tokenize($source)
+ {
+ $buffer = '';
+ $output = [];
+ $tokens = token_get_all($source);
+ $newType = $currentType = null;
+ foreach ($tokens as $token) {
+ if (\is_array($token)) {
+ switch ($token[0]) {
+ case T_INLINE_HTML:
+ $newType = self::TOKEN_HTML;
+ break;
+ case T_COMMENT:
+ case T_DOC_COMMENT:
+ $newType = self::TOKEN_COMMENT;
+ break;
+ case T_ENCAPSED_AND_WHITESPACE:
+ case T_CONSTANT_ENCAPSED_STRING:
+ $newType = self::TOKEN_STRING;
+ break;
+ case T_WHITESPACE:
+ break;
+ case T_OPEN_TAG:
+ case T_OPEN_TAG_WITH_ECHO:
+ case T_CLOSE_TAG:
+ case T_STRING:
+ case T_VARIABLE:
+ // Constants
+ // Constants
+ case T_DIR:
+ case T_FILE:
+ case T_METHOD_C:
+ case T_DNUMBER:
+ case T_LNUMBER:
+ case T_NS_C:
+ case T_LINE:
+ case T_CLASS_C:
+ case T_FUNC_C:
+ //case T_TRAIT_C:
+ $newType = self::TOKEN_DEFAULT;
+ break;
+ default:
+ // Compatibility with PHP 5.3
+ if (\defined('T_TRAIT_C') && $token[0] === T_TRAIT_C) {
+ $newType = self::TOKEN_DEFAULT;
+ } else {
+ $newType = self::TOKEN_KEYWORD;
+ }
+ }
+ } else {
+ $newType = $token === '"' ? self::TOKEN_STRING : self::TOKEN_KEYWORD;
+ }
+ if ($currentType === null) {
+ $currentType = $newType;
+ }
+ if ($currentType != $newType) {
+ $output[] = [$currentType, $buffer];
+ $buffer = '';
+ $currentType = $newType;
+ }
+ $buffer .= \is_array($token) ? $token[1] : $token;
+ }
+ if (null !== $newType) {
+ $output[] = [$newType, $buffer];
+ }
+
+ return $output;
+ }
+
+ /**
+ * @param array $tokens
+ * @return array
+ */
+ private function splitToLines(array $tokens)
+ {
+ $lines = $line = [];
+ foreach ($tokens as $token) {
+ foreach (explode("\n", $token[1]) as $count => $tokenLine) {
+ if ($count > 0) {
+ $lines[] = $line;
+ $line = [];
+ }
+ if ($tokenLine === '') {
+ continue;
+ }
+ $line[] = [$token[0], $tokenLine];
+ }
+ }
+ $lines[] = $line;
+
+ return $lines;
+ }
+
+ /**
+ * @param array[] $tokenLines
+ * @return array
+ * @throws \InvalidArgumentException
+ */
+ private function colorLines(array $tokenLines)
+ {
+ $lines = [];
+ foreach ($tokenLines as $lineCount => $tokenLine) {
+ $line = '';
+ // foreach ($tokenLine as $token) {
+ foreach ($tokenLine as list($tokenType, $tokenValue)) {
+ $style = $this->defaultTheme[$tokenType];
+ if ($this->color->hasStyle($style)) {
+ $line .= $this->color->apply($style, $tokenValue);
+ } else {
+ $line .= $tokenValue;
+ }
+ }
+ $lines[$lineCount] = $line;
+ }
+
+ return $lines;
+ }
+
+ /**
+ * @param array $lines
+ * @param null|int $markLine
+ * @return string
+ */
+ private function lineNumbers(array $lines, $markLine = null)
+ {
+ end($lines);
+ $lineStrlen = \strlen(key($lines) + 1);
+ $snippet = '';
+ $lmStyle = $this->defaultTheme[self::ACTUAL_LINE_MARK];
+ $lnStyle = $this->defaultTheme[self::LINE_NUMBER];
+ foreach ($lines as $i => $line) {
+ if ($markLine !== null) {
+ $snippet .= $markLine === $i + 1 ? $this->color->apply($lmStyle, ' > ') : ' ';
+ }
+ $snippet .= $this->color->apply($lnStyle, str_pad($i + 1, $lineStrlen, ' ', STR_PAD_LEFT) . '| ');
+ $snippet .= $line . PHP_EOL;
+ }
+
+ return $snippet;
+ }
+
+ /**
+ * @return array
+ */
+ public function getDefaultTheme()
+ {
+ return $this->defaultTheme;
+ }
+
+ /**
+ * @param array $defaultTheme
+ */
+ public function setDefaultTheme(array $defaultTheme)
+ {
+ $this->defaultTheme = array_merge($this->defaultTheme, $defaultTheme);
+ }
}
\ No newline at end of file
diff --git a/src/Style/LiteStyle.php b/src/Style/LiteStyle.php
index 4e3a2a1..1004543 100644
--- a/src/Style/LiteStyle.php
+++ b/src/Style/LiteStyle.php
@@ -35,7 +35,7 @@ class LiteStyle
const FG_LIGHT_BLUE = 94;
const FG_LIGHT_MAGENTA = 95;
const FG_LIGHT_CYAN = 96;
- const FG_WHITE_W = 97;
+ const FG_WHITE_EXTRA = 97;
// Background color
const BG_BLACK = 40;
const BG_RED = 41;
@@ -53,7 +53,7 @@ class LiteStyle
const BG_LIGHT_BLUE = 104;
const BG_LIGHT_MAGENTA = 105;
const BG_LIGHT_CYAN = 106;
- const BG_WHITE_W = 107;
+ const BG_WHITE_EXTRA = 107;
// color option
const BOLD = 1;
// 加粗
@@ -79,12 +79,8 @@ class LiteStyle
* @var array
*/
const STYLES = [
- 'light_red' => '1;31',
- 'light_green' => '1;32',
'yellow' => '1;33',
- 'light_blue' => '1;34',
'magenta' => '1;35',
- 'light_cyan' => '1;36',
'white' => '1;37',
'black' => '0;30',
'red' => '0;31',
@@ -92,6 +88,19 @@ class LiteStyle
'brown' => '0;33',
'blue' => '0;34',
'cyan' => '0;36',
+ 'light_red' => '1;31',
+ 'light_blue' => '1;34',
+ 'light_gray' => '37',
+ 'light_green' => '1;32',
+ 'light_cyan' => '1;36',
+ 'dark_gray' => '90',
+ 'light_red_ex' => '91',
+ 'light_green_ex' => '92',
+ 'light_yellow' => '93',
+ 'light_blue_ex' => '94',
+ 'light_magenta' => '95',
+ 'light_cyan_ex' => '96',
+ 'white_ex' => '97',
'bold' => '1',
'underscore' => '4',
'reverse' => '7',
@@ -110,7 +119,7 @@ class LiteStyle
];
/**
- * @param $text
+ * @param string $text
* @param string|int|array $style
* @return string
*/
@@ -119,7 +128,7 @@ public static function color($text, $style = null)
if (!$text) {
return $text;
}
- if (!Helper::isSupportColor()) {
+ if (!Helper::supportColor()) {
return self::clearColor($text);
}
if (\is_string($style)) {
@@ -141,7 +150,7 @@ public static function color($text, $style = null)
/**
* render color tag to color style
- * @param $text
+ * @param string $text
* @return mixed|string
*/
public static function renderColor($text)
@@ -150,7 +159,7 @@ public static function renderColor($text)
return $text;
}
// if don't support output color text, clear color tag.
- if (!Helper::isSupportColor()) {
+ if (!Helper::supportColor()) {
return static::clearColor($text);
}
if (!preg_match_all(self::COLOR_TAG, $text, $matches)) {
diff --git a/src/Style/Style.php b/src/Style/Style.php
index fc153b8..4355f52 100644
--- a/src/Style/Style.php
+++ b/src/Style/Style.php
@@ -17,6 +17,12 @@
* Class Style
* @package Inhere\Console\Style
* @link https://github.com/ventoviro/windwalker-IO
+ * @method string info(string $message)
+ * @method string comment(string $message)
+ * @method string success(string $message)
+ * @method string warning(string $message)
+ * @method string danger(string $message)
+ * @method string error(string $message)
*/
class Style
{
@@ -40,11 +46,11 @@ class Style
* Regex to match tags
* @var string
*/
- const COLOR_TAG = '/<([a-z=;]+)>(.*?)<\\/\\1>/s';
+ const COLOR_TAG = '/<([a-zA-Z=;]+)>(.*?)<\\/\\1>/s';
/**
* Regex used for removing color codes
*/
- const STRIP_TAG = '/<[\\/]?[a-z=;]+>/';
+ const STRIP_TAG = '/<[\\/]?[a-zA-Z=;]+>/';
/**
* @var self
*/
@@ -86,13 +92,38 @@ public function __construct($fg = '', $bg = '', array $options = [])
$this->loadDefaultStyles();
}
+ /**
+ * @param string $method
+ * @param array $args
+ * @return mixed|string
+ * @throws \InvalidArgumentException
+ */
+ public function __call($method, array $args)
+ {
+ if (isset($args[0]) && $this->hasStyle($method)) {
+ return $this->format(sprintf('<%s>%s%s>', $method, $args[0], $method));
+ }
+ throw new \InvalidArgumentException("You called method is not exists: {$method}");
+ }
+
/**
* Adds predefined color styles to the Color styles
* default primary success info warning danger
*/
protected function loadDefaultStyles()
{
- $this->add(self::NORMAL, ['fg' => 'normal'])->add(self::FAINTLY, ['fg' => 'normal', 'options' => ['italic']])->add(self::BOLD, ['options' => ['bold']])->add(self::INFO, ['fg' => 'green'])->add(self::NOTE, ['fg' => 'green', 'options' => ['bold']])->add(self::PRIMARY, ['fg' => 'blue'])->add(self::SUCCESS, ['fg' => 'green', 'options' => ['bold']])->add(self::NOTICE, ['options' => ['bold', 'underscore']])->add(self::WARNING, ['fg' => 'black', 'bg' => 'yellow'])->add(self::COMMENT, ['fg' => 'yellow'])->add(self::QUESTION, ['fg' => 'black', 'bg' => 'cyan'])->add(self::DANGER, ['fg' => 'red'])->add(self::ERROR, ['fg' => 'black', 'bg' => 'red'])->add('underline', ['fg' => 'normal', 'options' => ['underscore']])->add('blue', ['fg' => 'blue'])->add('cyan', ['fg' => 'cyan'])->add('magenta', ['fg' => 'magenta'])->add('red', ['fg' => 'red'])->add('yellow', ['fg' => 'yellow']);
+ $this->add(self::NORMAL, ['fg' => 'normal'])->add(self::FAINTLY, ['fg' => 'normal', 'options' => ['italic']])->add(self::BOLD, ['options' => ['bold']])->add(self::INFO, ['fg' => 'green'])->add(self::NOTE, ['fg' => 'cyan', 'options' => ['bold']])->add(self::PRIMARY, ['fg' => 'yellow', 'options' => ['bold']])->add(self::SUCCESS, ['fg' => 'green', 'options' => ['bold']])->add(self::NOTICE, ['options' => ['bold', 'underscore']])->add(self::WARNING, ['fg' => 'black', 'bg' => 'yellow'])->add(self::COMMENT, ['fg' => 'yellow'])->add(self::QUESTION, ['fg' => 'black', 'bg' => 'cyan'])->add(self::DANGER, ['fg' => 'red'])->add(self::ERROR, ['fg' => 'black', 'bg' => 'red'])->add('underline', ['fg' => 'normal', 'options' => ['underscore']])->add('blue', ['fg' => 'blue'])->add('cyan', ['fg' => 'cyan'])->add('magenta', ['fg' => 'magenta'])->add('red', ['fg' => 'red'])->add('darkGray', ['fg' => 'black', 'extra' => true])->add('yellow', ['fg' => 'yellow']);
+ }
+
+ /**
+ * Process a string use style
+ * @param string $style
+ * @param $text
+ * @return string
+ */
+ public function apply($style, $text)
+ {
+ return $this->format(Helper::wrapTag($text, $style));
}
/**
@@ -159,18 +190,20 @@ public static function stripColor($string)
// $text = strip_tags($text);
return preg_replace(self::STRIP_TAG, '', $string);
}
- ///////////////////////////////////////// Attr Color Style /////////////////////////////////////////
-
+ /****************************************************************************
+ * Attr Color Style
+ ****************************************************************************/
/**
* Add a style.
- * @param string $name
- * @param string|Color|array $fg 前景色|也可以穿入Color对象|也可以是style配置数组(@see self::addByArray())
- * 当它为Color对象或配置数组时,后面两个参数无效
- * @param string $bg 背景色
- * @param array $options 其它选项
+ * @param string $name
+ * @param string|Color|array $fg 前景色|Color对象|也可以是style配置数组(@see self::addByArray())
+ * 当它为Color对象或配置数组时,后面两个参数无效
+ * @param string $bg 背景色
+ * @param array $options 其它选项
+ * @param bool $extra
* @return $this
*/
- public function add($name, $fg = '', $bg = '', array $options = [])
+ public function add($name, $fg = '', $bg = '', array $options = [], $extra = false)
{
if (\is_array($fg)) {
return $this->addByArray($name, $fg);
@@ -178,7 +211,7 @@ public function add($name, $fg = '', $bg = '', array $options = [])
if (\is_object($fg) && $fg instanceof Color) {
$this->styles[$name] = $fg;
} else {
- $this->styles[$name] = Color::make($fg, $bg, $options);
+ $this->styles[$name] = Color::make($fg, $bg, $options, $extra);
}
return $this;
@@ -186,21 +219,23 @@ public function add($name, $fg = '', $bg = '', array $options = [])
/**
* Add a style by an array config
- * @param $name
+ * @param string $name
* @param array $styleConfig 样式设置信息
- * e.g [
- * 'fg' => 'white',
- * 'bg' => 'black',
- * 'options' => ['bold', 'underscore']
- * ]
+ * e.g
+ * [
+ * 'fg' => 'white',
+ * 'bg' => 'black',
+ * 'extra' => true,
+ * 'options' => ['bold', 'underscore']
+ * ]
* @return $this
*/
public function addByArray($name, array $styleConfig)
{
- $style = ['fg' => '', 'bg' => '', 'options' => []];
+ $style = ['fg' => '', 'bg' => '', 'extra' => false, 'options' => []];
$config = array_merge($style, $styleConfig);
- list($fg, $bg, $options) = array_values($config);
- $this->styles[$name] = Color::make($fg, $bg, $options);
+ list($fg, $bg, $extra, $options) = array_values($config);
+ $this->styles[$name] = Color::make($fg, $bg, $options, $extra);
return $this;
}
diff --git a/src/Traits/AdvancedFormatOutputTrait.php b/src/Traits/AdvancedFormatOutputTrait.php
new file mode 100644
index 0000000..f02b656
--- /dev/null
+++ b/src/Traits/AdvancedFormatOutputTrait.php
@@ -0,0 +1,18 @@
+fonts[$name] = $content;
- }
- }
-
- /**
- * @param string $path
- */
- public function addPath($path)
- {
- if (file_exists($path)) {
- $this->artPaths[] = $path;
- }
- }
-
- /**
- * @return array
- */
- public function getArtPaths()
- {
- return $this->artPaths;
- }
-
- /**
- * @param array $artPaths
- */
- public function setArtPaths(array $artPaths)
- {
- foreach ($artPaths as $path) {
- $this->addPath($path);
- }
- }
-
- /**
- * @return array
- */
- public function getFonts()
- {
- return $this->fonts;
- }
-
- /**
- * @param array $fonts
- */
- public function setFonts(array $fonts)
- {
- foreach ($fonts as $name => $font) {
- $this->addFont($name, $font);
- }
- }
-}
\ No newline at end of file
diff --git a/src/Utils/CliUtil.php b/src/Utils/CliUtil.php
new file mode 100644
index 0000000..4d806fb
--- /dev/null
+++ b/src/Utils/CliUtil.php
@@ -0,0 +1,208 @@
+> \"{$logfile}\" 2>&1", $dummy, $retVal);
+ if ($retVal !== 0) {
+ throw new \RuntimeException("command exited with status '{$retVal}'.");
+ }
+
+ return $dummy;
+ }
+
+ /**
+ * Method to execute a command in the sys
+ * Uses :
+ * 1. system
+ * 2. passthru
+ * 3. exec
+ * 4. shell_exec
+ * @param $command
+ * @param bool $returnStatus
+ * @return array|string
+ */
+ public static function runCommand($command, $returnStatus = true)
+ {
+ $return_var = 1;
+ //system
+ if (\function_exists('system')) {
+ ob_start();
+ system($command, $return_var);
+ $output = ob_get_contents();
+ ob_end_clean();
+ // passthru
+ } elseif (\function_exists('passthru')) {
+ ob_start();
+ passthru($command, $return_var);
+ $output = ob_get_contents();
+ ob_end_clean();
+ //exec
+ } else {
+ if (\function_exists('exec')) {
+ exec($command, $output, $return_var);
+ $output = implode("\n", $output);
+ //shell_exec
+ } else {
+ if (\function_exists('shell_exec')) {
+ $output = shell_exec($command);
+ } else {
+ $output = 'Command execution not possible on this system';
+ $return_var = 0;
+ }
+ }
+ }
+ if ($returnStatus) {
+ return ['output' => trim($output), 'status' => $return_var];
+ }
+
+ return trim($output);
+ }
+
+ /**
+ * @return string
+ */
+ public static function getTempDir()
+ {
+ // @codeCoverageIgnoreStart
+ if (\function_exists('sys_get_temp_dir')) {
+ $tmp = sys_get_temp_dir();
+ } elseif (!empty($_SERVER['TMP'])) {
+ $tmp = $_SERVER['TMP'];
+ } elseif (!empty($_SERVER['TEMP'])) {
+ $tmp = $_SERVER['TEMP'];
+ } elseif (!empty($_SERVER['TMPDIR'])) {
+ $tmp = $_SERVER['TMPDIR'];
+ } else {
+ $tmp = getcwd();
+ }
+
+ // @codeCoverageIgnoreEnd
+ return $tmp;
+ }
+
+ /**
+ * get screen size
+ * ```php
+ * list($width, $height) = Helper::getScreenSize();
+ * ```
+ * @from Yii2
+ * @param boolean $refresh whether to force checking and not re-use cached size value.
+ * This is useful to detect changing window size while the application is running but may
+ * not get up to date values on every terminal.
+ * @return array|boolean An array of ($width, $height) or false when it was not able to determine size.
+ */
+ public static function getScreenSize($refresh = false)
+ {
+ static $size;
+ if ($size !== null && !$refresh) {
+ return $size;
+ }
+ if (self::bashIsAvailable()) {
+ // try stty if available
+ $stty = [];
+ if (exec('stty -a 2>&1', $stty) && preg_match('/rows\\s+(\\d+);\\s*columns\\s+(\\d+);/mi', implode(' ', $stty), $matches)) {
+ return $size = [$matches[2], $matches[1]];
+ }
+ // fallback to tput, which may not be updated on terminal resize
+ if (($width = (int)exec('tput cols 2>&1')) > 0 && ($height = (int)exec('tput lines 2>&1')) > 0) {
+ return $size = [$width, $height];
+ }
+ // fallback to ENV variables, which may not be updated on terminal resize
+ if (($width = (int)getenv('COLUMNS')) > 0 && ($height = (int)getenv('LINES')) > 0) {
+ return $size = [$width, $height];
+ }
+ }
+ if (Helper::isOnWindows()) {
+ $output = [];
+ exec('mode con', $output);
+ if (isset($output[1]) && strpos($output[1], 'CON') !== false) {
+ return $size = [(int)preg_replace('~\\D~', '', $output[3]), (int)preg_replace('~\\D~', '', $output[4])];
+ }
+ }
+
+ return $size = false;
+ }
+
+ /**
+ * @param string $program
+ * @return int|string
+ */
+ public static function getCpuUsage($program)
+ {
+ if (!$program) {
+ return -1;
+ }
+ $info = exec('ps aux | grep ' . $program . ' | grep -v grep | grep -v su | awk {"print $3"}');
+
+ return $info;
+ }
+
+ /**
+ * @param $program
+ * @return int|string
+ */
+ public static function getMemUsage($program)
+ {
+ if (!$program) {
+ return -1;
+ }
+ $info = exec('ps aux | grep ' . $program . ' | grep -v grep | grep -v su | awk {"print $4"}');
+
+ return $info;
+ }
+}
\ No newline at end of file
diff --git a/src/Utils/ArgumentOptionParse.php b/src/Utils/CommandLine.php
similarity index 91%
rename from src/Utils/ArgumentOptionParse.php
rename to src/Utils/CommandLine.php
index bf7a147..c44c9dc 100644
--- a/src/Utils/ArgumentOptionParse.php
+++ b/src/Utils/CommandLine.php
@@ -10,10 +10,10 @@
namespace Inhere\Console\Utils;
/**
- * Class ArgumentOptionParse - console argument and option parse
+ * Class CommandLine - console argument and option parse
* @package Inhere\Console\Utils
*/
-final class ArgumentOptionParse
+final class CommandLine
{
/**
* These words will be as a Boolean value
@@ -46,7 +46,7 @@ final class ArgumentOptionParse
* @param bool $mergeOpts Whether merge short-opts and long-opts
* @return array
*/
- public static function byArgv(array $params, array $noValues = [], $mergeOpts = false)
+ public static function parseByArgv(array $params, array $noValues = [], $mergeOpts = false)
{
$args = $sOpts = $lOpts = [];
// each() will deprecated at 7.2. so,there use current and next instead it.
@@ -107,10 +107,14 @@ public static function byArgv(array $params, array $noValues = [], $mergeOpts =
return [$args, $sOpts, $lOpts];
}
+ public static function parseByDefinition(array $tokens, array $allowArray = [], array $noValues = [])
+ {
+ }
+
/**
* parse custom array params
* ```php
- * $result = CommandLineParse::byArray([
+ * $result = CommandLine::parseByArray([
* 'arg' => 'val',
* '--lp' => 'val2',
* '--s' => 'val3',
@@ -119,7 +123,7 @@ public static function byArgv(array $params, array $noValues = [], $mergeOpts =
* @param array $params
* @return array
*/
- public static function byArray(array $params)
+ public static function parseByArray(array $params)
{
$args = $sOpts = $lOpts = [];
foreach ($params as $key => $value) {
@@ -140,12 +144,12 @@ public static function byArray(array $params)
/**
* ```php
- * $result = CommandLineParse::byString('foo --bar="foobar"');
+ * $result = CommandLine::parseByString('foo --bar="foobar"');
* ```
* @todo ...
* @param string $string
*/
- public static function byString($string)
+ public static function parseByString($string)
{
}
diff --git a/src/Utils/FormatUtil.php b/src/Utils/FormatUtil.php
new file mode 100644
index 0000000..5ef3a63
--- /dev/null
+++ b/src/Utils/FormatUtil.php
@@ -0,0 +1,332 @@
+ $line) {
+ if ($first) {
+ $first = false;
+ continue;
+ }
+ $lines[$i] = $pad . $line;
+ }
+
+ return $pad . ' ' . implode("\n", $lines);
+ }
+
+ /**
+ * @param string $optsStr
+ */
+ public static function annotationOptions($optsStr)
+ {
+ }
+
+ /**
+ * this is a command's description message
+ * the second line text
+ * @format
+ * @usage usage message
+ * @arguments(format=true)
+ * arg1 argument description 1
+ * the second line
+ * a2,arg2 argument description 2
+ * the second line
+ * @arguments(
+ * arg1="argument description 1
+ * the second line",
+ * "a2,arg2"="argument description 2
+ * the second line"
+ * )
+ * @options
+ * -s, --long LONG option description 1
+ * --opt OPT option description 2
+ * @example example text one
+ * the second line example
+ * @param string $argsStr
+ */
+ public static function annotationArguments($argsStr)
+ {
+ }
+
+ /**
+ * @param array $options
+ * @return array
+ */
+ public static function alignmentOptions(array $options)
+ {
+ // e.g '-h, --help'
+ $hasShort = (bool)strpos(implode(array_keys($options), ''), ',');
+ if (!$hasShort) {
+ return $options;
+ }
+ $formatted = [];
+ foreach ($options as $name => $des) {
+ if (!($name = trim($name, ', '))) {
+ continue;
+ }
+ if (!strpos($name, ',')) {
+ // padding length equals to '-h, '
+ $name = ' ' . $name;
+ } else {
+ $name = str_replace([' ', ','], ['', ', '], $name);
+ }
+ $formatted[$name] = $des;
+ }
+
+ return $formatted;
+ }
+
+ /**
+ * 计算并格式化资源消耗
+ * @param int $startTime
+ * @param int|float $startMem
+ * @param array $info
+ * @return array
+ */
+ public static function runtime($startTime, $startMem, array $info = [])
+ {
+ $info['startTime'] = $startTime;
+ $info['endTime'] = microtime(true);
+ $info['endMemory'] = memory_get_usage(true);
+ // 计算运行时间
+ $info['runtime'] = number_format(($info['endTime'] - $startTime) * 1000, 3) . 'ms';
+ if ($startMem) {
+ $startMem = array_sum(explode(' ', $startMem));
+ $endMem = array_sum(explode(' ', $info['endMemory']));
+ $info['memory'] = number_format(($endMem - $startMem) / 1024, 3) . 'kb';
+ }
+ $peakMem = memory_get_peak_usage() / 1024 / 1024;
+ $info['peakMemory'] = number_format($peakMem, 3) . 'Mb';
+
+ return $info;
+ }
+
+ /**
+ * @param float $memory
+ * @return string
+ * ```
+ * FormatUtil::memoryUsage(memory_get_usage(true));
+ * ```
+ */
+ public static function memoryUsage($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);
+ }
+
+ /**
+ * format Timestamp
+ * @param int $secs
+ * @return string
+ */
+ public static function timestamp($secs)
+ {
+ static $timeFormats = [[0, '< 1 sec'], [1, '1 sec'], [2, 'secs', 1], [60, '1 min'], [120, 'mins', 60], [3600, '1 hr'], [7200, 'hrs', 3600], [86400, '1 day'], [172800, 'days', 86400]];
+ foreach ($timeFormats as $index => $format) {
+ if ($secs >= $format[0]) {
+ if (isset($timeFormats[$index + 1]) && ($secs < $timeFormats[$index + 1][0] || $index === \count($timeFormats) - 1)) {
+ if (2 === \count($format)) {
+ return $format[1];
+ }
+
+ return floor($secs / $format[2]) . ' ' . $format[1];
+ }
+ }
+ }
+
+ return date('Y-m-d H:i:s', $secs);
+ }
+
+ /**
+ * @param $string
+ * @param $width
+ * @return array
+ */
+ public static function splitStringByWidth($string, $width)
+ {
+ // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
+ // additionally, array_slice() is not enough as some character has doubled width.
+ // we need a function to split string not by character count but by string width
+ if (false === ($encoding = mb_detect_encoding($string, null, true))) {
+ return str_split($string, $width);
+ }
+ $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
+ $lines = array();
+ $line = '';
+ foreach (preg_split('//u', $utf8String) as $char) {
+ // test if $char could be appended to current line
+ if (mb_strwidth($line . $char, 'utf8') <= $width) {
+ $line .= $char;
+ continue;
+ }
+ // if not, push current line to array and make new line
+ $lines[] = str_pad($line, $width);
+ $line = $char;
+ }
+ if ('' !== $line) {
+ $lines[] = \count($lines) ? str_pad($line, $width) : $line;
+ }
+ mb_convert_variables($encoding, 'utf8', $lines);
+
+ return $lines;
+ }
+
+ /**
+ * splice Array
+ * @param array $data
+ * e.g [
+ * 'system' => 'Linux',
+ * 'version' => '4.4.5',
+ * ]
+ * @param array $opts
+ * @return string
+ */
+ public static function spliceKeyValue(array $data, array $opts = [])
+ {
+ $text = '';
+ $opts = array_merge([
+ 'leftChar' => '',
+ // e.g ' ', ' * '
+ 'sepChar' => ' ',
+ // e.g ' | ' OUT: key | value
+ 'keyStyle' => '',
+ // e.g 'info','comment'
+ 'valStyle' => '',
+ // e.g 'info','comment'
+ 'keyMinWidth' => 8,
+ 'keyMaxWidth' => null,
+ // if not set, will automatic calculation
+ 'ucFirst' => true,
+ ], $opts);
+ if (!is_numeric($opts['keyMaxWidth'])) {
+ $opts['keyMaxWidth'] = Helper::getKeyMaxWidth($data);
+ }
+ // compare
+ if ((int)$opts['keyMinWidth'] > $opts['keyMaxWidth']) {
+ $opts['keyMaxWidth'] = $opts['keyMinWidth'];
+ }
+ $keyStyle = trim($opts['keyStyle']);
+ foreach ($data as $key => $value) {
+ $hasKey = !\is_int($key);
+ $text .= $opts['leftChar'];
+ if ($hasKey && $opts['keyMaxWidth']) {
+ $key = str_pad($key, $opts['keyMaxWidth'], ' ');
+ $text .= Helper::wrapTag($key, $keyStyle) . $opts['sepChar'];
+ }
+ // if value is array, translate array to string
+ if (\is_array($value)) {
+ $temp = '';
+ /** @var array $value */
+ foreach ($value as $k => $val) {
+ if (\is_bool($val)) {
+ $val = $val ? 'True' : 'False';
+ } else {
+ $val = is_scalar($val) ? (string)$val : \gettype($val);
+ }
+ $temp .= (!is_numeric($k) ? "{$k}: " : '') . "{$val}, ";
+ }
+ $value = rtrim($temp, ' ,');
+ } else {
+ if (\is_bool($value)) {
+ $value = $value ? 'True' : 'False';
+ } else {
+ $value = (string)$value;
+ }
+ }
+ $value = $hasKey && $opts['ucFirst'] ? ucfirst($value) : $value;
+ $text .= Helper::wrapTag($value, $opts['valStyle']) . "\n";
+ }
+
+ return $text;
+ }
+}
\ No newline at end of file
diff --git a/src/Utils/Helper.php b/src/Utils/Helper.php
index 13f4cf4..f2f9d0f 100644
--- a/src/Utils/Helper.php
+++ b/src/Utils/Helper.php
@@ -26,6 +26,14 @@ public static function isOnWindows()
return DIRECTORY_SEPARATOR === '\\';
}
+ /**
+ * @return bool
+ */
+ public static function isMac()
+ {
+ return stripos(PHP_OS, 'Darwin') !== false;
+ }
+
/**
* @return bool
*/
@@ -56,13 +64,21 @@ public static function isRoot()
return getmyuid() === 0;
}
+ /**
+ * @return bool
+ */
+ public static function isSupportColor()
+ {
+ return self::supportColor();
+ }
+
/**
* Returns true if STDOUT supports colorization.
* This code has been copied and adapted from
* \Symfony\Component\Console\Output\OutputStream.
* @return boolean
*/
- public static function isSupportColor()
+ public static function supportColor()
{
if (DIRECTORY_SEPARATOR === '\\') {
return '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD || false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM');
@@ -100,154 +116,6 @@ public static function isInteractive($fileDescriptor)
return \function_exists('posix_isatty') && @posix_isatty($fileDescriptor);
}
- /**
- * @return string
- */
- public static function getNullDevice()
- {
- if (self::isUnix()) {
- return '/dev/null';
- }
-
- return 'NUL';
- }
-
- /**
- * run a command in background
- * @param string $cmd
- */
- public static function execInBackground($cmd)
- {
- if (self::isWindows()) {
- pclose(popen('start /B ' . $cmd, 'r'));
- } else {
- exec($cmd . ' > /dev/null &');
- }
- }
-
- /**
- * @param string $command
- * @param null|string $logfile
- * @param null|string $user
- * @return mixed
- * @throws \RuntimeException
- */
- public static function exec($command, $logfile = null, $user = null)
- {
- // If should run as another user, we must be on *nix and must have sudo privileges.
- $suDo = '';
- if ($user && self::isUnix() && self::isRoot()) {
- $suDo = "sudo -u {$user}";
- }
- // Start execution. Run in foreground (will block).
- $logfile = $logfile ?: self::getNullDevice();
- // Start execution. Run in foreground (will block).
- exec("{$suDo} {$command} 1>> \"{$logfile}\" 2>&1", $dummy, $retVal);
- if ($retVal !== 0) {
- throw new \RuntimeException("command exited with status '{$retVal}'.");
- }
-
- return $dummy;
- }
-
- /**
- * Method to execute a command in the sys
- * Uses :
- * 1. system
- * 2. passthru
- * 3. exec
- * 4. shell_exec
- * @param $command
- * @param bool $returnStatus
- * @return array|string
- */
- public static function runCommand($command, $returnStatus = true)
- {
- $return_var = 1;
- //system
- if (\function_exists('system')) {
- ob_start();
- system($command, $return_var);
- $output = ob_get_contents();
- ob_end_clean();
- // passthru
- } elseif (\function_exists('passthru')) {
- ob_start();
- passthru($command, $return_var);
- $output = ob_get_contents();
- ob_end_clean();
- //exec
- } else {
- if (\function_exists('exec')) {
- exec($command, $output, $return_var);
- $output = implode("\n", $output);
- //shell_exec
- } else {
- if (\function_exists('shell_exec')) {
- $output = shell_exec($command);
- } else {
- $output = 'Command execution not possible on this system';
- $return_var = 0;
- }
- }
- }
- if ($returnStatus) {
- return ['output' => trim($output), 'status' => $return_var];
- }
-
- return trim($output);
- }
-
- /**
- * @return string
- */
- public static function getTempDir()
- {
- // @codeCoverageIgnoreStart
- if (\function_exists('sys_get_temp_dir')) {
- $tmp = sys_get_temp_dir();
- } elseif (!empty($_SERVER['TMP'])) {
- $tmp = $_SERVER['TMP'];
- } elseif (!empty($_SERVER['TEMP'])) {
- $tmp = $_SERVER['TEMP'];
- } elseif (!empty($_SERVER['TMPDIR'])) {
- $tmp = $_SERVER['TMPDIR'];
- } else {
- $tmp = getcwd();
- }
-
- // @codeCoverageIgnoreEnd
- return $tmp;
- }
-
- /**
- * @param string $program
- * @return int|string
- */
- public static function getCpuUsage($program)
- {
- if (!$program) {
- return -1;
- }
- $info = exec('ps aux | grep ' . $program . ' | grep -v grep | grep -v su | awk {"print $3"}');
-
- return $info;
- }
-
- /**
- * @param $program
- * @return int|string
- */
- public static function getMemUsage($program)
- {
- if (!$program) {
- return -1;
- }
- $info = exec('ps aux | grep ' . $program . ' | grep -v grep | grep -v su | awk {"print $4"}');
-
- return $info;
- }
-
/**
* 给对象设置属性值
* @param $object
@@ -277,6 +145,14 @@ public static function recursiveDirectoryIterator($srcDir, callable $filter)
return new \RecursiveIteratorIterator($filterIterator);
}
+ /**
+ * @param string $command
+ * @param array $map
+ */
+ public static function commandSearch($command, array $map)
+ {
+ }
+
/**
* wrap a style tag
* @param string $string
@@ -328,23 +204,6 @@ public static function strLen($string)
return mb_strwidth($string, $encoding);
}
- /**
- * to camel
- * @param string $name
- * @return mixed|string
- */
- public static function camelCase($name)
- {
- $name = trim($name, '-_');
- // convert 'first-second' to 'firstSecond'
- if (strpos($name, '-')) {
- $name = ucwords(str_replace('-', ' ', $name));
- $name = str_replace(' ', '', lcfirst($name));
- }
-
- return $name;
- }
-
/**
* findValueByNodes
* @param array $data
@@ -368,82 +227,27 @@ public static function findValueByNodes(array $data, array $nodes, $default = nu
}
/**
- * @param $string
- * @param $width
+ * find similar text from an array|Iterator
+ * @param string $need
+ * @param \Iterator|array $iterator
+ * @param int $similarPercent
* @return array
*/
- public static function splitStringByWidth($string, $width)
- {
- // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
- // additionally, array_slice() is not enough as some character has doubled width.
- // we need a function to split string not by character count but by string width
- if (false === ($encoding = mb_detect_encoding($string, null, true))) {
- return str_split($string, $width);
- }
- $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
- $lines = array();
- $line = '';
- foreach (preg_split('//u', $utf8String) as $char) {
- // test if $char could be appended to current line
- if (mb_strwidth($line . $char, 'utf8') <= $width) {
- $line .= $char;
- continue;
- }
- // if not, push current line to array and make new line
- $lines[] = str_pad($line, $width);
- $line = $char;
- }
- if ('' !== $line) {
- $lines[] = \count($lines) ? str_pad($line, $width) : $line;
- }
- mb_convert_variables($encoding, 'utf8', $lines);
-
- return $lines;
- }
-
- /**
- * @param $memory
- * @return string
- * ```
- * Helper::formatMemory(memory_get_usage(true));
- * ```
- */
- public static function formatMemory($memory)
+ public static function findSimilar($need, $iterator, $similarPercent = 45)
{
- 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);
+ // find similar command names by similar_text()
+ $similar = [];
+ if (!$need) {
+ return $similar;
}
-
- return sprintf('%d B', $memory);
- }
-
- /**
- * formatTime
- * @param int $secs
- * @return string
- */
- public static function formatTime($secs)
- {
- static $timeFormats = [[0, '< 1 sec'], [1, '1 sec'], [2, 'secs', 1], [60, '1 min'], [120, 'mins', 60], [3600, '1 hr'], [7200, 'hrs', 3600], [86400, '1 day'], [172800, 'days', 86400]];
- foreach ($timeFormats as $index => $format) {
- if ($secs >= $format[0]) {
- if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0]) || $index === \count($timeFormats) - 1) {
- if (2 === \count($format)) {
- return $format[1];
- }
-
- return floor($secs / $format[2]) . ' ' . $format[1];
- }
+ foreach ($iterator as $name) {
+ similar_text($need, $name, $percent);
+ if ($similarPercent <= (int)$percent) {
+ $similar[] = $name;
}
}
- return date('Y-m-d H:i:s', $secs);
+ return $similar;
}
/**
@@ -470,182 +274,6 @@ public static function getKeyMaxWidth(array $data, $expectInt = false)
return $keyMaxWidth;
}
- /**
- * spliceArray
- * @param array $data
- * e.g [
- * 'system' => 'Linux',
- * 'version' => '4.4.5',
- * ]
- * @param array $opts
- * @return string
- */
- public static function spliceKeyValue(array $data, array $opts = [])
- {
- $text = '';
- $opts = array_merge([
- 'leftChar' => '',
- // e.g ' ', ' * '
- 'sepChar' => ' ',
- // e.g ' | ' OUT: key | value
- 'keyStyle' => '',
- // e.g 'info','comment'
- 'valStyle' => '',
- // e.g 'info','comment'
- 'keyMinWidth' => 8,
- 'keyMaxWidth' => null,
- // if not set, will automatic calculation
- 'ucFirst' => true,
- ], $opts);
- if (!is_numeric($opts['keyMaxWidth'])) {
- $opts['keyMaxWidth'] = self::getKeyMaxWidth($data);
- }
- // compare
- if ((int)$opts['keyMinWidth'] > $opts['keyMaxWidth']) {
- $opts['keyMaxWidth'] = $opts['keyMinWidth'];
- }
- $keyStyle = trim($opts['keyStyle']);
- foreach ($data as $key => $value) {
- $hasKey = !\is_int($key);
- $text .= $opts['leftChar'];
- if ($hasKey && $opts['keyMaxWidth']) {
- $key = str_pad($key, $opts['keyMaxWidth'], ' ');
- // $text .= ($keyStyle ? "<{$keyStyle}>$key{$keyStyle}> " : $key) . $opts['sepChar'];
- $text .= self::wrapTag($key, $keyStyle) . $opts['sepChar'];
- }
- // if value is array, translate array to string
- if (\is_array($value)) {
- $temp = '';
- /** @var array $value */
- foreach ($value as $k => $val) {
- if (\is_bool($val)) {
- $val = $val ? 'True' : 'False';
- } else {
- $val = is_scalar($val) ? (string)$val : \gettype($val);
- }
- $temp .= (!is_numeric($k) ? "{$k}: " : '') . "{$val}, ";
- }
- $value = rtrim($temp, ' ,');
- } else {
- if (\is_bool($value)) {
- $value = $value ? 'True' : 'False';
- } else {
- $value = (string)$value;
- }
- }
- $value = $hasKey && $opts['ucFirst'] ? ucfirst($value) : $value;
- $text .= self::wrapTag($value, $opts['valStyle']) . "\n";
- }
-
- return $text;
- }
- // next: form yii2
-
- /**
- * Usage: list($width, $height) = ConsoleHelper::getScreenSize();
- * @param boolean $refresh whether to force checking and not re-use cached size value.
- * This is useful to detect changing window size while the application is running but may
- * not get up to date values on every terminal.
- * @return array|boolean An array of ($width, $height) or false when it was not able to determine size.
- */
- public static function getScreenSize($refresh = false)
- {
- static $size;
- if ($size !== null && !$refresh) {
- return $size;
- }
- if (self::isOnWindows()) {
- $output = [];
- exec('mode con', $output);
- if (isset($output[1]) && strpos($output[1], 'CON') !== false) {
- return $size = [(int)preg_replace('~\\D~', '', $output[3]), (int)preg_replace('~\\D~', '', $output[4])];
- }
- } else {
- // try stty if available
- $stty = [];
- if (exec('stty -a 2>&1', $stty) && preg_match('/rows\\s+(\\d+);\\s*columns\\s+(\\d+);/mi', implode(' ', $stty), $matches)) {
- return $size = [$matches[2], $matches[1]];
- }
- // fallback to tput, which may not be updated on terminal resize
- if (($width = (int)exec('tput cols 2>&1')) > 0 && ($height = (int)exec('tput lines 2>&1')) > 0) {
- return $size = [$width, $height];
- }
- // fallback to ENV variables, which may not be updated on terminal resize
- if (($width = (int)getenv('COLUMNS')) > 0 && ($height = (int)getenv('LINES')) > 0) {
- return $size = [$width, $height];
- }
- }
-
- return $size = false;
- }
-
- /**
- * Word wrap text with indentation to fit the screen size
- * If screen size could not be detected, or the indentation is greater than the screen size, the text will not be wrapped.
- * The first line will **not** be indented, so `Console::wrapText("Lorem ipsum dolor sit amet.", 4)` will result in the
- * following output, given the screen width is 16 characters:
- * ```
- * Lorem ipsum
- * dolor sit
- * amet.
- * ```
- * @param string $text the text to be wrapped
- * @param integer $indent number of spaces to use for indentation.
- * @param integer $width
- * @return string the wrapped text.
- * @from yii2
- */
- public static function wrapText($text, $indent = 0, $width = 0)
- {
- if (!$text) {
- return $text;
- }
- if ((int)$width <= 0) {
- $size = static::getScreenSize();
- if ($size === false || $size[0] <= $indent) {
- return $text;
- }
- $width = $size[0];
- }
- $pad = str_repeat(' ', $indent);
- $lines = explode("\n", wordwrap($text, $width - $indent, "\n", true));
- $first = true;
- foreach ($lines as $i => $line) {
- if ($first) {
- $first = false;
- continue;
- }
- $lines[$i] = $pad . $line;
- }
-
- return $pad . ' ' . implode("\n", $lines);
- }
-
- /**
- * 获取资源消耗
- * @param int $startTime
- * @param int|float $startMem
- * @param array $info
- * @return array
- */
- public static function runtime($startTime, $startMem, array $info = [])
- {
- $info['startTime'] = $startTime;
- $info['endTime'] = microtime(true);
- $info['endMemory'] = memory_get_usage(true);
- // 计算运行时间
- $info['runtime'] = number_format(($info['endTime'] - $startTime) * 1000, 3) . 'ms';
- if ($startMem) {
- $startMem = array_sum(explode(' ', $startMem));
- $endMem = array_sum(explode(' ', $info['endMemory']));
- $info['memory'] = number_format(($endMem - $startMem) / 1024, 3) . 'kb';
- }
- $peakMem = memory_get_peak_usage() / 1024 / 1024;
- $info['peakMemory'] = number_format($peakMem, 3) . 'Mb';
-
- return $info;
- }
-
/**
* dump vars
* @param array ...$args
diff --git a/src/Utils/Interact.php b/src/Utils/Interact.php
index dad969d..b41948f 100644
--- a/src/Utils/Interact.php
+++ b/src/Utils/Interact.php
@@ -51,7 +51,7 @@ public static function read($message = null, $nl = false, array $opts = [])
* Interactive method (select/confirm/question/loopAsk)
**************************************************************************************************/
/**
- * Select one of the options 在多个选项中选择一个
+ * alias of the `select()`
* @param string $description 说明
* @param mixed $options 选项数据
* e.g
@@ -70,9 +70,9 @@ public static function select($description, $options, $default = null, $allowExi
}
/**
- * alias of the `select()`
+ * choice one of the options 在多个选项中选择一个
* @param $description
- * @param $options
+ * @param string|array $options
* @param null $default
* @param bool $allowExit
* @return string
@@ -90,13 +90,14 @@ public static function choice($description, $options, $default = null, $allowExi
if ($allowExit) {
$options['q'] = 'quit';
}
- beginChoice:
- $text = " {$description}";
+ $text = "{$description}";
foreach ($options as $key => $value) {
$text .= "\n {$key}) {$value}";
}
$defaultText = $default ? "[default:{$default}]" : '';
- $r = self::read($text . "\n You choice{$defaultText} : ");
+ self::write($text);
+ beginChoice:
+ $r = self::read("Your choice{$defaultText} : ");
// error, allow try again once.
if (!array_key_exists($r, $options)) {
goto beginChoice;
@@ -109,14 +110,68 @@ public static function choice($description, $options, $default = null, $allowExi
return $r;
}
+ /**
+ * alias of the `multiSelect()`
+ * @param string $description
+ * @param string|array $options
+ * @param null|mixed $default
+ * @param bool $allowExit
+ * @return array
+ */
public static function checkbox($description, $options, $default = null, $allowExit = true)
{
return self::multiSelect($description, $options, $default, $allowExit);
}
+ /**
+ * @param string $description
+ * @param string|array $options
+ * @param null|mixed $default
+ * @param bool $allowExit
+ * @return array
+ */
public static function multiSelect($description, $options, $default = null, $allowExit = true)
{
- return [];
+ if (!($description = trim($description))) {
+ self::error('Please provide a description text!', 1);
+ }
+ $sep = ',';
+ // ',' ' '
+ $options = \is_array($options) ? $options : explode(',', $options);
+ // If default option is error
+ if (null !== $default && !isset($options[$default])) {
+ self::error("The default option [{$default}] don't exists.", true);
+ }
+ if ($allowExit) {
+ $options['q'] = 'quit';
+ }
+ $text = "{$description}";
+ foreach ($options as $key => $value) {
+ $text .= "\n {$key}) {$value}";
+ }
+ self::write($text);
+ $defaultText = $default ? "[default:{$default}]" : '';
+ $filter = function ($val) use ($options) {
+ return $val !== 'q' && isset($options[$val]);
+ };
+ beginChoice:
+ $r = self::read("Your choice{$defaultText} : ");
+ $r = $r !== '' ? str_replace(' ', '', trim($r, $sep)) : '';
+ // empty
+ if ($r === '') {
+ goto beginChoice;
+ }
+ // exit
+ if ($r === 'q') {
+ self::write("\n Quit,ByeBye.", true, true);
+ }
+ $rs = strpos($r, $sep) ? array_filter(explode($sep, $r), $filter) : [$r];
+ // error, try again
+ if (!$rs) {
+ goto beginChoice;
+ }
+
+ return $rs;
}
/**
@@ -128,7 +183,7 @@ public static function multiSelect($description, $options, $default = null, $all
public static function confirm($question, $default = true)
{
if (!($question = trim($question))) {
- self::error('Please provide a question text!', 1);
+ self::warning('Please provide a question message!', 1);
}
$question = ucfirst(trim($question, '?'));
$default = (bool)$default;
@@ -152,20 +207,9 @@ public static function confirm($question, $default = true)
/**
* alias of the `question()`
- * 询问,提出问题;返回 输入的结果
* @param string $question 问题
* @param null|string $default 默认值
* @param \Closure $validator The validate callback. It must return bool.
- * @example This is an example
- * ```php
- * $answer = Interact::ask('Please input your name?', null, function ($answer) {
- * if (!preg_match('/\w+/', $answer)) {
- * Interact::error('The name must match "/\w+/"');
- * return false;
- * }
- * return true;
- * });
- * ```
* @return string
*/
public static function ask($question, $default = null, \Closure $validator = null)
@@ -175,9 +219,33 @@ public static function ask($question, $default = null, \Closure $validator = nul
/**
* 询问,提出问题;返回 输入的结果
+ * @example This is an example
+ * ```php
+ * $answer = Interact::ask('Please input your name?', null, function ($answer) {
+ * if (!preg_match('/\w{2,}/', $answer)) {
+ * // output error tips.
+ * Interact::error('The name must match "/\w{2,}/"');
+ * return false;
+ * }
+ * return true;
+ * });
+ * echo "Your input: $answer";
+ * ```
+ * ```php
+ * // use the second arg in the validator.
+ * $answer = Interact::ask('Please input your name?', null, function ($answer, &$err) {
+ * if (!preg_match('/\w{2,}/', $answer)) {
+ * // setting error message.
+ * $err = 'The name must match "/\w{2,}/"';
+ * return false;
+ * }
+ * return true;
+ * });
+ * echo "Your input: $answer";
+ * ```
* @param string $question
- * @param null $default
- * @param \Closure|null $validator
+ * @param null|mixed $default
+ * @param \Closure|null $validator Validator, must return bool.
* @return null|string
*/
public static function question($question, $default = null, \Closure $validator = null)
@@ -185,19 +253,28 @@ public static function question($question, $default = null, \Closure $validator
if (!($question = trim($question))) {
self::error('Please provide a question text!', 1);
}
- $defaultText = null !== $default ? "(default: {$default})" : '';
- $answer = self::read('' . ucfirst($question) . "{$defaultText} ");
+ $defText = null !== $default ? "(default: {$default})" : '';
+ $message = '' . ucfirst($question) . "{$defText} ";
+ askQuestion:
+ $answer = self::read($message);
if ('' === $answer) {
if (null === $default) {
self::error('A value is required.');
-
- return static::question($question, $default, $validator);
+ goto askQuestion;
}
return $default;
}
+ // has answer validator
if ($validator) {
- return $validator($answer) ? $answer : static::question($question, $default, $validator);
+ $error = null;
+ if ($validator($answer, $error)) {
+ return $answer;
+ }
+ if ($error) {
+ Show::warning($error);
+ }
+ goto askQuestion;
}
return $answer;
@@ -211,7 +288,7 @@ public static function question($question, $default = null, \Closure $validator
* @param null|string $default 默认值
* @param \Closure $validator (默认验证输入是否为空)自定义回调验证输入是否符合要求; 验证成功返回true 否则 可返回错误消息
* @example This is an example
- * ```
+ * ```php
* // no default value
* Interact::limitedAsk('please entry you age?', null, function($age)
* {
@@ -242,11 +319,17 @@ public static function limitedAsk($question, $default = null, \Closure $validato
$result = false;
$answer = '';
$question = ucfirst($question);
+ $hasDefault = null !== $default;
$back = $times = (int)$times > 6 || $times < 1 ? 3 : (int)$times;
- $defaultText = null !== $default ? "(default: {$default})" : '';
+ if ($hasDefault) {
+ $message = "{$question}(default: {$default}) ";
+ } else {
+ $message = "{$question}";
+ Show::write($message);
+ }
while ($times--) {
- if ($defaultText) {
- $answer = self::read("{$question}{$defaultText} ");
+ if ($hasDefault) {
+ $answer = self::read($message);
if ('' === $answer) {
$answer = $default;
$result = true;
@@ -254,7 +337,7 @@ public static function limitedAsk($question, $default = null, \Closure $validato
}
} else {
$num = $times + 1;
- $answer = self::read("{$question}\n(You have a [{$num}] chance to enter!) ");
+ $answer = self::read(sprintf('(You have [%s] chances to enter!) ', $num));
}
// If setting verify callback
if ($validator && ($result = $validator($answer)) === true) {
@@ -270,7 +353,7 @@ public static function limitedAsk($question, $default = null, \Closure $validato
if (null !== $default) {
return $default;
}
- self::write("\n You've entered incorrectly {$back} times in a row. exit!\n", true, 1);
+ self::write("\n You've entered incorrectly {$back} times in a row. exit!", true, 1);
}
return $answer;
@@ -286,25 +369,25 @@ public static function limitedAsk($question, $default = null, \Closure $validato
* @return string
* @link https://stackoverflow.com/questions/187736/command-line-password-prompt-in-php
* @link http://www.sitepoint.com/blogs/2009/05/01/interactive-cli-password-prompt-in-php
+ * @throws \RuntimeException
*/
public static function promptSilent($prompt = 'Enter Password:')
{
$prompt = $prompt ? addslashes($prompt) : 'Enter:';
// $checkCmd = "/usr/bin/env bash -c 'echo OK'";
// $shell = 'echo $0';
- $checkCmd = "bash -c 'echo OK'";
// linux, unix, git-bash
- if (Helper::runCommand($checkCmd, false) === 'OK') {
+ if (CliUtil::bashIsAvailable()) {
// COMMAND: bash -c 'read -p "Enter Password:" -s user_input && echo $user_input'
$command = sprintf('bash -c "read -p \'%s\' -s user_input && echo $user_input"', $prompt);
- $password = Helper::runCommand($command, false);
+ $password = CliUtil::runCommand($command, false);
echo "\n";
return $password;
}
// at windows cmd.
if (Helper::isWindows()) {
- $vbScript = sys_get_temp_dir() . 'prompt_password.vbs';
+ $vbScript = CliUtil::getTempDir() . '/hidden_prompt_input.vbs';
file_put_contents($vbScript, 'wscript.echo(InputBox("' . $prompt . '", "", "password here"))');
$command = 'cscript //nologo ' . escapeshellarg($vbScript);
$password = rtrim(shell_exec($command));
@@ -319,6 +402,7 @@ public static function promptSilent($prompt = 'Enter Password:')
* alias of the method `promptSilent()`
* @param string $prompt
* @return string
+ * @throws \RuntimeException
*/
public static function askHiddenInput($prompt = 'Enter Password:')
{
@@ -329,6 +413,7 @@ public static function askHiddenInput($prompt = 'Enter Password:')
* alias of the method `promptSilent()`
* @param string $prompt
* @return string
+ * @throws \RuntimeException
*/
public static function askPassword($prompt = 'Enter Password:')
{
diff --git a/src/Utils/ProcessUtil.php b/src/Utils/ProcessUtil.php
new file mode 100644
index 0000000..6eba65a
--- /dev/null
+++ b/src/Utils/ProcessUtil.php
@@ -0,0 +1,508 @@
+ /dev/null &');
+ }
+
+ return $ret;
+ }
+
+ /**
+ * @see ProcessUtil::forks()
+ * @param int $number
+ * @param callable|null $onStart
+ * @param callable|null $onError
+ * @return array|false
+ */
+ public static function multi($number, callable $onStart = null, callable $onError = null)
+ {
+ return self::forks($number, $onStart, $onError);
+ }
+
+ /**
+ * fork/create multi child processes.
+ * @param int $number
+ * @param callable|null $onStart Will running on the child processes.
+ * @param callable|null $onError
+ * @return array|false
+ */
+ public static function forks($number, callable $onStart = null, callable $onError = null)
+ {
+ if ($number <= 0) {
+ return false;
+ }
+ if (!self::isSupported()) {
+ return false;
+ }
+ $pidAry = [];
+ for ($id = 0; $id < $number; $id++) {
+ $info = self::fork($onStart, $onError, $id);
+ $pidAry[$info['pid']] = $info;
+ }
+
+ return $pidAry;
+ }
+
+ /**
+ * @see ProcessUtil::fork()
+ * @param callable|null $onStart
+ * @param callable|null $onError
+ * @param int $id
+ * @return array|false
+ */
+ public static function create(callable $onStart = null, callable $onError = null, $id = 0)
+ {
+ return self::fork($onStart, $onError, $id);
+ }
+
+ /**
+ * fork/create a child process.
+ * @param callable|null $onStart Will running on the child process start.
+ * @param callable|null $onError
+ * @param int $id The process index number. will use `forks()`
+ * @return array|false
+ */
+ public static function fork(callable $onStart = null, callable $onError = null, $id = 0)
+ {
+ if (!self::isSupported()) {
+ return false;
+ }
+ $info = [];
+ $pid = pcntl_fork();
+ // at parent, get forked child info
+ if ($pid > 0) {
+ $info = ['id' => $id, 'pid' => $pid, 'startTime' => time()];
+ } elseif ($pid === 0) {
+ // at child
+ $pid = getmypid();
+ if ($onStart) {
+ $onStart($pid, $id);
+ }
+ } else {
+ if ($onError) {
+ $onError($pid);
+ }
+ Show::error('Fork child process failed! exiting.');
+ }
+
+ return $info;
+ }
+
+ /**
+ * wait child exit.
+ * @param callable $onExit
+ * @return bool
+ */
+ public static function wait(callable $onExit)
+ {
+ if (!self::isSupported()) {
+ return false;
+ }
+ $status = null;
+ //pid<0:子进程都没了
+ //pid>0:捕获到一个子进程退出的情况
+ //pid=0:没有捕获到退出的子进程
+ while (($pid = pcntl_waitpid(-1, $status, WNOHANG)) >= 0) {
+ if ($pid) {
+ // ... (callback, pid, exitCode, status)
+ $onExit($pid, pcntl_wexitstatus($status), $status);
+ } else {
+ usleep(50000);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Stops all running children
+ * @param array $children
+ * [
+ * 'pid' => [
+ * 'id' => worker id
+ * ],
+ * ... ...
+ * ]
+ * @param int $signal
+ * @param array $events
+ * [
+ * 'beforeStops' => function ($sigText) {
+ * echo "Stopping processes({$sigText}) ...\n";
+ * },
+ * 'beforeStop' => function ($pid, $info) {
+ * echo "Stopping process(PID:$pid)\n";
+ * }
+ * ]
+ * @return bool
+ */
+ public static function stopChildren(array $children, $signal = SIGTERM, array $events = [])
+ {
+ if (!$children) {
+ return false;
+ }
+ if (!self::isSupported()) {
+ return false;
+ }
+ $events = array_merge(['beforeStops' => null, 'beforeStop' => null], $events);
+ $signals = [SIGINT => 'SIGINT(Ctrl+C)', SIGTERM => 'SIGTERM', SIGKILL => 'SIGKILL'];
+ if ($cb = $events['beforeStops']) {
+ $cb($signal, $signals[$signal]);
+ }
+ foreach ($children as $pid => $child) {
+ if ($cb = $events['beforeStop']) {
+ $cb($pid, $child);
+ }
+ // send exit signal.
+ self::sendSignal($pid, $signal);
+ }
+
+ return true;
+ }
+ /**************************************************************************************
+ * basic signal methods
+ *************************************************************************************/
+ /**
+ * send kill signal to the process
+ * @param int $pid
+ * @param bool $force
+ * @param int $timeout
+ * @return bool
+ */
+ public static function kill($pid, $force = false, $timeout = 3)
+ {
+ return self::sendSignal($pid, $force ? SIGKILL : SIGTERM, $timeout);
+ }
+
+ /**
+ * Do shutdown process and wait it exit.
+ * @param int $pid Master Pid
+ * @param int $signal
+ * @param int $waitTime
+ * @param null $error
+ * @param string $name
+ * @return bool
+ */
+ public static function killAndWait($pid, $signal = SIGTERM, $waitTime = 30, &$error = null, $name = 'process')
+ {
+ // $opts = array_merge([], $opts);
+ // do stop
+ if (!self::kill($signal)) {
+ $error = "Send stop signal to the {$name}(PID:{$pid}) failed!";
+
+ return false;
+ }
+ // not wait, only send signal
+ if ($waitTime <= 0) {
+ $error = "The {$name} process stopped";
+
+ return true;
+ }
+ $startTime = time();
+ Show::write('Stopping .', false);
+ // wait exit
+ while (true) {
+ if (!self::isRunning($pid)) {
+ break;
+ }
+ if (time() - $startTime > $waitTime) {
+ $error = "Stop the {$name}(PID:{$pid}) failed(timeout)!";
+ break;
+ }
+ Show::write('.', false);
+ sleep(1);
+ }
+
+ return true;
+ }
+
+ /**
+ * 杀死所有进程
+ * @param $name
+ * @param int $sigNo
+ * @return string
+ */
+ public static function killByName($name, $sigNo = 9)
+ {
+ $cmd = 'ps -eaf |grep "' . $name . '" | grep -v "grep"| awk "{print $2}"|xargs kill -' . $sigNo;
+
+ return exec($cmd);
+ }
+
+ /**
+ * @param int $pid
+ * @return bool
+ */
+ public static function isRunning($pid)
+ {
+ return $pid > 0 && @posix_kill($pid, 0);
+ }
+
+ /**
+ * exit
+ * @param int $code
+ */
+ public static function quit($code = 0)
+ {
+ exit((int)$code);
+ }
+ /**************************************************************************************
+ * process signal handle
+ *************************************************************************************/
+ /**
+ * send signal to the process
+ * @param int $pid
+ * @param int $signal
+ * @param int $timeout
+ * @return bool
+ */
+ public static function sendSignal($pid, $signal, $timeout = 0)
+ {
+ if ($pid <= 0) {
+ return false;
+ }
+ if (!self::isSupported()) {
+ return false;
+ }
+ // do send
+ if ($ret = posix_kill($pid, $signal)) {
+ return true;
+ }
+ // don't want retry
+ if ($timeout <= 0) {
+ return $ret;
+ }
+ // failed, try again ...
+ $timeout = $timeout > 0 && $timeout < 10 ? $timeout : 3;
+ $startTime = time();
+ // retry stop if not stopped.
+ while (true) {
+ // success
+ if (!($isRunning = @posix_kill($pid, 0))) {
+ break;
+ }
+ // have been timeout
+ if (time() - $startTime >= $timeout) {
+ return false;
+ }
+ // try again kill
+ $ret = posix_kill($pid, $signal);
+ usleep(10000);
+ }
+
+ return $ret;
+ }
+
+ /**
+ * install signal
+ * @param int $signal e.g: SIGTERM SIGINT(Ctrl+C) SIGUSR1 SIGUSR2 SIGHUP
+ * @param callable $handler
+ * @return bool
+ */
+ public static function installSignal($signal, callable $handler)
+ {
+ return pcntl_signal($signal, $handler, false);
+ }
+
+ /**
+ * dispatch signal
+ * @return bool
+ */
+ public static function dispatchSignal()
+ {
+ // receive and dispatch sig
+ return pcntl_signal_dispatch();
+ }
+ /**************************************************************************************
+ * some help method
+ *************************************************************************************/
+ /**
+ * get current process id
+ * @return int
+ */
+ public static function getPid()
+ {
+ return getmypid();
+ // or use posix_getpid()
+ }
+
+ /**
+ * get Pid from File
+ * @param string $file
+ * @param bool $checkLive
+ * @return int
+ */
+ public static function getPidByFile($file, $checkLive = false)
+ {
+ if ($file && file_exists($file)) {
+ $pid = (int)file_get_contents($file);
+ // check live
+ if ($checkLive && self::isRunning($pid)) {
+ return $pid;
+ }
+ unlink($file);
+ }
+
+ return 0;
+ }
+
+ /**
+ * Get unix user of current process.
+ * @return array
+ */
+ public static function getCurrentUser()
+ {
+ return posix_getpwuid(posix_getuid());
+ }
+
+ /**
+ * @param int $seconds
+ * @param callable $handler
+ */
+ public static function afterDo($seconds, callable $handler)
+ {
+ /*
+ self::signal(SIGALRM, function () {
+ static $i = 0;
+ echo "#{$i}\talarm\n";
+ $i++;
+ if ($i > 20) {
+ pcntl_alarm(-1);
+ }
+ });*/
+ self::installSignal(SIGALRM, $handler);
+ // self::alarm($seconds);
+ pcntl_alarm($seconds);
+ }
+
+ /**
+ * Set process title.
+ * @param string $title
+ * @return bool
+ */
+ public static function setName($title)
+ {
+ return self::setTitle($title);
+ }
+
+ /**
+ * Set process title.
+ * @param string $title
+ * @return bool
+ */
+ public static function setTitle($title)
+ {
+ if (Helper::isMac()) {
+ return false;
+ }
+ if (\function_exists('cli_set_process_title')) {
+ cli_set_process_title($title);
+ }
+
+ return true;
+ }
+
+ /**
+ * Set unix user and group for current process script.
+ * @param string $user
+ * @param string $group
+ * @throws \RuntimeException
+ */
+ public static function changeScriptOwner($user, $group = '')
+ {
+ $uInfo = posix_getpwnam($user);
+ if (!$uInfo || !isset($uInfo['uid'])) {
+ throw new \RuntimeException("User ({$user}) not found.");
+ }
+ $uid = (int)$uInfo['uid'];
+ // Get gid.
+ if ($group) {
+ if (!($gInfo = posix_getgrnam($group))) {
+ throw new \RuntimeException("Group {$group} not exists", -300);
+ }
+ $gid = (int)$gInfo['gid'];
+ } else {
+ $gid = (int)$uInfo['gid'];
+ }
+ if (!posix_initgroups($uInfo['name'], $gid)) {
+ throw new \RuntimeException("The user [{$user}] is not in the user group ID [GID:{$gid}]", -300);
+ }
+ posix_setgid($gid);
+ if (posix_geteuid() !== $gid) {
+ throw new \RuntimeException("Unable to change group to {$user} (UID: {$gid}).", -300);
+ }
+ posix_setuid($uid);
+ if (posix_geteuid() !== $uid) {
+ throw new \RuntimeException("Unable to change user to {$user} (UID: {$uid}).", -300);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Utils/ProgressBar.php b/src/Utils/ProgressBar.php
index dab8c42..d66d12e 100644
--- a/src/Utils/ProgressBar.php
+++ b/src/Utils/ProgressBar.php
@@ -493,7 +493,7 @@ private static function loadDefaultParsers()
return $display;
}, 'elapsed' => function (self $bar) {
- return Helper::formatTime(time() - $bar->getStartTime());
+ return FormatUtil::timestamp(time() - $bar->getStartTime());
}, 'remaining' => function (self $bar) {
if (!$bar->getMaxSteps()) {
throw new \LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
@@ -504,7 +504,7 @@ private static function loadDefaultParsers()
$remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress()));
}
- return Helper::formatTime($remaining);
+ return FormatUtil::timestamp($remaining);
}, 'estimated' => function (self $bar) {
if (!$bar->getMaxSteps()) {
return 0;
@@ -516,9 +516,9 @@ private static function loadDefaultParsers()
$estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps());
}
- return Helper::formatTime($estimated);
+ return FormatUtil::timestamp($estimated);
}, 'memory' => function () {
- return Helper::formatMemory(memory_get_usage(true));
+ return FormatUtil::memoryUsage(memory_get_usage(true));
}, 'current' => function (self $bar) {
return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT);
}, 'max' => function (self $bar) {
diff --git a/src/Utils/Show.php b/src/Utils/Show.php
index 0922315..fd21c27 100644
--- a/src/Utils/Show.php
+++ b/src/Utils/Show.php
@@ -9,6 +9,7 @@
namespace Inhere\Console\Utils;
+use Inhere\Console\Components\StrBuffer;
use Inhere\Console\Style\Style;
/**
@@ -54,9 +55,11 @@ class Show
const HELP_OPTIONS = 'options';
const HELP_EXAMPLES = 'examples';
const HELP_EXTRAS = 'extras';
- /**
- * @var array
- */
+ /** @var string */
+ private static $buffer;
+ /** @var bool */
+ private static $buffering = false;
+ /** @var array */
public static $defaultBlocks = ['block', 'primary', 'info', 'notice', 'success', 'warning', 'danger', 'error'];
/**************************************************************************************************
* Output block Message
@@ -226,7 +229,7 @@ public static function section($title, $body, array $opts = [])
}
}
$body = \is_array($body) ? implode(PHP_EOL, $body) : $body;
- $body = Helper::wrapText($body, 4, $opts['width']);
+ $body = FormatUtil::wrapText($body, 4, $opts['width']);
self::write(sprintf($tpl, $titleLine, $topBorder, $body, $bottomBorder));
}
@@ -269,23 +272,27 @@ public static function padding(array $data, $title = null, array $opts = [])
* ```
* @param array $data
* @param string $title
- * @param array $opts More @see Helper::spliceKeyValue()
+ * @param array $opts More {@see FormatUtil::spliceKeyValue()}
* @return int|string
*/
public static function aList($data, $title = null, array $opts = [])
{
$string = '';
- $opts = array_merge(['leftChar' => ' ', 'keyStyle' => 'info', 'keyMinWidth' => 8, 'titleStyle' => 'comment', 'returned' => false], $opts);
+ $opts = array_merge([
+ 'leftChar' => ' ',
+ // 'sepChar' => ' ',
+ 'keyStyle' => 'info',
+ 'keyMinWidth' => 8,
+ 'titleStyle' => 'comment',
+ 'returned' => false,
+ ], $opts);
// title
if ($title) {
$title = ucwords(trim($title));
- if ($style = $opts['titleStyle']) {
- $title = "<{$style}>{$title}{$style}>";
- }
- $string .= $title . PHP_EOL;
+ $string .= Helper::wrapTag($title, $opts['titleStyle']) . PHP_EOL;
}
// handle item list
- $string .= Helper::spliceKeyValue((array)$data, $opts);
+ $string .= FormatUtil::spliceKeyValue((array)$data, $opts);
if ($opts['returned']) {
return $string;
}
@@ -337,7 +344,7 @@ public static function mList(array $data, array $opts = [])
foreach ($data as $title => $list) {
$buffer[] = self::aList($list, $title, $opts);
}
- self::write($buffer);
+ self::write(implode("\n", $buffer));
}
/**
@@ -373,7 +380,8 @@ public static function mList(array $data, array $opts = [])
*/
public static function helpPanel(array $config, $showAfterQuit = true)
{
- $help = '';
+ $parts = [];
+ $option = ['indentDes' => ' '];
$config = array_merge([
'description' => '',
'usage' => '',
@@ -383,10 +391,16 @@ public static function helpPanel(array $config, $showAfterQuit = true)
'examples' => [],
// extra
'extras' => [],
+ '_opts' => [],
], $config);
+ // some option for show.
+ if (isset($config['_opts'])) {
+ $option = array_merge($option, $config['_opts']);
+ unset($config['_opts']);
+ }
// description
if ($config['description']) {
- $help .= " {$config['description']}\n\n";
+ $parts[] = "{$option['indentDes']}{$config['description']}\n";
unset($config['description']);
}
// now, render usage,commands,arguments,options,examples ...
@@ -401,17 +415,17 @@ public static function helpPanel(array $config, $showAfterQuit = true)
$value = implode(PHP_EOL . ' ', $value);
// is key-value [ 'key1' => 'text1', 'key2' => 'text2']
} else {
- $value = Helper::spliceKeyValue($value, ['leftChar' => ' ', 'keyStyle' => 'info']);
+ $value = FormatUtil::spliceKeyValue($value, ['leftChar' => ' ', 'sepChar' => ' ', 'keyStyle' => 'info']);
}
}
if (\is_string($value)) {
$value = trim($value);
$section = ucfirst($section);
- $help .= "{$section}:\n {$value}\n\n";
+ $parts[] = "{$section}:\n {$value}\n";
}
}
- if ($help) {
- self::write($help, false);
+ if ($parts) {
+ self::write(implode("\n", $parts), false);
}
if ($showAfterQuit) {
exit(0);
@@ -478,6 +492,7 @@ public static function panel($data, $title = 'Information Panel', array $opts =
}
$border = null;
$panelWidth = $labelMaxWidth + $valueMaxWidth;
+ self::startBuffer();
// output title
if ($title) {
$title = ucwords($title);
@@ -492,18 +507,28 @@ public static function panel($data, $title = 'Information Panel', array $opts =
self::write(' ' . $border);
}
// output panel body
- $panelStr = Helper::spliceKeyValue($panelData, ['leftChar' => " {$borderChar} ", 'sepChar' => ' | ', 'keyMaxWidth' => $labelMaxWidth, 'ucFirst' => $opts['ucFirst']]);
+ $panelStr = FormatUtil::spliceKeyValue($panelData, ['leftChar' => " {$borderChar} ", 'sepChar' => ' | ', 'keyMaxWidth' => $labelMaxWidth, 'ucFirst' => $opts['ucFirst']]);
// already exists "\n"
self::write($panelStr, false);
// output panel bottom border
if ($border) {
self::write(" {$border}\n");
}
+ self::flushBuffer();
unset($panelData);
return 0;
}
+ /**
+ * @todo un-completed
+ * @param array $data
+ * @param array $opts
+ */
+ public static function tree(array $data, array $opts = [])
+ {
+ }
+
/**
* 表格数据信息展示
* @param array $data
@@ -555,7 +580,7 @@ public static function table(array $data, $title = 'Data Table', array $opts = [
], $opts);
$hasHead = false;
$rowIndex = 0;
- $head = $table = [];
+ $head = [];
$tableHead = $opts['columns'];
$leftIndent = $opts['leftIndent'];
$showBorder = $opts['showBorder'];
@@ -656,6 +681,89 @@ public static function table(array $data, $title = 'Data Table', array $opts = [
return 0;
}
+ /***********************************************************************************
+ * Output progress message
+ ***********************************************************************************/
+ /**
+ * show a spinner icon message
+ * ```php
+ * $total = 5000;
+ * while ($total--) {
+ * Show::spinner();
+ * usleep(100);
+ * }
+ * Show::spinner('Done', true);
+ * ```
+ * @param string $msg
+ * @param bool $ended
+ */
+ public static function spinner($msg = '', $ended = false)
+ {
+ static $chars = '-\\|/';
+ static $counter = 0;
+ static $lastTime = null;
+ $tpl = (Helper::supportColor() ? "\r\33[2K" : "\r\r") . '%s';
+ if ($ended) {
+ printf($tpl, $msg);
+
+ return;
+ }
+ $now = microtime(true);
+ if (null === $lastTime || $lastTime < $now - 0.1) {
+ $lastTime = $now;
+ // echo $chars[$counter];
+ printf($tpl, $chars[$counter] . $msg);
+ $counter++;
+ if ($counter > \strlen($chars) - 1) {
+ $counter = 0;
+ }
+ }
+ }
+
+ /**
+ * alias of the pending()
+ * @param string $msg
+ * @param bool $ended
+ */
+ public static function loading($msg = 'Loading ', $ended = false)
+ {
+ self::pending($msg, $ended);
+ }
+
+ /**
+ * show a pending message
+ * ```php
+ * $total = 8000;
+ * while ($total--) {
+ * Show::pending();
+ * usleep(200);
+ * }
+ * Show::pending('Done', true);
+ * ```
+ * @param string $msg
+ * @param bool $ended
+ */
+ public static function pending($msg = 'Pending ', $ended = false)
+ {
+ static $counter = 0;
+ static $lastTime = null;
+ static $chars = ['', '.', '..', '...'];
+ $tpl = (Helper::supportColor() ? "\r\33[2K" : "\r\r") . '%s';
+ if ($ended) {
+ printf($tpl, $msg);
+
+ return;
+ }
+ $now = microtime(true);
+ if (null === $lastTime || $lastTime < $now - 0.8) {
+ $lastTime = $now;
+ printf($tpl, $msg . $chars[$counter]);
+ $counter++;
+ if ($counter > \count($chars) - 1) {
+ $counter = 0;
+ }
+ }
+ }
/**
* 与文本进度条相比,没有 total
@@ -667,7 +775,7 @@ public static function counterTxt($msg, $doneMsg = null)
{
$counter = 0;
$finished = false;
- $tpl = (Helper::isSupportColor() ? "\r\33[2K" : "\r\r") . '%d %s';
+ $tpl = (Helper::supportColor() ? "\r\33[2K" : "\r\r") . '%d %s';
$msg = self::getStyle()->render($msg);
$doneMsg = $doneMsg ? self::getStyle()->render($doneMsg) : null;
while (true) {
@@ -685,9 +793,6 @@ public static function counterTxt($msg, $doneMsg = null)
}
$counter += $step;
}
- // printf("\r%d%% %s", $percent, $msg);
- // printf("\x0D\x2K %d%% %s", $percent, $msg);
- // printf("\x0D\r%'2d%% %s", $percent, $msg);
printf($tpl, $counter, $msg);
if ($finished) {
echo "\n";
@@ -707,7 +812,7 @@ public static function progressTxt($total, $msg, $doneMsg = null)
{
$current = 0;
$finished = false;
- $tpl = (Helper::isSupportColor() ? "\r\33[2K" : "\r\r") . "%' 3d%% %s";
+ $tpl = (Helper::supportColor() ? "\r\33[2K" : "\r\r") . "%' 3d%% %s";
$msg = self::getStyle()->render($msg);
$doneMsg = $doneMsg ? self::getStyle()->render($doneMsg) : null;
while (true) {
@@ -762,7 +867,7 @@ public static function progressBar($total, array $opts = [])
{
$current = 0;
$finished = false;
- $tplPrefix = Helper::isSupportColor() ? "\r\33[2K" : "\r\r";
+ $tplPrefix = Helper::supportColor() ? "\r\33[2K" : "\r\r";
$opts = array_merge(['doneChar' => '=', 'waitChar' => ' ', 'signChar' => '>', 'msg' => '', 'doneMsg' => ''], $opts);
$msg = self::getStyle()->render($opts['msg']);
$doneMsg = self::getStyle()->render($opts['doneMsg']);
@@ -825,9 +930,87 @@ public static function createProgressBar($max = 0, $start = true)
return $bar;
}
- /////////////////////////////////////////////////////////////////
- /// Helper Method
- /////////////////////////////////////////////////////////////////
+ /***********************************************************************************
+ * Output buffer
+ ***********************************************************************************/
+ /**
+ * @return bool
+ */
+ public static function isBuffering()
+ {
+ return self::$buffering;
+ }
+
+ /**
+ * @return string
+ */
+ public static function getBuffer()
+ {
+ return self::$buffer;
+ }
+
+ /**
+ * @param string $buffer
+ */
+ public static function setBuffer($buffer)
+ {
+ self::$buffer = $buffer;
+ }
+
+ /**
+ * start buffering
+ */
+ public static function startBuffer()
+ {
+ self::$buffering = true;
+ }
+
+ /**
+ * start buffering
+ */
+ public static function clearBuffer()
+ {
+ self::$buffer = null;
+ }
+
+ /**
+ * stop buffering
+ * @see Show::write()
+ * @param bool $flush Whether flush buffer to output stream
+ * @param bool $nl Default is False, because the last write() have been added "\n"
+ * @param bool $quit
+ * @param array $opts
+ * @return null|string If flush = False, will return all buffer text.
+ */
+ public static function stopBuffer($flush = true, $nl = false, $quit = false, array $opts = [])
+ {
+ self::$buffering = false;
+ if ($flush && self::$buffer) {
+ // all text have been rendered by Style::render() in every write();
+ $opts['color'] = false;
+ // flush to stream
+ self::write(self::$buffer, $nl, $quit, $opts);
+ // clear buffer
+ self::$buffer = null;
+ }
+
+ return self::$buffer;
+ }
+
+ /**
+ * stop buffering and flush buffer text
+ * @see Show::write()
+ * @param bool $nl
+ * @param bool $quit
+ * @param array $opts
+ */
+ public static function flushBuffer($nl = false, $quit = false, array $opts = [])
+ {
+ self::stopBuffer(true, $nl, $quit, $opts);
+ }
+ /***********************************************************************************
+ * Helper methods
+ ***********************************************************************************/
/**
* @return Style
*/
@@ -845,7 +1028,7 @@ public static function getStyle()
* [
* 'color' => bool, // whether render color, default is: True.
* 'stream' => resource, // the stream resource, default is: STDOUT
- * 'flush' => flush, // flush the stream data, default is: True
+ * 'flush' => bool, // flush the stream data, default is: True
* ]
* @return int
*/
@@ -858,8 +1041,19 @@ public static function write($messages, $nl = true, $quit = false, array $opts =
if (!isset($opts['color']) || $opts['color']) {
$messages = static::getStyle()->render($messages);
}
- $stream = isset($opts['stream']) ? $opts['stream'] : STDOUT;
- fwrite($stream, $messages . ($nl ? PHP_EOL : ''));
+ // if open buffering
+ if (self::isBuffering()) {
+ self::$buffer .= $messages . ($nl ? PHP_EOL : '');
+ if (!$quit) {
+ return 0;
+ }
+ // if will quit.
+ $messages = self::$buffer;
+ self::clearBuffer();
+ } else {
+ $messages .= $nl ? PHP_EOL : '';
+ }
+ fwrite($stream = isset($opts['stream']) ? $opts['stream'] : \STDOUT, $messages);
if (!isset($opts['flush']) || $opts['flush']) {
fflush($stream);
}
@@ -871,6 +1065,21 @@ public static function write($messages, $nl = true, $quit = false, array $opts =
return 0;
}
+ /**
+ * write raw data to stdout
+ * @param string|array $text
+ * @param bool $nl
+ * @param bool|int $quit
+ * @param array $opts
+ * @return int
+ */
+ public static function writeRaw($text, $nl = true, $quit = false, array $opts = [])
+ {
+ $opts['color'] = false;
+
+ return self::write($text, $nl, $quit, $opts);
+ }
+
/**
* Logs data to stdout
* @param string|array $text
diff --git a/tests/Components/TextTemplateTest.php b/tests/Components/TextTemplateTest.php
new file mode 100644
index 0000000..9f1e364
--- /dev/null
+++ b/tests/Components/TextTemplateTest.php
@@ -0,0 +1,45 @@
+ 'test',
+ 'date' => $date,
+ 'map' => [
+ 'VAL0',
+ 'key1' => 'VAL1',
+ ],
+ ]);
+
+ $ret = $tt->render($tpl);
+ $this->assertNotEmpty($ret);
+ $this->assertTrue((bool)strpos($ret, $date));
+ $this->assertTrue((bool)strpos($ret, 'VAL0'));
+ $this->assertStringEndsWith('VAL1', $ret);
+ }
+}
diff --git a/tests/boot.php b/tests/boot.php
index 2565785..567adcb 100644
--- a/tests/boot.php
+++ b/tests/boot.php
@@ -1,6 +1,28 @@