zhamao-framework/src/ZM/Utils/CommandInfoUtil.php
2022-05-03 16:56:04 +08:00

223 lines
6.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace ZM\Utils;
use JetBrains\PhpStorm\ArrayShape;
use ReflectionException;
use ReflectionMethod;
use ZM\Annotation\CQ\CommandArgument;
use ZM\Annotation\CQ\CQCommand;
use ZM\Console\Console;
use ZM\Event\EventManager;
use ZM\Store\WorkerCache;
class CommandInfoUtil
{
/**
* 判断命令信息是否已生成并缓存
*/
public function exists(): bool
{
return WorkerCache::get('commands') !== null;
}
/**
* 获取命令信息
*/
#[ArrayShape([['id' => 'string', 'call' => 'callable', 'descriptions' => ['string'], 'triggers' => ['trigger_name' => ['string']], 'args' => ['arg_name' => ['name' => 'string', 'type' => 'string', 'description' => 'string', 'default' => 'mixed', 'required' => 'bool']]]])]
public function get(): array
{
if (!$this->exists()) {
return $this->generateCommandList();
}
return WorkerCache::get('commands');
}
/**
* 重新生成命令信息
*/
public function regenerate(): void
{
$this->generateCommandList();
}
/**
* 获取命令帮助
*
* @param string $command_id 命令ID为 `class@method` 格式
* @param bool $simple 是否仅输出简易信息(只有命令触发条件和描述)
*/
public function getHelp(string $command_id, bool $simple = false): string
{
$command = $this->get()[$command_id];
$formats = [
'match' => '%s',
'pattern' => '符合”%s“',
'regex' => '匹配“%s”',
'start_with' => '以”%s“开头',
'end_with' => '以”%s“结尾',
'keyword' => '包含“%s”',
'alias' => '%s',
];
$triggers = [];
foreach ($command['triggers'] as $trigger => $conditions) {
if (count($conditions) === 0) {
continue;
}
if (isset($formats[$trigger])) {
$format = $formats[$trigger];
} else {
Console::warning("未知的命令触发条件:{$trigger}");
continue;
}
foreach ($conditions as $condition) {
$condition = sprintf($format, $condition);
$triggers[] = $condition;
}
}
$name = array_shift($triggers);
if (count($triggers) > 0) {
$name .= '' . implode('', $triggers) . '';
}
if (empty($command['descriptions'])) {
$description = '作者很懒,啥也没说';
} else {
$description = implode('', $command['descriptions']);
}
if ($simple) {
return "{$name}{$description}";
}
$lines = [];
$lines[0][] = $name;
$lines[1][] = $description;
foreach ($command['args'] as $arg_name => $arg_info) {
if ($arg_info['required']) {
$lines[0][] = "<{$arg_name}: {$arg_info['type']}>";
} else {
$buffer = "[{$arg_name}: {$arg_info['type']}";
if (!empty($arg_info['default'])) {
$buffer .= " = {$arg_info['default']}";
}
$lines[0][] = $buffer . ']';
}
$lines[][] = "{$arg_name}{$arg_info['description']}";
}
$buffer = [];
foreach ($lines as $line) {
$buffer[] = implode(' ', $line);
}
return implode("\n", $buffer);
}
/**
* 缓存命令信息
*/
protected function save(array $helps): void
{
WorkerCache::set('commands', $helps);
}
/**
* 根据注解树生成命令信息(内部)
*/
protected function generateCommandList(): array
{
$commands = [];
foreach (EventManager::$events[CQCommand::class] ?? [] as $annotation) {
$id = "{$annotation->class}@{$annotation->method}";
try {
$reflection = new ReflectionMethod($annotation->class, $annotation->method);
} catch (ReflectionException $e) {
Console::warning('命令 ' . $id . ' 注解解析错误:' . $e->getMessage());
continue;
}
$doc = $reflection->getDocComment();
if ($doc) {
// 匹配出不以@开头,且后接中文或任意非空格字符,并以换行符结尾的字符串,也就是命令描述
preg_match_all('/\*\s((?!@)[\x{4e00}-\x{9fa5}\S]+)(\r\n|\r|\n)/u', $doc, $descriptions);
$descriptions = $descriptions[1];
}
$command = [
'id' => $id,
'call' => [$annotation->class, $annotation->method],
'descriptions' => $descriptions ?? [],
'triggers' => [],
'args' => [],
];
if (empty($command['descriptions'])) {
Console::warning("命令没有描述信息:{$id}");
}
// 可能的触发条件,顺序会影响命令帮助的生成结果
$possible_triggers = ['match', 'pattern', 'regex', 'start_with', 'end_with', 'keyword', 'alias'];
foreach ($possible_triggers as $trigger) {
if (isset($annotation->{$trigger}) && !empty($annotation->{$trigger})) {
// 部分触发条件可能存在多个
if (is_iterable($annotation->{$trigger})) {
foreach ($annotation->{$trigger} as $item) {
$command['triggers'][$trigger][] = $item;
}
} else {
$command['triggers'][$trigger][] = $annotation->{$trigger};
}
}
}
if (empty($command['triggers'])) {
Console::warning("命令没有触发条件:{$id}");
continue;
}
$command['args'] = $this->generateCommandArgumentList($id);
$commands[$id] = $command;
}
$this->save($commands);
return $commands;
}
/**
* 生成指定命令的参数列表
*
* @param string $id 命令 ID
*/
protected function generateCommandArgumentList(string $id): array
{
[$class, $method] = explode('@', $id);
$map = EventManager::$event_map[$class][$method];
$args = [];
foreach ($map as $annotation) {
if (!$annotation instanceof CommandArgument) {
continue;
}
$args[$annotation->name] = [
'name' => $annotation->name,
'type' => $annotation->type,
'description' => $annotation->description,
'default' => $annotation->default,
'required' => $annotation->required,
];
}
return $args;
}
}