1<?php
2/**
3 * A class to process command line phpcs scripts.
4 *
5 * PHP version 5
6 *
7 * @category  PHP
8 * @package   PHP_CodeSniffer
9 * @author    Greg Sherwood <gsherwood@squiz.net>
10 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
11 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
12 * @link      http://pear.php.net/package/PHP_CodeSniffer
13 */
14
15error_reporting(E_ALL | E_STRICT);
16
17// Make sure version id constant is available.
18if (defined('PHP_VERSION_ID') === false) {
19    $version = explode('.', PHP_VERSION);
20    define('PHP_VERSION_ID', (int) (($version[0] * 10000) + ($version[1] * 100) + $version[2]));
21    unset($version);
22}
23
24// Make sure that we autoload all dependencies if running via Composer.
25if (PHP_VERSION_ID >= 50302) {
26    if (file_exists($a = dirname(__FILE__).'/../../../autoload.php') === true) {
27        include_once $a;
28    } else if (file_exists($a = dirname(__FILE__).'/../vendor/autoload.php') === true) {
29        include_once $a;
30    }
31}
32
33if (file_exists($a = dirname(__FILE__).'/../CodeSniffer.php') === true) {
34    // Running from a git clone.
35    include_once $a;
36} else {
37    // PEAR installed.
38    include_once 'PHP/CodeSniffer.php';
39}
40
41/**
42 * A class to process command line phpcs scripts.
43 *
44 * @category  PHP
45 * @package   PHP_CodeSniffer
46 * @author    Greg Sherwood <gsherwood@squiz.net>
47 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
48 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
49 * @version   Release: @package_version@
50 * @link      http://pear.php.net/package/PHP_CodeSniffer
51 */
52class PHP_CodeSniffer_CLI
53{
54
55    /**
56     * An array of all values specified on the command line.
57     *
58     * @var array
59     */
60    protected $values = array();
61
62    /**
63     * The minimum severity level errors must have to be displayed.
64     *
65     * @var bool
66     */
67    public $errorSeverity = 0;
68
69    /**
70     * The minimum severity level warnings must have to be displayed.
71     *
72     * @var bool
73     */
74    public $warningSeverity = 0;
75
76    /**
77     * Whether or not to kill the process when an unknown command line arg is found.
78     *
79     * If FALSE, arguments that are not command line options or file/directory paths
80     * will be ignored and execution will continue.
81     *
82     * @var bool
83     */
84    public $dieOnUnknownArg = true;
85
86    /**
87     * An array of the current command line arguments we are processing.
88     *
89     * @var array
90     */
91    private $_cliArgs = array();
92
93
94    /**
95     * Run the PHPCS script.
96     *
97     * @return array
98     */
99    public function runphpcs()
100    {
101        if (defined('PHP_CODESNIFFER_CBF') === false) {
102            define('PHP_CODESNIFFER_CBF', false);
103        }
104
105        if (is_file(dirname(__FILE__).'/../CodeSniffer/Reporting.php') === true) {
106            include_once dirname(__FILE__).'/../CodeSniffer/Reporting.php';
107        } else {
108            include_once 'PHP/CodeSniffer/Reporting.php';
109        }
110
111        PHP_CodeSniffer_Reporting::startTiming();
112        $this->checkRequirements();
113        $numErrors = $this->process();
114        if ($numErrors === 0) {
115            exit(0);
116        } else {
117            exit(1);
118        }
119
120    }//end runphpcs()
121
122
123    /**
124     * Run the PHPCBF script.
125     *
126     * @return array
127     */
128    public function runphpcbf()
129    {
130        if (defined('PHP_CODESNIFFER_CBF') === false) {
131            define('PHP_CODESNIFFER_CBF', true);
132        }
133
134        if (is_file(dirname(__FILE__).'/../CodeSniffer/Reporting.php') === true) {
135            include_once dirname(__FILE__).'/../CodeSniffer/Reporting.php';
136        } else {
137            include_once 'PHP/CodeSniffer/Reporting.php';
138        }
139
140        PHP_CodeSniffer_Reporting::startTiming();
141        $this->checkRequirements();
142
143        $this->dieOnUnknownArg = false;
144
145        // Override some of the command line settings that might break the fixes.
146        $cliValues = $this->getCommandLineValues();
147        $cliValues['verbosity']    = 0;
148        $cliValues['showProgress'] = false;
149        $cliValues['generator']    = '';
150        $cliValues['explain']      = false;
151        $cliValues['interactive']  = false;
152        $cliValues['showSources']  = false;
153        $cliValues['reportFile']   = null;
154        $cliValues['reports']      = array();
155
156        $suffix = '';
157        if (isset($cliValues['suffix']) === true) {
158            $suffix = $cliValues['suffix'];
159        }
160
161        $allowPatch = true;
162        if (isset($cliValues['no-patch']) === true || empty($cliValues['files']) === true) {
163            // They either asked for this,
164            // or they are using STDIN, which can't use diff.
165            $allowPatch = false;
166        }
167
168        if ($suffix === '' && $allowPatch === true) {
169            // Using the diff/patch tools.
170            $diffFile = getcwd().'/phpcbf-fixed.diff';
171            $cliValues['reports'] = array('diff' => $diffFile);
172            if (file_exists($diffFile) === true) {
173                unlink($diffFile);
174            }
175        } else {
176            // Replace the file without the patch command
177            // or writing to a file with a new suffix.
178            $cliValues['reports']       = array('cbf' => null);
179            $cliValues['phpcbf-suffix'] = $suffix;
180        }
181
182        $numErrors = $this->process($cliValues);
183
184        if ($suffix === '' && $allowPatch === true) {
185            if (file_exists($diffFile) === false) {
186                // Nothing to fix.
187                if ($numErrors === 0) {
188                    // And no errors reported.
189                    $exit = 0;
190                } else {
191                    // Errors we can't fix.
192                    $exit = 2;
193                }
194            } else {
195                if (filesize($diffFile) < 10) {
196                    // Empty or bad diff file.
197                    if ($numErrors === 0) {
198                        // And no errors reported.
199                        $exit = 0;
200                    } else {
201                        // Errors we can't fix.
202                        $exit = 2;
203                    }
204                } else {
205                    $cmd    = "patch -p0 -ui \"$diffFile\"";
206                    $output = array();
207                    $retVal = null;
208                    exec($cmd, $output, $retVal);
209
210                    if ($retVal === 0) {
211                        // Everything went well.
212                        $filesPatched = count($output);
213                        echo "Patched $filesPatched file";
214                        if ($filesPatched > 1) {
215                            echo 's';
216                        }
217
218                        echo PHP_EOL;
219                        $exit = 1;
220                    } else {
221                        print_r($output);
222                        echo "Returned: $retVal".PHP_EOL;
223                        $exit = 3;
224                    }
225                }//end if
226
227                unlink($diffFile);
228            }//end if
229        } else {
230            // File are being patched manually, so we can't tell
231            // how many errors were fixed.
232            $exit = 1;
233        }//end if
234
235        if ($exit === 0) {
236            echo 'No fixable errors were found'.PHP_EOL;
237        } else if ($exit === 2) {
238            echo 'PHPCBF could not fix all the errors found'.PHP_EOL;
239        }
240
241        PHP_CodeSniffer_Reporting::printRunTime();
242        exit($exit);
243
244    }//end runphpcbf()
245
246
247    /**
248     * Exits if the minimum requirements of PHP_CodSniffer are not met.
249     *
250     * @return array
251     */
252    public function checkRequirements()
253    {
254        // Check the PHP version.
255        if (PHP_VERSION_ID < 50102) {
256            echo 'ERROR: PHP_CodeSniffer requires PHP version 5.1.2 or greater.'.PHP_EOL;
257            exit(2);
258        }
259
260        if (extension_loaded('tokenizer') === false) {
261            echo 'ERROR: PHP_CodeSniffer requires the tokenizer extension to be enabled.'.PHP_EOL;
262            exit(2);
263        }
264
265    }//end checkRequirements()
266
267
268    /**
269     * Get a list of default values for all possible command line arguments.
270     *
271     * @return array
272     */
273    public function getDefaults()
274    {
275        if (defined('PHP_CODESNIFFER_IN_TESTS') === true) {
276            return array();
277        }
278
279        // The default values for config settings.
280        $defaults['files']           = array();
281        $defaults['standard']        = null;
282        $defaults['verbosity']       = 0;
283        $defaults['interactive']     = false;
284        $defaults['colors']          = false;
285        $defaults['explain']         = false;
286        $defaults['local']           = false;
287        $defaults['showSources']     = false;
288        $defaults['extensions']      = array();
289        $defaults['sniffs']          = array();
290        $defaults['exclude']         = array();
291        $defaults['ignored']         = array();
292        $defaults['reportFile']      = null;
293        $defaults['generator']       = '';
294        $defaults['reports']         = array();
295        $defaults['bootstrap']       = array();
296        $defaults['errorSeverity']   = null;
297        $defaults['warningSeverity'] = null;
298        $defaults['stdin']           = null;
299        $defaults['stdinPath']       = '';
300
301        $reportFormat = PHP_CodeSniffer::getConfigData('report_format');
302        if ($reportFormat !== null) {
303            $defaults['reports'][$reportFormat] = null;
304        }
305
306        $tabWidth = PHP_CodeSniffer::getConfigData('tab_width');
307        if ($tabWidth === null) {
308            $defaults['tabWidth'] = 0;
309        } else {
310            $defaults['tabWidth'] = (int) $tabWidth;
311        }
312
313        $encoding = PHP_CodeSniffer::getConfigData('encoding');
314        if ($encoding === null) {
315            $defaults['encoding'] = 'iso-8859-1';
316        } else {
317            $defaults['encoding'] = strtolower($encoding);
318        }
319
320        $severity = PHP_CodeSniffer::getConfigData('severity');
321        if ($severity !== null) {
322            $defaults['errorSeverity']   = (int) $severity;
323            $defaults['warningSeverity'] = (int) $severity;
324        }
325
326        $severity = PHP_CodeSniffer::getConfigData('error_severity');
327        if ($severity !== null) {
328            $defaults['errorSeverity'] = (int) $severity;
329        }
330
331        $severity = PHP_CodeSniffer::getConfigData('warning_severity');
332        if ($severity !== null) {
333            $defaults['warningSeverity'] = (int) $severity;
334        }
335
336        $showWarnings = PHP_CodeSniffer::getConfigData('show_warnings');
337        if ($showWarnings !== null) {
338            $showWarnings = (bool) $showWarnings;
339            if ($showWarnings === false) {
340                $defaults['warningSeverity'] = 0;
341            }
342        }
343
344        $reportWidth = PHP_CodeSniffer::getConfigData('report_width');
345        if ($reportWidth !== null) {
346            $defaults['reportWidth'] = $this->_validateReportWidth($reportWidth);
347        } else {
348            // Use function defaults.
349            $defaults['reportWidth'] = null;
350        }
351
352        $showProgress = PHP_CodeSniffer::getConfigData('show_progress');
353        if ($showProgress === null) {
354            $defaults['showProgress'] = false;
355        } else {
356            $defaults['showProgress'] = (bool) $showProgress;
357        }
358
359        $quiet = PHP_CodeSniffer::getConfigData('quiet');
360        if ($quiet === null) {
361            $defaults['quiet'] = false;
362        } else {
363            $defaults['quiet'] = (bool) $quiet;
364        }
365
366        $colors = PHP_CodeSniffer::getConfigData('colors');
367        if ($colors === null) {
368            $defaults['colors'] = false;
369        } else {
370            $defaults['colors'] = (bool) $colors;
371        }
372
373        if (PHP_CodeSniffer::isPharFile(dirname(dirname(__FILE__))) === true) {
374            // If this is a phar file, check for the standard in the config.
375            $standard = PHP_CodeSniffer::getConfigData('standard');
376            if ($standard !== null) {
377                $defaults['standard'] = $standard;
378            }
379        }
380
381        return $defaults;
382
383    }//end getDefaults()
384
385
386    /**
387     * Gets the processed command line values.
388     *
389     * If the values have not yet been set, the values will be sourced
390     * from the command line arguments.
391     *
392     * @return array
393     */
394    public function getCommandLineValues()
395    {
396        if (empty($this->values) === false) {
397            return $this->values;
398        }
399
400        $args = $_SERVER['argv'];
401        array_shift($args);
402
403        $this->setCommandLineValues($args);
404
405        // Check for content on STDIN.
406        $handle = fopen('php://stdin', 'r');
407        if (stream_set_blocking($handle, false) === true) {
408            $fileContents = '';
409            while (($line = fgets($handle)) !== false) {
410                $fileContents .= $line;
411                usleep(10);
412            }
413
414            stream_set_blocking($handle, true);
415            fclose($handle);
416            if (trim($fileContents) !== '') {
417                $this->values['stdin'] = $fileContents;
418            }
419        }
420
421        return $this->values;
422
423    }//end getCommandLineValues()
424
425
426    /**
427     * Set the command line values.
428     *
429     * @param array $args An array of command line arguments to process.
430     *
431     * @return void
432     */
433    public function setCommandLineValues($args)
434    {
435        if (defined('PHP_CODESNIFFER_IN_TESTS') === true) {
436            $this->values = array(
437                             'stdin' => null,
438                             'quiet' => true,
439                            );
440        } else if (empty($this->values) === true) {
441            $this->values = $this->getDefaults();
442        }
443
444        $this->_cliArgs = $args;
445        $numArgs        = count($args);
446
447        for ($i = 0; $i < $numArgs; $i++) {
448            $arg = $this->_cliArgs[$i];
449            if ($arg === '') {
450                continue;
451            }
452
453            if ($arg{0} === '-') {
454                if ($arg === '-' || $arg === '--') {
455                    // Empty argument, ignore it.
456                    continue;
457                }
458
459                if ($arg{1} === '-') {
460                    $this->processLongArgument(substr($arg, 2), $i);
461                } else {
462                    $switches = str_split($arg);
463                    foreach ($switches as $switch) {
464                        if ($switch === '-') {
465                            continue;
466                        }
467
468                        $this->processShortArgument($switch, $i);
469                    }
470                }
471            } else {
472                $this->processUnknownArgument($arg, $i);
473            }//end if
474        }//end for
475
476    }//end setCommandLineValues()
477
478
479    /**
480     * Processes a short (-e) command line argument.
481     *
482     * @param string $arg The command line argument.
483     * @param int    $pos The position of the argument on the command line.
484     *
485     * @return void
486     */
487    public function processShortArgument($arg, $pos)
488    {
489        switch ($arg) {
490        case 'h':
491        case '?':
492            $this->printUsage();
493            exit(0);
494        case 'i' :
495            $this->printInstalledStandards();
496            exit(0);
497        case 'v' :
498            if ($this->values['quiet'] === true) {
499                // Ignore when quiet mode is enabled.
500                break;
501            }
502
503            if (isset($this->values['verbosity']) === false) {
504                $this->values['verbosity'] = 1;
505            } else {
506                $this->values['verbosity']++;
507            }
508            break;
509        case 'l' :
510            $this->values['local'] = true;
511            break;
512        case 's' :
513            $this->values['showSources'] = true;
514            break;
515        case 'a' :
516            $this->values['interactive'] = true;
517            break;
518        case 'e':
519            $this->values['explain'] = true;
520            break;
521        case 'p' :
522            if ($this->values['quiet'] === true) {
523                // Ignore when quiet mode is enabled.
524                break;
525            }
526
527            $this->values['showProgress'] = true;
528            break;
529        case 'q' :
530            // Quiet mode disables a few other settings as well.
531            $this->values['quiet']        = true;
532            $this->values['showProgress'] = false;
533            $this->values['verbosity']    = 0;
534            break;
535        case 'd' :
536            $ini = explode('=', $this->_cliArgs[($pos + 1)]);
537            $this->_cliArgs[($pos + 1)] = '';
538            if (isset($ini[1]) === true) {
539                ini_set($ini[0], $ini[1]);
540            } else {
541                ini_set($ini[0], true);
542            }
543            break;
544        case 'n' :
545            $this->values['warningSeverity'] = 0;
546            break;
547        case 'w' :
548            $this->values['warningSeverity'] = null;
549            break;
550        default:
551            if ($this->dieOnUnknownArg === false) {
552                $this->values[$arg] = $arg;
553            } else {
554                $this->processUnknownArgument('-'.$arg, $pos);
555            }
556        }//end switch
557
558    }//end processShortArgument()
559
560
561    /**
562     * Processes a long (--example) command line argument.
563     *
564     * @param string $arg The command line argument.
565     * @param int    $pos The position of the argument on the command line.
566     *
567     * @return void
568     */
569    public function processLongArgument($arg, $pos)
570    {
571        switch ($arg) {
572        case 'help':
573            $this->printUsage();
574            exit(0);
575        case 'version':
576            echo 'PHP_CodeSniffer version '.PHP_CodeSniffer::VERSION.' ('.PHP_CodeSniffer::STABILITY.') ';
577            echo 'by Squiz (http://www.squiz.net)'.PHP_EOL;
578            exit(0);
579        case 'colors':
580            $this->values['colors'] = true;
581            break;
582        case 'no-colors':
583            $this->values['colors'] = false;
584            break;
585        case 'config-set':
586            if (isset($this->_cliArgs[($pos + 1)]) === false
587                || isset($this->_cliArgs[($pos + 2)]) === false
588            ) {
589                echo 'ERROR: Setting a config option requires a name and value'.PHP_EOL.PHP_EOL;
590                $this->printUsage();
591                exit(0);
592            }
593
594            $key     = $this->_cliArgs[($pos + 1)];
595            $value   = $this->_cliArgs[($pos + 2)];
596            $current = PHP_CodeSniffer::getConfigData($key);
597
598            try {
599                PHP_CodeSniffer::setConfigData($key, $value);
600            } catch (Exception $e) {
601                echo $e->getMessage().PHP_EOL;
602                exit(2);
603            }
604
605            if ($current === null) {
606                echo "Config value \"$key\" added successfully".PHP_EOL;
607            } else {
608                echo "Config value \"$key\" updated successfully; old value was \"$current\"".PHP_EOL;
609            }
610            exit(0);
611        case 'config-delete':
612            if (isset($this->_cliArgs[($pos + 1)]) === false) {
613                echo 'ERROR: Deleting a config option requires the name of the option'.PHP_EOL.PHP_EOL;
614                $this->printUsage();
615                exit(0);
616            }
617
618            $key     = $this->_cliArgs[($pos + 1)];
619            $current = PHP_CodeSniffer::getConfigData($key);
620            if ($current === null) {
621                echo "Config value \"$key\" has not been set".PHP_EOL;
622            } else {
623                try {
624                    PHP_CodeSniffer::setConfigData($key, null);
625                } catch (Exception $e) {
626                    echo $e->getMessage().PHP_EOL;
627                    exit(2);
628                }
629
630                echo "Config value \"$key\" removed successfully; old value was \"$current\"".PHP_EOL;
631            }
632            exit(0);
633        case 'config-show':
634            $data = PHP_CodeSniffer::getAllConfigData();
635            $this->printConfigData($data);
636            exit(0);
637        case 'runtime-set':
638            if (isset($this->_cliArgs[($pos + 1)]) === false
639                || isset($this->_cliArgs[($pos + 2)]) === false
640            ) {
641                echo 'ERROR: Setting a runtime config option requires a name and value'.PHP_EOL.PHP_EOL;
642                $this->printUsage();
643                exit(0);
644            }
645
646            $key   = $this->_cliArgs[($pos + 1)];
647            $value = $this->_cliArgs[($pos + 2)];
648            $this->_cliArgs[($pos + 1)] = '';
649            $this->_cliArgs[($pos + 2)] = '';
650            PHP_CodeSniffer::setConfigData($key, $value, true);
651            break;
652        default:
653            if (substr($arg, 0, 7) === 'sniffs=') {
654                $sniffs = explode(',', substr($arg, 7));
655                foreach ($sniffs as $sniff) {
656                    if (substr_count($sniff, '.') !== 2) {
657                        echo 'ERROR: The specified sniff code "'.$sniff.'" is invalid'.PHP_EOL.PHP_EOL;
658                        $this->printUsage();
659                        exit(2);
660                    }
661                }
662
663                $this->values['sniffs'] = $sniffs;
664            } else if (substr($arg, 0, 8) === 'exclude=') {
665                $sniffs = explode(',', substr($arg, 8));
666                foreach ($sniffs as $sniff) {
667                    if (substr_count($sniff, '.') !== 2) {
668                        echo 'ERROR: The specified sniff code "'.$sniff.'" is invalid'.PHP_EOL.PHP_EOL;
669                        $this->printUsage();
670                        exit(2);
671                    }
672                }
673
674                $this->values['exclude'] = $sniffs;
675            } else if (substr($arg, 0, 10) === 'bootstrap=') {
676                $files = explode(',', substr($arg, 10));
677                foreach ($files as $file) {
678                    $path = PHP_CodeSniffer::realpath($file);
679                    if ($path === false) {
680                        echo 'ERROR: The specified bootstrap file "'.$file.'" does not exist'.PHP_EOL.PHP_EOL;
681                        $this->printUsage();
682                        exit(2);
683                    }
684
685                    $this->values['bootstrap'][] = $path;
686                }
687            } else if (substr($arg, 0, 10) === 'file-list=') {
688                $fileList = substr($arg, 10);
689                $path     = PHP_CodeSniffer::realpath($fileList);
690                if ($path === false) {
691                    echo 'ERROR: The specified file list "'.$file.'" does not exist'.PHP_EOL.PHP_EOL;
692                    $this->printUsage();
693                    exit(2);
694                }
695
696                $files = file($path);
697                foreach ($files as $inputFile) {
698                    $inputFile = trim($inputFile);
699
700                    // Skip empty lines.
701                    if ($inputFile === '') {
702                        continue;
703                    }
704
705                    $realFile = PHP_CodeSniffer::realpath($inputFile);
706                    if ($realFile === false) {
707                        echo 'ERROR: The specified file "'.$inputFile.'" does not exist'.PHP_EOL.PHP_EOL;
708                        $this->printUsage();
709                        exit(2);
710                    }
711
712                    $this->values['files'][] = $realFile;
713                }
714            } else if (substr($arg, 0, 11) === 'stdin-path=') {
715                $this->values['stdinPath'] = PHP_CodeSniffer::realpath(substr($arg, 11));
716
717                // It may not exist and return false instead, so just use whatever they gave us.
718                if ($this->values['stdinPath'] === false) {
719                    $this->values['stdinPath'] = trim(substr($arg, 11));
720                }
721            } else if (substr($arg, 0, 12) === 'report-file=') {
722                $this->values['reportFile'] = PHP_CodeSniffer::realpath(substr($arg, 12));
723
724                // It may not exist and return false instead.
725                if ($this->values['reportFile'] === false) {
726                    $this->values['reportFile'] = substr($arg, 12);
727
728                    $dir = dirname($this->values['reportFile']);
729                    if (is_dir($dir) === false) {
730                        echo 'ERROR: The specified report file path "'.$this->values['reportFile'].'" points to a non-existent directory'.PHP_EOL.PHP_EOL;
731                        $this->printUsage();
732                        exit(2);
733                    }
734
735                    if ($dir === '.') {
736                        // Passed report file is a file in the current directory.
737                        $this->values['reportFile'] = getcwd().'/'.basename($this->values['reportFile']);
738                    } else {
739                        if ($dir{0} === '/') {
740                            // An absolute path.
741                            $dir = PHP_CodeSniffer::realpath($dir);
742                        } else {
743                            $dir = PHP_CodeSniffer::realpath(getcwd().'/'.$dir);
744                        }
745
746                        if ($dir !== false) {
747                            // Report file path is relative.
748                            $this->values['reportFile'] = $dir.'/'.basename($this->values['reportFile']);
749                        }
750                    }
751                }//end if
752
753                if (is_dir($this->values['reportFile']) === true) {
754                    echo 'ERROR: The specified report file path "'.$this->values['reportFile'].'" is a directory'.PHP_EOL.PHP_EOL;
755                    $this->printUsage();
756                    exit(2);
757                }
758            } else if (substr($arg, 0, 13) === 'report-width=') {
759                $this->values['reportWidth'] = $this->_validateReportWidth(substr($arg, 13));
760            } else if (substr($arg, 0, 7) === 'report='
761                || substr($arg, 0, 7) === 'report-'
762            ) {
763                if ($arg[6] === '-') {
764                    // This is a report with file output.
765                    $split = strpos($arg, '=');
766                    if ($split === false) {
767                        $report = substr($arg, 7);
768                        $output = null;
769                    } else {
770                        $report = substr($arg, 7, ($split - 7));
771                        $output = substr($arg, ($split + 1));
772                        if ($output === false) {
773                            $output = null;
774                        } else {
775                            $dir = dirname($output);
776                            if ($dir === '.') {
777                                // Passed report file is a filename in the current directory.
778                                $output = getcwd().'/'.basename($output);
779                            } else {
780                                if ($dir{0} === '/') {
781                                    // An absolute path.
782                                    $dir = PHP_CodeSniffer::realpath($dir);
783                                } else {
784                                    $dir = PHP_CodeSniffer::realpath(getcwd().'/'.$dir);
785                                }
786
787                                if ($dir !== false) {
788                                    // Report file path is relative.
789                                    $output = $dir.'/'.basename($output);
790                                }
791                            }
792                        }//end if
793                    }//end if
794                } else {
795                    // This is a single report.
796                    $report = substr($arg, 7);
797                    $output = null;
798                }//end if
799
800                $this->values['reports'][$report] = $output;
801            } else if (substr($arg, 0, 9) === 'standard=') {
802                $standards = trim(substr($arg, 9));
803                if ($standards !== '') {
804                    $this->values['standard'] = explode(',', $standards);
805                }
806            } else if (substr($arg, 0, 11) === 'extensions=') {
807                if (isset($this->values['extensions']) === false) {
808                    $this->values['extensions'] = array();
809                }
810
811                $this->values['extensions'] = array_merge($this->values['extensions'], explode(',', substr($arg, 11)));
812            } else if (substr($arg, 0, 9) === 'severity=') {
813                $this->values['errorSeverity']   = (int) substr($arg, 9);
814                $this->values['warningSeverity'] = $this->values['errorSeverity'];
815            } else if (substr($arg, 0, 15) === 'error-severity=') {
816                $this->values['errorSeverity'] = (int) substr($arg, 15);
817            } else if (substr($arg, 0, 17) === 'warning-severity=') {
818                $this->values['warningSeverity'] = (int) substr($arg, 17);
819            } else if (substr($arg, 0, 7) === 'ignore=') {
820                // Split the ignore string on commas, unless the comma is escaped
821                // using 1 or 3 slashes (\, or \\\,).
822                $ignored = preg_split(
823                    '/(?<=(?<!\\\\)\\\\\\\\),|(?<!\\\\),/',
824                    substr($arg, 7)
825                );
826                foreach ($ignored as $pattern) {
827                    $pattern = trim($pattern);
828                    if ($pattern === '') {
829                        continue;
830                    }
831
832                    $this->values['ignored'][$pattern] = 'absolute';
833                }
834            } else if (substr($arg, 0, 10) === 'generator=') {
835                $this->values['generator'] = substr($arg, 10);
836            } else if (substr($arg, 0, 9) === 'encoding=') {
837                $this->values['encoding'] = strtolower(substr($arg, 9));
838            } else if (substr($arg, 0, 10) === 'tab-width=') {
839                $this->values['tabWidth'] = (int) substr($arg, 10);
840            } else {
841                if ($this->dieOnUnknownArg === false) {
842                    $eqPos = strpos($arg, '=');
843                    if ($eqPos === false) {
844                        $this->values[$arg] = $arg;
845                    } else {
846                        $value = substr($arg, ($eqPos + 1));
847                        $arg   = substr($arg, 0, $eqPos);
848                        $this->values[$arg] = $value;
849                    }
850                } else {
851                    $this->processUnknownArgument('--'.$arg, $pos);
852                }
853            }//end if
854
855            break;
856        }//end switch
857
858    }//end processLongArgument()
859
860
861    /**
862     * Processes an unknown command line argument.
863     *
864     * Assumes all unknown arguments are files and folders to check.
865     *
866     * @param string $arg The command line argument.
867     * @param int    $pos The position of the argument on the command line.
868     *
869     * @return void
870     */
871    public function processUnknownArgument($arg, $pos)
872    {
873        // We don't know about any additional switches; just files.
874        if ($arg{0} === '-') {
875            if ($this->dieOnUnknownArg === false) {
876                return;
877            }
878
879            echo 'ERROR: option "'.$arg.'" not known.'.PHP_EOL.PHP_EOL;
880            $this->printUsage();
881            exit(2);
882        }
883
884        $file = PHP_CodeSniffer::realpath($arg);
885        if (file_exists($file) === false) {
886            if ($this->dieOnUnknownArg === false) {
887                return;
888            }
889
890            echo 'ERROR: The file "'.$arg.'" does not exist.'.PHP_EOL.PHP_EOL;
891            $this->printUsage();
892            exit(2);
893        } else {
894            $this->values['files'][] = $file;
895        }
896
897    }//end processUnknownArgument()
898
899
900    /**
901     * Runs PHP_CodeSniffer over files and directories.
902     *
903     * @param array $values An array of values determined from CLI args.
904     *
905     * @return int The number of error and warning messages shown.
906     * @see    getCommandLineValues()
907     */
908    public function process($values=array())
909    {
910        if (empty($values) === true) {
911            $values = $this->getCommandLineValues();
912        } else {
913            $values       = array_merge($this->getDefaults(), $values);
914            $this->values = $values;
915        }
916
917        if ($values['generator'] !== '') {
918            $phpcs = new PHP_CodeSniffer($values['verbosity']);
919            if ($values['standard'] === null) {
920                $values['standard'] = $this->validateStandard(null);
921            }
922
923            foreach ($values['standard'] as $standard) {
924                $phpcs->generateDocs(
925                    $standard,
926                    $values['sniffs'],
927                    $values['generator']
928                );
929            }
930
931            exit(0);
932        }
933
934        // If no standard is supplied, get the default.
935        $values['standard'] = $this->validateStandard($values['standard']);
936        foreach ($values['standard'] as $standard) {
937            if (PHP_CodeSniffer::isInstalledStandard($standard) === false) {
938                // They didn't select a valid coding standard, so help them
939                // out by letting them know which standards are installed.
940                echo 'ERROR: the "'.$standard.'" coding standard is not installed. ';
941                $this->printInstalledStandards();
942                exit(2);
943            }
944        }
945
946        if ($values['explain'] === true) {
947            foreach ($values['standard'] as $standard) {
948                $this->explainStandard($standard);
949            }
950
951            exit(0);
952        }
953
954        $phpcs = new PHP_CodeSniffer($values['verbosity'], null, null, null);
955        $phpcs->setCli($this);
956        $phpcs->initStandard($values['standard'], $values['sniffs'], $values['exclude']);
957        $values = $this->values;
958
959        $phpcs->setTabWidth($values['tabWidth']);
960        $phpcs->setEncoding($values['encoding']);
961        $phpcs->setInteractive($values['interactive']);
962
963        // Set file extensions if they were specified. Otherwise,
964        // let PHP_CodeSniffer decide on the defaults.
965        if (empty($values['extensions']) === false) {
966            $phpcs->setAllowedFileExtensions($values['extensions']);
967        }
968
969        // Set ignore patterns if they were specified.
970        if (empty($values['ignored']) === false) {
971            $ignorePatterns = array_merge($phpcs->getIgnorePatterns(), $values['ignored']);
972            $phpcs->setIgnorePatterns($ignorePatterns);
973        }
974
975        // Set some convenience member vars.
976        if ($values['errorSeverity'] === null) {
977            $this->errorSeverity = PHPCS_DEFAULT_ERROR_SEV;
978        } else {
979            $this->errorSeverity = $values['errorSeverity'];
980        }
981
982        if ($values['warningSeverity'] === null) {
983            $this->warningSeverity = PHPCS_DEFAULT_WARN_SEV;
984        } else {
985            $this->warningSeverity = $values['warningSeverity'];
986        }
987
988        if (empty($values['reports']) === true) {
989            $values['reports']['full'] = $values['reportFile'];
990            $this->values['reports']   = $values['reports'];
991        }
992
993        // Include bootstrap files.
994        foreach ($values['bootstrap'] as $bootstrap) {
995            include $bootstrap;
996        }
997
998        $phpcs->processFiles($values['files'], $values['local']);
999
1000        if (empty($values['files']) === true || $values['stdin'] !== null) {
1001            $fileContents = $values['stdin'];
1002            if ($fileContents === null) {
1003                // Check if they are passing in the file contents.
1004                $handle = fopen('php://stdin', 'r');
1005                stream_set_blocking($handle, true);
1006                $fileContents = stream_get_contents($handle);
1007                fclose($handle);
1008            }
1009
1010            if ($fileContents === '') {
1011                // No files and no content passed in.
1012                echo 'ERROR: You must supply at least one file or directory to process.'.PHP_EOL.PHP_EOL;
1013                $this->printUsage();
1014                exit(2);
1015            } else {
1016                $this->values['stdin'] = $fileContents;
1017                $phpcs->processFile('STDIN', $fileContents);
1018            }
1019        }
1020
1021        // Interactive runs don't require a final report and it doesn't really
1022        // matter what the retun value is because we know it isn't being read
1023        // by a script.
1024        if ($values['interactive'] === true) {
1025            return 0;
1026        }
1027
1028        return $this->printErrorReport(
1029            $phpcs,
1030            $values['reports'],
1031            $values['showSources'],
1032            $values['reportFile'],
1033            $values['reportWidth']
1034        );
1035
1036    }//end process()
1037
1038
1039    /**
1040     * Prints the error report for the run.
1041     *
1042     * Note that this function may actually print multiple reports
1043     * as the user may have specified a number of output formats.
1044     *
1045     * @param PHP_CodeSniffer $phpcs       The PHP_CodeSniffer object containing
1046     *                                     the errors.
1047     * @param array           $reports     A list of reports to print.
1048     * @param bool            $showSources TRUE if report should show error sources
1049     *                                     (not used by all reports).
1050     * @param string          $reportFile  A default file to log report output to.
1051     * @param int             $reportWidth How wide the screen reports should be.
1052     *
1053     * @return int The number of error and warning messages shown.
1054     */
1055    public function printErrorReport(
1056        PHP_CodeSniffer $phpcs,
1057        $reports,
1058        $showSources,
1059        $reportFile,
1060        $reportWidth
1061    ) {
1062        if (empty($reports) === true) {
1063            $reports['full'] = $reportFile;
1064        }
1065
1066        $errors   = 0;
1067        $warnings = 0;
1068        $toScreen = false;
1069
1070        foreach ($reports as $report => $output) {
1071            if ($output === null) {
1072                $output = $reportFile;
1073            }
1074
1075            if ($reportFile === null) {
1076                $toScreen = true;
1077            }
1078
1079            // We don't add errors here because the number of
1080            // errors reported by each report type will always be the
1081            // same, so we really just need 1 number.
1082            $result = $phpcs->reporting->printReport(
1083                $report,
1084                $showSources,
1085                $this->values,
1086                $output,
1087                $reportWidth
1088            );
1089
1090            $errors   = $result['errors'];
1091            $warnings = $result['warnings'];
1092        }//end foreach
1093
1094        // Only print timer output if no reports were
1095        // printed to the screen so we don't put additional output
1096        // in something like an XML report. If we are printing to screen,
1097        // the report types would have already worked out who should
1098        // print the timer info.
1099        if (PHP_CODESNIFFER_INTERACTIVE === false
1100            && ($toScreen === false
1101            || (($errors + $warnings) === 0 && $this->values['showProgress'] === true))
1102        ) {
1103            PHP_CodeSniffer_Reporting::printRunTime();
1104        }
1105
1106        // They should all return the same value, so it
1107        // doesn't matter which return value we end up using.
1108        $ignoreWarnings = PHP_CodeSniffer::getConfigData('ignore_warnings_on_exit');
1109        $ignoreErrors   = PHP_CodeSniffer::getConfigData('ignore_errors_on_exit');
1110
1111        $return = ($errors + $warnings);
1112        if ($ignoreErrors !== null) {
1113            $ignoreErrors = (bool) $ignoreErrors;
1114            if ($ignoreErrors === true) {
1115                $return -= $errors;
1116            }
1117        }
1118
1119        if ($ignoreWarnings !== null) {
1120            $ignoreWarnings = (bool) $ignoreWarnings;
1121            if ($ignoreWarnings === true) {
1122                $return -= $warnings;
1123            }
1124        }
1125
1126        return $return;
1127
1128    }//end printErrorReport()
1129
1130
1131    /**
1132     * Convert the passed standards into valid standards.
1133     *
1134     * Checks things like default values and case.
1135     *
1136     * @param array $standards The standards to validate.
1137     *
1138     * @return array
1139     */
1140    public function validateStandard($standards)
1141    {
1142        if ($standards === null) {
1143            // They did not supply a standard to use.
1144            // Look for a default ruleset in the current directory or higher.
1145            $currentDir = getcwd();
1146
1147            do {
1148                $default = $currentDir.DIRECTORY_SEPARATOR.'phpcs.xml';
1149                if (is_file($default) === true) {
1150                    return array($default);
1151                }
1152
1153                $default = $currentDir.DIRECTORY_SEPARATOR.'phpcs.xml.dist';
1154                if (is_file($default) === true) {
1155                    return array($default);
1156                }
1157
1158                $lastDir    = $currentDir;
1159                $currentDir = dirname($currentDir);
1160            } while ($currentDir !== '.' && $currentDir !== $lastDir);
1161
1162            // Try to get the default from the config system.
1163            $standard = PHP_CodeSniffer::getConfigData('default_standard');
1164            if ($standard === null) {
1165                // Product default standard.
1166                $standard = 'PEAR';
1167            }
1168
1169            return explode(',', $standard);
1170        }//end if
1171
1172        $cleaned   = array();
1173        $standards = (array) $standards;
1174
1175        // Check if the standard name is valid, or if the case is invalid.
1176        $installedStandards = PHP_CodeSniffer::getInstalledStandards();
1177        foreach ($standards as $standard) {
1178            foreach ($installedStandards as $validStandard) {
1179                if (strtolower($standard) === strtolower($validStandard)) {
1180                    $standard = $validStandard;
1181                    break;
1182                }
1183            }
1184
1185            $cleaned[] = $standard;
1186        }
1187
1188        return $cleaned;
1189
1190    }//end validateStandard()
1191
1192
1193    /**
1194     * Prints a report showing the sniffs contained in a standard.
1195     *
1196     * @param string $standard The standard to validate.
1197     *
1198     * @return void
1199     */
1200    public function explainStandard($standard)
1201    {
1202        $phpcs = new PHP_CodeSniffer();
1203        $phpcs->process(array(), $standard);
1204        $sniffs = $phpcs->getSniffs();
1205        $sniffs = array_keys($sniffs);
1206        sort($sniffs);
1207
1208        ob_start();
1209
1210        $lastStandard = '';
1211        $lastCount    = '';
1212        $sniffCount   = count($sniffs);
1213        $sniffs[]     = '___';
1214
1215        echo PHP_EOL."The $standard standard contains $sniffCount sniffs".PHP_EOL;
1216
1217        ob_start();
1218
1219        foreach ($sniffs as $sniff) {
1220            $parts = explode('_', str_replace('\\', '_', $sniff));
1221            if ($lastStandard === '') {
1222                $lastStandard = $parts[0];
1223            }
1224
1225            if ($parts[0] !== $lastStandard) {
1226                $sniffList = ob_get_contents();
1227                ob_end_clean();
1228
1229                echo PHP_EOL.$lastStandard.' ('.$lastCount.' sniffs)'.PHP_EOL;
1230                echo str_repeat('-', (strlen($lastStandard.$lastCount) + 10));
1231                echo PHP_EOL;
1232                echo $sniffList;
1233
1234                $lastStandard = $parts[0];
1235                $lastCount    = 0;
1236
1237                ob_start();
1238            }
1239
1240            echo '  '.$parts[0].'.'.$parts[2].'.'.substr($parts[3], 0, -5).PHP_EOL;
1241            $lastCount++;
1242        }//end foreach
1243
1244        ob_end_clean();
1245
1246    }//end explainStandard()
1247
1248
1249    /**
1250     * Prints out the gathered config data.
1251     *
1252     * @param array $data The config data to print.
1253     *
1254     * @return void
1255     */
1256    public function printConfigData($data)
1257    {
1258        $max  = 0;
1259        $keys = array_keys($data);
1260        foreach ($keys as $key) {
1261            $len = strlen($key);
1262            if (strlen($key) > $max) {
1263                $max = $len;
1264            }
1265        }
1266
1267        if ($max === 0) {
1268            return;
1269        }
1270
1271        $max += 2;
1272        ksort($data);
1273        foreach ($data as $name => $value) {
1274            echo str_pad($name.': ', $max).$value.PHP_EOL;
1275        }
1276
1277    }//end printConfigData()
1278
1279
1280    /**
1281     * Prints out the usage information for this script.
1282     *
1283     * @return void
1284     */
1285    public function printUsage()
1286    {
1287        if (PHP_CODESNIFFER_CBF === true) {
1288            $this->printPHPCBFUsage();
1289        } else {
1290            $this->printPHPCSUsage();
1291        }
1292
1293    }//end printUsage()
1294
1295
1296    /**
1297     * Prints out the usage information for PHPCS.
1298     *
1299     * @return void
1300     */
1301    public function printPHPCSUsage()
1302    {
1303        echo 'Usage: phpcs [-nwlsaepqvi] [-d key[=value]] [--colors] [--no-colors] [--stdin-path=<stdinPath>]'.PHP_EOL;
1304        echo '    [--report=<report>] [--report-file=<reportFile>] [--report-<report>=<reportFile>] ...'.PHP_EOL;
1305        echo '    [--report-width=<reportWidth>] [--generator=<generator>] [--tab-width=<tabWidth>]'.PHP_EOL;
1306        echo '    [--severity=<severity>] [--error-severity=<severity>] [--warning-severity=<severity>]'.PHP_EOL;
1307        echo '    [--runtime-set key value] [--config-set key value] [--config-delete key] [--config-show]'.PHP_EOL;
1308        echo '    [--standard=<standard>] [--sniffs=<sniffs>] [--exclude=<sniffs>] [--encoding=<encoding>]'.PHP_EOL;
1309        echo '    [--extensions=<extensions>] [--ignore=<patterns>] [--bootstrap=<bootstrap>]'.PHP_EOL;
1310        echo '    [--file-list=<fileList>] <file> ...'.PHP_EOL;
1311        echo '                      Set runtime value (see --config-set) '.PHP_EOL;
1312        echo '        -n            Do not print warnings (shortcut for --warning-severity=0)'.PHP_EOL;
1313        echo '        -w            Print both warnings and errors (this is the default)'.PHP_EOL;
1314        echo '        -l            Local directory only, no recursion'.PHP_EOL;
1315        echo '        -s            Show sniff codes in all reports'.PHP_EOL;
1316        echo '        -a            Run interactively'.PHP_EOL;
1317        echo '        -e            Explain a standard by showing the sniffs it includes'.PHP_EOL;
1318        echo '        -p            Show progress of the run'.PHP_EOL;
1319        echo '        -q            Quiet mode; disables progress and verbose output'.PHP_EOL;
1320        echo '        -v[v][v]      Print verbose output'.PHP_EOL;
1321        echo '        -i            Show a list of installed coding standards'.PHP_EOL;
1322        echo '        -d            Set the [key] php.ini value to [value] or [true] if value is omitted'.PHP_EOL;
1323        echo '        --help        Print this help message'.PHP_EOL;
1324        echo '        --version     Print version information'.PHP_EOL;
1325        echo '        --colors      Use colors in output'.PHP_EOL;
1326        echo '        --no-colors   Do not use colors in output (this is the default)'.PHP_EOL;
1327        echo '        <file>        One or more files and/or directories to check'.PHP_EOL;
1328        echo '        <fileList>    A file containing a list of files and/or directories to check (one per line)'.PHP_EOL;
1329        echo '        <stdinPath>   If processing STDIN, the file path that STDIN will be processed as '.PHP_EOL;
1330        echo '        <bootstrap>   A comma separated list of files to run before processing starts'.PHP_EOL;
1331        echo '        <encoding>    The encoding of the files being checked (default is iso-8859-1)'.PHP_EOL;
1332        echo '        <extensions>  A comma separated list of file extensions to check'.PHP_EOL;
1333        echo '                      (extension filtering only valid when checking a directory)'.PHP_EOL;
1334        echo '                      The type of the file can be specified using: ext/type'.PHP_EOL;
1335        echo '                      e.g., module/php,es/js'.PHP_EOL;
1336        echo '        <generator>   Uses either the "HTML", "Markdown" or "Text" generator'.PHP_EOL;
1337        echo '                      (forces documentation generation instead of checking)'.PHP_EOL;
1338        echo '        <patterns>    A comma separated list of patterns to ignore files and directories'.PHP_EOL;
1339        echo '        <report>      Print either the "full", "xml", "checkstyle", "csv"'.PHP_EOL;
1340        echo '                      "json", "emacs", "source", "summary", "diff", "junit"'.PHP_EOL;
1341        echo '                      "svnblame", "gitblame", "hgblame" or "notifysend" report'.PHP_EOL;
1342        echo '                      (the "full" report is printed by default)'.PHP_EOL;
1343        echo '        <reportFile>  Write the report to the specified file path'.PHP_EOL;
1344        echo '        <reportWidth> How many columns wide screen reports should be printed'.PHP_EOL;
1345        echo '                      or set to "auto" to use current screen width, where supported'.PHP_EOL;
1346        echo '        <sniffs>      A comma separated list of sniff codes to include or exclude during checking'.PHP_EOL;
1347        echo '                      (all sniffs must be part of the specified standard)'.PHP_EOL;
1348        echo '        <severity>    The minimum severity required to display an error or warning'.PHP_EOL;
1349        echo '        <standard>    The name or path of the coding standard to use'.PHP_EOL;
1350        echo '        <tabWidth>    The number of spaces each tab represents'.PHP_EOL;
1351
1352    }//end printPHPCSUsage()
1353
1354
1355    /**
1356     * Prints out the usage information for PHPCBF.
1357     *
1358     * @return void
1359     */
1360    public function printPHPCBFUsage()
1361    {
1362        echo 'Usage: phpcbf [-nwli] [-d key[=value]] [--stdin-path=<stdinPath>]'.PHP_EOL;
1363        echo '    [--standard=<standard>] [--sniffs=<sniffs>] [--exclude=<sniffs>] [--suffix=<suffix>]'.PHP_EOL;
1364        echo '    [--severity=<severity>] [--error-severity=<severity>] [--warning-severity=<severity>]'.PHP_EOL;
1365        echo '    [--tab-width=<tabWidth>] [--encoding=<encoding>]'.PHP_EOL;
1366        echo '    [--extensions=<extensions>] [--ignore=<patterns>] [--bootstrap=<bootstrap>]'.PHP_EOL;
1367        echo '    [--file-list=<fileList>] <file> ...'.PHP_EOL;
1368        echo '        -n            Do not fix warnings (shortcut for --warning-severity=0)'.PHP_EOL;
1369        echo '        -w            Fix both warnings and errors (on by default)'.PHP_EOL;
1370        echo '        -l            Local directory only, no recursion'.PHP_EOL;
1371        echo '        -i            Show a list of installed coding standards'.PHP_EOL;
1372        echo '        -d            Set the [key] php.ini value to [value] or [true] if value is omitted'.PHP_EOL;
1373        echo '        --help        Print this help message'.PHP_EOL;
1374        echo '        --version     Print version information'.PHP_EOL;
1375        echo '        --no-patch    Do not make use of the "diff" or "patch" programs'.PHP_EOL;
1376        echo '        <file>        One or more files and/or directories to fix'.PHP_EOL;
1377        echo '        <fileList>    A file containing a list of files and/or directories to fix (one per line)'.PHP_EOL;
1378        echo '        <stdinPath>   If processing STDIN, the file path that STDIN will be processed as '.PHP_EOL;
1379        echo '        <bootstrap>   A comma separated list of files to run before processing starts'.PHP_EOL;
1380        echo '        <encoding>    The encoding of the files being fixed (default is iso-8859-1)'.PHP_EOL;
1381        echo '        <extensions>  A comma separated list of file extensions to fix'.PHP_EOL;
1382        echo '                      (extension filtering only valid when checking a directory)'.PHP_EOL;
1383        echo '                      The type of the file can be specified using: ext/type'.PHP_EOL;
1384        echo '                      e.g., module/php,es/js'.PHP_EOL;
1385        echo '        <patterns>    A comma separated list of patterns to ignore files and directories'.PHP_EOL;
1386        echo '        <sniffs>      A comma separated list of sniff codes to include or exclude during fixing'.PHP_EOL;
1387        echo '                      (all sniffs must be part of the specified standard)'.PHP_EOL;
1388        echo '        <severity>    The minimum severity required to fix an error or warning'.PHP_EOL;
1389        echo '        <standard>    The name or path of the coding standard to use'.PHP_EOL;
1390        echo '        <suffix>      Write modified files to a filename using this suffix'.PHP_EOL;
1391        echo '                      ("diff" and "patch" are not used in this mode)'.PHP_EOL;
1392        echo '        <tabWidth>    The number of spaces each tab represents'.PHP_EOL;
1393
1394    }//end printPHPCBFUsage()
1395
1396
1397    /**
1398     * Prints out a list of installed coding standards.
1399     *
1400     * @return void
1401     */
1402    public function printInstalledStandards()
1403    {
1404        $installedStandards = PHP_CodeSniffer::getInstalledStandards();
1405        $numStandards       = count($installedStandards);
1406
1407        if ($numStandards === 0) {
1408            echo 'No coding standards are installed.'.PHP_EOL;
1409        } else {
1410            $lastStandard = array_pop($installedStandards);
1411            if ($numStandards === 1) {
1412                echo "The only coding standard installed is $lastStandard".PHP_EOL;
1413            } else {
1414                $standardList  = implode(', ', $installedStandards);
1415                $standardList .= ' and '.$lastStandard;
1416                echo 'The installed coding standards are '.$standardList.PHP_EOL;
1417            }
1418        }
1419
1420    }//end printInstalledStandards()
1421
1422
1423    /**
1424     * Set report width based on terminal width.
1425     *
1426     * @param int $width The width of the report. If "auto" then will
1427     *                   be replaced by the terminal width.
1428     *
1429     * @return int
1430     */
1431    private function _validateReportWidth($width)
1432    {
1433        if ($width === 'auto'
1434            && preg_match('|\d+ (\d+)|', shell_exec('stty size 2>&1'), $matches) === 1
1435        ) {
1436            return (int) $matches[1];
1437        }
1438
1439        return (int) $width;
1440
1441    }//end _validateReportWidth()
1442
1443
1444}//end class
1445