Completed
Push — master ( 0763ed...345f2b )
by Greg
02:23
created

CommandInfo::setExampleUsage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 2
1
<?php
2
namespace Consolidation\AnnotatedCommand;
3
4
/**
5
 * Given a class and method name, parse the annotations in the
6
 * DocBlock comment, and provide accessor methods for all of
7
 * the elements that are needed to create a Symfony Console Command.
8
 */
9
class CommandInfo
10
{
11
    /**
12
     * @var \ReflectionParameter
13
     */
14
    protected $params;
15
16
    /**
17
     * @var string
18
     */
19
    protected $name;
20
21
    /**
22
     * @var string
23
     */
24
    protected $description = '';
25
26
    /**
27
     * @var string
28
     */
29
    protected $help = '';
30
31
    /**
32
     * @var array
33
     */
34
    protected $options = [];
35
36
    /**
37
     * @var array
38
     */
39
    protected $arguments = [];
40
41
    /**
42
     * @var array
43
     */
44
    protected $argumentDescriptions = [];
45
46
    /**
47
     * @var array
48
     */
49
    protected $optionDescriptions = [];
50
51
    /**
52
     * @var array
53
     */
54
    protected $exampleUsage = [];
55
56
    /**
57
     * @var array
58
     */
59
    protected $otherAnnotations = [];
60
61
    /**
62
     * @var array
63
     */
64
    protected $aliases = [];
65
66
    /**
67
     * @var string
68
     */
69
    protected $methodName;
70
71
    public function __construct($classNameOrInstance, $methodName)
72
    {
73
        $reflectionMethod = new \ReflectionMethod($classNameOrInstance, $methodName);
74
        $this->methodName = $methodName;
75
        $this->setDefaultName($methodName);
76
        $this->initializeFromParameters($reflectionMethod->getParameters());
77
        $this->parseDocBlock($reflectionMethod->getDocComment());
78
    }
79
80
    protected function initializeFromParameters($params)
81
    {
82
        // Set up a default name for the command from the method name.
83
        // This can be overridden via @command or @name annotations.
84
        $this->params = $params;
85
        $this->options = $this->determineOptionsFromParameters($this->params);
86
        $this->arguments = $this->determineAgumentClassifications($this->params);
87
    }
88
89
    public function getMethodName()
90
    {
91
        return $this->methodName;
92
    }
93
94
    public function getParameters()
95
    {
96
        return $this->params;
97
    }
98
99
    /**
100
     * Get the synopsis of the command (~first line).
101
     */
102
    public function getDescription()
103
    {
104
        return $this->description;
105
    }
106
107
    public function setDescription($description)
108
    {
109
        $this->description = $description;
110
    }
111
112
    /**
113
     * Get the help text of the command (the description)
114
     */
115
    public function getHelp()
116
    {
117
        return $this->help;
118
    }
119
120
    public function setHelp($help)
121
    {
122
        $this->help = $help;
123
    }
124
125
    public function getAliases()
126
    {
127
        return $this->aliases;
128
    }
129
130
    public function setAliases($aliases)
131
    {
132
        if (is_string($aliases)) {
133
            $aliases = explode(',', static::convertListToCommaSeparated($aliases));
134
        }
135
        $this->aliases = array_filter($aliases);
136
    }
137
138
    public function getExampleUsages()
139
    {
140
        return $this->exampleUsage;
141
    }
142
143
    public function getName()
144
    {
145
        return $this->name;
146
    }
147
148
    public function setDefaultName($methodName)
149
    {
150
        $this->name = $this->convertName($methodName);
151
    }
152
153
    public function setName($name)
154
    {
155
        $this->name = $name;
156
    }
157
158
    protected function determineAgumentClassifications($params)
159
    {
160
        $args = [];
161
        if (!empty($this->determineOptionsFromParameters($params))) {
162
            array_pop($params);
163
        }
164
        foreach ($params as $param) {
165
            $defaultValue = $this->getArgumentClassification($param);
166
            if ($defaultValue !== false) {
167
                $args[$param->name] = $defaultValue;
168
            }
169
        }
170
        return $args;
171
    }
172
173
    public function getArguments()
174
    {
175
        return $this->arguments;
176
    }
177
178
    public function hasArgument($name)
179
    {
180
        return array_key_exists($name, $this->arguments);
181
    }
182
183
    public function setArgumentDefaultValue($name, $defaultValue)
184
    {
185
        $this->arguments[$name] = $defaultValue;
186
    }
187
188
    public function addArgument($name, $description, $defaultValue = null)
189
    {
190
        if (!$this->hasArgument($name) || isset($defaultValue)) {
191
            $this->arguments[$name] = $defaultValue;
192
        }
193
        unset($this->argumentDescriptions[$name]);
194
        if (isset($description)) {
195
            $this->argumentDescriptions[$name] = $description;
196
        }
197
    }
198
199
    /**
200
     * Examine the provided parameter, and determine whether it
201
     * is a parameter that will be filled in with a positional
202
     * commandline argument.
203
     *
204
     * @return false|null|string|array
205
     */
206
    protected function getArgumentClassification($param)
207
    {
208
        $defaultValue = null;
209
        if ($param->isDefaultValueAvailable()) {
210
            $defaultValue = $param->getDefaultValue();
211
            if ($this->isAssoc($defaultValue)) {
212
                return false;
213
            }
214
        }
215
        if ($param->isArray()) {
216
            return [];
217
        }
218
        // Commandline arguments must be strings, so ignore
219
        // any parameter that is typehinted to anything else.
220
        if (($param->getClass() != null) && ($param->getClass() != 'string')) {
221
            return false;
222
        }
223
        return $defaultValue;
224
    }
225
226
    public function getOptions()
227
    {
228
        return $this->options;
229
    }
230
231
    public function hasOption($name)
232
    {
233
        return array_key_exists($name, $this->options);
234
    }
235
236
    public function setOptionDefaultValue($name, $defaultValue)
237
    {
238
        $this->options[$name] = $defaultValue;
239
    }
240
241
    public function addOption($name, $description, $defaultValue = false)
242
    {
243
        if (!$this->hasOption($name) || $defaultValue) {
244
            $this->options[$name] = $defaultValue;
245
        }
246
        unset($this->optionDescriptions[$name]);
247
        if (isset($description)) {
248
            $this->optionDescriptions[$name] = $description;
249
        }
250
    }
251
252
    public function determineOptionsFromParameters($params)
253
    {
254
        if (empty($params)) {
255
            return [];
256
        }
257
        $param = end($params);
258
        if (!$param->isDefaultValueAvailable()) {
259
            return [];
260
        }
261
        if (!$this->isAssoc($param->getDefaultValue())) {
262
            return [];
263
        }
264
        return $param->getDefaultValue();
265
    }
266
267
    public function getArgumentDescription($name)
268
    {
269
        if (array_key_exists($name, $this->argumentDescriptions)) {
270
            return $this->argumentDescriptions[$name];
271
        }
272
273
        return '';
274
    }
275
276
    public function getOptionDescription($name)
277
    {
278
        if (array_key_exists($name, $this->optionDescriptions)) {
279
            return $this->optionDescriptions[$name];
280
        }
281
282
        return '';
283
    }
284
285
    protected function isAssoc($arr)
286
    {
287
        if (!is_array($arr)) {
288
            return false;
289
        }
290
        return array_keys($arr) !== range(0, count($arr) - 1);
291
    }
292
293
    public function getAnnotations()
294
    {
295
        return $this->otherAnnotations;
296
    }
297
298
    public function getAnnotation($annotation)
299
    {
300
        // hasAnnotation parses the docblock
301
        if (!$this->hasAnnotation($annotation)) {
302
            return null;
303
        }
304
        return $this->otherAnnotations[$annotation];
305
    }
306
307
    public function hasAnnotation($annotation)
308
    {
309
        return array_key_exists($annotation, $this->otherAnnotations);
310
    }
311
312
    protected function convertName($camel)
313
    {
314
        $splitter="-";
315
        $camel=preg_replace('/(?!^)[[:upper:]][[:lower:]]/', '$0', preg_replace('/(?!^)[[:upper:]]+/', $splitter.'$0', $camel));
316
        $camel = preg_replace("/$splitter/", ':', $camel, 1);
317
        return strtolower($camel);
318
    }
319
320
    public function setExampleUsage($usage, $description)
321
    {
322
        $this->exampleUsage[$usage] = $description;
323
    }
324
325
    /**
326
     * Parse the docBlock comment for this command, and set the
327
     * fields of this class with the data thereby obtained.
328
     */
329
    protected function parseDocBlock($docblock)
330
    {
331
        $parser = new CommandDocBlockParser($this);
332
        $parser->parse($docblock);
333
    }
334
335
    /**
336
     * Save any tag that we do not explicitly recognize in the
337
     * 'otherAnnotations' map.
338
     */
339
    public function addOtherAnnotation($name, $content)
340
    {
341
        $this->otherAnnotations[$name] = $content;
342
    }
343
344
    /**
345
     * An option might have a name such as 'silent|s'. In this
346
     * instance, we will allow the @option or @default tag to
347
     * reference the option only by name (e.g. 'silent' or 's'
348
     * instead of 'silent|s').
349
     */
350
    public function findMatchingOption($optionName)
351
    {
352
        // Exit fast if there's an exact match
353
        if (isset($this->options[$optionName])) {
354
            return $optionName;
355
        }
356
        // Check to see if we can find the option name in an existing option,
357
        // e.g. if the options array has 'silent|s' => false, and the annotation
358
        // is @silent.
359
        foreach ($this->options as $name => $default) {
360
            if (in_array($optionName, explode('|', $name))) {
361
                return $name;
362
            }
363
        }
364
        // Check the other direction: if the annotation contains @silent|s
365
        // and the options array has 'silent|s'.
366
        $checkMatching = explode('|', $optionName);
367
        if (count($checkMatching) > 1) {
368
            foreach ($checkMatching as $checkName) {
369
                if (isset($this->options[$checkName])) {
370
                    $this->options[$optionName] = $this->options[$checkName];
371
                    unset($this->options[$checkName]);
372
                    return $optionName;
373
                }
374
            }
375
        }
376
        return $optionName;
377
    }
378
    /**
379
     * Given a list that might be 'a b c' or 'a, b, c' or 'a,b,c',
380
     * convert the data into the last of these forms.
381
     */
382
    protected static function convertListToCommaSeparated($text)
383
    {
384
        return preg_replace('#[ \t\n\r,]+#', ',', $text);
385
    }
386
}
387