1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the webmozart/console package. |
5
|
|
|
* |
6
|
|
|
* (c) Bernhard Schussek <[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 Webmozart\Console\Args; |
13
|
|
|
|
14
|
|
|
use RuntimeException; |
15
|
|
|
use Symfony\Component\Console\Input\ArgvInput; |
16
|
|
|
use Symfony\Component\Console\Input\InputArgument; |
17
|
|
|
use Webmozart\Console\Adapter\ArgsFormatInputDefinition; |
18
|
|
|
use Webmozart\Console\Api\Args\Args; |
19
|
|
|
use Webmozart\Console\Api\Args\ArgsParser; |
20
|
|
|
use Webmozart\Console\Api\Args\CannotParseArgsException; |
21
|
|
|
use Webmozart\Console\Api\Args\Format\ArgsFormat; |
22
|
|
|
use Webmozart\Console\Api\Args\RawArgs; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Default parser for {@link RawArgs} instances. |
26
|
|
|
* |
27
|
|
|
* This parser delegates most of the work to Symfony's {@link ArgvInput} class. |
28
|
|
|
* |
29
|
|
|
* @since 1.0 |
30
|
|
|
* |
31
|
|
|
* @author Bernhard Schussek <[email protected]> |
32
|
|
|
*/ |
33
|
|
|
class DefaultArgsParser extends ArgvInput implements ArgsParser |
34
|
|
|
{ |
35
|
|
|
/** |
36
|
|
|
* Creates a new parser. |
37
|
|
|
*/ |
38
|
307 |
|
public function __construct() |
39
|
|
|
{ |
40
|
|
|
// Hide the parent arguments from the public signature |
41
|
307 |
|
parent::__construct(); |
42
|
307 |
|
} |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* {@inheritdoc} |
46
|
|
|
*/ |
47
|
303 |
|
public function parseArgs(RawArgs $args, ArgsFormat $format, $lenient = false) |
48
|
|
|
{ |
49
|
303 |
|
$this->setTokens($args->getTokens()); |
50
|
|
|
|
51
|
303 |
|
$formatAdapter = new ArgsFormatInputDefinition($format); |
52
|
|
|
|
53
|
|
|
try { |
54
|
303 |
|
$this->bind($formatAdapter); |
55
|
13 |
|
} catch (RuntimeException $e) { |
56
|
13 |
|
if (!$lenient) { |
57
|
6 |
|
throw new CannotParseArgsException($e->getMessage()); |
58
|
|
|
} |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
// Prevent failing validation if not all command names are given |
62
|
297 |
|
$this->insertMissingCommandNames($formatAdapter, $lenient); |
63
|
|
|
|
64
|
|
|
try { |
65
|
296 |
|
$this->validate(); |
66
|
6 |
|
} catch (RuntimeException $e) { |
67
|
6 |
|
if (!$lenient) { |
68
|
3 |
|
throw new CannotParseArgsException($e->getMessage()); |
69
|
|
|
} |
70
|
|
|
} |
71
|
|
|
|
72
|
293 |
|
return $this->createArgs($format, $args); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Creates the arguments from the current class state. |
77
|
|
|
* |
78
|
|
|
* @param ArgsFormat $format The format. |
79
|
|
|
* @param RawArgs $rawArgs The raw arguments. |
80
|
|
|
* |
81
|
|
|
* @return Args The created console arguments. |
82
|
|
|
*/ |
83
|
293 |
|
private function createArgs(ArgsFormat $format, RawArgs $rawArgs) |
84
|
|
|
{ |
85
|
293 |
|
$args = new Args($format, $rawArgs); |
86
|
|
|
|
87
|
293 |
|
foreach ($this->arguments as $name => $value) { |
88
|
|
|
// Filter command names |
89
|
290 |
|
if ($format->hasArgument($name)) { |
90
|
290 |
|
$args->setArgument($name, $value); |
91
|
|
|
} |
92
|
|
|
} |
93
|
|
|
|
94
|
293 |
|
foreach ($this->options as $name => $value) { |
95
|
|
|
// Filter command options |
96
|
235 |
|
if ($format->hasOption($name)) { |
97
|
235 |
|
$args->setOption($name, $value); |
98
|
|
|
} |
99
|
|
|
} |
100
|
|
|
|
101
|
293 |
|
return $args; |
102
|
|
|
} |
103
|
|
|
|
104
|
297 |
|
private function insertMissingCommandNames(ArgsFormatInputDefinition $inputDefinition, $lenient = false) |
105
|
|
|
{ |
106
|
|
|
// Start with the default values of the arguments. |
107
|
297 |
|
$inputArguments = $inputDefinition->getArguments(); |
108
|
297 |
|
$fixedValues = array(); |
109
|
297 |
|
$commandNames = $inputDefinition->getCommandNamesByArgumentName(); |
110
|
|
|
|
111
|
|
|
// Flatten the actual arguments, in case they contain a multi-valued |
112
|
|
|
// argument. |
113
|
297 |
|
$actualValues = $this->flatten($this->arguments); |
114
|
|
|
|
115
|
|
|
// Reset all array pointers. |
116
|
297 |
|
reset($commandNames); |
117
|
297 |
|
reset($actualValues); |
118
|
297 |
|
reset($inputArguments); |
119
|
|
|
|
120
|
|
|
// Skip the command names. The resulting pointer is like this: |
121
|
|
|
// |
122
|
|
|
// actual: [ 0: remote, 1: origin, 2: foo/bar ] |
|
|
|
|
123
|
|
|
// ^ |
124
|
|
|
|
125
|
297 |
|
$this->skipCommandNames($actualValues, $commandNames); |
126
|
|
|
|
127
|
|
|
// Copy the command names into the fixed array. The result is: |
128
|
|
|
// |
129
|
|
|
// fixed: [ cmd1: remote, cmd2: add ] |
130
|
|
|
|
131
|
297 |
|
$this->copyArgumentValues($commandNames, $inputArguments, $fixedValues, $lenient); |
132
|
|
|
|
133
|
|
|
// Copy the remaining actual values. The result is: |
134
|
|
|
// |
135
|
|
|
// fixed: [ cmd1: remote, cmd2: add, name: origin, target: foo/bar ] |
136
|
|
|
|
137
|
297 |
|
$this->copyArgumentValues($actualValues, $inputArguments, $fixedValues, $lenient); |
138
|
|
|
|
139
|
|
|
// Overwrite all current arguments with the fixed values |
140
|
296 |
|
foreach ($fixedValues as $name => $value) { |
141
|
293 |
|
$this->arguments[$name] = $value; |
142
|
|
|
} |
143
|
296 |
|
} |
144
|
|
|
|
145
|
297 |
|
private function flatten(array $arguments, array &$result = array()) |
146
|
|
|
{ |
147
|
297 |
|
foreach ($arguments as $value) { |
148
|
238 |
|
if (is_array($value)) { |
149
|
3 |
|
$this->flatten($value, $result); |
150
|
|
|
} else { |
151
|
238 |
|
$result[] = $value; |
152
|
|
|
} |
153
|
|
|
} |
154
|
|
|
|
155
|
297 |
|
return $result; |
156
|
|
|
} |
157
|
|
|
|
158
|
297 |
|
private function skipCommandNames(array &$arguments, array $commandNames) |
159
|
|
|
{ |
160
|
297 |
|
reset($commandNames); |
161
|
|
|
|
162
|
297 |
|
while (null !== key($arguments) && null !== key($commandNames) && current($commandNames)->match(current($arguments))) { |
163
|
227 |
|
next($arguments); |
164
|
227 |
|
next($commandNames); |
165
|
|
|
} |
166
|
297 |
|
} |
167
|
|
|
|
168
|
297 |
|
private function copyArgumentValues(array &$actualValues, array &$inputArguments, array &$fixedValues, $lenient = false) |
169
|
|
|
{ |
170
|
|
|
// The starting point are two arrays of arguments with the array |
171
|
|
|
// pointers set: |
172
|
|
|
|
173
|
|
|
// values: [ 0: remote, 1: origin, 2: foo/bar ] |
|
|
|
|
174
|
|
|
// ^ |
175
|
|
|
// args: [ cmd1: Argument, cmd2: Argument, name: Argument, target: Argument ] |
176
|
|
|
// ^ |
177
|
|
|
|
178
|
|
|
// The fixed values may already contain values: |
179
|
|
|
|
180
|
|
|
// fixed: [ cmd1: remote, cmd2: add ] |
181
|
|
|
|
182
|
|
|
// The goal is to copy the actual values to the fixed array so that the |
183
|
|
|
// end result is: |
184
|
|
|
|
185
|
|
|
// fixed: [ cmd1: remote, cmd2: add, name: origin, target: foo/bar ] |
186
|
|
|
|
187
|
|
|
// Multi-valued arguments need special treatment. In this case, the |
188
|
|
|
// actual values are like this: |
189
|
|
|
|
190
|
|
|
// values: [ 0: remote, 1: one, 2: two, 3: three ] |
|
|
|
|
191
|
|
|
// ^ |
192
|
|
|
// args: [ cmd1: Argument, cmd2: Argument, multi: Argument ] |
193
|
|
|
// ^ |
194
|
|
|
|
195
|
|
|
// The expected result is: |
196
|
|
|
|
197
|
|
|
// fixed: [ cmd1: remote, cmd2: add, multi: [ one, two, three ] ] |
198
|
|
|
|
199
|
297 |
|
while (null !== key($actualValues)) { |
200
|
294 |
|
if (null === key($inputArguments)) { |
201
|
8 |
|
if ($lenient) { |
202
|
1 |
|
return; |
203
|
|
|
} |
204
|
|
|
|
205
|
7 |
|
throw new CannotParseArgsException('Too many arguments.'); |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** @var InputArgument $argument */ |
209
|
294 |
|
$argument = current($inputArguments); |
210
|
294 |
|
$name = $argument->getName(); |
211
|
294 |
|
$value = current($actualValues); |
212
|
|
|
|
213
|
|
|
// Append the value to multi-valued arguments |
214
|
294 |
|
if ($argument->isArray()) { |
215
|
3 |
|
if (!isset($fixedValues[$name])) { |
216
|
3 |
|
$fixedValues[$name] = array(); |
217
|
|
|
} |
218
|
|
|
|
219
|
3 |
|
$fixedValues[$name][] = $value; |
220
|
|
|
|
221
|
|
|
// The multi-valued argument is the last one, so we don't |
222
|
|
|
// need to advance the array pointer anymore. |
223
|
|
|
} else { |
224
|
294 |
|
$fixedValues[$name] = $value; |
225
|
|
|
|
226
|
294 |
|
next($inputArguments); |
227
|
|
|
} |
228
|
|
|
|
229
|
294 |
|
next($actualValues); |
230
|
|
|
} |
231
|
297 |
|
} |
232
|
|
|
} |
233
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.