Completed
Push — master ( 6a826d...fe4492 )
by Alexandre
11s
created

Cli::hasOptions()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.0218

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 8
cts 9
cp 0.8889
rs 9.2
c 0
b 0
f 0
cc 4
eloc 9
nc 4
nop 1
crap 4.0218
1
<?php
2
3
namespace Garden\Cli;
4
5
/**
6
 * A general purpose command line parser.
7
 *
8
 * @author Todd Burry <[email protected]>
9
 * @license MIT
10
 * @copyright 2010-2014 Vanilla Forums Inc.
11
 */
12
class Cli {
13
    /// Constants ///
14
15
    const META = '__meta';
16
    const ARGS = '__args';
17
18
    /// Properties ///
19
    /**
20
     * @var array All of the schemas, indexed by command pattern.
21
     */
22
    protected $commandSchemas;
23
24
    /**
25
     * @var array A pointer to the current schema.
26
     */
27
    protected $currentSchema;
28
29
    /**
30
     * @var bool Whether or not to format output with console codes.
31
     */
32
    protected $formatOutput;
33
34
    protected static $types = [
35
//        '=' => 'base64',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
36
        'i' => 'integer',
37
        's' => 'string',
38
//        'f' => 'float',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
39
        'b' => 'boolean',
40
//        'ts' => 'timestamp',
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
41
//        'dt' => 'datetime'
42
    ];
43
44
45
    /// Methods ///
46
47
    /**
48
     * Creates a {@link Cli} instance representing a command line parser for a given schema.
49
     */
50 41
    public function __construct() {
51 41
        $this->commandSchemas = ['*' => [Cli::META => []]];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
52
53
        // Select the current schema.
54 41
        $this->currentSchema =& $this->commandSchemas['*'];
55
56 41
        $this->formatOutput = static::guessFormatOutput();
57 41
    }
58
59
    /**
60
     * Backwards compatibility for the **format** property.
61
     *
62
     * @param string $name Must be **format**.
63
     * @return bool|null Returns {@link getFormatOutput()} or null if {@link $name} isn't **format**.
64
     */
65 1
    public function __get($name) {
66 1
        if ($name === 'format') {
67 1
            trigger_error("Cli->format is deprecated. Use Cli->getFormatOutput() instead.", E_USER_DEPRECATED);
68 1
            return $this->getFormatOutput();
69
        }
70
        return null;
71
    }
72
73
    /**
74
     * Backwards compatibility for the **format** property.
75
     *
76
     * @param string $name Must be **format**.
77
     * @param bool $value One of **true** or **false**.
78
     */
79 1
    public function __set($name, $value) {
80 1
        if ($name === 'format') {
81 1
            trigger_error("Cli->format is deprecated. Use Cli->setFormatOutput() instead.", E_USER_DEPRECATED);
82 1
            $this->setFormatOutput($value);
83 1
        }
84 1
    }
85
86
    /**
87
     * Get whether or not output should be formatted.
88
     *
89
     * @return boolean Returns **true** if output should be formatted or **false** otherwise.
90
     */
91 1
    public function getFormatOutput() {
92 1
        return $this->formatOutput;
93
    }
94
95
    /**
96
     * Set whether or not output should be formatted.
97
     *
98
     * @param boolean $formatOutput Whether or not to format output.
99
     * @return Cli Returns `$this` for fluent calls.
100
     */
101 2
    public function setFormatOutput($formatOutput) {
102 2
        $this->formatOutput = $formatOutput;
103 2
        return $this;
104
    }
105
106
    /**
107
     * Add an argument to an {@link Args} object, checking for a correct name.
108
     *
109
     * @param array $schema The schema for the args.
110
     * @param Args $args The args object to add the argument to.
111
     * @param $arg The value of the argument.
112
     */
113 2
    private function addArg(array $schema, Args $args, $arg) {
114 2
        $argsCount = count($args->getArgs());
115 2
        $schemaArgs = isset($schema[self::META][self::ARGS]) ? array_keys($schema[self::META][self::ARGS]) : [];
116 2
        $name = isset($schemaArgs[$argsCount]) ? $schemaArgs[$argsCount] : $argsCount;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
117
118 2
        $args->addArg($arg, $name);
119 2
    }
120
121
    /**
122
     * Construct and return a new {@link Cli} object.
123
     *
124
     * This method is mainly here so that an entire cli schema can be created and defined with fluent method calls.
125
     *
126
     * @return Cli Returns a new Cli object.
127
     */
128 2
    public static function create() {
129 2
        return new Cli();
130
    }
131
132
    /**
133
     * Breaks a cell into several lines according to a given width.
134
     *
135
     * @param string $text The text of the cell.
136
     * @param int $width The width of the cell.
137
     * @param bool $addSpaces Whether or not to right-pad the cell with spaces.
138
     * @return array Returns an array of strings representing the lines in the cell.
139
     */
140 7
    public static function breakLines($text, $width, $addSpaces = true) {
141 7
        $rawLines = explode("\n", $text);
142 7
        $lines = [];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
143
144 7
        foreach ($rawLines as $line) {
145
            // Check to see if the line needs to be broken.
146 7
            $sublines = static::breakString($line, $width, $addSpaces);
147 7
            $lines = array_merge($lines, $sublines);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
148 7
        }
149
150 7
        return $lines;
151
    }
152
153
    /**
154
     * Breaks a line of text according to a given width.
155
     *
156
     * @param string $line The text of the line.
157
     * @param int $width The width of the cell.
158
     * @param bool $addSpaces Whether or not to right pad the lines with spaces.
159
     * @return array Returns an array of lines, broken on word boundaries.
160
     */
161 7
    protected static function breakString($line, $width, $addSpaces = true) {
162 7
        $words = explode(' ', $line);
163 7
        $result = [];
164
165 7
        $line = '';
166 7
        foreach ($words as $word) {
167 7
            $candidate = trim($line.' '.$word);
168
169
            // Check for a new line.
170 7
            if (strlen($candidate) > $width) {
171 2
                if ($line === '') {
172
                    // The word is longer than a line.
173
                    if ($addSpaces) {
174
                        $result[] = substr($candidate, 0, $width);
175
                    } else {
176
                        $result[] = $candidate;
177
                    }
178
                } else {
179 2
                    if ($addSpaces) {
180
                        $line .= str_repeat(' ', $width - strlen($line));
181
                    }
182
183
                    // Start a new line.
184 2
                    $result[] = $line;
185 2
                    $line = $word;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
186
                }
187 2
            } else {
188 7
                $line = $candidate;
189
            }
190 7
        }
191
192
        // Add the remaining line.
193 7
        if ($line) {
194 7
            if ($addSpaces) {
195 7
                $line .= str_repeat(' ', $width - strlen($line));
196 7
            }
197
198
            // Start a new line.
199 7
            $result[] = $line;
200 7
        }
201
202 7
        return $result;
203
    }
204
205
    /**
206
     * Sets the description for the current command.
207
     *
208
     * @param string $str The description for the current schema or null to get the current description.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $str not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
209
     * @return Cli Returns this class for fluent calls.
210
     */
211 8
    public function description($str = null) {
212 8
        return $this->meta('description', $str);
213
    }
214
215
    /**
216
     * Determines whether or not the schema has a command.
217
     *
218
     * @param string $name Check for the specific command name.
219
     * @return bool Returns true if the schema has a command.
220
     */
221 39
    public function hasCommand($name = '') {
222 39
        if ($name) {
223
            return array_key_exists($name, $this->commandSchemas);
224
        } else {
225 39
            foreach ($this->commandSchemas as $pattern => $opts) {
226 39
                if (strpos($pattern, '*') === false) {
227 4
                    return true;
228
                }
229 39
            }
230 35
            return false;
231
        }
232
    }
233
234
    /**
235
     * Determines whether a command has options.
236
     *
237
     * @param string $command The name of the command or an empty string for any command.
238
     * @return bool Returns true if the command has options. False otherwise.
239
     */
240 6
    public function hasOptions($command = '') {
241 6
        if ($command) {
242 2
            $def = $this->getSchema($command);
243 2
            return $this->hasOptionsDef($def);
244
        } else {
245 4
            foreach ($this->commandSchemas as $pattern => $def) {
246 4
                if ($this->hasOptionsDef($def)) {
247 4
                    return true;
248
                }
249 1
            }
250
        }
251
        return false;
252
    }
253
254
    /**
255
     * Determines whether or not a command definition has options.
256
     *
257
     * @param array $commandDef The command definition as returned from {@link Cli::getSchema()}.
258
     * @return bool Returns true if the command def has options or false otherwise.
259
     */
260 6
    protected function hasOptionsDef($commandDef) {
261 6
        return count($commandDef) > 1 || (count($commandDef) > 0 && !isset($commandDef[Cli::META]));
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
262
    }
263
264
    /**
265
     * Determines whether or not a command has args.
266
     *
267
     * @param string $command The command name to check.
268
     * @return int Returns one of the following.
269
     * - 0: The command has no args.
270
     * - 1: The command has optional args.
271
     * - 2: The command has required args.
272
     */
273 6
    public function hasArgs($command = '') {
274 6
        $args = null;
275
276 6
        if ($command) {
277
            // Check to see if the specific command has args.
278 2
            $def = $this->getSchema($command);
279 2
            if (isset($def[Cli::META][Cli::ARGS])) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
280 2
                $args = $def[Cli::META][Cli::ARGS];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
281 2
            }
282 2
        } else {
283 4
            foreach ($this->commandSchemas as $pattern => $def) {
284 4
                if (isset($def[Cli::META][Cli::ARGS])) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
285 2
                    $args = $def[Cli::META][Cli::ARGS];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
286 2
                }
287 4
            }
288 4
            if (!empty($args)) {
289 2
                return 1;
290
            }
291
        }
