<?php declare(strict_types=1);
namespace Inhere\Console;
use Closure;
use Inhere\Console\Contract\ControllerInterface;
use Inhere\Console\IO\Input;
use Inhere\Console\IO\Output;
use Inhere\Console\Util\Helper;
use InvalidArgumentException;
use ReflectionException;
use SplFileInfo;
use function class_exists;
use function implode;
use function is_object;
use function is_string;
use function method_exists;
use function sprintf;
use function strlen;
use function strpos;
use function substr;
class Application extends AbstractApplication
{
public function __construct(array $config = [], Input $input = null, Output $output = null)
{
Console::setApp($this);
parent::__construct($config, $input, $output);
}
public function controller(string $name, $class = null, $option = null)
{
if (is_string($option)) {
$option = [
'description' => $option,
];
}
$this->getRouter()->addGroup($name, $class, (array)$option);
return $this;
}
public function addGroup(string $name, $class = null, $option = null)
{
return $this->controller($name, $class, $option);
}
public function addController(string $name, $class = null, $option = null)
{
return $this->controller($name, $class, $option);
}
public function controllers(array $controllers): void
{
$this->addControllers($controllers);
}
public function setControllers(array $controllers): void
{
$this->addControllers($controllers);
}
public function addControllers(array $controllers): void
{
$this->getRouter()->addControllers($controllers);
}
public function command(string $name, $handler = null, $option = null)
{
if (is_string($option)) {
$option = [
'description' => $option,
];
}
$this->getRouter()->addCommand($name, $handler, (array)$option);
return $this;
}
public function addCommand(string $name, $handler = null, $option = null): self
{
return $this->command($name, $handler, $option);
}
public function addCommands(array $commands): void
{
$this->getRouter()->addCommands($commands);
}
public function commands(array $commands): void
{
$this->addCommands($commands);
}
public function registerCommands(string $namespace, string $basePath): self
{
$length = strlen($basePath) + 1;
$iterator = Helper::directoryIterator($basePath, $this->getFileFilter());
foreach ($iterator as $file) {
$class = $namespace . '\\' . substr($file->getPathName(), $length, -4);
$this->addCommand($class);
}
return $this;
}
public function registerGroups(string $namespace, string $basePath): self
{
$length = strlen($basePath) + 1;
$iterator = Helper::directoryIterator($basePath, $this->getFileFilter());
foreach ($iterator as $file) {
$class = $namespace . '\\' . substr($file->getPathName(), $length, -4);
$this->addController($class);
}
return $this;
}
protected function getFileFilter(): callable
{
return static function (SplFileInfo $f) {
$name = $f->getFilename();
if (strpos($name, '.') === 0) {
return false;
}
if ($f->isDir()) {
return true;
}
return $f->isFile() && substr($name, -4) === '.php';
};
}
public function dispatch(string $name, bool $detachedRun = false)
{
$this->logf(Console::VERB_DEBUG, 'begin dispatch command: %s', $name);
$info = $this->getRouter()->match($name);
if (!$info) {
if (true === $this->fire(self::ON_NOT_FOUND, $name, $this)) {
$this->logf(Console::VERB_DEBUG, 'not found handle by user, command: %s', $name);
return 0;
}
$this->output->error("The command '{$name}' is not exists!");
$commands = $this->getRouter()->getAllNames();
if ($similar = Helper::findSimilar($name, $commands)) {
$this->output->printf("\nMaybe what you mean is:\n <info>%s</info>", implode(', ', $similar));
} else {
$scriptName = $this->getScriptName();
$this->output->colored("\nPlease use '$scriptName --help' for see all available commands");
}
return 2;
}
$cmdOptions = $info['options'];
$this->input->setCommandId($info['cmdId']);
if ($info['type'] === Router::TYPE_SINGLE) {
return $this->runCommand($info['name'], $info['handler'], $cmdOptions);
}
return $this->runAction($info['group'], $info['action'], $info['handler'], $cmdOptions, $detachedRun);
}
protected function runCommand(string $name, $handler, array $options)
{
if (is_object($handler) && method_exists($handler, '__invoke')) {
if ($this->input->getSameOpt(['h', 'help'])) {
$desc = $options['description'] ?? 'No command description message';
return $this->output->write($desc);
}
$result = $handler($this->input, $this->output);
} else {
if (!class_exists($handler)) {
Helper::throwInvalidArgument("The console command class [$handler] not exists!");
}
$object = new $handler($this->input, $this->output);
if (!($object instanceof Command)) {
Helper::throwInvalidArgument("The console command class [$handler] must instanceof the " . Command::class);
}
$object::setName($name);
$object->setApp($this);
$result = $object->run();
}
return $result;
}
protected function runAction(string $group, string $action, $handler, array $options, bool $detachedRun = false)
{
if (is_string($handler)) {
$class = $handler;
if (!class_exists($class)) {
Helper::throwInvalidArgument('The console controller class [%s] not exists!', $class);
}
$handler = new $class($this->input, $this->output);
}
if (!($handler instanceof Controller)) {
Helper::throwInvalidArgument(
'The console controller class [%s] must instanceof the %s',
$handler,
Controller::class
);
}
$handler::setName($group);
if ($desc = $options['description'] ?? '') {
$handler::setDescription($desc);
}
$handler->setApp($this);
$handler->setDelimiter($this->delimiter);
if ($detachedRun) {
$handler->setDetached();
}
return $handler->run($action);
}
}