From d1f9829db9b0d331fdf556bb2b95e1040050b63d Mon Sep 17 00:00:00 2001 From: Inhere Date: Sun, 26 Dec 2021 20:57:09 +0800 Subject: [PATCH] feat: allow attch multi level sub-commands to command,group --- examples/Command/TestCommand.php | 2 +- examples/Controller/Attach/DemoSubCommand.php | 40 ++++ examples/Controller/HomeController.php | 25 ++- examples/Controller/ShowController.php | 2 +- src/Command.php | 52 +++-- src/Component/PharCompiler.php | 1 - src/Component/ReadlineCompleter.php | 13 +- src/Component/Router.php | 17 +- src/Contract/CommandHandlerInterface.php | 5 + src/Controller.php | 15 +- src/Decorate/SubCommandsWareTrait.php | 161 ++++++++++++---- src/Handler/AbstractHandler.php | 86 +++------ src/Handler/CallableCommand.php | 126 +------------ src/Handler/CommandWrapper.php | 178 ++++++++++++++++++ src/Util/ConsoleUtil.php | 29 +++ test/CommandTest.php | 22 ++- test/ControllerTest.php | 2 +- test/TestCommand.php | 13 ++ 18 files changed, 510 insertions(+), 279 deletions(-) create mode 100644 examples/Controller/Attach/DemoSubCommand.php create mode 100644 src/Handler/CommandWrapper.php create mode 100644 src/Util/ConsoleUtil.php diff --git a/examples/Command/TestCommand.php b/examples/Command/TestCommand.php index 6725dc9..6239cb7 100644 --- a/examples/Command/TestCommand.php +++ b/examples/Command/TestCommand.php @@ -23,7 +23,7 @@ class TestCommand extends Command protected static string $desc = 'this is a test independent command'; - protected function commands(): array + protected function subCommands(): array { return [ 'sub' => static function ($fs, $out): void { diff --git a/examples/Controller/Attach/DemoSubCommand.php b/examples/Controller/Attach/DemoSubCommand.php new file mode 100644 index 0000000..c6c79c2 --- /dev/null +++ b/examples/Controller/Attach/DemoSubCommand.php @@ -0,0 +1,40 @@ + 'string option1', + 's2,str2' => 'string option2', + ]; + } + + /** + * Do execute command + * + * @param Input $input + * @param Output $output + * + * @return void|mixed + */ + protected function execute(Input $input, Output $output) + { + vdump(__METHOD__); + } +} diff --git a/examples/Controller/HomeController.php b/examples/Controller/HomeController.php index 05cc45b..0cbc71e 100644 --- a/examples/Controller/HomeController.php +++ b/examples/Controller/HomeController.php @@ -11,6 +11,8 @@ use Inhere\Console\Component\Symbol\ArtFont; use Inhere\Console\Controller; +use Inhere\Console\Examples\Controller\Attach\DemoSubCommand; +use Inhere\Console\Handler\CommandWrapper; use Inhere\Console\Util\Interact; use Inhere\Console\Util\ProgressBar; use Inhere\Console\Util\Show; @@ -23,6 +25,7 @@ use Toolkit\Stdlib\Php; use function sleep; use function trigger_error; +use function vdump; /** * default command controller. there are some command usage examples(1) @@ -74,13 +77,30 @@ protected function init(): void /** * @return array */ - protected function options(): array + protected function getOptions(): array { return [ '-c, --common' => 'This is a common option for all sub-commands', ]; } + protected function subCommands(): array + { + return [ + DemoSubCommand::class, + CommandWrapper::wrap(static function ($fs) { + vdump(__METHOD__, $fs->getOpts()); + }, [ + 'name' => 'sub2', + 'desc' => 'an sub command in group controller', + 'options' => [ + 'int1' => 'int;an int option1', + 'i2, int2' => 'int;an int option2', + ] + ]), + ]; + } + protected function disabledCommands(): array { return ['disabled']; @@ -207,6 +227,9 @@ public function colorCheckCommand(): void * --font Set the art font name(allow: {internalFonts}). * --italic bool;Set the art font type is italic. * --style Set the art font style. + * + * @param FlagsParser $fs + * * @return int */ public function artFontCommand(FlagsParser $fs): int diff --git a/examples/Controller/ShowController.php b/examples/Controller/ShowController.php index 9ed5b8a..dec3b54 100644 --- a/examples/Controller/ShowController.php +++ b/examples/Controller/ShowController.php @@ -43,7 +43,7 @@ public static function commandAliases(): array /** * @return array */ - protected function options(): array + protected function getOptions(): array { return [ '-c, --common' => 'This is a common option for all sub-commands', diff --git a/src/Command.php b/src/Command.php index 5526379..a3cd4f5 100644 --- a/src/Command.php +++ b/src/Command.php @@ -13,6 +13,7 @@ use Inhere\Console\Handler\AbstractHandler; use ReflectionException; use Toolkit\PFlag\FlagsParser; +use function array_shift; /** * Class Command @@ -38,14 +39,9 @@ abstract class Command extends AbstractHandler implements CommandInterface */ protected ?Controller $group = null; - /** - * @var Command|null - */ - protected ?Command $parent = null; - protected function init(): void { - $this->commandName = self::getName(); + $this->commandName = $this->getRealName(); parent::init(); } @@ -53,7 +49,7 @@ protected function init(): void /** * @return array */ - public function getArguments(): array + protected function getArguments(): array { return []; } @@ -63,12 +59,13 @@ public function getArguments(): array */ protected function beforeInitFlagsParser(FlagsParser $fs): void { + $fs->addArgsByRules($this->getArguments()); $fs->setStopOnFistArg(false); // old mode: options and arguments at method annotations - if ($this->compatible) { - $fs->setSkipOnUndefined(true); - } + // if ($this->compatible) { + // $fs->setSkipOnUndefined(true); + // } } /** @@ -93,31 +90,24 @@ protected function afterInitFlagsParser(FlagsParser $fs): void } /** - * @param Command $parent - */ - public function setParent(Command $parent): void - { - $this->parent = $parent; - } - - /** - * @return $this + * @param array $args + * + * @return mixed */ - public function getRoot(): Command + protected function doRun(array $args): mixed { - if ($this->parent) { - return $this->parent->getRoot(); + // if input sub-command name + if (isset($args[0])) { + $first = $args[0]; + $rName = $this->resolveAlias($first); + + if ($this->isSub($rName)) { + array_shift($args); + return $this->dispatchSub($rName, $args); + } } - return $this; - } - - /** - * @return Command|null - */ - public function getParent(): ?Command - { - return $this->parent; + return parent::doRun($args); } /** diff --git a/src/Component/PharCompiler.php b/src/Component/PharCompiler.php index 58653b0..984900a 100644 --- a/src/Component/PharCompiler.php +++ b/src/Component/PharCompiler.php @@ -16,7 +16,6 @@ use Exception; use FilesystemIterator; use Generator; -use Inhere\Console\Util\Helper; use InvalidArgumentException; use Iterator; use Phar; diff --git a/src/Component/ReadlineCompleter.php b/src/Component/ReadlineCompleter.php index 26d1bc3..2555836 100644 --- a/src/Component/ReadlineCompleter.php +++ b/src/Component/ReadlineCompleter.php @@ -44,10 +44,13 @@ public function isSupported(): bool /** * @param callable $completer + * + * @return ReadlineCompleter */ - public function setCompleter(callable $completer): void + public function setCompleter(callable $completer): static { $this->completer = $completer; + return $this; } /** @@ -113,4 +116,12 @@ public function setHistorySize(int $historySize): void { $this->historySize = $historySize; } + + /** + * @return callable + */ + public function getCompleter(): callable + { + return $this->completer; + } } diff --git a/src/Component/Router.php b/src/Component/Router.php index 500eebe..38d9966 100644 --- a/src/Component/Router.php +++ b/src/Component/Router.php @@ -16,6 +16,7 @@ use Inhere\Console\Contract\RouterInterface; use Inhere\Console\Controller; use Inhere\Console\IO\Output; +use Inhere\Console\Util\ConsoleUtil; use Inhere\Console\Util\Helper; use InvalidArgumentException; use Toolkit\PFlag\FlagsParser; @@ -206,7 +207,7 @@ public function addCommand(string $name, string|Closure|CommandInterface $handle if ($aliases = $handler::aliases()) { $config['aliases'] = array_merge($config['aliases'], $aliases); } - } elseif (!is_object($handler) || !$this->isValidCmdObject($handler)) { + } elseif (!is_object($handler) || !ConsoleUtil::isValidCmdObject($handler)) { Helper::throwInvalidArgument( 'The command handler must is an subclass of %s OR a Closure OR a sub-object of %s', Command::class, @@ -230,20 +231,6 @@ public function addCommand(string $name, string|Closure|CommandInterface $handle return $this; } - /** - * @param object $handler - * - * @return bool - */ - private function isValidCmdObject(object $handler): bool - { - if ($handler instanceof Command) { - return true; - } - - return method_exists($handler, '__invoke'); - } - /** * @param array $commands * diff --git a/src/Contract/CommandHandlerInterface.php b/src/Contract/CommandHandlerInterface.php index 264f7dc..3e0e8d2 100644 --- a/src/Contract/CommandHandlerInterface.php +++ b/src/Contract/CommandHandlerInterface.php @@ -59,6 +59,11 @@ public function getGroupName(): string; */ public function getRealName(): string; + /** + * @return string + */ + public function getRealDesc(): string; + /** * The real group name. * diff --git a/src/Controller.php b/src/Controller.php index 964aec3..724c522 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -300,21 +300,26 @@ public function doRun(array $args): mixed // convert 'boo-foo' to 'booFoo' $this->action = $action = Str::camelCase($command); $this->debugf("will run the '%s' group action: %s, subcommand: %s", $name, $action, $command); - $this->actionMethod = $method = $this->getMethodName($action); + $method = $this->getMethodName($action); // fire event $this->fire(ConsoleEvent::COMMAND_RUN_BEFORE, $this); $this->beforeRun(); // check method not exist - // - if command method not exists. if (!method_exists($this, $method)) { + if ($this->isSub($command)) { + return $this->dispatchSub($command, $args); + } + + // if command not exists. return $this->handleNotFound($name, $action, $args); } // init flags for subcommand $fs = $this->newActionFlags(); + $this->actionMethod = $method; $this->input->setFs($fs); $this->debugf('load flags by configure method, subcommand: %s', $command); $this->configure(); @@ -485,9 +490,9 @@ protected function newActionFlags(string $action = ''): FlagsParser }); // old mode: options and arguments at method annotations - if ($this->compatible) { - $fs->setSkipOnUndefined(true); - } + // if ($this->compatible) { + // $fs->setSkipOnUndefined(true); + // } // save $this->subFss[$action] = $fs; diff --git a/src/Decorate/SubCommandsWareTrait.php b/src/Decorate/SubCommandsWareTrait.php index 818bb2a..b042c70 100644 --- a/src/Decorate/SubCommandsWareTrait.php +++ b/src/Decorate/SubCommandsWareTrait.php @@ -13,8 +13,12 @@ use Inhere\Console\Command; use Inhere\Console\Console; use Inhere\Console\Contract\CommandInterface; +use Inhere\Console\Handler\AbstractHandler; +use Inhere\Console\Handler\CommandWrapper; +use Inhere\Console\Util\ConsoleUtil; use Inhere\Console\Util\Helper; use InvalidArgumentException; +use Toolkit\Stdlib\Helper\Assert; use Toolkit\Stdlib\Obj\Traits\NameAliasTrait; use function array_keys; use function array_merge; @@ -24,7 +28,6 @@ use function is_object; use function is_string; use function is_subclass_of; -use function method_exists; use function preg_match; /** @@ -36,6 +39,11 @@ trait SubCommandsWareTrait { use NameAliasTrait; + /** + * @var AbstractHandler|null + */ + protected ?AbstractHandler $parent = null; + /** * @var array */ @@ -44,28 +52,38 @@ trait SubCommandsWareTrait /** * The sub-commands of the command * - * @var array + * ```php * [ * 'name' => [ - * 'handler' => MyCommand::class, // allow: string|Closure|CommandInterface - * 'config' => [] + * 'handler' => MyCommand::class, + * 'config' => [ + * 'name' => 'string', + * 'desc' => 'string', + * 'options' => [], + * 'arguments' => [], + * ] * ] * ] + * ``` + * + * @var array */ private array $commands = []; /** - * Can attach sub-commands + * Can attach sub-commands to current command * * @return array */ - protected function commands(): array + protected function subCommands(): array { // [ // 'cmd1' => function(){}, + // // class name // MySubCommand::class, // 'cmd2' => MySubCommand2::class, - // new FooCommand, + // // no key + // new FooCommand(), // 'cmd3' => new FooCommand2(), // ] return []; @@ -73,22 +91,49 @@ protected function commands(): array /** * @param string $name + * @param array $args + * + * @return mixed */ - protected function dispatchCommand(string $name): void + protected function dispatchSub(string $name, array $args): mixed { + $subInfo = $this->commands[$name]; + $this->debugf('dispatch the attached subcommand: %s', $name); + + // create and init sub-command + $subCmd = $this->createSubCommand($subInfo); + $subCmd->setParent($this); + $subCmd->setInputOutput($this->input, $this->output); + + return $subCmd->run($args); + } + + /** + * @param array{name: string, desc: string, options: array, arguments: array} $subInfo + * + * @return Command + */ + protected function createSubCommand(array $subInfo): Command + { + $handler = $subInfo['handler']; + if (is_object($handler)) { + if ($handler instanceof Command) { + return $handler; + } + + return CommandWrapper::wrap($handler, $subInfo['config']); + } + + // class-string of Command + return new $handler; } /** * Register a app independent console command * - * @param string|CommandInterface $name + * @param string|class-string $name * @param string|Closure|CommandInterface|null $handler - * @param array $config - * array: - * - aliases The command aliases - * - description The description message - * - * @throws InvalidArgumentException + * @param array $config */ public function addSub(string $name, string|Closure|CommandInterface $handler = null, array $config = []): void { @@ -96,24 +141,21 @@ public function addSub(string $name, string|Closure|CommandInterface $handler = /** @var Command $name name is an command class */ $handler = $name; $name = $name::getName(); + } elseif (!$name && $handler instanceof Command) { + $name = $handler->getRealName(); + } elseif (!$name && class_exists($handler)) { + $name = $handler::getName(); } - if (!$name || !$handler) { - Helper::throwInvalidArgument("Command 'name' and 'handler' cannot be empty! name: $name"); - } + Assert::isFalse(!$name || !$handler, "Command 'name' and 'handler' cannot be empty! name: $name"); + Assert::isFalse(isset($this->commands[$name]), "Command '$name' have been registered!"); $this->validateName($name); - if (isset($this->commands[$name])) { - Helper::throwInvalidArgument("Command '$name' have been registered!"); - } - $config['aliases'] = isset($config['aliases']) ? (array)$config['aliases'] : []; if (is_string($handler)) { - if (!class_exists($handler)) { - Helper::throwInvalidArgument("The command handler class [$handler] not exists!"); - } + Assert::isTrue(class_exists($handler), "The console command class '$handler' not exists!"); if (!is_subclass_of($handler, Command::class)) { Helper::throwInvalidArgument('The command handler class must is subclass of the: ' . Command::class); @@ -129,42 +171,89 @@ public function addSub(string $name, string|Closure|CommandInterface $handler = if ($aliases = $handler::aliases()) { $config['aliases'] = array_merge($config['aliases'], $aliases); } - } elseif (!is_object($handler) || !method_exists($handler, '__invoke')) { + } elseif (!is_object($handler) || !ConsoleUtil::isValidCmdObject($handler)) { Helper::throwInvalidArgument( - 'The console command handler must is an subclass of %s OR a Closure OR a object have method __invoke()', - Command::class + 'The command handler must is an subclass of %s OR a Closure OR a sub-object of %s', + Command::class, + Command::class, ); } + // has alias option + if ($config['aliases']) { + $this->setAlias($name, $config['aliases'], true); + } + + $config['name'] = $name; + // save + $this->commands[$name] = [ + 'type' => Console::CMD_SINGLE, + 'handler' => $handler, + 'config' => $config, + ]; + } + + /** + * @param CommandInterface $handler + * + * @return $this + */ + public function addSubHandler(CommandInterface $handler): static + { + $name = $handler->getRealName(); + // is an class name string $this->commands[$name] = [ 'type' => Console::CMD_SINGLE, 'handler' => $handler, - 'config' => $config, + 'config' => [], ]; - // has alias option - if (isset($config['aliases'])) { - $this->setAlias($name, $config['aliases'], true); - } + return $this; } /** * @param array $commands - * - * @throws InvalidArgumentException */ public function addCommands(array $commands): void { foreach ($commands as $name => $handler) { if (is_int($name)) { - $this->addSub($handler); + $this->addSub('', $handler); } else { $this->addSub($name, $handler); } } } + /** + * @param AbstractHandler $parent + */ + public function setParent(AbstractHandler $parent): void + { + $this->parent = $parent; + } + + /** + * @return $this + */ + public function getRoot(): static + { + if ($this->parent) { + return $this->parent->getRoot(); + } + + return $this; + } + + /** + * @return static|null + */ + public function getParent(): ?static + { + return $this->parent; + } + /********************************************************** * helper methods **********************************************************/ diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php index 7ecc6d3..8c68359 100644 --- a/src/Handler/AbstractHandler.php +++ b/src/Handler/AbstractHandler.php @@ -23,7 +23,6 @@ use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; use Inhere\Console\Util\Helper; -use InvalidArgumentException; use ReflectionException; use RuntimeException; use Throwable; @@ -34,7 +33,6 @@ use function cli_set_process_title; use function error_get_last; use function function_exists; -use function vdump; use const PHP_OS; /** @@ -70,14 +68,6 @@ abstract class AbstractHandler implements CommandHandlerInterface */ protected static string $desc = ''; - /** - * The command/controller description message - * - * @var string - * @deprecated please use {@see $desc} - */ - protected static string $description = ''; - /** * @var bool Whether enable coroutine. It is require swoole extension. */ @@ -101,9 +91,9 @@ abstract class AbstractHandler implements CommandHandlerInterface protected string $processTitle = ''; /** - * @var DataObject + * @var DataObject|null */ - protected DataObject $params; + protected ?DataObject $params = null; /** * The input command name. maybe is an alias name. @@ -160,8 +150,8 @@ protected function init(): void { // $this->commentsVars = $this->annotationVars(); $this->afterInit(); - $this->debugf('attach inner subcommands to "%s"', self::getName()); - $this->addCommands($this->commands()); + $this->debugf('attach inner subcommands to "%s"', $this->getRealName()); + $this->addCommands($this->subCommands()); } protected function afterInit(): void @@ -170,7 +160,7 @@ protected function afterInit(): void } /** - * command options + * get command options * * **Alone Command** * @@ -183,7 +173,7 @@ protected function afterInit(): void * * @return array */ - protected function options(): array + protected function getOptions(): array { // ['--skip-invalid' => 'Whether ignore invalid arguments and options, when use input definition',] return []; @@ -257,13 +247,13 @@ protected function initFlagsParser(Input $input): void } $input->setFs($this->flags); - $this->flags->setDesc(self::getDesc()); - $this->flags->setScriptName(self::getName()); + $this->flags->setDesc($this->getRealDesc()); + $this->flags->setScriptName($this->getRealName()); $this->beforeInitFlagsParser($this->flags); // set options by options() - $optRules = $this->options(); + $optRules = $this->getOptions(); $this->flags->addOptsByRules($optRules); // for render help @@ -298,11 +288,10 @@ protected function afterInitFlagsParser(FlagsParser $fs): void * @param array $args * * @return mixed - * @throws Throwable */ public function run(array $args): mixed { - $name = self::getName(); + $name = $this->getRealName(); try { $this->initForRun(); @@ -314,7 +303,7 @@ public function run(array $args): mixed // parse options $this->flags->lock(); if (!$this->flags->parse($args)) { - return 0; // on error, help + return 0; // on error OR help } $args = $this->flags->getRawArgs(); @@ -324,7 +313,7 @@ public function run(array $args): mixed if ($this->isDetached()) { ErrorHandler::new()->handle($e); } else { - throw $e; + throw new RuntimeException('Run error - ' . $e->getMessage(), $e->getCode(), $e); } } @@ -340,23 +329,11 @@ public function run(array $args): mixed */ protected function doRun(array $args): mixed { - if (isset($args[0])) { - $first = $args[0]; - $rName = $this->resolveAlias($first); -// vdump($first, $rName); - // TODO - // if ($this->isSub($rName)) { - // } - } - // some prepare check - // - validate input arguments if (true !== $this->prepare()) { return -1; } - // $this->dispatchCommand($name); - // return False to deny goon run. if (false === $this->beforeExecute()) { return -1; @@ -378,6 +355,11 @@ protected function doRun(array $args): mixed return $result; } + protected function doExecute(): mixed + { + return ''; + } + /** * coroutine run by swoole go() * @@ -435,9 +417,6 @@ protected function afterExecute(): void /** * prepare run - * - * @throws InvalidArgumentException - * @throws RuntimeException */ protected function prepare(): bool { @@ -491,13 +470,10 @@ public function getParams(): DataObject /** * @param array $params - * - * @return DataObject */ - public function initParams(array $params): DataObject + public function initParams(array $params): void { $this->params = DataObject::new($params); - return $this->params; } /** @@ -587,6 +563,14 @@ public function getRealName(): string return self::getName(); } + /** + * @return string + */ + public function getRealDesc(): string + { + return self::getDesc(); + } + /** * @param bool $useReal * @@ -632,7 +616,7 @@ final public static function getName(): string */ public static function getDesc(): string { - return static::$desc ?: static::$description; + return static::$desc; } /** @@ -645,22 +629,6 @@ public static function setDesc(string $desc): void } } - /** - * @return string - */ - public static function getDescription(): string - { - return self::getDesc(); - } - - /** - * @param string $desc - */ - public static function setDescription(string $desc): void - { - self::setDesc($desc); - } - /** * @return bool */ diff --git a/src/Handler/CallableCommand.php b/src/Handler/CallableCommand.php index 9037c7c..561f85e 100644 --- a/src/Handler/CallableCommand.php +++ b/src/Handler/CallableCommand.php @@ -9,136 +9,12 @@ namespace Inhere\Console\Handler; -use Inhere\Console\Command; -use Inhere\Console\IO\Input; -use Inhere\Console\IO\Output; -use BadMethodCallException; -use Toolkit\PFlag\FlagsParser; - /** * Class CallableCommand - wrap an callable as Command * * @package Inhere\Console\Handler */ -class CallableCommand extends Command +class CallableCommand extends CommandWrapper { - /** - * @var callable(FlagsParser, Output): void - */ - private $callable; - - /** - * @var array{options: array, arguments: array} - */ - protected array $config = []; - - // public function new(callable $callable): self - // { - // } - - // public function __construct(Input $input, Output $output, InputDefinition $definition = null) - // { - // parent::__construct($input, $output, $definition); - // } - - // /** - // * @param callable $cb - // * - // * @return static - // */ - // public static function wrap(callable $cb): self - // { - // return (new self())->setCallable($cb); - // } - - /** - * @param callable $fn - * - * @return $this - */ - public function withFunc(callable $fn): self - { - return $this->setCallable($fn); - } - - /** - * @param callable $fn - * - * @return $this - */ - public function withCustom(callable $fn): self - { - $fn($this); - return $this; - } - - /** - * @param array $config - * - * @return $this - */ - public function withConfig(array $config): self - { - $this->config = $config; - return $this; - } - - /** - * @param array $options - * - * @return $this - */ - public function withOptions(array $options): self - { - $this->config['options'] = $options; - return $this; - } - - /** - * @param array $arguments - * - * @return $this - */ - public function withArguments(array $arguments): self - { - $this->config['arguments'] = $arguments; - return $this; - } - - /** - * @param callable $callable - * - * @return CallableCommand - */ - public function setCallable(callable $callable): self - { - $this->callable = $callable; - return $this; - } - - /** - * @return array - */ - public function getConfig(): array - { - return $this->config; - } - - /** - * Do execute command - * - * @param Input $input - * @param Output $output - * - * @return mixed - */ - protected function execute(Input $input, Output $output): mixed - { - if (!$call = $this->callable) { - throw new BadMethodCallException('The callable property is empty'); - } - // call custom callable - return $call($this->flags, $output); - } } diff --git a/src/Handler/CommandWrapper.php b/src/Handler/CommandWrapper.php new file mode 100644 index 0000000..b23a607 --- /dev/null +++ b/src/Handler/CommandWrapper.php @@ -0,0 +1,178 @@ +withConfig($config)->setCallable($handleFn); + } + + /** + * @param callable(FlagsParser, Output):mixed $handleFn + * @param array $config + * + * @return static + */ + public static function wrap(callable $handleFn, array $config = []): self + { + return (new self())->withConfig($config)->setCallable($handleFn); + } + + /** + * @param callable $fn + * + * @return $this + */ + public function withCustom(callable $fn): self + { + $fn($this); + return $this; + } + + /** + * @param callable(FlagsParser, Output):mixed $fn + * + * @return $this + */ + public function withFunc(callable $fn): self + { + return $this->setCallable($fn); + } + + /** + * @param array $config + * + * @return $this + */ + public function withConfig(array $config): self + { + $this->config = $config; + return $this; + } + + /** + * @param array $options + * + * @return $this + */ + public function withOptions(array $options): self + { + $this->config['options'] = $options; + return $this; + } + + /** + * @param array $arguments + * + * @return $this + */ + public function withArguments(array $arguments): self + { + $this->config['arguments'] = $arguments; + return $this; + } + + /** + * @param callable(FlagsParser, Output):mixed $callable + * + * @return static + */ + public function setCallable(callable $callable): self + { + $this->callable = $callable; + return $this; + } + + /** + * @return array + */ + protected function getOptions(): array + { + return $this->config['options'] ?? []; + } + + /** + * @return array + */ + protected function getArguments(): array + { + return $this->config['arguments'] ?? []; + } + + /** + * @return string + */ + public function getRealDesc(): string + { + return $this->config['desc'] ?? ''; + } + + /** + * @return string + */ + public function getRealName(): string + { + return $this->config['name'] ?? ''; + } + + /** + * @return array + */ + public function getConfig(): array + { + return $this->config; + } + + /** + * Do execute command + * + * @param Input $input + * @param Output $output + * + * @return mixed + */ + protected function execute(Input $input, Output $output): mixed + { + if (!$call = $this->callable) { + throw new BadMethodCallException('The command handler property is empty'); + } + + // call custom callable + return $call($this->flags, $output); + } +} diff --git a/src/Util/ConsoleUtil.php b/src/Util/ConsoleUtil.php new file mode 100644 index 0000000..fc3dc9f --- /dev/null +++ b/src/Util/ConsoleUtil.php @@ -0,0 +1,29 @@ +assertSame('test1', $c::getName()); - $this->assertStringContainsString('desc', $c::getDesc()); + $this->assertSame('test1', $c->getRealName()); + $this->assertStringContainsString('description', $c::getDesc()); + $this->assertStringContainsString('description', $c->getRealDesc()); + } + + public function testCommand_alone_run(): void + { + $c = new TestCommand(new Input(), new Output()); + + $str = $c->run([]); + $this->assertEquals('Inhere\ConsoleTest\TestCommand::execute', $str); + } + + public function testCommand_alone_run_sub(): void + { + $c = new TestCommand(new Input(), new Output()); + + $str = $c->run(['sub1']); + $this->assertEquals('Inhere\ConsoleTest\{closure}', $str); } } diff --git a/test/ControllerTest.php b/test/ControllerTest.php index 5b09153..22a70de 100644 --- a/test/ControllerTest.php +++ b/test/ControllerTest.php @@ -23,6 +23,6 @@ public function testBasic(): void $c = new TestController(new Input(), new Output()); $this->assertSame('test', $c::getName()); - $this->assertStringContainsString('desc', $c::getDescription()); + $this->assertStringContainsString('desc', $c::getDesc()); } } diff --git a/test/TestCommand.php b/test/TestCommand.php index 67c8eb4..1264e75 100644 --- a/test/TestCommand.php +++ b/test/TestCommand.php @@ -10,6 +10,7 @@ namespace Inhere\ConsoleTest; use Inhere\Console\Command; +use Inhere\Console\Handler\CommandWrapper; use Inhere\Console\IO\Input; use Inhere\Console\IO\Output; @@ -24,6 +25,18 @@ class TestCommand extends Command protected static string $desc = 'command description message'; + protected function subCommands(): array + { + return [ + CommandWrapper::new(static function () { + return __METHOD__; + })->withConfig([ + 'name' => 'sub1', + 'desc' => 'desc for sub1 in test1', + ]), + ]; + } + /** * do execute command *