292
293 4
        if (!$args || empty($args)) {
294 2
            return 0;
295
        }
296
297 2
        foreach ($args as $arg) {
298 2
            if (!Cli::val('required', $arg)) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
299 1
                return 1;
300
            }
301 1
        }
302 1
        return 2;
303
    }
304
305
    /**
306
     * Finds our whether a pattern is a command.
307
     *
308
     * @param string $pattern The pattern being evaluated.
309
     * @return bool Returns `true` if `$pattern` is a command, `false` otherwise.
310
     */
311 2
    public static function isCommand($pattern) {
312 2
        return strpos($pattern, '*') === false;
313
    }
314
315
    /**
316
     * Parses and validates a set of command line arguments the schema.
317
     *
318
     * @param array $argv The command line arguments a form compatible with the global `$argv` variable.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $argv not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
319
     *
320
     * Note that the `$argv` array must have at least one element and it must represent the path to the command that
321
     * invoked the command. This is used to write usage information.
322
     * @param bool $exit Whether to exit the application when there is an error or when writing help.
323
     * @return Args|null Returns an {@see Args} instance when a command should be executed
324
     * or `null` when one should not be executed.
325
     * @throws \Exception Throws an exception when {@link $exit} is false and the help or errors need to be displayed.
326
     */
327 39
    public function parse($argv = null, $exit = true) {
328 39
        $formatOutputBak = $this->formatOutput;
329
        // Only format commands if we are exiting.
330 39
        if (!$exit) {
331 38
            $this->formatOutput = false;
332 38
        }
333 39
        if (!$exit) {
334 38
            ob_start();
335 38
        }
336
337 39
        $args = $this->parseRaw($argv);
338
339 39
        $hasCommand = $this->hasCommand();
340
341 39
        if ($hasCommand && !$args->getCommand()) {
342
            // If no command is given then write a list of commands.
343 2
            $this->writeUsage($args);
344 2
            $this->writeCommands();
345 2
            $result = null;
346 39
        } elseif ($args->getOpt('help') || $args->getOpt('?')) {
347
            // Write the help.
348 4
            $this->writeUsage($args);
349 4
            $this->writeHelp($args->getCommand());
350 4
            $result = null;
351 4
        } else {
352
            // Validate the arguments against the schema.
353 33
            $validArgs = $this->validate($args);
354 33
            $result = $validArgs;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
355
        }
356 39
        if (!$exit) {
357 38
            $this->formatOutput = $formatOutputBak;
358 38
            $output = ob_get_clean();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 13 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
359 38
            if ($result === null) {
360 14
                throw new \Exception(trim($output));
361
            }
362 25
        } elseif ($result === null) {
363
            exit();
364
        }
365 25
        return $result;
366
    }
