mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-23 08:35:35 +08:00
refactor plugin make command
This commit is contained in:
@@ -4,10 +4,14 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ZM\Command\Plugin;
|
namespace ZM\Command\Plugin;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||||
|
use Symfony\Component\Console\Question\Question;
|
||||||
use ZM\Bootstrap;
|
use ZM\Bootstrap;
|
||||||
use ZM\Command\Command;
|
use ZM\Command\Command;
|
||||||
|
use ZM\Plugin\PluginManager;
|
||||||
|
|
||||||
abstract class PluginCommand extends Command
|
abstract class PluginCommand extends Command
|
||||||
{
|
{
|
||||||
@@ -18,6 +22,111 @@ abstract class PluginCommand extends Command
|
|||||||
Bootstrap\LoadPlugins::class,
|
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
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
{
|
{
|
||||||
return parent::execute($input, $output);
|
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;
|
namespace ZM\Command\Plugin;
|
||||||
|
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
|
||||||
use Symfony\Component\Console\Question\Question;
|
|
||||||
use ZM\Store\FileSystem;
|
use ZM\Store\FileSystem;
|
||||||
use ZM\Utils\CodeGenerator\PluginGenerator;
|
use ZM\Utils\CodeGenerator\PluginGenerator;
|
||||||
|
|
||||||
@@ -43,65 +40,24 @@ class PluginMakeCommand extends PluginCommand
|
|||||||
$load_dir = SOURCE_ROOT_DIR . '/' . $load_dir;
|
$load_dir = SOURCE_ROOT_DIR . '/' . $load_dir;
|
||||||
}
|
}
|
||||||
$plugin_dir = zm_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) {
|
if ($this->input->getArgument('name') === null) {
|
||||||
$question = new Question('<question>请输入插件名称:</question>');
|
$this->questionWithArgument('name', '请输入插件名称(插件名称格式为"所有者/插件名",例如"foobar/demo-plugin")', [$this, 'validatePluginName']);
|
||||||
$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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 询问插件类型
|
// 询问插件类型
|
||||||
if ($this->input->getOption('type') === null) {
|
if ($this->input->getOption('type') === null) {
|
||||||
$question = new ChoiceQuestion(
|
$this->choiceWithOption('type', '请输入要生成的插件结构类型', [
|
||||||
'<question>请输入要生成的插件结构类型</question>',
|
'file' => 'file 类型为单文件,方便写简单功能',
|
||||||
['file' => 'file 类型为单文件,方便写简单功能', 'psr4' => 'psr4 类型为目录,按照 psr-4 结构生成,同时将生成 composer.json 用来支持自动加载']
|
'psr4' => 'psr4 类型为目录,按照 psr-4 结构生成,同时将生成 composer.json 用来支持自动加载(推荐)',
|
||||||
);
|
]);
|
||||||
$this->input->setOption('type', $helper->ask($this->input, $this->output, $question));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->input->getOption('type') === 'psr4') {
|
if ($this->input->getOption('type') === 'psr4') {
|
||||||
// 询问命名空间
|
// 询问命名空间
|
||||||
if ($this->input->getOption('namespace') === null) {
|
if ($this->input->getOption('namespace') === null) {
|
||||||
$question = new Question('<question>请输入插件命名空间:</question>');
|
$this->questionWithOption('namespace', '请输入插件命名空间:', [$this, 'validateNamespace']);
|
||||||
$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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user