Parser   A
last analyzed

Complexity

Total Complexity 29

Size/Duplication

Total Lines 290
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 97.44%

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 4
dl 0
loc 290
ccs 76
cts 78
cp 0.9744
rs 10
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A setFilter() 0 7 1
A parse() 0 23 3
A command() 0 4 1
A arguments() 0 4 1
A trailing() 0 4 1
A removeTrailingArguments() 0 8 1
A prefixedArguments() 0 9 2
A nonPrefixedArguments() 0 8 3
A getNameAndValue() 0 12 2
A trySettingArgumentValue() 0 15 2
A setArgumentValue() 0 28 5
A isValidArgumentValue() 0 4 1
A findPrefixedArgument() 0 10 3
A getCommandAndArguments() 0 12 2
1
<?php
2
3
namespace League\CLImate\Argument;
4
5
use League\CLImate\Exceptions\InvalidArgumentException;
6
7
class Parser
8
{
9
    /**
10
     * Filter class to find various types of arguments
11
     *
12
     * @var \League\CLImate\Argument\Filter $filter
13
     */
14
    protected $filter;
15
16
    /**
17
     * Summary builder class
18
     *
19
     * @var \League\CLImate\Argument\Summary $summary
20
     */
21
    protected $summary;
22
23 948
    protected $trailing;
24
25 948
    public function __construct()
26 948
    {
27
        $this->summary = new Summary();
28
    }
29
30
    /**
31
     * @param Filter $filter
32
     * @param Argument[] $arguments
33
     *
34 16
     * @return \League\CLImate\Argument\Parser
35
     */
36 16
    public function setFilter($filter, $arguments)
37 16
    {
38
        $this->filter = $filter;
39 16
        $this->filter->setArguments($arguments);
40
41
        return $this;
42
    }
43
44
    /**
45
     * Parse command line arguments into CLImate arguments.
46
     *
47
     * @param array $argv
48 16
     *
49
     * @return void
50 16
     * @throws InvalidArgumentException if required arguments aren't defined.
51
     */
52 16
    public function parse(array $argv = null)
53 4
    {
54 4
        $cliArguments = $this->arguments($argv);
55
56 16
        if (in_array('--', $cliArguments)) {
57
            $cliArguments = $this->removeTrailingArguments($cliArguments);
58 16
        }
59
60
        $unParsedArguments = $this->prefixedArguments($cliArguments);
61
62 16
        $this->nonPrefixedArguments($unParsedArguments);
63
64 16
        // After parsing find out which arguments were required but not
65 4
        // defined on the command line.
66
        $missingArguments = $this->filter->missing();
67 4
68 4
        if (count($missingArguments) > 0) {
69
            throw new InvalidArgumentException(
70 12
                'The following arguments are required: '
71
                . $this->summary->short($missingArguments) . '.'
72
            );
73
        }
74
    }
75
76
    /**
77
     * Get the command name.
78
     *
79 4
     * @param array $argv
80
     *
81 4
     * @return string
82
     */
83
    public function command(array $argv = null)
84
    {
85
        return $this->getCommandAndArguments($argv)['command'];
86
    }
87
88
    /**
89
     * Get the passed arguments.
90
     *
91 20
     * @param array $argv
92
     *
93 20
     * @return array
94
     */
95
    public function arguments(array $argv = null)
96
    {
97
        return $this->getCommandAndArguments($argv)['arguments'];
98
    }
99
100
    /**
101 4
     * Get the trailing arguments
102
     *
103 4
     * @return string|null
104
     */
105
    public function trailing()
106
    {
107
        return $this->trailing;
108
    }
109
110
    /**
111
     * Remove the trailing arguments from the parser and set them aside
112
     *
113 4
     * @param array $arguments
114
     *
115 4
     * @return array
116 4
     */
117 4
    protected function removeTrailingArguments(array $arguments)
118
    {
119 4
        $trailing = array_splice($arguments, array_search('--', $arguments));
120
        array_shift($trailing);
121
        $this->trailing = implode(' ', $trailing);
122
123
        return $arguments;
124
    }
125
126
    /**
127
     * Parse command line options into prefixed CLImate arguments.
128
     *
129
     * Prefixed arguments are arguments with a prefix (-) or a long prefix (--)
130
     * on the command line.
131
     *
132
     * Return the arguments passed on the command line that didn't match up with
133
     * prefixed arguments so they can be assigned to non-prefixed arguments.
134 16
     *
135
     * @param array $argv
136 16
     * @return array
137 16
     */
138 16
    protected function prefixedArguments(array $argv = [])
139
    {
140
        foreach ($argv as $key => $passed_argument) {
141 16
            $argv = $this->trySettingArgumentValue($argv, $key, $passed_argument);
142
        }
143
144
        // Send un-parsed arguments back upstream.
145
        return array_values($argv);
146
    }
147
148
    /**
149
     * Parse unset command line options into non-prefixed CLImate arguments.
150
     *
151
     * Non-prefixed arguments are parsed after the prefixed arguments on the
152 16
     * command line, in the order that they're defined in the script.
153
     *
154 16
     * @param array $unParsedArguments
155 4
     */
156 4
    protected function nonPrefixedArguments(array $unParsedArguments = [])
157 4
    {
158 16
        foreach ($this->filter->withoutPrefix() as $key => $argument) {
159 16
            if (isset($unParsedArguments[$key])) {
160
                $argument->setValue($unParsedArguments[$key]);
161
            }
162
        }
163
    }
164
165
    /**
166
     * Parse the name and value of the argument passed in
167 16
     *
168
     * @param string $cliArgument
169
     * @return string[] [$name, $value]
170 16
     */
171 8
    protected function getNameAndValue($cliArgument)
172
    {
173
        // Look for arguments defined in the "key=value" format.
174
        if (strpos($cliArgument, '=') !== false) {
175
            return explode('=', $cliArgument, 2);
176
        }
177 16
178
        // If the argument isn't in "key=value" format then assume it's in
179
        // "key value" format and define the value after we've found the
180
        // matching CLImate argument.
181
        return [$cliArgument, null];
182
    }
183
184
    /**
185
     * Attempt to set the an argument's value and remove applicable
186
     * arguments from array
187
     *
188
     * @param array $argv
189
     * @param int $key
190 16
     * @param string $passed_argument
191
     *
192 16
     * @return array The new $argv
193
     */
194
    protected function trySettingArgumentValue($argv, $key, $passed_argument)
195
    {
196 16
        list($name, $value) = $this->getNameAndValue($passed_argument);
197 12
198
        // Look for the argument in our defined $arguments
199
        // and assign their value.
200
        if (!($argument = $this->findPrefixedArgument($name))) {
201 16
            return $argv;
202
        }
203 16
204
        // We found an argument key, so take it out of the array.
205
        unset($argv[$key]);
206
207
        return $this->setArgumentValue($argv, $argument, $key, $value);
208
    }
209
210
    /**
211
     * Set the argument's value
212
     *
213
     * @param array $argv
214
     * @param Argument $argument
215
     * @param int $key
216 16
     * @param string|null $value
217
     *
218
     * @return array The new $argv
219
     */
220 16
    protected function setArgumentValue($argv, $argument, $key, $value)
221 4
    {
222 4
        // Arguments are given the value true if they only need to
223
        // be defined on the command line to be set.
224
        if ($argument->noValue()) {
225 16
            $argument->setValue(true);
226 16
            return $argv;
227 4
        }
228
229
        if (is_null($value)) {
230
            if (count($argv) === 0) {
231
                return $argv;
232 12
            }
233 12
234 12
            // If the value wasn't previously defined in "key=value"
235
            // format then define it from the next command argument.
236
            $nextArgvValue = $argv[$key + 1];
237 8
            if ($this->isValidArgumentValue($nextArgvValue)) {
238
                $argument->setValue($nextArgvValue);
239 8
                unset($argv[$key + 1]);
240
                return $argv;
241
            }
242
        }
243
244
        $argument->setValue($value);
245
246
        return $argv;
247
    }
248
249 16
    /**
250
     * Check if the value is considered a valid input value.
251 16
     *
252 16
     * @param $argumentValue
253 16
     * @return bool
254
     */
255 12
    protected function isValidArgumentValue($argumentValue)
256
    {
257 12
        return empty($this->findPrefixedArgument($argumentValue));
258
    }
259
260
    /**
261
     * Search for argument in defined prefix arguments
262
     *
263
     * @param string $name
264
     *
265
     * @return Argument|false
266 24
     */
267
    protected function findPrefixedArgument($name)
268
    {
269 24
        foreach ($this->filter->withPrefix() as $argument) {
270
            if (in_array($name, ["-{$argument->prefix()}", "--{$argument->longPrefix()}"])) {
271
                return $argument;
272
            }
273 24
        }
274 24
275
        return false;
276 24
    }
277
278
    /**
279
     * Pull a command name and arguments from $argv.
280
     *
281
     * @param array $argv
282
     * @return array
283
     */
284
    protected function getCommandAndArguments(array $argv = null)
285
    {
286
        // If no $argv is provided then use the global PHP defined $argv.
287
        if (is_null($argv)) {
288
            global $argv;
289
        }
290
291
        $arguments = $argv;
292
        $command   = array_shift($arguments);
293
294
        return compact('arguments', 'command');
295
    }
296
}
297