Passed
Push — main ( 394fb3...82ed03 )
by Sebastian
03:26
created

Config::getHookConfig()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
/**
4
 * This file is part of CaptainHook
5
 *
6
 * (c) Sebastian Feldmann <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace CaptainHook\App;
13
14
use CaptainHook\App\Config\Run;
15
use InvalidArgumentException;
16
use SebastianFeldmann\Camino\Check;
17
18
/**
19
 * Class Config
20
 *
21
 * @package CaptainHook
22
 * @author  Sebastian Feldmann <[email protected]>
23
 * @link    https://github.com/captainhookphp/captainhook
24
 * @since   Class available since Release 0.9.0
25
 * @internal
26
 */
27
class Config
28
{
29
    public const SETTING_ALLOW_FAILURE       = 'allow-failure';
30
    public const SETTING_BOOTSTRAP           = 'bootstrap';
31
    public const SETTING_COLORS              = 'ansi-colors';
32
    public const SETTING_CUSTOM              = 'custom';
33
    public const SETTING_GIT_DIR             = 'git-directory';
34
    public const SETTING_INCLUDES            = 'includes';
35
    public const SETTING_INCLUDES_LEVEL      = 'includes-level';
36
    public const SETTING_LABEL               = 'label';
37
    public const SETTING_RUN_EXEC            = 'run-exec';
38
    public const SETTING_RUN_MODE            = 'run-mode';
39
    public const SETTING_RUN_PATH            = 'run-path';
40
    public const SETTING_RUN_GIT             = 'run-git';
41
    public const SETTING_PHP_PATH            = 'php-path';
42
    public const SETTING_VERBOSITY           = 'verbosity';
43
    public const SETTING_FAIL_ON_FIRST_ERROR = 'fail-on-first-error';
44
45
    /**
46
     * Path to the config file
47
     *
48
     * @var string
49
     */
50
    private string $path;
51
52
    /**
53
     * Does the config file exist
54
     *
55
     * @var bool
56
     */
57
    private bool $fileExists;
58
59
    /**
60
     * CaptainHook settings
61
     *
62
     * @var array<string, string>
63
     */
64
    private array $settings;
65
66
    /**
67
     * All options related to running CaptainHook
68
     *
69
     * @var \CaptainHook\App\Config\Run
70
     */
71
    private Run $runConfig;
72
73
    /**
74
     * List of users custom settings
75
     *
76
     * @var array<string, mixed>
77
     */
78
    private array $custom = [];
79
80
    /**
81
     * List of plugins
82
     *
83
     * @var array<string, \CaptainHook\App\Config\Plugin>
84
     */
85
    private array $plugins = [];
86
87
    /**
88
     * List of hook configs
89
     *
90
     * @var array<string, \CaptainHook\App\Config\Hook>
91
     */
92
    private array $hooks = [];
93
94
    /**
95
     * Config constructor
96
     *
97
     * @param string               $path
98
     * @param bool                 $fileExists
99
     * @param array<string, mixed> $settings
100
     */
101 136
    public function __construct(string $path, bool $fileExists = false, array $settings = [])
102
    {
103 136
        $settings = $this->setupPlugins($settings);
104 136
        $settings = $this->setupCustom($settings);
105 136
        $settings = $this->setupRunConfig($settings);
106
107
108 136
        $this->path       = $path;
109 136
        $this->fileExists = $fileExists;
110 136
        $this->settings   = $settings;
111
112 136
        foreach (Hooks::getValidHooks() as $hook => $value) {
113 136
            $this->hooks[$hook] = new Config\Hook($hook);
114
        }
115
    }
116
117
    /**
118
     * Extract custom settings from Captain Hook ones
119
     *
120
     * @param  array<string, mixed> $settings
121
     * @return array<string, mixed>
122
     */
123 136
    private function setupCustom(array $settings): array
124
    {
125
        /* @var array<string, mixed> $custom */
126 136
        $this->custom = $settings['custom'] ?? [];
127 136
        unset($settings['custom']);
128
129 136
        return $settings;
130
    }
131
132
    /**
133
     * Setup all configured plugins
134
     *
135
     * @param  array<string, mixed> $settings
136
     * @return array<string, mixed>
137
     */
138 136
    private function setupPlugins(array $settings): array
139
    {
140
        /* @var array<int, array<string, mixed>> $pluginSettings */
141 136
        $pluginSettings = $settings['plugins'] ?? [];
142 136
        unset($settings['plugins']);
143
144 136
        foreach ($pluginSettings as $plugin) {
145 1
            $name                 = (string) $plugin['plugin'];
146 1
            $options              = isset($plugin['options']) && is_array($plugin['options'])
147 1
                ? $plugin['options']
148 1
                : [];
149 1
            $this->plugins[$name] = new Config\Plugin($name, $options);
150
        }
151 136
        return $settings;
152
    }
153
154
    /**
155
     * Extract all running related settings into a run configuration
156
     *
157
     * @param  array<string, mixed> $settings
158
     * @return array<string, mixed>
159
     */
160 136
    private function setupRunConfig(array $settings): array
161
    {
162
        // extract the legacy settings
163 136
        $settingsToMove = [
164 136
            self::SETTING_RUN_MODE,
165 136
            self::SETTING_RUN_EXEC,
166 136
            self::SETTING_RUN_PATH,
167 136
            self::SETTING_RUN_GIT
168 136
        ];
169 136
        $config = [];
170 136
        foreach ($settingsToMove as $setting) {
171 136
            if (!empty($settings[$setting])) {
172 9
                $config[substr($setting, 4)] = $settings[$setting];
173
            }
174 136
            unset($settings[$setting]);
175
        }
176
        // make sure the new run configuration supersedes the legacy settings
177 136
        if (isset($settings['run']) && is_array($settings['run'])) {
178 1
            $config = array_merge($config, $settings['run']);
179 1
            unset($settings['run']);
180
        }
181 136
        $this->runConfig = new Run($config);
182 136
        return $settings;
183
    }
184
185
    /**
186
     * Is configuration loaded from file
187
     *
188
     * @return bool
189
     */
190 38
    public function isLoadedFromFile(): bool
191
    {
192 38
        return $this->fileExists;
193
    }
194
195
    /**
196
     * Are actions allowed to fail without stopping the git operation
197
     *
198
     * @return bool
199
     */
200 5
    public function isFailureAllowed(): bool
201
    {
202 5
        return (bool) ($this->settings[self::SETTING_ALLOW_FAILURE] ?? false);
203
    }
204
205
    /**
206
     * @param  string $hook
207
     * @param  bool   $withVirtual if true, also check if hook is enabled through any enabled virtual hook
208
     * @return bool
209
     */
210 15
    public function isHookEnabled(string $hook, bool $withVirtual = true): bool
211
    {
212
        // either this hook is explicitly enabled
213 15
        $hookConfig = $this->getHookConfig($hook);
214 15
        if ($hookConfig->isEnabled()) {
215 9
            return true;
216
        }
217
218
        // or any virtual hook that triggers it is enabled
219 9
        if ($withVirtual && Hooks::triggersVirtualHook($hookConfig->getName())) {
220 7
            $virtualHookConfig = $this->getHookConfig(Hooks::getVirtualHook($hookConfig->getName()));
221 7
            if ($virtualHookConfig->isEnabled()) {
222 4
                return true;
223
            }
224
        }
225
226 9
        return false;
227
    }
228
229
    /**
230
     * Path getter
231
     *
232
     * @return string
233
     */
234 52
    public function getPath(): string
235
    {
236 52
        return $this->path;
237
    }
238
239
    /**
240
     * Return git directory path if configured, CWD/.git if not
241
     *
242
     * @return string
243
     */
244 31
    public function getGitDirectory(): string
245
    {
246 31
        if (empty($this->settings[self::SETTING_GIT_DIR])) {
247 1
            return getcwd() . '/.git';
248
        }
249
250
        // if repo path is absolute use it otherwise create an absolute path relative to the configuration file
251 30
        return Check::isAbsolutePath($this->settings[self::SETTING_GIT_DIR])
252 27
            ? $this->settings[self::SETTING_GIT_DIR]
253 30
            : dirname($this->path) . '/' . $this->settings[self::SETTING_GIT_DIR];
254
    }
255
256
    /**
257
     * Return bootstrap file if configured, CWD/vendor/autoload.php by default
258
     *
259
     * @return string
260
     */
261 17
    public function getBootstrap(): string
262
    {
263 17
        return !empty($this->settings[self::SETTING_BOOTSTRAP])
264 5
            ? $this->settings[self::SETTING_BOOTSTRAP]
265 17
            : 'vendor/autoload.php';
266
    }
267
268
    /**
269
     * Return the configured verbosity
270
     *
271
     * @return string
272
     */
273 28
    public function getVerbosity(): string
274
    {
275 28
        return !empty($this->settings[self::SETTING_VERBOSITY])
276 2
            ? $this->settings[self::SETTING_VERBOSITY]
277 28
            : 'normal';
278
    }
279
280
    /**
281
     * Should the output use ansi colors
282
     *
283
     * @return bool
284
     */
285 16
    public function useAnsiColors(): bool
286
    {
287 16
        return (bool) ($this->settings[self::SETTING_COLORS] ?? true);
288
    }
289
290
    /**
291
     * Get configured php-path
292
     *
293
     * @return string
294
     */
295 51
    public function getPhpPath(): string
296
    {
297 51
        return (string) ($this->settings[self::SETTING_PHP_PATH] ?? '');
298
    }
299
300
    /**
301
     * Get run configuration
302
     *
303
     * @return \CaptainHook\App\Config\Run
304
     */
305 19
    public function getRunConfig(): Run
306
    {
307 19
        return $this->runConfig;
308
    }
309
310
    /**
311
     * Returns the users custom config values
312
     *
313
     * @return array<mixed>
314
     */
315 1
    public function getCustomSettings(): array
316
    {
317 1
        return $this->custom;
318
    }
319
320
    /**
321
     * Whether to abort the hook as soon as a any action has errored. Default is true.
322
     * Otherwise, all actions get executed (even if some of them have failed) and
323
     * finally, a non-zero exit code is returned if any action has errored.
324
     *
325
     * @return bool
326
     */
327 6
    public function failOnFirstError(): bool
328
    {
329 6
        return (bool) ($this->settings[self::SETTING_FAIL_ON_FIRST_ERROR] ?? true);
330
    }
331
332
    /**
333
     * Return config for given hook
334
     *
335
     * @param  string $hook
336
     * @return \CaptainHook\App\Config\Hook
337
     * @throws \InvalidArgumentException
338
     */
339 52
    public function getHookConfig(string $hook): Config\Hook
340
    {
341 52
        if (!Hook\Util::isValid($hook)) {
342 1
            throw new InvalidArgumentException('Invalid hook name: ' . $hook);
343
        }
344 51
        return $this->hooks[$hook];
345
    }
346
347
    /**
348
     * Returns a hook config containing all the actions to execute
349
     *
350
     * Returns all actions from the triggered hook but also any actions of virtual hooks that might be triggered.
351
     * E.g. 'post-rewrite' or 'post-checkout' trigger the virtual/artificial 'post-change' hook.
352
     * Virtual hooks are special hooks to simplify configuration.
353
     *
354
     * @param  string $hook
355
     * @return \CaptainHook\App\Config\Hook
356
     */
357 7
    public function getHookConfigToExecute(string $hook): Config\Hook
358
    {
359 7
        $config     = new Config\Hook($hook, true);
360 7
        $hookConfig = $this->getHookConfig($hook);
361 7
        $config->addAction(...$hookConfig->getActions());
362 7
        if (Hooks::triggersVirtualHook($hookConfig->getName())) {
363 1
            $vHookConfig = $this->getHookConfig(Hooks::getVirtualHook($hookConfig->getName()));
364 1
            if ($vHookConfig->isEnabled()) {
365 1
                $config->addAction(...$vHookConfig->getActions());
366
            }
367
        }
368 7
        return $config;
369
    }
370
371
    /**
372
     * Return plugins
373
     *
374
     * @return Config\Plugin[]
375
     */
376 8
    public function getPlugins(): array
377
    {
378 8
        return $this->plugins;
379
    }
380
381
    /**
382
     * Return config array to write to disc
383
     *
384
     * @return array<string, mixed>
385
     */
386 10
    public function getJsonData(): array
387
    {
388 10
        $data = [];
389 10
        if (!empty($this->settings)) {
390 1
            $data['config'] = $this->settings;
391
        }
392
393 10
        $runConfigData = $this->runConfig->getJsonData();
394 10
        if (!empty($runConfigData)) {
395 2
            $data['config']['run'] = $runConfigData;
396
        }
397 10
        if (!empty($this->plugins)) {
398 1
            $data['config']['plugins'] = $this->getPluginsJsonData();
399
        }
400 10
        foreach (Hooks::getValidHooks() as $hook => $value) {
401 10
            $data[$hook] = $this->hooks[$hook]->getJsonData();
402
        }
403 10
        return $data;
404
    }
405
406
    /**
407
     * Collect and return plugin json data for all plugins
408
     *
409
     * @return array<int, mixed>
410
     */
411 1
    private function getPluginsJsonData(): array
412
    {
413 1
        $plugins = [];
414 1
        foreach ($this->plugins as $plugin) {
415 1
            $plugins[] = $plugin->getJsonData();
416
        }
417 1
        return $plugins;
418
    }
419
}
420