367
368
    /**
369
     * Parse an array of arguments.
370
     *
371
     * If the first item in the array is in the form of a command (no preceding - or --),
372
     * 'command' is filled with its value.
373
     *
374
     * @param array $argv An array of arguments passed in a form compatible with the global `$argv` variable.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $argv not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
375
     * @return Args Returns the raw parsed arguments.
376
     * @throws \Exception Throws an exception when {@see $argv} isn't an array.
377
     */
378 39
    protected function parseRaw($argv = null) {
0 ignored issues
show
Coding Style introduced by
parseRaw uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
379 39
        if ($argv === null) {
380
            $argv = $GLOBALS['argv'];
381
        }
382
383 39
        if (!is_array($argv)) {
384
            throw new \Exception(__METHOD__ . " expects an array", 400);
385
        }
386
387 39
        $path = array_shift($argv);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
388 39
        $hasCommand = $this->hasCommand();
389
390 39
        $parsed = new Args();
391 39
        $parsed->setMeta('path', $path);
392 39
        $parsed->setMeta('filename', basename($path));
393
394 39
        if (count($argv)) {
395
            // Get possible command.
396 38
            if (substr($argv[0], 0, 1) != '-') {
397 4
                $arg0 = array_shift($argv);
398 4
                if ($hasCommand) {
399 2
                    $parsed->setCommand($arg0);
400 2
                } else {
401 2
                    $schema = $this->getSchema($parsed->getCommand());
402 2
                    $this->addArg($schema, $parsed, $arg0);
403
                }
404 4
            }
405
            // Get the data types for all of the commands.
406 38
            if (!isset($schema)) {
407 36
                $schema = $this->getSchema($parsed->getCommand());
408 36
            }
409
410 38
            $types = [];
411 38
            foreach ($schema as $sName => $sRow) {
412 38
                if ($sName === Cli::META) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
413 38
                    continue;
414
                }
415
416 36
                $type = Cli::val('type', $sRow, 'string');
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 10 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
417 36
                $types[$sName] = $type;
418 36
                if (isset($sRow['short'])) {
419 36
                    $types[$sRow['short']] = $type;
420 36
                }
421 38
            }
422
423
            // Parse opts.
424 38
            for ($i = 0; $i < count($argv); $i++) {
425 38
                $str = $argv[$i];
426
427
                // Parse the help command as a boolean since it is not part of the schema!
428 38
                if (in_array($str, ['-?', '--help'])) {
429 6
                    $parsed->setOpt('help', true);
430 6
                    continue;
431
                }
432
433 32
                if ($str === '--') {
434
                    // --
435
                    $i++;
436
                    break;
437 32
                } elseif (strlen($str) > 2 && substr($str, 0, 2) == '--') {
438
                    // --foo
439 16
                    $str = substr($str, 2);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
440 16
                    $parts = explode('=', $str);
441 16
                    $key = $parts[0];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
442 16
                    $v = null;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
443
444
                    // Has a =, so pick the second piece
445 16
                    if (count($parts) == 2) {
446 9
                        $v = $parts[1];
447
                    // Does not have an =
448 9
                    } else {
449
                        // If there is a value (even if there are no equals)
450 9
                        if (isset($argv[$i + 1]) && preg_match('/^--?.+/', $argv[$i + 1]) == 0) {
451
                            // so choose the next arg as its value if any,
452 6
                            $v = $argv[$i + 1];
453
                            // If this is a boolean we need to coerce the value
454 6
                            if (Cli::val($key, $types) === 'boolean') {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
455 1
                                if (in_array($v, ['0', '1', 'true', 'false', 'on', 'off', 'yes', 'no'])) {
456
                                    // The next arg looks like a boolean to me.
457 1
                                    $i++;
458 1
                                } else {
459
                                    // Next arg is not a boolean: set the flag on, and use next arg in its own iteration
460
                                    $v = true;
461
                                }
462 1
                            } else {
463 5
                                $i++;
464
                            }
465
                        // If there is no value but we have a no- before the command
466 9
                        } elseif (strpos($key, 'no-') === 0) {
467 2
                            $tmpKey = str_replace('no-', null, $key);
468 2
                            if (Cli::val($tmpKey, $types) === 'boolean') {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
469 2
                                $key = $tmpKey;
470 2
                                $v = false;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
471 2
                            }
472 5
                        } elseif (Cli::val($key, $types) === 'boolean') {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
473 2
                            $v = true;
474 2
                        }
475
                    }
476 16
                    $parsed->setOpt($key, $v);
477 32
                } elseif (strlen($str) == 2 && $str[0] == '-') {
478
                    // -a
479
480 9
                    $key = $str[1];
481 9
                    $type = Cli::val($key, $types, 'boolean');
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
482 9
                    $v = null;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
483
484 9
                    if (isset($argv[$i + 1])) {
485
                        // Try and be smart about the next arg.
486 6
                        $nextArg = $argv[$i + 1];
487
488 6
                        if ($type === 'boolean') {
489 2
                            if ($this->isStrictBoolean($nextArg)) {
490
                                // The next arg looks like a boolean to me.
491 1
                                $v = $nextArg;
492 1
                                $i++;
493 1
                            } else {
494 2
                                $v = true;
495
                            }
496 6
                        } elseif (!preg_match('/^--?.+/', $argv[$i + 1])) {
497
                            // The next arg is not an opt.
498 5
                            $v = $nextArg;
499 5
                            $i++;
500 5
                        } else {
501
                            // The next arg is another opt.
502
                            $v = null;
503
                        }
504 6
                    }
505
506 9
                    if ($v === null) {
507 3
                        $v = Cli::val($type, ['boolean' => true, 'integer' => 1, 'string' => '']);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
508 3
                    }
509
510 9
                    $parsed->setOpt($key, $v);
511 24
                } elseif (strlen($str) > 1 && $str[0] == '-') {
512
                    // -abcdef
513 17
                    for ($j = 1; $j < strlen($str); $j++) {
514 17
                        $opt = $str[$j];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
515 17
                        $remaining = substr($str, $j + 1);
516 17
                        $type = Cli::val($opt, $types, 'boolean');
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
517
518
                        // Check for an explicit equals sign.
519 17
                        if (substr($remaining, 0, 1) === '=') {
520 3
                            $remaining = substr($remaining, 1);
521 3
                            if ($type === 'boolean') {
522
                                // Bypass the boolean flag checking below.
523
                                $parsed->setOpt($opt, $remaining);
524
                                break;
525
                            }
526 3
                        }
527
528 17
                        if ($type === 'boolean') {
529 5
                            if (preg_match('`^([01])`', $remaining, $matches)) {
530
                                // Treat the 0 or 1 as a true or false.
531 3
                                $parsed->setOpt($opt, $matches[1]);
532 3
                                $j += strlen($matches[1]);
533 3
                            } else {
534
                                // Treat the option as a flag.
535 3
                                $parsed->setOpt($opt, true);
536
                            }
537 17
                        } elseif ($type === 'string') {
538
                            // Treat the option as a set with no = sign.
539 14
                            $parsed->setOpt($opt, $remaining);
540 14
                            break;
541 3
                        } elseif ($type === 'integer') {
542 3
                            if (preg_match('`^(\d+)`', $remaining, $matches)) {
543
                                // Treat the option as a set with no = sign.
544 2
                                $parsed->setOpt($opt, $matches[1]);
545 2
                                $j += strlen($matches[1]);
546 2
                            } else {
547
                                // Treat the option as either multiple flags.
548 2
                                $optVal = $parsed->getOpt($opt, 0);
549 2
                                $parsed->setOpt($opt, $optVal + 1);
550
                            }
551 3
                        } else {
552
                            // This should not happen unless we've put a bug in our code.
553
                            throw new \Exception("Invalid type $type for $opt.", 500);
554
                        }
555 6
                    }
556 17
                } else {
557
                    // End of opts
558 1
                    break;
559
                }
560 31
            }
561
562
            // Grab the remaining args.
563 38
            for (; $i < count($argv); $i++) {
564 1
                $this->addArg($schema, $parsed, $argv[$i]);
565 1
            }
566 38
        }
