mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-03-18 05:04:51 +08:00
refactor plugin make command
This commit is contained in:
parent
be2de100aa
commit
055d6d7b3a
@ -4,10 +4,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace ZM\Command\Plugin;
|
||||
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use ZM\Bootstrap;
|
||||
use ZM\Command\Command;
|
||||
use ZM\Plugin\PluginManager;
|
||||
|
||||
abstract class PluginCommand extends Command
|
||||
{
|
||||
@ -18,6 +22,111 @@ abstract class PluginCommand extends Command
|
||||
Bootstrap\LoadPlugins::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* 插件名称合规验证器
|
||||
*/
|
||||
public function validatePluginName(string $answer): string
|
||||
{
|
||||
if (empty($answer)) {
|
||||
throw new \RuntimeException('插件名称不能为空');
|
||||
}
|
||||
if (is_numeric(mb_substr($answer, 0, 1))) {
|
||||
throw new \RuntimeException('插件名称不能以数字开头,且只能包含字母、数字、下划线、短横线');
|
||||
}
|
||||
if (!preg_match('/^[\/a-zA-Z0-9_-]+$/', $answer)) {
|
||||
throw new \RuntimeException('插件名称只能包含字母、数字、下划线、短横线');
|
||||
}
|
||||
$exp = explode('/', $answer);
|
||||
if (count($exp) !== 2) {
|
||||
throw new \RuntimeException('插件名称必须为"组织或所有者/插件名称"的格式,且只允许有一个斜杠分割两者');
|
||||
}
|
||||
if ($exp[0] === 'zhamao') {
|
||||
throw new \RuntimeException('插件所有者或组织名不可以为"zhamao",请换个名字');
|
||||
}
|
||||
if ($exp[0] === '' || $exp[1] === '') {
|
||||
throw new \RuntimeException('插件所有者或组织名、插件名称均不可为空');
|
||||
}
|
||||
if (PluginManager::isPluginExists($answer)) {
|
||||
throw new \RuntimeException('名称为 ' . $answer . ' 的插件已存在,请换个名字');
|
||||
}
|
||||
return $answer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 命名空间合规验证器
|
||||
*/
|
||||
public function validateNamespace(string $answer): string
|
||||
{
|
||||
if (empty($answer)) {
|
||||
throw new \RuntimeException('插件命名空间不能为空');
|
||||
}
|
||||
if (is_numeric(mb_substr($answer, 0, 1))) {
|
||||
throw new \RuntimeException('插件命名空间不能以数字开头,且只能包含字母、数字、反斜线');
|
||||
}
|
||||
// 只能包含字母、数字和反斜线
|
||||
if (!preg_match('/^[a-zA-Z0-9\\\\]+$/', $answer)) {
|
||||
throw new \RuntimeException('插件命名空间只能包含字母、数字、反斜线');
|
||||
}
|
||||
return $answer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 Question 来询问缺失的参数
|
||||
*
|
||||
* @param string $name 参数名称
|
||||
* @param string $question 问题
|
||||
* @param callable $validator 验证器
|
||||
*/
|
||||
protected function questionWithArgument(string $name, string $question, callable $validator): void
|
||||
{
|
||||
/** @var QuestionHelper $helper */
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new Question('<question>' . $question . '</question>');
|
||||
$question->setValidator($validator);
|
||||
$this->input->setArgument($name, $helper->ask($this->input, $this->output, $question));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 Question 来询问缺失的参数
|
||||
*
|
||||
* @param string $name 参数名称
|
||||
* @param string $question 问题
|
||||
* @param callable $validator 验证器
|
||||
*/
|
||||
protected function questionWithOption(string $name, string $question, callable $validator): void
|
||||
{
|
||||
/** @var QuestionHelper $helper */
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new Question('<question>' . $question . '</question>');
|
||||
$question->setValidator($validator);
|
||||
$this->input->setOption($name, $helper->ask($this->input, $this->output, $question));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加选择题来询问缺失的参数
|
||||
*
|
||||
* @param string $name 可选参数名称
|
||||
* @param string $question 问题
|
||||
* @param array $selection 选项(K-V 类型)
|
||||
*/
|
||||
protected function choiceWithOption(string $name, string $question, array $selection): void
|
||||
{
|
||||
/** @var QuestionHelper $helper */
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new ChoiceQuestion('<question>' . $question . '</question>', $selection);
|
||||
$this->input->setOption($name, $helper->ask($this->input, $this->output, $question));
|
||||
}
|
||||
|
||||
protected function getTypeDisplayName(int $type): string
|
||||
{
|
||||
return match ($type) {
|
||||
ZM_PLUGIN_TYPE_NATIVE => '内部',
|
||||
ZM_PLUGIN_TYPE_PHAR => 'Phar',
|
||||
ZM_PLUGIN_TYPE_SOURCE => '源码',
|
||||
ZM_PLUGIN_TYPE_COMPOSER => 'Composer 外部加载'
|
||||
};
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
return parent::execute($input, $output);
|
||||
|
||||
26
src/ZM/Command/Plugin/PluginListCommand.php
Normal file
26
src/ZM/Command/Plugin/PluginListCommand.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Command\Plugin;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use ZM\Plugin\PluginManager;
|
||||
|
||||
#[AsCommand(name: 'plugin:list', description: '显示插件列表')]
|
||||
class PluginListCommand extends PluginCommand
|
||||
{
|
||||
protected function handle(): int
|
||||
{
|
||||
$all = PluginManager::getPlugins();
|
||||
$table = new Table($this->output);
|
||||
$table->setHeaders(['名称', '版本', '类型']);
|
||||
foreach ($all as $k => $v) {
|
||||
$table->addRow([$k, $v->getVersion(), $this->getTypeDisplayName($v->getPluginType())]);
|
||||
}
|
||||
$table->setStyle('box');
|
||||
$table->render();
|
||||
return static::SUCCESS;
|
||||
}
|
||||
}
|
||||
@ -5,11 +5,8 @@ declare(strict_types=1);
|
||||
namespace ZM\Command\Plugin;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use ZM\Store\FileSystem;
|
||||
use ZM\Utils\CodeGenerator\PluginGenerator;
|
||||
|
||||
@ -43,65 +40,24 @@ class PluginMakeCommand extends PluginCommand
|
||||
$load_dir = SOURCE_ROOT_DIR . '/' . $load_dir;
|
||||
}
|
||||
$plugin_dir = zm_dir($load_dir);
|
||||
/** @var QuestionHelper $helper */
|
||||
$helper = $this->getHelper('question');
|
||||
|
||||
$other_plugins = is_dir($plugin_dir) ? FileSystem::scanDirFiles($plugin_dir, false, true, true) : [];
|
||||
|
||||
// 询问插件名称
|
||||
if ($this->input->getArgument('name') === null) {
|
||||
$question = new Question('<question>请输入插件名称:</question>');
|
||||
$question->setValidator(function ($answer) use ($plugin_dir, $other_plugins) {
|
||||
if (empty($answer)) {
|
||||
throw new \RuntimeException('插件名称不能为空');
|
||||
}
|
||||
if (is_numeric(mb_substr($answer, 0, 1))) {
|
||||
throw new \RuntimeException('插件名称不能以数字开头,且只能包含字母、数字、下划线、短横线');
|
||||
}
|
||||
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $answer)) {
|
||||
throw new \RuntimeException('插件名称只能包含字母、数字、下划线、短横线');
|
||||
}
|
||||
if (is_dir($plugin_dir . '/' . strtolower($answer))) {
|
||||
throw new \RuntimeException('插件目录已存在,请换个名字');
|
||||
}
|
||||
foreach ($other_plugins as $dir_name) {
|
||||
$plugin_name = file_exists($plugin_dir . '/' . $dir_name . '/zmplugin.json') ? (json_decode(file_get_contents($plugin_dir . '/' . $dir_name . '/zmplugin.json'), true)['name'] ?? null) : null;
|
||||
if ($plugin_name !== null && $plugin_name === $answer) {
|
||||
throw new \RuntimeException('插件名称已存在,请换个名字');
|
||||
}
|
||||
}
|
||||
return $answer;
|
||||
});
|
||||
$this->input->setArgument('name', $helper->ask($this->input, $this->output, $question));
|
||||
$this->questionWithArgument('name', '请输入插件名称(插件名称格式为"所有者/插件名",例如"foobar/demo-plugin")', [$this, 'validatePluginName']);
|
||||
}
|
||||
|
||||
// 询问插件类型
|
||||
if ($this->input->getOption('type') === null) {
|
||||
$question = new ChoiceQuestion(
|
||||
'<question>请输入要生成的插件结构类型</question>',
|
||||
['file' => 'file 类型为单文件,方便写简单功能', 'psr4' => 'psr4 类型为目录,按照 psr-4 结构生成,同时将生成 composer.json 用来支持自动加载']
|
||||
);
|
||||
$this->input->setOption('type', $helper->ask($this->input, $this->output, $question));
|
||||
$this->choiceWithOption('type', '请输入要生成的插件结构类型', [
|
||||
'file' => 'file 类型为单文件,方便写简单功能',
|
||||
'psr4' => 'psr4 类型为目录,按照 psr-4 结构生成,同时将生成 composer.json 用来支持自动加载(推荐)',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->input->getOption('type') === 'psr4') {
|
||||
// 询问命名空间
|
||||
if ($this->input->getOption('namespace') === null) {
|
||||
$question = new Question('<question>请输入插件命名空间:</question>');
|
||||
$question->setValidator(function ($answer) {
|
||||
if (empty($answer)) {
|
||||
throw new \RuntimeException('插件命名空间不能为空');
|
||||
}
|
||||
if (is_numeric(mb_substr($answer, 0, 1))) {
|
||||
throw new \RuntimeException('插件命名空间不能以数字开头,且只能包含字母、数字、反斜线');
|
||||
}
|
||||
// 只能包含字母、数字和反斜线
|
||||
if (!preg_match('/^[a-zA-Z0-9\\\\]+$/', $answer)) {
|
||||
throw new \RuntimeException('插件命名空间只能包含字母、数字、反斜线');
|
||||
}
|
||||
return $answer;
|
||||
});
|
||||
$this->input->setOption('namespace', $helper->ask($this->input, $this->output, $question));
|
||||
$this->questionWithOption('namespace', '请输入插件命名空间:', [$this, 'validateNamespace']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user