From f862f5b41e1b1f7e7c339b828bf69171d156e334 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 12 Dec 2024 17:13:28 +0200 Subject: [PATCH] Auto-complete fork names for "sync" and "bc" commands --- CHANGELOG.md | 2 +- src/CodeInsight/Command/AbstractCommand.php | 24 +++++-- .../Command/BackwardsCompatibilityCommand.php | 72 ++++++++++++++----- .../Command/MissingTestsCommand.php | 4 +- src/CodeInsight/Command/ReportCommand.php | 2 +- src/CodeInsight/Command/SyncCommand.php | 26 ++++++- .../KnowledgeBase/DatabaseManager.php | 36 +++++++++- .../KnowledgeBase/KnowledgeBaseFactory.php | 12 ++++ .../KnowledgeBase/DatabaseManagerTest.php | 41 +++++++++++ 9 files changed, 191 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e150ce..d872992 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added -... +- The fork names are auto-completed for `sync` (`--project-fork` option) and `bc` (`--source-project-fork` and `--target-project-fork` options) commands. ### Changed ... diff --git a/src/CodeInsight/Command/AbstractCommand.php b/src/CodeInsight/Command/AbstractCommand.php index 4404ace..2d3e34c 100644 --- a/src/CodeInsight/Command/AbstractCommand.php +++ b/src/CodeInsight/Command/AbstractCommand.php @@ -12,6 +12,9 @@ use ConsoleHelpers\ConsoleKit\Command\AbstractCommand as BaseCommand; +use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputInterface; /** * Base command class. @@ -29,18 +32,31 @@ protected function prepareDependencies() $container = $this->getContainer(); } + /** + * Returns input from completion context. + * + * @param CompletionContext $context Completion context. + * + * @return InputInterface + */ + protected function getInputFromCompletionContext(CompletionContext $context) + { + $words = $context->getWords(); + array_splice($words, 1, 1); // Remove the command name. + + return new ArgvInput($words, $this->getDefinition()); + } + /** * Returns and validates path. * - * @param string $argument_name Argument name, that contains path. + * @param string $raw_path Raw path. * * @return string * @throws \InvalidArgumentException When path isn't valid. */ - protected function getPath($argument_name) + protected function getPath($raw_path) { - $raw_path = $this->io->getArgument($argument_name); - if ( !$raw_path ) { return ''; } diff --git a/src/CodeInsight/Command/BackwardsCompatibilityCommand.php b/src/CodeInsight/Command/BackwardsCompatibilityCommand.php index c485ff6..5e79676 100644 --- a/src/CodeInsight/Command/BackwardsCompatibilityCommand.php +++ b/src/CodeInsight/Command/BackwardsCompatibilityCommand.php @@ -19,6 +19,7 @@ use ConsoleHelpers\CodeInsight\KnowledgeBase\KnowledgeBaseFactory; use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext; use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -124,6 +125,18 @@ public function completeOptionValues($optionName, CompletionContext $context) { $ret = parent::completeOptionValues($optionName, $context); + if ( $optionName === 'source-project-fork' ) { + $input = $this->getInputFromCompletionContext($context); + + return $this->_knowledgeBaseFactory->getForks($this->getSourcePath($input, true)); + } + + if ( $optionName === 'target-project-fork' ) { + $input = $this->getInputFromCompletionContext($context); + + return $this->_knowledgeBaseFactory->getForks($this->getTargetPath($input)); + } + if ( $optionName === 'format' ) { return $this->_reporterFactory->getNames(); } @@ -133,33 +146,18 @@ public function completeOptionValues($optionName, CompletionContext $context) /** * {@inheritdoc} - * - * @throws RuntimeException When source project path is missing. */ protected function execute(InputInterface $input, OutputInterface $output) { // Get reporter upfront so that we can error out early for invalid reporters. $reporter = $this->_reporterFactory->get($this->io->getOption('format')); - $source_path = $this->getPath('source-project-path'); - $target_path = $this->getPath('target-project-path'); - - $source_fork = $this->io->getOption('source-project-fork'); - - if ( !$source_path ) { - if ( $source_fork ) { - // Single code base, but comparing with fork. - $source_path = $target_path; - } - else { - // Not using fork, then need to specify project path. - throw new RuntimeException('Not enough arguments (missing: "source-project-path").'); - } - } + $source_path = $this->getSourcePath($input, false); + $target_path = $this->getTargetPath($input); $source_knowledge_base = $this->_knowledgeBaseFactory->getKnowledgeBase( $source_path, - $source_fork, + $this->io->getOption('source-project-fork'), $this->io ); $target_knowledge_base = $this->_knowledgeBaseFactory->getKnowledgeBase( @@ -181,6 +179,44 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->io->writeln($reporter->generate($bc_breaks)); } + /** + * Returns source path. + * + * @param InputInterface $input Input. + * @param boolean $autocomplete Autocomplete. + * + * @return string + * @throws RuntimeException When source project path is missing. + */ + protected function getSourcePath(InputInterface $input, $autocomplete) + { + $source_path = $this->getPath($input->getArgument('source-project-path')); + + if ( $source_path ) { + return $source_path; + } + + // Single code base, but comparing with fork OR autocompleting forks. + if ( $autocomplete || $input->getOption('source-project-fork') ) { + return $this->getTargetPath($input); + } + + // Not using fork, then need to specify project path. + throw new RuntimeException('Not enough arguments (missing: "source-project-path").'); + } + + /** + * Returns target path. + * + * @param InputInterface $input Input. + * + * @return string + */ + protected function getTargetPath(InputInterface $input) + { + return $this->getPath($input->getArgument('target-project-path')); + } + /** * Finds backward compatibility breaks. * diff --git a/src/CodeInsight/Command/MissingTestsCommand.php b/src/CodeInsight/Command/MissingTestsCommand.php index 3e1482e..dc0b0a7 100644 --- a/src/CodeInsight/Command/MissingTestsCommand.php +++ b/src/CodeInsight/Command/MissingTestsCommand.php @@ -46,8 +46,8 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $src_path = $this->getPath('src-path'); - $tests_path = $this->getPath('tests-path'); + $src_path = $this->getPath($this->io->getArgument('src-path')); + $tests_path = $this->getPath($this->io->getArgument('tests-path')); $finder = new Finder(); $finder->files()->name('*.php')->notName('/^(I[A-Z]|Abstract[A-Z])/')->in($src_path); diff --git a/src/CodeInsight/Command/ReportCommand.php b/src/CodeInsight/Command/ReportCommand.php index 1f11d33..50f3f17 100644 --- a/src/CodeInsight/Command/ReportCommand.php +++ b/src/CodeInsight/Command/ReportCommand.php @@ -69,7 +69,7 @@ protected function prepareDependencies() protected function execute(InputInterface $input, OutputInterface $output) { $knowledge_base = $this->_knowledgeBaseFactory->getKnowledgeBase( - $this->getPath('project-path'), + $this->getPath($this->io->getArgument('project-path')), $this->io->getOption('project-fork'), $this->io ); diff --git a/src/CodeInsight/Command/SyncCommand.php b/src/CodeInsight/Command/SyncCommand.php index 775ba2d..92c532e 100644 --- a/src/CodeInsight/Command/SyncCommand.php +++ b/src/CodeInsight/Command/SyncCommand.php @@ -12,6 +12,7 @@ use ConsoleHelpers\CodeInsight\KnowledgeBase\KnowledgeBaseFactory; +use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -63,13 +64,36 @@ protected function prepareDependencies() $this->_knowledgeBaseFactory = $container['knowledge_base_factory']; } + /** + * Return possible values for the named option + * + * @param string $optionName Option name. + * @param CompletionContext $context Completion context. + * + * @return array + */ + public function completeOptionValues($optionName, CompletionContext $context) + { + $ret = parent::completeOptionValues($optionName, $context); + + if ( $optionName === 'project-fork' ) { + $input = $this->getInputFromCompletionContext($context); + + return $this->_knowledgeBaseFactory->getForks( + $this->getPath($input->getArgument('project-path')) + ); + } + + return $ret; + } + /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $knowledge_base = $this->_knowledgeBaseFactory->getKnowledgeBase( - $this->getPath('project-path'), + $this->getPath($this->io->getArgument('project-path')), $this->io->getOption('project-fork'), $this->io ); diff --git a/src/CodeInsight/KnowledgeBase/DatabaseManager.php b/src/CodeInsight/KnowledgeBase/DatabaseManager.php index 6eb99ad..a5a60c9 100644 --- a/src/CodeInsight/KnowledgeBase/DatabaseManager.php +++ b/src/CodeInsight/KnowledgeBase/DatabaseManager.php @@ -60,7 +60,7 @@ public function __construct(MigrationManager $migration_manager, $working_direct */ public function getDatabase($project_path, $fork = null) { - if ( substr($project_path, 0, 1) !== '/' ) { + if ( strpos($project_path, '/') !== 0 ) { throw new \InvalidArgumentException('The "$project_path" argument must contain absolute path.'); } @@ -85,6 +85,40 @@ public function getDatabase($project_path, $fork = null) return new ExtendedPdo('sqlite:' . $fork_db_file); } + /** + * Returns forks for given project. + * + * @param string $project_path Project path. + * + * @return string[] + * @throws \InvalidArgumentException When relative project path is given. + */ + public function getForks($project_path) + { + if ( strpos($project_path, '/') !== 0 ) { + throw new \InvalidArgumentException('The "$project_path" argument must contain absolute path.'); + } + + $project_path = $this->_databaseDirectory . $project_path; + + if ( !file_exists($project_path) ) { + return array(); + } + + $ret = array(); + $absolute_forks = glob($project_path . '/code_insight-*.sqlite'); + + foreach ( $absolute_forks as $absolute_fork ) { + $ret[] = preg_replace( + '/^' . preg_quote($project_path . '/code_insight-', '/') . '(.+).sqlite/', + '$1', + $absolute_fork + ); + } + + return $ret; + } + /** * Runs outstanding migrations on the database. * diff --git a/src/CodeInsight/KnowledgeBase/KnowledgeBaseFactory.php b/src/CodeInsight/KnowledgeBase/KnowledgeBaseFactory.php index d403b23..68cf5dc 100644 --- a/src/CodeInsight/KnowledgeBase/KnowledgeBaseFactory.php +++ b/src/CodeInsight/KnowledgeBase/KnowledgeBaseFactory.php @@ -58,4 +58,16 @@ public function getKnowledgeBase($project_path, $fork = null, ConsoleIO $io = nu return $knowledge_base; } + /** + * Returns forks for given project. + * + * @param string $project_path Project path. + * + * @return string[] + */ + public function getForks($project_path) + { + return $this->_databaseManager->getForks($project_path); + } + } diff --git a/tests/CodeInsight/KnowledgeBase/DatabaseManagerTest.php b/tests/CodeInsight/KnowledgeBase/DatabaseManagerTest.php index ad991f0..6ffe314 100644 --- a/tests/CodeInsight/KnowledgeBase/DatabaseManagerTest.php +++ b/tests/CodeInsight/KnowledgeBase/DatabaseManagerTest.php @@ -113,6 +113,47 @@ public function testCreatingForkedDatabaseFromOriginal() ); } + public function testRelativeProjectPathForksError() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The "$project_path" argument must contain absolute path.'); + + $this->getDatabaseManager()->getForks('relative/path'); + } + + /** + * @depends testCreatingDatabase + */ + public function testNoForksWithProjectDatabase() + { + $database_manager = $this->getDatabaseManager(); + $database_manager->getDatabase('/absolute/path'); + + $this->assertEmpty($database_manager->getForks('/absolute/path')); + } + + public function testNoForksWithoutProjectDatabase() + { + $database_manager = $this->getDatabaseManager(); + + $this->assertEmpty($database_manager->getForks('/absolute/path')); + } + + public function testGetForks() + { + $database_manager = $this->getDatabaseManager(); + $original_database = $database_manager->getDatabase('/absolute/path'); + $original_database->perform('CREATE TABLE "SampleTableOrg" ("Name" TEXT(255,0) NOT NULL, PRIMARY KEY("Name"))'); + + $db_fork1 = $database_manager->getDatabase('/absolute/path', 'fork1'); + $db_fork1->perform('CREATE TABLE "SampleTableFork1" ("Name" TEXT(255,0) NOT NULL, PRIMARY KEY("Name"))'); + + $db_fork2 = $database_manager->getDatabase('/absolute/path', 'fork2'); + $db_fork2->perform('CREATE TABLE "SampleTableFork2" ("Name" TEXT(255,0) NOT NULL, PRIMARY KEY("Name"))'); + + $this->assertEquals(array('fork1', 'fork2'), $database_manager->getForks('/absolute/path')); + } + /** * Returns database DSN. *