Skip to content

Commit

Permalink
feat: support parse flag rules by method docComment
Browse files Browse the repository at this point in the history
  • Loading branch information
inhere committed Sep 15, 2021
1 parent 9f6c6c3 commit 8d8b6af
Show file tree
Hide file tree
Showing 14 changed files with 373 additions and 99 deletions.
38 changes: 35 additions & 3 deletions src/AbstractHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

namespace Inhere\Console;

use Inhere\Console\Annotate\DocblockRules;
use Inhere\Console\Component\ErrorHandler;
use Inhere\Console\Concern\AttachApplicationTrait;
use Inhere\Console\Concern\CommandHelpTrait;
Expand All @@ -23,10 +24,12 @@
use Inhere\Console\Util\Helper;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionMethod;
use RuntimeException;
use Swoole\Coroutine;
use Swoole\Event;
use Throwable;
use Toolkit\PFlag\FlagsParser;
use Toolkit\PFlag\SFlags;
use Toolkit\Stdlib\Obj\ConfigObject;
use Toolkit\Stdlib\Util\PhpDoc;
Expand Down Expand Up @@ -459,6 +462,14 @@ public function isAlone(): bool
return $this instanceof CommandInterface;
}

/**
* @return bool
*/
public function isCommand(): bool
{
return $this instanceof CommandInterface;
}

/**
* @return ConfigObject
*/
Expand All @@ -482,6 +493,24 @@ public function initParams(array $params): ConfigObject
return $this->params;
}

/**
* @param string $method
* @param FlagsParser $fs
*
* @throws \ReflectionException
*/
public function loadRulesByDocblock(string $method, FlagsParser $fs): void
{
$rftMth = new ReflectionMethod($this, $method);

// parse doc for get flag rules
$dr = DocblockRules::newByDocblock($rftMth->getDocComment());
$dr->parse();

$fs->addArgsByRules($dr->getArgRules());
$fs->addOptsByRules($dr->getOptRules());
}

/**********************************************************
* display help information
**********************************************************/
Expand Down Expand Up @@ -576,11 +605,14 @@ protected function showHelpByAnnotations(string $method, string $action = '', ar
return 0;
}

$allowedTags = array_keys(self::$annotationTags);
$this->logf(Console::VERB_DEBUG, "render help for the command: %s", $this->input->getCommandId());

$help = [];
$doc = $ref->getMethod($method)->getDocComment();
$tags = PhpDoc::getTags($this->parseCommentsVars((string)$doc));
$tags = PhpDoc::getTags($this->parseCommentsVars((string)$doc), [
'allow' => $allowedTags,
]);

