| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829 |
- <?php
- /**
- * Responsible for running PHPCS and PHPCBF.
- *
- * After creating an object of this class, you probably just want to
- * call runPHPCS() or runPHPCBF().
- *
- * @author Greg Sherwood <gsherwood@squiz.net>
- * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
- * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
- */
- namespace PHP_CodeSniffer;
- use PHP_CodeSniffer\Files\FileList;
- use PHP_CodeSniffer\Files\File;
- use PHP_CodeSniffer\Files\DummyFile;
- use PHP_CodeSniffer\Util\Cache;
- use PHP_CodeSniffer\Util\Common;
- use PHP_CodeSniffer\Util\Standards;
- use PHP_CodeSniffer\Exceptions\RuntimeException;
- use PHP_CodeSniffer\Exceptions\DeepExitException;
- class Runner
- {
- /**
- * The config data for the run.
- *
- * @var \PHP_CodeSniffer\Config
- */
- public $config = null;
- /**
- * The ruleset used for the run.
- *
- * @var \PHP_CodeSniffer\Ruleset
- */
- public $ruleset = null;
- /**
- * The reporter used for generating reports after the run.
- *
- * @var \PHP_CodeSniffer\Reporter
- */
- public $reporter = null;
- /**
- * Run the PHPCS script.
- *
- * @return array
- */
- public function runPHPCS()
- {
- try {
- Util\Timing::startTiming();
- Runner::checkRequirements();
- if (defined('PHP_CODESNIFFER_CBF') === false) {
- define('PHP_CODESNIFFER_CBF', false);
- }
- // Creating the Config object populates it with all required settings
- // based on the CLI arguments provided to the script and any config
- // values the user has set.
- $this->config = new Config();
- // Init the run and load the rulesets to set additional config vars.
- $this->init();
- // Print a list of sniffs in each of the supplied standards.
- // We fudge the config here so that each standard is explained in isolation.
- if ($this->config->explain === true) {
- $standards = $this->config->standards;
- foreach ($standards as $standard) {
- $this->config->standards = [$standard];
- $ruleset = new Ruleset($this->config);
- $ruleset->explain();
- }
- return 0;
- }
- // Generate documentation for each of the supplied standards.
- if ($this->config->generator !== null) {
- $standards = $this->config->standards;
- foreach ($standards as $standard) {
- $this->config->standards = [$standard];
- $ruleset = new Ruleset($this->config);
- $class = 'PHP_CodeSniffer\Generators\\'.$this->config->generator;
- $generator = new $class($ruleset);
- $generator->generate();
- }
- return 0;
- }
- // Other report formats don't really make sense in interactive mode
- // so we hard-code the full report here and when outputting.
- // We also ensure parallel processing is off because we need to do one file at a time.
- if ($this->config->interactive === true) {
- $this->config->reports = ['full' => null];
- $this->config->parallel = 1;
- $this->config->showProgress = false;
- }
- // Disable caching if we are processing STDIN as we can't be 100%
- // sure where the file came from or if it will change in the future.
- if ($this->config->stdin === true) {
- $this->config->cache = false;
- }
- $numErrors = $this->run();
- // Print all the reports for this run.
- $toScreen = $this->reporter->printReports();
- // Only print timer output if no reports were
- // printed to the screen so we don't put additional output
- // in something like an XML report. If we are printing to screen,
- // the report types would have already worked out who should
- // print the timer info.
- if ($this->config->interactive === false
- && ($toScreen === false
- || (($this->reporter->totalErrors + $this->reporter->totalWarnings) === 0 && $this->config->showProgress === true))
- ) {
- Util\Timing::printRunTime();
- }
- } catch (DeepExitException $e) {
- echo $e->getMessage();
- return $e->getCode();
- }//end try
- if ($numErrors === 0) {
- // No errors found.
- return 0;
- } else if ($this->reporter->totalFixable === 0) {
- // Errors found, but none of them can be fixed by PHPCBF.
- return 1;
- } else {
- // Errors found, and some can be fixed by PHPCBF.
- return 2;
- }
- }//end runPHPCS()
- /**
- * Run the PHPCBF script.
- *
- * @return array
- */
- public function runPHPCBF()
- {
- if (defined('PHP_CODESNIFFER_CBF') === false) {
- define('PHP_CODESNIFFER_CBF', true);
- }
- try {
- Util\Timing::startTiming();
- Runner::checkRequirements();
- // Creating the Config object populates it with all required settings
- // based on the CLI arguments provided to the script and any config
- // values the user has set.
- $this->config = new Config();
- // When processing STDIN, we can't output anything to the screen
- // or it will end up mixed in with the file output.
- if ($this->config->stdin === true) {
- $this->config->verbosity = 0;
- }
- // Init the run and load the rulesets to set additional config vars.
- $this->init();
- // Override some of the command line settings that might break the fixes.
- $this->config->generator = null;
- $this->config->explain = false;
- $this->config->interactive = false;
- $this->config->cache = false;
- $this->config->showSources = false;
- $this->config->recordErrors = false;
- $this->config->reportFile = null;
- $this->config->reports = ['cbf' => null];
- // If a standard tries to set command line arguments itself, some
- // may be blocked because PHPCBF is running, so stop the script
- // dying if any are found.
- $this->config->dieOnUnknownArg = false;
- $this->run();
- $this->reporter->printReports();
- echo PHP_EOL;
- Util\Timing::printRunTime();
- } catch (DeepExitException $e) {
- echo $e->getMessage();
- return $e->getCode();
- }//end try
- if ($this->reporter->totalFixed === 0) {
- // Nothing was fixed by PHPCBF.
- if ($this->reporter->totalFixable === 0) {
- // Nothing found that could be fixed.
- return 0;
- } else {
- // Something failed to fix.
- return 2;
- }
- }
- if ($this->reporter->totalFixable === 0) {
- // PHPCBF fixed all fixable errors.
- return 1;
- }
- // PHPCBF fixed some fixable errors, but others failed to fix.
- return 2;
- }//end runPHPCBF()
- /**
- * Exits if the minimum requirements of PHP_CodSniffer are not met.
- *
- * @return array
- */
- public function checkRequirements()
- {
- // Check the PHP version.
- if (PHP_VERSION_ID < 50400) {
- $error = 'ERROR: PHP_CodeSniffer requires PHP version 5.4.0 or greater.'.PHP_EOL;
- throw new DeepExitException($error, 3);
- }
- if (extension_loaded('tokenizer') === false) {
- $error = 'ERROR: PHP_CodeSniffer requires the tokenizer extension to be enabled.'.PHP_EOL;
- throw new DeepExitException($error, 3);
- }
- }//end checkRequirements()
- /**
- * Init the rulesets and other high-level settings.
- *
- * @return void
- */
- public function init()
- {
- if (defined('PHP_CODESNIFFER_CBF') === false) {
- define('PHP_CODESNIFFER_CBF', false);
- }
- // Ensure this option is enabled or else line endings will not always
- // be detected properly for files created on a Mac with the /r line ending.
- ini_set('auto_detect_line_endings', true);
- // Check that the standards are valid.
- foreach ($this->config->standards as $standard) {
- if (Util\Standards::isInstalledStandard($standard) === false) {
- // They didn't select a valid coding standard, so help them
- // out by letting them know which standards are installed.
- $error = 'ERROR: the "'.$standard.'" coding standard is not installed. ';
- ob_start();
- Util\Standards::printInstalledStandards();
- $error .= ob_get_contents();
- ob_end_clean();
- throw new DeepExitException($error, 3);
- }
- }
- // Saves passing the Config object into other objects that only need
- // the verbostity flag for deubg output.
- if (defined('PHP_CODESNIFFER_VERBOSITY') === false) {
- define('PHP_CODESNIFFER_VERBOSITY', $this->config->verbosity);
- }
- // Create this class so it is autoloaded and sets up a bunch
- // of PHP_CodeSniffer-specific token type constants.
- $tokens = new Util\Tokens();
- // Allow autoloading of custom files inside installed standards.
- $installedStandards = Standards::getInstalledStandardDetails();
- foreach ($installedStandards as $name => $details) {
- Autoload::addSearchPath($details['path'], $details['namespace']);
- }
- // The ruleset contains all the information about how the files
- // should be checked and/or fixed.
- try {
- $this->ruleset = new Ruleset($this->config);
- } catch (RuntimeException $e) {
- $error = 'ERROR: '.$e->getMessage().PHP_EOL.PHP_EOL;
- $error .= $this->config->printShortUsage(true);
- throw new DeepExitException($error, 3);
- }
- }//end init()
- /**
- * Performs the run.
- *
- * @return int The number of errors and warnings found.
- */
- private function run()
- {
- // The class that manages all reporters for the run.
- $this->reporter = new Reporter($this->config);
- // Include bootstrap files.
- foreach ($this->config->bootstrap as $bootstrap) {
- include $bootstrap;
- }
- if ($this->config->stdin === true) {
- $fileContents = $this->config->stdinContent;
- if ($fileContents === null) {
- $handle = fopen('php://stdin', 'r');
- stream_set_blocking($handle, true);
- $fileContents = stream_get_contents($handle);
- fclose($handle);
- }
- $todo = new FileList($this->config, $this->ruleset);
- $dummy = new DummyFile($fileContents, $this->ruleset, $this->config);
- $todo->addFile($dummy->path, $dummy);
- } else {
- if (empty($this->config->files) === true) {
- $error = 'ERROR: You must supply at least one file or directory to process.'.PHP_EOL.PHP_EOL;
- $error .= $this->config->printShortUsage(true);
- throw new DeepExitException($error, 3);
- }
- if (PHP_CODESNIFFER_VERBOSITY > 0) {
- echo 'Creating file list... ';
- }
- $todo = new FileList($this->config, $this->ruleset);
- if (PHP_CODESNIFFER_VERBOSITY > 0) {
- $numFiles = count($todo);
- echo "DONE ($numFiles files in queue)".PHP_EOL;
- }
- if ($this->config->cache === true) {
- if (PHP_CODESNIFFER_VERBOSITY > 0) {
- echo 'Loading cache... ';
- }
- Cache::load($this->ruleset, $this->config);
- if (PHP_CODESNIFFER_VERBOSITY > 0) {
- $size = Cache::getSize();
- echo "DONE ($size files in cache)".PHP_EOL;
- }
- }
- }//end if
- // Turn all sniff errors into exceptions.
- set_error_handler([$this, 'handleErrors']);
- // If verbosity is too high, turn off parallelism so the
- // debug output is clean.
- if (PHP_CODESNIFFER_VERBOSITY > 1) {
- $this->config->parallel = 1;
- }
- // If the PCNTL extension isn't installed, we can't fork.
- if (function_exists('pcntl_fork') === false) {
- $this->config->parallel = 1;
- }
- $lastDir = '';
- $numFiles = count($todo);
- if ($this->config->parallel === 1) {
- // Running normally.
- $numProcessed = 0;
- foreach ($todo as $path => $file) {
- if ($file->ignored === false) {
- $currDir = dirname($path);
- if ($lastDir !== $currDir) {
- if (PHP_CODESNIFFER_VERBOSITY > 0) {
- echo 'Changing into directory '.Common::stripBasepath($currDir, $this->config->basepath).PHP_EOL;
- }
- $lastDir = $currDir;
- }
- $this->processFile($file);
- } else if (PHP_CODESNIFFER_VERBOSITY > 0) {
- echo 'Skipping '.basename($file->path).PHP_EOL;
- }
- $numProcessed++;
- $this->printProgress($file, $numFiles, $numProcessed);
- }
- } else {
- // Batching and forking.
- $childProcs = [];
- $numPerBatch = ceil($numFiles / $this->config->parallel);
- for ($batch = 0; $batch < $this->config->parallel; $batch++) {
- $startAt = ($batch * $numPerBatch);
- if ($startAt >= $numFiles) {
- break;
- }
- $endAt = ($startAt + $numPerBatch);
- if ($endAt > $numFiles) {
- $endAt = $numFiles;
- }
- $childOutFilename = tempnam(sys_get_temp_dir(), 'phpcs-child');
- $pid = pcntl_fork();
- if ($pid === -1) {
- throw new RuntimeException('Failed to create child process');
- } else if ($pid !== 0) {
- $childProcs[] = [
- 'pid' => $pid,
- 'out' => $childOutFilename,
- ];
- } else {
- // Move forward to the start of the batch.
- $todo->rewind();
- for ($i = 0; $i < $startAt; $i++) {
- $todo->next();
- }
- // Reset the reporter to make sure only figures from this
- // file batch are recorded.
- $this->reporter->totalFiles = 0;
- $this->reporter->totalErrors = 0;
- $this->reporter->totalWarnings = 0;
- $this->reporter->totalFixable = 0;
- $this->reporter->totalFixed = 0;
- // Process the files.
- $pathsProcessed = [];
- ob_start();
- for ($i = $startAt; $i < $endAt; $i++) {
- $path = $todo->key();
- $file = $todo->current();
- if ($file->ignored === true) {
- continue;
- }
- $currDir = dirname($path);
- if ($lastDir !== $currDir) {
- if (PHP_CODESNIFFER_VERBOSITY > 0) {
- echo 'Changing into directory '.Common::stripBasepath($currDir, $this->config->basepath).PHP_EOL;
- }
- $lastDir = $currDir;
- }
- $this->processFile($file);
- $pathsProcessed[] = $path;
- $todo->next();
- }//end for
- $debugOutput = ob_get_contents();
- ob_end_clean();
- // Write information about the run to the filesystem
- // so it can be picked up by the main process.
- $childOutput = [
- 'totalFiles' => $this->reporter->totalFiles,
- 'totalErrors' => $this->reporter->totalErrors,
- 'totalWarnings' => $this->reporter->totalWarnings,
- 'totalFixable' => $this->reporter->totalFixable,
- 'totalFixed' => $this->reporter->totalFixed,
- ];
- $output = '<'.'?php'."\n".' $childOutput = ';
- $output .= var_export($childOutput, true);
- $output .= ";\n\$debugOutput = ";
- $output .= var_export($debugOutput, true);
- if ($this->config->cache === true) {
- $childCache = [];
- foreach ($pathsProcessed as $path) {
- $childCache[$path] = Cache::get($path);
- }
- $output .= ";\n\$childCache = ";
- $output .= var_export($childCache, true);
- }
- $output .= ";\n?".'>';
- file_put_contents($childOutFilename, $output);
- exit($pid);
- }//end if
- }//end for
- $this->processChildProcs($childProcs);
- }//end if
- restore_error_handler();
- if (PHP_CODESNIFFER_VERBOSITY === 0
- && $this->config->interactive === false
- && $this->config->showProgress === true
- ) {
- echo PHP_EOL.PHP_EOL;
- }
- if ($this->config->cache === true) {
- Cache::save();
- }
- $ignoreWarnings = Config::getConfigData('ignore_warnings_on_exit');
- $ignoreErrors = Config::getConfigData('ignore_errors_on_exit');
- $return = ($this->reporter->totalErrors + $this->reporter->totalWarnings);
- if ($ignoreErrors !== null) {
- $ignoreErrors = (bool) $ignoreErrors;
- if ($ignoreErrors === true) {
- $return -= $this->reporter->totalErrors;
- }
- }
- if ($ignoreWarnings !== null) {
- $ignoreWarnings = (bool) $ignoreWarnings;
- if ($ignoreWarnings === true) {
- $return -= $this->reporter->totalWarnings;
- }
- }
- return $return;
- }//end run()
- /**
- * Converts all PHP errors into exceptions.
- *
- * This method forces a sniff to stop processing if it is not
- * able to handle a specific piece of code, instead of continuing
- * and potentially getting into a loop.
- *
- * @param int $code The level of error raised.
- * @param string $message The error message.
- * @param string $file The path of the file that raised the error.
- * @param int $line The line number the error was raised at.
- *
- * @return void
- */
- public function handleErrors($code, $message, $file, $line)
- {
- if ((error_reporting() & $code) === 0) {
- // This type of error is being muted.
- return true;
- }
- throw new RuntimeException("$message in $file on line $line");
- }//end handleErrors()
- /**
- * Processes a single file, including checking and fixing.
- *
- * @param \PHP_CodeSniffer\Files\File $file The file to be processed.
- *
- * @return void
- */
- public function processFile($file)
- {
- if (PHP_CODESNIFFER_VERBOSITY > 0) {
- $startTime = microtime(true);
- echo 'Processing '.basename($file->path).' ';
- if (PHP_CODESNIFFER_VERBOSITY > 1) {
- echo PHP_EOL;
- }
- }
- try {
- $file->process();
- if (PHP_CODESNIFFER_VERBOSITY > 0) {
- $timeTaken = ((microtime(true) - $startTime) * 1000);
- if ($timeTaken < 1000) {
- $timeTaken = round($timeTaken);
- echo "DONE in {$timeTaken}ms";
- } else {
- $timeTaken = round(($timeTaken / 1000), 2);
- echo "DONE in $timeTaken secs";
- }
- if (PHP_CODESNIFFER_CBF === true) {
- $errors = $file->getFixableCount();
- echo " ($errors fixable violations)".PHP_EOL;
- } else {
- $errors = $file->getErrorCount();
- $warnings = $file->getWarningCount();
- echo " ($errors errors, $warnings warnings)".PHP_EOL;
- }
- }
- } catch (\Exception $e) {
- $error = 'An error occurred during processing; checking has been aborted. The error message was: '.$e->getMessage();
- $file->addErrorOnLine($error, 1, 'Internal.Exception');
- }//end try
- $this->reporter->cacheFileReport($file, $this->config);
- if ($this->config->interactive === true) {
- /*
- Running interactively.
- Print the error report for the current file and then wait for user input.
- */
- // Get current violations and then clear the list to make sure
- // we only print violations for a single file each time.
- $numErrors = null;
- while ($numErrors !== 0) {
- $numErrors = ($file->getErrorCount() + $file->getWarningCount());
- if ($numErrors === 0) {
- continue;
- }
- $this->reporter->printReport('full');
- echo '<ENTER> to recheck, [s] to skip or [q] to quit : ';
- $input = fgets(STDIN);
- $input = trim($input);
- switch ($input) {
- case 's':
- break(2);
- case 'q':
- throw new DeepExitException('', 0);
- default:
- // Repopulate the sniffs because some of them save their state
- // and only clear it when the file changes, but we are rechecking
- // the same file.
- $file->ruleset->populateTokenListeners();
- $file->reloadContent();
- $file->process();
- $this->reporter->cacheFileReport($file, $this->config);
- break;
- }
- }//end while
- }//end if
- // Clean up the file to save (a lot of) memory.
- $file->cleanUp();
- }//end processFile()
- /**
- * Waits for child processes to complete and cleans up after them.
- *
- * The reporting information returned by each child process is merged
- * into the main reporter class.
- *
- * @param array $childProcs An array of child processes to wait for.
- *
- * @return void
- */
- private function processChildProcs($childProcs)
- {
- $numProcessed = 0;
- $totalBatches = count($childProcs);
- while (count($childProcs) > 0) {
- foreach ($childProcs as $key => $procData) {
- $res = pcntl_waitpid($procData['pid'], $status, WNOHANG);
- if ($res === $procData['pid']) {
- if (file_exists($procData['out']) === true) {
- include $procData['out'];
- if (isset($childOutput) === true) {
- $this->reporter->totalFiles += $childOutput['totalFiles'];
- $this->reporter->totalErrors += $childOutput['totalErrors'];
- $this->reporter->totalWarnings += $childOutput['totalWarnings'];
- $this->reporter->totalFixable += $childOutput['totalFixable'];
- $this->reporter->totalFixed += $childOutput['totalFixed'];
- }
- if (isset($debugOutput) === true) {
- echo $debugOutput;
- }
- if (isset($childCache) === true) {
- foreach ($childCache as $path => $cache) {
- Cache::set($path, $cache);
- }
- }
- unlink($procData['out']);
- unset($childProcs[$key]);
- $numProcessed++;
- // Fake a processed file so we can print progress output for the batch.
- $file = new DummyFile(null, $this->ruleset, $this->config);
- $file->setErrorCounts(
- $childOutput['totalErrors'],
- $childOutput['totalWarnings'],
- $childOutput['totalFixable'],
- $childOutput['totalFixed']
- );
- $this->printProgress($file, $totalBatches, $numProcessed);
- }//end if
- }//end if
- }//end foreach
- }//end while
- }//end processChildProcs()
- /**
- * Print progress information for a single processed file.
- *
- * @param File $file The file that was processed.
- * @param int $numFiles The total number of files to process.
- * @param int $numProcessed The number of files that have been processed,
- * including this one.
- *
- * @return void
- */
- public function printProgress($file, $numFiles, $numProcessed)
- {
- if (PHP_CODESNIFFER_VERBOSITY > 0
- || $this->config->showProgress === false
- ) {
- return;
- }
- // Show progress information.
- if ($file->ignored === true) {
- echo 'S';
- } else {
- $errors = $file->getErrorCount();
- $warnings = $file->getWarningCount();
- $fixable = $file->getFixableCount();
- $fixed = $file->getFixedCount();
- if (PHP_CODESNIFFER_CBF === true) {
- // Files with fixed errors or warnings are F (green).
- // Files with unfixable errors or warnings are E (red).
- // Files with no errors or warnings are . (black).
- if ($fixable > 0) {
- if ($this->config->colors === true) {
- echo "\033[31m";
- }
- echo 'E';
- if ($this->config->colors === true) {
- echo "\033[0m";
- }
- } else if ($fixed > 0) {
- if ($this->config->colors === true) {
- echo "\033[32m";
- }
- echo 'F';
- if ($this->config->colors === true) {
- echo "\033[0m";
- }
- } else {
- echo '.';
- }//end if
- } else {
- // Files with errors are E (red).
- // Files with fixable errors are E (green).
- // Files with warnings are W (yellow).
- // Files with fixable warnings are W (green).
- // Files with no errors or warnings are . (black).
- if ($errors > 0) {
- if ($this->config->colors === true) {
- if ($fixable > 0) {
- echo "\033[32m";
- } else {
- echo "\033[31m";
- }
- }
- echo 'E';
- if ($this->config->colors === true) {
- echo "\033[0m";
- }
- } else if ($warnings > 0) {
- if ($this->config->colors === true) {
- if ($fixable > 0) {
- echo "\033[32m";
- } else {
- echo "\033[33m";
- }
- }
- echo 'W';
- if ($this->config->colors === true) {
- echo "\033[0m";
- }
- } else {
- echo '.';
- }//end if
- }//end if
- }//end if
- $numPerLine = 60;
- if ($numProcessed !== $numFiles && ($numProcessed % $numPerLine) !== 0) {
- return;
- }
- $percent = round(($numProcessed / $numFiles) * 100);
- $padding = (strlen($numFiles) - strlen($numProcessed));
- if ($numProcessed === $numFiles && $numFiles > $numPerLine) {
- $padding += ($numPerLine - ($numFiles - (floor($numFiles / $numPerLine) * $numPerLine)));
- }
- echo str_repeat(' ', $padding)." $numProcessed / $numFiles ($percent%)".PHP_EOL;
- }//end printProgress()
- }//end class
|