Completed
Push — master ( bd07a2...bc095f )
by Stephen
19s queued 14s
created

CompletionCommand::execute()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 41
rs 8.6417
c 0
b 0
f 0
cc 6
nc 6
nop 2
1
<?php
2
3
namespace Stecman\Component\Symfony\Console\BashCompletion;
4
5
use Symfony\Component\Console\Command\Command as SymfonyCommand;
6
use Symfony\Component\Console\Input\InputDefinition;
7
use Symfony\Component\Console\Input\InputInterface;
8
use Symfony\Component\Console\Input\InputOption;
9
use Symfony\Component\Console\Output\OutputInterface;
10
11
class CompletionCommand extends SymfonyCommand
12
{
13
    /**
14
     * @var CompletionHandler
15
     */
16
    protected $handler;
17
18
    protected function configure()
19
    {
20
        $this
21
            ->setName('_completion')
22
            ->setDefinition($this->createDefinition())
23
            ->setDescription('BASH completion hook.')
24
            ->setHelp(<<<END
25
To enable BASH completion, run:
26
27
    <comment>eval `[program] _completion -g`</comment>.
28
29
Or for an alias:
30
31
    <comment>eval `[program] _completion -g -p [alias]`</comment>.
32
33
END
34
            );
35
36
        // Hide this command from listing if supported
37
        // Command::setHidden() was not available before Symfony 3.2.0
38
        if (method_exists($this, 'setHidden')) {
39
            $this->setHidden(true);
40
        }
41
    }
42
43
    /**
44
     * {@inheritdoc}
45
     */
46
    public function getNativeDefinition()
47
    {
48
        return $this->createDefinition();
49
    }
50
51
    /**
52
     * Ignore user-defined global options
53
     *
54
     * Any global options defined by user-code are meaningless to this command.
55
     * Options outside of the core defaults are ignored to avoid name and shortcut conflicts.
56
     */
57
    public function mergeApplicationDefinition($mergeArgs = true)
58
    {
59
        // Get current application options
60
        $appDefinition = $this->getApplication()->getDefinition();
61
        $originalOptions = $appDefinition->getOptions();
62
63
        // Temporarily replace application options with a filtered list
64
        $appDefinition->setOptions(
65
            $this->filterApplicationOptions($originalOptions)
66
        );
67
68
        parent::mergeApplicationDefinition($mergeArgs);
69
70
        // Restore original application options
71
        $appDefinition->setOptions($originalOptions);
72
    }
73
74
    /**
75
     * Reduce the passed list of options to the core defaults (if they exist)
76
     *
77
     * @param InputOption[] $appOptions
78
     * @return InputOption[]
79
     */
80
    protected function filterApplicationOptions(array $appOptions)
81
    {
82
        return array_filter($appOptions, function(InputOption $option) {
83
            static $coreOptions = array(
84
                'help' => true,
85
                'quiet' => true,
86
                'verbose' => true,
87
                'version' => true,
88
                'ansi' => true,
89
                'no-ansi' => true,
90
                'no-interaction' => true,
91
            );
92
93
            return isset($coreOptions[$option->getName()]);
94
        });
95
    }
96
97
    protected function execute(InputInterface $input, OutputInterface $output)
98
    {
99
        $this->handler = new CompletionHandler($this->getApplication());
100
        $handler = $this->handler;
101
102
        if ($input->getOption('generate-hook')) {
103
            global $argv;
104
            $program = $argv[0];
105
106
            $factory = new HookFactory();
107
            $alias = $input->getOption('program');
108
            $multiple = (bool)$input->getOption('multiple');
109
110
            if (!$alias) {
111
                $alias = basename($program);
112
            }
113
114
            $hook = $factory->generateHook(
115
                $input->getOption('shell-type') ?: $this->getShellType(),
116
                $program,
117
                $alias,
118
                $multiple
119
            );
120
121
            $output->write($hook, true);
122
        } else {
123
            $handler->setContext(new EnvironmentCompletionContext());
124
125
            // Get completion results
126
            $results = $this->runCompletion();
0 ignored issues
show
Deprecated Code introduced by
The method Stecman\Component\Symfon...ommand::runCompletion() has been deprecated with message: - This will be removed in 1.0.0 in favour of CompletionCommand::configureCompletion

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
127
128
            // Escape results for the current shell
129
            $shellType = $input->getOption('shell-type') ?: $this->getShellType();
130
131
            foreach ($results as &$result) {
132
                $result = $this->escapeForShell($result, $shellType);
133
            }
134
135
            $output->write($results, true);
0 ignored issues
show
Documentation introduced by
$results is of type array<integer,string>, but the function expects a string|object<Symfony\Co...onsole\Output\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
136
        }
137
    }
138
139
    /**
140
     * Escape each completion result for the specified shell
141
     *
142
     * @param string $result - Completion results that should appear in the shell
143
     * @param string $shellType - Valid shell type from HookFactory
144
     * @return string
145
     */
146
    protected function escapeForShell($result, $shellType)
147
    {
148
        switch ($shellType) {
149
            // BASH requires special escaping for multi-word and special character results
150
            // This emulates registering completion with`-o filenames`, without side-effects like dir name slashes
151
            case 'bash':
152
                $context = $this->handler->getContext();
153
                $wordStart = substr($context->getRawCurrentWord(), 0, 1);
154
155
                if ($wordStart == "'") {
156
                    // If the current word is single-quoted, escape any single quotes in the result
157
                    $result = str_replace("'", "\\'", $result);
158
                } else if ($wordStart == '"') {
159
                    // If the current word is double-quoted, escape any double quotes in the result
160
                    $result = str_replace('"', '\\"', $result);
161
                } else {
162
                    // Otherwise assume the string is unquoted and word breaks should be escaped
163
                    $result = preg_replace('/([\s\'"\\\\])/', '\\\\$1', $result);
164
                }
165
166
                // Escape output to prevent special characters being lost when passing results to compgen
167
                return escapeshellarg($result);
168
169
            // No transformation by default
170
            default:
171
                return $result;
172
        }
173
    }
174
175
    /**
176
     * Run the completion handler and return a filtered list of results
177
     *
178
     * @deprecated - This will be removed in 1.0.0 in favour of CompletionCommand::configureCompletion
179
     *
180
     * @return string[]
181
     */
182
    protected function runCompletion()
183
    {
184
        $this->configureCompletion($this->handler);
185
        return $this->handler->runCompletion();
186
    }
187
188
    /**
189
     * Configure the CompletionHandler instance before it is run
190
     *
191
     * @param CompletionHandler $handler
192
     */
193
    protected function configureCompletion(CompletionHandler $handler)
0 ignored issues
show
Unused Code introduced by
The parameter $handler is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
194
    {
195
        // Override this method to configure custom value completions
196
    }
197
198
    /**
199
     * Determine the shell type for use with HookFactory
200
     *
201
     * @return string
202
     */
203
    protected function getShellType()
204
    {
205
        if (!getenv('SHELL')) {
206
            throw new \RuntimeException('Could not read SHELL environment variable. Please specify your shell type using the --shell-type option.');
207
        }
208
209
        return basename(getenv('SHELL'));
210
    }
211
212
    protected function createDefinition()
213
    {
214
        return new InputDefinition(array(
215
            new InputOption(
216
                'generate-hook',
217
                'g',
218
                InputOption::VALUE_NONE,
219
                'Generate BASH code that sets up completion for this application.'
220
            ),
221
            new InputOption(
222
                'program',
223
                'p',
224
                InputOption::VALUE_REQUIRED,
225
                "Program name that should trigger completion\n<comment>(defaults to the absolute application path)</comment>."
226
            ),
227
            new InputOption(
228
                'multiple',
229
                'm',
230
                InputOption::VALUE_NONE,
231
                "Generated hook can be used for multiple applications."
232
            ),
233
            new InputOption(
234
                'shell-type',
235
                null,
236
                InputOption::VALUE_OPTIONAL,
237
                'Set the shell type (zsh or bash). Otherwise this is determined automatically.'
238
            ),
239
        ));
240
    }
241
}
242