Passed
Push — master ( f5c231...9f4d35 )
by Sebastian
02:26
created

Installer::shouldHookBeSkipped()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 0
cp 0
rs 10
cc 2
nc 2
nop 1
crap 6
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
declare(strict_types=1);
13
14
namespace CaptainHook\App\Runner;
15
16
use CaptainHook\App\Config;
17
use CaptainHook\App\Console\IO;
18
use CaptainHook\App\Console\IOUtil;
19
use CaptainHook\App\Exception;
20
use CaptainHook\App\Hook\Template;
21
use CaptainHook\App\Hook\Util as HookUtil;
22
use CaptainHook\App\Storage\File;
23
use SebastianFeldmann\Git\Repository;
24
25
/**
26
 * Class Installer
27
 *
28
 * @package CaptainHook
29
 * @author  Sebastian Feldmann <[email protected]>
30
 * @link    https://github.com/captainhookphp/captainhook
31
 * @since   Class available since Release 0.9.0
32
 */
33
class Installer extends RepositoryAware
34
{
35
    /**
36
     * Install hooks brute force
37
     *
38
     * @var bool
39
     */
40
    private $force = false;
41
42
    /**
43
     * Don't overwrite existing hooks
44
     *
45
     * @var bool
46
     */
47
    private $skipExisting = false;
48
49
    /**
50
     * Hook that should be handled.
51
     *
52
     * @var string
53 3
     */
54
    protected $hookToHandle;
55 3
56 3
    /**
57
     * Hook template
58
     *
59
     * @var Template
60
     */
61
    private $template;
62
63
    /**
64
     * Git repository.
65
     *
66 4
     * @var \SebastianFeldmann\Git\Repository
67
     */
68 4
    protected $repository;
69 1
70
    /**
71 3
     * HookHandler constructor.
72 3
     *
73
     * @param \CaptainHook\App\Console\IO       $io
74
     * @param \CaptainHook\App\Config           $config
75
     * @param \SebastianFeldmann\Git\Repository $repository
76
     * @param \CaptainHook\App\Hook\Template    $template
77
     */
78
    public function __construct(IO $io, Config $config, Repository $repository, Template $template)
79
    {
80 3
        $this->template = $template;
81
        parent::__construct($io, $config, $repository);
82 3
    }
83
84 3
    /**
85 3
     * @param  bool $force
86
     * @return \CaptainHook\App\Runner\Installer
87 3
     */
88
    public function setForce(bool $force): Installer
89
    {
90
        $this->force = $force;
91
        return $this;
92
    }
93
94 3
    /**
95
     * @param  bool $skip
96 3
     * @return \CaptainHook\App\Runner\Installer
97
     */
98
    public function setSkipExisting(bool $skip): Installer
99
    {
100
        $this->skipExisting = $skip;
101
        return $this;
102
    }
103
104
    /**
105 4
     * Hook setter
106
     *
107 4
     * @param  string $hook
108 4
     * @return \CaptainHook\App\Runner\Installer
109 1
     * @throws \CaptainHook\App\Exception\InvalidHookName
110 1
     */
111
    public function setHook(string $hook): Installer
112
    {
113 4
        if (!empty($hook) && !HookUtil::isValid($hook)) {
114 3
            throw new Exception\InvalidHookName('Invalid hook name \'' . $hook . '\'');
115
        }
116 4
        $this->hookToHandle = $hook;
117
        return $this;
118
    }
119
120
    /**
121
     * Execute installation
122
     *
123
     * @return void
124 5
     */
125
    public function run(): void
126 5
    {
127 5
        $hooks = $this->getHooksToInstall();
128 5
129
        foreach ($hooks as $hook => $ask) {
130
            $this->installHook($hook, ($ask && !$this->force));
131
        }
132 5
    }
133 1
134 1
    /**
135
     * Return list of hooks to install
136
     *
137 5
     * [
138 4
     *   string    => bool
139 4
     *   HOOK_NAME => ASK_USER_TO_CONFIRM_INSTALL
140 4
     * ]
141 4
     *
142 4
     * @return array<string, bool>
143
     */
144 5
    public function getHooksToInstall(): array
145
    {
146
        // callback to write bool true to all array entries
147
        // to make sure the user will be asked to confirm every hook installation
148
        // unless the user provided the force option
149
        $callback = function () {
150
            return true;
151
        };
152 4
        // if a specific hook is set the user chose it so don't ask for permission anymore
153
        return empty($this->hookToHandle)
154 4
            ? array_map($callback, HookUtil::getValidHooks())
155
            : [$this->hookToHandle => false];
156
    }
157
158
    /**
159
     * Install given hook
160
     *
161
     * @param string $hook
162
     * @param bool   $ask
163 5
     */
164
    private function installHook(string $hook, bool $ask): void
165 5
    {
166
        if ($this->shouldHookBeSkipped($hook)) {
167
            $this->io->write($hook . ' is already installed, remove the --skip-existing option to overwrite.');
168
            return;
169
        }
170
171
        $doIt = true;
172
        if ($ask) {
173
            $answer = $this->io->ask('  <info>Install \'' . $hook . '\' hook?</info> <comment>[y,n]</comment> ', 'y');
174
            $doIt   = IOUtil::answerToBool($answer);
175 4
        }
176
177 4
        if ($doIt) {
178 4
            $this->writeHookFile($hook);
179
        }
180
    }
181
182
    /**
183
     * Check if the hook is installed and should be skipped
184
     *
185
     * @param  string $hook
186
     * @return bool
187
     */
188
    private function shouldHookBeSkipped(string $hook): bool
189
    {
190
        return $this->skipExisting && $this->repository->hookExists($hook);
191
    }
192
193
    /**
194
     * Write given hook to .git/hooks directory
195
     *
196
     * @param  string $hook
197
     * @return void
198
     */
199
    private function writeHookFile(string $hook): void
200
    {
201
        $hooksDir = $this->repository->getHooksDir();
202
        $hookFile = $hooksDir . DIRECTORY_SEPARATOR . $hook;
203
        $doIt     = true;
204
205
        // if hook is configured and no force option is set
206
        // ask the user if overwriting the hook is ok
207
        if ($this->needInstallConfirmation($hook)) {
208
            $ans  = $this->io->ask('  <comment>The \'' . $hook . '\' hook exists! Overwrite? [y,n]</comment> ', 'n');
209
            $doIt = IOUtil::answerToBool($ans);
210
        }
211
212
        if ($doIt) {
213
            $code = $this->getHookSourceCode($hook);
214
            $file = new File($hookFile);
215
            $file->write($code);
216
            chmod($hookFile, 0755);
217
            $this->io->write('  <info>\'' . $hook . '\' hook installed successfully</info>');
218
        }
219
    }
220
221
    /**
222
     * Return the source code for a given hook script
223
     *
224
     * @param  string $hook
225
     * @return string
226
     */
227
    private function getHookSourceCode(string $hook): string
228
    {
229
        return $this->template->getCode($hook);
230
    }
231
232
    /**
233
     * If the hook already exists the user has to confirm the installation
234
     *
235
     * @param  string $hook The name of the hook to check
236
     * @return bool
237
     */
238
    private function needInstallConfirmation(string $hook): bool
239
    {
240
        return $this->repository->hookExists($hook) && !$this->force;
241
    }
242
}
243