First commit
This commit is contained in:
commit
c6e2478c40
13918 changed files with 2303184 additions and 0 deletions
452
vendor/consolidation/annotated-command/src/AnnotatedCommand.php
vendored
Normal file
452
vendor/consolidation/annotated-command/src/AnnotatedCommand.php
vendored
Normal file
|
@ -0,0 +1,452 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand;
|
||||
|
||||
use Consolidation\AnnotatedCommand\Hooks\HookManager;
|
||||
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
|
||||
use Consolidation\AnnotatedCommand\Help\HelpDocumentAlter;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* AnnotatedCommands are created automatically by the
|
||||
* AnnotatedCommandFactory. Each command method in a
|
||||
* command file will produce one AnnotatedCommand. These
|
||||
* are then added to your Symfony Console Application object;
|
||||
* nothing else is needed.
|
||||
*
|
||||
* Optionally, though, you may extend AnnotatedCommand directly
|
||||
* to make a single command. The usage pattern is the same
|
||||
* as for any other Symfony Console command, except that you may
|
||||
* omit the 'Confiure' method, and instead place your annotations
|
||||
* on the execute() method.
|
||||
*
|
||||
* @package Consolidation\AnnotatedCommand
|
||||
*/
|
||||
class AnnotatedCommand extends Command implements HelpDocumentAlter
|
||||
{
|
||||
protected $commandCallback;
|
||||
protected $commandProcessor;
|
||||
protected $annotationData;
|
||||
protected $examples = [];
|
||||
protected $topics = [];
|
||||
protected $usesInputInterface;
|
||||
protected $usesOutputInterface;
|
||||
protected $returnType;
|
||||
|
||||
public function __construct($name = null)
|
||||
{
|
||||
$commandInfo = false;
|
||||
|
||||
// If this is a subclass of AnnotatedCommand, check to see
|
||||
// if the 'execute' method is annotated. We could do this
|
||||
// unconditionally; it is a performance optimization to skip
|
||||
// checking the annotations if $this is an instance of
|
||||
// AnnotatedCommand. Alternately, we break out a new subclass.
|
||||
// The command factory instantiates the subclass.
|
||||
if (get_class($this) != 'Consolidation\AnnotatedCommand\AnnotatedCommand') {
|
||||
$commandInfo = CommandInfo::create($this, 'execute');
|
||||
if (!isset($name)) {
|
||||
$name = $commandInfo->getName();
|
||||
}
|
||||
}
|
||||
parent::__construct($name);
|
||||
if ($commandInfo && $commandInfo->hasAnnotation('command')) {
|
||||
$this->setCommandInfo($commandInfo);
|
||||
$this->setCommandOptions($commandInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public function setCommandCallback($commandCallback)
|
||||
{
|
||||
$this->commandCallback = $commandCallback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCommandProcessor($commandProcessor)
|
||||
{
|
||||
$this->commandProcessor = $commandProcessor;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function commandProcessor()
|
||||
{
|
||||
// If someone is using an AnnotatedCommand, and is NOT getting
|
||||
// it from an AnnotatedCommandFactory OR not correctly injecting
|
||||
// a command processor via setCommandProcessor() (ideally via the
|
||||
// DI container), then we'll just give each annotated command its
|
||||
// own command processor. This is not ideal; preferably, there would
|
||||
// only be one instance of the command processor in the application.
|
||||
if (!isset($this->commandProcessor)) {
|
||||
$this->commandProcessor = new CommandProcessor(new HookManager());
|
||||
}
|
||||
return $this->commandProcessor;
|
||||
}
|
||||
|
||||
public function getReturnType()
|
||||
{
|
||||
return $this->returnType;
|
||||
}
|
||||
|
||||
public function setReturnType($returnType)
|
||||
{
|
||||
$this->returnType = $returnType;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAnnotationData()
|
||||
{
|
||||
return $this->annotationData;
|
||||
}
|
||||
|
||||
public function setAnnotationData($annotationData)
|
||||
{
|
||||
$this->annotationData = $annotationData;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTopics()
|
||||
{
|
||||
return $this->topics;
|
||||
}
|
||||
|
||||
public function setTopics($topics)
|
||||
{
|
||||
$this->topics = $topics;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCommandInfo($commandInfo)
|
||||
{
|
||||
$this->setDescription($commandInfo->getDescription());
|
||||
$this->setHelp($commandInfo->getHelp());
|
||||
$this->setAliases($commandInfo->getAliases());
|
||||
$this->setAnnotationData($commandInfo->getAnnotations());
|
||||
$this->setTopics($commandInfo->getTopics());
|
||||
foreach ($commandInfo->getExampleUsages() as $usage => $description) {
|
||||
$this->addUsageOrExample($usage, $description);
|
||||
}
|
||||
$this->setCommandArguments($commandInfo);
|
||||
$this->setReturnType($commandInfo->getReturnType());
|
||||
// Hidden commands available since Symfony 3.2
|
||||
// http://symfony.com/doc/current/console/hide_commands.html
|
||||
if (method_exists($this, 'setHidden')) {
|
||||
$this->setHidden($commandInfo->getHidden());
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExampleUsages()
|
||||
{
|
||||
return $this->examples;
|
||||
}
|
||||
|
||||
protected function addUsageOrExample($usage, $description)
|
||||
{
|
||||
$this->addUsage($usage);
|
||||
if (!empty($description)) {
|
||||
$this->examples[$usage] = $description;
|
||||
}
|
||||
}
|
||||
|
||||
public function helpAlter(\DomDocument $originalDom)
|
||||
{
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
$dom->appendChild($commandXML = $dom->createElement('command'));
|
||||
$commandXML->setAttribute('id', $this->getName());
|
||||
$commandXML->setAttribute('name', $this->getName());
|
||||
|
||||
// Get the original <command> element and its top-level elements.
|
||||
$originalCommandXML = $this->getSingleElementByTagName($dom, $originalDom, 'command');
|
||||
$originalUsagesXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'usages');
|
||||
$originalDescriptionXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'description');
|
||||
$originalHelpXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'help');
|
||||
$originalArgumentsXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'arguments');
|
||||
$originalOptionsXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'options');
|
||||
|
||||
// Keep only the first of the <usage> elements
|
||||
$newUsagesXML = $dom->createElement('usages');
|
||||
$firstUsageXML = $this->getSingleElementByTagName($dom, $originalUsagesXML, 'usage');
|
||||
$newUsagesXML->appendChild($firstUsageXML);
|
||||
|
||||
// Create our own <example> elements
|
||||
$newExamplesXML = $dom->createElement('examples');
|
||||
foreach ($this->examples as $usage => $description) {
|
||||
$newExamplesXML->appendChild($exampleXML = $dom->createElement('example'));
|
||||
$exampleXML->appendChild($usageXML = $dom->createElement('usage', $usage));
|
||||
$exampleXML->appendChild($descriptionXML = $dom->createElement('description', $description));
|
||||
}
|
||||
|
||||
// Create our own <alias> elements
|
||||
$newAliasesXML = $dom->createElement('aliases');
|
||||
foreach ($this->getAliases() as $alias) {
|
||||
$newAliasesXML->appendChild($dom->createElement('alias', $alias));
|
||||
}
|
||||
|
||||
// Create our own <topic> elements
|
||||
$newTopicsXML = $dom->createElement('topics');
|
||||
foreach ($this->getTopics() as $topic) {
|
||||
$newTopicsXML->appendChild($topicXML = $dom->createElement('topic', $topic));
|
||||
}
|
||||
|
||||
// Place the different elements into the <command> element in the desired order
|
||||
$commandXML->appendChild($newUsagesXML);
|
||||
$commandXML->appendChild($newExamplesXML);
|
||||
$commandXML->appendChild($originalDescriptionXML);
|
||||
$commandXML->appendChild($originalArgumentsXML);
|
||||
$commandXML->appendChild($originalOptionsXML);
|
||||
$commandXML->appendChild($originalHelpXML);
|
||||
$commandXML->appendChild($newAliasesXML);
|
||||
$commandXML->appendChild($newTopicsXML);
|
||||
|
||||
return $dom;
|
||||
}
|
||||
|
||||
protected function getSingleElementByTagName($dom, $parent, $tagName)
|
||||
{
|
||||
// There should always be exactly one '<command>' element.
|
||||
$elements = $parent->getElementsByTagName($tagName);
|
||||
$result = $elements->item(0);
|
||||
|
||||
$result = $dom->importNode($result, true);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function setCommandArguments($commandInfo)
|
||||
{
|
||||
$this->setUsesInputInterface($commandInfo);
|
||||
$this->setUsesOutputInterface($commandInfo);
|
||||
$this->setCommandArgumentsFromParameters($commandInfo);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the first parameter is an InputInterface.
|
||||
*/
|
||||
protected function checkUsesInputInterface($params)
|
||||
{
|
||||
/** @var \ReflectionParameter $firstParam */
|
||||
$firstParam = reset($params);
|
||||
return $firstParam && $firstParam->getClass() && $firstParam->getClass()->implementsInterface(
|
||||
'\\Symfony\\Component\\Console\\Input\\InputInterface'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this command wants to get its inputs
|
||||
* via an InputInterface or via its command parameters
|
||||
*/
|
||||
protected function setUsesInputInterface($commandInfo)
|
||||
{
|
||||
$params = $commandInfo->getParameters();
|
||||
$this->usesInputInterface = $this->checkUsesInputInterface($params);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this command wants to send its output directly
|
||||
* to the provided OutputInterface, or whether it will returned
|
||||
* structured output to be processed by the command processor.
|
||||
*/
|
||||
protected function setUsesOutputInterface($commandInfo)
|
||||
{
|
||||
$params = $commandInfo->getParameters();
|
||||
$index = $this->checkUsesInputInterface($params) ? 1 : 0;
|
||||
$this->usesOutputInterface =
|
||||
(count($params) > $index) &&
|
||||
$params[$index]->getClass() &&
|
||||
$params[$index]->getClass()->implementsInterface(
|
||||
'\\Symfony\\Component\\Console\\Output\\OutputInterface'
|
||||
)
|
||||
;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function setCommandArgumentsFromParameters($commandInfo)
|
||||
{
|
||||
$args = $commandInfo->arguments()->getValues();
|
||||
foreach ($args as $name => $defaultValue) {
|
||||
$description = $commandInfo->arguments()->getDescription($name);
|
||||
$hasDefault = $commandInfo->arguments()->hasDefault($name);
|
||||
$parameterMode = $this->getCommandArgumentMode($hasDefault, $defaultValue);
|
||||
$this->addArgument($name, $parameterMode, $description, $defaultValue);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function getCommandArgumentMode($hasDefault, $defaultValue)
|
||||
{
|
||||
if (!$hasDefault) {
|
||||
return InputArgument::REQUIRED;
|
||||
}
|
||||
if (is_array($defaultValue)) {
|
||||
return InputArgument::IS_ARRAY;
|
||||
}
|
||||
return InputArgument::OPTIONAL;
|
||||
}
|
||||
|
||||
public function setCommandOptions($commandInfo, $automaticOptions = [])
|
||||
{
|
||||
$inputOptions = $commandInfo->inputOptions();
|
||||
|
||||
$this->addOptions($inputOptions + $automaticOptions, $automaticOptions);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addOptions($inputOptions, $automaticOptions = [])
|
||||
{
|
||||
foreach ($inputOptions as $name => $inputOption) {
|
||||
$description = $inputOption->getDescription();
|
||||
|
||||
if (empty($description) && isset($automaticOptions[$name])) {
|
||||
$description = $automaticOptions[$name]->getDescription();
|
||||
$inputOption = static::inputOptionSetDescription($inputOption, $description);
|
||||
}
|
||||
$this->getDefinition()->addOption($inputOption);
|
||||
}
|
||||
}
|
||||
|
||||
protected static function inputOptionSetDescription($inputOption, $description)
|
||||
{
|
||||
// Recover the 'mode' value, because Symfony is stubborn
|
||||
$mode = 0;
|
||||
if ($inputOption->isValueRequired()) {
|
||||
$mode |= InputOption::VALUE_REQUIRED;
|
||||
}
|
||||
if ($inputOption->isValueOptional()) {
|
||||
$mode |= InputOption::VALUE_OPTIONAL;
|
||||
}
|
||||
if ($inputOption->isArray()) {
|
||||
$mode |= InputOption::VALUE_IS_ARRAY;
|
||||
}
|
||||
if (!$mode) {
|
||||
$mode = InputOption::VALUE_NONE;
|
||||
}
|
||||
|
||||
$inputOption = new InputOption(
|
||||
$inputOption->getName(),
|
||||
$inputOption->getShortcut(),
|
||||
$mode,
|
||||
$description,
|
||||
$inputOption->getDefault()
|
||||
);
|
||||
return $inputOption;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all of the hook names that may be called for this command.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getNames()
|
||||
{
|
||||
return HookManager::getNames($this, $this->commandCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add any options to this command that are defined by hook implementations
|
||||
*/
|
||||
public function optionsHook()
|
||||
{
|
||||
$this->commandProcessor()->optionsHook(
|
||||
$this,
|
||||
$this->getNames(),
|
||||
$this->annotationData
|
||||
);
|
||||
}
|
||||
|
||||
public function optionsHookForHookAnnotations($commandInfoList)
|
||||
{
|
||||
foreach ($commandInfoList as $commandInfo) {
|
||||
$inputOptions = $commandInfo->inputOptions();
|
||||
$this->addOptions($inputOptions);
|
||||
foreach ($commandInfo->getExampleUsages() as $usage => $description) {
|
||||
if (!in_array($usage, $this->getUsages())) {
|
||||
$this->addUsageOrExample($usage, $description);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function interact(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->commandProcessor()->interact(
|
||||
$input,
|
||||
$output,
|
||||
$this->getNames(),
|
||||
$this->annotationData
|
||||
);
|
||||
}
|
||||
|
||||
protected function initialize(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
// Allow the hook manager a chance to provide configuration values,
|
||||
// if there are any registered hooks to do that.
|
||||
$this->commandProcessor()->initializeHook($input, $this->getNames(), $this->annotationData);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
// Validate, run, process, alter, handle results.
|
||||
return $this->commandProcessor()->process(
|
||||
$output,
|
||||
$this->getNames(),
|
||||
$this->commandCallback,
|
||||
$this->createCommandData($input, $output)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is available for use by a class that may
|
||||
* wish to extend this class rather than use annotations to
|
||||
* define commands. Using this technique does allow for the
|
||||
* use of annotations to define hooks.
|
||||
*/
|
||||
public function processResults(InputInterface $input, OutputInterface $output, $results)
|
||||
{
|
||||
$commandData = $this->createCommandData($input, $output);
|
||||
$commandProcessor = $this->commandProcessor();
|
||||
$names = $this->getNames();
|
||||
$results = $commandProcessor->processResults(
|
||||
$names,
|
||||
$results,
|
||||
$commandData
|
||||
);
|
||||
return $commandProcessor->handleResults(
|
||||
$output,
|
||||
$names,
|
||||
$results,
|
||||
$commandData
|
||||
);
|
||||
}
|
||||
|
||||
protected function createCommandData(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$commandData = new CommandData(
|
||||
$this->annotationData,
|
||||
$input,
|
||||
$output
|
||||
);
|
||||
|
||||
$commandData->setUseIOInterfaces(
|
||||
$this->usesInputInterface,
|
||||
$this->usesOutputInterface
|
||||
);
|
||||
|
||||
// Allow the commandData to cache the list of options with
|
||||
// special default values ('null' and 'true'), as these will
|
||||
// need special handling. @see CommandData::options().
|
||||
$commandData->cacheSpecialDefaults($this->getDefinition());
|
||||
|
||||
return $commandData;
|
||||
}
|
||||
}
|
458
vendor/consolidation/annotated-command/src/AnnotatedCommandFactory.php
vendored
Normal file
458
vendor/consolidation/annotated-command/src/AnnotatedCommandFactory.php
vendored
Normal file
|
@ -0,0 +1,458 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand;
|
||||
|
||||
use Consolidation\AnnotatedCommand\Cache\CacheWrapper;
|
||||
use Consolidation\AnnotatedCommand\Cache\NullCache;
|
||||
use Consolidation\AnnotatedCommand\Cache\SimpleCacheInterface;
|
||||
use Consolidation\AnnotatedCommand\Hooks\HookManager;
|
||||
use Consolidation\AnnotatedCommand\Options\AutomaticOptionsProviderInterface;
|
||||
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
|
||||
use Consolidation\AnnotatedCommand\Parser\CommandInfoDeserializer;
|
||||
use Consolidation\AnnotatedCommand\Parser\CommandInfoSerializer;
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* The AnnotatedCommandFactory creates commands for your application.
|
||||
* Use with a Dependency Injection Container and the CommandFactory.
|
||||
* Alternately, use the CommandFileDiscovery to find commandfiles, and
|
||||
* then use AnnotatedCommandFactory::createCommandsFromClass() to create
|
||||
* commands. See the README for more information.
|
||||
*
|
||||
* @package Consolidation\AnnotatedCommand
|
||||
*/
|
||||
class AnnotatedCommandFactory implements AutomaticOptionsProviderInterface
|
||||
{
|
||||
/** var CommandProcessor */
|
||||
protected $commandProcessor;
|
||||
|
||||
/** var CommandCreationListenerInterface[] */
|
||||
protected $listeners = [];
|
||||
|
||||
/** var AutomaticOptionsProvider[] */
|
||||
protected $automaticOptionsProviderList = [];
|
||||
|
||||
/** var boolean */
|
||||
protected $includeAllPublicMethods = true;
|
||||
|
||||
/** var CommandInfoAltererInterface */
|
||||
protected $commandInfoAlterers = [];
|
||||
|
||||
/** var SimpleCacheInterface */
|
||||
protected $dataStore;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->dataStore = new NullCache();
|
||||
$this->commandProcessor = new CommandProcessor(new HookManager());
|
||||
$this->addAutomaticOptionProvider($this);
|
||||
}
|
||||
|
||||
public function setCommandProcessor(CommandProcessor $commandProcessor)
|
||||
{
|
||||
$this->commandProcessor = $commandProcessor;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CommandProcessor
|
||||
*/
|
||||
public function commandProcessor()
|
||||
{
|
||||
return $this->commandProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'include all public methods flag'. If true (the default), then
|
||||
* every public method of each commandFile will be used to create commands.
|
||||
* If it is false, then only those public methods annotated with @command
|
||||
* or @name (deprecated) will be used to create commands.
|
||||
*/
|
||||
public function setIncludeAllPublicMethods($includeAllPublicMethods)
|
||||
{
|
||||
$this->includeAllPublicMethods = $includeAllPublicMethods;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIncludeAllPublicMethods()
|
||||
{
|
||||
return $this->includeAllPublicMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HookManager
|
||||
*/
|
||||
public function hookManager()
|
||||
{
|
||||
return $this->commandProcessor()->hookManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener that is notified immediately before the command
|
||||
* factory creates commands from a commandFile instance. This
|
||||
* listener can use this opportunity to do more setup for the commandFile,
|
||||
* and so on.
|
||||
*
|
||||
* @param CommandCreationListenerInterface $listener
|
||||
*/
|
||||
public function addListener(CommandCreationListenerInterface $listener)
|
||||
{
|
||||
$this->listeners[] = $listener;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener that's just a simple 'callable'.
|
||||
* @param callable $listener
|
||||
*/
|
||||
public function addListernerCallback(callable $listener)
|
||||
{
|
||||
$this->addListener(new CommandCreationListener($listener));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call all command creation listeners
|
||||
*
|
||||
* @param object $commandFileInstance
|
||||
*/
|
||||
protected function notify($commandFileInstance)
|
||||
{
|
||||
foreach ($this->listeners as $listener) {
|
||||
$listener->notifyCommandFileAdded($commandFileInstance);
|
||||
}
|
||||
}
|
||||
|
||||
public function addAutomaticOptionProvider(AutomaticOptionsProviderInterface $optionsProvider)
|
||||
{
|
||||
$this->automaticOptionsProviderList[] = $optionsProvider;
|
||||
}
|
||||
|
||||
public function addCommandInfoAlterer(CommandInfoAltererInterface $alterer)
|
||||
{
|
||||
$this->commandInfoAlterers[] = $alterer;
|
||||
}
|
||||
|
||||
/**
|
||||
* n.b. This registers all hooks from the commandfile instance as a side-effect.
|
||||
*/
|
||||
public function createCommandsFromClass($commandFileInstance, $includeAllPublicMethods = null)
|
||||
{
|
||||
// Deprecated: avoid using the $includeAllPublicMethods in favor of the setIncludeAllPublicMethods() accessor.
|
||||
if (!isset($includeAllPublicMethods)) {
|
||||
$includeAllPublicMethods = $this->getIncludeAllPublicMethods();
|
||||
}
|
||||
$this->notify($commandFileInstance);
|
||||
$commandInfoList = $this->getCommandInfoListFromClass($commandFileInstance);
|
||||
$this->registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance);
|
||||
return $this->createCommandsFromClassInfo($commandInfoList, $commandFileInstance, $includeAllPublicMethods);
|
||||
}
|
||||
|
||||
public function getCommandInfoListFromClass($commandFileInstance)
|
||||
{
|
||||
$cachedCommandInfoList = $this->getCommandInfoListFromCache($commandFileInstance);
|
||||
$commandInfoList = $this->createCommandInfoListFromClass($commandFileInstance, $cachedCommandInfoList);
|
||||
if (!empty($commandInfoList)) {
|
||||
$cachedCommandInfoList = array_merge($commandInfoList, $cachedCommandInfoList);
|
||||
$this->storeCommandInfoListInCache($commandFileInstance, $cachedCommandInfoList);
|
||||
}
|
||||
return $cachedCommandInfoList;
|
||||
}
|
||||
|
||||
protected function storeCommandInfoListInCache($commandFileInstance, $commandInfoList)
|
||||
{
|
||||
if (!$this->hasDataStore()) {
|
||||
return;
|
||||
}
|
||||
$cache_data = [];
|
||||
$serializer = new CommandInfoSerializer();
|
||||
foreach ($commandInfoList as $i => $commandInfo) {
|
||||
$cache_data[$i] = $serializer->serialize($commandInfo);
|
||||
}
|
||||
$className = get_class($commandFileInstance);
|
||||
$this->getDataStore()->set($className, $cache_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the command info list from the cache
|
||||
*
|
||||
* @param mixed $commandFileInstance
|
||||
* @return array
|
||||
*/
|
||||
protected function getCommandInfoListFromCache($commandFileInstance)
|
||||
{
|
||||
$commandInfoList = [];
|
||||
$className = get_class($commandFileInstance);
|
||||
if (!$this->getDataStore()->has($className)) {
|
||||
return [];
|
||||
}
|
||||
$deserializer = new CommandInfoDeserializer();
|
||||
|
||||
$cache_data = $this->getDataStore()->get($className);
|
||||
foreach ($cache_data as $i => $data) {
|
||||
if (CommandInfoDeserializer::isValidSerializedData((array)$data)) {
|
||||
$commandInfoList[$i] = $deserializer->deserialize((array)$data);
|
||||
}
|
||||
}
|
||||
return $commandInfoList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if this factory has a cache datastore.
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasDataStore()
|
||||
{
|
||||
return !($this->dataStore instanceof NullCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cache datastore for this factory. Any object with 'set' and
|
||||
* 'get' methods is acceptable. The key is the classname being cached,
|
||||
* and the value is a nested associative array of strings.
|
||||
*
|
||||
* TODO: Typehint this to SimpleCacheInterface
|
||||
*
|
||||
* This is not done currently to allow clients to use a generic cache
|
||||
* store that does not itself depend on the annotated-command library.
|
||||
*
|
||||
* @param Mixed $dataStore
|
||||
* @return type
|
||||
*/
|
||||
public function setDataStore($dataStore)
|
||||
{
|
||||
if (!($dataStore instanceof SimpleCacheInterface)) {
|
||||
$dataStore = new CacheWrapper($dataStore);
|
||||
}
|
||||
$this->dataStore = $dataStore;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data store attached to this factory.
|
||||
*/
|
||||
public function getDataStore()
|
||||
{
|
||||
return $this->dataStore;
|
||||
}
|
||||
|
||||
protected function createCommandInfoListFromClass($classNameOrInstance, $cachedCommandInfoList)
|
||||
{
|
||||
$commandInfoList = [];
|
||||
|
||||
// Ignore special functions, such as __construct and __call, which
|
||||
// can never be commands.
|
||||
$commandMethodNames = array_filter(
|
||||
get_class_methods($classNameOrInstance) ?: [],
|
||||
function ($m) use ($classNameOrInstance) {
|
||||
$reflectionMethod = new \ReflectionMethod($classNameOrInstance, $m);
|
||||
return !$reflectionMethod->isStatic() && !preg_match('#^_#', $m);
|
||||
}
|
||||
);
|
||||
|
||||
foreach ($commandMethodNames as $commandMethodName) {
|
||||
if (!array_key_exists($commandMethodName, $cachedCommandInfoList)) {
|
||||
$commandInfo = CommandInfo::create($classNameOrInstance, $commandMethodName);
|
||||
if (!static::isCommandOrHookMethod($commandInfo, $this->getIncludeAllPublicMethods())) {
|
||||
$commandInfo->invalidate();
|
||||
}
|
||||
$commandInfoList[$commandMethodName] = $commandInfo;
|
||||
}
|
||||
}
|
||||
|
||||
return $commandInfoList;
|
||||
}
|
||||
|
||||
public function createCommandInfo($classNameOrInstance, $commandMethodName)
|
||||
{
|
||||
return CommandInfo::create($classNameOrInstance, $commandMethodName);
|
||||
}
|
||||
|
||||
public function createCommandsFromClassInfo($commandInfoList, $commandFileInstance, $includeAllPublicMethods = null)
|
||||
{
|
||||
// Deprecated: avoid using the $includeAllPublicMethods in favor of the setIncludeAllPublicMethods() accessor.
|
||||
if (!isset($includeAllPublicMethods)) {
|
||||
$includeAllPublicMethods = $this->getIncludeAllPublicMethods();
|
||||
}
|
||||
return $this->createSelectedCommandsFromClassInfo(
|
||||
$commandInfoList,
|
||||
$commandFileInstance,
|
||||
function ($commandInfo) use ($includeAllPublicMethods) {
|
||||
return static::isCommandMethod($commandInfo, $includeAllPublicMethods);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function createSelectedCommandsFromClassInfo($commandInfoList, $commandFileInstance, callable $commandSelector)
|
||||
{
|
||||
$commandInfoList = $this->filterCommandInfoList($commandInfoList, $commandSelector);
|
||||
return array_map(
|
||||
function ($commandInfo) use ($commandFileInstance) {
|
||||
return $this->createCommand($commandInfo, $commandFileInstance);
|
||||
},
|
||||
$commandInfoList
|
||||
);
|
||||
}
|
||||
|
||||
protected function filterCommandInfoList($commandInfoList, callable $commandSelector)
|
||||
{
|
||||
return array_filter($commandInfoList, $commandSelector);
|
||||
}
|
||||
|
||||
public static function isCommandOrHookMethod($commandInfo, $includeAllPublicMethods)
|
||||
{
|
||||
return static::isHookMethod($commandInfo) || static::isCommandMethod($commandInfo, $includeAllPublicMethods);
|
||||
}
|
||||
|
||||
public static function isHookMethod($commandInfo)
|
||||
{
|
||||
return $commandInfo->hasAnnotation('hook');
|
||||
}
|
||||
|
||||
public static function isCommandMethod($commandInfo, $includeAllPublicMethods)
|
||||
{
|
||||
// Ignore everything labeled @hook
|
||||
if (static::isHookMethod($commandInfo)) {
|
||||
return false;
|
||||
}
|
||||
// Include everything labeled @command
|
||||
if ($commandInfo->hasAnnotation('command')) {
|
||||
return true;
|
||||
}
|
||||
// Skip anything that has a missing or invalid name.
|
||||
$commandName = $commandInfo->getName();
|
||||
if (empty($commandName) || preg_match('#[^a-zA-Z0-9:_-]#', $commandName)) {
|
||||
return false;
|
||||
}
|
||||
// Skip anything named like an accessor ('get' or 'set')
|
||||
if (preg_match('#^(get[A-Z]|set[A-Z])#', $commandInfo->getMethodName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Default to the setting of 'include all public methods'.
|
||||
return $includeAllPublicMethods;
|
||||
}
|
||||
|
||||
public function registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance)
|
||||
{
|
||||
foreach ($commandInfoList as $commandInfo) {
|
||||
if (static::isHookMethod($commandInfo)) {
|
||||
$this->registerCommandHook($commandInfo, $commandFileInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a command hook given the CommandInfo for a method.
|
||||
*
|
||||
* The hook format is:
|
||||
*
|
||||
* @hook type name type
|
||||
*
|
||||
* For example, the pre-validate hook for the core:init command is:
|
||||
*
|
||||
* @hook pre-validate core:init
|
||||
*
|
||||
* If no command name is provided, then this hook will affect every
|
||||
* command that is defined in the same file.
|
||||
*
|
||||
* If no hook is provided, then we will presume that ALTER_RESULT
|
||||
* is intended.
|
||||
*
|
||||
* @param CommandInfo $commandInfo Information about the command hook method.
|
||||
* @param object $commandFileInstance An instance of the CommandFile class.
|
||||
*/
|
||||
public function registerCommandHook(CommandInfo $commandInfo, $commandFileInstance)
|
||||
{
|
||||
// Ignore if the command info has no @hook
|
||||
if (!static::isHookMethod($commandInfo)) {
|
||||
return;
|
||||
}
|
||||
$hookData = $commandInfo->getAnnotation('hook');
|
||||
$hook = $this->getNthWord($hookData, 0, HookManager::ALTER_RESULT);
|
||||
$commandName = $this->getNthWord($hookData, 1);
|
||||
|
||||
// Register the hook
|
||||
$callback = [$commandFileInstance, $commandInfo->getMethodName()];
|
||||
$this->commandProcessor()->hookManager()->add($callback, $hook, $commandName);
|
||||
|
||||
// If the hook has options, then also register the commandInfo
|
||||
// with the hook manager, so that we can add options and such to
|
||||
// the commands they hook.
|
||||
if (!$commandInfo->options()->isEmpty()) {
|
||||
$this->commandProcessor()->hookManager()->recordHookOptions($commandInfo, $commandName);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getNthWord($string, $n, $default = '', $delimiter = ' ')
|
||||
{
|
||||
$words = explode($delimiter, $string);
|
||||
if (!empty($words[$n])) {
|
||||
return $words[$n];
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
public function createCommand(CommandInfo $commandInfo, $commandFileInstance)
|
||||
{
|
||||
$this->alterCommandInfo($commandInfo, $commandFileInstance);
|
||||
$command = new AnnotatedCommand($commandInfo->getName());
|
||||
$commandCallback = [$commandFileInstance, $commandInfo->getMethodName()];
|
||||
$command->setCommandCallback($commandCallback);
|
||||
$command->setCommandProcessor($this->commandProcessor);
|
||||
$command->setCommandInfo($commandInfo);
|
||||
$automaticOptions = $this->callAutomaticOptionsProviders($commandInfo);
|
||||
$command->setCommandOptions($commandInfo, $automaticOptions);
|
||||
// Annotation commands are never bootstrap-aware, but for completeness
|
||||
// we will notify on every created command, as some clients may wish to
|
||||
// use this notification for some other purpose.
|
||||
$this->notify($command);
|
||||
return $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give plugins an opportunity to update the commandInfo
|
||||
*/
|
||||
public function alterCommandInfo(CommandInfo $commandInfo, $commandFileInstance)
|
||||
{
|
||||
foreach ($this->commandInfoAlterers as $alterer) {
|
||||
$alterer->alterCommandInfo($commandInfo, $commandFileInstance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the options that are implied by annotations, e.g. @fields implies
|
||||
* that there should be a --fields and a --format option.
|
||||
*
|
||||
* @return InputOption[]
|
||||
*/
|
||||
public function callAutomaticOptionsProviders(CommandInfo $commandInfo)
|
||||
{
|
||||
$automaticOptions = [];
|
||||
foreach ($this->automaticOptionsProviderList as $automaticOptionsProvider) {
|
||||
$automaticOptions += $automaticOptionsProvider->automaticOptions($commandInfo);
|
||||
}
|
||||
return $automaticOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the options that are implied by annotations, e.g. @fields implies
|
||||
* that there should be a --fields and a --format option.
|
||||
*
|
||||
* @return InputOption[]
|
||||
*/
|
||||
public function automaticOptions(CommandInfo $commandInfo)
|
||||
{
|
||||
$automaticOptions = [];
|
||||
$formatManager = $this->commandProcessor()->formatterManager();
|
||||
if ($formatManager) {
|
||||
$annotationData = $commandInfo->getAnnotations()->getArrayCopy();
|
||||
$formatterOptions = new FormatterOptions($annotationData);
|
||||
$dataType = $commandInfo->getReturnType();
|
||||
$automaticOptions = $formatManager->automaticOptions($formatterOptions, $dataType);
|
||||
}
|
||||
return $automaticOptions;
|
||||
}
|
||||
}
|
27
vendor/consolidation/annotated-command/src/AnnotationData.php
vendored
Normal file
27
vendor/consolidation/annotated-command/src/AnnotationData.php
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand;
|
||||
|
||||
use Consolidation\AnnotatedCommand\Parser\Internal\CsvUtils;
|
||||
|
||||
class AnnotationData extends \ArrayObject
|
||||
{
|
||||
public function get($key, $default = '')
|
||||
{
|
||||
return $this->has($key) ? CsvUtils::toString($this[$key]) : $default;
|
||||
}
|
||||
|
||||
public function getList($key, $default = [])
|
||||
{
|
||||
return $this->has($key) ? CsvUtils::toList($this[$key]) : $default;
|
||||
}
|
||||
|
||||
public function has($key)
|
||||
{
|
||||
return isset($this[$key]);
|
||||
}
|
||||
|
||||
public function keys()
|
||||
{
|
||||
return array_keys($this->getArrayCopy());
|
||||
}
|
||||
}
|
49
vendor/consolidation/annotated-command/src/Cache/CacheWrapper.php
vendored
Normal file
49
vendor/consolidation/annotated-command/src/Cache/CacheWrapper.php
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Cache;
|
||||
|
||||
/**
|
||||
* Make a generic cache object conform to our expected interface.
|
||||
*/
|
||||
class CacheWrapper implements SimpleCacheInterface
|
||||
{
|
||||
protected $dataStore;
|
||||
|
||||
public function __construct($dataStore)
|
||||
{
|
||||
$this->dataStore = $dataStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for an entry from the cache
|
||||
* @param string $key
|
||||
* @return boolean
|
||||
*/
|
||||
public function has($key)
|
||||
{
|
||||
if (method_exists($this->dataStore, 'has')) {
|
||||
return $this->dataStore->has($key);
|
||||
}
|
||||
$test = $this->dataStore->get($key);
|
||||
return !empty($test);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an entry from the cache
|
||||
* @param string $key
|
||||
* @return array
|
||||
*/
|
||||
public function get($key)
|
||||
{
|
||||
return (array) $this->dataStore->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store an entry in the cache
|
||||
* @param string $key
|
||||
* @param array $data
|
||||
*/
|
||||
public function set($key, $data)
|
||||
{
|
||||
$this->dataStore->set($key, $data);
|
||||
}
|
||||
}
|
37
vendor/consolidation/annotated-command/src/Cache/NullCache.php
vendored
Normal file
37
vendor/consolidation/annotated-command/src/Cache/NullCache.php
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Cache;
|
||||
|
||||
/**
|
||||
* An empty cache that never stores or fetches any objects.
|
||||
*/
|
||||
class NullCache implements SimpleCacheInterface
|
||||
{
|
||||
/**
|
||||
* Test for an entry from the cache
|
||||
* @param string $key
|
||||
* @return boolean
|
||||
*/
|
||||
public function has($key)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an entry from the cache
|
||||
* @param string $key
|
||||
* @return array
|
||||
*/
|
||||
public function get($key)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Store an entry in the cache
|
||||
* @param string $key
|
||||
* @param array $data
|
||||
*/
|
||||
public function set($key, $data)
|
||||
{
|
||||
}
|
||||
}
|
35
vendor/consolidation/annotated-command/src/Cache/SimpleCacheInterface.php
vendored
Normal file
35
vendor/consolidation/annotated-command/src/Cache/SimpleCacheInterface.php
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Cache;
|
||||
|
||||
/**
|
||||
* Documentation interface.
|
||||
*
|
||||
* Clients that use AnnotatedCommandFactory::setDataStore()
|
||||
* are encouraged to provide a data store that implements
|
||||
* this interface.
|
||||
*
|
||||
* This is not currently required to allow clients to use a generic cache
|
||||
* store that does not itself depend on the annotated-command library.
|
||||
* This might be required in a future version.
|
||||
*/
|
||||
interface SimpleCacheInterface
|
||||
{
|
||||
/**
|
||||
* Test for an entry from the cache
|
||||
* @param string $key
|
||||
* @return boolean
|
||||
*/
|
||||
public function has($key);
|
||||
/**
|
||||
* Get an entry from the cache
|
||||
* @param string $key
|
||||
* @return array
|
||||
*/
|
||||
public function get($key);
|
||||
/**
|
||||
* Store an entry in the cache
|
||||
* @param string $key
|
||||
* @param array $data
|
||||
*/
|
||||
public function set($key, $data);
|
||||
}
|
25
vendor/consolidation/annotated-command/src/CommandCreationListener.php
vendored
Normal file
25
vendor/consolidation/annotated-command/src/CommandCreationListener.php
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand;
|
||||
|
||||
/**
|
||||
* Command cration listeners can be added to the annotation
|
||||
* command factory. These will be notified whenever a new
|
||||
* commandfile is provided to the factory. This is useful for
|
||||
* initializing new commandfile objects.
|
||||
*
|
||||
* @see AnnotatedCommandFactory::addListener()
|
||||
*/
|
||||
class CommandCreationListener implements CommandCreationListenerInterface
|
||||
{
|
||||
protected $listener;
|
||||
|
||||
public function __construct($listener)
|
||||
{
|
||||
$this->listener = $listener;
|
||||
}
|
||||
|
||||
public function notifyCommandFileAdded($command)
|
||||
{
|
||||
call_user_func($this->listener, $command);
|
||||
}
|
||||
}
|
15
vendor/consolidation/annotated-command/src/CommandCreationListenerInterface.php
vendored
Normal file
15
vendor/consolidation/annotated-command/src/CommandCreationListenerInterface.php
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand;
|
||||
|
||||
/**
|
||||
* Command cration listeners can be added to the annotation
|
||||
* command factory. These will be notified whenever a new
|
||||
* commandfile is provided to the factory. This is useful for
|
||||
* initializing new commandfile objects.
|
||||
*
|
||||
* @see AnnotatedCommandFactory::addListener()
|
||||
*/
|
||||
interface CommandCreationListenerInterface
|
||||
{
|
||||
public function notifyCommandFileAdded($command);
|
||||
}
|
195
vendor/consolidation/annotated-command/src/CommandData.php
vendored
Normal file
195
vendor/consolidation/annotated-command/src/CommandData.php
vendored
Normal file
|
@ -0,0 +1,195 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand;
|
||||
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CommandData
|
||||
{
|
||||
/** var AnnotationData */
|
||||
protected $annotationData;
|
||||
/** var InputInterface */
|
||||
protected $input;
|
||||
/** var OutputInterface */
|
||||
protected $output;
|
||||
/** var boolean */
|
||||
protected $usesInputInterface;
|
||||
/** var boolean */
|
||||
protected $usesOutputInterface;
|
||||
/** var boolean */
|
||||
protected $includeOptionsInArgs;
|
||||
/** var array */
|
||||
protected $specialDefaults = [];
|
||||
|
||||
public function __construct(
|
||||
AnnotationData $annotationData,
|
||||
InputInterface $input,
|
||||
OutputInterface $output,
|
||||
$usesInputInterface = false,
|
||||
$usesOutputInterface = false
|
||||
) {
|
||||
$this->annotationData = $annotationData;
|
||||
$this->input = $input;
|
||||
$this->output = $output;
|
||||
$this->usesInputInterface = false;
|
||||
$this->usesOutputInterface = false;
|
||||
$this->includeOptionsInArgs = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use only; indicates that the function to be called
|
||||
* should be passed an InputInterface &/or an OutputInterface.
|
||||
* @param booean $usesInputInterface
|
||||
* @param boolean $usesOutputInterface
|
||||
* @return self
|
||||
*/
|
||||
public function setUseIOInterfaces($usesInputInterface, $usesOutputInterface)
|
||||
{
|
||||
$this->usesInputInterface = $usesInputInterface;
|
||||
$this->usesOutputInterface = $usesOutputInterface;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* For backwards-compatibility mode only: disable addition of
|
||||
* options on the end of the arguments list.
|
||||
*/
|
||||
public function setIncludeOptionsInArgs($includeOptionsInArgs)
|
||||
{
|
||||
$this->includeOptionsInArgs = $includeOptionsInArgs;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function annotationData()
|
||||
{
|
||||
return $this->annotationData;
|
||||
}
|
||||
|
||||
public function input()
|
||||
{
|
||||
return $this->input;
|
||||
}
|
||||
|
||||
public function output()
|
||||
{
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
public function arguments()
|
||||
{
|
||||
return $this->input->getArguments();
|
||||
}
|
||||
|
||||
public function options()
|
||||
{
|
||||
// We cannot tell the difference between '--foo' (an option without
|
||||
// a value) and the absence of '--foo' when the option has an optional
|
||||
// value, and the current vallue of the option is 'null' using only
|
||||
// the public methods of InputInterface. We'll try to figure out
|
||||
// which is which by other means here.
|
||||
$options = $this->getAdjustedOptions();
|
||||
|
||||
// Make two conversions here:
|
||||
// --foo=0 wil convert $value from '0' to 'false' for binary options.
|
||||
// --foo with $value of 'true' will be forced to 'false' if --no-foo exists.
|
||||
foreach ($options as $option => $value) {
|
||||
if ($this->shouldConvertOptionToFalse($options, $option, $value)) {
|
||||
$options[$option] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use 'hasParameterOption()' to attempt to disambiguate option states.
|
||||
*/
|
||||
protected function getAdjustedOptions()
|
||||
{
|
||||
$options = $this->input->getOptions();
|
||||
|
||||
// If Input isn't an ArgvInput, then return the options as-is.
|
||||
if (!$this->input instanceof ArgvInput) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
// If we have an ArgvInput, then we can determine if options
|
||||
// are missing from the command line. If the option value is
|
||||
// missing from $input, then we will keep the value `null`.
|
||||
// If it is present, but has no explicit value, then change it its
|
||||
// value to `true`.
|
||||
foreach ($options as $option => $value) {
|
||||
if (($value === null) && ($this->input->hasParameterOption("--$option"))) {
|
||||
$options[$option] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
protected function shouldConvertOptionToFalse($options, $option, $value)
|
||||
{
|
||||
// If the value is 'true' (e.g. the option is '--foo'), then convert
|
||||
// it to false if there is also an option '--no-foo'. n.b. if the
|
||||
// commandline has '--foo=bar' then $value will not be 'true', and
|
||||
// --no-foo will be ignored.
|
||||
if ($value === true) {
|
||||
// Check if the --no-* option exists. Note that none of the other
|
||||
// alteration apply in the $value == true case, so we can exit early here.
|
||||
$negation_key = 'no-' . $option;
|
||||
return array_key_exists($negation_key, $options) && $options[$negation_key];
|
||||
}
|
||||
|
||||
// If the option is '--foo=0', convert the '0' to 'false' when appropriate.
|
||||
if ($value !== '0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The '--foo=0' convertion is only applicable when the default value
|
||||
// is not in the special defaults list. i.e. you get a literal '0'
|
||||
// when your default is a string.
|
||||
return in_array($option, $this->specialDefaults);
|
||||
}
|
||||
|
||||
public function cacheSpecialDefaults($definition)
|
||||
{
|
||||
foreach ($definition->getOptions() as $option => $inputOption) {
|
||||
$defaultValue = $inputOption->getDefault();
|
||||
if (($defaultValue === null) || ($defaultValue === true)) {
|
||||
$this->specialDefaults[] = $option;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getArgsWithoutAppName()
|
||||
{
|
||||
$args = $this->arguments();
|
||||
|
||||
// When called via the Application, the first argument
|
||||
// will be the command name. The Application alters the
|
||||
// input definition to match, adding a 'command' argument
|
||||
// to the beginning.
|
||||
array_shift($args);
|
||||
|
||||
if ($this->usesOutputInterface) {
|
||||
array_unshift($args, $this->output());
|
||||
}
|
||||
|
||||
if ($this->usesInputInterface) {
|
||||
array_unshift($args, $this->input());
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
public function getArgsAndOptions()
|
||||
{
|
||||
// Get passthrough args, and add the options on the end.
|
||||
$args = $this->getArgsWithoutAppName();
|
||||
if ($this->includeOptionsInArgs) {
|
||||
$args['options'] = $this->options();
|
||||
}
|
||||
return $args;
|
||||
}
|
||||
}
|
32
vendor/consolidation/annotated-command/src/CommandError.php
vendored
Normal file
32
vendor/consolidation/annotated-command/src/CommandError.php
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand;
|
||||
|
||||
/**
|
||||
* Return a CommandError as the result of a command to pass a status
|
||||
* code and error message to be displayed.
|
||||
*
|
||||
* @package Consolidation\AnnotatedCommand
|
||||
*/
|
||||
class CommandError implements ExitCodeInterface, OutputDataInterface
|
||||
{
|
||||
protected $message;
|
||||
protected $exitCode;
|
||||
|
||||
public function __construct($message = null, $exitCode = 1)
|
||||
{
|
||||
$this->message = $message;
|
||||
// Ensure the exit code is non-zero. The exit code may have
|
||||
// come from an exception, and those often default to zero if
|
||||
// a specific value is not provided.
|
||||
$this->exitCode = $exitCode == 0 ? 1 : $exitCode;
|
||||
}
|
||||
public function getExitCode()
|
||||
{
|
||||
return $this->exitCode;
|
||||
}
|
||||
|
||||
public function getOutputData()
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
}
|
406
vendor/consolidation/annotated-command/src/CommandFileDiscovery.php
vendored
Normal file
406
vendor/consolidation/annotated-command/src/CommandFileDiscovery.php
vendored
Normal file
|
@ -0,0 +1,406 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand;
|
||||
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
/**
|
||||
* Do discovery presuming that the namespace of the command will
|
||||
* contain the last component of the path. This is the convention
|
||||
* that should be used when searching for command files that are
|
||||
* bundled with the modules of a framework. The convention used
|
||||
* is that the namespace for a module in a framework should start with
|
||||
* the framework name followed by the module name.
|
||||
*
|
||||
* For example, if base namespace is "Drupal", then a command file in
|
||||
* modules/default_content/src/CliTools/ExampleCommands.cpp
|
||||
* will be in the namespace Drupal\default_content\CliTools.
|
||||
*
|
||||
* For global locations, the middle component of the namespace is
|
||||
* omitted. For example, if the base namespace is "Drupal", then
|
||||
* a command file in __DRUPAL_ROOT__/CliTools/ExampleCommands.cpp
|
||||
* will be in the namespace Drupal\CliTools.
|
||||
*
|
||||
* To discover namespaced commands in modules:
|
||||
*
|
||||
* $commandFiles = $discovery->discoverNamespaced($moduleList, '\Drupal');
|
||||
*
|
||||
* To discover global commands:
|
||||
*
|
||||
* $commandFiles = $discovery->discover($drupalRoot, '\Drupal');
|
||||
*/
|
||||
class CommandFileDiscovery
|
||||
{
|
||||
/** @var string[] */
|
||||
protected $excludeList;
|
||||
/** @var string[] */
|
||||
protected $searchLocations;
|
||||
/** @var string */
|
||||
protected $searchPattern = '*Commands.php';
|
||||
/** @var boolean */
|
||||
protected $includeFilesAtBase = true;
|
||||
/** @var integer */
|
||||
protected $searchDepth = 2;
|
||||
/** @var bool */
|
||||
protected $followLinks = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->excludeList = ['Exclude'];
|
||||
$this->searchLocations = [
|
||||
'Command',
|
||||
'CliTools', // TODO: Maybe remove
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether to search for files at the base directory
|
||||
* ($directoryList parameter to discover and discoverNamespaced
|
||||
* methods), or only in the directories listed in the search paths.
|
||||
*
|
||||
* @param boolean $includeFilesAtBase
|
||||
*/
|
||||
public function setIncludeFilesAtBase($includeFilesAtBase)
|
||||
{
|
||||
$this->includeFilesAtBase = $includeFilesAtBase;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of excludes to add to the finder, replacing
|
||||
* whatever was there before.
|
||||
*
|
||||
* @param array $excludeList The list of directory names to skip when
|
||||
* searching for command files.
|
||||
*/
|
||||
public function setExcludeList($excludeList)
|
||||
{
|
||||
$this->excludeList = $excludeList;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one more location to the exclude list.
|
||||
*
|
||||
* @param string $exclude One directory name to skip when searching
|
||||
* for command files.
|
||||
*/
|
||||
public function addExclude($exclude)
|
||||
{
|
||||
$this->excludeList[] = $exclude;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the search depth. By default, fills immediately in the
|
||||
* base directory are searched, plus all of the search locations
|
||||
* to this specified depth. If the search locations is set to
|
||||
* an empty array, then the base directory is searched to this
|
||||
* depth.
|
||||
*/
|
||||
public function setSearchDepth($searchDepth)
|
||||
{
|
||||
$this->searchDepth = $searchDepth;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that the discovery object should follow symlinks. By
|
||||
* default, symlinks are not followed.
|
||||
*/
|
||||
public function followLinks($followLinks = true)
|
||||
{
|
||||
$this->followLinks = $followLinks;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of search locations to examine in each directory where
|
||||
* command files may be found. This replaces whatever was there before.
|
||||
*
|
||||
* @param array $searchLocations The list of locations to search for command files.
|
||||
*/
|
||||
public function setSearchLocations($searchLocations)
|
||||
{
|
||||
$this->searchLocations = $searchLocations;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one more location to the search location list.
|
||||
*
|
||||
* @param string $location One more relative path to search
|
||||
* for command files.
|
||||
*/
|
||||
public function addSearchLocation($location)
|
||||
{
|
||||
$this->searchLocations[] = $location;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the pattern / regex used by the finder to search for
|
||||
* command files.
|
||||
*/
|
||||
public function setSearchPattern($searchPattern)
|
||||
{
|
||||
$this->searchPattern = $searchPattern;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of directories, e.g. Drupal modules like:
|
||||
*
|
||||
* core/modules/block
|
||||
* core/modules/dblog
|
||||
* modules/default_content
|
||||
*
|
||||
* Discover command files in any of these locations.
|
||||
*
|
||||
* @param string|string[] $directoryList Places to search for commands.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function discoverNamespaced($directoryList, $baseNamespace = '')
|
||||
{
|
||||
return $this->discover($this->convertToNamespacedList((array)$directoryList), $baseNamespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a simple list containing paths to directories, where
|
||||
* the last component of the path should appear in the namespace,
|
||||
* after the base namespace, this function will return an
|
||||
* associative array mapping the path's basename (e.g. the module
|
||||
* name) to the directory path.
|
||||
*
|
||||
* Module names must be unique.
|
||||
*
|
||||
* @param string[] $directoryList A list of module locations
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function convertToNamespacedList($directoryList)
|
||||
{
|
||||
$namespacedArray = [];
|
||||
foreach ((array)$directoryList as $directory) {
|
||||
$namespacedArray[basename($directory)] = $directory;
|
||||
}
|
||||
return $namespacedArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for command files in the specified locations. This is the function that
|
||||
* should be used for all locations that are NOT modules of a framework.
|
||||
*
|
||||
* @param string|string[] $directoryList Places to search for commands.
|
||||
* @return array
|
||||
*/
|
||||
public function discover($directoryList, $baseNamespace = '')
|
||||
{
|
||||
$commandFiles = [];
|
||||
foreach ((array)$directoryList as $key => $directory) {
|
||||
$itemsNamespace = $this->joinNamespace([$baseNamespace, $key]);
|
||||
$commandFiles = array_merge(
|
||||
$commandFiles,
|
||||
$this->discoverCommandFiles($directory, $itemsNamespace),
|
||||
$this->discoverCommandFiles("$directory/src", $itemsNamespace)
|
||||
);
|
||||
}
|
||||
return $commandFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for command files in specific locations within a single directory.
|
||||
*
|
||||
* In each location, we will accept only a few places where command files
|
||||
* can be found. This will reduce the need to search through many unrelated
|
||||
* files.
|
||||
*
|
||||
* The default search locations include:
|
||||
*
|
||||
* .
|
||||
* CliTools
|
||||
* src/CliTools
|
||||
*
|
||||
* The pattern we will look for is any file whose name ends in 'Commands.php'.
|
||||
* A list of paths to found files will be returned.
|
||||
*/
|
||||
protected function discoverCommandFiles($directory, $baseNamespace)
|
||||
{
|
||||
$commandFiles = [];
|
||||
// In the search location itself, we will search for command files
|
||||
// immediately inside the directory only.
|
||||
if ($this->includeFilesAtBase) {
|
||||
$commandFiles = $this->discoverCommandFilesInLocation(
|
||||
$directory,
|
||||
$this->getBaseDirectorySearchDepth(),
|
||||
$baseNamespace
|
||||
);
|
||||
}
|
||||
|
||||
// In the other search locations,
|
||||
foreach ($this->searchLocations as $location) {
|
||||
$itemsNamespace = $this->joinNamespace([$baseNamespace, $location]);
|
||||
$commandFiles = array_merge(
|
||||
$commandFiles,
|
||||
$this->discoverCommandFilesInLocation(
|
||||
"$directory/$location",
|
||||
$this->getSearchDepth(),
|
||||
$itemsNamespace
|
||||
)
|
||||
);
|
||||
}
|
||||
return $commandFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Finder search depth appropriate for our selected search depth.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getSearchDepth()
|
||||
{
|
||||
return $this->searchDepth <= 0 ? '== 0' : '<= ' . $this->searchDepth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Finder search depth for the base directory. If the
|
||||
* searchLocations array has been populated, then we will only search
|
||||
* for files immediately inside the base directory; no traversal into
|
||||
* deeper directories will be done, as that would conflict with the
|
||||
* specification provided by the search locations. If there is no
|
||||
* search location, then we will search to whatever depth was specified
|
||||
* by the client.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getBaseDirectorySearchDepth()
|
||||
{
|
||||
if (!empty($this->searchLocations)) {
|
||||
return '== 0';
|
||||
}
|
||||
return $this->getSearchDepth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for command files in just one particular location. Returns
|
||||
* an associative array mapping from the pathname of the file to the
|
||||
* classname that it contains. The pathname may be ignored if the search
|
||||
* location is included in the autoloader.
|
||||
*
|
||||
* @param string $directory The location to search
|
||||
* @param string $depth How deep to search (e.g. '== 0' or '< 2')
|
||||
* @param string $baseNamespace Namespace to prepend to each classname
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function discoverCommandFilesInLocation($directory, $depth, $baseNamespace)
|
||||
{
|
||||
if (!is_dir($directory)) {
|
||||
return [];
|
||||
}
|
||||
$finder = $this->createFinder($directory, $depth);
|
||||
|
||||
$commands = [];
|
||||
foreach ($finder as $file) {
|
||||
$relativePathName = $file->getRelativePathname();
|
||||
$relativeNamespaceAndClassname = str_replace(
|
||||
['/', '.php'],
|
||||
['\\', ''],
|
||||
$relativePathName
|
||||
);
|
||||
$classname = $this->joinNamespace([$baseNamespace, $relativeNamespaceAndClassname]);
|
||||
$commandFilePath = $this->joinPaths([$directory, $relativePathName]);
|
||||
$commands[$commandFilePath] = $classname;
|
||||
}
|
||||
|
||||
return $commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Finder object for use in searching a particular directory
|
||||
* location.
|
||||
*
|
||||
* @param string $directory The location to search
|
||||
* @param string $depth The depth limitation
|
||||
*
|
||||
* @return Finder
|
||||
*/
|
||||
protected function createFinder($directory, $depth)
|
||||
{
|
||||
$finder = new Finder();
|
||||
$finder->files()
|
||||
->name($this->searchPattern)
|
||||
->in($directory)
|
||||
->depth($depth);
|
||||
|
||||
foreach ($this->excludeList as $item) {
|
||||
$finder->exclude($item);
|
||||
}
|
||||
|
||||
if ($this->followLinks) {
|
||||
$finder->followLinks();
|
||||
}
|
||||
|
||||
return $finder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine the items of the provied array into a backslash-separated
|
||||
* namespace string. Empty and numeric items are omitted.
|
||||
*
|
||||
* @param array $namespaceParts List of components of a namespace
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function joinNamespace(array $namespaceParts)
|
||||
{
|
||||
return $this->joinParts(
|
||||
'\\',
|
||||
$namespaceParts,
|
||||
function ($item) {
|
||||
return !is_numeric($item) && !empty($item);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine the items of the provied array into a slash-separated
|
||||
* pathname. Empty items are omitted.
|
||||
*
|
||||
* @param array $pathParts List of components of a path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function joinPaths(array $pathParts)
|
||||
{
|
||||
$path = $this->joinParts(
|
||||
'/',
|
||||
$pathParts,
|
||||
function ($item) {
|
||||
return !empty($item);
|
||||
}
|
||||
);
|
||||
return str_replace(DIRECTORY_SEPARATOR, '/', $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple wrapper around implode and array_filter.
|
||||
*
|
||||
* @param string $delimiter
|
||||
* @param array $parts
|
||||
* @param callable $filterFunction
|
||||
*/
|
||||
protected function joinParts($delimiter, $parts, $filterFunction)
|
||||
{
|
||||
$parts = array_map(
|
||||
function ($item) use ($delimiter) {
|
||||
return rtrim($item, $delimiter);
|
||||
},
|
||||
$parts
|
||||
);
|
||||
return implode(
|
||||
$delimiter,
|
||||
array_filter($parts, $filterFunction)
|
||||
);
|
||||
}
|
||||
}
|
9
vendor/consolidation/annotated-command/src/CommandInfoAltererInterface.php
vendored
Normal file
9
vendor/consolidation/annotated-command/src/CommandInfoAltererInterface.php
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand;
|
||||
|
||||
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
|
||||
|
||||
interface CommandInfoAltererInterface
|
||||
{
|
||||
public function alterCommandInfo(CommandInfo $commandInfo, $commandFileInstance);
|
||||
}
|
363
vendor/consolidation/annotated-command/src/CommandProcessor.php
vendored
Normal file
363
vendor/consolidation/annotated-command/src/CommandProcessor.php
vendored
Normal file
|
@ -0,0 +1,363 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand;
|
||||
|
||||
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ReplaceCommandHookDispatcher;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
|
||||
use Consolidation\OutputFormatters\FormatterManager;
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\AnnotatedCommand\Hooks\HookManager;
|
||||
use Consolidation\AnnotatedCommand\Options\PrepareFormatter;
|
||||
|
||||
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\InitializeHookDispatcher;
|
||||
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\OptionsHookDispatcher;
|
||||
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\InteractHookDispatcher;
|
||||
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ValidateHookDispatcher;
|
||||
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ProcessResultHookDispatcher;
|
||||
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\StatusDeterminerHookDispatcher;
|
||||
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ExtracterHookDispatcher;
|
||||
|
||||
/**
|
||||
* Process a command, including hooks and other callbacks.
|
||||
* There should only be one command processor per application.
|
||||
* Provide your command processor to the AnnotatedCommandFactory
|
||||
* via AnnotatedCommandFactory::setCommandProcessor().
|
||||
*/
|
||||
class CommandProcessor implements LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/** var HookManager */
|
||||
protected $hookManager;
|
||||
/** var FormatterManager */
|
||||
protected $formatterManager;
|
||||
/** var callable */
|
||||
protected $displayErrorFunction;
|
||||
/** var PrepareFormatterOptions[] */
|
||||
protected $prepareOptionsList = [];
|
||||
/** var boolean */
|
||||
protected $passExceptions;
|
||||
|
||||
public function __construct(HookManager $hookManager)
|
||||
{
|
||||
$this->hookManager = $hookManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hook manager
|
||||
* @return HookManager
|
||||
*/
|
||||
public function hookManager()
|
||||
{
|
||||
return $this->hookManager;
|
||||
}
|
||||
|
||||
public function addPrepareFormatter(PrepareFormatter $preparer)
|
||||
{
|
||||
$this->prepareOptionsList[] = $preparer;
|
||||
}
|
||||
|
||||
public function setFormatterManager(FormatterManager $formatterManager)
|
||||
{
|
||||
$this->formatterManager = $formatterManager;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDisplayErrorFunction(callable $fn)
|
||||
{
|
||||
$this->displayErrorFunction = $fn;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a mode to make the annotated command library re-throw
|
||||
* any exception that it catches while processing a command.
|
||||
*
|
||||
* The default behavior in the current (2.x) branch is to catch
|
||||
* the exception and replace it with a CommandError object that
|
||||
* may be processed by the normal output processing passthrough.
|
||||
*
|
||||
* In the 3.x branch, exceptions will never be caught; they will
|
||||
* be passed through, as if setPassExceptions(true) were called.
|
||||
* This is the recommended behavior.
|
||||
*/
|
||||
public function setPassExceptions($passExceptions)
|
||||
{
|
||||
$this->passExceptions = $passExceptions;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function commandErrorForException(\Exception $e)
|
||||
{
|
||||
if ($this->passExceptions) {
|
||||
throw $e;
|
||||
}
|
||||
return new CommandError($e->getMessage(), $e->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the formatter manager
|
||||
* @return FormatterManager
|
||||
*/
|
||||
public function formatterManager()
|
||||
{
|
||||
return $this->formatterManager;
|
||||
}
|
||||
|
||||
public function initializeHook(
|
||||
InputInterface $input,
|
||||
$names,
|
||||
AnnotationData $annotationData
|
||||
) {
|
||||
$initializeDispatcher = new InitializeHookDispatcher($this->hookManager(), $names);
|
||||
return $initializeDispatcher->initialize($input, $annotationData);
|
||||
}
|
||||
|
||||
public function optionsHook(
|
||||
AnnotatedCommand $command,
|
||||
$names,
|
||||
AnnotationData $annotationData
|
||||
) {
|
||||
$optionsDispatcher = new OptionsHookDispatcher($this->hookManager(), $names);
|
||||
$optionsDispatcher->getOptions($command, $annotationData);
|
||||
}
|
||||
|
||||
public function interact(
|
||||
InputInterface $input,
|
||||
OutputInterface $output,
|
||||
$names,
|
||||
AnnotationData $annotationData
|
||||
) {
|
||||
$interactDispatcher = new InteractHookDispatcher($this->hookManager(), $names);
|
||||
return $interactDispatcher->interact($input, $output, $annotationData);
|
||||
}
|
||||
|
||||
public function process(
|
||||
OutputInterface $output,
|
||||
$names,
|
||||
$commandCallback,
|
||||
CommandData $commandData
|
||||
) {
|
||||
$result = [];
|
||||
try {
|
||||
$result = $this->validateRunAndAlter(
|
||||
$names,
|
||||
$commandCallback,
|
||||
$commandData
|
||||
);
|
||||
return $this->handleResults($output, $names, $result, $commandData);
|
||||
} catch (\Exception $e) {
|
||||
$result = $this->commandErrorForException($e);
|
||||
return $this->handleResults($output, $names, $result, $commandData);
|
||||
}
|
||||
}
|
||||
|
||||
public function validateRunAndAlter(
|
||||
$names,
|
||||
$commandCallback,
|
||||
CommandData $commandData
|
||||
) {
|
||||
// Validators return any object to signal a validation error;
|
||||
// if the return an array, it replaces the arguments.
|
||||
$validateDispatcher = new ValidateHookDispatcher($this->hookManager(), $names);
|
||||
$validated = $validateDispatcher->validate($commandData);
|
||||
if (is_object($validated)) {
|
||||
return $validated;
|
||||
}
|
||||
|
||||
$replaceDispatcher = new ReplaceCommandHookDispatcher($this->hookManager(), $names);
|
||||
if ($this->logger) {
|
||||
$replaceDispatcher->setLogger($this->logger);
|
||||
}
|
||||
if ($replaceDispatcher->hasReplaceCommandHook()) {
|
||||
$commandCallback = $replaceDispatcher->getReplacementCommand($commandData);
|
||||
}
|
||||
|
||||
// Run the command, alter the results, and then handle output and status
|
||||
$result = $this->runCommandCallback($commandCallback, $commandData);
|
||||
return $this->processResults($names, $result, $commandData);
|
||||
}
|
||||
|
||||
public function processResults($names, $result, CommandData $commandData)
|
||||
{
|
||||
$processDispatcher = new ProcessResultHookDispatcher($this->hookManager(), $names);
|
||||
return $processDispatcher->process($result, $commandData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the result output and status code calculation.
|
||||
*/
|
||||
public function handleResults(OutputInterface $output, $names, $result, CommandData $commandData)
|
||||
{
|
||||
$statusCodeDispatcher = new StatusDeterminerHookDispatcher($this->hookManager(), $names);
|
||||
$status = $statusCodeDispatcher->determineStatusCode($result);
|
||||
// If the result is an integer and no separate status code was provided, then use the result as the status and do no output.
|
||||
if (is_integer($result) && !isset($status)) {
|
||||
return $result;
|
||||
}
|
||||
$status = $this->interpretStatusCode($status);
|
||||
|
||||
// Get the structured output, the output stream and the formatter
|
||||
$extractDispatcher = new ExtracterHookDispatcher($this->hookManager(), $names);
|
||||
$structuredOutput = $extractDispatcher->extractOutput($result);
|
||||
$output = $this->chooseOutputStream($output, $status);
|
||||
if ($status != 0) {
|
||||
return $this->writeErrorMessage($output, $status, $structuredOutput, $result);
|
||||
}
|
||||
if ($this->dataCanBeFormatted($structuredOutput) && isset($this->formatterManager)) {
|
||||
return $this->writeUsingFormatter($output, $structuredOutput, $commandData);
|
||||
}
|
||||
return $this->writeCommandOutput($output, $structuredOutput);
|
||||
}
|
||||
|
||||
protected function dataCanBeFormatted($structuredOutput)
|
||||
{
|
||||
if (!isset($this->formatterManager)) {
|
||||
return false;
|
||||
}
|
||||
return
|
||||
is_object($structuredOutput) ||
|
||||
is_array($structuredOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the main command callback
|
||||
*/
|
||||
protected function runCommandCallback($commandCallback, CommandData $commandData)
|
||||
{
|
||||
$result = false;
|
||||
try {
|
||||
$args = $commandData->getArgsAndOptions();
|
||||
$result = call_user_func_array($commandCallback, $args);
|
||||
} catch (\Exception $e) {
|
||||
$result = $this->commandErrorForException($e);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the formatter that should be used to render
|
||||
* output.
|
||||
*
|
||||
* If the user specified a format via the --format option,
|
||||
* then always return that. Otherwise, return the default
|
||||
* format, unless --pipe was specified, in which case
|
||||
* return the default pipe format, format-pipe.
|
||||
*
|
||||
* n.b. --pipe is a handy option introduced in Drush 2
|
||||
* (or perhaps even Drush 1) that indicates that the command
|
||||
* should select the output format that is most appropriate
|
||||
* for use in scripts (e.g. to pipe to another command).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getFormat(FormatterOptions $options)
|
||||
{
|
||||
// In Symfony Console, there is no way for us to differentiate
|
||||
// between the user specifying '--format=table', and the user
|
||||
// not specifying --format when the default value is 'table'.
|
||||
// Therefore, we must make --field always override --format; it
|
||||
// cannot become the default value for --format.
|
||||
if ($options->get('field')) {
|
||||
return 'string';
|
||||
}
|
||||
$defaults = [];
|
||||
if ($options->get('pipe')) {
|
||||
return $options->get('pipe-format', [], 'tsv');
|
||||
}
|
||||
return $options->getFormat($defaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether we should use stdout or stderr.
|
||||
*/
|
||||
protected function chooseOutputStream(OutputInterface $output, $status)
|
||||
{
|
||||
// If the status code indicates an error, then print the
|
||||
// result to stderr rather than stdout
|
||||
if ($status && ($output instanceof ConsoleOutputInterface)) {
|
||||
return $output->getErrorOutput();
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the formatter to output the provided data.
|
||||
*/
|
||||
protected function writeUsingFormatter(OutputInterface $output, $structuredOutput, CommandData $commandData)
|
||||
{
|
||||
$formatterOptions = $this->createFormatterOptions($commandData);
|
||||
$format = $this->getFormat($formatterOptions);
|
||||
$this->formatterManager->write(
|
||||
$output,
|
||||
$format,
|
||||
$structuredOutput,
|
||||
$formatterOptions
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a FormatterOptions object for use in writing the formatted output.
|
||||
* @param CommandData $commandData
|
||||
* @return FormatterOptions
|
||||
*/
|
||||
protected function createFormatterOptions($commandData)
|
||||
{
|
||||
$options = $commandData->input()->getOptions();
|
||||
$formatterOptions = new FormatterOptions($commandData->annotationData()->getArrayCopy(), $options);
|
||||
foreach ($this->prepareOptionsList as $preparer) {
|
||||
$preparer->prepare($commandData, $formatterOptions);
|
||||
}
|
||||
return $formatterOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
* @param OutputInterface $output
|
||||
* @param int $status
|
||||
* @param string $structuredOutput
|
||||
* @param mixed $originalResult
|
||||
* @return type
|
||||
*/
|
||||
protected function writeErrorMessage($output, $status, $structuredOutput, $originalResult)
|
||||
{
|
||||
if (isset($this->displayErrorFunction)) {
|
||||
call_user_func($this->displayErrorFunction, $output, $structuredOutput, $status, $originalResult);
|
||||
} else {
|
||||
$this->writeCommandOutput($output, $structuredOutput);
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the result object is a string, then print it.
|
||||
*/
|
||||
protected function writeCommandOutput(
|
||||
OutputInterface $output,
|
||||
$structuredOutput
|
||||
) {
|
||||
// If there is no formatter, we will print strings,
|
||||
// but can do no more than that.
|
||||
if (is_string($structuredOutput)) {
|
||||
$output->writeln($structuredOutput);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a status code was set, then return it; otherwise,
|
||||
* presume success.
|
||||
*/
|
||||
protected function interpretStatusCode($status)
|
||||
{
|
||||
if (isset($status)) {
|
||||
return $status;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
20
vendor/consolidation/annotated-command/src/Events/CustomEventAwareInterface.php
vendored
Normal file
20
vendor/consolidation/annotated-command/src/Events/CustomEventAwareInterface.php
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Events;
|
||||
|
||||
use Consolidation\AnnotatedCommand\Hooks\HookManager;
|
||||
|
||||
interface CustomEventAwareInterface
|
||||
{
|
||||
/**
|
||||
* Set a reference to the hook manager for later use
|
||||
* @param HookManager $hookManager
|
||||
*/
|
||||
public function setHookManager(HookManager $hookManager);
|
||||
|
||||
/**
|
||||
* Get all of the defined event handlers of the specified name.
|
||||
* @param string $eventName
|
||||
* @return Callable[]
|
||||
*/
|
||||
public function getCustomEventHandlers($eventName);
|
||||
}
|
29
vendor/consolidation/annotated-command/src/Events/CustomEventAwareTrait.php
vendored
Normal file
29
vendor/consolidation/annotated-command/src/Events/CustomEventAwareTrait.php
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Events;
|
||||
|
||||
use Consolidation\AnnotatedCommand\Hooks\HookManager;
|
||||
|
||||
trait CustomEventAwareTrait
|
||||
{
|
||||
/** var HookManager */
|
||||
protected $hookManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setHookManager(HookManager $hookManager)
|
||||
{
|
||||
$this->hookManager = $hookManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCustomEventHandlers($eventName)
|
||||
{
|
||||
if (!$this->hookManager) {
|
||||
return [];
|
||||
}
|
||||
return $this->hookManager->getHook($eventName, HookManager::ON_EVENT);
|
||||
}
|
||||
}
|
12
vendor/consolidation/annotated-command/src/ExitCodeInterface.php
vendored
Normal file
12
vendor/consolidation/annotated-command/src/ExitCodeInterface.php
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand;
|
||||
|
||||
/**
|
||||
* If an annotated command method encounters an error, then it
|
||||
* should either throw an exception, or return a result object
|
||||
* that implements ExitCodeInterface.
|
||||
*/
|
||||
interface ExitCodeInterface
|
||||
{
|
||||
public function getExitCode();
|
||||
}
|
48
vendor/consolidation/annotated-command/src/Help/HelpCommand.php
vendored
Normal file
48
vendor/consolidation/annotated-command/src/Help/HelpCommand.php
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Help;
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Descriptor\XmlDescriptor;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class HelpCommand
|
||||
{
|
||||
/** var Application */
|
||||
protected $application;
|
||||
|
||||
/**
|
||||
* Create a help document from a Symfony Console command
|
||||
*/
|
||||
public function __construct(Application $application)
|
||||
{
|
||||
$this->application = $application;
|
||||
}
|
||||
|
||||
public function getApplication()
|
||||
{
|
||||
return $this->application;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the help command
|
||||
*
|
||||
* @command my-help
|
||||
* @return \Consolidation\AnnotatedCommand\Help\HelpDocument
|
||||
*/
|
||||
public function help($commandName = 'help')
|
||||
{
|
||||
$command = $this->getApplication()->find($commandName);
|
||||
|
||||
$helpDocument = $this->getHelpDocument($command);
|
||||
return $helpDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a help document.
|
||||
*/
|
||||
protected function getHelpDocument($command)
|
||||
{
|
||||
return new HelpDocument($command);
|
||||
}
|
||||
}
|
65
vendor/consolidation/annotated-command/src/Help/HelpDocument.php
vendored
Normal file
65
vendor/consolidation/annotated-command/src/Help/HelpDocument.php
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Help;
|
||||
|
||||
use Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Descriptor\XmlDescriptor;
|
||||
|
||||
class HelpDocument implements DomDataInterface
|
||||
{
|
||||
/** var Command */
|
||||
protected $command;
|
||||
|
||||
/** var \DOMDocument */
|
||||
protected $dom;
|
||||
|
||||
/**
|
||||
* Create a help document from a Symfony Console command
|
||||
*/
|
||||
public function __construct(Command $command)
|
||||
{
|
||||
$dom = $this->generateBaseHelpDom($command);
|
||||
$dom = $this->alterHelpDocument($command, $dom);
|
||||
|
||||
$this->command = $command;
|
||||
$this->dom = $dom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert data into a \DomDocument.
|
||||
*
|
||||
* @return \DomDocument
|
||||
*/
|
||||
public function getDomData()
|
||||
{
|
||||
return $this->dom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the base help DOM prior to alteration by the Command object.
|
||||
* @param Command $command
|
||||
* @return \DomDocument
|
||||
*/
|
||||
protected function generateBaseHelpDom(Command $command)
|
||||
{
|
||||
// Use Symfony to generate xml text. If other formats are
|
||||
// requested, convert from xml to the desired form.
|
||||
$descriptor = new XmlDescriptor();
|
||||
return $descriptor->getCommandDocument($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the DOM document per the command object
|
||||
* @param Command $command
|
||||
* @param \DomDocument $dom
|
||||
* @return \DomDocument
|
||||
*/
|
||||
protected function alterHelpDocument(Command $command, \DomDocument $dom)
|
||||
{
|
||||
if ($command instanceof HelpDocumentAlter) {
|
||||
$dom = $command->helpAlter($dom);
|
||||
}
|
||||
return $dom;
|
||||
}
|
||||
}
|
7
vendor/consolidation/annotated-command/src/Help/HelpDocumentAlter.php
vendored
Normal file
7
vendor/consolidation/annotated-command/src/Help/HelpDocumentAlter.php
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Help;
|
||||
|
||||
interface HelpDocumentAlter
|
||||
{
|
||||
public function helpAlter(\DomDocument $dom);
|
||||
}
|
12
vendor/consolidation/annotated-command/src/Hooks/AlterResultInterface.php
vendored
Normal file
12
vendor/consolidation/annotated-command/src/Hooks/AlterResultInterface.php
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Hooks;
|
||||
|
||||
/**
|
||||
* Alter the result of a command after it has been processed.
|
||||
* An alter result interface isa process result interface.
|
||||
*
|
||||
* @see HookManager::addAlterResult()
|
||||
*/
|
||||
interface AlterResultInterface extends ProcessResultInterface
|
||||
{
|
||||
}
|
37
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/CommandEventHookDispatcher.php
vendored
Normal file
37
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/CommandEventHookDispatcher.php
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
|
||||
|
||||
use Consolidation\AnnotatedCommand\Hooks\HookManager;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\ConsoleEvents;
|
||||
use Symfony\Component\Console\Event\ConsoleCommandEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* Call hooks
|
||||
*/
|
||||
class CommandEventHookDispatcher extends HookDispatcher
|
||||
{
|
||||
/**
|
||||
* @param ConsoleCommandEvent $event
|
||||
*/
|
||||
public function callCommandEventHooks(ConsoleCommandEvent $event)
|
||||
{
|
||||
$hooks = [
|
||||
HookManager::PRE_COMMAND_EVENT,
|
||||
HookManager::COMMAND_EVENT,
|
||||
HookManager::POST_COMMAND_EVENT
|
||||
];
|
||||
$commandEventHooks = $this->getHooks($hooks);
|
||||
foreach ($commandEventHooks as $commandEvent) {
|
||||
if ($commandEvent instanceof EventDispatcherInterface) {
|
||||
$commandEvent->dispatch(ConsoleEvents::COMMAND, $event);
|
||||
}
|
||||
if (is_callable($commandEvent)) {
|
||||
$commandEvent($event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
47
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ExtracterHookDispatcher.php
vendored
Normal file
47
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ExtracterHookDispatcher.php
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
|
||||
|
||||
use Consolidation\AnnotatedCommand\Hooks\ExtractOutputInterface;
|
||||
use Consolidation\AnnotatedCommand\Hooks\HookManager;
|
||||
use Consolidation\AnnotatedCommand\OutputDataInterface;
|
||||
|
||||
/**
|
||||
* Call hooks
|
||||
*/
|
||||
class ExtracterHookDispatcher extends HookDispatcher implements ExtractOutputInterface
|
||||
{
|
||||
/**
|
||||
* Convert the result object to printable output in
|
||||
* structured form.
|
||||
*/
|
||||
public function extractOutput($result)
|
||||
{
|
||||
if ($result instanceof OutputDataInterface) {
|
||||
return $result->getOutputData();
|
||||
}
|
||||
|
||||
$hooks = [
|
||||
HookManager::EXTRACT_OUTPUT,
|
||||
];
|
||||
$extractors = $this->getHooks($hooks);
|
||||
foreach ($extractors as $extractor) {
|
||||
$structuredOutput = $this->callExtractor($extractor, $result);
|
||||
if (isset($structuredOutput)) {
|
||||
return $structuredOutput;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function callExtractor($extractor, $result)
|
||||
{
|
||||
if ($extractor instanceof ExtractOutputInterface) {
|
||||
return $extractor->extractOutput($result);
|
||||
}
|
||||
if (is_callable($extractor)) {
|
||||
return $extractor($result);
|
||||
}
|
||||
}
|
||||
}
|
27
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/HookDispatcher.php
vendored
Normal file
27
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/HookDispatcher.php
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
|
||||
|
||||
use Consolidation\AnnotatedCommand\Hooks\HookManager;
|
||||
use Consolidation\AnnotatedCommand\AnnotationData;
|
||||
|
||||
/**
|
||||
* Call hooks
|
||||
*/
|
||||
class HookDispatcher
|
||||
{
|
||||
/** var HookManager */
|
||||
protected $hookManager;
|
||||
protected $names;
|
||||
|
||||
public function __construct(HookManager $hookManager, $names)
|
||||
{
|
||||
$this->hookManager = $hookManager;
|
||||
$this->names = $names;
|
||||
}
|
||||
|
||||
public function getHooks($hooks, $annotationData = null)
|
||||
{
|
||||
return $this->hookManager->getHooks($this->names, $hooks, $annotationData);
|
||||
}
|
||||
}
|
40
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/InitializeHookDispatcher.php
vendored
Normal file
40
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/InitializeHookDispatcher.php
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
|
||||
|
||||
use Consolidation\AnnotatedCommand\Hooks\HookManager;
|
||||
use Consolidation\AnnotatedCommand\AnnotationData;
|
||||
use Consolidation\AnnotatedCommand\Hooks\InitializeHookInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
|
||||
/**
|
||||
* Call hooks
|
||||
*/
|
||||
class InitializeHookDispatcher extends HookDispatcher implements InitializeHookInterface
|
||||
{
|
||||
public function initialize(
|
||||
InputInterface $input,
|
||||
AnnotationData $annotationData
|
||||
) {
|
||||
$hooks = [
|
||||
HookManager::PRE_INITIALIZE,
|
||||
HookManager::INITIALIZE,
|
||||
HookManager::POST_INITIALIZE
|
||||
];
|
||||
$providers = $this->getHooks($hooks, $annotationData);
|
||||
foreach ($providers as $provider) {
|
||||
$this->callInitializeHook($provider, $input, $annotationData);
|
||||
}
|
||||
}
|
||||
|
||||
protected function callInitializeHook($provider, $input, AnnotationData $annotationData)
|
||||
{
|
||||
if ($provider instanceof InitializeHookInterface) {
|
||||
return $provider->initialize($input, $annotationData);
|
||||
}
|
||||
if (is_callable($provider)) {
|
||||
return $provider($input, $annotationData);
|
||||
}
|
||||
}
|
||||
}
|
41
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/InteractHookDispatcher.php
vendored
Normal file
41
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/InteractHookDispatcher.php
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
|
||||
|
||||
use Consolidation\AnnotatedCommand\AnnotationData;
|
||||
use Consolidation\AnnotatedCommand\Hooks\HookManager;
|
||||
use Consolidation\AnnotatedCommand\Hooks\InteractorInterface;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Call hooks
|
||||
*/
|
||||
class InteractHookDispatcher extends HookDispatcher
|
||||
{
|
||||
public function interact(
|
||||
InputInterface $input,
|
||||
OutputInterface $output,
|
||||
AnnotationData $annotationData
|
||||
) {
|
||||
$hooks = [
|
||||
HookManager::PRE_INTERACT,
|
||||
HookManager::INTERACT,
|
||||
HookManager::POST_INTERACT
|
||||
];
|
||||
$interactors = $this->getHooks($hooks, $annotationData);
|
||||
foreach ($interactors as $interactor) {
|
||||
$this->callInteractor($interactor, $input, $output, $annotationData);
|
||||
}
|
||||
}
|
||||
|
||||
protected function callInteractor($interactor, $input, $output, AnnotationData $annotationData)
|
||||
{
|
||||
if ($interactor instanceof InteractorInterface) {
|
||||
return $interactor->interact($input, $output, $annotationData);
|
||||
}
|
||||
if (is_callable($interactor)) {
|
||||
return $interactor($input, $output, $annotationData);
|
||||
}
|
||||
}
|
||||
}
|
44
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/OptionsHookDispatcher.php
vendored
Normal file
44
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/OptionsHookDispatcher.php
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Consolidation\AnnotatedCommand\AnnotatedCommand;
|
||||
use Consolidation\AnnotatedCommand\AnnotationData;
|
||||
use Consolidation\AnnotatedCommand\Hooks\HookManager;
|
||||
use Consolidation\AnnotatedCommand\Hooks\OptionHookInterface;
|
||||
|
||||
/**
|
||||
* Call hooks
|
||||
*/
|
||||
class OptionsHookDispatcher extends HookDispatcher implements OptionHookInterface
|
||||
{
|
||||
public function getOptions(
|
||||
Command $command,
|
||||
AnnotationData $annotationData
|
||||
) {
|
||||
$hooks = [
|
||||
HookManager::PRE_OPTION_HOOK,
|
||||
HookManager::OPTION_HOOK,
|
||||
HookManager::POST_OPTION_HOOK
|
||||
];
|
||||
$optionHooks = $this->getHooks($hooks, $annotationData);
|
||||
foreach ($optionHooks as $optionHook) {
|
||||
$this->callOptionHook($optionHook, $command, $annotationData);
|
||||
}
|
||||
$commandInfoList = $this->hookManager->getHookOptionsForCommand($command);
|
||||
if ($command instanceof AnnotatedCommand) {
|
||||
$command->optionsHookForHookAnnotations($commandInfoList);
|
||||
}
|
||||
}
|
||||
|
||||
protected function callOptionHook($optionHook, $command, AnnotationData $annotationData)
|
||||
{
|
||||
if ($optionHook instanceof OptionHookInterface) {
|
||||
return $optionHook->getOptions($command, $annotationData);
|
||||
}
|
||||
if (is_callable($optionHook)) {
|
||||
return $optionHook($command, $annotationData);
|
||||
}
|
||||
}
|
||||
}
|
53
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ProcessResultHookDispatcher.php
vendored
Normal file
53
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ProcessResultHookDispatcher.php
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
|
||||
|
||||
use Consolidation\AnnotatedCommand\AnnotationData;
|
||||
use Consolidation\AnnotatedCommand\CommandData;
|
||||
use Consolidation\AnnotatedCommand\Hooks\HookManager;
|
||||
use Consolidation\AnnotatedCommand\Hooks\ProcessResultInterface;
|
||||
|
||||
/**
|
||||
* Call hooks
|
||||
*/
|
||||
class ProcessResultHookDispatcher extends HookDispatcher implements ProcessResultInterface
|
||||
{
|
||||
/**
|
||||
* Process result and decide what to do with it.
|
||||
* Allow client to add transformation / interpretation
|
||||
* callbacks.
|
||||
*/
|
||||
public function process($result, CommandData $commandData)
|
||||
{
|
||||
$hooks = [
|
||||
HookManager::PRE_PROCESS_RESULT,
|
||||
HookManager::PROCESS_RESULT,
|
||||
HookManager::POST_PROCESS_RESULT,
|
||||
HookManager::PRE_ALTER_RESULT,
|
||||
HookManager::ALTER_RESULT,
|
||||
HookManager::POST_ALTER_RESULT,
|
||||
HookManager::POST_COMMAND_HOOK,
|
||||
];
|
||||
$processors = $this->getHooks($hooks, $commandData->annotationData());
|
||||
foreach ($processors as $processor) {
|
||||
$result = $this->callProcessor($processor, $result, $commandData);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function callProcessor($processor, $result, CommandData $commandData)
|
||||
{
|
||||
$processed = null;
|
||||
if ($processor instanceof ProcessResultInterface) {
|
||||
$processed = $processor->process($result, $commandData);
|
||||
}
|
||||
if (is_callable($processor)) {
|
||||
$processed = $processor($result, $commandData);
|
||||
}
|
||||
if (isset($processed)) {
|
||||
return $processed;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
66
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ReplaceCommandHookDispatcher.php
vendored
Normal file
66
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ReplaceCommandHookDispatcher.php
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
|
||||
|
||||
use Consolidation\AnnotatedCommand\CommandData;
|
||||
use Consolidation\AnnotatedCommand\Hooks\HookManager;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* Call hooks.
|
||||
*/
|
||||
class ReplaceCommandHookDispatcher extends HookDispatcher implements LoggerAwareInterface
|
||||
{
|
||||
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function hasReplaceCommandHook()
|
||||
{
|
||||
return (bool) count($this->getReplaceCommandHooks());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \callable[]
|
||||
*/
|
||||
public function getReplaceCommandHooks()
|
||||
{
|
||||
$hooks = [
|
||||
HookManager::REPLACE_COMMAND_HOOK,
|
||||
];
|
||||
$replaceCommandHooks = $this->getHooks($hooks);
|
||||
|
||||
return $replaceCommandHooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Consolidation\AnnotatedCommand\CommandData $commandData
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
public function getReplacementCommand(CommandData $commandData)
|
||||
{
|
||||
$replaceCommandHooks = $this->getReplaceCommandHooks();
|
||||
|
||||
// We only take the first hook implementation of "replace-command" as the replacement. Commands shouldn't have
|
||||
// more than one replacement.
|
||||
$replacementCommand = reset($replaceCommandHooks);
|
||||
|
||||
if ($this->logger && count($replaceCommandHooks) > 1) {
|
||||
$command_name = $commandData->annotationData()->get('command', 'unknown');
|
||||
$message = "Multiple implementations of the \"replace - command\" hook exist for the \"$command_name\" command.\n";
|
||||
foreach ($replaceCommandHooks as $replaceCommandHook) {
|
||||
$class = get_class($replaceCommandHook[0]);
|
||||
$method = $replaceCommandHook[1];
|
||||
$hook_name = "$class->$method";
|
||||
$message .= " - $hook_name\n";
|
||||
}
|
||||
$this->logger->warning($message);
|
||||
}
|
||||
|
||||
return $replacementCommand;
|
||||
}
|
||||
}
|
51
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/StatusDeterminerHookDispatcher.php
vendored
Normal file
51
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/StatusDeterminerHookDispatcher.php
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
|
||||
|
||||
use Consolidation\AnnotatedCommand\ExitCodeInterface;
|
||||
use Consolidation\AnnotatedCommand\Hooks\HookManager;
|
||||
use Consolidation\AnnotatedCommand\Hooks\StatusDeterminerInterface;
|
||||
|
||||
/**
|
||||
* Call hooks
|
||||
*/
|
||||
class StatusDeterminerHookDispatcher extends HookDispatcher implements StatusDeterminerInterface
|
||||
{
|
||||
/**
|
||||
* Call all status determiners, and see if any of them
|
||||
* know how to convert to a status code.
|
||||
*/
|
||||
public function determineStatusCode($result)
|
||||
{
|
||||
// If the result (post-processing) is an object that
|
||||
// implements ExitCodeInterface, then we will ask it
|
||||
// to give us the status code.
|
||||
if ($result instanceof ExitCodeInterface) {
|
||||
return $result->getExitCode();
|
||||
}
|
||||
|
||||
$hooks = [
|
||||
HookManager::STATUS_DETERMINER,
|
||||
];
|
||||
// If the result does not implement ExitCodeInterface,
|
||||
// then we'll see if there is a determiner that can
|
||||
// extract a status code from the result.
|
||||
$determiners = $this->getHooks($hooks);
|
||||
foreach ($determiners as $determiner) {
|
||||
$status = $this->callDeterminer($determiner, $result);
|
||||
if (isset($status)) {
|
||||
return $status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function callDeterminer($determiner, $result)
|
||||
{
|
||||
if ($determiner instanceof StatusDeterminerInterface) {
|
||||
return $determiner->determineStatusCode($result);
|
||||
}
|
||||
if (is_callable($determiner)) {
|
||||
return $determiner($result);
|
||||
}
|
||||
}
|
||||
}
|
46
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ValidateHookDispatcher.php
vendored
Normal file
46
vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ValidateHookDispatcher.php
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
|
||||
|
||||
use Consolidation\AnnotatedCommand\AnnotationData;
|
||||
use Consolidation\AnnotatedCommand\CommandData;
|
||||
use Consolidation\AnnotatedCommand\CommandError;
|
||||
use Consolidation\AnnotatedCommand\Hooks\HookManager;
|
||||
use Consolidation\AnnotatedCommand\Hooks\ValidatorInterface;
|
||||
|
||||
/**
|
||||
* Call hooks
|
||||
*/
|
||||
class ValidateHookDispatcher extends HookDispatcher implements ValidatorInterface
|
||||
{
|
||||
public function validate(CommandData $commandData)
|
||||
{
|
||||
$hooks = [
|
||||
HookManager::PRE_ARGUMENT_VALIDATOR,
|
||||
HookManager::ARGUMENT_VALIDATOR,
|
||||
HookManager::POST_ARGUMENT_VALIDATOR,
|
||||
HookManager::PRE_COMMAND_HOOK,
|
||||
HookManager::COMMAND_HOOK,
|
||||
];
|
||||
$validators = $this->getHooks($hooks, $commandData->annotationData());
|
||||
foreach ($validators as $validator) {
|
||||
$validated = $this->callValidator($validator, $commandData);
|
||||
if ($validated === false) {
|
||||
return new CommandError();
|
||||
}
|
||||
if (is_object($validated)) {
|
||||
return $validated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function callValidator($validator, CommandData $commandData)
|
||||
{
|
||||
if ($validator instanceof ValidatorInterface) {
|
||||
return $validator->validate($commandData);
|
||||
}
|
||||
if (is_callable($validator)) {
|
||||
return $validator($commandData);
|
||||
}
|
||||
}
|
||||
}
|
14
vendor/consolidation/annotated-command/src/Hooks/ExtractOutputInterface.php
vendored
Normal file
14
vendor/consolidation/annotated-command/src/Hooks/ExtractOutputInterface.php
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Hooks;
|
||||
|
||||
/**
|
||||
* Extract Output hooks are used to select the particular
|
||||
* data elements of the result that should be printed as
|
||||
* the command output -- perhaps after being formatted.
|
||||
*
|
||||
* @see HookManager::addOutputExtractor()
|
||||
*/
|
||||
interface ExtractOutputInterface
|
||||
{
|
||||
public function extractOutput($result);
|
||||
}
|
434
vendor/consolidation/annotated-command/src/Hooks/HookManager.php
vendored
Normal file
434
vendor/consolidation/annotated-command/src/Hooks/HookManager.php
vendored
Normal file
|
@ -0,0 +1,434 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Hooks;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use Symfony\Component\Console\ConsoleEvents;
|
||||
use Symfony\Component\Console\Event\ConsoleCommandEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
|
||||
use Consolidation\AnnotatedCommand\ExitCodeInterface;
|
||||
use Consolidation\AnnotatedCommand\OutputDataInterface;
|
||||
use Consolidation\AnnotatedCommand\AnnotationData;
|
||||
use Consolidation\AnnotatedCommand\CommandData;
|
||||
use Consolidation\AnnotatedCommand\CommandError;
|
||||
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\CommandEventHookDispatcher;
|
||||
|
||||
/**
|
||||
* Manage named callback hooks
|
||||
*/
|
||||
class HookManager implements EventSubscriberInterface
|
||||
{
|
||||
protected $hooks = [];
|
||||
/** var CommandInfo[] */
|
||||
protected $hookOptions = [];
|
||||
|
||||
const REPLACE_COMMAND_HOOK = 'replace-command';
|
||||
const PRE_COMMAND_EVENT = 'pre-command-event';
|
||||
const COMMAND_EVENT = 'command-event';
|
||||
const POST_COMMAND_EVENT = 'post-command-event';
|
||||
const PRE_OPTION_HOOK = 'pre-option';
|
||||
const OPTION_HOOK = 'option';
|
||||
const POST_OPTION_HOOK = 'post-option';
|
||||
const PRE_INITIALIZE = 'pre-init';
|
||||
const INITIALIZE = 'init';
|
||||
const POST_INITIALIZE = 'post-init';
|
||||
const PRE_INTERACT = 'pre-interact';
|
||||
const INTERACT = 'interact';
|
||||
const POST_INTERACT = 'post-interact';
|
||||
const PRE_ARGUMENT_VALIDATOR = 'pre-validate';
|
||||
const ARGUMENT_VALIDATOR = 'validate';
|
||||
const POST_ARGUMENT_VALIDATOR = 'post-validate';
|
||||
const PRE_COMMAND_HOOK = 'pre-command';
|
||||
const COMMAND_HOOK = 'command';
|
||||
const POST_COMMAND_HOOK = 'post-command';
|
||||
const PRE_PROCESS_RESULT = 'pre-process';
|
||||
const PROCESS_RESULT = 'process';
|
||||
const POST_PROCESS_RESULT = 'post-process';
|
||||
const PRE_ALTER_RESULT = 'pre-alter';
|
||||
const ALTER_RESULT = 'alter';
|
||||
const POST_ALTER_RESULT = 'post-alter';
|
||||
const STATUS_DETERMINER = 'status';
|
||||
const EXTRACT_OUTPUT = 'extract';
|
||||
const ON_EVENT = 'on-event';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function getAllHooks()
|
||||
{
|
||||
return $this->hooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a hook
|
||||
*
|
||||
* @param mixed $callback The callback function to call
|
||||
* @param string $hook The name of the hook to add
|
||||
* @param string $name The name of the command to hook
|
||||
* ('*' for all)
|
||||
*/
|
||||
public function add(callable $callback, $hook, $name = '*')
|
||||
{
|
||||
if (empty($name)) {
|
||||
$name = static::getClassNameFromCallback($callback);
|
||||
}
|
||||
$this->hooks[$name][$hook][] = $callback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function recordHookOptions($commandInfo, $name)
|
||||
{
|
||||
$this->hookOptions[$name][] = $commandInfo;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public static function getNames($command, $callback)
|
||||
{
|
||||
return array_filter(
|
||||
array_merge(
|
||||
static::getNamesUsingCommands($command),
|
||||
[static::getClassNameFromCallback($callback)]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
protected static function getNamesUsingCommands($command)
|
||||
{
|
||||
return array_merge(
|
||||
[$command->getName()],
|
||||
$command->getAliases()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a command hook does not specify any particular command
|
||||
* name that it should be attached to, then it will be applied
|
||||
* to every command that is defined in the same class as the hook.
|
||||
* This is controlled by using the namespace + class name of
|
||||
* the implementing class of the callback hook.
|
||||
*/
|
||||
protected static function getClassNameFromCallback($callback)
|
||||
{
|
||||
if (!is_array($callback)) {
|
||||
return '';
|
||||
}
|
||||
$reflectionClass = new \ReflectionClass($callback[0]);
|
||||
return $reflectionClass->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a replace command hook
|
||||
*
|
||||
* @param type ReplaceCommandHookInterface $provider
|
||||
* @param type string $command_name The name of the command to replace
|
||||
*/
|
||||
public function addReplaceCommandHook(ReplaceCommandHookInterface $replaceCommandHook, $name)
|
||||
{
|
||||
$this->hooks[$name][self::REPLACE_COMMAND_HOOK][] = $replaceCommandHook;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addPreCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*')
|
||||
{
|
||||
$this->hooks[$name][self::PRE_COMMAND_EVENT][] = $eventDispatcher;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*')
|
||||
{
|
||||
$this->hooks[$name][self::COMMAND_EVENT][] = $eventDispatcher;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addPostCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*')
|
||||
{
|
||||
$this->hooks[$name][self::POST_COMMAND_EVENT][] = $eventDispatcher;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addCommandEvent(EventSubscriberInterface $eventSubscriber)
|
||||
{
|
||||
// Wrap the event subscriber in a dispatcher and add it
|
||||
$dispatcher = new EventDispatcher();
|
||||
$dispatcher->addSubscriber($eventSubscriber);
|
||||
return $this->addCommandEventDispatcher($dispatcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an configuration provider hook
|
||||
*
|
||||
* @param type InitializeHookInterface $provider
|
||||
* @param type $name The name of the command to hook
|
||||
* ('*' for all)
|
||||
*/
|
||||
public function addInitializeHook(InitializeHookInterface $initializeHook, $name = '*')
|
||||
{
|
||||
$this->hooks[$name][self::INITIALIZE][] = $initializeHook;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an option hook
|
||||
*
|
||||
* @param type ValidatorInterface $validator
|
||||
* @param type $name The name of the command to hook
|
||||
* ('*' for all)
|
||||
*/
|
||||
public function addOptionHook(OptionHookInterface $interactor, $name = '*')
|
||||
{
|
||||
$this->hooks[$name][self::INTERACT][] = $interactor;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an interact hook
|
||||
*
|
||||
* @param type ValidatorInterface $validator
|
||||
* @param type $name The name of the command to hook
|
||||
* ('*' for all)
|
||||
*/
|
||||
public function addInteractor(InteractorInterface $interactor, $name = '*')
|
||||
{
|
||||
$this->hooks[$name][self::INTERACT][] = $interactor;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a pre-validator hook
|
||||
*
|
||||
* @param type ValidatorInterface $validator
|
||||
* @param type $name The name of the command to hook
|
||||
* ('*' for all)
|
||||
*/
|
||||
public function addPreValidator(ValidatorInterface $validator, $name = '*')
|
||||
{
|
||||
$this->hooks[$name][self::PRE_ARGUMENT_VALIDATOR][] = $validator;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a validator hook
|
||||
*
|
||||
* @param type ValidatorInterface $validator
|
||||
* @param type $name The name of the command to hook
|
||||
* ('*' for all)
|
||||
*/
|
||||
public function addValidator(ValidatorInterface $validator, $name = '*')
|
||||
{
|
||||
$this->hooks[$name][self::ARGUMENT_VALIDATOR][] = $validator;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a pre-command hook. This is the same as a validator hook, except
|
||||
* that it will run after all of the post-validator hooks.
|
||||
*
|
||||
* @param type ValidatorInterface $preCommand
|
||||
* @param type $name The name of the command to hook
|
||||
* ('*' for all)
|
||||
*/
|
||||
public function addPreCommandHook(ValidatorInterface $preCommand, $name = '*')
|
||||
{
|
||||
$this->hooks[$name][self::PRE_COMMAND_HOOK][] = $preCommand;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a post-command hook. This is the same as a pre-process hook,
|
||||
* except that it will run before the first pre-process hook.
|
||||
*
|
||||
* @param type ProcessResultInterface $postCommand
|
||||
* @param type $name The name of the command to hook
|
||||
* ('*' for all)
|
||||
*/
|
||||
public function addPostCommandHook(ProcessResultInterface $postCommand, $name = '*')
|
||||
{
|
||||
$this->hooks[$name][self::POST_COMMAND_HOOK][] = $postCommand;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a result processor.
|
||||
*
|
||||
* @param type ProcessResultInterface $resultProcessor
|
||||
* @param type $name The name of the command to hook
|
||||
* ('*' for all)
|
||||
*/
|
||||
public function addResultProcessor(ProcessResultInterface $resultProcessor, $name = '*')
|
||||
{
|
||||
$this->hooks[$name][self::PROCESS_RESULT][] = $resultProcessor;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a result alterer. After a result is processed
|
||||
* by a result processor, an alter hook may be used
|
||||
* to convert the result from one form to another.
|
||||
*
|
||||
* @param type AlterResultInterface $resultAlterer
|
||||
* @param type $name The name of the command to hook
|
||||
* ('*' for all)
|
||||
*/
|
||||
public function addAlterResult(AlterResultInterface $resultAlterer, $name = '*')
|
||||
{
|
||||
$this->hooks[$name][self::ALTER_RESULT][] = $resultAlterer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a status determiner. Usually, a command should return
|
||||
* an integer on error, or a result object on success (which
|
||||
* implies a status code of zero). If a result contains the
|
||||
* status code in some other field, then a status determiner
|
||||
* can be used to call the appropriate accessor method to
|
||||
* determine the status code. This is usually not necessary,
|
||||
* though; a command that fails may return a CommandError
|
||||
* object, which contains a status code and a result message
|
||||
* to display.
|
||||
* @see CommandError::getExitCode()
|
||||
*
|
||||
* @param type StatusDeterminerInterface $statusDeterminer
|
||||
* @param type $name The name of the command to hook
|
||||
* ('*' for all)
|
||||
*/
|
||||
public function addStatusDeterminer(StatusDeterminerInterface $statusDeterminer, $name = '*')
|
||||
{
|
||||
$this->hooks[$name][self::STATUS_DETERMINER][] = $statusDeterminer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an output extractor. If a command returns an object
|
||||
* object, by default it is passed directly to the output
|
||||
* formatter (if in use) for rendering. If the result object
|
||||
* contains more information than just the data to render, though,
|
||||
* then an output extractor can be used to call the appopriate
|
||||
* accessor method of the result object to get the data to
|
||||
* rendered. This is usually not necessary, though; it is preferable
|
||||
* to have complex result objects implement the OutputDataInterface.
|
||||
* @see OutputDataInterface::getOutputData()
|
||||
*
|
||||
* @param type ExtractOutputInterface $outputExtractor
|
||||
* @param type $name The name of the command to hook
|
||||
* ('*' for all)
|
||||
*/
|
||||
public function addOutputExtractor(ExtractOutputInterface $outputExtractor, $name = '*')
|
||||
{
|
||||
$this->hooks[$name][self::EXTRACT_OUTPUT][] = $outputExtractor;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHookOptionsForCommand($command)
|
||||
{
|
||||
$names = $this->addWildcardHooksToNames($command->getNames(), $command->getAnnotationData());
|
||||
return $this->getHookOptions($names);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CommandInfo[]
|
||||
*/
|
||||
public function getHookOptions($names)
|
||||
{
|
||||
$result = [];
|
||||
foreach ($names as $name) {
|
||||
if (isset($this->hookOptions[$name])) {
|
||||
$result = array_merge($result, $this->hookOptions[$name]);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a set of hooks with the provided name(s). Include the
|
||||
* pre- and post- hooks, and also include the global hooks ('*')
|
||||
* in addition to the named hooks provided.
|
||||
*
|
||||
* @param string|array $names The name of the function being hooked.
|
||||
* @param string[] $hooks A list of hooks (e.g. [HookManager::ALTER_RESULT])
|
||||
*
|
||||
* @return callable[]
|
||||
*/
|
||||
public function getHooks($names, $hooks, $annotationData = null)
|
||||
{
|
||||
return $this->get($this->addWildcardHooksToNames($names, $annotationData), $hooks);
|
||||
}
|
||||
|
||||
protected function addWildcardHooksToNames($names, $annotationData = null)
|
||||
{
|
||||
$names = array_merge(
|
||||
(array)$names,
|
||||
($annotationData == null) ? [] : array_map(function ($item) {
|
||||
return "@$item";
|
||||
}, $annotationData->keys())
|
||||
);
|
||||
$names[] = '*';
|
||||
return array_unique($names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a set of hooks with the provided name(s).
|
||||
*
|
||||
* @param string|array $names The name of the function being hooked.
|
||||
* @param string[] $hooks The list of hook names (e.g. [HookManager::ALTER_RESULT])
|
||||
*
|
||||
* @return callable[]
|
||||
*/
|
||||
public function get($names, $hooks)
|
||||
{
|
||||
$result = [];
|
||||
foreach ((array)$hooks as $hook) {
|
||||
foreach ((array)$names as $name) {
|
||||
$result = array_merge($result, $this->getHook($name, $hook));
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single named hook.
|
||||
*
|
||||
* @param string $name The name of the hooked method
|
||||
* @param string $hook The specific hook name (e.g. alter)
|
||||
*
|
||||
* @return callable[]
|
||||
*/
|
||||
public function getHook($name, $hook)
|
||||
{
|
||||
if (isset($this->hooks[$name][$hook])) {
|
||||
return $this->hooks[$name][$hook];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the command event hooks.
|
||||
*
|
||||
* TODO: This should be moved to CommandEventHookDispatcher, which
|
||||
* should become the class that implements EventSubscriberInterface.
|
||||
* This change would break all clients, though, so postpone until next
|
||||
* major release.
|
||||
*
|
||||
* @param ConsoleCommandEvent $event
|
||||
*/
|
||||
public function callCommandEventHooks(ConsoleCommandEvent $event)
|
||||
{
|
||||
/* @var Command $command */
|
||||
$command = $event->getCommand();
|
||||
$dispatcher = new CommandEventHookDispatcher($this, [$command->getName()]);
|
||||
$dispatcher->callCommandEventHooks($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @{@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [ConsoleEvents::COMMAND => 'callCommandEventHooks'];
|
||||
}
|
||||
}
|
15
vendor/consolidation/annotated-command/src/Hooks/InitializeHookInterface.php
vendored
Normal file
15
vendor/consolidation/annotated-command/src/Hooks/InitializeHookInterface.php
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Hooks;
|
||||
|
||||
use Consolidation\AnnotatedCommand\AnnotationData;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
|
||||
/**
|
||||
* Non-interactively (e.g. via configuration files) apply configuration values to the Input object.
|
||||
*
|
||||
* @see HookManager::addInitializeHook()
|
||||
*/
|
||||
interface InitializeHookInterface
|
||||
{
|
||||
public function initialize(InputInterface $input, AnnotationData $annotationData);
|
||||
}
|
18
vendor/consolidation/annotated-command/src/Hooks/InteractorInterface.php
vendored
Normal file
18
vendor/consolidation/annotated-command/src/Hooks/InteractorInterface.php
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Hooks;
|
||||
|
||||
use Consolidation\AnnotatedCommand\AnnotationData;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Interactively supply values for missing required arguments for
|
||||
* the current command. Note that this hook is not called if
|
||||
* the --no-interaction flag is set.
|
||||
*
|
||||
* @see HookManager::addInteractor()
|
||||
*/
|
||||
interface InteractorInterface
|
||||
{
|
||||
public function interact(InputInterface $input, OutputInterface $output, AnnotationData $annotationData);
|
||||
}
|
16
vendor/consolidation/annotated-command/src/Hooks/OptionHookInterface.php
vendored
Normal file
16
vendor/consolidation/annotated-command/src/Hooks/OptionHookInterface.php
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Hooks;
|
||||
|
||||
use Consolidation\AnnotatedCommand\AnnotationData;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
/**
|
||||
* Add options to a command.
|
||||
*
|
||||
* @see HookManager::addOptionHook()
|
||||
* @see AnnotatedCommandFactory::addListener()
|
||||
*/
|
||||
interface OptionHookInterface
|
||||
{
|
||||
public function getOptions(Command $command, AnnotationData $annotationData);
|
||||
}
|
27
vendor/consolidation/annotated-command/src/Hooks/ProcessResultInterface.php
vendored
Normal file
27
vendor/consolidation/annotated-command/src/Hooks/ProcessResultInterface.php
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Hooks;
|
||||
|
||||
use Consolidation\AnnotatedCommand\CommandData;
|
||||
|
||||
/**
|
||||
* A result processor takes a result object, processes it, and
|
||||
* returns another result object. For example, if a result object
|
||||
* represents a 'task', then a task-runner hook could run the
|
||||
* task and return the result from that execution.
|
||||
*
|
||||
* @see HookManager::addResultProcessor()
|
||||
*/
|
||||
interface ProcessResultInterface
|
||||
{
|
||||
/**
|
||||
* After a command has executed, if the result is something
|
||||
* that needs to be processed, e.g. a collection of tasks to
|
||||
* run, then execute it and return the new result.
|
||||
*
|
||||
* @param mixed $result Result to (potentially) be processed
|
||||
* @param CommandData $commandData Reference to commandline arguments and options
|
||||
*
|
||||
* @return mixed $result
|
||||
*/
|
||||
public function process($result, CommandData $commandData);
|
||||
}
|
18
vendor/consolidation/annotated-command/src/Hooks/StatusDeterminerInterface.php
vendored
Normal file
18
vendor/consolidation/annotated-command/src/Hooks/StatusDeterminerInterface.php
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Hooks;
|
||||
|
||||
/**
|
||||
* A StatusDeterminer maps from a result to a status exit code.
|
||||
*
|
||||
* @see HookManager::addStatusDeterminer()
|
||||
*/
|
||||
interface StatusDeterminerInterface
|
||||
{
|
||||
/**
|
||||
* Convert a result object into a status code, if
|
||||
* possible. Return null if the result object is unknown.
|
||||
*
|
||||
* @return null|integer
|
||||
*/
|
||||
public function determineStatusCode($result);
|
||||
}
|
14
vendor/consolidation/annotated-command/src/Hooks/ValidatorInterface.php
vendored
Normal file
14
vendor/consolidation/annotated-command/src/Hooks/ValidatorInterface.php
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Hooks;
|
||||
|
||||
use Consolidation\AnnotatedCommand\CommandData;
|
||||
|
||||
/**
|
||||
* Validate the arguments for the current command.
|
||||
*
|
||||
* @see HookManager::addValidator()
|
||||
*/
|
||||
interface ValidatorInterface
|
||||
{
|
||||
public function validate(CommandData $commandData);
|
||||
}
|
92
vendor/consolidation/annotated-command/src/Options/AlterOptionsCommandEvent.php
vendored
Normal file
92
vendor/consolidation/annotated-command/src/Options/AlterOptionsCommandEvent.php
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Options;
|
||||
|
||||
use Consolidation\AnnotatedCommand\AnnotatedCommand;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\ConsoleEvents;
|
||||
use Symfony\Component\Console\Event\ConsoleCommandEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* AlterOptionsCommandEvent is a subscriber to the Command Event
|
||||
* that looks up any additional options (e.g. from an OPTION_HOOK)
|
||||
* that should be added to the command. Options need to be added
|
||||
* in two circumstances:
|
||||
*
|
||||
* 1. When 'help' for the command is called, so that the additional
|
||||
* command options may be listed in the command description.
|
||||
*
|
||||
* 2. When the command itself is called, so that option validation
|
||||
* may be done.
|
||||
*
|
||||
* We defer the addition of options until these times so that we
|
||||
* do not invoke the option hooks for every command on every run
|
||||
* of the program, and so that we do not need to defer the addition
|
||||
* of all of the application hooks until after all of the application
|
||||
* commands have been added. (Hooks may appear in the same command files
|
||||
* as command implementations; applications may support command file
|
||||
* plug-ins, and hooks may add options to commands defined in other
|
||||
* commandfiles.)
|
||||
*/
|
||||
class AlterOptionsCommandEvent implements EventSubscriberInterface
|
||||
{
|
||||
/** var Application */
|
||||
protected $application;
|
||||
|
||||
public function __construct(Application $application)
|
||||
{
|
||||
$this->application = $application;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ConsoleCommandEvent $event
|
||||
*/
|
||||
public function alterCommandOptions(ConsoleCommandEvent $event)
|
||||
{
|
||||
/* @var Command $command */
|
||||
$command = $event->getCommand();
|
||||
$input = $event->getInput();
|
||||
if ($command->getName() == 'help') {
|
||||
// Symfony 3.x prepares $input for us; Symfony 2.x, on the other
|
||||
// hand, passes it in prior to binding with the command definition,
|
||||
// so we have to go to a little extra work. It may be inadvisable
|
||||
// to do these steps for commands other than 'help'.
|
||||
if (!$input->hasArgument('command_name')) {
|
||||
$command->ignoreValidationErrors();
|
||||
$command->mergeApplicationDefinition();
|
||||
$input->bind($command->getDefinition());
|
||||
}
|
||||
|
||||
// Symfony Console helpfully swaps 'command_name' and 'command'
|
||||
// depending on whether the user entered `help foo` or `--help foo`.
|
||||
// One of these is always `help`, and the other is the command we
|
||||
// are actually interested in.
|
||||
$nameOfCommandToDescribe = $event->getInput()->getArgument('command_name');
|
||||
if ($nameOfCommandToDescribe == 'help') {
|
||||
$nameOfCommandToDescribe = $event->getInput()->getArgument('command');
|
||||
}
|
||||
$commandToDescribe = $this->application->find($nameOfCommandToDescribe);
|
||||
$this->findAndAddHookOptions($commandToDescribe);
|
||||
} else {
|
||||
$this->findAndAddHookOptions($command);
|
||||
}
|
||||
}
|
||||
|
||||
public function findAndAddHookOptions($command)
|
||||
{
|
||||
if (!$command instanceof AnnotatedCommand) {
|
||||
return;
|
||||
}
|
||||
$command->optionsHook();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @{@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [ConsoleEvents::COMMAND => 'alterCommandOptions'];
|
||||
}
|
||||
}
|
21
vendor/consolidation/annotated-command/src/Options/AutomaticOptionsProviderInterface.php
vendored
Normal file
21
vendor/consolidation/annotated-command/src/Options/AutomaticOptionsProviderInterface.php
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Options;
|
||||
|
||||
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
/**
|
||||
* Option providers can add options to commands based on the annotations
|
||||
* present in a command. For example, a command that specifies @fields
|
||||
* will automatically be given --format and --fields options.
|
||||
*
|
||||
* @see AnnotatedCommandFactory::addListener()
|
||||
* @see HookManager::addOptionHook()
|
||||
*/
|
||||
interface AutomaticOptionsProviderInterface
|
||||
{
|
||||
/**
|
||||
* @return InputOption[]
|
||||
*/
|
||||
public function automaticOptions(CommandInfo $commandInfo);
|
||||
}
|
10
vendor/consolidation/annotated-command/src/Options/PrepareFormatter.php
vendored
Normal file
10
vendor/consolidation/annotated-command/src/Options/PrepareFormatter.php
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Options;
|
||||
|
||||
use Consolidation\AnnotatedCommand\CommandData;
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
interface PrepareFormatter
|
||||
{
|
||||
public function prepare(CommandData $commandData, FormatterOptions $options);
|
||||
}
|
106
vendor/consolidation/annotated-command/src/Options/PrepareTerminalWidthOption.php
vendored
Normal file
106
vendor/consolidation/annotated-command/src/Options/PrepareTerminalWidthOption.php
vendored
Normal file
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Options;
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
use Consolidation\AnnotatedCommand\CommandData;
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
class PrepareTerminalWidthOption implements PrepareFormatter
|
||||
{
|
||||
/** var Application */
|
||||
protected $application;
|
||||
|
||||
protected $terminal;
|
||||
|
||||
/** var int */
|
||||
protected $defaultWidth;
|
||||
|
||||
/** var int */
|
||||
protected $maxWidth = PHP_INT_MAX;
|
||||
|
||||
/** var int */
|
||||
protected $minWidth = 0;
|
||||
|
||||
/* var boolean */
|
||||
protected $shouldWrap = true;
|
||||
|
||||
public function __construct($defaultWidth = 0)
|
||||
{
|
||||
$this->defaultWidth = $defaultWidth;
|
||||
}
|
||||
|
||||
public function setApplication(Application $application)
|
||||
{
|
||||
$this->application = $application;
|
||||
}
|
||||
|
||||
public function setTerminal($terminal)
|
||||
{
|
||||
$this->terminal = $terminal;
|
||||
}
|
||||
|
||||
public function getTerminal()
|
||||
{
|
||||
if (!$this->terminal && class_exists('\Symfony\Component\Console\Terminal')) {
|
||||
$this->terminal = new \Symfony\Component\Console\Terminal();
|
||||
}
|
||||
return $this->terminal;
|
||||
}
|
||||
|
||||
public function enableWrap($shouldWrap)
|
||||
{
|
||||
$this->shouldWrap = $shouldWrap;
|
||||
}
|
||||
|
||||
public function prepare(CommandData $commandData, FormatterOptions $options)
|
||||
{
|
||||
$width = $this->getTerminalWidth();
|
||||
if (!$width) {
|
||||
$width = $this->defaultWidth;
|
||||
}
|
||||
|
||||
// Enforce minimum and maximum widths
|
||||
$width = min($width, $this->getMaxWidth($commandData));
|
||||
$width = max($width, $this->getMinWidth($commandData));
|
||||
|
||||
$options->setWidth($width);
|
||||
}
|
||||
|
||||
protected function getTerminalWidth()
|
||||
{
|
||||
// Don't wrap if wrapping has been disabled.
|
||||
if (!$this->shouldWrap) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$terminal = $this->getTerminal();
|
||||
if ($terminal) {
|
||||
return $terminal->getWidth();
|
||||
}
|
||||
|
||||
return $this->getTerminalWidthViaApplication();
|
||||
}
|
||||
|
||||
protected function getTerminalWidthViaApplication()
|
||||
{
|
||||
if (!$this->application) {
|
||||
return 0;
|
||||
}
|
||||
$dimensions = $this->application->getTerminalDimensions();
|
||||
if ($dimensions[0] == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $dimensions[0];
|
||||
}
|
||||
|
||||
protected function getMaxWidth(CommandData $commandData)
|
||||
{
|
||||
return $this->maxWidth;
|
||||
}
|
||||
|
||||
protected function getMinWidth(CommandData $commandData)
|
||||
{
|
||||
return $this->minWidth;
|
||||
}
|
||||
}
|
13
vendor/consolidation/annotated-command/src/OutputDataInterface.php
vendored
Normal file
13
vendor/consolidation/annotated-command/src/OutputDataInterface.php
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand;
|
||||
|
||||
/**
|
||||
* If an annotated command method returns an object that
|
||||
* implements OutputDataInterface, then the getOutputData()
|
||||
* method is used to fetch the output to print from the
|
||||
* result object.
|
||||
*/
|
||||
interface OutputDataInterface
|
||||
{
|
||||
public function getOutputData();
|
||||
}
|
764
vendor/consolidation/annotated-command/src/Parser/CommandInfo.php
vendored
Normal file
764
vendor/consolidation/annotated-command/src/Parser/CommandInfo.php
vendored
Normal file
|
@ -0,0 +1,764 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Parser;
|
||||
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Consolidation\AnnotatedCommand\Parser\Internal\CommandDocBlockParser;
|
||||
use Consolidation\AnnotatedCommand\Parser\Internal\CommandDocBlockParserFactory;
|
||||
use Consolidation\AnnotatedCommand\AnnotationData;
|
||||
|
||||
/**
|
||||
* Given a class and method name, parse the annotations in the
|
||||
* DocBlock comment, and provide accessor methods for all of
|
||||
* the elements that are needed to create a Symfony Console Command.
|
||||
*
|
||||
* Note that the name of this class is now somewhat of a misnomer,
|
||||
* as we now use it to hold annotation data for hooks as well as commands.
|
||||
* It would probably be better to rename this to MethodInfo at some point.
|
||||
*/
|
||||
class CommandInfo
|
||||
{
|
||||
/**
|
||||
* Serialization schema version. Incremented every time the serialization schema changes.
|
||||
*/
|
||||
const SERIALIZATION_SCHEMA_VERSION = 3;
|
||||
|
||||
/**
|
||||
* @var \ReflectionMethod
|
||||
*/
|
||||
protected $reflection;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
* @var string
|
||||
*/
|
||||
protected $docBlockIsParsed = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $help = '';
|
||||
|
||||
/**
|
||||
* @var DefaultsWithDescriptions
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* @var DefaultsWithDescriptions
|
||||
*/
|
||||
protected $arguments;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $exampleUsage = [];
|
||||
|
||||
/**
|
||||
* @var AnnotationData
|
||||
*/
|
||||
protected $otherAnnotations;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $aliases = [];
|
||||
|
||||
/**
|
||||
* @var InputOption[]
|
||||
*/
|
||||
protected $inputOptions;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $methodName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $returnType;
|
||||
|
||||
/**
|
||||
* Create a new CommandInfo class for a particular method of a class.
|
||||
*
|
||||
* @param string|mixed $classNameOrInstance The name of a class, or an
|
||||
* instance of it, or an array of cached data.
|
||||
* @param string $methodName The name of the method to get info about.
|
||||
* @param array $cache Cached data
|
||||
* @deprecated Use CommandInfo::create() or CommandInfo::deserialize()
|
||||
* instead. In the future, this constructor will be protected.
|
||||
*/
|
||||
public function __construct($classNameOrInstance, $methodName, $cache = [])
|
||||
{
|
||||
$this->reflection = new \ReflectionMethod($classNameOrInstance, $methodName);
|
||||
$this->methodName = $methodName;
|
||||
$this->arguments = new DefaultsWithDescriptions();
|
||||
$this->options = new DefaultsWithDescriptions();
|
||||
|
||||
// If the cache came from a newer version, ignore it and
|
||||
// regenerate the cached information.
|
||||
if (!empty($cache) && CommandInfoDeserializer::isValidSerializedData($cache) && !$this->cachedFileIsModified($cache)) {
|
||||
$deserializer = new CommandInfoDeserializer();
|
||||
$deserializer->constructFromCache($this, $cache);
|
||||
$this->docBlockIsParsed = true;
|
||||
} else {
|
||||
$this->constructFromClassAndMethod($classNameOrInstance, $methodName);
|
||||
}
|
||||
}
|
||||
|
||||
public static function create($classNameOrInstance, $methodName)
|
||||
{
|
||||
return new self($classNameOrInstance, $methodName);
|
||||
}
|
||||
|
||||
public static function deserialize($cache)
|
||||
{
|
||||
$cache = (array)$cache;
|
||||
return new self($cache['class'], $cache['method_name'], $cache);
|
||||
}
|
||||
|
||||
public function cachedFileIsModified($cache)
|
||||
{
|
||||
$path = $this->reflection->getFileName();
|
||||
return filemtime($path) != $cache['mtime'];
|
||||
}
|
||||
|
||||
protected function constructFromClassAndMethod($classNameOrInstance, $methodName)
|
||||
{
|
||||
$this->otherAnnotations = new AnnotationData();
|
||||
// Set up a default name for the command from the method name.
|
||||
// This can be overridden via @command or @name annotations.
|
||||
$this->name = $this->convertName($methodName);
|
||||
$this->options = new DefaultsWithDescriptions($this->determineOptionsFromParameters(), false);
|
||||
$this->arguments = $this->determineAgumentClassifications();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recover the method name provided to the constructor.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMethodName()
|
||||
{
|
||||
return $this->methodName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the primary name for this command.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
$this->parseDocBlock();
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the primary name for this command.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether or not this method represents a valid command
|
||||
* or hook.
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return !empty($this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* If higher-level code decides that this CommandInfo is not interesting
|
||||
* or useful (if it is not a command method or a hook method), then
|
||||
* we will mark it as invalid to prevent it from being created as a command.
|
||||
* We still cache a placeholder record for invalid methods, so that we
|
||||
* do not need to re-parse the method again later simply to determine that
|
||||
* it is invalid.
|
||||
*/
|
||||
public function invalidate()
|
||||
{
|
||||
$this->name = '';
|
||||
}
|
||||
|
||||
public function getReturnType()
|
||||
{
|
||||
$this->parseDocBlock();
|
||||
return $this->returnType;
|
||||
}
|
||||
|
||||
public function setReturnType($returnType)
|
||||
{
|
||||
$this->returnType = $returnType;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any annotations included in the docblock comment for the
|
||||
* implementation method of this command that are not already
|
||||
* handled by the primary methods of this class.
|
||||
*
|
||||
* @return AnnotationData
|
||||
*/
|
||||
public function getRawAnnotations()
|
||||
{
|
||||
$this->parseDocBlock();
|
||||
return $this->otherAnnotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the annotation data.
|
||||
*/
|
||||
public function replaceRawAnnotations($annotationData)
|
||||
{
|
||||
$this->otherAnnotations = new AnnotationData((array) $annotationData);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any annotations included in the docblock comment,
|
||||
* also including default values such as @command. We add
|
||||
* in the default @command annotation late, and only in a
|
||||
* copy of the annotation data because we use the existance
|
||||
* of a @command to indicate that this CommandInfo is
|
||||
* a command, and not a hook or anything else.
|
||||
*
|
||||
* @return AnnotationData
|
||||
*/
|
||||
public function getAnnotations()
|
||||
{
|
||||
// Also provide the path to the commandfile that these annotations
|
||||
// were pulled from and the classname of that file.
|
||||
$path = $this->reflection->getFileName();
|
||||
$className = $this->reflection->getDeclaringClass()->getName();
|
||||
return new AnnotationData(
|
||||
$this->getRawAnnotations()->getArrayCopy() +
|
||||
[
|
||||
'command' => $this->getName(),
|
||||
'_path' => $path,
|
||||
'_classname' => $className,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a specific named annotation for this command as a list.
|
||||
*
|
||||
* @param string $name The name of the annotation.
|
||||
* @return array|null
|
||||
*/
|
||||
public function getAnnotationList($name)
|
||||
{
|
||||
// hasAnnotation parses the docblock
|
||||
if (!$this->hasAnnotation($name)) {
|
||||
return null;
|
||||
}
|
||||
return $this->otherAnnotations->getList($name);
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a specific named annotation for this command as a string.
|
||||
*
|
||||
* @param string $name The name of the annotation.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getAnnotation($name)
|
||||
{
|
||||
// hasAnnotation parses the docblock
|
||||
if (!$this->hasAnnotation($name)) {
|
||||
return null;
|
||||
}
|
||||
return $this->otherAnnotations->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if the specified annotation exists for this command.
|
||||
*
|
||||
* @param string $annotation The name of the annotation.
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasAnnotation($annotation)
|
||||
{
|
||||
$this->parseDocBlock();
|
||||
return isset($this->otherAnnotations[$annotation]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save any tag that we do not explicitly recognize in the
|
||||
* 'otherAnnotations' map.
|
||||
*/
|
||||
public function addAnnotation($name, $content)
|
||||
{
|
||||
// Convert to an array and merge if there are multiple
|
||||
// instances of the same annotation defined.
|
||||
if (isset($this->otherAnnotations[$name])) {
|
||||
$content = array_merge((array) $this->otherAnnotations[$name], (array)$content);
|
||||
}
|
||||
$this->otherAnnotations[$name] = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an annotation that was previoudly set.
|
||||
*/
|
||||
public function removeAnnotation($name)
|
||||
{
|
||||
unset($this->otherAnnotations[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the synopsis of the command (~first line).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
$this->parseDocBlock();
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the command description.
|
||||
*
|
||||
* @param string $description The description to set.
|
||||
*/
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->description = str_replace("\n", ' ', $description);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the help text of the command (the description)
|
||||
*/
|
||||
public function getHelp()
|
||||
{
|
||||
$this->parseDocBlock();
|
||||
return $this->help;
|
||||
}
|
||||
/**
|
||||
* Set the help text for this command.
|
||||
*
|
||||
* @param string $help The help text.
|
||||
*/
|
||||
public function setHelp($help)
|
||||
{
|
||||
$this->help = $help;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of aliases for this command.
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAliases()
|
||||
{
|
||||
$this->parseDocBlock();
|
||||
return $this->aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set aliases that can be used in place of the command's primary name.
|
||||
*
|
||||
* @param string|string[] $aliases
|
||||
*/
|
||||
public function setAliases($aliases)
|
||||
{
|
||||
if (is_string($aliases)) {
|
||||
$aliases = explode(',', static::convertListToCommaSeparated($aliases));
|
||||
}
|
||||
$this->aliases = array_filter($aliases);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hidden status for the command.
|
||||
* @return bool
|
||||
*/
|
||||
public function getHidden()
|
||||
{
|
||||
$this->parseDocBlock();
|
||||
return $this->hasAnnotation('hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set hidden status. List command omits hidden commands.
|
||||
*
|
||||
* @param bool $hidden
|
||||
*/
|
||||
public function setHidden($hidden)
|
||||
{
|
||||
$this->hidden = $hidden;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the examples for this command. This is @usage instead of
|
||||
* @example because the later is defined by the phpdoc standard to
|
||||
* be example method calls.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getExampleUsages()
|
||||
{
|
||||
$this->parseDocBlock();
|
||||
return $this->exampleUsage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an example usage for this command.
|
||||
*
|
||||
* @param string $usage An example of the command, including the command
|
||||
* name and all of its example arguments and options.
|
||||
* @param string $description An explanation of what the example does.
|
||||
*/
|
||||
public function setExampleUsage($usage, $description)
|
||||
{
|
||||
$this->exampleUsage[$usage] = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite all example usages
|
||||
*/
|
||||
public function replaceExampleUsages($usages)
|
||||
{
|
||||
$this->exampleUsage = $usages;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the topics for this command.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getTopics()
|
||||
{
|
||||
if (!$this->hasAnnotation('topics')) {
|
||||
return [];
|
||||
}
|
||||
$topics = $this->getAnnotation('topics');
|
||||
return explode(',', trim($topics));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of refleaction parameters.
|
||||
*
|
||||
* @return ReflectionParameter[]
|
||||
*/
|
||||
public function getParameters()
|
||||
{
|
||||
return $this->reflection->getParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Descriptions of commandline arguements for this command.
|
||||
*
|
||||
* @return DefaultsWithDescriptions
|
||||
*/
|
||||
public function arguments()
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Descriptions of commandline options for this command.
|
||||
*
|
||||
* @return DefaultsWithDescriptions
|
||||
*/
|
||||
public function options()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the inputOptions for the options associated with this CommandInfo
|
||||
* object, e.g. via @option annotations, or from
|
||||
* $options = ['someoption' => 'defaultvalue'] in the command method
|
||||
* parameter list.
|
||||
*
|
||||
* @return InputOption[]
|
||||
*/
|
||||
public function inputOptions()
|
||||
{
|
||||
if (!isset($this->inputOptions)) {
|
||||
$this->inputOptions = $this->createInputOptions();
|
||||
}
|
||||
return $this->inputOptions;
|
||||
}
|
||||
|
||||
protected function addImplicitNoOptions()
|
||||
{
|
||||
$opts = $this->options()->getValues();
|
||||
foreach ($opts as $name => $defaultValue) {
|
||||
if ($defaultValue === true) {
|
||||
$key = 'no-' . $name;
|
||||
if (!array_key_exists($key, $opts)) {
|
||||
$description = "Negate --$name option.";
|
||||
$this->options()->add($key, $description, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function createInputOptions()
|
||||
{
|
||||
$explicitOptions = [];
|
||||
$this->addImplicitNoOptions();
|
||||
|
||||
$opts = $this->options()->getValues();
|
||||
foreach ($opts as $name => $defaultValue) {
|
||||
$description = $this->options()->getDescription($name);
|
||||
|
||||
$fullName = $name;
|
||||
$shortcut = '';
|
||||
if (strpos($name, '|')) {
|
||||
list($fullName, $shortcut) = explode('|', $name, 2);
|
||||
}
|
||||
|
||||
// Treat the following two cases identically:
|
||||
// - 'foo' => InputOption::VALUE_OPTIONAL
|
||||
// - 'foo' => null
|
||||
// The first form is preferred, but we will convert the value
|
||||
// to 'null' for storage as the option default value.
|
||||
if ($defaultValue === InputOption::VALUE_OPTIONAL) {
|
||||
$defaultValue = null;
|
||||
}
|
||||
|
||||
if ($defaultValue === false) {
|
||||
$explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_NONE, $description);
|
||||
} elseif ($defaultValue === InputOption::VALUE_REQUIRED) {
|
||||
$explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_REQUIRED, $description);
|
||||
} elseif (is_array($defaultValue)) {
|
||||
$optionality = count($defaultValue) ? InputOption::VALUE_OPTIONAL : InputOption::VALUE_REQUIRED;
|
||||
$explicitOptions[$fullName] = new InputOption(
|
||||
$fullName,
|
||||
$shortcut,
|
||||
InputOption::VALUE_IS_ARRAY | $optionality,
|
||||
$description,
|
||||
count($defaultValue) ? $defaultValue : null
|
||||
);
|
||||
} else {
|
||||
$explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_OPTIONAL, $description, $defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
return $explicitOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* An option might have a name such as 'silent|s'. In this
|
||||
* instance, we will allow the @option or @default tag to
|
||||
* reference the option only by name (e.g. 'silent' or 's'
|
||||
* instead of 'silent|s').
|
||||
*
|
||||
* @param string $optionName
|
||||
* @return string
|
||||
*/
|
||||
public function findMatchingOption($optionName)
|
||||
{
|
||||
// Exit fast if there's an exact match
|
||||
if ($this->options->exists($optionName)) {
|
||||
return $optionName;
|
||||
}
|
||||
$existingOptionName = $this->findExistingOption($optionName);
|
||||
if (isset($existingOptionName)) {
|
||||
return $existingOptionName;
|
||||
}
|
||||
return $this->findOptionAmongAlternatives($optionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $optionName
|
||||
* @return string
|
||||
*/
|
||||
protected function findOptionAmongAlternatives($optionName)
|
||||
{
|
||||
// Check the other direction: if the annotation contains @silent|s
|
||||
// and the options array has 'silent|s'.
|
||||
$checkMatching = explode('|', $optionName);
|
||||
if (count($checkMatching) > 1) {
|
||||
foreach ($checkMatching as $checkName) {
|
||||
if ($this->options->exists($checkName)) {
|
||||
$this->options->rename($checkName, $optionName);
|
||||
return $optionName;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $optionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $optionName
|
||||
* @return string|null
|
||||
*/
|
||||
protected function findExistingOption($optionName)
|
||||
{
|
||||
// Check to see if we can find the option name in an existing option,
|
||||
// e.g. if the options array has 'silent|s' => false, and the annotation
|
||||
// is @silent.
|
||||
foreach ($this->options()->getValues() as $name => $default) {
|
||||
if (in_array($optionName, explode('|', $name))) {
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Examine the parameters of the method for this command, and
|
||||
* build a list of commandline arguements for them.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function determineAgumentClassifications()
|
||||
{
|
||||
$result = new DefaultsWithDescriptions();
|
||||
$params = $this->reflection->getParameters();
|
||||
$optionsFromParameters = $this->determineOptionsFromParameters();
|
||||
if ($this->lastParameterIsOptionsArray()) {
|
||||
array_pop($params);
|
||||
}
|
||||
foreach ($params as $param) {
|
||||
$this->addParameterToResult($result, $param);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Examine the provided parameter, and determine whether it
|
||||
* is a parameter that will be filled in with a positional
|
||||
* commandline argument.
|
||||
*/
|
||||
protected function addParameterToResult($result, $param)
|
||||
{
|
||||
// Commandline arguments must be strings, so ignore any
|
||||
// parameter that is typehinted to any non-primative class.
|
||||
if ($param->getClass() != null) {
|
||||
return;
|
||||
}
|
||||
$result->add($param->name);
|
||||
if ($param->isDefaultValueAvailable()) {
|
||||
$defaultValue = $param->getDefaultValue();
|
||||
if (!$this->isAssoc($defaultValue)) {
|
||||
$result->setDefaultValue($param->name, $defaultValue);
|
||||
}
|
||||
} elseif ($param->isArray()) {
|
||||
$result->setDefaultValue($param->name, []);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Examine the parameters of the method for this command, and determine
|
||||
* the disposition of the options from them.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function determineOptionsFromParameters()
|
||||
{
|
||||
$params = $this->reflection->getParameters();
|
||||
if (empty($params)) {
|
||||
return [];
|
||||
}
|
||||
$param = end($params);
|
||||
if (!$param->isDefaultValueAvailable()) {
|
||||
return [];
|
||||
}
|
||||
if (!$this->isAssoc($param->getDefaultValue())) {
|
||||
return [];
|
||||
}
|
||||
return $param->getDefaultValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the last argument contains $options.
|
||||
*
|
||||
* Two forms indicate options:
|
||||
* - $options = []
|
||||
* - $options = ['flag' => 'default-value']
|
||||
*
|
||||
* Any other form, including `array $foo`, is not options.
|
||||
*/
|
||||
protected function lastParameterIsOptionsArray()
|
||||
{
|
||||
$params = $this->reflection->getParameters();
|
||||
if (empty($params)) {
|
||||
return [];
|
||||
}
|
||||
$param = end($params);
|
||||
if (!$param->isDefaultValueAvailable()) {
|
||||
return [];
|
||||
}
|
||||
return is_array($param->getDefaultValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper; determine if an array is associative or not. An array
|
||||
* is not associative if its keys are numeric, and numbered sequentially
|
||||
* from zero. All other arrays are considered to be associative.
|
||||
*
|
||||
* @param array $arr The array
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isAssoc($arr)
|
||||
{
|
||||
if (!is_array($arr)) {
|
||||
return false;
|
||||
}
|
||||
return array_keys($arr) !== range(0, count($arr) - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from a method name to the corresponding command name. A
|
||||
* method 'fooBar' will become 'foo:bar', and 'fooBarBazBoz' will
|
||||
* become 'foo:bar-baz-boz'.
|
||||
*
|
||||
* @param string $camel method name.
|
||||
* @return string
|
||||
*/
|
||||
protected function convertName($camel)
|
||||
{
|
||||
$splitter="-";
|
||||
$camel=preg_replace('/(?!^)[[:upper:]][[:lower:]]/', '$0', preg_replace('/(?!^)[[:upper:]]+/', $splitter.'$0', $camel));
|
||||
$camel = preg_replace("/$splitter/", ':', $camel, 1);
|
||||
return strtolower($camel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the docBlock comment for this command, and set the
|
||||
* fields of this class with the data thereby obtained.
|
||||
*/
|
||||
protected function parseDocBlock()
|
||||
{
|
||||
if (!$this->docBlockIsParsed) {
|
||||
// The parse function will insert data from the provided method
|
||||
// into this object, using our accessors.
|
||||
CommandDocBlockParserFactory::parse($this, $this->reflection);
|
||||
$this->docBlockIsParsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list that might be 'a b c' or 'a, b, c' or 'a,b,c',
|
||||
* convert the data into the last of these forms.
|
||||
*/
|
||||
protected static function convertListToCommaSeparated($text)
|
||||
{
|
||||
return preg_replace('#[ \t\n\r,]+#', ',', $text);
|
||||
}
|
||||
}
|
87
vendor/consolidation/annotated-command/src/Parser/CommandInfoDeserializer.php
vendored
Normal file
87
vendor/consolidation/annotated-command/src/Parser/CommandInfoDeserializer.php
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Parser;
|
||||
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Consolidation\AnnotatedCommand\Parser\Internal\CommandDocBlockParser;
|
||||
use Consolidation\AnnotatedCommand\Parser\Internal\CommandDocBlockParserFactory;
|
||||
use Consolidation\AnnotatedCommand\AnnotationData;
|
||||
|
||||
/**
|
||||
* Deserialize a CommandInfo object
|
||||
*/
|
||||
class CommandInfoDeserializer
|
||||
{
|
||||
// TODO: in a future version, move CommandInfo::deserialize here
|
||||
public function deserialize($data)
|
||||
{
|
||||
return CommandInfo::deserialize((array)$data);
|
||||
}
|
||||
|
||||
protected static function cachedMethodExists($cache)
|
||||
{
|
||||
return method_exists($cache['class'], $cache['method_name']);
|
||||
}
|
||||
|
||||
public static function isValidSerializedData($cache)
|
||||
{
|
||||
return
|
||||
isset($cache['schema']) &&
|
||||
isset($cache['method_name']) &&
|
||||
isset($cache['mtime']) &&
|
||||
($cache['schema'] > 0) &&
|
||||
($cache['schema'] <= CommandInfo::SERIALIZATION_SCHEMA_VERSION) &&
|
||||
self::cachedMethodExists($cache);
|
||||
}
|
||||
|
||||
public function constructFromCache(CommandInfo $commandInfo, $info_array)
|
||||
{
|
||||
$info_array += $this->defaultSerializationData();
|
||||
|
||||
$commandInfo
|
||||
->setName($info_array['name'])
|
||||
->replaceRawAnnotations($info_array['annotations'])
|
||||
->setAliases($info_array['aliases'])
|
||||
->setHelp($info_array['help'])
|
||||
->setDescription($info_array['description'])
|
||||
->replaceExampleUsages($info_array['example_usages'])
|
||||
->setReturnType($info_array['return_type'])
|
||||
;
|
||||
|
||||
$this->constructDefaultsWithDescriptions($commandInfo->arguments(), (array)$info_array['arguments']);
|
||||
$this->constructDefaultsWithDescriptions($commandInfo->options(), (array)$info_array['options']);
|
||||
}
|
||||
|
||||
protected function constructDefaultsWithDescriptions(DefaultsWithDescriptions $defaults, $data)
|
||||
{
|
||||
foreach ($data as $key => $info) {
|
||||
$info = (array)$info;
|
||||
$defaults->add($key, $info['description']);
|
||||
if (array_key_exists('default', $info)) {
|
||||
$defaults->setDefaultValue($key, $info['default']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Default data. Everything should be provided during serialization;
|
||||
* this is just as a fallback for unusual circumstances.
|
||||
* @return array
|
||||
*/
|
||||
protected function defaultSerializationData()
|
||||
{
|
||||
return [
|
||||
'name' => '',
|
||||
'description' => '',
|
||||
'help' => '',
|
||||
'aliases' => [],
|
||||
'annotations' => [],
|
||||
'example_usages' => [],
|
||||
'return_type' => [],
|
||||
'parameters' => [],
|
||||
'arguments' => [],
|
||||
'options' => [],
|
||||
'mtime' => 0,
|
||||
];
|
||||
}
|
||||
}
|
59
vendor/consolidation/annotated-command/src/Parser/CommandInfoSerializer.php
vendored
Normal file
59
vendor/consolidation/annotated-command/src/Parser/CommandInfoSerializer.php
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Parser;
|
||||
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Consolidation\AnnotatedCommand\Parser\Internal\CommandDocBlockParser;
|
||||
use Consolidation\AnnotatedCommand\Parser\Internal\CommandDocBlockParserFactory;
|
||||
use Consolidation\AnnotatedCommand\AnnotationData;
|
||||
|
||||
/**
|
||||
* Serialize a CommandInfo object
|
||||
*/
|
||||
class CommandInfoSerializer
|
||||
{
|
||||
public function serialize(CommandInfo $commandInfo)
|
||||
{
|
||||
$allAnnotations = $commandInfo->getAnnotations();
|
||||
$path = $allAnnotations['_path'];
|
||||
$className = $allAnnotations['_classname'];
|
||||
|
||||
// Include the minimum information for command info (including placeholder records)
|
||||
$info = [
|
||||
'schema' => CommandInfo::SERIALIZATION_SCHEMA_VERSION,
|
||||
'class' => $className,
|
||||
'method_name' => $commandInfo->getMethodName(),
|
||||
'mtime' => filemtime($path),
|
||||
];
|
||||
|
||||
// If this is a valid method / hook, then add more information.
|
||||
if ($commandInfo->valid()) {
|
||||
$info += [
|
||||
'name' => $commandInfo->getName(),
|
||||
'description' => $commandInfo->getDescription(),
|
||||
'help' => $commandInfo->getHelp(),
|
||||
'aliases' => $commandInfo->getAliases(),
|
||||
'annotations' => $commandInfo->getRawAnnotations()->getArrayCopy(),
|
||||
'example_usages' => $commandInfo->getExampleUsages(),
|
||||
'return_type' => $commandInfo->getReturnType(),
|
||||
];
|
||||
$info['arguments'] = $this->serializeDefaultsWithDescriptions($commandInfo->arguments());
|
||||
$info['options'] = $this->serializeDefaultsWithDescriptions($commandInfo->options());
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
protected function serializeDefaultsWithDescriptions(DefaultsWithDescriptions $defaults)
|
||||
{
|
||||
$result = [];
|
||||
foreach ($defaults->getValues() as $key => $val) {
|
||||
$result[$key] = [
|
||||
'description' => $defaults->getDescription($key),
|
||||
];
|
||||
if ($defaults->hasDefault($key)) {
|
||||
$result[$key]['default'] = $val;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
162
vendor/consolidation/annotated-command/src/Parser/DefaultsWithDescriptions.php
vendored
Normal file
162
vendor/consolidation/annotated-command/src/Parser/DefaultsWithDescriptions.php
vendored
Normal file
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Parser;
|
||||
|
||||
/**
|
||||
* An associative array that maps from key to default value;
|
||||
* each entry can also have a description.
|
||||
*/
|
||||
class DefaultsWithDescriptions
|
||||
{
|
||||
/**
|
||||
* @var array Associative array of key : default mappings
|
||||
*/
|
||||
protected $values;
|
||||
|
||||
/**
|
||||
* @var array Associative array used like a set to indicate default value
|
||||
* exists for the key.
|
||||
*/
|
||||
protected $hasDefault;
|
||||
|
||||
/**
|
||||
* @var array Associative array of key : description mappings
|
||||
*/
|
||||
protected $descriptions;
|
||||
|
||||
/**
|
||||
* @var mixed Default value that the default value of items in
|
||||
* the collection should take when not specified in the 'add' method.
|
||||
*/
|
||||
protected $defaultDefault;
|
||||
|
||||
public function __construct($values = [], $defaultDefault = null)
|
||||
{
|
||||
$this->values = $values;
|
||||
$this->hasDefault = array_filter($this->values, function ($value) {
|
||||
return isset($value);
|
||||
});
|
||||
$this->descriptions = [];
|
||||
$this->defaultDefault = $defaultDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return just the key : default values mapping
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValues()
|
||||
{
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this set of options is empty
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
return empty($this->values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see whether the speicifed key exists in the collection.
|
||||
*
|
||||
* @param string $key
|
||||
* @return boolean
|
||||
*/
|
||||
public function exists($key)
|
||||
{
|
||||
return array_key_exists($key, $this->values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of one entry.
|
||||
*
|
||||
* @param string $key The key of the item.
|
||||
* @return string
|
||||
*/
|
||||
public function get($key)
|
||||
{
|
||||
if (array_key_exists($key, $this->values)) {
|
||||
return $this->values[$key];
|
||||
}
|
||||
return $this->defaultDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the description of one entry.
|
||||
*
|
||||
* @param string $key The key of the item.
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription($key)
|
||||
{
|
||||
if (array_key_exists($key, $this->descriptions)) {
|
||||
return $this->descriptions[$key];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add another argument to this command.
|
||||
*
|
||||
* @param string $key Name of the argument.
|
||||
* @param string $description Help text for the argument.
|
||||
* @param mixed $defaultValue The default value for the argument.
|
||||
*/
|
||||
public function add($key, $description = '', $defaultValue = null)
|
||||
{
|
||||
if (!$this->exists($key) || isset($defaultValue)) {
|
||||
$this->values[$key] = isset($defaultValue) ? $defaultValue : $this->defaultDefault;
|
||||
}
|
||||
unset($this->descriptions[$key]);
|
||||
if (!empty($description)) {
|
||||
$this->descriptions[$key] = $description;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the default value of an entry.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $defaultValue
|
||||
*/
|
||||
public function setDefaultValue($key, $defaultValue)
|
||||
{
|
||||
$this->values[$key] = $defaultValue;
|
||||
$this->hasDefault[$key] = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if the named argument definitively has a default value.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDefault($key)
|
||||
{
|
||||
return array_key_exists($key, $this->hasDefault);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an entry
|
||||
*
|
||||
* @param string $key The entry to remove
|
||||
*/
|
||||
public function clear($key)
|
||||
{
|
||||
unset($this->values[$key]);
|
||||
unset($this->descriptions[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename an existing option to something else.
|
||||
*/
|
||||
public function rename($oldName, $newName)
|
||||
{
|
||||
$this->add($newName, $this->getDescription($oldName), $this->get($oldName));
|
||||
$this->clear($oldName);
|
||||
}
|
||||
}
|
322
vendor/consolidation/annotated-command/src/Parser/Internal/BespokeDocBlockParser.php
vendored
Normal file
322
vendor/consolidation/annotated-command/src/Parser/Internal/BespokeDocBlockParser.php
vendored
Normal file
|
@ -0,0 +1,322 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Parser\Internal;
|
||||
|
||||
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
|
||||
use Consolidation\AnnotatedCommand\Parser\DefaultsWithDescriptions;
|
||||
|
||||
/**
|
||||
* Given a class and method name, parse the annotations in the
|
||||
* DocBlock comment, and provide accessor methods for all of
|
||||
* the elements that are needed to create an annotated Command.
|
||||
*/
|
||||
class BespokeDocBlockParser
|
||||
{
|
||||
protected $fqcnCache;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $tagProcessors = [
|
||||
'command' => 'processCommandTag',
|
||||
'name' => 'processCommandTag',
|
||||
'arg' => 'processArgumentTag',
|
||||
'param' => 'processArgumentTag',
|
||||
'return' => 'processReturnTag',
|
||||
'option' => 'processOptionTag',
|
||||
'default' => 'processDefaultTag',
|
||||
'aliases' => 'processAliases',
|
||||
'usage' => 'processUsageTag',
|
||||
'description' => 'processAlternateDescriptionTag',
|
||||
'desc' => 'processAlternateDescriptionTag',
|
||||
];
|
||||
|
||||
public function __construct(CommandInfo $commandInfo, \ReflectionMethod $reflection, $fqcnCache = null)
|
||||
{
|
||||
$this->commandInfo = $commandInfo;
|
||||
$this->reflection = $reflection;
|
||||
$this->fqcnCache = $fqcnCache ?: new FullyQualifiedClassCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the docBlock comment for this command, and set the
|
||||
* fields of this class with the data thereby obtained.
|
||||
*/
|
||||
public function parse()
|
||||
{
|
||||
$doc = $this->reflection->getDocComment();
|
||||
$this->parseDocBlock($doc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save any tag that we do not explicitly recognize in the
|
||||
* 'otherAnnotations' map.
|
||||
*/
|
||||
protected function processGenericTag($tag)
|
||||
{
|
||||
$this->commandInfo->addAnnotation($tag->getTag(), $tag->getContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the command from a @command or @name annotation.
|
||||
*/
|
||||
protected function processCommandTag($tag)
|
||||
{
|
||||
if (!$tag->hasWordAndDescription($matches)) {
|
||||
throw new \Exception('Could not determine command name from tag ' . (string)$tag);
|
||||
}
|
||||
$commandName = $matches['word'];
|
||||
$this->commandInfo->setName($commandName);
|
||||
// We also store the name in the 'other annotations' so that is is
|
||||
// possible to determine if the method had a @command annotation.
|
||||
$this->commandInfo->addAnnotation($tag->getTag(), $commandName);
|
||||
}
|
||||
|
||||
/**
|
||||
* The @description and @desc annotations may be used in
|
||||
* place of the synopsis (which we call 'description').
|
||||
* This is discouraged.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
protected function processAlternateDescriptionTag($tag)
|
||||
{
|
||||
$this->commandInfo->setDescription($tag->getContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the data from a @arg annotation in our argument descriptions.
|
||||
*/
|
||||
protected function processArgumentTag($tag)
|
||||
{
|
||||
if (!$tag->hasVariable($matches)) {
|
||||
throw new \Exception('Could not determine argument name from tag ' . (string)$tag);
|
||||
}
|
||||
if ($matches['variable'] == $this->optionParamName()) {
|
||||
return;
|
||||
}
|
||||
$this->addOptionOrArgumentTag($tag, $this->commandInfo->arguments(), $matches['variable'], $matches['description']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the data from an @option annotation in our option descriptions.
|
||||
*/
|
||||
protected function processOptionTag($tag)
|
||||
{
|
||||
if (!$tag->hasVariable($matches)) {
|
||||
throw new \Exception('Could not determine option name from tag ' . (string)$tag);
|
||||
}
|
||||
$this->addOptionOrArgumentTag($tag, $this->commandInfo->options(), $matches['variable'], $matches['description']);
|
||||
}
|
||||
|
||||
protected function addOptionOrArgumentTag($tag, DefaultsWithDescriptions $set, $name, $description)
|
||||
{
|
||||
$variableName = $this->commandInfo->findMatchingOption($name);
|
||||
$description = static::removeLineBreaks($description);
|
||||
$set->add($variableName, $description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the data from a @default annotation in our argument or option store,
|
||||
* as appropriate.
|
||||
*/
|
||||
protected function processDefaultTag($tag)
|
||||
{
|
||||
if (!$tag->hasVariable($matches)) {
|
||||
throw new \Exception('Could not determine parameter name for default value from tag ' . (string)$tag);
|
||||
}
|
||||
$variableName = $matches['variable'];
|
||||
$defaultValue = $this->interpretDefaultValue($matches['description']);
|
||||
if ($this->commandInfo->arguments()->exists($variableName)) {
|
||||
$this->commandInfo->arguments()->setDefaultValue($variableName, $defaultValue);
|
||||
return;
|
||||
}
|
||||
$variableName = $this->commandInfo->findMatchingOption($variableName);
|
||||
if ($this->commandInfo->options()->exists($variableName)) {
|
||||
$this->commandInfo->options()->setDefaultValue($variableName, $defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the data from a @usage annotation in our example usage list.
|
||||
*/
|
||||
protected function processUsageTag($tag)
|
||||
{
|
||||
$lines = explode("\n", $tag->getContent());
|
||||
$usage = trim(array_shift($lines));
|
||||
$description = static::removeLineBreaks(implode("\n", array_map(function ($line) {
|
||||
return trim($line);
|
||||
}, $lines)));
|
||||
|
||||
$this->commandInfo->setExampleUsage($usage, $description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the comma-separated list of aliases
|
||||
*/
|
||||
protected function processAliases($tag)
|
||||
{
|
||||
$this->commandInfo->setAliases((string)$tag->getContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the data from a @return annotation in our argument descriptions.
|
||||
*/
|
||||
protected function processReturnTag($tag)
|
||||
{
|
||||
// The return type might be a variable -- '$this'. It will
|
||||
// usually be a type, like RowsOfFields, or \Namespace\RowsOfFields.
|
||||
if (!$tag->hasVariableAndDescription($matches)) {
|
||||
throw new \Exception('Could not determine return type from tag ' . (string)$tag);
|
||||
}
|
||||
// Look at namespace and `use` statments to make returnType a fqdn
|
||||
$returnType = $matches['variable'];
|
||||
$returnType = $this->findFullyQualifiedClass($returnType);
|
||||
$this->commandInfo->setReturnType($returnType);
|
||||
}
|
||||
|
||||
protected function findFullyQualifiedClass($className)
|
||||
{
|
||||
if (strpos($className, '\\') !== false) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
return $this->fqcnCache->qualify($this->reflection->getFileName(), $className);
|
||||
}
|
||||
|
||||
private function parseDocBlock($doc)
|
||||
{
|
||||
// Remove the leading /** and the trailing */
|
||||
$doc = preg_replace('#^\s*/\*+\s*#', '', $doc);
|
||||
$doc = preg_replace('#\s*\*+/\s*#', '', $doc);
|
||||
|
||||
// Nothing left? Exit.
|
||||
if (empty($doc)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tagFactory = new TagFactory();
|
||||
$lines = [];
|
||||
|
||||
foreach (explode("\n", $doc) as $row) {
|
||||
// Remove trailing whitespace and leading space + '*'s
|
||||
$row = rtrim($row);
|
||||
$row = preg_replace('#^[ \t]*\**#', '', $row);
|
||||
|
||||
if (!$tagFactory->parseLine($row)) {
|
||||
$lines[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
$this->processDescriptionAndHelp($lines);
|
||||
$this->processAllTags($tagFactory->getTags());
|
||||
}
|
||||
|
||||
protected function processDescriptionAndHelp($lines)
|
||||
{
|
||||
// Trim all of the lines individually.
|
||||
$lines =
|
||||
array_map(
|
||||
function ($line) {
|
||||
return trim($line);
|
||||
},
|
||||
$lines
|
||||
);
|
||||
|
||||
// Everything up to the first blank line goes in the description.
|
||||
$description = array_shift($lines);
|
||||
while ($this->nextLineIsNotEmpty($lines)) {
|
||||
$description .= ' ' . array_shift($lines);
|
||||
}
|
||||
|
||||
// Everything else goes in the help.
|
||||
$help = trim(implode("\n", $lines));
|
||||
|
||||
$this->commandInfo->setDescription($description);
|
||||
$this->commandInfo->setHelp($help);
|
||||
}
|
||||
|
||||
protected function nextLineIsNotEmpty($lines)
|
||||
{
|
||||
if (empty($lines)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$nextLine = trim($lines[0]);
|
||||
return !empty($nextLine);
|
||||
}
|
||||
|
||||
protected function processAllTags($tags)
|
||||
{
|
||||
// Iterate over all of the tags, and process them as necessary.
|
||||
foreach ($tags as $tag) {
|
||||
$processFn = [$this, 'processGenericTag'];
|
||||
if (array_key_exists($tag->getTag(), $this->tagProcessors)) {
|
||||
$processFn = [$this, $this->tagProcessors[$tag->getTag()]];
|
||||
}
|
||||
$processFn($tag);
|
||||
}
|
||||
}
|
||||
|
||||
protected function lastParameterName()
|
||||
{
|
||||
$params = $this->commandInfo->getParameters();
|
||||
$param = end($params);
|
||||
if (!$param) {
|
||||
return '';
|
||||
}
|
||||
return $param->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the last parameter if it holds the options.
|
||||
*/
|
||||
public function optionParamName()
|
||||
{
|
||||
// Remember the name of the last parameter, if it holds the options.
|
||||
// We will use this information to ignore @param annotations for the options.
|
||||
if (!isset($this->optionParamName)) {
|
||||
$this->optionParamName = '';
|
||||
$options = $this->commandInfo->options();
|
||||
if (!$options->isEmpty()) {
|
||||
$this->optionParamName = $this->lastParameterName();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->optionParamName;
|
||||
}
|
||||
|
||||
protected function interpretDefaultValue($defaultValue)
|
||||
{
|
||||
$defaults = [
|
||||
'null' => null,
|
||||
'true' => true,
|
||||
'false' => false,
|
||||
"''" => '',
|
||||
'[]' => [],
|
||||
];
|
||||
foreach ($defaults as $defaultName => $defaultTypedValue) {
|
||||
if ($defaultValue == $defaultName) {
|
||||
return $defaultTypedValue;
|
||||
}
|
||||
}
|
||||
return $defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list that might be 'a b c' or 'a, b, c' or 'a,b,c',
|
||||
* convert the data into the last of these forms.
|
||||
*/
|
||||
protected static function convertListToCommaSeparated($text)
|
||||
{
|
||||
return preg_replace('#[ \t\n\r,]+#', ',', $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a multiline description and convert it into a single
|
||||
* long unbroken line.
|
||||
*/
|
||||
protected static function removeLineBreaks($text)
|
||||
{
|
||||
return trim(preg_replace('#[ \t\n\r]+#', ' ', $text));
|
||||
}
|
||||
}
|
20
vendor/consolidation/annotated-command/src/Parser/Internal/CommandDocBlockParserFactory.php
vendored
Normal file
20
vendor/consolidation/annotated-command/src/Parser/Internal/CommandDocBlockParserFactory.php
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Parser\Internal;
|
||||
|
||||
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
|
||||
|
||||
/**
|
||||
* Create an appropriate CommandDocBlockParser.
|
||||
*/
|
||||
class CommandDocBlockParserFactory
|
||||
{
|
||||
public static function parse(CommandInfo $commandInfo, \ReflectionMethod $reflection)
|
||||
{
|
||||
return static::create($commandInfo, $reflection)->parse();
|
||||
}
|
||||
|
||||
private static function create(CommandInfo $commandInfo, \ReflectionMethod $reflection)
|
||||
{
|
||||
return new BespokeDocBlockParser($commandInfo, $reflection);
|
||||
}
|
||||
}
|
49
vendor/consolidation/annotated-command/src/Parser/Internal/CsvUtils.php
vendored
Normal file
49
vendor/consolidation/annotated-command/src/Parser/Internal/CsvUtils.php
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Parser\Internal;
|
||||
|
||||
/**
|
||||
* Methods to convert to / from a csv string.
|
||||
*/
|
||||
class CsvUtils
|
||||
{
|
||||
/**
|
||||
* Ensure that the provided data is a string.
|
||||
*
|
||||
* @param string|array $data The data to convert to a string.
|
||||
* @return string
|
||||
*/
|
||||
public static function toString($data)
|
||||
{
|
||||
if (is_array($data)) {
|
||||
return static::csvEscape($data);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to a csv.
|
||||
*/
|
||||
public static function csvEscape(array $data, $delimiter = ',')
|
||||
{
|
||||
$buffer = fopen('php://temp', 'r+');
|
||||
fputcsv($buffer, $data, $delimiter);
|
||||
rewind($buffer);
|
||||
$csv = fgets($buffer);
|
||||
fclose($buffer);
|
||||
return rtrim($csv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a specific named annotation for this command.
|
||||
*
|
||||
* @param string|array $data The data to convert to an array.
|
||||
* @return array
|
||||
*/
|
||||
public static function toList($data)
|
||||
{
|
||||
if (!is_array($data)) {
|
||||
return str_getcsv($data);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
155
vendor/consolidation/annotated-command/src/Parser/Internal/DocblockTag.php
vendored
Normal file
155
vendor/consolidation/annotated-command/src/Parser/Internal/DocblockTag.php
vendored
Normal file
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Parser\Internal;
|
||||
|
||||
/**
|
||||
* Hold the tag definition for one tag in a DocBlock.
|
||||
*
|
||||
* The tag can be sliced into the following forms:
|
||||
* - "@tag content"
|
||||
* - "@tag word description"
|
||||
* - "@tag $variable description"
|
||||
* - "@tag word $variable description"
|
||||
*/
|
||||
class DocblockTag
|
||||
{
|
||||
/** @var string Name of the tag */
|
||||
protected $tag;
|
||||
|
||||
/** @var string|null Contents of the tag. */
|
||||
protected $content;
|
||||
|
||||
const TAG_REGEX = '@(?P<tag>[^\s$]+)[\s]*';
|
||||
const VARIABLE_REGEX = '\\$(?P<variable>[^\s$]+)[\s]*';
|
||||
const VARIABLE_OR_WORD_REGEX = '\\$?(?P<variable>[^\s$]+)[\s]*';
|
||||
const TYPE_REGEX = '(?P<type>[^\s$]+)[\s]*';
|
||||
const WORD_REGEX = '(?P<word>[^\s$]+)[\s]*';
|
||||
const DESCRIPTION_REGEX = '(?P<description>.*)';
|
||||
const IS_TAG_REGEX = '/^[*\s]*@/';
|
||||
|
||||
/**
|
||||
* Check if the provided string begins with a tag
|
||||
* @param string $subject
|
||||
* @return bool
|
||||
*/
|
||||
public static function isTag($subject)
|
||||
{
|
||||
return preg_match(self::IS_TAG_REGEX, $subject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a regular expression to separate the tag from the content.
|
||||
*
|
||||
* @param string $subject
|
||||
* @param string[] &$matches Sets $matches['tag'] and $matches['description']
|
||||
* @return bool
|
||||
*/
|
||||
public static function splitTagAndContent($subject, &$matches)
|
||||
{
|
||||
$regex = '/' . self::TAG_REGEX . self::DESCRIPTION_REGEX . '/s';
|
||||
return preg_match($regex, $subject, $matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* DockblockTag constructor
|
||||
*/
|
||||
public function __construct($tag, $content = null)
|
||||
{
|
||||
$this->tag = $tag;
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add more content onto a tag during parsing.
|
||||
*/
|
||||
public function appendContent($line)
|
||||
{
|
||||
$this->content .= "\n$line";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tag - e.g. "@foo description" returns 'foo'
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTag()
|
||||
{
|
||||
return $this->tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the content portion of the tag - e.g. "@foo bar baz boz" returns
|
||||
* "bar baz boz"
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert tag back into a string.
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return '@' . $this->getTag() . ' ' . $this->getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if tag is one of:
|
||||
* - "@tag variable description"
|
||||
* - "@tag $variable description"
|
||||
* - "@tag type $variable description"
|
||||
*
|
||||
* @param string $subject
|
||||
* @param string[] &$matches Sets $matches['variable'] and
|
||||
* $matches['description']; might set $matches['type'].
|
||||
* @return bool
|
||||
*/
|
||||
public function hasVariable(&$matches)
|
||||
{
|
||||
return
|
||||
$this->hasTypeVariableAndDescription($matches) ||
|
||||
$this->hasVariableAndDescription($matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if tag is "@tag $variable description"
|
||||
* @param string $subject
|
||||
* @param string[] &$matches Sets $matches['variable'] and
|
||||
* $matches['description']
|
||||
* @return bool
|
||||
*/
|
||||
public function hasVariableAndDescription(&$matches)
|
||||
{
|
||||
$regex = '/^\s*' . self::VARIABLE_OR_WORD_REGEX . self::DESCRIPTION_REGEX . '/s';
|
||||
return preg_match($regex, $this->getContent(), $matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if tag is "@tag type $variable description"
|
||||
*
|
||||
* @param string $subject
|
||||
* @param string[] &$matches Sets $matches['variable'],
|
||||
* $matches['description'] and $matches['type'].
|
||||
* @return bool
|
||||
*/
|
||||
public function hasTypeVariableAndDescription(&$matches)
|
||||
{
|
||||
$regex = '/^\s*' . self::TYPE_REGEX . self::VARIABLE_REGEX . self::DESCRIPTION_REGEX . '/s';
|
||||
return preg_match($regex, $this->getContent(), $matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if tag is "@tag word description"
|
||||
* @param string $subject
|
||||
* @param string[] &$matches Sets $matches['word'] and
|
||||
* $matches['description']
|
||||
* @return bool
|
||||
*/
|
||||
public function hasWordAndDescription(&$matches)
|
||||
{
|
||||
$regex = '/^\s*' . self::WORD_REGEX . self::DESCRIPTION_REGEX . '/s';
|
||||
return preg_match($regex, $this->getContent(), $matches);
|
||||
}
|
||||
}
|
106
vendor/consolidation/annotated-command/src/Parser/Internal/FullyQualifiedClassCache.php
vendored
Normal file
106
vendor/consolidation/annotated-command/src/Parser/Internal/FullyQualifiedClassCache.php
vendored
Normal file
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Parser\Internal;
|
||||
|
||||
class FullyQualifiedClassCache
|
||||
{
|
||||
protected $classCache = [];
|
||||
protected $namespaceCache = [];
|
||||
|
||||
public function qualify($filename, $className)
|
||||
{
|
||||
$this->primeCache($filename, $className);
|
||||
return $this->cached($filename, $className);
|
||||
}
|
||||
|
||||
protected function cached($filename, $className)
|
||||
{
|
||||
return isset($this->classCache[$filename][$className]) ? $this->classCache[$filename][$className] : $className;
|
||||
}
|
||||
|
||||
protected function primeCache($filename, $className)
|
||||
{
|
||||
// If the cache has already been primed, do no further work
|
||||
if (isset($this->namespaceCache[$filename])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$handle = fopen($filename, "r");
|
||||
if (!$handle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$namespaceName = $this->primeNamespaceCache($filename, $handle);
|
||||
$this->primeUseCache($filename, $handle);
|
||||
|
||||
// If there is no 'use' statement for the className, then
|
||||
// generate an effective classname from the namespace
|
||||
if (!isset($this->classCache[$filename][$className])) {
|
||||
$this->classCache[$filename][$className] = $namespaceName . '\\' . $className;
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
protected function primeNamespaceCache($filename, $handle)
|
||||
{
|
||||
$namespaceName = $this->readNamespace($handle);
|
||||
if (!$namespaceName) {
|
||||
return false;
|
||||
}
|
||||
$this->namespaceCache[$filename] = $namespaceName;
|
||||
return $namespaceName;
|
||||
}
|
||||
|
||||
protected function primeUseCache($filename, $handle)
|
||||
{
|
||||
$usedClasses = $this->readUseStatements($handle);
|
||||
if (empty($usedClasses)) {
|
||||
return false;
|
||||
}
|
||||
$this->classCache[$filename] = $usedClasses;
|
||||
}
|
||||
|
||||
protected function readNamespace($handle)
|
||||
{
|
||||
$namespaceRegex = '#^\s*namespace\s+#';
|
||||
$line = $this->readNextRelevantLine($handle);
|
||||
if (!$line || !preg_match($namespaceRegex, $line)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$namespaceName = preg_replace($namespaceRegex, '', $line);
|
||||
$namespaceName = rtrim($namespaceName, ';');
|
||||
return $namespaceName;
|
||||
}
|
||||
|
||||
protected function readUseStatements($handle)
|
||||
{
|
||||
$useRegex = '#^\s*use\s+#';
|
||||
$result = [];
|
||||
while (true) {
|
||||
$line = $this->readNextRelevantLine($handle);
|
||||
if (!$line || !preg_match($useRegex, $line)) {
|
||||
return $result;
|
||||
}
|
||||
$usedClass = preg_replace($useRegex, '', $line);
|
||||
$usedClass = rtrim($usedClass, ';');
|
||||
$unqualifiedClass = preg_replace('#.*\\\\#', '', $usedClass);
|
||||
// If this is an aliased class, 'use \Foo\Bar as Baz', then adjust
|
||||
if (strpos($usedClass, ' as ')) {
|
||||
$unqualifiedClass = preg_replace('#.*\sas\s+#', '', $usedClass);
|
||||
$usedClass = preg_replace('#\s+as\s+#', '', $usedClass);
|
||||
}
|
||||
$result[$unqualifiedClass] = $usedClass;
|
||||
}
|
||||
}
|
||||
|
||||
protected function readNextRelevantLine($handle)
|
||||
{
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
if (preg_match('#^\s*\w#', $line)) {
|
||||
return trim($line);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
67
vendor/consolidation/annotated-command/src/Parser/Internal/TagFactory.php
vendored
Normal file
67
vendor/consolidation/annotated-command/src/Parser/Internal/TagFactory.php
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
namespace Consolidation\AnnotatedCommand\Parser\Internal;
|
||||
|
||||
/**
|
||||
* Hold some state. Collect tags.
|
||||
*/
|
||||
class TagFactory
|
||||
{
|
||||
/** @var DocblockTag|null Current tag */
|
||||
protected $current;
|
||||
|
||||
/** @var DocblockTag[] All tag */
|
||||
protected $tags;
|
||||
|
||||
/**
|
||||
* DocblockTag constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->current = null;
|
||||
$this->tags = [];
|
||||
}
|
||||
|
||||
public function parseLine($line)
|
||||
{
|
||||
if (DocblockTag::isTag($line)) {
|
||||
return $this->createTag($line);
|
||||
}
|
||||
if (empty($line)) {
|
||||
return $this->storeCurrentTag();
|
||||
}
|
||||
return $this->accumulateContent($line);
|
||||
}
|
||||
|
||||
public function getTags()
|
||||
{
|
||||
$this->storeCurrentTag();
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
protected function createTag($line)
|
||||
{
|
||||
DocblockTag::splitTagAndContent($line, $matches);
|
||||
$this->storeCurrentTag();
|
||||
$this->current = new DocblockTag($matches['tag'], $matches['description']);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function storeCurrentTag()
|
||||
{
|
||||
if (!$this->current) {
|
||||
return false;
|
||||
}
|
||||
$this->tags[] = $this->current;
|
||||
$this->current = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function accumulateContent($line)
|
||||
{
|
||||
if (!$this->current) {
|
||||
return false;
|
||||
}
|
||||
$this->current->appendContent($line);
|
||||
return true;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue