diff --git a/composer.json b/composer.json
index 6b67f08b..74de0301 100644
--- a/composer.json
+++ b/composer.json
@@ -3,7 +3,7 @@
"type": "library",
"description": "PHP git hook manager",
"keywords": ["git", "hooks", "pre-commit", "pre-push", "commit-msg", "prepare-commit-msg", "post-merge"],
- "homepage": "http://php.captainhook.info/",
+ "homepage": "https://php.captainhook.info/",
"license": "MIT",
"authors": [
{
diff --git a/src/Hook/Branch/Action/BlockFixupAndSquashCommits.php b/src/Hook/Branch/Action/BlockFixupAndSquashCommits.php
index c8f854fe..a09a9440 100644
--- a/src/Hook/Branch/Action/BlockFixupAndSquashCommits.php
+++ b/src/Hook/Branch/Action/BlockFixupAndSquashCommits.php
@@ -27,17 +27,17 @@
* This action blocks pushes that contain fixup! or squash! commits.
* Just as a security layer, so you are not pushing stuff you wanted to autosquash.
*
- * Configure like this:
- *
+ * Example configuration:
+ *
* {
- * "action": "\\CaptainHook\\App\\Hook\\Branch\\Action\\BlockFixupAndSquashCommits",
- * "options": {
- * "blockSquashCommits": true,
- * "blockFixupCommits": true,
- * "protectedBranches": ["main", "master", "integration"]
- * },
- * "conditions": []
- * }
+ * "action": "CaptainHook.Branch.PreventPushOfFixupAndSquashCommits",
+ * "options": {
+ * "blockSquashCommits": true,
+ * "blockFixupCommits": true,
+ * "protectedBranches": ["main", "master", "integration"]
+ * }
+ * }
+ *
*
* @package CaptainHook
* @author Sebastian Feldmann
diff --git a/src/Hook/Branch/Action/EnsureNaming.php b/src/Hook/Branch/Action/EnsureNaming.php
index 4a80da81..9665110f 100644
--- a/src/Hook/Branch/Action/EnsureNaming.php
+++ b/src/Hook/Branch/Action/EnsureNaming.php
@@ -22,6 +22,20 @@
/**
* Class EnsureNaming
*
+ * This Action makes sure you are on a branch that follows your naming conventions.
+ *
+ * Example configuration:
+ *
+ * {
+ * "action": "CaptainHook.Branch.EnsureNaming",
+ * "options": {
+ * "regex": "#feature/issue[0-9]+-.*#i",
+ * "error": "Arr matey! Ye be on the wrong branch!",
+ * "success": "All clear, Captain! Full speed ahead!",
+ * }
+ * }
+ *
+ *
* @package CaptainHook
* @author Felix Edelmann
* @link https://github.com/captainhook-git/captainhook
diff --git a/src/Hook/Condition/Branch/NotOn.php b/src/Hook/Condition/Branch/NotOn.php
index d30d0762..978bc215 100644
--- a/src/Hook/Condition/Branch/NotOn.php
+++ b/src/Hook/Condition/Branch/NotOn.php
@@ -18,7 +18,20 @@
use SebastianFeldmann\Git\Repository;
/**
- * NotOn condition
+ * NotOn Branch condition
+ *
+ * Example configuration:
+ *
+ * {
+ * "action": "some-action",
+ * "conditions": [
+ * {
+ * "exec": "CaptainHook.Status.NotOnBranch",
+ * "args": ["not-on-this-branch"]
+ * }
+ * ]
+ * }
+ *
*
* @package CaptainHook
* @author Sebastian Feldmann
diff --git a/src/Hook/Condition/Branch/NotOnMatching.php b/src/Hook/Condition/Branch/NotOnMatching.php
index 59ec917c..dc3ac546 100644
--- a/src/Hook/Condition/Branch/NotOnMatching.php
+++ b/src/Hook/Condition/Branch/NotOnMatching.php
@@ -18,7 +18,20 @@
use SebastianFeldmann\Git\Repository;
/**
- * NotOnMatching condition
+ * NotOnMatching Branch condition
+ *
+ * Example configuration:
+ *
+ * {
+ * "action": "some-action",
+ * "conditions": [
+ * {
+ * "exec": "CaptainHook.Status.NotOnMatchingBranch",
+ * "args": ["#^branches-names/not-matching[0-9]+-this-regex$#i"]
+ * }
+ * ]
+ * }
+ *
*
* @package CaptainHook
* @author Sebastian Feldmann
diff --git a/src/Hook/Condition/Branch/On.php b/src/Hook/Condition/Branch/On.php
index cf35f8b6..919d3e26 100644
--- a/src/Hook/Condition/Branch/On.php
+++ b/src/Hook/Condition/Branch/On.php
@@ -18,7 +18,20 @@
use SebastianFeldmann\Git\Repository;
/**
- * On condition
+ * On Branch condition
+ *
+ * Example configuration:
+ *
+ * {
+ * "action": "some-action",
+ * "conditions": [
+ * {
+ * "exec": "CaptainHook.Status.OnBranch",
+ * "args": ["only-on-this-branch"]
+ * }
+ * ]
+ * }
+ *
*
* @package CaptainHook
* @author Sebastian Feldmann
diff --git a/src/Hook/Condition/Branch/OnMatching.php b/src/Hook/Condition/Branch/OnMatching.php
index 53264108..e14e958c 100644
--- a/src/Hook/Condition/Branch/OnMatching.php
+++ b/src/Hook/Condition/Branch/OnMatching.php
@@ -18,7 +18,20 @@
use SebastianFeldmann\Git\Repository;
/**
- * OnMatching condition
+ * OnMatching Branch condition
+ *
+ * Example configuration:
+ *
+ * {
+ * "action": "some-action",
+ * "conditions": [
+ * {
+ * "exec": "CaptainHook.Status.OnMatchingBranch",
+ * "args": ["#^branches-names/matching[0-9]+-this-regex$#i"]
+ * }
+ * ]
+ * }
+ *
*
* @package CaptainHook
* @author Sebastian Feldmann
diff --git a/src/Hook/Condition/Config/CustomValueIsFalsy.php b/src/Hook/Condition/Config/CustomValueIsFalsy.php
index 83e26616..01df6313 100644
--- a/src/Hook/Condition/Config/CustomValueIsFalsy.php
+++ b/src/Hook/Condition/Config/CustomValueIsFalsy.php
@@ -17,22 +17,31 @@
use SebastianFeldmann\Git\Repository;
/**
- * Class CustomValueIsFalsy
+ * Condition CustomValueIsFalsy
+ *
+ * With this condition, you can check if a given custom value is falsy.
+ * The Action only is executed if the custom value is falsy.
+ * Values considered falsy are, 0, null, empty string, empty array and false.
*
* Example configuration:
*
- * "action": "some-action"
- * "conditions": [
- * {"exec": "\\CaptainHook\\App\\Hook\\Condition\\Config\\CustomValueIsFalsy",
- * "args": [
- * "NAME_OF_CUSTOM_VALUE"
- * ]}
- * ]
+ *
+ * {
+ * "action": "some-action"
+ * "conditions": [
+ * {
+ * "exec": "CaptainHook.Config.CustomValueIsFalsy",
+ * "args": ["NAME_OF_CUSTOM_VALUE"]
+ * }
+ * ]
+ * }
+ *
*
* @package CaptainHook
* @author Sebastian Feldmann
* @link https://github.com/captainhook-git/captainhook
* @since Class available since Release 5.17.2
+ * @short CaptainHook.Config.CustomValueIsFalsy
*/
class CustomValueIsFalsy extends Condition\Config
{
diff --git a/src/Hook/Condition/Config/CustomValueIsTruthy.php b/src/Hook/Condition/Config/CustomValueIsTruthy.php
index 2815aa3e..280f10a7 100644
--- a/src/Hook/Condition/Config/CustomValueIsTruthy.php
+++ b/src/Hook/Condition/Config/CustomValueIsTruthy.php
@@ -21,13 +21,17 @@
*
* Example configuration:
*
- * "action": "some-action"
- * "conditions": [
- * {"exec": "\\CaptainHook\\App\\Hook\\Condition\\Config\\CustomValueIsTruthy",
- * "args": [
- * "NAME_OF_CUSTOM_VALUE"
- * ]}
- * ]
+ *
+ * {
+ * "action": "some-action"
+ * "conditions": [
+ * {
+ * "exec": "CaptainHook.Config.CustomValueIsTruthy",
+ * "args": ["NAME_OF_CUSTOM_VALUE"]
+ * }
+ * ]
+ * }
+ *
*
* @package CaptainHook
* @author Sebastian Feldmann
diff --git a/src/Hook/Condition/FileChanged/All.php b/src/Hook/Condition/FileChanged/All.php
index e6dcc0d4..9f663b36 100644
--- a/src/Hook/Condition/FileChanged/All.php
+++ b/src/Hook/Condition/FileChanged/All.php
@@ -21,6 +21,22 @@
* The FileChange condition is applicable for `post-merge` and `post-checkout` hooks.
* It checks if all configured files are updated within the last change set.
*
+ * Example configuration:
+ *
+ *
+ * {
+ * "action": "some-action"
+ * "conditions": [
+ * {
+ * "exec": "CaptainHook.FileChanged.All",
+ * "args": [
+ * ["list", "of", "files"]
+ * ]
+ * }
+ * ]
+ * }
+ *
+ *
* @package CaptainHook
* @author Sebastian Feldmann
* @link https://github.com/captainhook-git/captainhook
diff --git a/src/Hook/Condition/FileChanged/Any.php b/src/Hook/Condition/FileChanged/Any.php
index d40f6dd4..8db27500 100644
--- a/src/Hook/Condition/FileChanged/Any.php
+++ b/src/Hook/Condition/FileChanged/Any.php
@@ -19,21 +19,24 @@
* Class Any
*
* The FileChange condition is applicable for `post-merge` and `post-checkout` hooks.
- * For example it can be used to trigger an automatic composer install if the composer.json
- * or composer.lock file is changed during a checkout or merge.
+ * For example, it can be used to trigger an automatic composer install if the composer.json
+ * or `composer.lock` file is changed during a checkout or merge.
*
* Example configuration:
*
- * "action": "composer install"
- * "conditions": [
- * {"exec": "\\CaptainHook\\App\\Hook\\Condition\\FileChange\\Any",
- * "args": [
- * [
- * "composer.json",
- * "composer.lock"
- * ]
- * ]}
- * ]
+ *
+ * {
+ * "action": "some-action"
+ * "conditions": [
+ * {
+ * "exec": "CaptainHook.FileChanged.Any",
+ * "args": [
+ * ["list", "of", "files"]
+ * ]
+ * }
+ * ]
+ * }
+ *
*
* @package CaptainHook
* @author Sebastian Feldmann
diff --git a/src/Hook/Condition/FileChanged/OfType.php b/src/Hook/Condition/FileChanged/OfType.php
index b13b5a8a..7b03e395 100644
--- a/src/Hook/Condition/FileChanged/OfType.php
+++ b/src/Hook/Condition/FileChanged/OfType.php
@@ -25,13 +25,17 @@
*
* Example configuration:
*
- * "action": "some-action"
- * "conditions": [
- * {"exec": "\\CaptainHook\\App\\Hook\\Condition\\FileChanged\\OfType",
- * "args": [
- * "php"
- * ]}
- * ]
+ *
+ * {
+ * "action": "some-action"
+ * "conditions": [
+ * {
+ * "exec": "CaptainHook.FileChanged.OfType",
+ * "args": ["php"]
+ * }
+ * ]
+ * }
+ *
*
* @package CaptainHook
* @author Sebastian Feldmann
diff --git a/src/Hook/Condition/FileStaged/All.php b/src/Hook/Condition/FileStaged/All.php
index 2c7e61a8..efb7aac8 100644
--- a/src/Hook/Condition/FileStaged/All.php
+++ b/src/Hook/Condition/FileStaged/All.php
@@ -23,13 +23,19 @@
*
* Example configuration:
*
- * "action": "some-action"
- * "conditions": [
- * {"exec": "\\CaptainHook\\App\\Hook\\Condition\\FileStaged\\All",
- * "args": [
- * ["file1", "file2", "file3"]
- * ]}
- * ]
+ *
+ * {
+ * "action": "some-action"
+ * "conditions": [
+ * {
+ * "exec": "CaptainHook.FileStaged.All",
+ * "args": [
+ * ["list", "of", "files"]
+ * ]
+ * }
+ * ]
+ * }
+ *
*
* The file list can also be defined as comma seperated string "file1,file2,file3"
*
diff --git a/src/Hook/Condition/FileStaged/Any.php b/src/Hook/Condition/FileStaged/Any.php
index fe65b340..8e43be19 100644
--- a/src/Hook/Condition/FileStaged/Any.php
+++ b/src/Hook/Condition/FileStaged/Any.php
@@ -20,15 +20,21 @@
*
* The FileStaged condition is applicable for `pre-commit hooks.
*
- * Example configuration:
+ * Example configuration:
*
+ *
+ * {
* "action": "some-action"
* "conditions": [
- * {"exec": "\\CaptainHook\\App\\Hook\\Condition\\FileStaged\\Any",
- * "args": [
- * ["file1", "file2", "file3"]
- * ]}
+ * {
+ * "exec": "CaptainHook.FileStaged.Any",
+ * "args": [
+ * ["list", "of", "files"]
+ * ]
+ * }
* ]
+ * }
+ *
*
* The file list can also be defined as comma seperated string "file1,file2,file3"
*
diff --git a/src/Hook/Condition/FileStaged/InDirectory.php b/src/Hook/Condition/FileStaged/InDirectory.php
index 7ddd0562..b69d2485 100644
--- a/src/Hook/Condition/FileStaged/InDirectory.php
+++ b/src/Hook/Condition/FileStaged/InDirectory.php
@@ -26,13 +26,17 @@
*
* Example configuration:
*
- * "action": "some-action"
- * "conditions": [
- * {"exec": "\\CaptainHook\\App\\Hook\\Condition\\FileStaged\\InDirectory",
- * "args": [
- * "src/"
- * ]}
- * ]
+ *
+ * {
+ * "action": "some-action"
+ * "conditions": [
+ * {
+ * "exec": "CaptainHook.FileStaged.InDirectory",
+ * "args": ["src"]
+ * }
+ * ]
+ * }
+ *
*
* @package CaptainHook
* @author Sebastian Feldmann
diff --git a/src/Hook/Condition/FileStaged/OfType.php b/src/Hook/Condition/FileStaged/OfType.php
index e56bf5c4..eaf5e6b7 100644
--- a/src/Hook/Condition/FileStaged/OfType.php
+++ b/src/Hook/Condition/FileStaged/OfType.php
@@ -27,18 +27,24 @@
*
* Example configuration:
*
- * "action": "some-action"
- * "conditions": [
- * {"exec": "\\CaptainHook\\App\\Hook\\Condition\\FileStaged\\OfType",
- * "args": [
- * "php",
- * ["A", "C"]
- * ]}
- * ]
+ *
+ * {
+ * "action": "some-action"
+ * "conditions": [
+ * {
+ * "exec": "CaptainHook.FileStaged.OfType",
+ * "args": [
+ * "php",
+ * ["A", "C"]
+ * ]
+ * }
+ * ]
+ * }
+ *
*
* Multiple types can be configured using a comma separated string or an array
- * "php,html,xml"
- * ["php", "html", "xml"]
+ * - "php,html,xml"
+ * - ["php", "html", "xml"]
*
* @package CaptainHook
* @author Sebastian Feldmann
diff --git a/src/Hook/Condition/FileStaged/ThatIs.php b/src/Hook/Condition/FileStaged/ThatIs.php
index 6e749a38..65bccec5 100644
--- a/src/Hook/Condition/FileStaged/ThatIs.php
+++ b/src/Hook/Condition/FileStaged/ThatIs.php
@@ -26,13 +26,19 @@
*
* Example configuration:
*
- * "action": "some-action"
- * "conditions": [
- * {"exec": "\\CaptainHook\\App\\Hook\\Condition\\FileStaged\\ThatIs",
- * "args": [
- * {"ofType": "php", "inDirectory": "foo/", "diff-filter": ["A", "C"]}
- * ]}
- * ]
+ *
+ * {
+ * "action": "some-action"
+ * "conditions": [
+ * {
+ * "exec": "CaptainHook.FileStaged.ThatIs",
+ * "args": [
+ * {"ofType": "php", "inDirectory": "foo/", "diff-filter": ["A", "C"]}
+ * ]
+ * }
+ * ]
+ * }
+ *
*
* @package CaptainHook
* @author Sebastian Feldmann
diff --git a/src/Hook/Debug/Failure.php b/src/Hook/Debug/Failure.php
index e3dd9827..4dad62ca 100644
--- a/src/Hook/Debug/Failure.php
+++ b/src/Hook/Debug/Failure.php
@@ -18,7 +18,17 @@
use SebastianFeldmann\Git\Repository;
/**
- * Debug hook to test hook triggering that fails the hook execution
+ * Debug Failure
+ *
+ * Debug hook to test hook triggering, arguments, stdIn ect.
+ * Fails the hook execution and stops current the git operation.
+ *
+ * Example configuration:
+ *
+ * {
+ * "action": "CaptainHook.Debug.Fail"
+ * }
+ *
*
* @package CaptainHook
* @author Sebastian Feldmann
diff --git a/src/Hook/Debug/Success.php b/src/Hook/Debug/Success.php
index 7a9d47b0..097f9b78 100644
--- a/src/Hook/Debug/Success.php
+++ b/src/Hook/Debug/Success.php
@@ -17,7 +17,17 @@
use SebastianFeldmann\Git\Repository;
/**
- * Debug hook to test hook triggering that allows the hook to pass
+ * Debug Success
+ *
+ * Debug hook action to test hook triggering, arguments, stdIn etc.
+ * Does not crash the hook execution. The git operation will succeed.
+ *
+ * Example configuration:
+ *
+ * {
+ * "action": "CaptainHook.Debug.Ok"
+ * }
+ *
*
* @package CaptainHook
* @author Sebastian Feldmann
diff --git a/src/Hook/Message/Action/InjectIssueKeyFromBranch.php b/src/Hook/Message/Action/InjectIssueKeyFromBranch.php
index 3ff4f505..408dba8c 100644
--- a/src/Hook/Message/Action/InjectIssueKeyFromBranch.php
+++ b/src/Hook/Message/Action/InjectIssueKeyFromBranch.php
@@ -28,16 +28,20 @@
* Class PrepareFromFile
*
* Example configuration:
+ *
+ *
* {
- * "action": "\\CaptainHook\\App\\Hook\\Message\\Action\\InjectIssueKeyFromBranch",
+ * "action": "CaptainHook.Message.InjectIssueKeyFromBranch",
* "options": {
* "regex": "#([A-Z]+\\-[0-9]+)#i",
- * "into": "body",
- * "mode": "append",
- * "prefix": "\nissue: ",
+ * "into": "body|subject",
+ * "mode": "append|prepend",
+ * "prefix": "",
+ * "suffix": "",
* "force": true
* }
* }
+ *
*
* The regex option needs group $1 (...) to be the issue key
*
diff --git a/src/Runner/Action/PHP.php b/src/Runner/Action/PHP.php
index 138e0011..ab54b11b 100644
--- a/src/Runner/Action/PHP.php
+++ b/src/Runner/Action/PHP.php
@@ -19,6 +19,7 @@
use CaptainHook\App\Hook\Constrained;
use CaptainHook\App\Hook\EventSubscriber;
use CaptainHook\App\Runner\Action as ActionRunner;
+use CaptainHook\App\Runner\Shorthand;
use Error;
use Exception;
use RuntimeException;
@@ -40,13 +41,13 @@ class PHP implements ActionRunner
*
* @var string
*/
- private $hook;
+ private string $hook;
/**
*
* @var \CaptainHook\App\Event\Dispatcher
*/
- private $dispatcher;
+ private Dispatcher $dispatcher;
/**
* PHP constructor.
@@ -71,7 +72,7 @@ public function __construct(string $hook, Dispatcher $dispatcher)
*/
public function execute(Config $config, IO $io, Repository $repository, Config\Action $action): void
{
- $class = $action->getAction();
+ $class = $this->getActionClass($action->getAction());
try {
// if the configured action is a static php method display the captured output and exit
@@ -80,7 +81,7 @@ public function execute(Config $config, IO $io, Repository $repository, Config\A
return;
}
- // if not static it has to be an 'Action' so let's instantiate
+ // if not static, it has to be an 'Action' so let's instantiate
$exe = $this->createAction($class);
// check for any given restrictions
if (!$this->isApplicable($exe)) {
@@ -170,4 +171,15 @@ private function isApplicable(Action $action)
}
return true;
}
+
+ /**
+ * Make sure action shorthands are translated before instantiating
+ *
+ * @param string $action
+ * @return string
+ */
+ private function getActionClass(string $action): string
+ {
+ return Shorthand::isShorthand($action) ? Shorthand::getActionClass($action) : $action;
+ }
}
diff --git a/src/Runner/Condition.php b/src/Runner/Condition.php
index 3363db89..a43edc47 100644
--- a/src/Runner/Condition.php
+++ b/src/Runner/Condition.php
@@ -109,7 +109,7 @@ private function createCondition(Config\Condition $config): ConditionInterface
}
/** @var class-string<\CaptainHook\App\Hook\Condition> $class */
- $class = $config->getExec();
+ $class = $this->getConditionClass($config->getExec());
if (!class_exists($class)) {
throw new RuntimeException('could not find condition class: ' . $class);
}
@@ -165,4 +165,15 @@ private function isLogicCondition(Config\Condition $config): bool
{
return in_array(strtolower($config->getExec()), ['and', 'or']);
}
+
+ /**
+ * Returns the condition class
+ *
+ * @param string $exec
+ * @return string
+ */
+ private function getConditionClass(string $exec): string
+ {
+ return Shorthand::isShorthand($exec) ? Shorthand::getConditionClass($exec) : $exec;
+ }
}
diff --git a/src/Runner/Shorthand.php b/src/Runner/Shorthand.php
new file mode 100644
index 00000000..a17ef5c4
--- /dev/null
+++ b/src/Runner/Shorthand.php
@@ -0,0 +1,143 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CaptainHook\App\Runner;
+
+use CaptainHook\App\Hook;
+use RuntimeException;
+
+/**
+ * Class Shorthand
+ *
+ * Defines some shorthands that can be used in the configuration file to not
+ * clutter the configuration with the full classnames.
+ *
+ * @package CaptainHook
+ * @author Sebastian Feldmann
+ * @link https://github.com/captainhook-git/captainhook
+ * @since Class available since Release 5.26.0
+ */
+class Shorthand
+{
+ /**
+ * Shorthand to action mapping
+ *
+ * @var array>>
+ */
+ private static array $map = [
+ 'action' => [
+ 'branch' => [
+ 'ensurenaming' => Hook\Branch\Action\EnsureNaming::class,
+ 'preventpushoffixupandsquashcommits' => Hook\Branch\Action\BlockFixupAndSquashCommits::class,
+ ],
+ 'debug' => [
+ 'fail' => Hook\Debug\Failure::class,
+ 'ok' => Hook\Debug\Success::class,
+ ],
+ 'file' => [
+ 'blocksecrets' => Hook\Diff\Action\BlockSecrets::class,
+ 'doesnotcontainregex' => Hook\File\Action\DoesNotContainRegex::class,
+ 'isnotempty' => Hook\File\Action\IsNotEmpty::class,
+ 'maxsize' => Hook\File\Action\MaxSize::class,
+ ],
+ 'message' => [
+ 'injectissuekeyfrombranch' => Hook\Message\Action\InjectIssueKeyFromBranch::class,
+ 'cacheonfail ' => Hook\Message\Action\CacheOnFail::class,
+ 'mustfollowbeamsrules' => Hook\Message\Action\Beams::class,
+ 'mustcontainsregex' => Hook\Message\Action\Regex::class,
+ 'preparefromfile' => Hook\Message\Action\PrepareFromFile::class,
+ 'prepare' => Hook\Message\Action\Prepare::class,
+ ],
+ 'notify' => [
+ 'gitnotify' => Hook\Notify\Action\Notify::class,
+ ],
+ ],
+ 'condition' => [
+ 'config' => [
+ 'customvalueistruthy' => Hook\Condition\Config\CustomValueIsTruthy::class,
+ 'customvalueisfalsy' => Hook\Condition\Config\CustomValueIsFalsy::class,
+ ],
+ 'filechanged' => [
+ 'any' => Hook\Condition\FileChanged\Any::class,
+ 'all' => Hook\Condition\FileChanged\All::class,
+ ],
+ 'filestaged' => [
+ 'all' => Hook\Condition\FileStaged\All::class,
+ 'any' => Hook\Condition\FileStaged\Any::class,
+ 'indirectory' => Hook\Condition\FileStaged\InDirectory::class,
+ 'oftype' => Hook\Condition\FileStaged\OfType::class,
+ 'thatis' => Hook\Condition\FileStaged\ThatIs::class,
+ ],
+ 'status' => [
+ 'onbranch' => Hook\Condition\Branch\On::class,
+ 'onmatchingbranch' => Hook\Condition\Branch\OnMatching::class,
+ 'notonbranch' => Hook\Condition\Branch\NotOn::class,
+ 'notonmatchingbranch' => Hook\Condition\Branch\NotOnMatching::class,
+ ]
+ ]
+ ];
+
+ /**
+ * Check if a configured action or condition value is actually a shorthand for an internal action
+ *
+ * @param string $shorthand
+ * @return bool
+ */
+ public static function isShorthand(string $shorthand): bool
+ {
+ return (bool) preg_match('#^captainhook\.[a-z]+\.[a-z]+$#i', $shorthand);
+ }
+
+ /**
+ * Return the matching action class for given action shorthand
+ *
+ * @param string $shorthand
+ * @return string
+ */
+ public static function getActionClass(string $shorthand): string
+ {
+ return Shorthand::getClass('action', $shorthand);
+ }
+
+ /**
+ * Return the matching condition class for given condition shorthand
+ *
+ * @param string $shorthand
+ * @return string
+ */
+ public static function getConditionClass(string $shorthand): string
+ {
+ return Shorthand::getClass('condition', $shorthand);
+ }
+
+ /**
+ * Returns the matching class for shorthand
+ *
+ * @param string $type
+ * @param string $shorthand
+ * @return string
+ */
+ private static function getClass(string $type, string $shorthand): string
+ {
+ $path = explode('.', strtolower($shorthand));
+ if (count($path) !== 3) {
+ throw new RuntimeException('Invalid ' . $type . ' shorthand: ' . $shorthand);
+ }
+ [$trigger, $group, $name] = $path;
+ if (!isset(self::$map[$type][$group])) {
+ throw new RuntimeException('Invalid ' . $type . ' group: ' . $group);
+ }
+ if (!isset(self::$map[$type][$group][$name])) {
+ throw new RuntimeException('Invalid ' . $type . ' => ' . $name);
+ }
+ return self::$map[$type][$group][$name];
+ }
+}
diff --git a/src/Runner/Util.php b/src/Runner/Util.php
index 3b3b6e45..7a182303 100644
--- a/src/Runner/Util.php
+++ b/src/Runner/Util.php
@@ -45,7 +45,18 @@ public static function isTypeValid(string $type): bool
*/
public static function getExecType(string $action): string
{
- return substr($action, 0, 1) === '\\' ? 'php' : 'cli';
+ return self::isPHPType($action) ? 'php' : 'cli';
+ }
+
+ /**
+ * Check if the action type is PHP
+ *
+ * @param string $action
+ * @return bool
+ */
+ private static function isPHPType(string $action): bool
+ {
+ return str_starts_with($action, '\\') || Shorthand::isShorthand($action);
}
/**
diff --git a/tests/unit/Runner/Action/PHPTest.php b/tests/unit/Runner/Action/PHPTest.php
index d53c8b85..edd82fa1 100644
--- a/tests/unit/Runner/Action/PHPTest.php
+++ b/tests/unit/Runner/Action/PHPTest.php
@@ -118,6 +118,24 @@ public function testExecuteError(): void
$php->execute($config, $io, $repo, $action);
}
+ public function testExecuteByShorthand(): void
+ {
+ $this->expectException(Exception::class);
+ $this->expectExceptionMessageMatches('/debugging/i');
+
+ $config = $this->createConfigMock();
+ $io = $this->createIOMock();
+ $repo = $this->createRepositoryMock();
+ $action = $this->createActionConfigMock();
+ $events = new Dispatcher($io, $config, $repo);
+ $class = 'CaptainHook.Debug.fail';
+
+ $action->expects($this->once())->method('getAction')->willReturn($class);
+
+ $php = new PHP('pre-commit', $events);
+ $php->execute($config, $io, $repo, $action);
+ }
+
public function testExecuteNoAction(): void
{
$this->expectException(Exception::class);
diff --git a/tests/unit/Runner/ShorthandTest.php b/tests/unit/Runner/ShorthandTest.php
new file mode 100644
index 00000000..e2d26c88
--- /dev/null
+++ b/tests/unit/Runner/ShorthandTest.php
@@ -0,0 +1,89 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CaptainHook\App\Runner;
+
+use Exception;
+use PHPUnit\Framework\TestCase;
+
+class ShorthandTest extends TestCase
+{
+ /**
+ * Checks if shorthands are identified correctly
+ */
+ public function testCanIdentifyShorthand()
+ {
+ // negative
+ $this->assertFalse(Shorthand::isShorthand('foo'));
+ $this->assertFalse(Shorthand::isShorthand('\\CaptainHook\\App'));
+ $this->assertFalse(Shorthand::isShorthand('CaptainHook.'));
+ $this->assertFalse(Shorthand::isShorthand('captainhook.sh'));
+
+ // positive
+ $this->assertTrue(Shorthand::isShorthand('CaptainHook.foo.fiz'));
+ $this->assertTrue(Shorthand::isShorthand('captainhook.Bar.Baz'));
+ $this->assertTrue(Shorthand::isShorthand('CAPTAINHOOK.FOO.BAR'));
+ }
+
+ /**
+ * Check if invalid shorthand detection works
+ */
+ public function testDetectsInvalidActionShortHand(): void
+ {
+ $this->expectException(Exception::class);
+ Shorthand::getActionClass('Captainhook.foo.bar.baz');
+ }
+
+ /**
+ * Check if an invalid shorthand group is detected
+ */
+ public function testDetectsInvalidActionShorthandGroup(): void
+ {
+ $this->expectException(Exception::class);
+ Shorthand::getActionClass('Captainhook.foo.bar');
+ }
+
+ /**
+ * Check if an invalid action shorthand name is detected
+ */
+ public function testDetectsInvalidActionShorthandName(): void
+ {
+ $this->expectException(Exception::class);
+ Shorthand::getActionClass('Captainhook.File.bar');
+ }
+
+ /**
+ * Check if an invalid condition shorthand name is detected
+ */
+ public function testDetectsInvalidConditionShorthandName(): void
+ {
+ $this->expectException(Exception::class);
+ Shorthand::getConditionClass('Captainhook.FileStaged.bar');
+ }
+
+ /**
+ * Check if a valid action shorthand is mapped correctly
+ */
+ public function testFindsActionClassByShorthand(): void
+ {
+ $class = Shorthand::getActionClass('Captainhook.Branch.EnsureNaming');
+ $this->assertTrue(str_contains($class, 'CaptainHook\App\Hook\Branch\Action\EnsureNaming'));
+ }
+
+ /**
+ * Check if a valid condition shorthand is mapped correctly
+ */
+ public function testFindsConditionClassByShorthand(): void
+ {
+ $class = Shorthand::getConditionClass('Captainhook.Status.OnBranch');
+ $this->assertTrue(str_contains($class, 'CaptainHook\App\Hook\Condition\Branch\On'));
+ }
+}