if ($aliases) {
$realName = $action ?: static::getName();
Expand All @@ -598,11 +630,11 @@ protected function showHelpByAnnotations(string $method, string $action = '', ar

// is an command object
$isCommand = $ref->isSubclassOf(CommandInterface::class);
foreach (array_keys(self::$annotationTags) as $tag) {
foreach ($allowedTags as $tag) {
if (empty($tags[$tag]) || !is_string($tags[$tag])) {
// for alone command
if ($tag === 'description' && $isCommand) {
$help['Description:'] = static::getDescription();
$help['Description:'] = static::getDesc();
continue;
}

Expand Down
33 changes: 33 additions & 0 deletions src/Annotate/AnnotateRules.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php declare(strict_types=1);

namespace Inhere\Console\Annotate;

/**
* class AnnotateRules
*/
class AnnotateRules
{
/**
* Allow display message tags in the command method docblock
*
* @var array
*/
protected static $allowedTags = [
// tag name => allow multi tags
'desc' => false,
'usage' => false,
'argument' => true,
'option' => true,
'example' => false,
'help' => false,
];

/**
* @return array
*/
public static function getAllowedTags(): array
{
return self::$allowedTags;
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php declare(strict_types=1);

namespace Inhere\Console\Attr;
namespace Inhere\Console\Annotate\Attr;

use Attribute;
use Toolkit\PFlag\Flag\Argument;
Expand Down
2 changes: 1 addition & 1 deletion src/Attr/CmdOption.php → src/Annotate/Attr/CmdOption.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php declare(strict_types=1);

namespace Inhere\Console\Attr;
namespace Inhere\Console\Annotate\Attr;

use Attribute;
use Toolkit\PFlag\Flag\Option;
Expand Down
2 changes: 1 addition & 1 deletion src/Attr/RuleArg.php → src/Annotate/Attr/RuleArg.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php declare(strict_types=1);

namespace Inhere\Console\Attr;
namespace Inhere\Console\Annotate\Attr;

use Attribute;
use Toolkit\PFlag\FlagsParser;
Expand Down
2 changes: 1 addition & 1 deletion src/Attr/RuleOpt.php → src/Annotate/Attr/RuleOpt.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php declare(strict_types=1);

namespace Inhere\Console\Attr;
namespace Inhere\Console\Annotate\Attr;

use Attribute;
use Toolkit\PFlag\FlagsParser;
Expand Down
228 changes: 228 additions & 0 deletions src/Annotate/DocblockRules.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
<?php declare(strict_types=1);

namespace Inhere\Console\Annotate;

use Toolkit\Stdlib\Str;
use Toolkit\Stdlib\Util\PhpDoc;
use function array_keys;
use function explode;
use function preg_match;
use function trim;

/**
* class DocblockRules
*/
class DocblockRules
{
/**
* Allow display message tags in the command method docblock
*
* @var array
*/
protected static $allowedTags = [
// tag name => multi line align
'desc' => false,
'usage' => false,
'arguments' => true,
'options' => true,
'example' => true,
'help' => true,
];

/**
* Parsed docblock tags
*
* @var array
* @see $allowedTags for keys
* @psalm-var array<string, mixed>
*/
private $docTags;

/**
* @var array
*/
private $argRules = [];

/**
* @var array
*/
private $optRules = [];

/**
* @param string $doc
*
* @return static
*/
public static function newByDocblock(string $doc): self
{
$dr = new self();
$dr->setDocTagsByDocblock($doc);

return $dr;
}

/**
* @param array $docTags
*
* @return static
*/
public static function new(array $docTags = []): self
{
return new self($docTags);
}

/**
* Class constructor.
*
* @param array $docTags
*/
public function __construct(array $docTags = [])
{
$this->docTags = $docTags;
}

/**
* @param string $doc
*/
public function setDocTagsByDocblock(string $doc): void
{
$docTags = PhpDoc::parseDocs($doc, [
'default' => 'desc',
'allow' => self::getAllowedTags(),
]);

$this->docTags = $docTags;
}

/**
* parse multi line text to flag rules
*
* @options
* -r, --remote The git remote name. default is `origin`
* --main bool;Use the config `mainRemote` name
*
* @arguments
* repoPath The remote git repo URL or repository group/name.
* If not input, will auto parse from current work directory
*
* @return $this
* @example
* {fullCmd} php-toolkit/cli-utils
* {fullCmd} https://github.com/php-toolkit/cli-utils
*
*/
public function parse(): self
{
if ($argsText = $this->docTags['arguments'] ?? '') {
$lines = explode("\n", $argsText);

$this->argRules = $this->parseMultiLines($lines);
}

if ($optsText = $this->docTags['options'] ?? '') {
$lines = explode("\n", $optsText);

$this->optRules = $this->parseMultiLines($lines);
}

return $this;
}

/**
* @param array $lines
*
* @return array
*/
protected function parseMultiLines(array $lines): array
{
$index = 0;
$rules = $kvRules = [];

// $keyWidth = 16;
foreach ($lines as $line) {
$trimmed = trim($line);
if (!$trimmed) {
continue;
}

$nodes = Str::explode($trimmed, ' ', 2);
if (!isset($nodes[1])) {
if ($index === 0) { // invalid first line
continue;
}

// multi desc message.
$rules[$index - 1][1] .= "\n" . $trimmed;
continue;
}

$name = $nodes[0];
if (!preg_match('/^[\w ,-]{0,48}$/', $name)) {
// multi desc message.
$rules[$index - 1][1] .= "\n" . $trimmed;
continue;
}

$rules[$index] = $nodes;
$index++;
}

if ($rules) {
foreach ($rules as [$name, $rule]) {
$kvRules[$name] = $rule;
}
}

return $kvRules;
}

/**
* @param string $tag
*
* @return string
*/
public function getTagValue(string $tag): string
{
return $this->docTags[$tag] ?? '';
}

/**
* @return array
*/
public static function getAllowedTags(): array
{
return array_keys(self::$allowedTags);
}

/**
* @return array
*/
public function getArgRules(): array
{
return $this->argRules;
}

/**
* @return array
*/
public function getOptRules(): array
{
return $this->optRules;
}

/**
* @return array
*/
public function getDocTags(): array
{
return $this->docTags;
}

/**
* @param array $docTags
*/
public function setDocTags(array $docTags): void
{
$this->docTags = $docTags;
}
}
Empty file removed src/Attr/.keep
Empty file.
3 changes: 2 additions & 1 deletion src/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ protected function showHelp(): bool
// }

// TODO show help by flags

// if ($this->flags->isNotEmpty()) {
// }

$execMethod = self::METHOD;

Expand Down
Loading

0 comments on commit 8d8b6af

Please sign in to comment.