comparison vendor/squizlabs/php_codesniffer/src/Runner.php @ 4:a9cd425dd02b

Update, including to Drupal core 8.6.10
author Chris Cannam
date Thu, 28 Feb 2019 13:11:55 +0000
parents
children 12f9dff5fda9
comparison
equal deleted inserted replaced
3:307d7a7fd348 4:a9cd425dd02b
1 <?php
2 /**
3 * Responsible for running PHPCS and PHPCBF.
4 *
5 * After creating an object of this class, you probably just want to
6 * call runPHPCS() or runPHPCBF().
7 *
8 * @author Greg Sherwood <gsherwood@squiz.net>
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\Files\FileList;
16 use PHP_CodeSniffer\Files\File;
17 use PHP_CodeSniffer\Files\DummyFile;
18 use PHP_CodeSniffer\Util\Cache;
19 use PHP_CodeSniffer\Util\Common;
20 use PHP_CodeSniffer\Util\Standards;
21 use PHP_CodeSniffer\Exceptions\RuntimeException;
22 use PHP_CodeSniffer\Exceptions\DeepExitException;
23
24 class Runner
25 {
26
27 /**
28 * The config data for the run.
29 *
30 * @var \PHP_CodeSniffer\Config
31 */
32 public $config = null;
33
34 /**
35 * The ruleset used for the run.
36 *
37 * @var \PHP_CodeSniffer\Ruleset
38 */
39 public $ruleset = null;
40
41 /**
42 * The reporter used for generating reports after the run.
43 *
44 * @var \PHP_CodeSniffer\Reporter
45 */
46 public $reporter = null;
47
48
49 /**
50 * Run the PHPCS script.
51 *
52 * @return array
53 */
54 public function runPHPCS()
55 {
56 try {
57 Util\Timing::startTiming();
58 Runner::checkRequirements();
59
60 if (defined('PHP_CODESNIFFER_CBF') === false) {
61 define('PHP_CODESNIFFER_CBF', false);
62 }
63
64 // Creating the Config object populates it with all required settings
65 // based on the CLI arguments provided to the script and any config
66 // values the user has set.
67 $this->config = new Config();
68
69 // Init the run and load the rulesets to set additional config vars.
70 $this->init();
71
72 // Print a list of sniffs in each of the supplied standards.
73 // We fudge the config here so that each standard is explained in isolation.
74 if ($this->config->explain === true) {
75 $standards = $this->config->standards;
76 foreach ($standards as $standard) {
77 $this->config->standards = [$standard];
78 $ruleset = new Ruleset($this->config);
79 $ruleset->explain();
80 }
81
82 return 0;
83 }
84
85 // Generate documentation for each of the supplied standards.
86 if ($this->config->generator !== null) {
87 $standards = $this->config->standards;
88 foreach ($standards as $standard) {
89 $this->config->standards = [$standard];
90 $ruleset = new Ruleset($this->config);
91 $class = 'PHP_CodeSniffer\Generators\\'.$this->config->generator;
92 $generator = new $class($ruleset);
93 $generator->generate();
94 }
95
96 return 0;
97 }
98
99 // Other report formats don't really make sense in interactive mode
100 // so we hard-code the full report here and when outputting.
101 // We also ensure parallel processing is off because we need to do one file at a time.
102 if ($this->config->interactive === true) {
103 $this->config->reports = ['full' => null];
104 $this->config->parallel = 1;
105 $this->config->showProgress = false;
106 }
107
108 // Disable caching if we are processing STDIN as we can't be 100%
109 // sure where the file came from or if it will change in the future.
110 if ($this->config->stdin === true) {
111 $this->config->cache = false;
112 }
113
114 $numErrors = $this->run();
115
116 // Print all the reports for this run.
117 $toScreen = $this->reporter->printReports();
118
119 // Only print timer output if no reports were
120 // printed to the screen so we don't put additional output
121 // in something like an XML report. If we are printing to screen,
122 // the report types would have already worked out who should
123 // print the timer info.
124 if ($this->config->interactive === false
125 && ($toScreen === false
126 || (($this->reporter->totalErrors + $this->reporter->totalWarnings) === 0 && $this->config->showProgress === true))
127 ) {
128 Util\Timing::printRunTime();
129 }
130 } catch (DeepExitException $e) {
131 echo $e->getMessage();
132 return $e->getCode();
133 }//end try
134
135 if ($numErrors === 0) {
136 // No errors found.
137 return 0;
138 } else if ($this->reporter->totalFixable === 0) {
139 // Errors found, but none of them can be fixed by PHPCBF.
140 return 1;
141 } else {
142 // Errors found, and some can be fixed by PHPCBF.
143 return 2;
144 }
145
146 }//end runPHPCS()
147
148
149 /**
150 * Run the PHPCBF script.
151 *
152 * @return array
153 */
154 public function runPHPCBF()
155 {
156 if (defined('PHP_CODESNIFFER_CBF') === false) {
157 define('PHP_CODESNIFFER_CBF', true);
158 }
159
160 try {
161 Util\Timing::startTiming();
162 Runner::checkRequirements();
163
164 // Creating the Config object populates it with all required settings
165 // based on the CLI arguments provided to the script and any config
166 // values the user has set.
167 $this->config = new Config();
168
169 // When processing STDIN, we can't output anything to the screen
170 // or it will end up mixed in with the file output.
171 if ($this->config->stdin === true) {
172 $this->config->verbosity = 0;
173 }
174
175 // Init the run and load the rulesets to set additional config vars.
176 $this->init();
177
178 // When processing STDIN, we only process one file at a time and
179 // we don't process all the way through, so we can't use the parallel
180 // running system.
181 if ($this->config->stdin === true) {
182 $this->config->parallel = 1;
183 }
184
185 // Override some of the command line settings that might break the fixes.
186 $this->config->generator = null;
187 $this->config->explain = false;
188 $this->config->interactive = false;
189 $this->config->cache = false;
190 $this->config->showSources = false;
191 $this->config->recordErrors = false;
192 $this->config->reportFile = null;
193 $this->config->reports = ['cbf' => null];
194
195 // If a standard tries to set command line arguments itself, some
196 // may be blocked because PHPCBF is running, so stop the script
197 // dying if any are found.
198 $this->config->dieOnUnknownArg = false;
199
200 $this->run();
201 $this->reporter->printReports();
202
203 echo PHP_EOL;
204 Util\Timing::printRunTime();
205 } catch (DeepExitException $e) {
206 echo $e->getMessage();
207 return $e->getCode();
208 }//end try
209
210 if ($this->reporter->totalFixed === 0) {
211 // Nothing was fixed by PHPCBF.
212 if ($this->reporter->totalFixable === 0) {
213 // Nothing found that could be fixed.
214 return 0;
215 } else {
216 // Something failed to fix.
217 return 2;
218 }
219 }
220
221 if ($this->reporter->totalFixable === 0) {
222 // PHPCBF fixed all fixable errors.
223 return 1;
224 }
225
226 // PHPCBF fixed some fixable errors, but others failed to fix.
227 return 2;
228
229 }//end runPHPCBF()
230
231
232 /**
233 * Exits if the minimum requirements of PHP_CodSniffer are not met.
234 *
235 * @return array
236 */
237 public function checkRequirements()
238 {
239 // Check the PHP version.
240 if (PHP_VERSION_ID < 50400) {
241 $error = 'ERROR: PHP_CodeSniffer requires PHP version 5.4.0 or greater.'.PHP_EOL;
242 throw new DeepExitException($error, 3);
243 }
244
245 if (extension_loaded('tokenizer') === false) {
246 $error = 'ERROR: PHP_CodeSniffer requires the tokenizer extension to be enabled.'.PHP_EOL;
247 throw new DeepExitException($error, 3);
248 }
249
250 }//end checkRequirements()
251
252
253 /**
254 * Init the rulesets and other high-level settings.
255 *
256 * @return void
257 */
258 public function init()
259 {
260 if (defined('PHP_CODESNIFFER_CBF') === false) {
261 define('PHP_CODESNIFFER_CBF', false);
262 }
263
264 // Ensure this option is enabled or else line endings will not always
265 // be detected properly for files created on a Mac with the /r line ending.
266 ini_set('auto_detect_line_endings', true);
267
268 // Check that the standards are valid.
269 foreach ($this->config->standards as $standard) {
270 if (Util\Standards::isInstalledStandard($standard) === false) {
271 // They didn't select a valid coding standard, so help them
272 // out by letting them know which standards are installed.
273 $error = 'ERROR: the "'.$standard.'" coding standard is not installed. ';
274 ob_start();
275 Util\Standards::printInstalledStandards();
276 $error .= ob_get_contents();
277 ob_end_clean();
278 throw new DeepExitException($error, 3);
279 }
280 }
281
282 // Saves passing the Config object into other objects that only need
283 // the verbostity flag for deubg output.
284 if (defined('PHP_CODESNIFFER_VERBOSITY') === false) {
285 define('PHP_CODESNIFFER_VERBOSITY', $this->config->verbosity);
286 }
287
288 // Create this class so it is autoloaded and sets up a bunch
289 // of PHP_CodeSniffer-specific token type constants.
290 $tokens = new Util\Tokens();
291
292 // Allow autoloading of custom files inside installed standards.
293 $installedStandards = Standards::getInstalledStandardDetails();
294 foreach ($installedStandards as $name => $details) {
295 Autoload::addSearchPath($details['path'], $details['namespace']);
296 }
297
298 // The ruleset contains all the information about how the files
299 // should be checked and/or fixed.
300 try {
301 $this->ruleset = new Ruleset($this->config);
302 } catch (RuntimeException $e) {
303 $error = 'ERROR: '.$e->getMessage().PHP_EOL.PHP_EOL;
304 $error .= $this->config->printShortUsage(true);
305 throw new DeepExitException($error, 3);
306 }
307
308 }//end init()
309
310
311 /**
312 * Performs the run.
313 *
314 * @return int The number of errors and warnings found.
315 */
316 private function run()
317 {
318 // The class that manages all reporters for the run.
319 $this->reporter = new Reporter($this->config);
320
321 // Include bootstrap files.
322 foreach ($this->config->bootstrap as $bootstrap) {
323 include $bootstrap;
324 }
325
326 if ($this->config->stdin === true) {
327 $fileContents = $this->config->stdinContent;
328 if ($fileContents === null) {
329 $handle = fopen('php://stdin', 'r');
330 stream_set_blocking($handle, true);
331 $fileContents = stream_get_contents($handle);
332 fclose($handle);
333 }
334
335 $todo = new FileList($this->config, $this->ruleset);
336 $dummy = new DummyFile($fileContents, $this->ruleset, $this->config);
337 $todo->addFile($dummy->path, $dummy);
338 } else {
339 if (empty($this->config->files) === true) {
340 $error = 'ERROR: You must supply at least one file or directory to process.'.PHP_EOL.PHP_EOL;
341 $error .= $this->config->printShortUsage(true);
342 throw new DeepExitException($error, 3);
343 }
344
345 if (PHP_CODESNIFFER_VERBOSITY > 0) {
346 echo 'Creating file list... ';
347 }
348
349 $todo = new FileList($this->config, $this->ruleset);
350
351 if (PHP_CODESNIFFER_VERBOSITY > 0) {
352 $numFiles = count($todo);
353 echo "DONE ($numFiles files in queue)".PHP_EOL;
354 }
355
356 if ($this->config->cache === true) {
357 if (PHP_CODESNIFFER_VERBOSITY > 0) {
358 echo 'Loading cache... ';
359 }
360
361 Cache::load($this->ruleset, $this->config);
362
363 if (PHP_CODESNIFFER_VERBOSITY > 0) {
364 $size = Cache::getSize();
365 echo "DONE ($size files in cache)".PHP_EOL;
366 }
367 }
368 }//end if
369
370 // Turn all sniff errors into exceptions.
371 set_error_handler([$this, 'handleErrors']);
372
373 // If verbosity is too high, turn off parallelism so the
374 // debug output is clean.
375 if (PHP_CODESNIFFER_VERBOSITY > 1) {
376 $this->config->parallel = 1;
377 }
378
379 // If the PCNTL extension isn't installed, we can't fork.
380 if (function_exists('pcntl_fork') === false) {
381 $this->config->parallel = 1;
382 }
383
384 $lastDir = '';
385 $numFiles = count($todo);
386
387 if ($this->config->parallel === 1) {
388 // Running normally.
389 $numProcessed = 0;
390 foreach ($todo as $path => $file) {
391 if ($file->ignored === false) {
392 $currDir = dirname($path);
393 if ($lastDir !== $currDir) {
394 if (PHP_CODESNIFFER_VERBOSITY > 0) {
395 echo 'Changing into directory '.Common::stripBasepath($currDir, $this->config->basepath).PHP_EOL;
396 }
397
398 $lastDir = $currDir;
399 }
400
401 $this->processFile($file);
402 } else if (PHP_CODESNIFFER_VERBOSITY > 0) {
403 echo 'Skipping '.basename($file->path).PHP_EOL;
404 }
405
406 $numProcessed++;
407 $this->printProgress($file, $numFiles, $numProcessed);
408 }
409 } else {
410 // Batching and forking.
411 $childProcs = [];
412 $numPerBatch = ceil($numFiles / $this->config->parallel);
413
414 for ($batch = 0; $batch < $this->config->parallel; $batch++) {
415 $startAt = ($batch * $numPerBatch);
416 if ($startAt >= $numFiles) {
417 break;
418 }
419
420 $endAt = ($startAt + $numPerBatch);
421 if ($endAt > $numFiles) {
422 $endAt = $numFiles;
423 }
424
425 $childOutFilename = tempnam(sys_get_temp_dir(), 'phpcs-child');
426 $pid = pcntl_fork();
427 if ($pid === -1) {
428 throw new RuntimeException('Failed to create child process');
429 } else if ($pid !== 0) {
430 $childProcs[] = [
431 'pid' => $pid,
432 'out' => $childOutFilename,
433 ];
434 } else {
435 // Move forward to the start of the batch.
436 $todo->rewind();
437 for ($i = 0; $i < $startAt; $i++) {
438 $todo->next();
439 }
440
441 // Reset the reporter to make sure only figures from this
442 // file batch are recorded.
443 $this->reporter->totalFiles = 0;
444 $this->reporter->totalErrors = 0;
445 $this->reporter->totalWarnings = 0;
446 $this->reporter->totalFixable = 0;
447 $this->reporter->totalFixed = 0;
448
449 // Process the files.
450 $pathsProcessed = [];
451 ob_start();
452 for ($i = $startAt; $i < $endAt; $i++) {
453 $path = $todo->key();
454 $file = $todo->current();
455
456 if ($file->ignored === true) {
457 continue;
458 }
459
460 $currDir = dirname($path);
461 if ($lastDir !== $currDir) {
462 if (PHP_CODESNIFFER_VERBOSITY > 0) {
463 echo 'Changing into directory '.Common::stripBasepath($currDir, $this->config->basepath).PHP_EOL;
464 }
465
466 $lastDir = $currDir;
467 }
468
469 $this->processFile($file);
470
471 $pathsProcessed[] = $path;
472 $todo->next();
473 }//end for
474
475 $debugOutput = ob_get_contents();
476 ob_end_clean();
477
478 // Write information about the run to the filesystem
479 // so it can be picked up by the main process.
480 $childOutput = [
481 'totalFiles' => $this->reporter->totalFiles,
482 'totalErrors' => $this->reporter->totalErrors,
483 'totalWarnings' => $this->reporter->totalWarnings,
484 'totalFixable' => $this->reporter->totalFixable,
485 'totalFixed' => $this->reporter->totalFixed,
486 ];
487
488 $output = '<'.'?php'."\n".' $childOutput = ';
489 $output .= var_export($childOutput, true);
490 $output .= ";\n\$debugOutput = ";
491 $output .= var_export($debugOutput, true);
492
493 if ($this->config->cache === true) {
494 $childCache = [];
495 foreach ($pathsProcessed as $path) {
496 $childCache[$path] = Cache::get($path);
497 }
498
499 $output .= ";\n\$childCache = ";
500 $output .= var_export($childCache, true);
501 }
502
503 $output .= ";\n?".'>';
504 file_put_contents($childOutFilename, $output);
505 exit($pid);
506 }//end if
507 }//end for
508
509 $this->processChildProcs($childProcs);
510 }//end if
511
512 restore_error_handler();
513
514 if (PHP_CODESNIFFER_VERBOSITY === 0
515 && $this->config->interactive === false
516 && $this->config->showProgress === true
517 ) {
518 echo PHP_EOL.PHP_EOL;
519 }
520
521 if ($this->config->cache === true) {
522 Cache::save();
523 }
524
525 $ignoreWarnings = Config::getConfigData('ignore_warnings_on_exit');
526 $ignoreErrors = Config::getConfigData('ignore_errors_on_exit');
527
528 $return = ($this->reporter->totalErrors + $this->reporter->totalWarnings);
529 if ($ignoreErrors !== null) {
530 $ignoreErrors = (bool) $ignoreErrors;
531 if ($ignoreErrors === true) {
532 $return -= $this->reporter->totalErrors;
533 }
534 }
535
536 if ($ignoreWarnings !== null) {
537 $ignoreWarnings = (bool) $ignoreWarnings;
538 if ($ignoreWarnings === true) {
539 $return -= $this->reporter->totalWarnings;
540 }
541 }
542
543 return $return;
544
545 }//end run()
546
547
548 /**
549 * Converts all PHP errors into exceptions.
550 *
551 * This method forces a sniff to stop processing if it is not
552 * able to handle a specific piece of code, instead of continuing
553 * and potentially getting into a loop.
554 *
555 * @param int $code The level of error raised.
556 * @param string $message The error message.
557 * @param string $file The path of the file that raised the error.
558 * @param int $line The line number the error was raised at.
559 *
560 * @return void
561 */
562 public function handleErrors($code, $message, $file, $line)
563 {
564 if ((error_reporting() & $code) === 0) {
565 // This type of error is being muted.
566 return true;
567 }
568
569 throw new RuntimeException("$message in $file on line $line");
570
571 }//end handleErrors()
572
573
574 /**
575 * Processes a single file, including checking and fixing.
576 *
577 * @param \PHP_CodeSniffer\Files\File $file The file to be processed.
578 *
579 * @return void
580 */
581 public function processFile($file)
582 {
583 if (PHP_CODESNIFFER_VERBOSITY > 0) {
584 $startTime = microtime(true);
585 echo 'Processing '.basename($file->path).' ';
586 if (PHP_CODESNIFFER_VERBOSITY > 1) {
587 echo PHP_EOL;
588 }
589 }
590
591 try {
592 $file->process();
593
594 if (PHP_CODESNIFFER_VERBOSITY > 0) {
595 $timeTaken = ((microtime(true) - $startTime) * 1000);
596 if ($timeTaken < 1000) {
597 $timeTaken = round($timeTaken);
598 echo "DONE in {$timeTaken}ms";
599 } else {
600 $timeTaken = round(($timeTaken / 1000), 2);
601 echo "DONE in $timeTaken secs";
602 }
603
604 if (PHP_CODESNIFFER_CBF === true) {
605 $errors = $file->getFixableCount();
606 echo " ($errors fixable violations)".PHP_EOL;
607 } else {
608 $errors = $file->getErrorCount();
609 $warnings = $file->getWarningCount();
610 echo " ($errors errors, $warnings warnings)".PHP_EOL;
611 }
612 }
613 } catch (\Exception $e) {
614 $error = 'An error occurred during processing; checking has been aborted. The error message was: '.$e->getMessage();
615 $file->addErrorOnLine($error, 1, 'Internal.Exception');
616 }//end try
617
618 $this->reporter->cacheFileReport($file, $this->config);
619
620 if ($this->config->interactive === true) {
621 /*
622 Running interactively.
623 Print the error report for the current file and then wait for user input.
624 */
625
626 // Get current violations and then clear the list to make sure
627 // we only print violations for a single file each time.
628 $numErrors = null;
629 while ($numErrors !== 0) {
630 $numErrors = ($file->getErrorCount() + $file->getWarningCount());
631 if ($numErrors === 0) {
632 continue;
633 }
634
635 $this->reporter->printReport('full');
636
637 echo '<ENTER> to recheck, [s] to skip or [q] to quit : ';
638 $input = fgets(STDIN);
639 $input = trim($input);
640
641 switch ($input) {
642 case 's':
643 break(2);
644 case 'q':
645 throw new DeepExitException('', 0);
646 default:
647 // Repopulate the sniffs because some of them save their state
648 // and only clear it when the file changes, but we are rechecking
649 // the same file.
650 $file->ruleset->populateTokenListeners();
651 $file->reloadContent();
652 $file->process();
653 $this->reporter->cacheFileReport($file, $this->config);
654 break;
655 }
656 }//end while
657 }//end if
658
659 // Clean up the file to save (a lot of) memory.
660 $file->cleanUp();
661
662 }//end processFile()
663
664
665 /**
666 * Waits for child processes to complete and cleans up after them.
667 *
668 * The reporting information returned by each child process is merged
669 * into the main reporter class.
670 *
671 * @param array $childProcs An array of child processes to wait for.
672 *
673 * @return void
674 */
675 private function processChildProcs($childProcs)
676 {
677 $numProcessed = 0;
678 $totalBatches = count($childProcs);
679
680 while (count($childProcs) > 0) {
681 foreach ($childProcs as $key => $procData) {
682 $res = pcntl_waitpid($procData['pid'], $status, WNOHANG);
683 if ($res === $procData['pid']) {
684 if (file_exists($procData['out']) === true) {
685 include $procData['out'];
686 if (isset($childOutput) === true) {
687 $this->reporter->totalFiles += $childOutput['totalFiles'];
688 $this->reporter->totalErrors += $childOutput['totalErrors'];
689 $this->reporter->totalWarnings += $childOutput['totalWarnings'];
690 $this->reporter->totalFixable += $childOutput['totalFixable'];
691 $this->reporter->totalFixed += $childOutput['totalFixed'];
692 }
693
694 if (isset($debugOutput) === true) {
695 echo $debugOutput;
696 }
697
698 if (isset($childCache) === true) {
699 foreach ($childCache as $path => $cache) {
700 Cache::set($path, $cache);
701 }
702 }
703
704 unlink($procData['out']);
705 unset($childProcs[$key]);
706
707 $numProcessed++;
708
709 // Fake a processed file so we can print progress output for the batch.
710 $file = new DummyFile(null, $this->ruleset, $this->config);
711 $file->setErrorCounts(
712 $childOutput['totalErrors'],
713 $childOutput['totalWarnings'],
714 $childOutput['totalFixable'],
715 $childOutput['totalFixed']
716 );
717 $this->printProgress($file, $totalBatches, $numProcessed);
718 }//end if
719 }//end if
720 }//end foreach
721 }//end while
722
723 }//end processChildProcs()
724
725
726 /**
727 * Print progress information for a single processed file.
728 *
729 * @param File $file The file that was processed.
730 * @param int $numFiles The total number of files to process.
731 * @param int $numProcessed The number of files that have been processed,
732 * including this one.
733 *
734 * @return void
735 */
736 public function printProgress($file, $numFiles, $numProcessed)
737 {
738 if (PHP_CODESNIFFER_VERBOSITY > 0
739 || $this->config->showProgress === false
740 ) {
741 return;
742 }
743
744 // Show progress information.
745 if ($file->ignored === true) {
746 echo 'S';
747 } else {
748 $errors = $file->getErrorCount();
749 $warnings = $file->getWarningCount();
750 $fixable = $file->getFixableCount();
751 $fixed = $file->getFixedCount();
752
753 if (PHP_CODESNIFFER_CBF === true) {
754 // Files with fixed errors or warnings are F (green).
755 // Files with unfixable errors or warnings are E (red).
756 // Files with no errors or warnings are . (black).
757 if ($fixable > 0) {
758 if ($this->config->colors === true) {
759 echo "\033[31m";
760 }
761
762 echo 'E';
763
764 if ($this->config->colors === true) {
765 echo "\033[0m";
766 }
767 } else if ($fixed > 0) {
768 if ($this->config->colors === true) {
769 echo "\033[32m";
770 }
771
772 echo 'F';
773
774 if ($this->config->colors === true) {
775 echo "\033[0m";
776 }
777 } else {
778 echo '.';
779 }//end if
780 } else {
781 // Files with errors are E (red).
782 // Files with fixable errors are E (green).
783 // Files with warnings are W (yellow).
784 // Files with fixable warnings are W (green).
785 // Files with no errors or warnings are . (black).
786 if ($errors > 0) {
787 if ($this->config->colors === true) {
788 if ($fixable > 0) {
789 echo "\033[32m";
790 } else {
791 echo "\033[31m";
792 }
793 }
794
795 echo 'E';
796
797 if ($this->config->colors === true) {
798 echo "\033[0m";
799 }
800 } else if ($warnings > 0) {
801 if ($this->config->colors === true) {
802 if ($fixable > 0) {
803 echo "\033[32m";
804 } else {
805 echo "\033[33m";
806 }
807 }
808
809 echo 'W';
810
811 if ($this->config->colors === true) {
812 echo "\033[0m";
813 }
814 } else {
815 echo '.';
816 }//end if
817 }//end if
818 }//end if
819
820 $numPerLine = 60;
821 if ($numProcessed !== $numFiles && ($numProcessed % $numPerLine) !== 0) {
822 return;
823 }
824
825 $percent = round(($numProcessed / $numFiles) * 100);
826 $padding = (strlen($numFiles) - strlen($numProcessed));
827 if ($numProcessed === $numFiles && $numFiles > $numPerLine) {
828 $padding += ($numPerLine - ($numFiles - (floor($numFiles / $numPerLine) * $numPerLine)));
829 }
830
831 echo str_repeat(' ', $padding)." $numProcessed / $numFiles ($percent%)".PHP_EOL;
832
833 }//end printProgress()
834
835
836 }//end class