From 24d6c0b2b053b7f3b1db35a69632317abb930d5b Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Sat, 29 May 2021 18:10:32 -0500 Subject: [PATCH 1/9] Add public isEnabled() and getActions() methods --- src/Hook/File/Action/Check.php | 1 + src/Runner/Hook.php | 20 +++ tests/unit/Runner/Hook/PreCommitTest.php | 10 +- tests/unit/Runner/HookTest.php | 203 +++++++++++++++++++++++ 4 files changed, 232 insertions(+), 2 deletions(-) diff --git a/src/Hook/File/Action/Check.php b/src/Hook/File/Action/Check.php index 6a07c01f..527e6d43 100644 --- a/src/Hook/File/Action/Check.php +++ b/src/Hook/File/Action/Check.php @@ -92,6 +92,7 @@ public function execute(Config $config, IO $io, Repository $repository, Config\A * Setup the action, reading and validating all config settings * * @param \CaptainHook\App\Config\Options $options + * @codeCoverageIgnore */ protected function setUp(Config\Options $options): void { diff --git a/src/Runner/Hook.php b/src/Runner/Hook.php index b4c75594..e3121ecf 100644 --- a/src/Runner/Hook.php +++ b/src/Runner/Hook.php @@ -153,6 +153,16 @@ public function getHookConfigsToHandle(): array return $configs; } + /** + * Returns true if this hook or any applicable virtual hooks are enabled + * + * @return bool + */ + public function isEnabled(): bool + { + return $this->isAnyConfigEnabled($this->getHookConfigsToHandle()); + } + /** * @param \CaptainHook\App\Config\Hook[] $configs * @return bool @@ -194,6 +204,16 @@ public function shouldSkipActions(?bool $shouldSkip = null): bool return $this->skipActions; } + /** + * Returns all actions configured for this hook or applicable virtual hooks + * + * @return \CaptainHook\App\Config\Action[] + */ + public function getActions(): array + { + return $this->getActionsToExecute($this->getHookConfigsToHandle()); + } + /** * Return all the actions to execute * diff --git a/tests/unit/Runner/Hook/PreCommitTest.php b/tests/unit/Runner/Hook/PreCommitTest.php index 815efe1f..19648aac 100644 --- a/tests/unit/Runner/Hook/PreCommitTest.php +++ b/tests/unit/Runner/Hook/PreCommitTest.php @@ -82,12 +82,18 @@ public function testRunHookDontFailOnFirstError(): void // so even if the first actions fails this action has to get executed $actionConfigSuccess->expects($this->atLeastOnce()) ->method('getAction') - ->willReturn(CH_PATH_FILES . '/bin/failure'); + ->willReturn(CH_PATH_FILES . '/bin/success'); + + $actionConfigWithReallyLongName = $this->createActionConfigMock(); + $actionConfigWithReallyLongName + ->expects($this->atLeastOnce()) + ->method('getAction') + ->willReturn(CH_PATH_FILES . '/bin/success --really-long-option-name-to-ensure-this-is-over-65-characters'); $hookConfig->expects($this->atLeast(1))->method('isEnabled')->willReturn(true); $hookConfig->expects($this->once()) ->method('getActions') - ->willReturn([$actionConfigFail, $actionConfigSuccess]); + ->willReturn([$actionConfigFail, $actionConfigSuccess, $actionConfigWithReallyLongName]); $config->expects($this->once())->method('getHookConfig')->willReturn($hookConfig); $io->expects($this->atLeast(1))->method('write'); diff --git a/tests/unit/Runner/HookTest.php b/tests/unit/Runner/HookTest.php index 1a099d4a..15b23338 100644 --- a/tests/unit/Runner/HookTest.php +++ b/tests/unit/Runner/HookTest.php @@ -262,4 +262,207 @@ public function testRunHookSkipsActionsFromPluginBeforeAction(): void $this->assertSame(2, DummyHookPlugin::$afterActionCalled); $this->assertSame(1, DummyHookPlugin::$afterHookCalled); } + + public function testGetActionsWithoutVirtualHooks(): void + { + $actionConfig1 = $this->createActionConfigMock(); + $actionConfig1->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success --foo'); + + $actionConfig2 = $this->createActionConfigMock(); + $actionConfig2->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success --bar'); + + $hookConfig = $this->createHookConfigMock(); + $hookConfig->expects($this->atLeast(1))->method('isEnabled')->willReturn(true); + $hookConfig->expects($this->once())->method('getActions')->willReturn([$actionConfig1, $actionConfig2]); + + $config = $this->createConfigMock(); + $config->expects($this->once())->method('getHookConfig')->with('pre-commit')->willReturn($hookConfig); + + $io = $this->createIOMock(); + $repo = $this->createRepositoryMock(); + + $runner = new class ($io, $config, $repo) extends Hook { + protected $hook = Hooks::PRE_COMMIT; + }; + + $this->assertSame([$actionConfig1, $actionConfig2], $runner->getActions()); + } + + public function testGetActionsWithVirtualHooks(): void + { + $actionConfig1 = $this->createActionConfigMock(); + $actionConfig1->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success --foo'); + + $actionConfig2 = $this->createActionConfigMock(); + $actionConfig2->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success --bar'); + + $actionConfig3 = $this->createActionConfigMock(); + $actionConfig3->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success --baz'); + + $hookConfig1 = $this->createHookConfigMock(); + $hookConfig1->expects($this->exactly(2))->method('getName')->willReturn('post-checkout'); + $hookConfig1->expects($this->atLeast(1))->method('isEnabled')->willReturn(true); + $hookConfig1->expects($this->once())->method('getActions')->willReturn([$actionConfig1, $actionConfig2]); + + $hookConfig2 = $this->createHookConfigMock(); + $hookConfig2->expects($this->atLeast(1))->method('isEnabled')->willReturn(true); + $hookConfig2->expects($this->once())->method('getActions')->willReturn([$actionConfig3]); + + $config = $this->createConfigMock(); + $config + ->expects($this->exactly(2)) + ->method('getHookConfig') + ->withConsecutive(['post-checkout'], ['post-change']) + ->willReturn($hookConfig1, $hookConfig2); + + $io = $this->createIOMock(); + $repo = $this->createRepositoryMock(); + + $runner = new class ($io, $config, $repo) extends Hook { + protected $hook = Hooks::POST_CHECKOUT; + }; + + $this->assertSame([$actionConfig1, $actionConfig2, $actionConfig3], $runner->getActions()); + } + + public function testGetActionsReturnsEmptyArrayWhenNoConfigsAreEnabled(): void + { + $actionConfig1 = $this->createActionConfigMock(); + $actionConfig1->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success --foo'); + + $actionConfig2 = $this->createActionConfigMock(); + $actionConfig2->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success --bar'); + + $actionConfig3 = $this->createActionConfigMock(); + $actionConfig3->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success --baz'); + + $hookConfig1 = $this->createHookConfigMock(); + $hookConfig1->expects($this->exactly(2))->method('getName')->willReturn('post-checkout'); + $hookConfig1->expects($this->atLeast(1))->method('isEnabled')->willReturn(false); + + $hookConfig2 = $this->createHookConfigMock(); + $hookConfig2->expects($this->atLeast(1))->method('isEnabled')->willReturn(false); + + $config = $this->createConfigMock(); + $config + ->expects($this->exactly(2)) + ->method('getHookConfig') + ->withConsecutive(['post-checkout'], ['post-change']) + ->willReturn($hookConfig1, $hookConfig2); + + $io = $this->createIOMock(); + $repo = $this->createRepositoryMock(); + + $runner = new class ($io, $config, $repo) extends Hook { + protected $hook = Hooks::POST_CHECKOUT; + }; + + $this->assertSame([], $runner->getActions()); + } + + public function testGetActionReturnsOnlyConfigsThatAreEnabled(): void + { + $actionConfig1 = $this->createActionConfigMock(); + $actionConfig1->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success --foo'); + + $actionConfig2 = $this->createActionConfigMock(); + $actionConfig2->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success --bar'); + + $actionConfig3 = $this->createActionConfigMock(); + $actionConfig3->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success --baz'); + + $hookConfig1 = $this->createHookConfigMock(); + $hookConfig1->expects($this->exactly(2))->method('getName')->willReturn('post-checkout'); + $hookConfig1->expects($this->atLeast(1))->method('isEnabled')->willReturn(false); + + $hookConfig2 = $this->createHookConfigMock(); + $hookConfig2->expects($this->atLeast(1))->method('isEnabled')->willReturn(true); + $hookConfig2->expects($this->once())->method('getActions')->willReturn([$actionConfig3]); + + $config = $this->createConfigMock(); + $config + ->expects($this->exactly(2)) + ->method('getHookConfig') + ->withConsecutive(['post-checkout'], ['post-change']) + ->willReturn($hookConfig1, $hookConfig2); + + $io = $this->createIOMock(); + $repo = $this->createRepositoryMock(); + + $runner = new class ($io, $config, $repo) extends Hook { + protected $hook = Hooks::POST_CHECKOUT; + }; + + $this->assertSame([$actionConfig3], $runner->getActions()); + } + + public function testIsEnabledReturnsFalseWhenNoConfigsAreEnabled(): void + { + $actionConfig1 = $this->createActionConfigMock(); + $actionConfig1->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success --foo'); + + $actionConfig2 = $this->createActionConfigMock(); + $actionConfig2->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success --bar'); + + $actionConfig3 = $this->createActionConfigMock(); + $actionConfig3->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success --baz'); + + $hookConfig1 = $this->createHookConfigMock(); + $hookConfig1->expects($this->exactly(2))->method('getName')->willReturn('post-checkout'); + $hookConfig1->expects($this->atLeast(1))->method('isEnabled')->willReturn(false); + + $hookConfig2 = $this->createHookConfigMock(); + $hookConfig2->expects($this->atLeast(1))->method('isEnabled')->willReturn(false); + + $config = $this->createConfigMock(); + $config + ->expects($this->exactly(2)) + ->method('getHookConfig') + ->withConsecutive(['post-checkout'], ['post-change']) + ->willReturn($hookConfig1, $hookConfig2); + + $io = $this->createIOMock(); + $repo = $this->createRepositoryMock(); + + $runner = new class ($io, $config, $repo) extends Hook { + protected $hook = Hooks::POST_CHECKOUT; + }; + + $this->assertFalse($runner->isEnabled()); + } + + public function testIsEnabledReturnsTrueWhenAtLeastOneConfigIsEnabled(): void + { + $actionConfig1 = $this->createActionConfigMock(); + $actionConfig1->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success --foo'); + + $actionConfig2 = $this->createActionConfigMock(); + $actionConfig2->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success --bar'); + + $actionConfig3 = $this->createActionConfigMock(); + $actionConfig3->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success --baz'); + + $hookConfig1 = $this->createHookConfigMock(); + $hookConfig1->expects($this->exactly(2))->method('getName')->willReturn('post-checkout'); + $hookConfig1->expects($this->atLeast(1))->method('isEnabled')->willReturn(false); + + $hookConfig2 = $this->createHookConfigMock(); + $hookConfig2->expects($this->atLeast(1))->method('isEnabled')->willReturn(true); + + $config = $this->createConfigMock(); + $config + ->expects($this->exactly(2)) + ->method('getHookConfig') + ->withConsecutive(['post-checkout'], ['post-change']) + ->willReturn($hookConfig1, $hookConfig2); + + $io = $this->createIOMock(); + $repo = $this->createRepositoryMock(); + + $runner = new class ($io, $config, $repo) extends Hook { + protected $hook = Hooks::POST_CHECKOUT; + }; + + $this->assertTrue($runner->isEnabled()); + } } From 6ea20301e957623a95d0d640ab0abe2e9b4ce213 Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Sat, 29 May 2021 18:57:31 -0500 Subject: [PATCH 2/9] Add --list-actions option to hook commands --- src/Console/Command/Hook.php | 63 +++++++++++++++++++ .../config/valid-with-all-disabled-hooks.json | 2 + .../Console/Command/Hook/PreCommitTest.php | 61 ++++++++++++++++++ .../Command/Hook/PrepareCommitMsgTest.php | 32 ++++++++++ 4 files changed, 158 insertions(+) create mode 100644 tests/files/config/valid-with-all-disabled-hooks.json diff --git a/src/Console/Command/Hook.php b/src/Console/Command/Hook.php index 69aa538e..3d8cc5e3 100644 --- a/src/Console/Command/Hook.php +++ b/src/Console/Command/Hook.php @@ -14,6 +14,7 @@ use CaptainHook\App\Config; use CaptainHook\App\Console\IOUtil; use CaptainHook\App\Hook\Util; +use CaptainHook\App\Runner\Hook as RunnerHook; use Exception; use RuntimeException; use Symfony\Component\Console\Input\InputInterface; @@ -55,6 +56,36 @@ protected function configure(): void InputOption::VALUE_OPTIONAL, 'Relative path from your config file to your bootstrap file' ); + + $this->addOption( + 'list-actions', + 'l', + InputOption::VALUE_NONE, + 'List actions for this hook without running the hook' + ); + } + + /** + * Initialize the command by checking/modifying inputs before validation + * + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return void + */ + protected function initialize(InputInterface $input, OutputInterface $output): void + { + // If `--list-actions` is present, we will ignore any arguments, since + // this option intends to output a list of actions for the hook without + // running the hook. So, if any arguments are required but not present + // in the input, we will set them to an empty string in the input to + // suppress any validation errors. + if ($input->getOption('list-actions') === true) { + foreach ($this->getDefinition()->getArguments() as $arg) { + if ($arg->isRequired() && $input->getArgument($arg->getName()) === null) { + $input->setArgument($arg->getName(), ''); + } + } + } } /** @@ -83,6 +114,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** @var \CaptainHook\App\Runner\Hook $hook */ $hook = new $class($io, $config, $repository); + // If list-actions is true, then list the hook actions instead of running them. + if ($input->getOption('list-actions') === true) { + $this->listActions($output, $hook); + return 0; + } + try { $hook->run(); return 0; @@ -128,4 +165,30 @@ private function handleError(OutputInterface $output, Exception $e): int return 1; } + + /** + * Print out a list of actions for this hook + * + * @param OutputInterface $output + * @param RunnerHook $hook + */ + private function listActions(OutputInterface $output, RunnerHook $hook): void + { + $output->writeln('Listing ' . $hook->getName() . ' actions:'); + + if (!$hook->isEnabled()) { + $output->writeln(' - hook is disabled'); + return; + } + + $actions = $hook->getActions(); + if (count($actions) === 0) { + $output->writeln(' - no actions configured'); + return; + } + + foreach ($actions as $action) { + $output->writeln(" - {$action->getAction()}"); + } + } } diff --git a/tests/files/config/valid-with-all-disabled-hooks.json b/tests/files/config/valid-with-all-disabled-hooks.json new file mode 100644 index 00000000..2c63c085 --- /dev/null +++ b/tests/files/config/valid-with-all-disabled-hooks.json @@ -0,0 +1,2 @@ +{ +} diff --git a/tests/unit/Console/Command/Hook/PreCommitTest.php b/tests/unit/Console/Command/Hook/PreCommitTest.php index 11b1f1db..7b4de9a0 100644 --- a/tests/unit/Console/Command/Hook/PreCommitTest.php +++ b/tests/unit/Console/Command/Hook/PreCommitTest.php @@ -11,12 +11,14 @@ namespace CaptainHook\App\Console\Command\Hook; +use CaptainHook\App\Console\Command; use CaptainHook\App\Console\Runtime\Resolver; use CaptainHook\App\Git\DummyRepo; use Exception; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\NullOutput; use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Output\Output; class PreCommitTest extends TestCase { @@ -150,4 +152,63 @@ public function testExecuteFailingActionInVerboseMode(): void $cmd = new PreCommit($resolver); $this->assertEquals(1, $cmd->run($input, $output)); } + + public function testListActionsPrintsActions(): void + { + $output = $this->getMockBuilder(Output::class) + ->disableOriginalConstructor() + ->getMock(); + + $output + ->expects($this->exactly(3)) + ->method('writeln') + ->withConsecutive( + ['Listing pre-commit actions:'], + [' - phpcs --standard=psr2 src'], + [' - phpunit --configuration=build/phpunit-hook.xml'] + ); + + $repo = new DummyRepo(); + $input = new ArrayInput( + [ + '--configuration' => CH_PATH_FILES . '/config/valid-with-includes.json', + '--git-directory' => $repo->getGitDir(), + '--list-actions' => true, + ] + ); + + $cmd = new PreCommit(new Resolver()); + $cmd->run($input, $output); + + $this->assertTrue(true); + } + + public function testListActionsForDisabledHook(): void + { + $output = $this->getMockBuilder(Output::class) + ->disableOriginalConstructor() + ->getMock(); + + $output + ->expects($this->exactly(2)) + ->method('writeln') + ->withConsecutive( + ['Listing pre-commit actions:'], + [' - hook is disabled'] + ); + + $repo = new DummyRepo(); + $input = new ArrayInput( + [ + '--configuration' => CH_PATH_FILES . '/config/valid-with-all-disabled-hooks.json', + '--git-directory' => $repo->getGitDir(), + '--list-actions' => true, + ] + ); + + $cmd = new PreCommit(new Resolver()); + $cmd->run($input, $output); + + $this->assertTrue(true); + } } diff --git a/tests/unit/Console/Command/Hook/PrepareCommitMsgTest.php b/tests/unit/Console/Command/Hook/PrepareCommitMsgTest.php index 216f84c4..e9a34f51 100644 --- a/tests/unit/Console/Command/Hook/PrepareCommitMsgTest.php +++ b/tests/unit/Console/Command/Hook/PrepareCommitMsgTest.php @@ -11,11 +11,14 @@ namespace CaptainHook\App\Console\Command\Hook; +use CaptainHook\App\Console\Command; +use CaptainHook\App\Console\IO\DefaultIO; use CaptainHook\App\Console\Runtime\Resolver; use CaptainHook\App\Git\DummyRepo; use Symfony\Component\Console\Input\ArrayInput; use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Output\NullOutput; +use Symfony\Component\Console\Output\Output; class PrepareCommitMsgTest extends TestCase { @@ -42,4 +45,33 @@ public function testExecute(): void $this->assertTrue(true); } + + public function testListActionsDisablesRequiredArguments(): void + { + $output = $this->getMockBuilder(Output::class) + ->disableOriginalConstructor() + ->getMock(); + + $output + ->expects($this->exactly(2)) + ->method('writeln') + ->withConsecutive( + ['Listing prepare-commit-msg actions:'], + [' - no actions configured'] + ); + + $repo = new DummyRepo(); + $input = new ArrayInput( + [ + '--configuration' => CH_PATH_FILES . '/config/valid.json', + '--git-directory' => $repo->getGitDir(), + '--list-actions' => true, + ] + ); + + $cmd = new PrepareCommitMsg(new Resolver()); + $cmd->run($input, $output); + + $this->assertTrue(true); + } } From e65c88c84cddd9cf884606dbe06a69c2b798d332 Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Sun, 30 May 2021 17:36:53 -0500 Subject: [PATCH 3/9] Add getOptions() and getOption() to the IO classes --- src/Console/IO/Base.php | 22 ++++++++++++++++++ src/Console/IO/DefaultIO.php | 22 ++++++++++++++++++ tests/unit/Console/IO/DefaultIOTest.php | 30 +++++++++++++++++++++++++ tests/unit/Console/IO/NullIOTest.php | 19 ++++++++++++++++ 4 files changed, 93 insertions(+) diff --git a/src/Console/IO/Base.php b/src/Console/IO/Base.php index 85949c3b..fc351c1a 100644 --- a/src/Console/IO/Base.php +++ b/src/Console/IO/Base.php @@ -45,6 +45,28 @@ public function getArgument(string $name, string $default = ''): string return $default; } + /** + * Return the original cli options + * + * @return array + */ + public function getOptions(): array + { + return []; + } + + /** + * Return the original cli option or a given default + * + * @param string $name + * @param string|string[]|bool|null $default + * @return string|string[]|bool|null + */ + public function getOption(string $name, $default = null) + { + return $default; + } + /** * Return the piped in standard input * diff --git a/src/Console/IO/DefaultIO.php b/src/Console/IO/DefaultIO.php index 5a4a95b3..63672446 100644 --- a/src/Console/IO/DefaultIO.php +++ b/src/Console/IO/DefaultIO.php @@ -103,6 +103,28 @@ public function getArgument(string $name, string $default = ''): string return (string)($this->getArguments()[$name] ?? $default); } + /** + * Return the original cli options + * + * @return array + */ + public function getOptions(): array + { + return $this->input->getOptions(); + } + + /** + * Return the original cli option or a given default + * + * @param string $name + * @param string|string[]|bool|null $default + * @return string|string[]|bool|null + */ + public function getOption(string $name, $default = null) + { + return $this->getOptions()[$name] ?? $default; + } + /** * Return the piped in standard input * diff --git a/tests/unit/Console/IO/DefaultIOTest.php b/tests/unit/Console/IO/DefaultIOTest.php index 1e6b3d04..a88d1bf3 100644 --- a/tests/unit/Console/IO/DefaultIOTest.php +++ b/tests/unit/Console/IO/DefaultIOTest.php @@ -104,6 +104,36 @@ public function testGetArgument(): void $this->assertEquals('bar', $io->getArgument('fiz', 'bar')); } + /** + * Tests DefaultIO::getOptions + */ + public function testGetOptions(): void + { + $input = $this->getInputMock(); + $output = $this->getOutputMock(); + $helper = $this->getHelperSetMock(); + + $input->expects($this->once())->method('getOptions')->willReturn(['foo' => 'bar']); + $io = new DefaultIO($this->fakeStdIn(), $input, $output, $helper); + + $this->assertEquals(['foo' => 'bar'], $io->getOptions()); + } + + /** + * Tests DefaultIO::getOption + */ + public function testGetOption(): void + { + $input = $this->getInputMock(); + $output = $this->getOutputMock(); + $helper = $this->getHelperSetMock(); + + $input->expects($this->exactly(2))->method('getOptions')->willReturn(['foo' => 'bar']); + $io = new DefaultIO($this->fakeStdIn(), $input, $output, $helper); + + $this->assertEquals('bar', $io->getOption('foo')); + $this->assertEquals('bar', $io->getOption('fiz', 'bar')); + } /** * Tests DefaultIO::getStandardInput diff --git a/tests/unit/Console/IO/NullIOTest.php b/tests/unit/Console/IO/NullIOTest.php index fa0dece9..20f0f591 100644 --- a/tests/unit/Console/IO/NullIOTest.php +++ b/tests/unit/Console/IO/NullIOTest.php @@ -34,6 +34,25 @@ public function testGetArgument(): void $this->assertEquals('bar', $io->getArgument('foo', 'bar')); } + /** + * Tests NullIO::getOptions + */ + public function testGetOptions(): void + { + $io = new NullIO(); + $this->assertEquals([], $io->getOptions()); + } + + /** + * Tests NullIO::getOption + */ + public function testGetOption(): void + { + $io = new NullIO(); + $this->assertEquals('', $io->getOption('foo')); + $this->assertEquals('bar', $io->getOption('foo', 'bar')); + } + /** * Tests NullIO::getStandardInput */ From 163f38033113eeee88f3939b22e56753c49afc6c Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Sun, 30 May 2021 17:38:36 -0500 Subject: [PATCH 4/9] Allow disabling plugins from the command line --- src/Console/Command/Hook.php | 7 ++++++ src/Runner/Hook.php | 8 ++++++ tests/unit/Runner/HookTest.php | 45 ++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/src/Console/Command/Hook.php b/src/Console/Command/Hook.php index 3d8cc5e3..d00cae7e 100644 --- a/src/Console/Command/Hook.php +++ b/src/Console/Command/Hook.php @@ -63,6 +63,13 @@ protected function configure(): void InputOption::VALUE_NONE, 'List actions for this hook without running the hook' ); + + $this->addOption( + 'disable-plugins', + null, + InputOption::VALUE_NONE, + 'Disable all hook plugins' + ); } /** diff --git a/src/Runner/Hook.php b/src/Runner/Hook.php index e3121ecf..0fb87d58 100644 --- a/src/Runner/Hook.php +++ b/src/Runner/Hook.php @@ -116,6 +116,10 @@ public function run(): void $this->io->write('' . $this->hook . ': '); + if ($this->io->getOption('disable-plugins')) { + $this->io->write('Running with plugins disabled'); + } + // if the hook and all triggered virtual hooks // are NOT enabled in the captainhook configuration skip the execution if (!$this->isAnyConfigEnabled($hookConfigs)) { @@ -469,6 +473,10 @@ private function getHookPlugins(): array */ private function executeHookPluginsFor(string $method, ?Config\Action $action = null): void { + if ($this->io->getOption('disable-plugins')) { + return; + } + $plugins = $this->getHookPlugins(); if (count($plugins) === 0) { diff --git a/tests/unit/Runner/HookTest.php b/tests/unit/Runner/HookTest.php index 15b23338..0f7f919e 100644 --- a/tests/unit/Runner/HookTest.php +++ b/tests/unit/Runner/HookTest.php @@ -13,6 +13,7 @@ use CaptainHook\App\Config; use CaptainHook\App\Config\Mockery as ConfigMockery; +use CaptainHook\App\Console\IO; use CaptainHook\App\Console\IO\Mockery as IOMockery; use CaptainHook\App\Hook\Restriction; use CaptainHook\App\Hooks; @@ -465,4 +466,48 @@ public function testIsEnabledReturnsTrueWhenAtLeastOneConfigIsEnabled(): void $this->assertTrue($runner->isEnabled()); } + + public function testRunHookWhenPluginsAreDisabled(): void + { + $pluginConfig1 = new Config\Plugin(DummyHookPlugin::class); + $pluginConfig2 = new Config\Plugin(DummyHookPlugin::class); + + $config = $this->createConfigMock(); + $config->method('failOnFirstError')->willReturn(false); + $config->method('getPlugins')->willReturn([$pluginConfig1, $pluginConfig2]); + + $io = $this->createIOMock(); + $repo = $this->createRepositoryMock(); + $hookConfig = $this->createHookConfigMock(); + $actionConfig = $this->createActionConfigMock(); + $actionConfig->expects($this->atLeastOnce())->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success'); + $hookConfig->expects($this->atLeastOnce())->method('isEnabled')->willReturn(true); + $hookConfig->expects($this->once())->method('getActions')->willReturn([$actionConfig, clone $actionConfig]); + $config->expects($this->once())->method('getHookConfig')->willReturn($hookConfig); + $io->expects($this->atLeastOnce())->method('getOption')->with('disable-plugins')->willReturn(true); + + $io + ->expects($this->exactly(8)) + ->method('write') + ->withConsecutive( + ['pre-commit: '], + ['Running with plugins disabled'], + [' - ' . CH_PATH_FILES . '/bin/success : ', false], + [['', 'foo', ''], true, IO::VERBOSE], + ['done'], + [' - ' . CH_PATH_FILES . '/bin/success : ', false], + [['', 'foo', ''], true, IO::VERBOSE], + ['done'] + ); + + $runner = new class ($io, $config, $repo) extends Hook { + protected $hook = Hooks::PRE_COMMIT; + }; + $runner->run(); + + $this->assertSame(0, DummyHookPlugin::$beforeHookCalled); + $this->assertSame(0, DummyHookPlugin::$beforeActionCalled); + $this->assertSame(0, DummyHookPlugin::$afterActionCalled); + $this->assertSame(0, DummyHookPlugin::$afterHookCalled); + } } From e29570ed99f9714e57543344878f488d33540337 Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Sun, 30 May 2021 17:38:58 -0500 Subject: [PATCH 5/9] Allow setting verbosity from CLI to override configuration --- src/Console/Command/Hook.php | 8 ++++++-- src/Runner/Hook.php | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Console/Command/Hook.php b/src/Console/Command/Hook.php index d00cae7e..fa5f6ecc 100644 --- a/src/Console/Command/Hook.php +++ b/src/Console/Command/Hook.php @@ -114,8 +114,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int // use ansi coloring only if not disabled in captainhook.json $output->setDecorated($config->useAnsiColors()); - // use the configured verbosity to manage general output verbosity - $output->setVerbosity(IOUtil::mapConfigVerbosity($config->getVerbosity())); + + // If the verbose option is present on the command line, then use it. + // Otherwise, use the verbosity setting from the configuration. + if (!$input->hasOption('verbose') || !$input->getOption('verbose')) { + $output->setVerbosity(IOUtil::mapConfigVerbosity($config->getVerbosity())); + } $class = '\\CaptainHook\\App\\Runner\\Hook\\' . Util::getHookCommand($this->hookName); /** @var \CaptainHook\App\Runner\Hook $hook */ diff --git a/src/Runner/Hook.php b/src/Runner/Hook.php index 0fb87d58..e0a3b3a2 100644 --- a/src/Runner/Hook.php +++ b/src/Runner/Hook.php @@ -437,7 +437,7 @@ private function getHookPlugins(): array } $this->io->write( - ['', 'Configuring Hook Plugin: ' . $pluginClass . ''], + ['Configured Hook Plugin: ' . $pluginClass . ''], true, IO::VERBOSE ); @@ -494,7 +494,7 @@ private function executeHookPluginsFor(string $method, ?Config\Action $action = $this->io->write(['', 'Executing plugins for: ' . $method . ''], true, IO::DEBUG); foreach ($plugins as $plugin) { - $this->io->write('- Running ' . get_class($plugin) . '::' . $method . '', true, IO::DEBUG); + $this->io->write(' - Running ' . get_class($plugin) . '::' . $method . '', true, IO::DEBUG); $plugin->{$method}(...$params); } } From e41340969ddff89f2395e4e92d9c794d9925d7f2 Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Sun, 30 May 2021 19:33:16 -0500 Subject: [PATCH 6/9] Run only actions passed on the command line --- src/Console/Command/Hook.php | 7 +++ src/Runner/Hook.php | 58 ++++++++++++++++++---- tests/unit/Runner/HookTest.php | 89 +++++++++++++++++++++++++++++++++- 3 files changed, 143 insertions(+), 11 deletions(-) diff --git a/src/Console/Command/Hook.php b/src/Console/Command/Hook.php index fa5f6ecc..a8f13545 100644 --- a/src/Console/Command/Hook.php +++ b/src/Console/Command/Hook.php @@ -64,6 +64,13 @@ protected function configure(): void 'List actions for this hook without running the hook' ); + $this->addOption( + 'action', + 'a', + InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, + 'Run only the actions listed' + ); + $this->addOption( 'disable-plugins', null, diff --git a/src/Runner/Hook.php b/src/Runner/Hook.php index e0a3b3a2..8b5cba14 100644 --- a/src/Runner/Hook.php +++ b/src/Runner/Hook.php @@ -307,18 +307,9 @@ private function executeFailAfterAllActions(array $actions): void */ private function handleAction(Config\Action $action): void { - if ($this->shouldSkipActions()) { - $this->io->write( - $this->formatActionOutput($action->getAction()) . ': deactivated', - true - ); - return; - } - $this->io->write(' - ' . $this->formatActionOutput($action->getAction()) . ' : ', false); - if (!$this->doConditionsApply($action->getConditions())) { - $this->io->write('skipped', true); + if ($this->checkSkipAction($action)) { return; } @@ -327,6 +318,7 @@ private function handleAction(Config\Action $action): void // The beforeAction() method may indicate that the current and all // remaining actions should be skipped. If so, return here. if ($this->shouldSkipActions()) { + $this->io->write('deactivated', true); return; } @@ -384,6 +376,52 @@ public static function getExecMethod(string $type): string return $valid[$type]; } + /** + * Check if the action should be skipped + * + * @param Config\Action $action + * @return bool + */ + private function checkSkipAction(Config\Action $action): bool + { + if ( + $this->shouldSkipActions() + || $this->cliSkipAction($action) + || !$this->doConditionsApply($action->getConditions()) + ) { + $this->io->write('skipped', true); + + return true; + } + + return false; + } + + /** + * Check if the CLI `action` options indicates the action should be skipped + * + * @param Config\Action $action + * @return bool + */ + private function cliSkipAction(Config\Action $action): bool + { + /** @var string[] $actionsToRun */ + $actionsToRun = $this->io->getOption('action', []); + + if (empty($actionsToRun)) { + // No actions specified on CLI; run all actions. + return false; + } + + if (in_array($action->getAction(), $actionsToRun)) { + // Action specified on CLI; do not skip. + return false; + } + + // Action not specified on CLI; skip. + return true; + } + /** * Check if conditions apply * diff --git a/tests/unit/Runner/HookTest.php b/tests/unit/Runner/HookTest.php index 0f7f919e..829c99eb 100644 --- a/tests/unit/Runner/HookTest.php +++ b/tests/unit/Runner/HookTest.php @@ -25,6 +25,7 @@ use CaptainHook\App\Plugin\DummyHookPlugin; use CaptainHook\App\Plugin\DummyHookPluginSkipsActions; use Exception; +use InvalidArgumentException; use PHPUnit\Framework\TestCase; class HookTest extends TestCase @@ -476,6 +477,17 @@ public function testRunHookWhenPluginsAreDisabled(): void $config->method('failOnFirstError')->willReturn(false); $config->method('getPlugins')->willReturn([$pluginConfig1, $pluginConfig2]); + $optionCallback = function (string $option) { + switch ($option) { + case 'disable-plugins': + return true; + case 'action': + return []; + default: + throw new InvalidArgumentException('Received invalid option: ' . $option); + } + }; + $io = $this->createIOMock(); $repo = $this->createRepositoryMock(); $hookConfig = $this->createHookConfigMock(); @@ -484,7 +496,14 @@ public function testRunHookWhenPluginsAreDisabled(): void $hookConfig->expects($this->atLeastOnce())->method('isEnabled')->willReturn(true); $hookConfig->expects($this->once())->method('getActions')->willReturn([$actionConfig, clone $actionConfig]); $config->expects($this->once())->method('getHookConfig')->willReturn($hookConfig); - $io->expects($this->atLeastOnce())->method('getOption')->with('disable-plugins')->willReturn(true); + + $io + ->method('getOption') + ->with($this->logicalOr( + $this->equalTo('disable-plugins'), + $this->equalTo('action') + )) + ->willReturn($this->returnCallback($optionCallback)); $io ->expects($this->exactly(8)) @@ -510,4 +529,72 @@ public function testRunHookWhenPluginsAreDisabled(): void $this->assertSame(0, DummyHookPlugin::$afterActionCalled); $this->assertSame(0, DummyHookPlugin::$afterHookCalled); } + + public function testRunHookWhenActionsSpecifiedOnCli(): void + { + $optionCallback = function (string $option) { + switch ($option) { + case 'disable-plugins': + return true; + case 'action': + return [CH_PATH_FILES . '/bin/success']; + default: + throw new InvalidArgumentException('Received invalid option: ' . $option); + } + }; + + $repo = $this->createRepositoryMock(); + + $actionSuccessConfig = $this->createActionConfigMock(); + $actionSuccessConfig + ->expects($this->atLeastOnce()) + ->method('getAction') + ->willReturn(CH_PATH_FILES . '/bin/success'); + + $actionFailureConfig = $this->createActionConfigMock(); + $actionFailureConfig + ->expects($this->atLeastOnce()) + ->method('getAction') + ->willReturn(CH_PATH_FILES . '/bin/failure'); + + $hookConfig = $this->createHookConfigMock(); + $hookConfig->expects($this->atLeastOnce())->method('isEnabled')->willReturn(true); + $hookConfig + ->expects($this->once()) + ->method('getActions') + ->willReturn([$actionSuccessConfig, $actionFailureConfig]); + + $config = $this->createConfigMock(); + $config->method('failOnFirstError')->willReturn(false); + $config->method('getPlugins')->willReturn([]); + $config->expects($this->once())->method('getHookConfig')->willReturn($hookConfig); + + $io = $this->createIOMock(); + + $io + ->method('getOption') + ->with($this->logicalOr( + $this->equalTo('disable-plugins'), + $this->equalTo('action') + )) + ->willReturn($this->returnCallback($optionCallback)); + + $io + ->expects($this->exactly(7)) + ->method('write') + ->withConsecutive( + ['pre-commit: '], + ['Running with plugins disabled'], + [' - ' . CH_PATH_FILES . '/bin/success : ', false], + [['', 'foo', ''], true, IO::VERBOSE], + ['done'], + [' - ' . CH_PATH_FILES . '/bin/failure : ', false], + ['skipped'] + ); + + $runner = new class ($io, $config, $repo) extends Hook { + protected $hook = Hooks::PRE_COMMIT; + }; + $runner->run(); + } } From 3b64e0d1917a6748f8af710754200d3006566546 Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Sun, 30 May 2021 20:13:00 -0500 Subject: [PATCH 7/9] Ensure action output in tests is consistent with expected output --- tests/unit/Runner/HookTest.php | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/tests/unit/Runner/HookTest.php b/tests/unit/Runner/HookTest.php index 829c99eb..7bf132d0 100644 --- a/tests/unit/Runner/HookTest.php +++ b/tests/unit/Runner/HookTest.php @@ -470,6 +470,8 @@ public function testIsEnabledReturnsTrueWhenAtLeastOneConfigIsEnabled(): void public function testRunHookWhenPluginsAreDisabled(): void { + $successProgram = CH_PATH_FILES . '/bin/success'; + $pluginConfig1 = new Config\Plugin(DummyHookPlugin::class); $pluginConfig2 = new Config\Plugin(DummyHookPlugin::class); @@ -492,7 +494,7 @@ public function testRunHookWhenPluginsAreDisabled(): void $repo = $this->createRepositoryMock(); $hookConfig = $this->createHookConfigMock(); $actionConfig = $this->createActionConfigMock(); - $actionConfig->expects($this->atLeastOnce())->method('getAction')->willReturn(CH_PATH_FILES . '/bin/success'); + $actionConfig->expects($this->atLeastOnce())->method('getAction')->willReturn($successProgram); $hookConfig->expects($this->atLeastOnce())->method('isEnabled')->willReturn(true); $hookConfig->expects($this->once())->method('getActions')->willReturn([$actionConfig, clone $actionConfig]); $config->expects($this->once())->method('getHookConfig')->willReturn($hookConfig); @@ -511,10 +513,10 @@ public function testRunHookWhenPluginsAreDisabled(): void ->withConsecutive( ['pre-commit: '], ['Running with plugins disabled'], - [' - ' . CH_PATH_FILES . '/bin/success : ', false], + [' - ' . $this->formatActionOutput($successProgram) . ' : ', false], [['', 'foo', ''], true, IO::VERBOSE], ['done'], - [' - ' . CH_PATH_FILES . '/bin/success : ', false], + [' - ' . $this->formatActionOutput($successProgram) . ' : ', false], [['', 'foo', ''], true, IO::VERBOSE], ['done'] ); @@ -532,6 +534,9 @@ public function testRunHookWhenPluginsAreDisabled(): void public function testRunHookWhenActionsSpecifiedOnCli(): void { + $successProgram = CH_PATH_FILES . '/bin/success'; + $failureProgram = CH_PATH_FILES . '/bin/failure'; + $optionCallback = function (string $option) { switch ($option) { case 'disable-plugins': @@ -549,13 +554,13 @@ public function testRunHookWhenActionsSpecifiedOnCli(): void $actionSuccessConfig ->expects($this->atLeastOnce()) ->method('getAction') - ->willReturn(CH_PATH_FILES . '/bin/success'); + ->willReturn($successProgram); $actionFailureConfig = $this->createActionConfigMock(); $actionFailureConfig ->expects($this->atLeastOnce()) ->method('getAction') - ->willReturn(CH_PATH_FILES . '/bin/failure'); + ->willReturn($failureProgram); $hookConfig = $this->createHookConfigMock(); $hookConfig->expects($this->atLeastOnce())->method('isEnabled')->willReturn(true); @@ -585,10 +590,10 @@ public function testRunHookWhenActionsSpecifiedOnCli(): void ->withConsecutive( ['pre-commit: '], ['Running with plugins disabled'], - [' - ' . CH_PATH_FILES . '/bin/success : ', false], + [' - ' . $this->formatActionOutput($successProgram) . ' : ', false], [['', 'foo', ''], true, IO::VERBOSE], ['done'], - [' - ' . CH_PATH_FILES . '/bin/failure : ', false], + [' - ' . $this->formatActionOutput($failureProgram) . ' : ', false], ['skipped'] ); @@ -597,4 +602,14 @@ public function testRunHookWhenActionsSpecifiedOnCli(): void }; $runner->run(); } + + private function formatActionOutput(string $action): string + { + $actionLength = 65; + if (mb_strlen($action) < $actionLength) { + return str_pad($action, $actionLength, ' '); + } + + return mb_substr($action, 0, $actionLength - 3) . '...'; + } } From 6acf735604d704450b3c304d5c186c6abd810c2a Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Sun, 30 May 2021 20:17:45 -0500 Subject: [PATCH 8/9] Hint to PHPStan to use IO\Base for getOption() --- src/Runner/Hook.php | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/Runner/Hook.php b/src/Runner/Hook.php index 8b5cba14..98097d02 100644 --- a/src/Runner/Hook.php +++ b/src/Runner/Hook.php @@ -114,16 +114,19 @@ public function run(): void { $hookConfigs = $this->getHookConfigsToHandle(); - $this->io->write('' . $this->hook . ': '); + /** @var IO\Base $io */ + $io = $this->io; - if ($this->io->getOption('disable-plugins')) { - $this->io->write('Running with plugins disabled'); + $io->write('' . $this->hook . ': '); + + if ($io->getOption('disable-plugins')) { + $io->write('Running with plugins disabled'); } // if the hook and all triggered virtual hooks // are NOT enabled in the captainhook configuration skip the execution if (!$this->isAnyConfigEnabled($hookConfigs)) { - $this->io->write(' - hook is disabled'); + $io->write(' - hook is disabled'); return; } @@ -132,7 +135,7 @@ public function run(): void $actions = $this->getActionsToExecute($hookConfigs); // are any actions configured if (count($actions) === 0) { - $this->io->write(' - no actions to execute', true); + $io->write(' - no actions to execute', true); } else { $this->executeActions($actions); } @@ -405,8 +408,11 @@ private function checkSkipAction(Config\Action $action): bool */ private function cliSkipAction(Config\Action $action): bool { + /** @var IO\Base $io */ + $io = $this->io; + /** @var string[] $actionsToRun */ - $actionsToRun = $this->io->getOption('action', []); + $actionsToRun = $io->getOption('action', []); if (empty($actionsToRun)) { // No actions specified on CLI; run all actions. @@ -511,14 +517,17 @@ private function getHookPlugins(): array */ private function executeHookPluginsFor(string $method, ?Config\Action $action = null): void { - if ($this->io->getOption('disable-plugins')) { + /** @var IO\Base $io */ + $io = $this->io; + + if ($io->getOption('disable-plugins')) { return; } $plugins = $this->getHookPlugins(); if (count($plugins) === 0) { - $this->io->write(['', 'No plugins to execute for: ' . $method . ''], true, IO::DEBUG); + $io->write(['', 'No plugins to execute for: ' . $method . ''], true, IO::DEBUG); return; } @@ -529,10 +538,10 @@ private function executeHookPluginsFor(string $method, ?Config\Action $action = $params[] = $action; } - $this->io->write(['', 'Executing plugins for: ' . $method . ''], true, IO::DEBUG); + $io->write(['', 'Executing plugins for: ' . $method . ''], true, IO::DEBUG); foreach ($plugins as $plugin) { - $this->io->write(' - Running ' . get_class($plugin) . '::' . $method . '', true, IO::DEBUG); + $io->write(' - Running ' . get_class($plugin) . '::' . $method . '', true, IO::DEBUG); $plugin->{$method}(...$params); } } From c23b6ede2ed6af5e14acb486956d1645e286675a Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Tue, 21 Dec 2021 15:34:23 -0600 Subject: [PATCH 9/9] commit wip --- src/Plugin/Hook/DisallowActionChanges.php | 81 +++++++++++++++++++ src/Plugin/Hook/Example.php | 56 +++++++++++++ .../Hook/Exception/ActionChangedFiles.php | 27 +++++++ 3 files changed, 164 insertions(+) create mode 100644 src/Plugin/Hook/DisallowActionChanges.php create mode 100644 src/Plugin/Hook/Example.php create mode 100644 src/Plugin/Hook/Exception/ActionChangedFiles.php diff --git a/src/Plugin/Hook/DisallowActionChanges.php b/src/Plugin/Hook/DisallowActionChanges.php new file mode 100644 index 00000000..ab069574 --- /dev/null +++ b/src/Plugin/Hook/DisallowActionChanges.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace CaptainHook\App\Plugin\Hook; + +use CaptainHook\App\Config; +use CaptainHook\App\Plugin; +use CaptainHook\App\Plugin\Hook\Exception\ActionChangedFiles; +use CaptainHook\App\Runner\Hook as RunnerHook; +use SebastianFeldmann\Git\Diff\File; + +/** + * DisallowActionChanges runner plugin + * + * @package CaptainHook + * @author Sebastian Feldmann + * @link https://github.com/captainhookphp/captainhook + * @since Class available since Release 5.11.0. + */ +class DisallowActionChanges extends PreserveWorkingTree implements Plugin\Hook +{ + /** + * @var iterable + */ + private $priorDiff = []; + + /** + * An array of actions that made changes to files. Each action name is the + * key for an array of file changes made by that action. + * + * @var array + */ + private $actionChanges = []; + + public function beforeHook(RunnerHook $hook): void + { + parent::beforeHook($hook); + + // Get a diff of the current state of the working tree. Since we ran + // the parent beforeHook(), which moves changes out of the working + // tree, this should be an empty diff. + $this->priorDiff = $this->repository->getDiffOperator()->compareTo(); + } + + public function afterAction(RunnerHook $hook, Config\Action $action): void + { + $afterDiff = $this->repository->getDiffOperator()->compareTo(); + + // Did this action make any changes? + if ($afterDiff != $this->priorDiff) { + $this->actionChanges[$action->getAction()] = $afterDiff; + } + + $this->priorDiff = $afterDiff; + } + + public function afterHook(RunnerHook $hook): void + { + parent::afterHook($hook); + + if (count($this->actionChanges) > 0) { + $message = ''; + foreach ($this->actionChanges as $action => $changes) { + $message .= 'Action \'' . $action + . '\' on hook ' . $hook->getName() + . ' modified files' + . PHP_EOL; + } + + throw new ActionChangedFiles($message); + } + } +} diff --git a/src/Plugin/Hook/Example.php b/src/Plugin/Hook/Example.php new file mode 100644 index 00000000..6bb08710 --- /dev/null +++ b/src/Plugin/Hook/Example.php @@ -0,0 +1,56 @@ +io->write(['Plugin ' . self::class . '::beforeHook()', '']); + } + + /** + * Runs before each action. + * + * @param RunnerHook $hook This is the current hook that's running. + * @param Config\Action $action This is the configuration for action that will + * run immediately following this method. + */ + public function beforeAction(RunnerHook $hook, Config\Action $action): void + { + $this->io->write(['', ' Plugin ' . self::class . '::beforeAction()']); + } + + /** + * Runs after each action. + * + * @param RunnerHook $hook This is the current hook that's running. + * @param Config\Action $action This is the configuration for action that just + * ran immediately before this method. + */ + public function afterAction(RunnerHook $hook, Config\Action $action): void + { + $this->io->write(['', ' Plugin ' . self::class . '::afterAction()', '']); + } + + /** + * Runs after the hook. + * + * @param RunnerHook $hook This is the current hook that's running. + */ + public function afterHook(RunnerHook $hook): void + { + $this->io->write(['', 'Plugin ' . self::class . '::afterHook()']); + } +} diff --git a/src/Plugin/Hook/Exception/ActionChangedFiles.php b/src/Plugin/Hook/Exception/ActionChangedFiles.php new file mode 100644 index 00000000..4667e30a --- /dev/null +++ b/src/Plugin/Hook/Exception/ActionChangedFiles.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace CaptainHook\App\Plugin\Hook\Exception; + +use CaptainHook\App\Exception\CaptainHookException; +use RuntimeException; + +/** + * Class ActionChangedFiles + * + * @package CaptainHook + * @author Sebastian Feldmann + * @link https://github.com/captainhookphp/captainhook + * @since Class available since Release 5.11.0. + */ +class ActionChangedFiles extends RuntimeException implements CaptainHookException +{ +}