567
568 39
        return $parsed;
569
    }
570
571
    /**
572
     * Validates arguments against the schema.
573
     *
574
     * @param Args $args The arguments that were returned from {@link Cli::parseRaw()}.
575
     * @return Args|null
576
     */
577 33
    public function validate(Args $args) {
578 33
        $isValid = true;
579 33
        $command = $args->getCommand();
580 33
        $valid = new Args($command);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
581 33
        $schema = $this->getSchema($command);
582 33
        ksort($schema);
583
584
//        $meta = $schema[Cli::META];
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
585 33
        unset($schema[Cli::META]);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
586 33
        $opts = $args->getOpts();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
587 33
        $missing = [];
588
589
        // Check to see if the command is correct.
590 33
        if ($command && !$this->hasCommand($command) && $this->hasCommand()) {
591
            echo $this->red("Invalid command: $command.".PHP_EOL);
592
            $isValid = false;
593
        }
594
595
        // Add the args.
596 33
        $valid->setArgs($args->getArgs());
597
598 33
        foreach ($schema as $key => $definition) {
599
            // No Parameter (default)
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
600 32
            $type = Cli::val('type', $definition, 'string');
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
601
602 32
            if (array_key_exists($key, $opts)) {
603
                // Check for --key.
604 14
                $value = $opts[$key];
605 14
                if ($this->validateType($value, $type, $key, $definition)) {
606 11
                    $valid->setOpt($key, $value);
607 11
                } else {
608 3
                    $isValid = false;
609
                }
610 14
                unset($opts[$key]);
611 32
            } elseif (isset($definition['short']) && array_key_exists($definition['short'], $opts)) {
612
                // Check for -s.
613 23
                $value = $opts[$definition['short']];
614 23
                if ($this->validateType($value, $type, $key, $definition)) {
615 22
                    $valid->setOpt($key, $value);
616 22
                } else {
617 2
                    $isValid = false;
618
                }
619 23
                unset($opts[$definition['short']]);
620 27
            } elseif (array_key_exists('no-'.$key, $opts)) {
621
                // Check for --no-key.
622 2
                $value = $opts['no-'.$key];
623
624 2
                if ($type !== 'boolean') {
625 1
                    echo $this->red("Cannot apply the --no- prefix on the non boolean --$key.".PHP_EOL);
626 1
                    $isValid = false;
627 2
                } elseif ($this->validateType($value, $type, $key, $definition)) {
628
                    $valid->setOpt($key, !$value);
629
                } else {
630 1
                    $isValid = false;
631
                }
632 2
                unset($opts['no-'.$key]);
633 18
            } elseif ($definition['required']) {
634
                // The key was not supplied. Is it required?
635 2
                $missing[$key] = true;
636 2
                $valid->setOpt($key, false);
637 2
            }
638 33
        }
639
640 33
        if (count($missing)) {
641 2
            $isValid = false;
642 2
            foreach ($missing as $key => $v) {
643 2
                echo $this->red("Missing required option: $key".PHP_EOL);
644 2
            }
645 2
        }
646
647 33
        if (count($opts)) {
648
            $isValid = false;
649
            foreach ($opts as $key => $v) {
650
                echo $this->red("Invalid option: $key".PHP_EOL);
651
            }
652
        }
653
654 33
        if ($isValid) {
655 25
            return $valid;
656
        } else {
657 8
            echo PHP_EOL;
658 8
            return null;
659
        }
660
    }
