Completed
Push — develop ( f11ef2...d41b65 )
by David
06:01 queued 11s
created

ArgvInput::parseShortOption()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 1
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Symfony\Component\Console\Input;
13
14
use Symfony\Component\Console\Exception\RuntimeException;
15
16
/**
17
 * ArgvInput represents an input coming from the CLI arguments.
18
 *
19
 * Usage:
20
 *
21
 *     $input = new ArgvInput();
22
 *
23
 * By default, the `$_SERVER['argv']` array is used for the input values.
24
 *
25
 * This can be overridden by explicitly passing the input values in the constructor:
26
 *
27
 *     $input = new ArgvInput($_SERVER['argv']);
28
 *
29
 * If you pass it yourself, don't forget that the first element of the array
30
 * is the name of the running application.
31
 *
32
 * When passing an argument to the constructor, be sure that it respects
33
 * the same rules as the argv one. It's almost always better to use the
34
 * `StringInput` when you want to provide your own input.
35
 *
36
 * @author Fabien Potencier <[email protected]>
37
 *
38
 * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
39
 * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02
40
 */
41
class ArgvInput extends Input
42
{
43
    private $tokens;
44
    private $parsed;
45
46
    public function __construct(array $argv = null, InputDefinition $definition = null)
47
    {
48
        if (null === $argv) {
49
            $argv = $_SERVER['argv'];
50
        }
51
52
        // strip the application name
53
        array_shift($argv);
54
55
        $this->tokens = $argv;
56
57
        parent::__construct($definition);
58
    }
59
60
    protected function setTokens(array $tokens)
61
    {
62
        $this->tokens = $tokens;
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68
    protected function parse()
69
    {
70
        $parseOptions = true;
71
        $this->parsed = $this->tokens;
72
        while (null !== $token = array_shift($this->parsed)) {
73
            if ($parseOptions && '' == $token) {
74
                $this->parseArgument($token);
75
            } elseif ($parseOptions && '--' == $token) {
76
                $parseOptions = false;
77
            } elseif ($parseOptions && 0 === strpos($token, '--')) {
78
                $this->parseLongOption($token);
79
            } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
80
                $this->parseShortOption($token);
81
            } else {
82
                $this->parseArgument($token);
83
            }
84
        }
85
    }
86
87
    /**
88
     * Parses a short option.
89
     */
90
    private function parseShortOption(string $token)
91
    {
92
        $name = substr($token, 1);
93
94
        if (\strlen($name) > 1) {
95
            if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) {
96
                // an option with a value (with no space)
97
                $this->addShortOption($name[0], substr($name, 1));
98
            } else {
99
                $this->parseShortOptionSet($name);
100
            }
101
        } else {
102
            $this->addShortOption($name, null);
103
        }
104
    }
105
106
    /**
107
     * Parses a short option set.
108
     *
109
     * @throws RuntimeException When option given doesn't exist
110
     */
111
    private function parseShortOptionSet(string $name)
112
    {
113
        $len = \strlen($name);
114
        for ($i = 0; $i < $len; ++$i) {
115
            if (!$this->definition->hasShortcut($name[$i])) {
116
                $encoding = mb_detect_encoding($name, null, true);
117
                throw new RuntimeException(sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding)));
118
            }
119
120
            $option = $this->definition->getOptionForShortcut($name[$i]);
121
            if ($option->acceptValue()) {
122
                $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));
123
124
                break;
125
            } else {
126
                $this->addLongOption($option->getName(), null);
127
            }
128
        }
129
    }
130
131
    /**
132
     * Parses a long option.
133
     */
134
    private function parseLongOption(string $token)
135
    {
136
        $name = substr($token, 2);
137
138
        if (false !== $pos = strpos($name, '=')) {
139
            if (0 === \strlen($value = substr($name, $pos + 1))) {
140
                array_unshift($this->parsed, $value);
141
            }
142
            $this->addLongOption(substr($name, 0, $pos), $value);
143
        } else {
144
            $this->addLongOption($name, null);
145
        }
146
    }
147
148
    /**
149
     * Parses an argument.
150
     *
151
     * @throws RuntimeException When too many arguments are given
152
     */
153
    private function parseArgument(string $token)
154
    {
155
        $c = \count($this->arguments);
156
157
        // if input is expecting another argument, add it
158
        if ($this->definition->hasArgument($c)) {
159
            $arg = $this->definition->getArgument($c);
160
            $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token;
161
162
        // if last argument isArray(), append token to last argument
163
        } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
164
            $arg = $this->definition->getArgument($c - 1);
165
            $this->arguments[$arg->getName()][] = $token;
166
167
        // unexpected argument
168
        } else {
169
            $all = $this->definition->getArguments();
170
            if (\count($all)) {
171
                throw new RuntimeException(sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all))));
