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\Resolver; |
13
|
|
|
|
14
|
|
|
use Webmozart\Console\Api\Application\Application; |
15
|
|
|
use Webmozart\Console\Api\Args\RawArgs; |
16
|
|
|
use Webmozart\Console\Api\Command\Command; |
17
|
|
|
use Webmozart\Console\Api\Command\CommandCollection; |
18
|
|
|
use Webmozart\Console\Api\Config\OptionCommandConfig; |
19
|
|
|
use Webmozart\Console\Api\Resolver\CannotResolveCommandException; |
20
|
|
|
use Webmozart\Console\Api\Resolver\CommandResolver; |
21
|
|
|
use Webmozart\Console\Api\Resolver\ResolvedCommand; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Parses the raw console arguments for the command to execute. |
25
|
|
|
* |
26
|
|
|
* @since 1.0 |
27
|
|
|
* |
28
|
|
|
* @author Bernhard Schussek <[email protected]> |
29
|
|
|
*/ |
30
|
|
|
class DefaultResolver implements CommandResolver |
31
|
|
|
{ |
32
|
|
|
/** |
33
|
|
|
* {@inheritdoc} |
34
|
|
|
*/ |
35
|
217 |
|
public function resolveCommand(RawArgs $args, Application $application) |
36
|
|
|
{ |
37
|
217 |
|
$tokens = $args->getTokens(); |
38
|
217 |
|
$namedCommands = $application->getNamedCommands(); |
39
|
|
|
|
40
|
217 |
|
$argumentsToTest = $this->getArgumentsToTest($tokens); |
41
|
217 |
|
$optionsToTest = $this->getOptionsToTest($tokens); |
42
|
|
|
|
43
|
|
|
// Try to find a command for the passed arguments and options. |
44
|
217 |
|
if ($result = $this->processArguments($args, $namedCommands, $argumentsToTest, $optionsToTest)) { |
45
|
199 |
|
return $this->createResolvedCommand($result); |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
// If arguments were passed, we did not find the matching command. |
49
|
18 |
|
if ($argumentsToTest) { |
|
|
|
|
50
|
1 |
|
throw CannotResolveCommandException::nameNotFound(reset($argumentsToTest), $namedCommands); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
// If no arguments were passed, run the application's default command. |
54
|
17 |
|
if ($result = $this->processDefaultCommands($args, $application->getDefaultCommands())) { |
55
|
17 |
|
return $this->createResolvedCommand($result); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
// No default command is configured. |
59
|
|
|
throw CannotResolveCommandException::noDefaultCommand(); |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @param RawArgs $args |
64
|
|
|
* @param CommandCollection $namedCommands |
65
|
|
|
* @param string[] $argumentsToTest |
66
|
|
|
* @param string[] $optionsToTest |
67
|
|
|
* |
68
|
|
|
* @return ResolveResult |
|
|
|
|
69
|
|
|
*/ |
70
|
217 |
|
private function processArguments(RawArgs $args, CommandCollection $namedCommands, array $argumentsToTest, array $optionsToTest) |
71
|
|
|
{ |
72
|
217 |
|
$currentCommand = null; |
73
|
|
|
|
74
|
|
|
// Parse the arguments for command names until we fail to find a |
75
|
|
|
// matching command |
76
|
217 |
View Code Duplication |
foreach ($argumentsToTest as $name) { |
|
|
|
|
77
|
200 |
|
if (!$namedCommands->contains($name)) { |
78
|
16 |
|
break; |
79
|
|
|
} |
80
|
|
|
|
81
|
199 |
|
$nextCommand = $namedCommands->get($name); |
82
|
|
|
|
83
|
199 |
|
if ($nextCommand->getConfig() instanceof OptionCommandConfig) { |
84
|
|
|
break; |
85
|
|
|
} |
86
|
|
|
|
87
|
199 |
|
$currentCommand = $nextCommand; |
88
|
199 |
|
$namedCommands = $currentCommand->getNamedSubCommands(); |
89
|
|
|
} |
90
|
|
|
|
91
|
217 |
|
if (!$currentCommand) { |
92
|
18 |
|
return null; |
93
|
|
|
} |
94
|
|
|
|
95
|
199 |
|
return $this->processOptions($args, $currentCommand, $optionsToTest); |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* @param RawArgs $args |
100
|
|
|
* @param Command $currentCommand |
101
|
|
|
* @param string[] $optionsToTest |
102
|
|
|
* |
103
|
|
|
* @return ResolveResult |
104
|
|
|
*/ |
105
|
199 |
|
private function processOptions(RawArgs $args, Command $currentCommand, array $optionsToTest) |
106
|
|
|
{ |
107
|
199 |
View Code Duplication |
foreach ($optionsToTest as $option) { |
|
|
|
|
108
|
142 |
|
$commands = $currentCommand->getNamedSubCommands(); |
109
|
|
|
|
110
|
142 |
|
if (!$commands->contains($option)) { |
111
|
131 |
|
continue; |
112
|
|
|
} |
113
|
|
|
|
114
|
45 |
|
$nextCommand = $commands->get($option); |
115
|
|
|
|
116
|
45 |
|
if (!$nextCommand->getConfig() instanceof OptionCommandConfig) { |
117
|
|
|
break; |
118
|
|
|
} |
119
|
|
|
|
120
|
45 |
|
$currentCommand = $nextCommand; |
121
|
|
|
} |
122
|
|
|
|
123
|
199 |
|
return $this->processDefaultSubCommands($args, $currentCommand); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* @param RawArgs $args |
128
|
|
|
* @param Command $currentCommand |
129
|
|
|
* |
130
|
|
|
* @return ResolveResult |
131
|
|
|
*/ |
132
|
199 |
|
private function processDefaultSubCommands(RawArgs $args, Command $currentCommand) |
133
|
|
|
{ |
134
|
199 |
|
if ($result = $this->processDefaultCommands($args, $currentCommand->getDefaultSubCommands())) { |
135
|
42 |
|
return $result; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
// No default commands, return the current command |
139
|
157 |
|
return new ResolveResult($currentCommand, $args); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* @param RawArgs $args |
144
|
|
|
* @param CommandCollection $defaultCommands |
145
|
|
|
* |
146
|
|
|
* @return ResolveResult |
|
|
|
|
147
|
|
|
*/ |
148
|
216 |
|
private function processDefaultCommands(RawArgs $args, CommandCollection $defaultCommands) |
149
|
|
|
{ |
150
|
216 |
|
$firstResult = null; |
151
|
|
|
|
152
|
216 |
|
foreach ($defaultCommands as $defaultCommand) { |
153
|
59 |
|
$resolvedCommand = new ResolveResult($defaultCommand, $args); |
154
|
|
|
|
155
|
59 |
|
if ($resolvedCommand->isParsable()) { |
156
|
58 |
|
return $resolvedCommand; |
157
|
|
|
} |
158
|
|
|
|
159
|
7 |
|
if (!$firstResult) { |
160
|
7 |
|
$firstResult = $resolvedCommand; |
161
|
|
|
} |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
// Return the first default command if one was found |
165
|
158 |
|
return $firstResult; |
166
|
|
|
} |
167
|
|
|
|
168
|
217 |
|
private function getArgumentsToTest(array &$tokens) |
169
|
|
|
{ |
170
|
217 |
|
$argumentsToTest = array(); |
171
|
|
|
|
172
|
217 |
|
for (; null !== key($tokens); next($tokens)) { |
173
|
214 |
|
$token = current($tokens); |
174
|
|
|
|
175
|
|
|
// "--" stops argument parsing |
176
|
214 |
|
if ('--' === $token) { |
177
|
3 |
|
break; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
// Stop argument parsing when we reach the first option. |
181
|
|
|
|
182
|
|
|
// Command names must be passed before any option. The reason |
183
|
|
|
// is that we cannot determine whether an argument after an |
184
|
|
|
// option is the value of that option or an argument by itself |
185
|
|
|
// without getting the input definition of the corresponding |
186
|
|
|
// command first. |
187
|
|
|
|
188
|
|
|
// For example, in the command "server -f add" we don't know |
189
|
|
|
// whether "add" is the value of the "-f" option or an argument. |
190
|
|
|
// Hence we stop argument parsing after "-f" and assume that |
191
|
|
|
// "server" (or "server -f") is the command to execute. |
192
|
214 |
|
if (isset($token[0]) && '-' === $token[0]) { |
193
|
173 |
|
break; |
194
|
|
|
} |
195
|
|
|
|
196
|
200 |
|
$argumentsToTest[] = $token; |
197
|
|
|
} |
198
|
|
|
|
199
|
217 |
|
return $argumentsToTest; |
200
|
|
|
} |
201
|
|
|
|
202
|
217 |
|
private function getOptionsToTest(array &$tokens) |
203
|
|
|
{ |
204
|
217 |
|
$optionsToTest = array(); |
205
|
|
|
|
206
|
217 |
|
for (; null !== key($tokens); next($tokens)) { |
207
|
176 |
|
$token = current($tokens); |
208
|
|
|
|
209
|
|
|
// "--" stops option parsing |
210
|
176 |
|
if ('--' === $token) { |
211
|
3 |
|
break; |
212
|
|
|
} |
213
|
|
|
|
214
|
173 |
|
if (isset($token[0]) && '-' === $token[0]) { |
215
|
173 |
|
if ('--' === substr($token, 0, 2) && strlen($token) > 2) { |
216
|
98 |
|
$optionsToTest[] = substr($token, 2); |
217
|
97 |
|
} elseif (2 === strlen($token)) { |
218
|
75 |
|
$optionsToTest[] = substr($token, 1); |
219
|
|
|
} |
220
|
|
|
|
221
|
173 |
|
continue; |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
|
225
|
217 |
|
return $optionsToTest; |
226
|
|
|
} |
227
|
|
|
|
228
|
216 |
|
private function createResolvedCommand(ResolveResult $result) |
229
|
|
|
{ |
230
|
216 |
|
if (!$result->isParsable()) { |
231
|
1 |
|
throw $result->getParseError(); |
232
|
|
|
} |
233
|
|
|
|
234
|
215 |
|
return new ResolvedCommand($result->getCommand(), $result->getParsedArgs()); |
235
|
|
|
} |
236
|
|
|
} |
237
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.