661
662
    /**
663
     * Gets the full cli schema.
664
     *
665
     * @param string $command The name of the command. This can be left blank if there is no command.
666
     * @return array Returns the schema that matches the command.
667
     */
668 40
    public function getSchema($command = '') {
669 40
        $result = [];
670 40
        foreach ($this->commandSchemas as $pattern => $opts) {
671 40
            if (fnmatch($pattern, $command)) {
672 40
                $result = array_replace_recursive($result, $opts);
673 40
            }
674 40
        }
675 40
        return $result;
676
    }
677
678
    /**
679
     * Gets/sets the value for a current meta item.
680
     *
681
     * @param string $name The name of the meta key.
682
     * @param mixed $value Set a new value for the meta key.
683
     * @return Cli|mixed Returns the current value of the meta item or `$this` for fluent setting.
684
     */
685 8
    public function meta($name, $value = null) {
686 8
        if ($value !== null) {
687 8
            $this->currentSchema[Cli::META][$name] = $value;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
688 8
            return $this;
689
        }
690
        if (!isset($this->currentSchema[Cli::META][$name])) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
691
            return null;
692
        }
693
        return $this->currentSchema[Cli::META][$name];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
694
    }
695
696
    /**
697
     * Adds an option (opt) to the current schema.
698
     *
699
     * @param string $name The long name(s) of the parameter.
700
     * You can use either just one name or a string in the form 'long:short' to specify the long and short name.
701
     * @param string $description A human-readable description for the column.
702
     * @param bool $required Whether or not the opt is required.
703
     * @param string $type The type of parameter.
704
     * This must be one of string, bool, integer.
705
     * @return Cli Returns this object for fluent calls.
706
     * @throws \Exception Throws an exception when the type is invalid.
707
     */
708 38
    public function opt($name, $description, $required = false, $type = 'string') {
709
        switch ($type) {
710 38
            case 'str':
711 38
            case 'string':
712 36
                $type = 'string';
713 36
                break;
714 29
            case 'bool':
715 29
            case 'boolean':
716 26
                $type = 'boolean';
717 26
                break;
718 22
            case 'int':
719 22
            case 'integer':
720 22
                $type = 'integer';
721 22
                break;
722
            default:
723
                throw new \Exception("Invalid type: $type. Must be one of string, boolean, or integer.", 422);
724
        }
725
726
        // Break the name up into its long and short form.
727 38
        $parts = explode(':', $name, 2);
728 38
        $long = $parts[0];
729 38
        $short = static::val(1, $parts, '');
730
731 38
        $this->currentSchema[$long] = ['description' => $description, 'required' => $required, 'type' => $type, 'short' => $short];
732 38
        return $this;
733
    }
734
735
    /**
736
     * Define an arg on the current command.
737
     *
738
     * @param string $name The name of the arg.
739
     * @param string $description The arg description.
740
     * @param bool $required Whether or not the arg is required.
741
     * @return Cli Returns $this for fluent calls.
742
     */
743 5
    public function arg($name, $description, $required = false) {
744 5
        $this->currentSchema[Cli::META][Cli::ARGS][$name] =
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
745 5
            ['description' => $description, 'required' => $required];
746 5
        return $this;
747
    }
748
749
    /**
750
     * Selects the current command schema name.
751
     *
752
     * @param string $pattern The command pattern.
753
     * @return Cli Returns $this for fluent calls.
754
     */
755 4
    public function command($pattern) {
756 4
        if (!isset($this->commandSchemas[$pattern])) {
757 4
            $this->commandSchemas[$pattern] = [Cli::META => []];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
758 4
        }
759 4
        $this->currentSchema =& $this->commandSchemas[$pattern];
760
761 4
        return $this;
762
    }
763
764
765
    /**
766
     * Determine weather or not a value can be represented as a boolean.
767
     *
768
     * This method is sort of like {@link Cli::validateType()} but requires a more strict check of a boolean value.
769
     *
770
     * @param mixed $value The value to test.
771
     * @return bool
772
     */
773 2
    protected function isStrictBoolean($value, &$boolValue = null) {
774 2
        if ($value === true || $value === false) {
775
            $boolValue = $value;
776
            return true;
777 2
        } elseif (in_array($value, ['0', 'false', 'off', 'no'])) {
778 1
            $boolValue = false;
779 1
            return true;
780 2
        } elseif (in_array($value, ['1', 'true', 'on', 'yes'])) {
781
            $boolValue = true;
782
            return true;
783
        } else {
784 2
            $boolValue = null;
785 2
            return false;
786
        }
787
    }