172
            }
173
174
            throw new RuntimeException(sprintf('No arguments expected, got "%s".', $token));
175
        }
176
    }
177
178
    /**
179
     * Adds a short option value.
180
     *
181
     * @throws RuntimeException When option given doesn't exist
182
     */
183 View Code Duplication
    private function addShortOption(string $shortcut, $value)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
184
    {
185
        if (!$this->definition->hasShortcut($shortcut)) {
186
            throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
187
        }
188
189
        $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
190
    }
191
192
    /**
193
     * Adds a long option value.
194
     *
195
     * @throws RuntimeException When option given doesn't exist
196
     */
197
    private function addLongOption(string $name, $value)
198
    {
199
        if (!$this->definition->hasOption($name)) {
200
            throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name));
201
        }
202
203
        $option = $this->definition->getOption($name);
204
205
        if (null !== $value && !$option->acceptValue()) {
206
            throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
207
        }
208
209
        if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) {
210
            // if option accepts an optional or mandatory argument
211
            // let's see if there is one provided
212
            $next = array_shift($this->parsed);
213
            if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, ['', null], true)) {
214
                $value = $next;
215
            } else {
216
                array_unshift($this->parsed, $next);
217
            }
218
        }
219
220 View Code Duplication
        if (null === $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
221
            if ($option->isValueRequired()) {
222
                throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name));
223
            }
224
225
            if (!$option->isArray() && !$option->isValueOptional()) {
226
                $value = true;
227
            }
228
        }
229
230
        if ($option->isArray()) {
231
            $this->options[$name][] = $value;
232
        } else {
233
            $this->options[$name] = $value;
234
        }
235
    }
236
237
    /**
238
     * {@inheritdoc}
239
     */
240
    public function getFirstArgument()
241
    {
242
        $isOption = false;
243
        foreach ($this->tokens as $i => $token) {
244
            if ($token && '-' === $token[0]) {
245
                if (false !== strpos($token, '=') || !isset($this->tokens[$i + 1])) {
246
                    continue;
247
                }
248
249
                // If it's a long option, consider that everything after "--" is the option name.
250
                // Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator)
251
                $name = '-' === $token[1] ? substr($token, 2) : substr($token, -1);
252
                if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) {
253
                    // noop
254
                } elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) {
255
                    $isOption = true;
256
                }
257
258
                continue;
259
            }
260
261
            if ($isOption) {
262
                $isOption = false;
263
                continue;
264
            }
265
266
            return $token;
267
        }
268
269
        return null;
270
    }
271
272
    /**
273
     * {@inheritdoc}
274
     */
275
    public function hasParameterOption($values, bool $onlyParams = false)
276
    {
277
        $values = (array) $values;
278
279
        foreach ($this->tokens as $token) {
280
            if ($onlyParams && '--' === $token) {
281
                return false;
282
            }
283
            foreach ($values as $value) {
284
                // Options with values:
285
                //   For long options, test for '--option=' at beginning
286
                //   For short options, test for '-o' at beginning
287
                $leading = 0 === strpos($value, '--') ? $value.'=' : $value;
288
                if ($token === $value || '' !== $leading && 0 === strpos($token, $leading)) {
289
                    return true;
290
                }
291
            }
292
        }
293
294
        return false;
295
    }
296
297
    /**
298
     * {@inheritdoc}
299
     */
300
    public function getParameterOption($values, $default = false, bool $onlyParams = false)
301
    {
302
        $values = (array) $values;
303
        $tokens = $this->tokens;
304
305
        while (0 < \count($tokens)) {
306
            $token = array_shift($tokens);
307
            if ($onlyParams && '--' === $token) {
308
                return $default;
309
            }
310
311
            foreach ($values as $value) {
312
                if ($token === $value) {
313
                    return array_shift($tokens);
314
                }
315
                // Options with values:
316
                //   For long options, test for '--option=' at beginning
317
                //   For short options, test for '-o' at beginning
318
                $leading = 0 === strpos($value, '--') ? $value.'=' : $value;
319
                if ('' !== $leading && 0 === strpos($token, $leading)) {
320
                    return substr($token, \strlen($leading));
321
                }
322
            }
323
        }
324
325
        return $default;
326
    }
327
328
    /**
329
     * Returns a stringified representation of the args passed to the command.
330
     *
331
     * @return string
332
     */
333
    public function __toString()
334
    {
335
        $tokens = array_map(function ($token) {
336
            if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
337
                return $match[1].$this->escapeToken($match[2]);
338
            }
339
340
            if ($token && '-' !== $token[0]) {
341
                return $this->escapeToken($token);
342
            }
343
344
            return $token;
345
        }, $this->tokens);
346
347
        return implode(' ', $tokens);
348
    }
349
}
350