Config::getGitDirectory()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 10
cc 3
nc 3
nop 0
crap 3
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/captainhook-git/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 152
    public function __construct(string $path, bool $fileExists = false, array $settings = [])
102
    {
103 152
        $settings = $this->setupPlugins($settings);
104 152
        $settings = $this->setupCustom($settings);
105 152
        $settings = $this->setupRunConfig($settings);
106
107
108 152
        $this->path       = $path;
109 152
        $this->fileExists = $fileExists;
110 152
        $this->settings   = $settings;
111
112 152
        foreach (Hooks::getValidHooks() as $hook => $value) {
113 152
            $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 152
    private function setupCustom(array $settings): array
124
    {
125
        /* @var array<string, mixed> $custom */
126 152
        $this->custom = $settings['custom'] ?? [];
127 152
        unset($settings['custom']);
128
129 152
        return $settings;
130
    }
131
132
    /**
133
     * Setup all configured plugins
134
     *
135
     * @param  array<string, mixed> $settings
136
     * @return array<string, mixed>
137
     */
138 152
    private function setupPlugins(array $settings): array
139
    {
140
        /* @var array<int, array<string, mixed>> $pluginSettings */
141 152
        $pluginSettings = $settings['plugins'] ?? [];
142 152
        unset($settings['plugins']);
143
144 152
        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 152
        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 152
    private function setupRunConfig(array $settings): array
161
    {
162
        // extract the legacy settings
163 152
        $settingsToMove = [
164 152
            self::SETTING_RUN_MODE,
165 152
            self::SETTING_RUN_EXEC,
166 152
            self::SETTING_RUN_PATH,
167 152
            self::SETTING_RUN_GIT
168 152
        ];
169 152
        $config = [];
170 152
        foreach ($settingsToMove as $setting) {
171 152
            if (!empty($settings[$setting])) {
172 9
                $config[substr($setting, 4)] = $settings[$setting];
173
            }
174 152
            unset($settings[$setting]);
175
        }
176
        // make sure the new run configuration supersedes the legacy settings
177 152
        if (isset($settings['run']) && is_array($settings['run'])) {
178 1
            $config = array_merge($config, $settings['run']);
179 1
            unset($settings['run']);
180
        }
181 152
        $this->runConfig = new Run($config);
182 152
        return $settings;
183
    }
184
185
    /**
186
     * Is configuration loaded from file
187
     *
188
     * @return bool
189
     */
190 46
    public function isLoadedFromFile(): bool
191
    {
192 46
        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 16
    public function isHookEnabled(string $hook, bool $withVirtual = true): bool
211
    {
212
        // either this hook is explicitly enabled
213 16
        $hookConfig = $this->getHookConfig($hook);
214 16
        if ($hookConfig->isEnabled()) {
215 10
            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 59
    public function getPath(): string
235
    {
236 59
        return $this->path;
237
    }
238
239
    /**
240
     * Return git directory path if configured, CWD/.git if not
241
     *
242
     * @return string
243
     */
244 33
    public function getGitDirectory(): string
245
    {
246 33
        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 32
        return Check::isAbsolutePath($this->settings[self::SETTING_GIT_DIR])
252 29
            ? $this->settings[self::SETTING_GIT_DIR]
253 32
            : 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
     * @param  string $default
260
     * @return string
261
     */
262 18
    public function getBootstrap(string $default = 'vendor/autoload.php'): string
263
    {
264 18
        return !empty($this->settings[self::SETTING_BOOTSTRAP])
265 5
            ? $this->settings[self::SETTING_BOOTSTRAP]
266 18
            : $default;
267
    }
268
269
    /**
270
     * Return the configured verbosity
271
     *
272
     * @return string
273
     */
274 34
    public function getVerbosity(): string
275
    {
276 34
        return !empty($this->settings[self::SETTING_VERBOSITY])
277 2
            ? $this->settings[self::SETTING_VERBOSITY]
278 34
            : 'normal';
279
    }
280
281
    /**
282
     * Should the output use ansi colors
283
     *
284
     * @return bool
285
     */
286 3
    public function useAnsiColors(): bool
287
    {
288 3
        return (bool) ($this->settings[self::SETTING_COLORS] ?? true);
289
    }
290
291
    /**
292
     * Get configured php-path
293
     *
294
     * @return string
295
     */
296 57
    public function getPhpPath(): string
297
    {
298 57
        return (string) ($this->settings[self::SETTING_PHP_PATH] ?? '');
299
    }
300
301
    /**
302
     * Get run configuration
303
     *
304
     * @return \CaptainHook\App\Config\Run
305
     */
306 19
    public function getRunConfig(): Run
307
    {
308 19
        return $this->runConfig;
309
    }
310
311
    /**
312
     * Returns the users custom config values
313
     *
314
     * @return array<mixed>
315
     */
316 1
    public function getCustomSettings(): array
317
    {
318 1
        return $this->custom;
319
    }
320
321
    /**
322
     * Whether to abort the hook as soon as a any action has errored. Default is true.
323
     * Otherwise, all actions get executed (even if some of them have failed) and
324
     * finally, a non-zero exit code is returned if any action has errored.
325
     *
326
     * @return bool
327
     */
328 6
    public function failOnFirstError(): bool
329
    {
330 6
        return (bool) ($this->settings[self::SETTING_FAIL_ON_FIRST_ERROR] ?? true);
331
    }
332
333
    /**
334
     * Return config for given hook
335
     *
336
     * @param  string $hook
337
     * @return \CaptainHook\App\Config\Hook
338
     * @throws \InvalidArgumentException
339
     */
340 59
    public function getHookConfig(string $hook): Config\Hook
341
    {
342 59
        if (!Hook\Util::isValid($hook)) {
343 1
            throw new InvalidArgumentException('Invalid hook name: ' . $hook);
344
        }
345 58
        return $this->hooks[$hook];
346
    }
347
348
    /**
349
     * Return hook configs
350
     *
351
     * @return array<string, \CaptainHook\App\Config\Hook>
352
     */
353 6
    public function getHookConfigs(): array
354
    {
355 6
        return $this->hooks;
356
    }
357
358
    /**
359
     * Returns a hook config containing all the actions to execute
360
     *
361
     * Returns all actions from the triggered hook but also any actions of virtual hooks that might be triggered.
362
     * E.g. 'post-rewrite' or 'post-checkout' trigger the virtual/artificial 'post-change' hook.
363
     * Virtual hooks are special hooks to simplify configuration.
364
     *
365
     * @param  string $hook
366
     * @return \CaptainHook\App\Config\Hook
367
     */
368 8
    public function getHookConfigToExecute(string $hook): Config\Hook
369
    {
370 8
        $config     = new Config\Hook($hook, true);
371 8
        $hookConfig = $this->getHookConfig($hook);
372 8
        $config->addAction(...$hookConfig->getActions());
373 8
        if (Hooks::triggersVirtualHook($hookConfig->getName())) {
374 1
            $vHookConfig = $this->getHookConfig(Hooks::getVirtualHook($hookConfig->getName()));
375 1
            if ($vHookConfig->isEnabled()) {
376 1
                $config->addAction(...$vHookConfig->getActions());
377
            }
378
        }
379 8
        return $config;
380
    }
381
382
    /**
383
     * Return plugins
384
     *
385
     * @return Config\Plugin[]
386
     */
387 9
    public function getPlugins(): array
388
    {
389 9
        return $this->plugins;
390
    }
391
392
    /**
393
     * Return config array to write to disc
394
     *
395
     * @return array<string, mixed>
396
     */
397 11
    public function getJsonData(): array
398
    {
399 11
        $data = [];
400 11
        if (!empty($this->settings)) {
401 1
            $data['config'] = $this->settings;
402
        }
403
404 11
        $runConfigData = $this->runConfig->getJsonData();
405 11
        if (!empty($runConfigData)) {
406 2
            $data['config']['run'] = $runConfigData;
407
        }
408 11
        if (!empty($this->plugins)) {
409 1
            $data['config']['plugins'] = $this->getPluginsJsonData();
410
        }
411 11
        foreach (Hooks::getValidHooks() as $hook => $value) {
412 11
            $data[$hook] = $this->hooks[$hook]->getJsonData();
413
        }
414 11
        return $data;
415
    }
416
417
    /**
418
     * Collect and return plugin json data for all plugins
419
     *
420
     * @return array<int, mixed>
421
     */
422 1
    private function getPluginsJsonData(): array
423
    {
424 1
        $plugins = [];
425 1
        foreach ($this->plugins as $plugin) {
426 1
            $plugins[] = $plugin->getJsonData();
427
        }
428 1
        return $plugins;
429
    }
430
}
431