diff --git a/composer.json b/composer.json index 7543e2a..909b1cb 100755 --- a/composer.json +++ b/composer.json @@ -7,6 +7,7 @@ "keywords": ["module", "xp"], "require" : { "xp-framework/core": "^12.0 | ^11.0 | ^10.15", + "xp-framework/reflection": "^3.0 | ^2.9", "php" : ">=7.0.0" }, "require-dev" : { diff --git a/src/main/php/util/cmd/Commands.class.php b/src/main/php/util/cmd/Commands.class.php index 5728f5e..d3834f4 100755 --- a/src/main/php/util/cmd/Commands.class.php +++ b/src/main/php/util/cmd/Commands.class.php @@ -1,7 +1,7 @@ writeLine('*** ', $this->verbose ? $e : $e->getMessage()); return 1; @@ -107,71 +108,67 @@ protected function runCommand($command, $params, $config) { // Usage if ($params->exists('help', '?')) { - $this->commandUsage($class); + $this->commandUsage($type); return 0; } - if ($class->hasMethod('newInstance')) { - $instance= $class->getMethod('newInstance')->invoke(null, [$config]); - } else if ($class->hasConstructor()) { - $instance= $class->newInstance($config); + if ($method= $type->method('newInstance')) { + $instance= $method->invoke(null, [$config]); } else { - $instance= $class->newInstance(); + $instance= $type->newInstance($config); } + $instance->in= self::$in; $instance->out= self::$out; $instance->err= self::$err; // Arguments - foreach ($class->getMethods() as $method) { - if ($method->hasAnnotation('args')) { // Pass all arguments - if (!$method->hasAnnotation('args', 'select')) { - $begin= 0; - $end= $params->count; - $pass= array_slice($params->list, 0, $end); - } else { + foreach ($type->methods() as $method) { + if ($args= $method->annotation(Args::class)) { + if ($select= $args->argument('select')) { $pass= []; - foreach (preg_split('/, ?/', $method->getAnnotation('args', 'select')) as $def) { - if (is_numeric($def) || '-' == $def[0]) { + foreach (preg_split('/, ?/', $select) as $def) { + if (is_numeric($def) || '-' === $def[0]) { $pass[]= $params->value((int)$def); } else { sscanf($def, '[%d..%d]', $begin, $end); - isset($begin) || $begin= 0; - isset($end) || $end= $params->count- 1; + $begin ?? $begin= 0; + $end ?? $end= $params->count - 1; while ($begin <= $end) { $pass[]= $params->value($begin++); } } } + } else { + $begin= 0; + $end= $params->count; + $pass= array_slice($params->list, 0, $end); } + try { $method->invoke($instance, [$pass]); - } catch (Throwable $e) { - self::$err->writeLine('*** Error for arguments '.$begin.'..'.$end.': ', $this->verbose ? $e : $e->getMessage()); + } catch (InvocationFailed $e) { + self::$err->writeLine("*** Error for arguments {$begin}..{$end}: ", $this->verbose ? $e : $e->getMessage()); return 2; } - } else if ($method->hasAnnotation('arg')) { // Pass arguments - $arg= $method->getAnnotation('arg'); - if (isset($arg['position'])) { - $name= '#'.($arg['position']+ 1); - $select= intval($arg['position']); + } else if ($arg= $method->annotation(Arg::class)) { + if (null !== ($position= $arg->argument('position'))) { + $select= (int)$position; + $name= '#'.($position + 1); $short= null; - } else if (isset($arg['name'])) { - $name= $select= $arg['name']; - $short= $arg['short'] ?? null; } else { - $name= $select= strtolower(preg_replace('/^set/', '', $method->getName())); - $short= $arg['short'] ?? null; + $select= $name= $arg->argument('name') ?? strtolower(preg_replace('/^set/', '', $method->name())); + $short= $arg->argument('short'); } - if (0 == $method->numParameters()) { + $first= $method->parameter(0); + if (null === $first) { if (!$params->exists($select, $short)) continue; $args= []; } else if (!$params->exists($select, $short)) { - list($first, )= $method->getParameters(); - if (!$first->isOptional()) { - self::$err->writeLine('*** Argument '.$name.' does not exist!'); + if (!$first->optional()) { + self::$err->writeLine("*** Argument {$name} does not exist!"); return 2; } @@ -182,8 +179,8 @@ protected function runCommand($command, $params, $config) { try { $method->invoke($instance, $args); - } catch (TargetInvocationException $e) { - self::$err->writeLine('*** Error for argument '.$name.': ', $this->verbose ? $e->getCause() : $e->getCause()->compoundMessage()); + } catch (InvocationFailed $e) { + self::$err->writeLine("*** Error for argument {$name}: ", $this->verbose ? $e->getCause() : $e->getCause()->compoundMessage()); return 2; } } diff --git a/src/main/php/xp/command/CmdRunner.class.php b/src/main/php/xp/command/CmdRunner.class.php index 907787f..fddf924 100755 --- a/src/main/php/xp/command/CmdRunner.class.php +++ b/src/main/php/xp/command/CmdRunner.class.php @@ -1,6 +1,7 @@ getComment(); + protected function commandUsage(Type $type) { + $comment= $type->comment(); if ('' === (string)$comment) { - $markdown= '# '.$class->getSimpleName()."\n\n"; + $markdown= '# '.$type->name()."\n\n"; $text= ''; } else { @list($headline, $text)= explode("\n", $comment, 2); $markdown= '# '.ltrim($headline, ' #')."\n\n"; } - $markdown.= "- Usage\n ```sh\n$ xp cmd ".Commands::nameOf($class); + $markdown.= "- Usage\n ```sh\n$ xp cmd ".Commands::nameOf($type->class()); $extra= $details= $positional= []; - foreach ($class->getMethods() as $method) { - if (!$method->hasAnnotation('arg')) continue; - - $arg= $method->getAnnotation('arg'); - $name= strtolower(preg_replace('/^set/', '', $method->getName())); - $optional= 0 === $method->numParameters() || $method->getParameters()[0]->isOptional(); - $comment= $method->getComment(); + foreach ($type->methods()->annotated(Arg::class) as $method) { + $arg= $method->annotation(Arg::class)->arguments(); + $name= strtolower(preg_replace('/^set/', '', $method->name())); + $first= $method->parameter(0); + $optional= $first ? $first->optional() : true; + $comment= $method->comment(); if (isset($arg['position'])) { $details[$name]= [$comment, null]; @@ -100,7 +100,7 @@ protected function commandUsage(XPClass $class) { ); } - Help::render(self::$err, substr($markdown, 0, -1).$text, $class->getClassLoader()); + Help::render(self::$err, substr($markdown, 0, -1).$text, $type->classLoader()); } /** diff --git a/src/main/php/xp/command/Runner.class.php b/src/main/php/xp/command/Runner.class.php index 39789a4..35f0257 100755 --- a/src/main/php/xp/command/Runner.class.php +++ b/src/main/php/xp/command/Runner.class.php @@ -1,7 +1,8 @@ getComment())) { + if (null !== ($comment= $type->comment())) { self::$err->writeLine(self::textOf($comment)); self::$err->writeLine(str_repeat('=', 72)); } $extra= $details= $positional= []; - foreach ($class->getMethods() as $method) { - if (!$method->hasAnnotation('arg')) continue; - - $arg= $method->getAnnotation('arg'); - $name= strtolower(preg_replace('/^set/', '', $method->getName()));; - $comment= self::textOf($method->getComment()); - - if (0 == $method->numParameters()) { - $optional= true; - } else { - list($first, )= $method->getParameters(); - $optional= $first->isOptional(); - } + foreach ($type->methods()->annotated(Arg::class) as $method) { + $arg= $method->annotation('arg')->arguments(); + $name= strtolower(preg_replace('/^set/', '', $method->name()));; + $first= $method->parameter(0); + $optional= $first ? $first->optional() : true; + $comment= self::textOf($method->comment()); if (isset($arg['position'])) { $details['#'.($arg['position'] + 1)]= $comment; @@ -102,7 +96,7 @@ protected function commandUsage(XPClass $class) { // Usage asort($positional); - self::$err->write('Usage: $ xpcli ', Commands::nameOf($class), ' '); + self::$err->write('Usage: $ xpcli ', Commands::nameOf($type->class()), ' '); foreach ($positional as $name) { self::$err->write('<', $name, '> '); } @@ -124,7 +118,7 @@ protected function commandUsage(XPClass $class) { * @return void */ protected function selfUsage() { - self::$err->writeLine($this->textOf((new XPClass(__CLASS__))->getComment())); + self::$err->writeLine($this->textOf(Reflection::type(self::class)->comment())); } /** diff --git a/src/test/php/util/cmd/unittest/AbstractRunnerTest.class.php b/src/test/php/util/cmd/unittest/AbstractRunnerTest.class.php index 90dc89a..38a1ced 100755 --- a/src/test/php/util/cmd/unittest/AbstractRunnerTest.class.php +++ b/src/test/php/util/cmd/unittest/AbstractRunnerTest.class.php @@ -58,7 +58,7 @@ protected function assertAllArgs($args, Command $command) { * @throws unittest.AssertionFailedError */ protected function assertOnStream(MemoryOutputStream $m, $bytes, $message= 'Not contained') { - strstr($m->bytes(), $bytes) || $this->fail($message, $m->bytes(), $bytes); + strstr($m->bytes(), $bytes) || Assert::false("{$message}: '{$bytes}' in '{$m->bytes()}'"); } /** @@ -154,7 +154,7 @@ public function positionalArgument() { $command= newinstance(Command::class, [], '{ private $arg= null; - #[Arg(["position" => 0])] + #[Arg(position: 0)] public function setArg($arg) { $this->arg= $arg; } public function run() { $this->out->write($this->arg); } }'); @@ -170,7 +170,7 @@ public function missingPositionalArgumentt() { $command= newinstance(Command::class, [], '{ private $arg= null; - #[Arg(["position" => 0])] + #[Arg(position: 0)] public function setArg($arg) { $this->arg= $arg; } public function run() { throw new \unittest\AssertionFailedError("Should not be executed"); } }'); @@ -218,7 +218,7 @@ public function shortRenamedArgument() { $command= newinstance(Command::class, [], '{ private $arg= null; - #[Arg(["name" => "pass"])] + #[Arg(name: "pass")] public function setArg($arg) { $this->arg= $arg; } public function run() { $this->out->write($this->arg); } }'); @@ -234,7 +234,7 @@ public function longRenamedArgument() { $command= newinstance(Command::class, [], '{ private $arg= null; - #[Arg(["name" => "pass"])] + #[Arg(name: "pass")] public function setArg($arg) { $this->arg= $arg; } public function run() { $this->out->write($this->arg); } }'); @@ -347,7 +347,7 @@ public function run() { $this->out->write($this->verbose ? "true" : "false"); } public function positionalArgumentException() { $command= newinstance(Command::class, [], '{ - #[Arg(["position" => 0])] + #[Arg(position: 0)] public function setHost($host) { throw new \lang\IllegalArgumentException("Connecting to ".$host." disallowed by policy"); } @@ -385,7 +385,7 @@ public function allArgs() { private $verbose= false; private $args= []; - #[Args(["select" => "[0..]"])] + #[Args(select: "[0..]")] public function setArgs($args) { $this->args= $args; } public function run() { $this->out->write(implode(", ", $this->args)); } }')); @@ -397,7 +397,7 @@ public function allArgsCompactNotation() { private $verbose= false; private $args= []; - #[Args(["select" => "*"])] + #[Args(select: "*")] public function setArgs($args) { $this->args= $args; } public function run() { $this->out->write(implode(", ", $this->args)); } }')); @@ -409,7 +409,7 @@ public function boundedArgs() { private $verbose= false; private $args= []; - #[Args(["select" => "[0..2]"])] + #[Args(select: "[0..2]")] public function setArgs($args) { $this->args= $args; } public function run() { $this->out->write(implode(", ", $this->args)); } }')); @@ -421,7 +421,7 @@ public function boundedArgsFromOffset() { private $verbose= false; private $args= []; - #[Args(["select" => "[2..4]"])] + #[Args(select: "[2..4]")] public function setArgs($args) { $this->args= $args; } public function run() { $this->out->write(implode(", ", $this->args)); } }')); @@ -433,7 +433,7 @@ public function positionalAndBoundedArgsFromOffset() { private $verbose= false; private $args= []; - #[Args(["select" => "0, [2..4]"])] + #[Args(select: "0, [2..4]")] public function setArgs($args) { $this->args= $args; } public function run() { $this->out->write(implode(", ", $this->args)); } }')); @@ -445,7 +445,7 @@ public function boundedAndPositionalArgsWithOverlap() { private $verbose= false; private $args= []; - #[Args(["select" => "[0..2], 1"])] + #[Args(select: "[0..2], 1")] public function setArgs($args) { $this->args= $args; } public function run() { $this->out->write(implode(", ", $this->args)); } }')); @@ -457,7 +457,7 @@ public function positionalArgs() { private $verbose= false; private $args= []; - #[Args(["select" => "0, 2, 4, 5"])] + #[Args(select: "0, 2, 4, 5")] public function setArgs($args) { $this->args= $args; } public function run() { $this->out->write(implode(", ", $this->args)); } }')); diff --git a/src/test/php/util/cmd/unittest/RunnerTest.class.php b/src/test/php/util/cmd/unittest/RunnerTest.class.php index c3e45a8..62c7e86 100755 --- a/src/test/php/util/cmd/unittest/RunnerTest.class.php +++ b/src/test/php/util/cmd/unittest/RunnerTest.class.php @@ -43,7 +43,7 @@ public function classPathOption() { $command= newinstance(Command::class, [], '{ private $copy= NULL; - #[Arg(["short" => "cp"])] + #[Arg(short: "cp")] public function setCopy($copy) { $this->copy= \lang\reflect\Package::forName("net.xp_forge.instructions")->loadClass($copy); }