788
789
    /**
790
     * Set the schema for a command.
791
     *
792
     * The schema array uses a short syntax so that commands can be specified as quickly as possible.
793
     * This schema is the exact same as those provided to {@link Schema::create()}.
794
     * The basic format of the array is the following:
795
     *
796
     * ```
797
     * [
798
     *     type:name[:shortCode][?],
799
     *     type:name[:shortCode][?],
800
     *     ...
801
     * ]
802
     * ```
803
     *
804
     * @param array $schema The schema array.
805
     */
806 1
    public function schema(array $schema) {
807 1
        $parsed = static::parseSchema($schema);
808
809 1
        $this->currentSchema = array_replace($this->currentSchema, $parsed);
810 1
    }
811
812
    /**
813
     * Bold some text.
814
     *
815
     * @param string $text The text to format.
816
     * @return string Returns the text surrounded by formatting commands.
817
     */
818 7
    public function bold($text) {
819 7
        return $this->formatString($text, ["\033[1m", "\033[0m"]);
820
    }
821
822
    /**
823
     * Bold some text.
824
     *
825
     * @param string $text The text to format.
826
     * @return string Returns the text surrounded by formatting commands.
827
     */
828
    public static function boldText($text) {
829
        return "\033[1m{$text}\033[0m";
830
    }
831
832
    /**
833
     * Make some text red.
834
     *
835
     * @param string $text The text to format.
836
     * @return string Returns  text surrounded by formatting commands.
837
     */
838 8
    public function red($text) {
839 8
        return $this->formatString($text, ["\033[1;31m", "\033[0m"]);
840
    }
841
842
    /**
843
     * Make some text red.
844
     *
845
     * @param string $text The text to format.
846
     * @return string Returns  text surrounded by formatting commands.
847
     */
848 1
    public static function redText($text) {
849 1
        return "\033[1;31m{$text}\033[0m";
850
    }
851
852
    /**
853
     * Make some text green.
854
     *
855
     * @param string $text The text to format.
856
     * @return string Returns  text surrounded by formatting commands.
857
     */
858
    public function green($text) {
859
        return $this->formatString($text, ["\033[1;32m", "\033[0m"]);
860
    }
861
862
    /**
863
     * Make some text green.
864
     *
865
     * @param string $text The text to format.
866
     * @return string Returns  text surrounded by formatting commands.
867
     */
868 1
    public static function greenText($text) {
869 1
        return "\033[1;32m{$text}\033[0m";
870
    }
871
872
    /**
873
     * Make some text blue.
874
     *
875
     * @param string $text The text to format.
876
     * @return string Returns  text surrounded by formatting commands.
877
     */
878
    public function blue($text) {
879
        return $this->formatString($text, ["\033[1;34m", "\033[0m"]);
880
    }
881
882
    /**
883
     * Make some text blue.
884
     *
885
     * @param string $text The text to format.
886
     * @return string Returns  text surrounded by formatting commands.
887
     */
888
    public static function blueText($text) {
889
        return "\033[1;34m{$text}\033[0m";
890
    }
891
892
    /**
893
     * Make some text purple.
894
     *
895
     * @param string $text The text to format.
896
     * @return string Returns  text surrounded by formatting commands.
897
     */
898
    public function purple($text) {
899
        return $this->formatString($text, ["\033[0;35m", "\033[0m"]);
900
    }
901
902
    /**
903
     * Make some text purple.
904
     *
905
     * @param string $text The text to format.
906
     * @return string Returns  text surrounded by formatting commands.
907
     */
908
    public static function purpleText($text) {
909
        return "\033[0;35m{$text}\033[0m";
910
    }
911
912
    /**
913
     * Format some text for the console.
914
     *
915
     * @param string $text The text to format.
916
     * @param string[] $wrap The format to wrap in the form ['before', 'after'].
917
     * @return string Returns the string formatted according to {@link Cli::$format}.
918
     */
919 15
    protected function formatString($text, array $wrap) {
920 15
        if ($this->formatOutput) {
921 1
            return "{$wrap[0]}$text{$wrap[1]}";
922
        } else {
923 14
            return $text;
924
        }
925
    }
926
927
    /**
928
     * Guess whether or not to format the output with colors.
929
     *
930
     * If the current environment is being redirected to a file then output should not be formatted. Also, Windows
931
     * machines do not support terminal colors so formatting should be suppressed on them too.
932
     *
933
     * @return bool Returns **true** if the output can be formatter or **false** otherwise.
934
     */
935 54
    public static function guessFormatOutput() {
936 54
        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
937
            return false;
938 54
        } elseif (function_exists('posix_isatty')) {
939 54
            return posix_isatty(STDOUT);
940
        } else {
941
            return true;
942
        }
943
    }
944
945
    /**
946
     * Sleep for a number of seconds, echoing out a dot on each second.
947
     *
948
     * @param int $seconds The number of seconds to sleep.
949
     */
950
    public static function sleep($seconds) {
951
        for ($i = 0; $i < $seconds; $i++) {
952
            sleep(1);
953
            echo '.';
954
        }
955
    }
956
957
    /**
958
     * Validate the type of a value and coerce it into the proper type.
959
     *
960
     * @param mixed &$value The value to validate.
961
     * @param string $type One of: bool, int, string.
962
     * @param string $name The name of the option if you want to print an error message.
963
     * @param array|null $def The option def if you want to print an error message.
964
     * @return bool Returns `true` if the value is the correct type.
965
     * @throws \Exception Throws an exception when {@see $type} is not a known value.
966
     */
