consolidation / annotated-command Goto Github PK
View Code? Open in Web Editor NEWCreate Symfony Console commands from annotated command class methods.
License: Other
Create Symfony Console commands from annotated command class methods.
License: Other
Console now supports hidden options. Here is their support later being added as an Attribute symfony/symfony#41942
It seems that phpspec/prophecy has resolved their dependency issues:
Can AnnotatedCommand now be updated so it targets the 3.x branch of phpdocumentor/reflection-docblock?
This would resolve compatibility issues with Symfony 3.1.
This is technically a support request, so maybe this is not the right place to file this. I didn't see in the contributing guidelines anything about support requests, or I missed it.
I am using the replace-command
hook to override the recipes:multisite:init
command provided as part of BLT. I'm also using the post-command
hook to run some additional operations. Within the replace-command
method, how do I set option values so that they will be available in the post-command
hook? In the post-command
hook, there is a CommandData object that I can lookup options in, but there doesn't seem to be a corresponding way inside the command itself to set options in a way that they later show up in the CommandData object.
I'm probably missing something obvious. Any help is appreciated.
Thanks,
Sean
PHP8 is released! Ideally PHP8-only sites can author using attributes and these commands are recognized in addition to commands that use annotations. We're going to be in this dual world for a long time.
For our use case, we could use reflection to find the name of the file that the method is in, and skip it if it contains "Trait".
https://stackoverflow.com/questions/30661997/php-reflection-how-to-know-if-a-method-property-constant-is-inherited-from-trai/45912866 h/t @greg-1-anderson
This will save parse time in Drush at least as every commandfile inherits traits from DrushCommands base class.
Writing a command that simply adds data to another command, and with the PropertyList output, it doesn't seem to have a clear way to add new field data.
Inside the @hook init
, I would expect to be able to alter $annotationData
with a setter. e.g.
/**
* Registers additional information to domain:info.
*
* @hook init domain:info
*/
public function initDomainInfo(InputInterface $input, AnnotationData $annotationData) {
$annotationData->set('field-labels', ['new' => 'New field']);
}
However, in my use case, it's not really set()
it's append()
, so I'm not certain what the best approach would be.
Here's the method I have to use now, leveraging exchangeArray
to swap out data.
/**
* Registers additional information to domain:info.
*
* @hook init domain:info
*/
public function initDomainInfo(InputInterface $input, AnnotationData $annotationData) {
// To add a field label, we have to swap out the annotation array.
$iterator = $annotationData->getIterator();
$new = [];
// Simple while loop
while ($iterator->valid()) {
$new[$iterator->key()] = $iterator->current();
if ($iterator->key() == 'field-labels') {
$new[$iterator->key()] .= "\n" . 'domain_access: Domain Access';
}
$iterator->next();
}
// $annotationData is an \ArrayObject, so we can exchange.
$annotationData->exchangeArray($new);
}
n/a
Both should work
OSX 10.11
I think we are going to want to formally declare supported tags in Drush - for easier documentation of supported tags. Would be good to have support for that in AnnotatedCommand or reflection-docblock page. FWIW, I found a library in my searching - http://php-annotations.readthedocs.io/en/latest/UsingAnnotations.html#what-does-this-library-do
FYI, I also found https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc.md
The update should work without trouble on any PHP version ^5.4.0
The requirement for the dependency phpdocumentor/reflection-docblock
^4.0 is PHP ^7.0; Composer complains and fails accordingly
Mac OS 10.12.6, PHP 5.5.38
I'm looking at porting cache-* commands. How do you suggest we implement the feature of cache-clear where other commandfiles can alter in their own cache types. For example, views alters in the views cache as a selection during cache-clear. Would be nice to do away with the old Drush hook system for this.
This arguably is not a bug with annotated-command. I'm just reporting and linking to symfony/console#30 in case this info is useful to others.
@option
annotation and another option from a @hook option implementation
.composer update
.composer update
.This problem is also present when I test with symfony/console 3.2.
Here's the PR for symfony/console 2.8.18.
dsnopek writes:
In the Symfony Console PR it appears the inputBound property is used to prevent $input->bind() from getting called multiple times.
However... this actually seems a little off. I don't know the Symfony Console code very well, but it seems to me that this would fail to bind and probably hide validation errors if the arguments/options failed to validate for some reason. This is because the first time $input->bind() is called, it just swallows any exceptions, but sets the inputBound property regardless. The 2nd time $input->bind() is called it does show validation errors, but it won't actually try to bind a 2nd time because inputBound is set. At least that's my interpretation just from looking at the code.
I'm guessing that 'annotated-command' is updating the commands definition in the event, and was expecting Symfony Console to do the 2nd $input->bind() with the new definition. But since the inputBound property is set, it won't run it the 2nd time.
Firstly, I think this is a bug in Symfony Console. If $input->setInputBound(true) only happens if the first bind is successful, then things work as expected. See this PR:
symfony/console#30
Or, possibly, I think the 'annotated-command' project could bind the $input to the new definition itself rather than depending on Symfony Console to do it later? I'm not sure which project will be easier to convince that there is something to fix.
We ran into an issue with the replace-command hook. As an example, our command class looks like this:
<?php
namespace Drupal\example\Commands;
use Drush\Commands\DrushCommands;
/**
* A Drush commandfile to overwrite some of the drush commands.
*/
class ExampleCommands extends DrushCommands {
/**
* @hook replace-command default-content-access:import-module
*/
public function contentImportModule($module, $options = ['update-existing' => FALSE]): void {
print 'Disabled on this site!';
}
/**
* @hook replace-command locale:import
*/
public function import($langcode, $file, $options = ['type' => self::OPT, 'override' => self::OPT]) {
print 'Disabled on this site!';
}
}
This worked just fine for several years now but started to fail yesterday. TBH, I don't know which component has changed to make this fail but the error message is
In InputDefinition.php line 232:
[Symfony\Component\Console\Exception\LogicException]
An option named "type" already exists.
The stack trace is
at /var/www/html/vendor/symfony/console/Input/InputDefinition.php:232
Symfony\Component\Console\Input\InputDefinition->addOption() at /var/www/html/vendor/consolidation/annotated-command/src/AnnotatedCommand.php:206
Consolidation\AnnotatedCommand\AnnotatedCommand->addOptions() at /var/www/html/vendor/consolidation/annotated-command/src/AnnotatedCommand.php:263
Consolidation\AnnotatedCommand\AnnotatedCommand->optionsHookForHookAnnotations() at /var/www/html/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/OptionsHookDispatcher.php:31
Consolidation\AnnotatedCommand\Hooks\Dispatchers\OptionsHookDispatcher->getOptions() at /var/www/html/vendor/consolidation/annotated-command/src/CommandProcessor.php:154
Consolidation\AnnotatedCommand\CommandProcessor->optionsHook() at /var/www/html/vendor/consolidation/annotated-command/src/AnnotatedCommand.php:255
Consolidation\AnnotatedCommand\AnnotatedCommand->optionsHook() at /var/www/html/vendor/consolidation/annotated-command/src/Options/AlterOptionsCommandEvent.php:81
Consolidation\AnnotatedCommand\Options\AlterOptionsCommandEvent->findAndAddHookOptions() at /var/www/html/vendor/consolidation/annotated-command/src/Options/AlterOptionsCommandEvent.php:72
Consolidation\AnnotatedCommand\Options\AlterOptionsCommandEvent->alterCommandOptions() at /var/www/html/vendor/symfony/event-dispatcher/EventDispatcher.php:214
Symfony\Component\EventDispatcher\EventDispatcher->doDispatch() at /var/www/html/vendor/symfony/event-dispatcher/EventDispatcher.php:44
Symfony\Component\EventDispatcher\EventDispatcher->dispatch() at /var/www/html/vendor/consolidation/annotated-command/src/Hooks/Dispatchers/CommandEventHookDispatcher.php:30
Consolidation\AnnotatedCommand\Hooks\Dispatchers\CommandEventHookDispatcher->callCommandEventHooks() at /var/www/html/vendor/consolidation/annotated-command/src/Hooks/HookManager.php:424
Consolidation\AnnotatedCommand\Hooks\HookManager->callCommandEventHooks() at /var/www/html/vendor/symfony/event-dispatcher/EventDispatcher.php:214
Symfony\Component\EventDispatcher\EventDispatcher->doDispatch() at /var/www/html/vendor/symfony/event-dispatcher/EventDispatcher.php:44
Symfony\Component\EventDispatcher\EventDispatcher->dispatch() at /var/www/html/vendor/symfony/console/Application.php:1007
Symfony\Component\Console\Application->doRunCommand() at /var/www/html/vendor/symfony/console/Application.php:255
Symfony\Component\Console\Application->doRun() at /var/www/html/vendor/symfony/console/Application.php:148
Symfony\Component\Console\Application->run() at /var/www/html/vendor/drush/drush/src/Runtime/Runtime.php:118
Drush\Runtime\Runtime->doRun() at /var/www/html/vendor/drush/drush/src/Runtime/Runtime.php:49
Drush\Runtime\Runtime->run() at /var/www/html/vendor/drush/drush/drush.php:72
require() at /var/www/html/vendor/drush/drush/includes/preflight.inc:18
drush_main() at phar:///usr/local/bin/drush/bin/drush.php:141
require() at /usr/local/bin/drush:10
When removing the options parameter from our overwrite function, then it works just fine. But that contradicts the documentation: Parameters must match original command method.
I set up a composer package to provide a Drush command in this way, as explained in the Drush docs:
Nested (e.g. Commandfile is part of a Composer package)
Filename: $PROJECT_ROOT/drush/Commands/dev_modules/ExampleCommands.php
Namespace: Drush\Commands\dev_modules
The command file is picked up.
The command file is not picked up.
The array of file data returned by discovery has this:
"/Users/joachim/Sites/_sandbox/drupal-code-builder-drush-9x/CodeBuilderDevCommands.php" => "\Drush\CodeBuilderDevCommands"
The class name that is in the array value is incorrect -- it should be: Drush\Commands\code_builder_commands\CodeBuilderDevCommands
The reason for that is that discoverCommandFilesInLocation() makes assumptions about the namespace based on the filepath:
$relativePathName = $file->getRelativePathname();
$relativeNamespaceAndClassname = str_replace(
['/', '.php'],
['\\', ''],
$relativePathName
);
Having separate short and long form strings for command information would be useful.
The short, existing string should be displayed when commands are listed. The long form string should be displayed when a user asks for help with the command.
The annotation would look like this:
* @description Short description of the command.
* @help This text is output when the user types `command --help` and is intended to provided more detailed information.
Is there a way we can support phpdoc inheritance: https://www.phpdoc.org/docs/latest/guides/inheritance.html
All of @inheritdoc
, @inheritDoc
, {@inheritdoc}
and {@inheritDoc}
would need to be supported.
The variants with the {}
are used to import the docblock of the parent in the current docblock.
We're looking to create a package with a RoboFileBase for our CI tool. Each project can have a RoboFile that extends that class and can overwrite the commands. It would be nice if we could use the docblock inheritance to provide the documentation on the command line.
Commandfiles may provide hooks in addition to commands. A commandfile method that contains a @hook annotation is registered as a hook instead of a command.
I'm going by this example in Drush:
/**
* When a hook extends a command with additional options, it must
* implement declare those option(s) in a @hook option like this one. Doing so will add
* the option to the help text for the modified command, and will also
* allow the new option to be specified on the command line. Without
* this, Drush will fail with an error when a user attempts to use
* an unknown option.
*
* @hook option sql-sync
* @option http-sync Copy the database via http instead of rsync. Value is the url that the existing database dump can be found at.
* @option http-sync-user Username for the protected directory containing the sql dump.
* @option http-sync-password Password for the same directory.
*/
public function optionsetSqlSync() {}
There doesn't seem to be a way for a default value for the extra option to be defined.
Having it set in the $options parameter for the command being enhanced causes a crash.
I think it would make sense to have an $options parameter in the hook function declaration.
/**
* @hook command bar
*/
public function foo()
{
$this->writeln('foo');
}
/**
* @hook command baz
*/
public function bar()
{
$this->writeln('bar');
}
public function baz()
{
$this->writeln('baz');
}
robo baz
should output
foo
bar
baz
And go even further so that Robo could build a big tree of tasks.
In general this task-hook-system could become a bit more detailled.
For example: I have several things to do before the build but they need to be in some semi-specific order (like @hook pre-command build 0
for priority).
But this issue is about recursing all the way to each hook
or finding an alternative way/word for doing so
Tell us what happens instead
baz
bar
Dynamically alter the way output is formatted in a post-process hook, e.g. to dynamically select the default fields based on the command output.
The FormatterOptions should be available via a method of CommandData.
FormatterOptions is created in a protected method CommandProcessor::writeUsingFormatter() immediately before it is used.
This was briefly discussed on Twitter:
https://twitter.com/sebreghtsjelle/status/835124401616076801
/**
* Documentation for mynamespace:my-command
*
* @param array $arguments
* Variable amount of arguments. Last 2 arguments are SSH username and password.
* All remaining (first x) arguments are servers to execute a command on.
*/
public function mynamespaceMyCommand(array $arguments) {
}
How would we document individual arguments for this command?
Might be related to #69.
Is there a defined order for hooks of the same type, eg interact?
In particular, within a single commands class, is it:
Currently, the CommandFileDiscovery class searches for command files named *Commands.php
in directories named CliTools
(or src/CliTools
).
In Drush, I tried an alternative search pattern, and currently look for command files named "*CommandFile.php" in directories named CommandFiles
(or src/CommandFiles
).
Ideally, we should decide on a single standard default that both Drush and DrupalConsole can use, so that command files are found consistently. At the moment, I am leaning towards the second option, and calling these literally CommandFiles, as Drush is currently doing. Other conventions are possible, of course; anyone with opinions on the subject should weigh in.
I think that it would be nice if there were a possibility to have an array type.
Would be handy if the list if allowed values could be given in an annotation for options and arguments. See \Drush\CommandFiles\core\ViewsCommands::vlist (status option), for example.
Probably, it could be easily argued that it's confusing because I am new to this library and at the first place didn't know what i was doing back then. However I think that this thing is worth to mention that the way you define the mode and default values of options for the command is a bit confusing.
As an example, if you want to have a default value to be equal 2, there are two ways (as far as I know) of doing so. The first is through the annotation which works fine and the second one is through the default values of arguments of a function which could behave as you may not expect.
Ex.
I started to use this lib with robo framework, where I wanted to run my tests in two threads as a default arg.
public function parallelRunTest(array $opts = ['threads|t' => 2, 'suite|s' => 'api'])
Here I wanted the threads
to be equal 2 as default value but it turns out that 2 (phpInputOption::VALUE_REQUIRED
) is equal a constant that specifies that this field is required, the other option will work though. So the only way is to do it though the annotation like this:
/**
* @option int $threads Number of threads to use for running the tests
* @default int $threads 2
*/
public function parallelRunTest(array $opts = ['threads|t' => null, 'suite|s' => 'api'])
I created a symlink to a command folder into my drush command location folder.
The command should be picked up.
It's not, because Symfony finder doens't follow symlinks by default.
OS X 10.11.6.
The README section on hooks doesn't say that the target can be *. This is used in Drush, for instance:
/**
* Print druplicon as post-command output.
*
* @hook post-command *
*/
public function druplicon($result, CommandData $commandData)
Forgive me for not using the issue template, but I think this is more a question/idea than a specific bug.
I am wondering if there is some ability to cancel a command via a hook? I am using a library that is using robo, and I am able to hook into a command, and I thought I could use something like the validate hook to check for some condition, and ultimately see if I want to allow my command to run based on certain criteria. If the criteria is met, I would love to be able to cancel the command execution, print a helpful message, but still return a successful exit code.
This does not seem possible with validation, as you can only return a CommandError or throw an exception, both which end up in the whole process being killed.
Thoughts? Thanks in advance for any help or ideas....
I'm programmatically creating a command that will override an annotated command (@command foo
) already added in application. But the annotated command has also a validate hook (@hook validate foo
). With the new command the hook makes no sense. Is there a way to disable it?
There's a way to create a single-command tool in the Symfony Console with such line:
$app->setDefaultCommand('my-command', TRUE);
to use the tool like this:
./my-command arguments
Unfortunately, this case is skipped in the Annotated Command.
The command should be executed normally.
The first argument, inputted in the command line, is skipped when my-command method is called.
Linux Mint 18.3 64-bit, PHP 7.2.2-3+ubuntu16.04.1+deb.sury.org+1 ( NTS )
@hook type commandname|annotation
The commandname may be the command's primary name (e.g. my:command), it's method name (e.g. myCommand) or any of its aliases.
If an annotation is given instead, then this hook function will run for all commands with the specified annotation.
It would be helpful here to explicitly say that 'annotation' is of the form '@foo' rather than just 'foo'.
drush help
is a common mistake when user meant to type drush list
. See https://drupal.stackexchange.com/questions/249793/how-to-list-all-the-commands-in-drush-9-aka-drush-help
Run drush list
. If not possible, give better error message.
Error:
[Symfony\Component\Console\Exception\CommandNotFoundException]
Command was not found. Pass --root or a @siteAlias in order to run Drupal-specific commands.
I think things go awry at https://github.com/consolidation/annotated-command/blob/master/src/Options/AlterOptionsCommandEvent.php#L65. $commandToDescribe
is null.
I would like to be able to calls other commands from my annotated command using an annotation. E.g.,
<?php
/**
* @command tests:all
*
* @calls tests:behat
* @calls tests:phpunit
*/
public function testsAll() {
}
/**
* @command tests:behat
* @validateme
*/
public function testsBehat() {
...
}
In this example, using @calls tests:behat
would cause the tests:behat
command to be executed prior the the body of testsAll()
.
This is better than simply calling $this->testsBehat()
from $this->testsAll()
because it will invoke the hooks for $this->testsBehat()
.
The README mentions the following in the Validate hook section:
It is possible to alter the values of the arguments and options if necessary, although this is better done in the configure hook.
But there is no mention of a "configure hook" anywhere in the documentation. Which hook is intended to alter the options before executing the command?
Read the documentation and clicked on the link to HookReplaceCommandInterface.php
The file should have opened for viewing and reference
404
The Acquia BLT docs for replacing a command point to the section of the README with a short example and a link to the interface to reference. This file apparently does not exist anymore. This makes it difficult to know what to do in order to replace a command or if that is even still an option.
The name of a @param needs to start with a $. Consider relaxing that. Tripped me up for a while. If not possible, throw an exception when param name is empty to alert the command author.
Implement an options hook:
class StepCommandOptions {
/**
* @hook option @stepoption
* @option global-list Global list blah.
*/
public function stepOptions();
In the command desiring the options from the hook add this annotation:
/**
* @stepoption
*
* @command test:step-command
*
* @option bool $list List the steps for this command.
*/
public function stepCommand($options = [
'list' => FALSE,
]) {
In the application file use code like that in Terminus.php to make the hook code available to the application:
/**
* Add the commands and hooks which are shipped with core Wps
*
* @param $container
*/
private function addBuiltInCommandsAndHooks($container) {
// Add the built in commands.
$commands = $this->getCommands([
'path' => __DIR__ . '/Commands',
'namespace' => 'Wps\Console\Commands',
]);
// Add the built in hooks
$hooks = [
'Wps\Console\Hooks\StepCommandOptions',
];
$this->commands = array_merge($commands, $hooks);
}
I should see 'global-list' in the output of wps test:step-command -h
$ php bin/wps.php test:step-command -h
Usage:
test:step-command [options]
Options:
--list List the steps for this command.
-h, --help Display this help message
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi Force ANSI output
--no-ansi Disable ANSI output
-n, --no-interaction Do not ask any interactive question
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
Darwin Kernel Version 15.6.0
I see in the docs I can dynamically add an option in the option hook with:
$command->addOption()
Is there a way to add an argument? Symfony command has an addArgument() method, but I don't see how to get hold of that from somewhere like an interact hook, which is where I'd want to add it based on input.
Is there a way to specify a shorter version of an option?
For example, the way that 'git clean' has both --dry-run and -n.
The README.md gives the following example implementation of the @Usage annotation:
* @usage bet alpha --flip
* Concatenate "alpha" and "bet".
When executing a command with this annotation, the following is output:
my:cat bet alpha --flip
The description Concatenate "alpha" and "bet".
does not appear.
From pantheon-systems/terminus#1503
Added to Jira in BUGS-1138
The following two command declarations should behave identically:
/**
* @command foo:bar
* @field-labels
* foo: Foo Label
* bar: Bar Label
* @return PropertyList
*/
public function fooBar() {
// Returns the following property list in a formatted table.
return new PropertyList(['foo' => 'foo val', 'bar' => 'bar val']);
}
/**
* @command foo:bar
* @field-labels
* foo: Foo Label
* bar: Bar Label
* @return PropertyList Comment for myself in code.
*/
public function fooBar() {
// Returns an unformatted list of the values below.
return new PropertyList(['foo' => 'foo val', 'bar' => 'bar val']);
}
When authoring a plugin for terminus 1.x, if my @return
declaration in my docblock includes comments after the return type of RowsOfFields
or PropertyList
, the returned output is not formatted as expected.
See above.
Get help on a command with arguments, but no options. e.g.:
terminus help org:people:add
See: pantheon-systems/terminus#1558
The role
argument should contain its help description
The role
argument has no description in its help output.
See pantheon-systems/terminus#1503
@return PropertyList Some description of my properties
The command should work correctly
The @return annotation is not recognized, and formatting fails
Which O.S. and PHP version are you using?
As in the example in the docs, I am using the init hook to set a some option values to defaults if they were not given on the command line.
What I'd like to do when this happens is notify the user, .e.g:
Foo option has been set to 'bar'.
However, the hook doesn't appear to allow an $output parameter.
Is there a reason that it's not allowed to use this with symfony/console v 5.2?
"symfony/console": "^4.4.8|~5.1.0",
It would be useful if Drush's debug output said the command class and method that was about to be run.
CommandProcessor has LoggerAwareTrait, so it can log, but Drush uses constants from Drush\Log\LogLevel, so I don't know how we do that over here.
The readme lists the hooks by a human name, eg 'Option Event Hook', and links to the interface, which is great.
But it doesn't seem to say anywhere that this hook must be annotated as '@hook option'.
These machine names should be in the Readme text.
Maybe they could also be annotated in some way on the interface?
InteractorInterface is not used in this project. I'd love to see examples. Can be handy.
Link for text 'See Using Robo as a Framework' on https://github.com/consolidation/annotated-command is broken.
I created a command class with the following function:
public function initialize($options = ['project-dir' => __DIR__, 'gitignore' => TRUE]) {
I ran app category:initialize
to run the command.
The $options['gitignore']
would be set to TRUE
The $options['gitignore']
was set to FALSE
php 5.5.38, MacOS Sierra
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.