Completed
Branch development (b1b115)
by Johannes
10:28
created

Config   F

Complexity

Total Complexity 244

Size/Duplication

Total Lines 1665
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 244
c 0
b 0
f 0
dl 0
loc 1665
rs 0.8

How to fix   Complexity   

Complex Class

Complex classes like Config often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Config, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Stores the configuration used to run PHPCS and PHPCBF.
4
 *
5
 * Parses the command line to determine user supplied values
6
 * and provides functions to access data stored in config files.
7
 *
8
 * @author    Greg Sherwood <[email protected]>
9
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
10
 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
11
 */
12
13
namespace PHP_CodeSniffer;
14
15
use PHP_CodeSniffer\Exceptions\RuntimeException;
16
use PHP_CodeSniffer\Exceptions\DeepExitException;
17
18
class Config
19
{
20
21
    /**
22
     * The current version.
23
     *
24
     * @var string
25
     */
26
    const VERSION = '3.2.2';
27
28
    /**
29
     * Package stability; either stable, beta or alpha.
30
     *
31
     * @var string
32
     */
33
    const STABILITY = 'stable';
34
35
    /**
36
     * An array of settings that PHPCS and PHPCBF accept.
37
     *
38
     * This array is not meant to be accessed directly. Instead, use the settings
39
     * as if they are class member vars so the __get() and __set() magic methods
40
     * can be used to validate the values. For example, to set the verbosity level to
41
     * level 2, use $this->verbosity = 2; instead of accessing this property directly.
42
     *
43
     * The list of settings are:
44
     *
45
     * string[] files           The files and directories to check.
46
     * string[] standards       The standards being used for checking.
47
     * int      verbosity       How verbose the output should be.
48
     *                          0: no unnecessary output
49
     *                          1: basic output for files being checked
50
     *                          2: ruleset and file parsing output
51
     *                          3: sniff execution output
52
     * bool     interactive     Enable interactive checking mode.
53
     * bool     parallel        Check files in parallel.
54
     * bool     cache           Enable the use of the file cache.
55
     * bool     cacheFile       A file where the cache data should be written
56
     * bool     colors          Display colours in output.
57
     * bool     explain         Explain the coding standards.
58
     * bool     local           Process local files in directories only (no recursion).
59
     * bool     showSources     Show sniff source codes in report output.
60
     * bool     showProgress    Show basic progress information while running.
61
     * bool     quiet           Quiet mode; disables progress and verbose output.
62
     * bool     annotations     Process @codingStandard annotations.
63
     * int      tabWidth        How many spaces each tab is worth.
64
     * string   encoding        The encoding of the files being checked.
65
     * string[] sniffs          The sniffs that should be used for checking.
66
     *                          If empty, all sniffs in the supplied standards will be used.
67
     * string[] exclude         The sniffs that should be excluded from checking.
68
     *                          If empty, all sniffs in the supplied standards will be used.
69
     * string[] ignored         Regular expressions used to ignore files and folders during checking.
70
     * string   reportFile      A file where the report output should be written.
71
     * string   generator       The documentation generator to use.
72
     * string   filter          The filter to use for the run.
73
     * string[] bootstrap       One of more files to include before the run begins.
74
     * int      reportWidth     The maximum number of columns that reports should use for output.
75
     *                          Set to "auto" for have this value changed to the width of the terminal.
76
     * int      errorSeverity   The minimum severity an error must have to be displayed.
77
     * int      warningSeverity The minimum severity a warning must have to be displayed.
78
     * bool     recordErrors    Record the content of error messages as well as error counts.
79
     * string   suffix          A suffix to add to fixed files.
80
     * string   basepath        A file system location to strip from the paths of files shown in reports.
81
     * bool     stdin           Read content from STDIN instead of supplied files.
82
     * string   stdinContent    Content passed directly to PHPCS on STDIN.
83
     * string   stdinPath       The path to use for content passed on STDIN.
84
     *
85
     * array<string, string>      extensions File extensions that should be checked, and what tokenizer to use.
86
     *                                       E.g., array('inc' => 'PHP');
87
     * array<string, string|null> reports    The reports to use for printing output after the run.
88
     *                                       The format of the array is:
89
     *                                           array(
90
     *                                            'reportName1' => 'outputFile',
91
     *                                            'reportName2' => null,
92
     *                                           );
93
     *                                       If the array value is NULL, the report will be written to the screen.
94
     *
95
     * string[] unknown Any arguments gathered on the command line that are unknown to us.
96
     *                  E.g., using `phpcs -c` will give array('c');
97
     *
98
     * @var array<string, mixed>
99
     */
100
    private $settings = [
101
        'files'           => null,
102
        'standards'       => null,
103
        'verbosity'       => null,
104
        'interactive'     => null,
105
        'parallel'        => null,
106
        'cache'           => null,
107
        'cacheFile'       => null,
108
        'colors'          => null,
109
        'explain'         => null,
110
        'local'           => null,
111
        'showSources'     => null,
112
        'showProgress'    => null,
113
        'quiet'           => null,
114
        'annotations'     => null,
115
        'tabWidth'        => null,
116
        'encoding'        => null,
117
        'extensions'      => null,
118
        'sniffs'          => null,
119
        'exclude'         => null,
120
        'ignored'         => null,
121
        'reportFile'      => null,
122
        'generator'       => null,
123
        'filter'          => null,
124
        'bootstrap'       => null,
125
        'reports'         => null,
126
        'basepath'        => null,
127
        'reportWidth'     => null,
128
        'errorSeverity'   => null,
129
        'warningSeverity' => null,
130
        'recordErrors'    => null,
131
        'suffix'          => null,
132
        'stdin'           => null,
133
        'stdinContent'    => null,
134
        'stdinPath'       => null,
135
        'unknown'         => null,
136
    ];
137
138
    /**
139
     * Whether or not to kill the process when an unknown command line arg is found.
140
     *
141
     * If FALSE, arguments that are not command line options or file/directory paths
142
     * will be ignored and execution will continue. These values will be stored in
143
     * $this->unknown.
144
     *
145
     * @var boolean
146
     */
147
    public $dieOnUnknownArg;
148
149
    /**
150
     * The current command line arguments we are processing.
151
     *
152
     * @var string[]
153
     */
154
    private $cliArgs = [];
155
156
    /**
157
     * Command line values that the user has supplied directly.
158
     *
159
     * @var array<string, TRUE>
160
     */
161
    private $overriddenDefaults = [];
162
163
    /**
164
     * Config file data that has been loaded for the run.
165
     *
166
     * @var array<string, string>
167
     */
168
    private static $configData = null;
169
170
    /**
171
     * The full path to the config data file that has been loaded.
172
     *
173
     * @var string
174
     */
175
    private static $configDataFile = null;
176
177
    /**
178
     * Automatically discovered executable utility paths.
179
     *
180
     * @var array<string, string>
181
     */
182
    private static $executablePaths = [];
183
184
185
    /**
186
     * Get the value of an inaccessible property.
187
     *
188
     * @param string $name The name of the property.
189
     *
190
     * @return mixed
191
     * @throws RuntimeException If the setting name is invalid.
192
     */
193
    public function __get($name)
194
    {
195
        if (array_key_exists($name, $this->settings) === false) {
196
            throw new RuntimeException("ERROR: unable to get value of property \"$name\"");
197
        }
198
199
        return $this->settings[$name];
200
201
    }//end __get()
202
203
204
    /**
205
     * Set the value of an inaccessible property.
206
     *
207
     * @param string $name  The name of the property.
208
     * @param mixed  $value The value of the property.
209
     *
210
     * @return void
211
     * @throws RuntimeException If the setting name is invalid.
212
     */
213
    public function __set($name, $value)
214
    {
215
        if (array_key_exists($name, $this->settings) === false) {
216
            throw new RuntimeException("Can't __set() $name; setting doesn't exist");
217
        }
218
219
        switch ($name) {
220
        case 'reportWidth' :
221
            // Support auto terminal width.
222
            if ($value === 'auto' && preg_match('|\d+ (\d+)|', shell_exec('stty size 2>&1'), $matches) === 1) {
223
                $value = (int) $matches[1];
224
            } else {
225
                $value = (int) $value;
226
            }
227
            break;
228
        case 'standards' :
229
            $cleaned = [];
230
231
            // Check if the standard name is valid, or if the case is invalid.
232
            $installedStandards = Util\Standards::getInstalledStandards();
233
            foreach ($value as $standard) {
234
                foreach ($installedStandards as $validStandard) {
235
                    if (strtolower($standard) === strtolower($validStandard)) {
236
                        $standard = $validStandard;
237
                        break;
238
                    }
239
                }
240
241
                $cleaned[] = $standard;
242
            }
243
244
            $value = $cleaned;
245
            break;
246
        default :
247
            // No validation required.
248
            break;
249
        }//end switch
250
251
        $this->settings[$name] = $value;
252
253
    }//end __set()
254
255
256
    /**
257
     * Check if the value of an inaccessible property is set.
258
     *
259
     * @param string $name The name of the property.
260
     *
261
     * @return bool
262
     */
263
    public function __isset($name)
264
    {
265
        return isset($this->settings[$name]);
266
267
    }//end __isset()
268
269
270
    /**
271
     * Unset the value of an inaccessible property.
272
     *
273
     * @param string $name The name of the property.
274
     *
275
     * @return void
276
     */
277
    public function __unset($name)
278
    {
279
        $this->settings[$name] = null;
280
281
    }//end __unset()
282
283
284
    /**
285
     * Get the array of all config settings.
286
     *
287
     * @return array<string, mixed>
288
     */
289
    public function getSettings()
290
    {
291
        return $this->settings;
292
293
    }//end getSettings()
294
295
296
    /**
297
     * Set the array of all config settings.
298
     *
299
     * @param array<string, mixed> $settings The array of config settings.
300
     *
301
     * @return void
302
     */
303
    public function setSettings($settings)
304
    {
305
        return $this->settings = $settings;
306
307
    }//end setSettings()
308
309
310
    /**
311
     * Creates a Config object and populates it with command line values.
312
     *
313
     * @param array $cliArgs         An array of values gathered from CLI args.
314
     * @param bool  $dieOnUnknownArg Whether or not to kill the process when an
315
     *                               unknown command line arg is found.
316
     *
317
     * @return void
318
     */
319
    public function __construct(array $cliArgs=[], $dieOnUnknownArg=true)
320
    {
321
        if (defined('PHP_CODESNIFFER_IN_TESTS') === true) {
322
            // Let everything through during testing so that we can
323
            // make use of PHPUnit command line arguments as well.
324
            $this->dieOnUnknownArg = false;
325
        } else {
326
            $this->dieOnUnknownArg = $dieOnUnknownArg;
327
        }
328
329
        if (empty($cliArgs) === true) {
330
            $cliArgs = $_SERVER['argv'];
331
            array_shift($cliArgs);
332
        }
333
334
        $this->restoreDefaults();
335
        $this->setCommandLineValues($cliArgs);
336
337
        if (isset($this->overriddenDefaults['standards']) === false) {
338
            // They did not supply a standard to use.
339
            // Look for a default ruleset in the current directory or higher.
340
            $currentDir = getcwd();
341
342
            $defaultFiles = [
343
                '.phpcs.xml',
344
                'phpcs.xml',
345
                '.phpcs.xml.dist',
346
                'phpcs.xml.dist',
347
            ];
348
349
            do {
350
                foreach ($defaultFiles as $defaultFilename) {
351
                    $default = $currentDir.DIRECTORY_SEPARATOR.$defaultFilename;
352
                    if (is_file($default) === true) {
353
                        $this->standards = [$default];
354
                        break(2);
355
                    }
356
                }
357
358
                $lastDir    = $currentDir;
359
                $currentDir = dirname($currentDir);
360
            } while ($currentDir !== '.' && $currentDir !== $lastDir);
361
        }//end if
362
363
        if (defined('STDIN') === false
364
            || strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'
365
        ) {
366
            return;
367
        }
368
369
        $handle = fopen('php://stdin', 'r');
370
371
        // Check for content on STDIN.
372
        if ($this->stdin === true
373
            || (Util\Common::isStdinATTY() === false
374
            && feof($handle) === false)
375
        ) {
376
            $readStreams = [$handle];
377
            $writeSteams = null;
378
379
            $fileContents = '';
380
            while (is_resource($handle) === true && feof($handle) === false) {
381
                // Set a timeout of 200ms.
382
                if (stream_select($readStreams, $writeSteams, $writeSteams, 0, 200000) === 0) {
383
                    break;
384
                }
385
386
                $fileContents .= fgets($handle);
387
            }
388
389
            if (trim($fileContents) !== '') {
390
                $this->stdin        = true;
391
                $this->stdinContent = $fileContents;
392
                $this->overriddenDefaults['stdin']        = true;
393
                $this->overriddenDefaults['stdinContent'] = true;
394
            }
395
        }//end if
396
397
        fclose($handle);
398
399
    }//end __construct()
400
401
402
    /**
403
     * Set the command line values.
404
     *
405
     * @param array $args An array of command line arguments to set.
406
     *
407
     * @return void
408
     */
409
    public function setCommandLineValues($args)
410
    {
411
        $this->cliArgs = $args;
412
        $numArgs       = count($args);
413
414
        for ($i = 0; $i < $numArgs; $i++) {
415
            $arg = $this->cliArgs[$i];
416
            if ($arg === '') {
417
                continue;
418
            }
419
420
            if ($arg{0} === '-') {
421
                if ($arg === '-') {
422
                    // Asking to read from STDIN.
423
                    $this->stdin = true;
424
                    $this->overriddenDefaults['stdin'] = true;
425
                    continue;
426
                }
427
428
                if ($arg === '--') {
429
                    // Empty argument, ignore it.
430
                    continue;
431
                }
432
433
                if ($arg{1} === '-') {
434
                    $this->processLongArgument(substr($arg, 2), $i);
435
                } else {
436
                    $switches = str_split($arg);
437
                    foreach ($switches as $switch) {
438
                        if ($switch === '-') {
439
                            continue;
440
                        }
441
442
                        $this->processShortArgument($switch, $i);
443
                    }
444
                }
445
            } else {
446
                $this->processUnknownArgument($arg, $i);
447
            }//end if
448
        }//end for
449
450
    }//end setCommandLineValues()
451
452
453
    /**
454
     * Restore default values for all possible command line arguments.
455
     *
456
     * @return array
457
     */
458
    public function restoreDefaults()
459
    {
460
        $this->files           = [];
461
        $this->standards       = ['PEAR'];
462
        $this->verbosity       = 0;
463
        $this->interactive     = false;
464
        $this->cache           = false;
465
        $this->cacheFile       = null;
466
        $this->colors          = false;
467
        $this->explain         = false;
468
        $this->local           = false;
469
        $this->showSources     = false;
470
        $this->showProgress    = false;
471
        $this->quiet           = false;
472
        $this->annotations     = true;
473
        $this->parallel        = 1;
474
        $this->tabWidth        = 0;
475
        $this->encoding        = 'utf-8';
476
        $this->extensions      = [
477
            'php' => 'PHP',
478
            'inc' => 'PHP',
479
            'js'  => 'JS',
480
            'css' => 'CSS',
481
        ];
482
        $this->sniffs          = [];
483
        $this->exclude         = [];
484
        $this->ignored         = [];
485
        $this->reportFile      = null;
486
        $this->generator       = null;
487
        $this->filter          = null;
488
        $this->bootstrap       = [];
489
        $this->basepath        = null;
490
        $this->reports         = ['full' => null];
491
        $this->reportWidth     = 'auto';
492
        $this->errorSeverity   = 5;
493
        $this->warningSeverity = 5;
494
        $this->recordErrors    = true;
495
        $this->suffix          = '';
496
        $this->stdin           = false;
497
        $this->stdinContent    = null;
498
        $this->stdinPath       = null;
499
        $this->unknown         = [];
500
501
        $standard = self::getConfigData('default_standard');
502
        if ($standard !== null) {
503
            $this->standards = explode(',', $standard);
504
        }
505
506
        $reportFormat = self::getConfigData('report_format');
507
        if ($reportFormat !== null) {
508
            $this->reports = [$reportFormat => null];
509
        }
510
511
        $tabWidth = self::getConfigData('tab_width');
512
        if ($tabWidth !== null) {
513
            $this->tabWidth = (int) $tabWidth;
514
        }
515
516
        $encoding = self::getConfigData('encoding');
517
        if ($encoding !== null) {
518
            $this->encoding = strtolower($encoding);
519
        }
520
521
        $severity = self::getConfigData('severity');
522
        if ($severity !== null) {
523
            $this->errorSeverity   = (int) $severity;
524
            $this->warningSeverity = (int) $severity;
525
        }
526
527
        $severity = self::getConfigData('error_severity');
528
        if ($severity !== null) {
529
            $this->errorSeverity = (int) $severity;
530
        }
531
532
        $severity = self::getConfigData('warning_severity');
533
        if ($severity !== null) {
534
            $this->warningSeverity = (int) $severity;
535
        }
536
537
        $showWarnings = self::getConfigData('show_warnings');
538
        if ($showWarnings !== null) {
539
            $showWarnings = (bool) $showWarnings;
540
            if ($showWarnings === false) {
541
                $this->warningSeverity = 0;
542
            }
543
        }
544
545
        $reportWidth = self::getConfigData('report_width');
546
        if ($reportWidth !== null) {
547
            $this->reportWidth = $reportWidth;
548
        }
549
550
        $showProgress = self::getConfigData('show_progress');
551
        if ($showProgress !== null) {
552
            $this->showProgress = (bool) $showProgress;
553
        }
554
555
        $quiet = self::getConfigData('quiet');
556
        if ($quiet !== null) {
557
            $this->quiet = (bool) $quiet;
558
        }
559
560
        $colors = self::getConfigData('colors');
561
        if ($colors !== null) {
562
            $this->colors = (bool) $colors;
563
        }
564
565
        if (defined('PHP_CODESNIFFER_IN_TESTS') === false) {
566
            $cache = self::getConfigData('cache');
567
            if ($cache !== null) {
568
                $this->cache = (bool) $cache;
569
            }
570
571
            $parallel = self::getConfigData('parallel');
572
            if ($parallel !== null) {
573
                $this->parallel = max((int) $parallel, 1);
574
            }
575
        }
576
577
    }//end restoreDefaults()
578
579
580
    /**
581
     * Processes a short (-e) command line argument.
582
     *
583
     * @param string $arg The command line argument.
584
     * @param int    $pos The position of the argument on the command line.
585
     *
586
     * @return void
587
     */
588
    public function processShortArgument($arg, $pos)
589
    {
590
        switch ($arg) {
591
        case 'h':
592
        case '?':
593
            ob_start();
594
            $this->printUsage();
595
            $output = ob_get_contents();
596
            ob_end_clean();
597
            throw new DeepExitException($output, 0);
598
        case 'i' :
599
            ob_start();
600
            Util\Standards::printInstalledStandards();
601
            $output = ob_get_contents();
602
            ob_end_clean();
603
            throw new DeepExitException($output, 0);
604
        case 'v' :
605
            if ($this->quiet === true) {
606
                // Ignore when quiet mode is enabled.
607
                break;
608
            }
609
610
            $this->verbosity++;
611
            $this->overriddenDefaults['verbosity'] = true;
612
            break;
613
        case 'l' :
614
            $this->local = true;
615
            $this->overriddenDefaults['local'] = true;
616
            break;
617
        case 's' :
618
            $this->showSources = true;
619
            $this->overriddenDefaults['showSources'] = true;
620
            break;
621
        case 'a' :
622
            $this->interactive = true;
623
            $this->overriddenDefaults['interactive'] = true;
624
            break;
625
        case 'e':
626
            $this->explain = true;
627
            $this->overriddenDefaults['explain'] = true;
628
            break;
629
        case 'p' :
630
            if ($this->quiet === true) {
631
                // Ignore when quiet mode is enabled.
632
                break;
633
            }
634
635
            $this->showProgress = true;
636
            $this->overriddenDefaults['showProgress'] = true;
637
            break;
638
        case 'q' :
639
            // Quiet mode disables a few other settings as well.
640
            $this->quiet        = true;
641
            $this->showProgress = false;
642
            $this->verbosity    = 0;
643
644
            $this->overriddenDefaults['quiet'] = true;
645
            break;
646
        case 'm' :
647
            $this->recordErrors = false;
648
            $this->overriddenDefaults['recordErrors'] = true;
649
            break;
650
        case 'd' :
651
            $ini = explode('=', $this->cliArgs[($pos + 1)]);
652
            $this->cliArgs[($pos + 1)] = '';
653
            if (isset($ini[1]) === true) {
654
                ini_set($ini[0], $ini[1]);
655
            } else {
656
                ini_set($ini[0], true);
657
            }
658
            break;
659
        case 'n' :
660
            if (isset($this->overriddenDefaults['warningSeverity']) === false) {
661
                $this->warningSeverity = 0;
662
                $this->overriddenDefaults['warningSeverity'] = true;
663
            }
664
            break;
665
        case 'w' :
666
            if (isset($this->overriddenDefaults['warningSeverity']) === false) {
667
                $this->warningSeverity = $this->errorSeverity;
668
                $this->overriddenDefaults['warningSeverity'] = true;
669
            }
670
            break;
671
        default:
672
            if ($this->dieOnUnknownArg === false) {
673
                $unknown       = $this->unknown;
674
                $unknown[]     = $arg;
675
                $this->unknown = $unknown;
676
            } else {
677
                $this->processUnknownArgument('-'.$arg, $pos);
678
            }
679
        }//end switch
680
681
    }//end processShortArgument()
682
683
684
    /**
685
     * Processes a long (--example) command line argument.
686
     *
687
     * @param string $arg The command line argument.
688
     * @param int    $pos The position of the argument on the command line.
689
     *
690
     * @return void
691
     */
692
    public function processLongArgument($arg, $pos)
693
    {
694
        switch ($arg) {
695
        case 'help':
696
            ob_start();
697
            $this->printUsage();
698
            $output = ob_get_contents();
699
            ob_end_clean();
700
            throw new DeepExitException($output, 0);
701
        case 'version':
702
            $output  = 'PHP_CodeSniffer version '.self::VERSION.' ('.self::STABILITY.') ';
703
            $output .= 'by Squiz (http://www.squiz.net)'.PHP_EOL;
704
            throw new DeepExitException($output, 0);
705
        case 'colors':
706
            if (isset($this->overriddenDefaults['colors']) === true) {
707
                break;
708
            }
709
710
            $this->colors = true;
711
            $this->overriddenDefaults['colors'] = true;
712
            break;
713
        case 'no-colors':
714
            if (isset($this->overriddenDefaults['colors']) === true) {
715
                break;
716
            }
717
718
            $this->colors = false;
719
            $this->overriddenDefaults['colors'] = true;
720
            break;
721
        case 'cache':
722
            if (isset($this->overriddenDefaults['cache']) === true) {
723
                break;
724
            }
725
726
            if (defined('PHP_CODESNIFFER_IN_TESTS') === false) {
727
                $this->cache = true;
728
                $this->overriddenDefaults['cache'] = true;
729
            }
730
            break;
731
        case 'no-cache':
732
            if (isset($this->overriddenDefaults['cache']) === true) {
733
                break;
734
            }
735
736
            $this->cache = false;
737
            $this->overriddenDefaults['cache'] = true;
738
            break;
739
        case 'ignore-annotations':
740
            if (isset($this->overriddenDefaults['annotations']) === true) {
741
                break;
742
            }
743
744
            $this->annotations = false;
745
            $this->overriddenDefaults['annotations'] = true;
746
            break;
747
        case 'config-set':
748
            if (isset($this->cliArgs[($pos + 1)]) === false
749
                || isset($this->cliArgs[($pos + 2)]) === false
750
            ) {
751
                $error  = 'ERROR: Setting a config option requires a name and value'.PHP_EOL.PHP_EOL;
752
                $error .= $this->printShortUsage(true);
753
                throw new DeepExitException($error, 3);
754
            }
755
756
            $key     = $this->cliArgs[($pos + 1)];
757
            $value   = $this->cliArgs[($pos + 2)];
758
            $current = self::getConfigData($key);
759
760
            try {
761
                $this->setConfigData($key, $value);
762
            } catch (\Exception $e) {
763
                throw new DeepExitException($e->getMessage().PHP_EOL, 3);
764
            }
765
766
            $output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
767
768
            if ($current === null) {
769
                $output .= "Config value \"$key\" added successfully".PHP_EOL;
770
            } else {
771
                $output .= "Config value \"$key\" updated successfully; old value was \"$current\"".PHP_EOL;
772
            }
773
            throw new DeepExitException($output, 0);
774
        case 'config-delete':
775
            if (isset($this->cliArgs[($pos + 1)]) === false) {
776
                $error  = 'ERROR: Deleting a config option requires the name of the option'.PHP_EOL.PHP_EOL;
777
                $error .= $this->printShortUsage(true);
778
                throw new DeepExitException($error, 3);
779
            }
780
781
            $output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
782
783
            $key     = $this->cliArgs[($pos + 1)];
784
            $current = self::getConfigData($key);
785
            if ($current === null) {
786
                $output .= "Config value \"$key\" has not been set".PHP_EOL;
787
            } else {
788
                try {
789
                    $this->setConfigData($key, null);
790
                } catch (\Exception $e) {
791
                    throw new DeepExitException($e->getMessage().PHP_EOL, 3);
792
                }
793
794
                $output .= "Config value \"$key\" removed successfully; old value was \"$current\"".PHP_EOL;
795
            }
796
            throw new DeepExitException($output, 0);
797
        case 'config-show':
798
            ob_start();
799
            $data = self::getAllConfigData();
800
            echo 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL;
801
            $this->printConfigData($data);
802
            $output = ob_get_contents();
803
            ob_end_clean();
804
            throw new DeepExitException($output, 0);
805
        case 'runtime-set':
806
            if (isset($this->cliArgs[($pos + 1)]) === false
807
                || isset($this->cliArgs[($pos + 2)]) === false
808
            ) {
809
                $error  = 'ERROR: Setting a runtime config option requires a name and value'.PHP_EOL.PHP_EOL;
810
                $error .= $this->printShortUsage(true);
811
                throw new DeepExitException($error, 3);
812
            }
813
814
            $key   = $this->cliArgs[($pos + 1)];
815
            $value = $this->cliArgs[($pos + 2)];
816
            $this->cliArgs[($pos + 1)] = '';
817
            $this->cliArgs[($pos + 2)] = '';
818
            self::setConfigData($key, $value, true);
819
            break;
820
        default:
821
            if (substr($arg, 0, 7) === 'sniffs=') {
822
                if (isset($this->overriddenDefaults['sniffs']) === true) {
823
                    break;
824
                }
825
826
                $sniffs = explode(',', substr($arg, 7));
827
                foreach ($sniffs as $sniff) {
828
                    if (substr_count($sniff, '.') !== 2) {
829
                        $error  = 'ERROR: The specified sniff code "'.$sniff.'" is invalid'.PHP_EOL.PHP_EOL;
830
                        $error .= $this->printShortUsage(true);
831
                        throw new DeepExitException($error, 3);
832
                    }
833
                }
834
835
                $this->sniffs = $sniffs;
836
                $this->overriddenDefaults['sniffs'] = true;
837
            } else if (substr($arg, 0, 8) === 'exclude=') {
838
                if (isset($this->overriddenDefaults['exclude']) === true) {
839
                    break;
840
                }
841
842
                $sniffs = explode(',', substr($arg, 8));
843
                foreach ($sniffs as $sniff) {
844
                    if (substr_count($sniff, '.') !== 2) {
845
                        $error  = 'ERROR: The specified sniff code "'.$sniff.'" is invalid'.PHP_EOL.PHP_EOL;
846
                        $error .= $this->printShortUsage(true);
847
                        throw new DeepExitException($error, 3);
848
                    }
849
                }
850
851
                $this->exclude = $sniffs;
852
                $this->overriddenDefaults['exclude'] = true;
853
            } else if (defined('PHP_CODESNIFFER_IN_TESTS') === false
854
                && substr($arg, 0, 6) === 'cache='
855
            ) {
856
                if ((isset($this->overriddenDefaults['cache']) === true
857
                    && $this->cache === false)
858
                    || isset($this->overriddenDefaults['cacheFile']) === true
859
                ) {
860
                    break;
861
                }
862
863
                // Turn caching on.
864
                $this->cache = true;
865
                $this->overriddenDefaults['cache'] = true;
866
867
                $this->cacheFile = Util\Common::realpath(substr($arg, 6));
868
869
                // It may not exist and return false instead.
870
                if ($this->cacheFile === false) {
871
                    $this->cacheFile = substr($arg, 6);
872
873
                    $dir = dirname($this->cacheFile);
874
                    if (is_dir($dir) === false) {
875
                        $error  = 'ERROR: The specified cache file path "'.$this->cacheFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
876
                        $error .= $this->printShortUsage(true);
877
                        throw new DeepExitException($error, 3);
878
                    }
879
880
                    if ($dir === '.') {
881
                        // Passed cache file is a file in the current directory.
882
                        $this->cacheFile = getcwd().'/'.basename($this->cacheFile);
883
                    } else {
884
                        if ($dir{0} === '/') {
885
                            // An absolute path.
886
                            $dir = Util\Common::realpath($dir);
887
                        } else {
888
                            $dir = Util\Common::realpath(getcwd().'/'.$dir);
889
                        }
890
891
                        if ($dir !== false) {
892
                            // Cache file path is relative.
893
                            $this->cacheFile = $dir.'/'.basename($this->cacheFile);
894
                        }
895
                    }
896
                }//end if
897
898
                $this->overriddenDefaults['cacheFile'] = true;
899
900
                if (is_dir($this->cacheFile) === true) {
901
                    $error  = 'ERROR: The specified cache file path "'.$this->cacheFile.'" is a directory'.PHP_EOL.PHP_EOL;
902
                    $error .= $this->printShortUsage(true);
903
                    throw new DeepExitException($error, 3);
904
                }
905
            } else if (substr($arg, 0, 10) === 'bootstrap=') {
906
                $files     = explode(',', substr($arg, 10));
907
                $bootstrap = [];
908
                foreach ($files as $file) {
909
                    $path = Util\Common::realpath($file);
910
                    if ($path === false) {
911
                        $error  = 'ERROR: The specified bootstrap file "'.$file.'" does not exist'.PHP_EOL.PHP_EOL;
912
                        $error .= $this->printShortUsage(true);
913
                        throw new DeepExitException($error, 3);
914
                    }
915
916
                    $bootstrap[] = $path;
917
                }
918
919
                $this->bootstrap = array_merge($this->bootstrap, $bootstrap);
920
                $this->overriddenDefaults['bootstrap'] = true;
921
            } else if (substr($arg, 0, 10) === 'file-list=') {
922
                $fileList = substr($arg, 10);
923
                $path     = Util\Common::realpath($fileList);
924
                if ($path === false) {
925
                    $error  = 'ERROR: The specified file list "'.$fileList.'" does not exist'.PHP_EOL.PHP_EOL;
926
                    $error .= $this->printShortUsage(true);
927
                    throw new DeepExitException($error, 3);
928
                }
929
930
                $files = file($path);
931
                foreach ($files as $inputFile) {
932
                    $inputFile = trim($inputFile);
933
934
                    // Skip empty lines.
935
                    if ($inputFile === '') {
936
                        continue;
937
                    }
938
939
                    $this->processFilePath($inputFile);
940
                }
941
            } else if (substr($arg, 0, 11) === 'stdin-path=') {
942
                if (isset($this->overriddenDefaults['stdinPath']) === true) {
943
                    break;
944
                }
945
946
                $this->stdinPath = Util\Common::realpath(substr($arg, 11));
947
948
                // It may not exist and return false instead, so use whatever they gave us.
949
                if ($this->stdinPath === false) {
950
                    $this->stdinPath = trim(substr($arg, 11));
951
                }
952
953
                $this->overriddenDefaults['stdinPath'] = true;
954
            } else if (PHP_CODESNIFFER_CBF === false && substr($arg, 0, 12) === 'report-file=') {
955
                if (isset($this->overriddenDefaults['reportFile']) === true) {
956
                    break;
957
                }
958
959
                $this->reportFile = Util\Common::realpath(substr($arg, 12));
960
961
                // It may not exist and return false instead.
962
                if ($this->reportFile === false) {
963
                    $this->reportFile = substr($arg, 12);
964
965
                    $dir = dirname($this->reportFile);
966
                    if (is_dir($dir) === false) {
967
                        $error  = 'ERROR: The specified report file path "'.$this->reportFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
968
                        $error .= $this->printShortUsage(true);
969
                        throw new DeepExitException($error, 3);
970
                    }
971
972
                    if ($dir === '.') {
973
                        // Passed report file is a file in the current directory.
974
                        $this->reportFile = getcwd().'/'.basename($this->reportFile);
975
                    } else {
976
                        if ($dir{0} === '/') {
977
                            // An absolute path.
978
                            $dir = Util\Common::realpath($dir);
979
                        } else {
980
                            $dir = Util\Common::realpath(getcwd().'/'.$dir);
981
                        }
982
983
                        if ($dir !== false) {
984
                            // Report file path is relative.
985
                            $this->reportFile = $dir.'/'.basename($this->reportFile);
986
                        }
987
                    }
988
                }//end if
989
990
                $this->overriddenDefaults['reportFile'] = true;
991
992
                if (is_dir($this->reportFile) === true) {
993
                    $error  = 'ERROR: The specified report file path "'.$this->reportFile.'" is a directory'.PHP_EOL.PHP_EOL;
994
                    $error .= $this->printShortUsage(true);
995
                    throw new DeepExitException($error, 3);
996
                }
997
            } else if (substr($arg, 0, 13) === 'report-width=') {
998
                if (isset($this->overriddenDefaults['reportWidth']) === true) {
999
                    break;
1000
                }
1001
1002
                $this->reportWidth = substr($arg, 13);
1003
                $this->overriddenDefaults['reportWidth'] = true;
1004
            } else if (substr($arg, 0, 9) === 'basepath=') {
1005
                if (isset($this->overriddenDefaults['basepath']) === true) {
1006
                    break;
1007
                }
1008
1009
                $this->overriddenDefaults['basepath'] = true;
1010
1011
                if (substr($arg, 9) === '') {
1012
                    $this->basepath = null;
1013
                    break;
1014
                }
1015
1016
                $this->basepath = Util\Common::realpath(substr($arg, 9));
1017
1018
                // It may not exist and return false instead.
1019
                if ($this->basepath === false) {
1020
                    $this->basepath = substr($arg, 9);
1021
                }
1022
1023
                if (is_dir($this->basepath) === false) {
1024
                    $error  = 'ERROR: The specified basepath "'.$this->basepath.'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
1025
                    $error .= $this->printShortUsage(true);
1026
                    throw new DeepExitException($error, 3);
1027
                }
1028
            } else if ((substr($arg, 0, 7) === 'report=' || substr($arg, 0, 7) === 'report-')) {
1029
                $reports = [];
1030
1031
                if ($arg[6] === '-') {
1032
                    // This is a report with file output.
1033
                    $split = strpos($arg, '=');
1034
                    if ($split === false) {
1035
                        $report = substr($arg, 7);
1036
                        $output = null;
1037
                    } else {
1038
                        $report = substr($arg, 7, ($split - 7));
1039
                        $output = substr($arg, ($split + 1));
1040
                        if ($output === false) {
1041
                            $output = null;
1042
                        } else {
1043
                            $dir = dirname($output);
1044
                            if ($dir === '.') {
1045
                                // Passed report file is a filename in the current directory.
1046
                                $output = getcwd().'/'.basename($output);
1047
                            } else {
1048
                                if ($dir{0} === '/') {
1049
                                    // An absolute path.
1050
                                    $dir = Util\Common::realpath($dir);
1051
                                } else {
1052
                                    $dir = Util\Common::realpath(getcwd().'/'.$dir);
1053
                                }
1054
1055
                                if ($dir !== false) {
1056
                                    // Report file path is relative.
1057
                                    $output = $dir.'/'.basename($output);
1058
                                }
1059
                            }
1060
                        }//end if
1061
                    }//end if
1062
1063
                    $reports[$report] = $output;
1064
                } else {
1065
                    // This is a single report.
1066
                    if (isset($this->overriddenDefaults['reports']) === true) {
1067
                        break;
1068
                    }
1069
1070
                    $reportNames = explode(',', substr($arg, 7));
1071
                    foreach ($reportNames as $report) {
1072
                        $reports[$report] = null;
1073
                    }
1074
                }//end if
1075
1076
                // Remove the default value so the CLI value overrides it.
1077
                if (isset($this->overriddenDefaults['reports']) === false) {
1078
                    $this->reports = $reports;
1079
                } else {
1080
                    $this->reports = array_merge($this->reports, $reports);
1081
                }
1082
1083
                $this->overriddenDefaults['reports'] = true;
1084
            } else if (substr($arg, 0, 7) === 'filter=') {
1085
                if (isset($this->overriddenDefaults['filter']) === true) {
1086
                    break;
1087
                }
1088
1089
                $this->filter = substr($arg, 7);
1090
                $this->overriddenDefaults['filter'] = true;
1091
            } else if (substr($arg, 0, 9) === 'standard=') {
1092
                $standards = trim(substr($arg, 9));
1093
                if ($standards !== '') {
1094
                    $this->standards = explode(',', $standards);
1095
                }
1096
1097
                $this->overriddenDefaults['standards'] = true;
1098
            } else if (substr($arg, 0, 11) === 'extensions=') {
1099
                if (isset($this->overriddenDefaults['extensions']) === true) {
1100
                    break;
1101
                }
1102
1103
                $extensions    = explode(',', substr($arg, 11));
1104
                $newExtensions = [];
1105
                foreach ($extensions as $ext) {
1106
                    $slash = strpos($ext, '/');
1107
                    if ($slash !== false) {
1108
                        // They specified the tokenizer too.
1109
                        list($ext, $tokenizer) = explode('/', $ext);
1110
                        $newExtensions[$ext]   = strtoupper($tokenizer);
1111
                        continue;
1112
                    }
1113
1114
                    if (isset($this->extensions[$ext]) === true) {
1115
                        $newExtensions[$ext] = $this->extensions[$ext];
1116
                    } else {
1117
                        $newExtensions[$ext] = 'PHP';
1118
                    }
1119
                }
1120
1121
                $this->extensions = $newExtensions;
1122
                $this->overriddenDefaults['extensions'] = true;
1123
            } else if (substr($arg, 0, 7) === 'suffix=') {
1124
                if (isset($this->overriddenDefaults['suffix']) === true) {
1125
                    break;
1126
                }
1127
1128
                $this->suffix = substr($arg, 7);
1129
                $this->overriddenDefaults['suffix'] = true;
1130
            } else if (substr($arg, 0, 9) === 'parallel=') {
1131
                if (isset($this->overriddenDefaults['parallel']) === true) {
1132
                    break;
1133
                }
1134
1135
                $this->parallel = max((int) substr($arg, 9), 1);
1136
                $this->overriddenDefaults['parallel'] = true;
1137
            } else if (substr($arg, 0, 9) === 'severity=') {
1138
                $this->errorSeverity   = (int) substr($arg, 9);
1139
                $this->warningSeverity = $this->errorSeverity;
1140
                if (isset($this->overriddenDefaults['errorSeverity']) === false) {
1141
                    $this->overriddenDefaults['errorSeverity'] = true;
1142
                }
1143
1144
                if (isset($this->overriddenDefaults['warningSeverity']) === false) {
1145
                    $this->overriddenDefaults['warningSeverity'] = true;
1146
                }
1147
            } else if (substr($arg, 0, 15) === 'error-severity=') {
1148
                if (isset($this->overriddenDefaults['errorSeverity']) === true) {
1149
                    break;
1150
                }
1151
1152
                $this->errorSeverity = (int) substr($arg, 15);
1153
                $this->overriddenDefaults['errorSeverity'] = true;
1154
            } else if (substr($arg, 0, 17) === 'warning-severity=') {
1155
                if (isset($this->overriddenDefaults['warningSeverity']) === true) {
1156
                    break;
1157
                }
1158
1159
                $this->warningSeverity = (int) substr($arg, 17);
1160
                $this->overriddenDefaults['warningSeverity'] = true;
1161
            } else if (substr($arg, 0, 7) === 'ignore=') {
1162
                if (isset($this->overriddenDefaults['ignored']) === true) {
1163
                    break;
1164
                }
1165
1166
                // Split the ignore string on commas, unless the comma is escaped
1167
                // using 1 or 3 slashes (\, or \\\,).
1168
                $patterns = preg_split(
1169
                    '/(?<=(?<!\\\\)\\\\\\\\),|(?<!\\\\),/',
1170
                    substr($arg, 7)
1171
                );
1172
1173
                $ignored = [];
1174
                foreach ($patterns as $pattern) {
1175
                    $pattern = trim($pattern);
1176
                    if ($pattern === '') {
1177
                        continue;
1178
                    }
1179
1180
                    $ignored[$pattern] = 'absolute';
1181
                }
1182
1183
                $this->ignored = $ignored;
1184
                $this->overriddenDefaults['ignored'] = true;
1185
            } else if (substr($arg, 0, 10) === 'generator='
1186
                && PHP_CODESNIFFER_CBF === false
1187
            ) {
1188
                if (isset($this->overriddenDefaults['generator']) === true) {
1189
                    break;
1190
                }
1191
1192
                $this->generator = substr($arg, 10);
1193
                $this->overriddenDefaults['generator'] = true;
1194
            } else if (substr($arg, 0, 9) === 'encoding=') {
1195
                if (isset($this->overriddenDefaults['encoding']) === true) {
1196
                    break;
1197
                }
1198
1199
                $this->encoding = strtolower(substr($arg, 9));
1200
                $this->overriddenDefaults['encoding'] = true;
1201
            } else if (substr($arg, 0, 10) === 'tab-width=') {
1202
                if (isset($this->overriddenDefaults['tabWidth']) === true) {
1203
                    break;
1204
                }
1205
1206
                $this->tabWidth = (int) substr($arg, 10);
1207
                $this->overriddenDefaults['tabWidth'] = true;
1208
            } else {
1209
                if ($this->dieOnUnknownArg === false) {
1210
                    $eqPos = strpos($arg, '=');
1211
                    try {
1212
                        if ($eqPos === false) {
1213
                            $this->values[$arg] = $arg;
1214
                        } else {
1215
                            $value = substr($arg, ($eqPos + 1));
1216
                            $arg   = substr($arg, 0, $eqPos);
1217
                            $this->values[$arg] = $value;
1218
                        }
1219
                    } catch (RuntimeException $e) {
1220
                        // Value is not valid, so just ignore it.
1221
                    }
1222
                } else {
1223
                    $this->processUnknownArgument('--'.$arg, $pos);
1224
                }
1225
            }//end if
1226
1227
            break;
1228
        }//end switch
1229
1230
    }//end processLongArgument()
1231
1232
1233
    /**
1234
     * Processes an unknown command line argument.
1235
     *
1236
     * Assumes all unknown arguments are files and folders to check.
1237
     *
1238
     * @param string $arg The command line argument.
1239
     * @param int    $pos The position of the argument on the command line.
1240
     *
1241
     * @return void
1242
     */
1243
    public function processUnknownArgument($arg, $pos)
1244
    {
1245
        // We don't know about any additional switches; just files.
1246
        if ($arg{0} === '-') {
1247
            if ($this->dieOnUnknownArg === false) {
1248
                return;
1249
            }
1250
1251
            $error  = "ERROR: option \"$arg\" not known".PHP_EOL.PHP_EOL;
1252
            $error .= $this->printShortUsage(true);
1253
            throw new DeepExitException($error, 3);
1254
        }
1255
1256
        $this->processFilePath($arg);
1257
1258
    }//end processUnknownArgument()
1259
1260
1261
    /**
1262
     * Processes a file path and add it to the file list.
1263
     *
1264
     * @param string $path The path to the file to add.
1265
     *
1266
     * @return void
1267
     */
1268
    public function processFilePath($path)
1269
    {
1270
        // If we are processing STDIN, don't record any files to check.
1271
        if ($this->stdin === true) {
1272
            return;
1273
        }
1274
1275
        $file = Util\Common::realpath($path);
1276
        if (file_exists($file) === false) {
1277
            if ($this->dieOnUnknownArg === false) {
1278
                return;
1279
            }
1280
1281
            $error  = 'ERROR: The file "'.$path.'" does not exist.'.PHP_EOL.PHP_EOL;
1282
            $error .= $this->printShortUsage(true);
1283
            throw new DeepExitException($error, 3);
1284
        } else {
1285
            $files       = $this->files;
1286
            $files[]     = $file;
1287
            $this->files = $files;
1288
            $this->overriddenDefaults['files'] = true;
1289
        }
1290
1291
    }//end processFilePath()
1292
1293
1294
    /**
1295
     * Prints out the usage information for this script.
1296
     *
1297
     * @return void
1298
     */
1299
    public function printUsage()
1300
    {
1301
        echo PHP_EOL;
1302
1303
        if (PHP_CODESNIFFER_CBF === true) {
1304
            $this->printPHPCBFUsage();
1305
        } else {
1306
            $this->printPHPCSUsage();
1307
        }
1308
1309
        echo PHP_EOL;
1310
1311
    }//end printUsage()
1312
1313
1314
    /**
1315
     * Prints out the short usage information for this script.
1316
     *
1317
     * @param bool $return If TRUE, the usage string is returned
1318
     *                     instead of output to screen.
1319
     *
1320
     * @return string|void
1321
     */
1322
    public function printShortUsage($return=false)
1323
    {
1324
        if (PHP_CODESNIFFER_CBF === true) {
1325
            $usage = 'Run "phpcbf --help" for usage information';
1326
        } else {
1327
            $usage = 'Run "phpcs --help" for usage information';
1328
        }
1329
1330
        $usage .= PHP_EOL.PHP_EOL;
1331
1332
        if ($return === true) {
1333
            return $usage;
1334
        }
1335
1336
        echo $usage;
1337
1338
    }//end printShortUsage()
1339
1340
1341
    /**
1342
     * Prints out the usage information for PHPCS.
1343
     *
1344
     * @return void
1345
     */
1346
    public function printPHPCSUsage()
1347
    {
1348
        echo 'Usage: phpcs [-nwlsaepqvi] [-d key[=value]] [--colors] [--no-colors]'.PHP_EOL;
1349
        echo '  [--cache[=<cacheFile>]] [--no-cache] [--tab-width=<tabWidth>]'.PHP_EOL;
1350
        echo '  [--report=<report>] [--report-file=<reportFile>] [--report-<report>=<reportFile>]'.PHP_EOL;
1351
        echo '  [--report-width=<reportWidth>] [--basepath=<basepath>] [--bootstrap=<bootstrap>]'.PHP_EOL;
1352
        echo '  [--severity=<severity>] [--error-severity=<severity>] [--warning-severity=<severity>]'.PHP_EOL;
1353
        echo '  [--runtime-set key value] [--config-set key value] [--config-delete key] [--config-show]'.PHP_EOL;
1354
        echo '  [--standard=<standard>] [--sniffs=<sniffs>] [--exclude=<sniffs>]'.PHP_EOL;
1355
        echo '  [--encoding=<encoding>] [--parallel=<processes>] [--generator=<generator>]'.PHP_EOL;
1356
        echo '  [--extensions=<extensions>] [--ignore=<patterns>] [--ignore-annotations]'.PHP_EOL;
1357
        echo '  [--stdin-path=<stdinPath>] [--file-list=<fileList>] <file> - ...'.PHP_EOL;
1358
        echo PHP_EOL;
1359
        echo ' -     Check STDIN instead of local files and directories'.PHP_EOL;
1360
        echo ' -n    Do not print warnings (shortcut for --warning-severity=0)'.PHP_EOL;
1361
        echo ' -w    Print both warnings and errors (this is the default)'.PHP_EOL;
1362
        echo ' -l    Local directory only, no recursion'.PHP_EOL;
1363
        echo ' -s    Show sniff codes in all reports'.PHP_EOL;
1364
        echo ' -a    Run interactively'.PHP_EOL;
1365
        echo ' -e    Explain a standard by showing the sniffs it includes'.PHP_EOL;
1366
        echo ' -p    Show progress of the run'.PHP_EOL;
1367
        echo ' -q    Quiet mode; disables progress and verbose output'.PHP_EOL;
1368
        echo ' -m    Stop error messages from being recorded'.PHP_EOL;
1369
        echo '       (saves a lot of memory, but stops many reports from being used)'.PHP_EOL;
1370
        echo ' -v    Print processed files'.PHP_EOL;
1371
        echo ' -vv   Print ruleset and token output'.PHP_EOL;
1372
        echo ' -vvv  Print sniff processing information'.PHP_EOL;
1373
        echo ' -i    Show a list of installed coding standards'.PHP_EOL;
1374
        echo ' -d    Set the [key] php.ini value to [value] or [true] if value is omitted'.PHP_EOL;
1375
        echo PHP_EOL;
1376
        echo ' --help                Print this help message'.PHP_EOL;
1377
        echo ' --version             Print version information'.PHP_EOL;
1378
        echo ' --colors              Use colors in output'.PHP_EOL;
1379
        echo ' --no-colors           Do not use colors in output (this is the default)'.PHP_EOL;
1380
        echo ' --cache               Cache results between runs'.PHP_EOL;
1381
        echo ' --no-cache            Do not cache results between runs (this is the default)'.PHP_EOL;
1382
        echo ' --ignore-annotations  Ignore all @codingStandard annotations in code comments'.PHP_EOL;
1383
        echo PHP_EOL;
1384
        echo ' <cacheFile>    Use a specific file for caching (uses a temporary file by default)'.PHP_EOL;
1385
        echo ' <basepath>     A path to strip from the front of file paths inside reports'.PHP_EOL;
1386
        echo ' <bootstrap>    A comma separated list of files to run before processing begins'.PHP_EOL;
1387
        echo ' <file>         One or more files and/or directories to check'.PHP_EOL;
1388
        echo ' <fileList>     A file containing a list of files and/or directories to check (one per line)'.PHP_EOL;
1389
        echo ' <encoding>     The encoding of the files being checked (default is utf-8)'.PHP_EOL;
1390
        echo ' <extensions>   A comma separated list of file extensions to check'.PHP_EOL;
1391
        echo '                The type of the file can be specified using: ext/type'.PHP_EOL;
1392
        echo '                e.g., module/php,es/js'.PHP_EOL;
1393
        echo ' <generator>    Uses either the "HTML", "Markdown" or "Text" generator'.PHP_EOL;
1394
        echo '                (forces documentation generation instead of checking)'.PHP_EOL;
1395
        echo ' <patterns>     A comma separated list of patterns to ignore files and directories'.PHP_EOL;
1396
        echo ' <processes>    How many files should be checked simultaneously (default is 1)'.PHP_EOL;
1397
        echo ' <report>       Print either the "full", "xml", "checkstyle", "csv"'.PHP_EOL;
1398
        echo '                "json", "junit", "emacs", "source", "summary", "diff"'.PHP_EOL;
1399
        echo '                "svnblame", "gitblame", "hgblame" or "notifysend" report'.PHP_EOL;
1400
        echo '                (the "full" report is printed by default)'.PHP_EOL;
1401
        echo ' <reportFile>   Write the report to the specified file path'.PHP_EOL;
1402
        echo ' <reportWidth>  How many columns wide screen reports should be printed'.PHP_EOL;
1403
        echo '                or set to "auto" to use current screen width, where supported'.PHP_EOL;
1404
        echo ' <severity>     The minimum severity required to display an error or warning'.PHP_EOL;
1405
        echo ' <sniffs>       A comma separated list of sniff codes to include or exclude from checking'.PHP_EOL;
1406
        echo '                (all sniffs must be part of the specified standard)'.PHP_EOL;
1407
        echo ' <standard>     The name or path of the coding standard to use'.PHP_EOL;
1408
        echo ' <stdinPath>    If processing STDIN, the file path that STDIN will be processed as'.PHP_EOL;
1409
        echo ' <tabWidth>     The number of spaces each tab represents'.PHP_EOL;
1410
1411
    }//end printPHPCSUsage()
1412
1413
1414
    /**
1415
     * Prints out the usage information for PHPCBF.
1416
     *
1417
     * @return void
1418
     */
1419
    public function printPHPCBFUsage()
1420
    {
1421
        echo 'Usage: phpcbf [-nwli] [-d key[=value]] [--ignore-annotations] [--bootstrap=<bootstrap>]'.PHP_EOL;
1422
        echo '  [--standard=<standard>] [--sniffs=<sniffs>] [--exclude=<sniffs>] [--suffix=<suffix>]'.PHP_EOL;
1423
        echo '  [--severity=<severity>] [--error-severity=<severity>] [--warning-severity=<severity>]'.PHP_EOL;
1424
        echo '  [--tab-width=<tabWidth>] [--encoding=<encoding>] [--parallel=<processes>]'.PHP_EOL;
1425
        echo '  [--basepath=<basepath>] [--extensions=<extensions>] [--ignore=<patterns>]'.PHP_EOL;
1426
        echo '  [--stdin-path=<stdinPath>] [--file-list=<fileList>] <file> - ...'.PHP_EOL;
1427
        echo PHP_EOL;
1428
        echo ' -     Fix STDIN instead of local files and directories'.PHP_EOL;
1429
        echo ' -n    Do not fix warnings (shortcut for --warning-severity=0)'.PHP_EOL;
1430
        echo ' -w    Fix both warnings and errors (on by default)'.PHP_EOL;
1431
        echo ' -l    Local directory only, no recursion'.PHP_EOL;
1432
        echo ' -p    Show progress of the run'.PHP_EOL;
1433
        echo ' -q    Quiet mode; disables progress and verbose output'.PHP_EOL;
1434
        echo ' -v    Print processed files'.PHP_EOL;
1435
        echo ' -vv   Print ruleset and token output'.PHP_EOL;
1436
        echo ' -vvv  Print sniff processing information'.PHP_EOL;
1437
        echo ' -i    Show a list of installed coding standards'.PHP_EOL;
1438
        echo ' -d    Set the [key] php.ini value to [value] or [true] if value is omitted'.PHP_EOL;
1439
        echo PHP_EOL;
1440
        echo ' --help                Print this help message'.PHP_EOL;
1441
        echo ' --version             Print version information'.PHP_EOL;
1442
        echo ' --ignore-annotations  Ignore all @codingStandard annotations in code comments'.PHP_EOL;
1443
        echo PHP_EOL;
1444
        echo ' <basepath>    A path to strip from the front of file paths inside reports'.PHP_EOL;
1445
        echo ' <bootstrap>   A comma separated list of files to run before processing begins'.PHP_EOL;
1446
        echo ' <file>        One or more files and/or directories to fix'.PHP_EOL;
1447
        echo ' <fileList>    A file containing a list of files and/or directories to fix (one per line)'.PHP_EOL;
1448
        echo ' <encoding>    The encoding of the files being fixed (default is utf-8)'.PHP_EOL;
1449
        echo ' <extensions>  A comma separated list of file extensions to fix'.PHP_EOL;
1450
        echo '               The type of the file can be specified using: ext/type'.PHP_EOL;
1451
        echo '               e.g., module/php,es/js'.PHP_EOL;
1452
        echo ' <patterns>    A comma separated list of patterns to ignore files and directories'.PHP_EOL;
1453
        echo ' <processes>   How many files should be fixed simultaneously (default is 1)'.PHP_EOL;
1454
        echo ' <severity>    The minimum severity required to fix an error or warning'.PHP_EOL;
1455
        echo ' <sniffs>      A comma separated list of sniff codes to include or exclude from fixing'.PHP_EOL;
1456
        echo '               (all sniffs must be part of the specified standard)'.PHP_EOL;
1457
        echo ' <standard>    The name or path of the coding standard to use'.PHP_EOL;
1458
        echo ' <stdinPath>   If processing STDIN, the file path that STDIN will be processed as'.PHP_EOL;
1459
        echo ' <suffix>      Write modified files to a filename using this suffix'.PHP_EOL;
1460
        echo '               ("diff" and "patch" are not used in this mode)'.PHP_EOL;
1461
        echo ' <tabWidth>    The number of spaces each tab represents'.PHP_EOL;
1462
1463
    }//end printPHPCBFUsage()
1464
1465
1466
    /**
1467
     * Get a single config value.
1468
     *
1469
     * @param string $key The name of the config value.
1470
     *
1471
     * @return string|null
1472
     * @see    setConfigData()
1473
     * @see    getAllConfigData()
1474
     */
1475
    public static function getConfigData($key)
1476
    {
1477
        $phpCodeSnifferConfig = self::getAllConfigData();
1478
1479
        if ($phpCodeSnifferConfig === null) {
1480
            return null;
1481
        }
1482
1483
        if (isset($phpCodeSnifferConfig[$key]) === false) {
1484
            return null;
1485
        }
1486
1487
        return $phpCodeSnifferConfig[$key];
1488
1489
    }//end getConfigData()
1490
1491
1492
    /**
1493
     * Get the path to an executable utility.
1494
     *
1495
     * @param string $name The name of the executable utility.
1496
     *
1497
     * @return string|null
1498
     * @see    getConfigData()
1499
     */
1500
    public static function getExecutablePath($name)
1501
    {
1502
        $data = self::getConfigData($name.'_path');
1503
        if ($data !== null) {
1504
            return $data;
1505
        }
1506
1507
        if (array_key_exists($name, self::$executablePaths) === true) {
1508
            return self::$executablePaths[$name];
1509
        }
1510
1511
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
1512
            $cmd = 'where '.escapeshellarg($name).' 2> nul';
1513
        } else {
1514
            $cmd = 'which '.escapeshellarg($name).' 2> /dev/null';
1515
        }
1516
1517
        $result = exec($cmd, $output, $retVal);
1518
        if ($retVal !== 0) {
1519
            $result = null;
1520
        }
1521
1522
        self::$executablePaths[$name] = $result;
1523
        return $result;
1524
1525
    }//end getExecutablePath()
