From 75c7057f82ef12d79a192f6a9abe853e7dd66549 Mon Sep 17 00:00:00 2001 From: Bill Mitchell Date: Mon, 21 Aug 2017 21:38:31 -0700 Subject: [PATCH] Fixes #42 --- README.md | 51 ++++++++++-- composer.json | 3 +- src/Commands/ChurnCommand.php | 14 ++-- src/Factories/ProcessFactory.php | 12 ++- src/Managers/FileManager.php | 14 ++++ src/Values/Config.php | 79 +++++++++++++++++++ .../Integration/Managers/FileManagerTest.php | 3 +- tests/Unit/Factories/ProcessFactoryTest.php | 3 +- tests/Unit/Managers/FileManagerTest.php | 3 +- tests/Unit/Values/ConfigTest.php | 49 ++++++++++++ 10 files changed, 212 insertions(+), 19 deletions(-) create mode 100644 src/Values/Config.php create mode 100644 tests/Unit/Values/ConfigTest.php diff --git a/README.md b/README.md index c687dbfc..38bedc9b 100644 --- a/README.md +++ b/README.md @@ -8,36 +8,45 @@ Helps discover good candidates for refactoring. * [Compatibility](#compatibility) * [How to Install?](#how-to-install) * [How to Use?](#how-to-use) +* [How to Configure?](#how-to-configure) * [Similar Packages](#similar-packages) * [Contributing](#contributing) * [License](#license) ## What is it? ## -`churn-php` is a package that helps you identify files in your project that could be good candidates for refactoring. It examines each PHP file in the path it is provided and: +`churn-php` is a package that helps you identify php files in your project that could be good candidates for refactoring. It examines each PHP file in the path it is provided and: * Checks how many commits it has. * Calculates the cyclomatic complexity. * Creates a score based on these two values. The results are displayed in a table: ``` + ___ _ _ __ __ ____ _ _ ____ _ _ ____ + / __)( )_( )( )( )( _ \( \( )___( _ \( )_( )( _ \ + ( (__ ) _ ( )(__)( ) / ) ((___))___/ ) _ ( )___/ + \___)(_) (_)(______)(_)\_)(_)\_) (__) (_) (_)(__) https://github.com/bmitch/churn-php + +---------------------------------------------------------------------+---------------+------------+-------+ | File | Times Changed | Complexity | Score | +---------------------------------------------------------------------+---------------+------------+-------+ +| src/Managers/FileManager.php | 5 | 4 | 9 | | src/Assessors/CyclomaticComplexity/CyclomaticComplexityAssessor.php | 4 | 4 | 8 | -| src/Assessors/GitCommitCount/GitCommitCountAssessor.php | 3 | 4 | 7 | -| src/Commands/ChurnCommand.php | 3 | 2 | 5 | -| src/Managers/FileManager.php | 2 | 3 | 5 | -| src/Results/ResultsGenerator.php | 2 | 2 | 4 | +| src/Results/ResultsParser.php | 3 | 3 | 6 | | src/Results/Result.php | 2 | 1 | 3 | -| src/Services/CommandService.php | 2 | 1 | 3 | +| src/Factories/ProcessFactory.php | 2 | 1 | 3 | | src/Results/ResultCollection.php | 1 | 1 | 2 | +| src/Values/File.php | 1 | 1 | 2 | +| src/Collections/FileCollection.php | 1 | 1 | 2 | +| src/Values/Config.php | 1 | 1 | 2 | +| src/Processes/ChurnProcess.php | 1 | 1 | 2 | +---------------------------------------------------------------------+---------------+------------+-------+ + 10 files analysed in 0.24276995658875 seconds using 15 parallel jobs. ``` -A file that changes a lot and has a high complexity might be a higher candidate for refactoring than a file that doesn't change a lot and has a low complexity. +A file that changes a lot and has a high complexity might be a better candidate for refactoring than a file that doesn't change a lot and has a low complexity. -`churn-php` only intends to assist the developer identifying files for refactoring. It's best to use the results in addition to your own judgement to decide which files you may want to refactor. +`churn-php` only assists the developer to identify files for refactoring. It's best to use the results in addition to your own judgment to decide which files you may want to refactor. ## Compatibility ## * PHP 7+ @@ -53,6 +62,32 @@ composer require bmitch/churn-php --dev vendor/bin/churn run ``` +## How to Configure? +You may add an optional `churn.yml` file to the root of your project which can be used to configure churn-php. A sample `churm.yml` file looks like: + +```yml +# The maximum number of files to display in the results table. +# Default: 10 +filesToShow: 10 + +# The number of parallel jobs to use when processing files. +# Default 10: +parallelJobs: 10 + +# How far back in the git history to count the number of commits to a file +# Can be a human readable date like 'One week ago' or a date like '2017-07-12' +# Default '10 Years ago' +commitsSince: One year ago + +# Files to ignore when processing. The full path to the file relative to the root of your project is required +# Default: All PHP files in the path provided to churn-php are processed. +filesToIgnore: + - src/Commands/ChurnCommand.php + - src/Results/ResultsParser.php + ``` + +If a `churm.yml` file is omitted or an individual setting is omitted the default values above will be used. + ## Similar Packages * https://github.com/danmayer/churn (Ruby) diff --git a/composer.json b/composer.json index b4fc138e..6a1778af 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,8 @@ "php": ">=7.0.0", "symfony/console": "~3.2", "tightenco/collect": "^5.4", - "symfony/process": "^3.3" + "symfony/process": "^3.3", + "symfony/yaml": "^3.3" }, "require-dev": { "phpunit/phpunit": "^5.7", diff --git a/src/Commands/ChurnCommand.php b/src/Commands/ChurnCommand.php index 35c718dc..57791b89 100644 --- a/src/Commands/ChurnCommand.php +++ b/src/Commands/ChurnCommand.php @@ -3,6 +3,7 @@ namespace Churn\Commands; use Churn\Factories\ProcessFactory; +use Churn\Values\Config; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -12,6 +13,7 @@ use Churn\Results\ResultCollection; use Illuminate\Support\Collection; use Churn\Results\ResultsParser; +use Symfony\Component\Yaml\Yaml; class ChurnCommand extends Command { @@ -51,9 +53,10 @@ class ChurnCommand extends Command public function __construct() { parent::__construct(); - $this->fileManager = new FileManager; + $this->config = new Config(Yaml::parse(@file_get_contents(getcwd() . '/churn.yml')) ?? []); + $this->fileManager = new FileManager($this->config); + $this->processFactory = new ProcessFactory($this->config); $this->resultsParser = new ResultsParser; - $this->processFactory = new ProcessFactory; } /** @@ -97,7 +100,7 @@ protected function execute(InputInterface $input, OutputInterface $output) */ private function getProcessResults() { - for ($index = $this->runningProcesses->count(); $this->filesCollection->hasFiles() > 0 && $index < 10; $index++) { + for ($index = $this->runningProcesses->count(); $this->filesCollection->hasFiles() > 0 && $index < $this->config->getParallelJobs(); $index++) { $file = $this->filesCollection->getNextFile(); $process = $this->processFactory->createGitCommitProcess($file); @@ -135,11 +138,10 @@ protected function displayResults(OutputInterface $output, ResultCollection $res $table = new Table($output); $table->setHeaders(['File', 'Times Changed', 'Complexity', 'Score']); - - foreach ($results->orderByScoreDesc()->take(10) as $result) { + foreach ($results->orderByScoreDesc()->take($this->config->getFilesToShow()) as $result) { $table->addRow($result->toArray()); } $table->render(); - echo " " . $this->filesCount . " files analysed in {$totalTime} seconds.\n\n"; + echo " " . $this->filesCount . " files analysed in {$totalTime} seconds using " . $this->config->getParallelJobs() . " parallel jobs.\n\n"; } } diff --git a/src/Factories/ProcessFactory.php b/src/Factories/ProcessFactory.php index 27427b28..9e2fd97f 100644 --- a/src/Factories/ProcessFactory.php +++ b/src/Factories/ProcessFactory.php @@ -3,11 +3,21 @@ namespace Churn\Factories; use Churn\Processes\ChurnProcess; +use Churn\Values\Config; use Churn\Values\File; use Symfony\Component\Process\Process; class ProcessFactory { + /** + * ProcessFactory constructor. + * @param Config $config Configuration Settings. + */ + public function __construct(Config $config) + { + $this->config = $config; + } + /** * Creates a Git Commit Process that will run on $file. * @param File $file File that the process will execute on. @@ -16,7 +26,7 @@ class ProcessFactory public function createGitCommitProcess(File $file): ChurnProcess { $process = new Process( - 'git -C ' . getcwd() . " log --name-only --pretty=format: " . $file->getFullPath(). " | sort | uniq -c | sort -nr" + 'git -C ' . getcwd() . " log --since=\"" . $this->config->getCommitsSince() . "\" --name-only --pretty=format: " . $file->getFullPath(). " | sort | uniq -c | sort -nr" ); return new ChurnProcess($file, $process, 'GitCommitProcess'); diff --git a/src/Managers/FileManager.php b/src/Managers/FileManager.php index b8a1beb8..6fc945eb 100644 --- a/src/Managers/FileManager.php +++ b/src/Managers/FileManager.php @@ -3,12 +3,22 @@ namespace Churn\Managers; use Churn\Collections\FileCollection; +use Churn\Values\Config; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use Churn\Values\File; class FileManager { + /** + * FileManager constructor. + * @param Config $config Configuration Settings. + */ + public function __construct(Config $config) + { + $this->config = $config; + } + /** * Recursively finds all files with the .php extension in the provided * $path and returns list as array. @@ -24,6 +34,10 @@ public function getPhpFiles(string $path): FileCollection continue; } + if (in_array($file->getPathname(), $this->config->getFilesToIgnore())) { + continue; + } + $files->push(new File(['displayPath' => $file->getPathName(), 'fullPath' => $file->getRealPath()])); } diff --git a/src/Values/Config.php b/src/Values/Config.php new file mode 100644 index 00000000..93b61e25 --- /dev/null +++ b/src/Values/Config.php @@ -0,0 +1,79 @@ +filesToShow = $rawData['filesToShow'] ?? 10; + $this->parallelJobs = $rawData['parallelJobs'] ?? 10; + $this->commitsSince = $rawData['commitsSince'] ?? '10 years ago'; + $this->filesToIgnore = $rawData['filesToIgnore'] ?? []; + } + + /** + * Get the number of files to display in the results table. + * @return integer + */ + public function getFilesToShow(): int + { + return $this->filesToShow; + } + + /** + * Get the number of parallel jobs to use to process the files. + * @return integer + */ + public function getParallelJobs(): int + { + return $this->parallelJobs; + } + + /** + * Get how far back in the git history to go to count commits. + * @return string + */ + public function getCommitsSince(): string + { + return $this->commitsSince; + } + + /** + * Get the paths to files to ignore when processing. + * @return array + */ + public function getFilesToIgnore(): array + { + return $this->filesToIgnore; + } +} diff --git a/tests/Integration/Managers/FileManagerTest.php b/tests/Integration/Managers/FileManagerTest.php index ea41033a..74a83cec 100644 --- a/tests/Integration/Managers/FileManagerTest.php +++ b/tests/Integration/Managers/FileManagerTest.php @@ -4,6 +4,7 @@ use Churn\Managers\FileManager; use Churn\Tests\BaseTestCase; +use Churn\Values\Config; use Churn\Values\File; use Illuminate\Support\Collection; @@ -35,6 +36,6 @@ public function setUp() { parent::setup(); - $this->fileManager = new FileManager; + $this->fileManager = new FileManager(new Config); } } \ No newline at end of file diff --git a/tests/Unit/Factories/ProcessFactoryTest.php b/tests/Unit/Factories/ProcessFactoryTest.php index 134c8182..60582732 100644 --- a/tests/Unit/Factories/ProcessFactoryTest.php +++ b/tests/Unit/Factories/ProcessFactoryTest.php @@ -6,6 +6,7 @@ use Churn\Tests\BaseTestCase; use Churn\Factories\ProcessFactory; use Churn\Values\File; +use Churn\Values\Config; class ProcessFactoryTest extends BaseTestCase { @@ -38,6 +39,6 @@ public function it_can_create_a_cyclomatic_complexity_process() public function setup() { - $this->processFactory = new ProcessFactory; + $this->processFactory = new ProcessFactory(new Config); } } \ No newline at end of file diff --git a/tests/Unit/Managers/FileManagerTest.php b/tests/Unit/Managers/FileManagerTest.php index 00da3b87..a1b2372c 100644 --- a/tests/Unit/Managers/FileManagerTest.php +++ b/tests/Unit/Managers/FileManagerTest.php @@ -4,6 +4,7 @@ use Churn\Tests\BaseTestCase; use Churn\Managers\FileManager; +use Churn\Values\Config; class FileManagerTest extends BaseTestCase { @@ -27,6 +28,6 @@ public function it_can_get_the_php_files_in_a_filter() public function setup() { - $this->fileManager = new FileManager(); + $this->fileManager = new FileManager(new Config); } } \ No newline at end of file diff --git a/tests/Unit/Values/ConfigTest.php b/tests/Unit/Values/ConfigTest.php new file mode 100644 index 00000000..54114f49 --- /dev/null +++ b/tests/Unit/Values/ConfigTest.php @@ -0,0 +1,49 @@ +assertInstanceOf(Config::class, new Config([])); + } + + /** @test **/ + public function it_can_be_instantiated_without_any_parameters() + { + $this->assertInstanceOf(Config::class, new Config); + } + + /** @test **/ + public function it_can_return_its_default_values_when_instantiated_without_any_parameters() + { + $config = new Config; + $this->assertSame(10, $config->getFilesToShow()); + $this->assertSame(10, $config->getParallelJobs()); + $this->assertSame('10 years ago', $config->getCommitsSince()); + $this->assertSame([], $config->getFilesToIgnore()); + } + + /** @test **/ + public function it_can_return_its_values_when_instantiated_parameters() + { + $config = new Config([ + 'filesToShow' => 13, + 'parallelJobs' => 7, + 'commitsSince' => '4 years ago', + 'filesToIgnore' => ['foo.php', 'bar.php', 'baz.php'] + ]); + $this->assertSame(13, $config->getFilesToShow()); + $this->assertSame(7, $config->getParallelJobs()); + $this->assertSame('4 years ago', $config->getCommitsSince()); + $this->assertSame(['foo.php', 'bar.php', 'baz.php'], $config->getFilesToIgnore()); + } + +} \ No newline at end of file