967 31
    protected function validateType(&$value, $type, $name = '', $def = null) {
968
        switch ($type) {
969 31
            case 'boolean':
970 15
                if (is_bool($value)) {
971 10
                    $valid = true;
972 15
                } elseif ($value === 0) {
973
                    // 0 doesn't work well with in_array() so check it separately.
974
                    $value = false;
975
                    $valid = true;
976 8
                } elseif (in_array($value, [null, '', '0', 'false', 'no', 'disabled'])) {
977 5
                    $value = false;
978 5
                    $valid = true;
979 8
                } elseif (in_array($value, [1, '1', 'true', 'yes', 'enabled'])) {
980 2
                    $value = true;
981 2
                    $valid = true;
982 2
                } else {
983 3
                    $valid = false;
984
                }
985 15
                break;
986 28
            case 'integer':
987 9
                if (is_numeric($value)) {
988 6
                    $value = (int)$value;
989 6
                    $valid = true;
990 6
                } else {
991 3
                    $valid = false;
992
                }
993 9
                break;
994 27
            case 'string':
995 27
                $value = (string)$value;
996 27
                $valid = true;
997 27
                break;
998
            default:
999
                throw new \Exception("Unknown type: $type.", 400);
1000
        }
1001
1002 31
        if (!$valid && $name) {
1003 6
            $short = static::val('short', (array)$def);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
1004 6
            $nameStr = "--$name".($short ? " (-$short)" : '');
1005 6
            echo $this->red("The value of $nameStr is not a valid $type.".PHP_EOL);
1006 6
        }
1007
1008 31
        return $valid;
1009
    }
1010
1011
    /**
1012
     * Writes a lis of all of the commands.
1013
     */
1014 2
    protected function writeCommands() {
1015 2
        echo static::bold("COMMANDS").PHP_EOL;
1016
1017 2
        $table = new Table();
1018 2
        foreach ($this->commandSchemas as $pattern => $schema) {
1019 2
            if (static::isCommand($pattern)) {
1020
                $table
1021 2
                    ->row()
1022 2
                    ->cell($pattern)
1023 2
                    ->cell(Cli::val('description', Cli::val(Cli::META, $schema), ''));
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1024 2
            }
1025 2
        }
1026 2
        $table->write();
1027 2
    }
1028
1029
    /**
1030
     * Writes the cli help.
1031
     *
1032
     * @param string $command The name of the command or blank if there is no command.
1033
     */
1034 5
    public function writeHelp($command = '') {
1035 5
        $schema = $this->getSchema($command);
1036 5
        $this->writeSchemaHelp($schema);
1037 5
    }
1038
1039
    /**
1040
     * Writes the help for a given schema.
1041
     *
1042
     * @param array $schema A command line scheme returned from {@see Cli::getSchema()}.
1043
     */
1044 5
    protected function writeSchemaHelp($schema) {
1045
        // Write the command description.
1046 5
        $meta = Cli::val(Cli::META, $schema, []);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1047 5
        $description = Cli::val('description', $meta);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1048
1049 5
        if ($description) {
1050 3
            echo implode("\n", Cli::breakLines($description, 80, false)).PHP_EOL.PHP_EOL;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1051 3
        }
1052
1053 5
        unset($schema[Cli::META]);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1054
1055
        // Add the help.
1056 5
        $schema['help'] = [
1057 5
            'description' => 'Display this help.',
1058 5
            'type' => 'boolean',
1059
            'short' => '?'
1060 5
        ];
1061
1062 5
        echo Cli::bold('OPTIONS').PHP_EOL;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1063
1064 5
        ksort($schema);
1065
1066 5
        $table = new Table();
1067 5
        $table->setFormatOutput($this->formatOutput);
1068
1069 5
        foreach ($schema as $key => $definition) {
1070 5
            $table->row();
1071
1072
            // Write the keys.
1073 5
            $keys = "--{$key}";
1074 5
            if ($shortKey = Cli::val('short', $definition, false)) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1075 5
                $keys .= ", -$shortKey";
1076 5
            }
1077 5
            if (Cli::val('required', $definition)) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1078 3
                $table->bold($keys);
1079 3
            } else {
1080 5
                $table->cell($keys);
1081
            }
1082
1083
            // Write the description.
1084 5
            $table->cell(Cli::val('description', $definition, ''));
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1085 5
        }
1086
1087 5
        $table->write();
1088 5
        echo PHP_EOL;
1089
1090 5
        $args = Cli::val(Cli::ARGS, $meta, []);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1091 5
        if (!empty($args)) {
1092 2
            echo Cli::bold('ARGUMENTS').PHP_EOL;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1093
1094 2
            $table = new Table();
1095 2
            $table->setFormatOutput($this->formatOutput);
1096
1097 2
            foreach ($args as $argName => $arg) {
1098 2
                $table->row();
1099
1100 2
                if (Cli::val('required', $arg)) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1101 1
                    $table->bold($argName);
1102 1
                } else {
1103 1
                    $table->cell($argName);
1104
                }
1105
1106 2
                $table->cell(Cli::val('description', $arg, ''));
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1107 2
            }
1108 2
            $table->write();
1109 2
            echo PHP_EOL;
1110 2
        }
1111 5
    }
1112
1113
    /**
1114
     * Writes the basic usage information of the command.
1115
     *
1116
     * @param Args $args The parsed args returned from {@link Cli::parseRaw()}.
1117
     */
1118 6
    protected function writeUsage(Args $args) {
1119 6
        if ($filename = $args->getMeta('filename')) {
1120 6
            $schema = $this->getSchema($args->getCommand());
1121 6
            unset($schema[Cli::META]);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1122
1123 6
            echo static::bold("usage: ").$filename;
1124
1125 6
            if ($this->hasCommand()) {
1126 4
                if ($args->getCommand() && isset($this->commandSchemas[$args->getCommand()])) {
1127 2
                    echo ' '.$args->getCommand();
1128
1129 2
                } else {
1130 2
                    echo ' <command>';
1131
                }
1132 4
            }
1133
1134 6
            if ($this->hasOptions($args->getCommand())) {
1135 6
                echo " [<options>]";
1136 6
            }
1137
1138 6
            if ($hasArgs = $this->hasArgs($args->getCommand())) {
1139 4
                echo $hasArgs === 2 ? " <args>" : " [<args>]";
1140 4
            }
1141
1142 6
            echo PHP_EOL.PHP_EOL;
1143 6
        }
1144 6
    }