1526
1527
1528
    /**
1529
     * Set a single config value.
1530
     *
1531
     * @param string      $key   The name of the config value.
1532
     * @param string|null $value The value to set. If null, the config
1533
     *                           entry is deleted, reverting it to the
1534
     *                           default value.
1535
     * @param boolean     $temp  Set this config data temporarily for this
1536
     *                           script run. This will not write the config
1537
     *                           data to the config file.
1538
     *
1539
     * @return bool
1540
     * @see    getConfigData()
1541
     * @throws RuntimeException If the config file can not be written.
1542
     */
1543
    public static function setConfigData($key, $value, $temp=false)
1544
    {
1545
        if ($temp === false) {
1546
            $path = '';
1547
            if (is_callable('\Phar::running') === true) {
1548
                $path = \Phar::running(false);
1549
            }
1550
1551
            if ($path !== '') {
1552
                $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
1553
            } else {
1554
                $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
1555
                if (is_file($configFile) === false
1556
                    && strpos('@data_dir@', '@data_dir') === false
1557
                ) {
1558
                    // If data_dir was replaced, this is a PEAR install and we can
1559
                    // use the PEAR data dir to store the conf file.
1560
                    $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf';
1561
                }
1562
            }
1563
1564
            if (is_file($configFile) === true
1565
                && is_writable($configFile) === false
1566
            ) {
1567
                $error = 'ERROR: Config file '.$configFile.' is not writable'.PHP_EOL.PHP_EOL;
1568
                throw new DeepExitException($error, 3);
1569
            }
1570
        }//end if
1571
1572
        $phpCodeSnifferConfig = self::getAllConfigData();
1573
1574
        if ($value === null) {
1575
            if (isset($phpCodeSnifferConfig[$key]) === true) {
1576
                unset($phpCodeSnifferConfig[$key]);
1577
            }
1578
        } else {
1579
            $phpCodeSnifferConfig[$key] = $value;
1580
        }
1581
1582
        if ($temp === false) {
1583
            $output  = '<'.'?php'."\n".' $phpCodeSnifferConfig = ';
1584
            $output .= var_export($phpCodeSnifferConfig, true);
1585
            $output .= "\n?".'>';
1586
1587
            if (file_put_contents($configFile, $output) === false) {
1588
                $error = 'ERROR: Config file '.$configFile.' could not be written'.PHP_EOL.PHP_EOL;
1589
                throw new DeepExitException($error, 3);
1590
            }
1591
1592
            self::$configDataFile = $configFile;
1593
        }
1594
1595
        self::$configData = $phpCodeSnifferConfig;
1596
1597
        // If the installed paths are being set, make sure all known
1598
        // standards paths are added to the autoloader.
1599
        if ($key === 'installed_paths') {
1600
            $installedStandards = Util\Standards::getInstalledStandardDetails();
1601
            foreach ($installedStandards as $name => $details) {
1602
                Autoload::addSearchPath($details['path'], $details['namespace']);
1603
            }
1604
        }
1605
1606
        return true;
1607
1608
    }//end setConfigData()
