Version 1
[yaffs-website] / vendor / stecman / symfony-console-completion / src / CompletionHandler.php
diff --git a/vendor/stecman/symfony-console-completion/src/CompletionHandler.php b/vendor/stecman/symfony-console-completion/src/CompletionHandler.php
new file mode 100644 (file)
index 0000000..531fae3
--- /dev/null
@@ -0,0 +1,445 @@
+<?php
+
+namespace Stecman\Component\Symfony\Console\BashCompletion;
+
+use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionAwareInterface;
+use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionInterface;
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\ArrayInput;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputOption;
+
+class CompletionHandler
+{
+    /**
+     * Application to complete for
+     * @var \Symfony\Component\Console\Application
+     */
+    protected $application;
+
+    /**
+     * @var Command
+     */
+    protected $command;
+
+    /**
+     * @var CompletionContext
+     */
+    protected $context;
+
+    /**
+     * Array of completion helpers.
+     * @var CompletionInterface[]
+     */
+    protected $helpers = array();
+
+    public function __construct(Application $application, CompletionContext $context = null)
+    {
+        $this->application = $application;
+        $this->context = $context;
+
+        $this->addHandler(
+            new Completion(
+                'help',
+                'command_name',
+                Completion::TYPE_ARGUMENT,
+                array_keys($application->all())
+            )
+        );
+
+        $this->addHandler(
+            new Completion(
+                'list',
+                'namespace',
+                Completion::TYPE_ARGUMENT,
+                $application->getNamespaces()
+            )
+        );
+    }
+
+    public function setContext(CompletionContext $context)
+    {
+        $this->context = $context;
+    }
+
+    /**
+     * @return CompletionContext
+     */
+    public function getContext()
+    {
+        return $this->context;
+    }
+
+    /**
+     * @param CompletionInterface[] $array
+     */
+    public function addHandlers(array $array)
+    {
+        $this->helpers = array_merge($this->helpers, $array);
+    }
+
+    /**
+     * @param CompletionInterface $helper
+     */
+    public function addHandler(CompletionInterface $helper)
+    {
+        $this->helpers[] = $helper;
+    }
+
+    /**
+     * Do the actual completion, returning an array of strings to provide to the parent shell's completion system
+     *
+     * @throws \RuntimeException
+     * @return string[]
+     */
+    public function runCompletion()
+    {
+        if (!$this->context) {
+            throw new \RuntimeException('A CompletionContext must be set before requesting completion.');
+        }
+
+        $cmdName = $this->getInput()->getFirstArgument();
+
+        try {
+            $this->command = $this->application->find($cmdName);
+        } catch (\InvalidArgumentException $e) {
+            // Exception thrown, when multiple or none commands are found.
+        }
+
+        $process = array(
+            'completeForOptionValues',
+            'completeForOptionShortcuts',
+            'completeForOptionShortcutValues',
+            'completeForOptions',
+            'completeForCommandName',
+            'completeForCommandArguments'
+        );
+
+        foreach ($process as $methodName) {
+            $result = $this->{$methodName}();
+
+            if (false !== $result) {
+                // Return the result of the first completion mode that matches
+                return $this->filterResults((array) $result);
+            }
+        }
+
+        return array();
+    }
+
+    /**
+     * Get an InputInterface representation of the completion context
+     *
+     * @return ArrayInput
+     */
+    public function getInput()
+    {
+        // Filter the command line content to suit ArrayInput
+        $words = $this->context->getWords();
+        array_shift($words);
+        $words = array_filter($words);
+
+        return new ArrayInput($words);
+    }
+
+    /**
+     * Attempt to complete the current word as a long-form option (--my-option)
+     *
+     * @return array|false
+     */
+    protected function completeForOptions()
+    {
+        $word = $this->context->getCurrentWord();
+
+        if (substr($word, 0, 2) === '--') {
+            $options = array();
+
+            foreach ($this->getAllOptions() as $opt) {
+                $options[] = '--'.$opt->getName();
+            }
+
+            return $options;
+        }
+
+        return false;
+    }
+
+    /**
+     * Attempt to complete the current word as an option shortcut.
+     *
+     * If the shortcut exists it will be completed, but a list of possible shortcuts is never returned for completion.
+     *
+     * @return array|false
+     */
+    protected function completeForOptionShortcuts()
+    {
+        $word = $this->context->getCurrentWord();
+
+        if (strpos($word, '-') === 0 && strlen($word) == 2) {
+            $definition = $this->command ? $this->command->getNativeDefinition() : $this->application->getDefinition();
+
+            if ($definition->hasShortcut(substr($word, 1))) {
+                return array($word);
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Attempt to complete the current word as the value of an option shortcut
+     *
+     * @return array|false
+     */
+    protected function completeForOptionShortcutValues()
+    {
+        $wordIndex = $this->context->getWordIndex();
+
+        if ($this->command && $wordIndex > 1) {
+            $left = $this->context->getWordAtIndex($wordIndex - 1);
+
+            // Complete short options
+            if ($left[0] == '-' && strlen($left) == 2) {
+                $shortcut = substr($left, 1);
+                $def = $this->command->getNativeDefinition();
+
+                if (!$def->hasShortcut($shortcut)) {
+                    return false;
+                }
+
+                $opt = $def->getOptionForShortcut($shortcut);
+                if ($opt->isValueRequired() || $opt->isValueOptional()) {
+                    return $this->completeOption($opt);
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Attemp to complete the current word as the value of a long-form option
+     *
+     * @return array|false
+     */
+    protected function completeForOptionValues()
+    {
+        $wordIndex = $this->context->getWordIndex();
+
+        if ($this->command && $wordIndex > 1) {
+            $left = $this->context->getWordAtIndex($wordIndex - 1);
+
+            if (strpos($left, '--') === 0) {
+                $name = substr($left, 2);
+                $def = $this->command->getNativeDefinition();
+
+                if (!$def->hasOption($name)) {
+                    return false;
+                }
+
+                $opt = $def->getOption($name);
+                if ($opt->isValueRequired() || $opt->isValueOptional()) {
+                    return $this->completeOption($opt);
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Attempt to complete the current word as a command name
+     *
+     * @return array|false
+     */
+    protected function completeForCommandName()
+    {
+        if (!$this->command || (count($this->context->getWords()) == 2 && $this->context->getWordIndex() == 1)) {
+            $commands = $this->application->all();
+            $names = array_keys($commands);
+
+            if ($key = array_search('_completion', $names)) {
+                unset($names[$key]);
+            }
+
+            return $names;
+        }
+
+        return false;
+    }
+
+    /**
+     * Attempt to complete the current word as a command argument value
+     *
+     * @see Symfony\Component\Console\Input\InputArgument
+     * @return array|false
+     */
+    protected function completeForCommandArguments()
+    {
+        if (!$this->command || strpos($this->context->getCurrentWord(), '-') === 0) {
+            return false;
+        }
+
+        $definition = $this->command->getNativeDefinition();
+        $argWords = $this->mapArgumentsToWords($definition->getArguments());
+        $wordIndex = $this->context->getWordIndex();
+
+        if (isset($argWords[$wordIndex])) {
+            $name = $argWords[$wordIndex];
+        } elseif (!empty($argWords) && $definition->getArgument(end($argWords))->isArray()) {
+            $name = end($argWords);
+        } else {
+            return false;
+        }
+
+        if ($helper = $this->getCompletionHelper($name, Completion::TYPE_ARGUMENT)) {
+            return $helper->run();
+        }
+
+        if ($this->command instanceof CompletionAwareInterface) {
+            return $this->command->completeArgumentValues($name, $this->context);
+        }
+
+        return false;
+    }
+
+    /**
+     * Find a CompletionInterface that matches the current command, target name, and target type
+     *
+     * @param string $name
+     * @param string $type
+     * @return CompletionInterface|null
+     */
+    protected function getCompletionHelper($name, $type)
+    {
+        foreach ($this->helpers as $helper) {
+            if ($helper->getType() != $type && $helper->getType() != CompletionInterface::ALL_TYPES) {
+                continue;
+            }
+
+            if ($helper->getCommandName() == CompletionInterface::ALL_COMMANDS || $helper->getCommandName() == $this->command->getName()) {
+                if ($helper->getTargetName() == $name) {
+                    return $helper;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Complete the value for the given option if a value completion is availble
+     *
+     * @param InputOption $option
+     * @return array|false
+     */
+    protected function completeOption(InputOption $option)
+    {
+        if ($helper = $this->getCompletionHelper($option->getName(), Completion::TYPE_OPTION)) {
+            return $helper->run();
+        }
+
+        if ($this->command instanceof CompletionAwareInterface) {
+            return $this->command->completeOptionValues($option->getName(), $this->context);
+        }
+
+        return false;
+    }
+
+    /**
+     * Step through the command line to determine which word positions represent which argument values
+     *
+     * The word indexes of argument values are found by eliminating words that are known to not be arguments (options,
+     * option values, and command names). Any word that doesn't match for elimination is assumed to be an argument value,
+     *
+     * @param InputArgument[] $argumentDefinitions
+     * @return array as [argument name => word index on command line]
+     */
+    protected function mapArgumentsToWords($argumentDefinitions)
+    {
+        $argumentPositions = array();
+        $argumentNumber = 0;
+        $previousWord = null;
+        $argumentNames = array_keys($argumentDefinitions);
+
+        // Build a list of option values to filter out
+        $optionsWithArgs = $this->getOptionWordsWithValues();
+
+        foreach ($this->context->getWords() as $wordIndex => $word) {
+            // Skip program name, command name, options, and option values
+            if ($wordIndex < 2
+                || ($word && '-' === $word[0])
+                || in_array($previousWord, $optionsWithArgs)) {
+                $previousWord = $word;
+                continue;
+            } else {
+                $previousWord = $word;
+            }
+
+            // If argument n exists, pair that argument's name with the current word
+            if (isset($argumentNames[$argumentNumber])) {
+                $argumentPositions[$wordIndex] = $argumentNames[$argumentNumber];
+            }
+
+            $argumentNumber++;
+        }
+
+        return $argumentPositions;
+    }
+
+    /**
+     * Build a list of option words/flags that will have a value after them
+     * Options are returned in the format they appear as on the command line.
+     *
+     * @return string[] - eg. ['--myoption', '-m', ... ]
+     */
+    protected function getOptionWordsWithValues()
+    {
+        $strings = array();
+
+        foreach ($this->getAllOptions() as $option) {
+            if ($option->isValueRequired()) {
+                $strings[] = '--' . $option->getName();
+
+                if ($option->getShortcut()) {
+                    $strings[] = '-' . $option->getShortcut();
+                }
+            }
+        }
+
+        return $strings;
+    }
+
+    /**
+     * Filter out results that don't match the current word on the command line
+     *
+     * @param string[] $array
+     * @return string[]
+     */
+    protected function filterResults(array $array)
+    {
+        $curWord = $this->context->getCurrentWord();
+
+        return array_filter($array, function($val) use ($curWord) {
+            return fnmatch($curWord.'*', $val);
+        });
+    }
+
+    /**
+     * Get the combined options of the application and entered command
+     *
+     * @return InputOption[]
+     */
+    protected function getAllOptions()
+    {
+        if (!$this->command) {
+            return $this->application->getDefinition()->getOptions();
+        }
+
+        return array_merge(
+            $this->command->getNativeDefinition()->getOptions(),
+            $this->application->getDefinition()->getOptions()
+        );
+    }
+}