diff --git a/docs/Annotations.md b/docs/Annotations.md index b4019096..c213891b 100644 --- a/docs/Annotations.md +++ b/docs/Annotations.md @@ -39,6 +39,9 @@ You get autocompletion on any `$this->Apples->...()` usage in your controllers t Use `-p PluginName` to annotate inside a plugin. It will then use the plugin name as namespace. +Tip: Use `*` wildcard to refer to a group of plugins. Make sure to only touch internal plugins (in version control), however. +E.g. `-p SomePrefix/*` which are all inside your own `plugins/` directory - and not in `vendor/`. + ### Primary model via $modelClass definition When defining `$modelClass` it will be used instead: ```php diff --git a/src/Annotator/ModelAnnotator.php b/src/Annotator/ModelAnnotator.php index 2ebbc4ae..13f464c3 100644 --- a/src/Annotator/ModelAnnotator.php +++ b/src/Annotator/ModelAnnotator.php @@ -279,7 +279,7 @@ protected function getAssociations(AssociationCollection $tableAssociations): ar continue; } - /** @var \Cake\ORM\Association\BelongsToMany $association */ + /** @var \Cake\ORM\Association\BelongsToMany<\Cake\ORM\Table> $association */ $through = $this->throughAlias($association); if (!$through) { continue; @@ -302,7 +302,7 @@ protected function getAssociations(AssociationCollection $tableAssociations): ar } /** - * @param \Cake\ORM\Association\BelongsToMany $association + * @param \Cake\ORM\Association\BelongsToMany<\Cake\ORM\Table> $association * @return string */ protected function throughAlias(BelongsToMany $association): string { @@ -324,7 +324,7 @@ protected function throughAlias(BelongsToMany $association): string { /** * @uses \Cake\ORM\Association\BelongsToMany::_junctionTableName() * - * @param \Cake\ORM\Association\BelongsToMany $association + * @param \Cake\ORM\Association\BelongsToMany<\Cake\ORM\Table> $association * @return string */ protected function junctionTableName(BelongsToMany $association): string { @@ -409,17 +409,17 @@ protected function resolvePluginName(string $className, string $name): ?string { return ''; } - if (strpos($name, '\\') !== false) { - preg_match('#^(.+)\\\\Model\\\\Behavior\\\\#', $className, $matches); - if (!$matches) { - return null; - } - } else { - preg_match('#^(.+)\\\\Model\\\\Behavior\\\\' . $name . 'Behavior$#', $className, $matches); - if (!$matches) { - return null; - } - } + if (strpos($name, '\\') !== false) { + preg_match('#^(.+)\\\\Model\\\\Behavior\\\\#', $className, $matches); + if (!$matches) { + return null; + } + } else { + preg_match('#^(.+)\\\\Model\\\\Behavior\\\\' . $name . 'Behavior$#', $className, $matches); + if (!$matches) { + return null; + } + } return str_replace('\\', '/', $matches[1]); } diff --git a/src/Shell/AnnotationsShell.php b/src/Shell/AnnotationsShell.php index 28a4d93c..c4c962c5 100644 --- a/src/Shell/AnnotationsShell.php +++ b/src/Shell/AnnotationsShell.php @@ -81,16 +81,18 @@ public function startup(): void { * @return int */ public function callbacks() { - $plugin = (string)$this->param('plugin') ?: null; - - $path = $plugin ? PluginPath::classPath($plugin) : ROOT . DS . APP_DIR . DS; - - $folder = new Folder($path); + $paths = $this->getPaths(); + foreach ($paths as $path) { + if (!is_dir($path)) { + continue; + } - $folders = $folder->subdirectories(); + $folder = new Folder($path); - foreach ($folders as $folder) { - $this->_callbacks($folder . DS); + $folders = $folder->subdirectories(); + foreach ($folders as $folder) { + $this->_callbacks($folder . DS); + } } if ($this->param('ci') && $this->_annotatorMadeChanges()) { @@ -205,11 +207,9 @@ public function all() { * @return int */ public function models() { - $plugin = (string)$this->param('plugin') ?: null; - $folders = AppPath::get('Model/Table', $plugin); - - foreach ($folders as $folder) { - $this->_models($folder); + $paths = $this->getPaths('Model/Table'); + foreach ($paths as $path) { + $this->_models($path); } if ($this->param('ci') && $this->_annotatorMadeChanges()) { @@ -245,14 +245,17 @@ protected function _models($folder) { * @return int */ public function classes() { - $plugin = (string)$this->param('plugin') ?: null; - - $path = $plugin ? PluginPath::classPath($plugin) : ROOT . DS . APP_DIR . DS; + $paths = $this->getPaths('Model/Table'); + foreach ($paths as $path) { + if (!is_dir($path)) { + continue; + } - $folder = new Folder($path); - $folders = $folder->subdirectories(); - foreach ($folders as $folder) { - $this->_classes($folder . DS); + $folder = new Folder($path); + $folders = $folder->subdirectories(); + foreach ($folders as $folder) { + $this->_classes($folder . DS); + } } $collection = new ClassAnnotatorTaskCollection(); @@ -261,16 +264,18 @@ public function classes() { return static::CODE_SUCCESS; } - $path = $plugin ? PluginPath::get($plugin) : ROOT . DS; - $path .= 'tests' . DS . 'TestCase' . DS; - if (!is_dir($path)) { - return static::CODE_SUCCESS; - } + $paths = $this->getPaths(); + foreach ($paths as $path) { + $path .= 'tests' . DS . 'TestCase' . DS; + if (!is_dir($path)) { + continue; + } - $folder = new Folder($path); - $folders = $folder->subdirectories(); - foreach ($folders as $folder) { - $this->_classes($folder . DS); + $folder = new Folder($path); + $folders = $folder->subdirectories(); + foreach ($folders as $folder) { + $this->_classes($folder . DS); + } } if ($this->param('ci') && $this->_annotatorMadeChanges()) { @@ -321,11 +326,10 @@ protected function _classes($folder) { * @return int */ public function controllers() { - $plugin = (string)$this->param('plugin') ?: null; - $folders = AppPath::get('Controller', $plugin); + $paths = $this->getPaths('Controller'); - foreach ($folders as $folder) { - $this->_controllers($folder); + foreach ($paths as $path) { + $this->_controllers($path); } if ($this->param('ci') && $this->_annotatorMadeChanges()) { @@ -375,18 +379,19 @@ protected function _controllers($folder) { * @return int */ public function routes() { - $plugin = (string)$this->param('plugin') ?: null; - $path = $plugin ? Plugin::path($plugin) : ROOT . DS; + $paths = $this->getPaths(); - $name = 'routes.php'; - $path .= 'config' . DS . $name; - if (!file_exists($path)) { - return static::CODE_SUCCESS; - } + foreach ($paths as $path) { + $name = 'routes.php'; + $path .= 'config' . DS . $name; + if (!file_exists($path)) { + continue; + } - $this->out('-> ' . $name, 1, Shell::VERBOSE); - $annotator = $this->getAnnotator(RoutesAnnotator::class); - $annotator->annotate($path); + $this->out('-> ' . $name, 1, Shell::VERBOSE); + $annotator = $this->getAnnotator(RoutesAnnotator::class); + $annotator->annotate($path); + } if ($this->param('ci') && $this->_annotatorMadeChanges()) { return static::CODE_CHANGES; @@ -399,11 +404,10 @@ public function routes() { * @return int */ public function templates() { - $plugin = (string)$this->param('plugin') ?: null; - $folders = App::path('templates', $plugin); + $paths = $this->getPaths('templates'); - foreach ($folders as $folder) { - $this->_templates($folder); + foreach ($paths as $path) { + $this->_templates($path); } if ($this->param('ci') && $this->_annotatorMadeChanges()) { @@ -463,11 +467,10 @@ protected function _templates($folder) { * @return int */ public function helpers() { - $plugin = (string)$this->param('plugin') ?: null; - $folders = AppPath::get('View/Helper', $plugin); + $paths = $this->getPaths('View/Helper'); - foreach ($folders as $folder) { - $this->_helpers($folder); + foreach ($paths as $path) { + $this->_helpers($path); } if ($this->param('ci') && $this->_annotatorMadeChanges()) { @@ -505,11 +508,10 @@ protected function _helpers($folder) { * @return int */ public function components() { - $plugin = (string)$this->param('plugin') ?: null; - $folders = AppPath::get('Controller/Component', $plugin); + $paths = $this->getPaths('Controller/Component'); - foreach ($folders as $folder) { - $this->_components($folder); + foreach ($paths as $path) { + $this->_components($path); } if ($this->param('ci') && $this->_annotatorMadeChanges()) { @@ -547,29 +549,10 @@ protected function _components($folder) { * @return int */ public function commands() { - $plugin = (string)$this->param('plugin') ?: null; - $folders = AppPath::get('Command', $plugin); - - foreach ($folders as $folder) { - $this->_commands($folder); - } - - if ($this->param('ci') && $this->_annotatorMadeChanges()) { - return static::CODE_CHANGES; - } - - return static::CODE_SUCCESS; - } - - /** - * @return int - */ - public function shells() { - $plugin = (string)$this->param('plugin') ?: null; - $folders = AppPath::get('Shell', $plugin); + $paths = $this->getPaths('Command'); - foreach ($folders as $folder) { - $this->_shells($folder); + foreach ($paths as $path) { + $this->_commands($path); } if ($this->param('ci') && $this->_annotatorMadeChanges()) { @@ -599,6 +582,23 @@ protected function _commands($folder) { } } + /** + * @return int + */ + public function shells() { + $paths = $this->getPaths('Shell'); + + foreach ($paths as $path) { + $this->_shells($path); + } + + if ($this->param('ci') && $this->_annotatorMadeChanges()) { + return static::CODE_CHANGES; + } + + return static::CODE_SUCCESS; + } + /** * @param string $folder * @return void @@ -669,7 +669,7 @@ public function getOptionParser(): ConsoleOptionParser { ], 'plugin' => [ 'short' => 'p', - 'help' => 'The plugin to run. Defaults to the application otherwise.', + 'help' => 'The plugin to run. Defaults to the application otherwise. Supports wildcard `*` for partial match.', 'default' => null, ], 'remove' => [ @@ -801,4 +801,59 @@ protected function _annotatorMadeChanges(): bool { return AbstractAnnotator::$output !== false; } + /** + * @param string|null $type + * @return array + */ + protected function getPaths(?string $type = null): array { + $plugin = (string)$this->param('plugin') ?: null; + if (!$plugin) { + if (!$type) { + return [ROOT . DS . APP_DIR . DS]; + } + + return $type === 'templates' ? App::path('templates') : AppPath::get($type); + } + + $plugins = $this->getPlugins($plugin); + + $paths = []; + foreach ($plugins as $plugin) { + if (!$type) { + $pluginPaths = [PluginPath::classPath($plugin)]; + } else { + $pluginPaths = $type === 'templates' ? App::path('templates', $plugin) : AppPath::get($type, $plugin); + } + foreach ($pluginPaths as $pluginPath) { + $paths[] = $pluginPath; + } + } + + return $paths; + } + + /** + * @param string $plugin + * + * @return array + */ + protected function getPlugins(string $plugin): array { + if (strpos($plugin, '*') === false) { + return [$plugin]; + } + + return $this->filterPlugins(Plugin::loaded(), $plugin); + } + + /** + * @param array $plugins + * @param string $pattern + * @return array + */ + protected function filterPlugins(array $plugins, string $pattern): array { + return array_filter($plugins, function($plugin) use ($pattern) { + return fnmatch($pattern, $plugin); + }); + } + } diff --git a/tests/TestCase/Shell/AnnotationsShellTest.php b/tests/TestCase/Shell/AnnotationsShellTest.php index a92f4938..fecafc9b 100644 --- a/tests/TestCase/Shell/AnnotationsShellTest.php +++ b/tests/TestCase/Shell/AnnotationsShellTest.php @@ -49,7 +49,7 @@ protected function setUp(): void { $io = new ConsoleIo($this->out, $this->err); $this->Shell = $this->getMockBuilder(AnnotationsShell::class) - ->setMethods(['in', '_stop', 'storeFile']) + ->onlyMethods(['in', '_stop']) ->setConstructorArgs([$io]) ->getMock(); }