1609
1610
1611
    /**
1612
     * Get all config data.
1613
     *
1614
     * @return array<string, string>
1615
     * @see    getConfigData()
1616
     */
1617
    public static function getAllConfigData()
1618
    {
1619
        if (self::$configData !== null) {
1620
            return self::$configData;
1621
        }
1622
1623
        $path = '';
1624
        if (is_callable('\Phar::running') === true) {
1625
            $path = \Phar::running(false);
1626
        }
1627
1628
        if ($path !== '') {
1629
            $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
1630
        } else {
1631
            $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf';
1632
            if (is_file($configFile) === false
1633
                && strpos('@data_dir@', '@data_dir') === false
1634
            ) {
1635
                $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf';
1636
            }
1637
        }
1638
1639
        if (is_file($configFile) === false) {
1640
            self::$configData = [];
1641
            return [];
1642
        }
1643
1644
        if (is_readable($configFile) === false) {
1645
            $error = 'ERROR: Config file '.$configFile.' is not readable'.PHP_EOL.PHP_EOL;
1646
            throw new DeepExitException($error, 3);
1647
        }
1648
1649
        include $configFile;
1650
        self::$configDataFile = $configFile;
1651
        self::$configData     = $phpCodeSnifferConfig;
1652
        return self::$configData;
1653
1654
    }//end getAllConfigData()
1655
1656
1657
    /**
1658
     * Prints out the gathered config data.
1659
     *
1660
     * @param array $data The config data to print.
1661
     *
1662
     * @return void
1663
     */
1664
    public function printConfigData($data)
1665
    {
1666
        $max  = 0;
1667
        $keys = array_keys($data);
1668
        foreach ($keys as $key) {
1669
            $len = strlen($key);
1670
            if (strlen($key) > $max) {
1671
                $max = $len;
1672
            }
1673
        }
1674
1675
        if ($max === 0) {
1676
            return;
1677
        }
1678
1679
        $max += 2;
1680
        ksort($data);
1681
        foreach ($data as $name => $value) {
1682
            echo str_pad($name.': ', $max).$value.PHP_EOL;
1683
        }
1684
1685
    }//end printConfigData()
1686
1687
1688
}//end class
1689