1145
1146
    /**
1147
     * Parse a schema in short form into a full schema array.
1148
     *
1149
     * @param array $arr The array to parse into a schema.
1150
     * @return array The full schema array.
1151
     * @throws \InvalidArgumentException Throws an exception when an item in the schema is invalid.
1152
     */
1153 1
    public static function parseSchema(array $arr) {
1154 1
        $result = [];
1155
1156 1
        foreach ($arr as $key => $value) {
1157 1
            if (is_int($key)) {
1158 1
                if (is_string($value)) {
1159
                    // This is a short param value.
1160 1
                    $param = static::parseShortParam($value);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 9 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
1161 1
                    $name = $param['name'];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 10 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
1162 1
                    $result[$name] = $param;
1163 1
                } else {
1164
                    throw new \InvalidArgumentException("Schema at position $key is not a valid param.", 500);
1165
                }
1166 1
            } else {
1167
                // The parameter is defined in the key.
1168 1
                $param = static::parseShortParam($key, $value);
1169 1
                $name = $param['name'];
1170
1171 1
                if (is_array($value)) {
1172
                    // The value describes a bit more about the schema.
1173
                    switch ($param['type']) {
1174
                        case 'array':
1175
                            if (isset($value['items'])) {
1176
                                // The value includes array schema information.
1177
                                $param = array_replace($param, $value);
1178
                            } else {
1179
                                // The value is a schema of items.
1180
                                $param['items'] = $value;
1181
                            }
1182
                            break;
1183
                        case 'object':
1184
                            // The value is a schema of the object.
1185
                            $param['properties'] = static::parseSchema($value);
1186
                            break;
1187
                        default:
1188
                            $param = array_replace($param, $value);
1189
                            break;
1190
                    }
1191 1
                } elseif (is_string($value)) {
1192 1
                    if ($param['type'] === 'array') {
1193
                        // Check to see if the value is the item type in the array.
1194
                        if (isset(self::$types[$value])) {
1195
                            $arrType = self::$types[$value];
1196
                        } elseif (($index = array_search($value, self::$types)) !== false) {
1197
                            $arrType = self::$types[$value];
1198
                        }
1199
1200
                        if (isset($arrType)) {
1201
                            $param['items'] = ['type' => $arrType];
1202
                        } else {
1203
                            $param['description'] = $value;
1204
                        }
1205
                    } else {
1206
                        // The value is the schema description.
1207 1
                        $param['description'] = $value;
1208
                    }
1209 1
                }
1210
1211 1
                $result[$name] = $param;
1212
            }
1213 1
        }
1214
1215 1
        return $result;
1216
    }
1217
1218
    /**
1219
     * Parse a short parameter string into a full array parameter.
1220
     *
1221
     * @param string $str The short parameter string to parse.
1222
     * @param array $other An array of other information that might help resolve ambiguity.
1223
     * @return array Returns an array in the form [name, [param]].
1224
     * @throws \InvalidArgumentException Throws an exception if the short param is not in the correct format.
1225
     */
1226 1
    protected static function parseShortParam($str, $other = []) {
1227
        // Is the parameter optional?
1228 1
        if (substr($str, -1) === '?') {
1229 1
            $required = false;
1230 1
            $str = substr($str, 0, -1);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
1231 1
        } else {
1232 1
            $required = true;
1233
        }
1234
1235
        // Check for a type.
1236 1
        $parts = explode(':', $str);
1237
1238 1
        if (count($parts) === 1) {
1239 1
            if (isset($other['type'])) {
1240
                $type = $other['type'];
1241
            } else {
1242 1
                $type = 'string';
1243
            }
1244 1
            $name = $parts[0];
1245 1
        } else {
1246 1
            $name = $parts[1];
1247
1248 1
            if (isset(self::$types[$parts[0]])) {
1249 1
                $type = self::$types[$parts[0]];
1250 1
            } else {
1251
                throw new \InvalidArgumentException("Invalid type {$parts[1]} for field $name.", 500);
1252
            }
1253
1254 1
            if (isset($parts[2])) {
1255 1
                $short = $parts[2];
1256 1
            }
1257
        }
1258
1259 1
        $result = ['name' => $name, 'type' => $type, 'required' => $required];
1260
1261 1
        if (isset($short)) {
1262 1
            $result['short'] = $short;
1263 1
        }
1264
1265 1
        return $result;
1266
    }
1267
1268
    /**
1269
     * Safely get a value out of an array.
1270
     *
1271
     * This function uses optimizations found in the [facebook libphputil library](https://github.com/facebook/libphutil).
1272
     *
1273
     * @param string|int $key The array key.
1274
     * @param array $array The array to get the value from.
1275
     * @param mixed $default The default value to return if the key doesn't exist.
1276
     * @return mixed The item from the array or `$default` if the array key doesn't exist.
1277
     */
1278 41
    public static function val($key, array $array, $default = null) {
1279
        // isset() is a micro-optimization - it is fast but fails for null values.
1280 41
        if (isset($array[$key])) {
1281 40
            return $array[$key];
1282
        }
1283
1284
        // Comparing $default is also a micro-optimization.
1285 41
        if ($default === null || array_key_exists($key, $array)) {
1286 39
            return null;
1287
        }
1288
1289 10
        return $default;
1290
    }
1291
}
1292