Completed
Push — master ( 01982b...3c7394 )
by ANTHONIUS
11s
created

InstallCommand::execute()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 9.2
c 0
b 0
f 0
cc 2
eloc 14
nc 2
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the dotfiles project.
7
 *
8
 *     (c) Anthonius Munthi <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Dotfiles\Core\Command;
15
16
use Dotfiles\Core\Config\Config;
17
use Dotfiles\Core\Event\Dispatcher;
18
use Dotfiles\Core\Event\InstallEvent;
19
use Dotfiles\Core\Util\Filesystem;
20
use Dotfiles\Core\Util\Toolkit;
21
use Psr\Log\LoggerInterface;
22
use Symfony\Component\Console\Command\Command;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Dotfiles\Core\Command\Command. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
23
use Symfony\Component\Console\Input\InputInterface;
24
use Symfony\Component\Console\Output\OutputInterface;
25
use Symfony\Component\Finder\Finder;
26
use Symfony\Component\Finder\SplFileInfo;
27
use Symfony\Component\Process\Process;
28
29
class InstallCommand extends Command implements CommandInterface
30
{
31
    /**
32
     * @var Config
33
     */
34
    private $config;
35
36
    /**
37
     * @var Dispatcher
38
     */
39
    private $dispatcher;
40
41
    private $dryRun = false;
42
43
    /**
44
     * @var LoggerInterface
45
     */
46
    private $logger;
47
48
    /**
49
     * @var OutputInterface
50
     */
51
    private $output;
52
53
    private $overwriteNewFiles = false;
54
55
    /**
56
     * @var array
57
     */
58
    private $patches = array();
59
60
    public function __construct(
61
        ?string $name = null,
62
        Dispatcher $dispatcher,
63
        Config $config,
64
        LoggerInterface $logger
65
    ) {
66
        parent::__construct($name);
67
        $this->dispatcher = $dispatcher;
68
        $this->config = $config;
69
        $this->logger = $logger;
70
    }
71
72
    public function configure(): void
73
    {
74
        $this->setName('install');
75
    }
76
77
    public function execute(InputInterface $input, OutputInterface $output): void
78
    {
79
        $this->getApplication()->get('backup')->execute($input, $output);
80
81
        $output->writeln('Begin installing <comment>dotfiles</comment>');
82
        $config = $this->config;
83
        $this->dryRun = $config->get('dotfiles.dry_run');
84
        $this->output = $output;
85
86
        Toolkit::ensureDir($config->get('dotfiles.bin_dir'));
87
        Toolkit::ensureDir($config->get('dotfiles.vendor_dir'));
88
89
        $this->processSection($output, 'defaults');
90
        if (null !== ($machineName = $config->get('dotfiles.machine_name'))) {
91
            $this->processSection($output, 'machines/'.$machineName);
92
        }
93
94
        $event = new InstallEvent();
95
        $this->dispatcher->dispatch(InstallEvent::NAME, $event);
96
        $this->patches = array_merge($this->patches, $event->getPatches());
97
98
        $this->applyPatch();
99
    }
100
101
    private function applyPatch(): void
102
    {
103
        $fs = new Filesystem();
104
        foreach ($this->patches as $target => $patches) {
105
            $patchContents = implode("\n", $patches);
106
            if (!$this->dryRun) {
107
                $fs->patch($target, $patchContents);
108
            }
109
            $this->debug(
110
                sprintf(
111
                    '[patch] <comment>%s</comment>',
112
                            Toolkit::stripPath($target)
113
                )
114
            );
115
        }
116
    }
117
118
    private function copy(string $origin, string $target): void
119
    {
120
        if (!$this->dryRun) {
121
            $fs = new Filesystem();
122
            $fs->copy($origin, $target, array('overwriteNewerFiles' => $this->overwriteNewFiles));
0 ignored issues
show
Bug introduced by
array('overwriteNewerFil...his->overwriteNewFiles) of type array<string,boolean> is incompatible with the type boolean expected by parameter $overwriteNewerFiles of Symfony\Component\Filesystem\Filesystem::copy(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

122
            $fs->copy($origin, $target, /** @scrutinizer ignore-type */ array('overwriteNewerFiles' => $this->overwriteNewFiles));
Loading history...
123
        }
124
        $this->debug(sprintf(
125
            '[copy] <comment>%s</comment> to <comment>%s</comment>',
126
            Toolkit::stripPath($origin),
127
            Toolkit::stripPath($target)
128
        ));
129
    }
130
131
    private function debug($message, $context = array()): void
132
    {
133
        $this->logger->debug($message, $context);
134
    }
135
136
    private function doProcessBin($binDir): void
137
    {
138
        if (!is_dir($binDir)) {
139
            return;
140
        }
141
142
        $finder = Finder::create()
143
            ->in($binDir)
144
            ->ignoreVCS(true)
145
            ->ignoreDotFiles(false)
146
        ;
147
148
        /* @var SplFileInfo $file */
149
        foreach ($finder->files() as $file) {
150
            $target = $this->config->get('dotfiles.bin_dir').DIRECTORY_SEPARATOR.$file->getRelativePathName();
151
            $this->copy($file->getRealPath(), $target);
152
        }
153
    }
154
155
    private function doProcessInstallHook($hookDir): void
156
    {
157
        if (!is_dir($hookDir)) {
158
            return;
159
        }
160
        $finder = Finder::create()
161
            ->in($hookDir)
162
            ->name('install')
163
            ->name('install.sh')
164
        ;
165
        foreach ($finder->files() as $file) {
166
            $this->debug("executing <comment>$file</comment>");
167
            $cmd = $file;
168
            $process = new Process($cmd);
169
            $process->run(function ($type, $buffer): void {
170
                if (Process::ERR === $type) {
171
                    $this->output->writeln("Error: $buffer");
172
                } else {
173
                    $this->output->writeln($buffer);
174
                }
175
            });
176
        }
177
    }
178
179
    private function doProcessPatch($patchDir): void
180
    {
181
        if (!is_dir($patchDir)) {
182
            return;
183
        }
184
        $finder = Finder::create()
185
            ->in($patchDir)
186
        ;
187
        /* @var SplFileInfo $file */
188
        foreach ($finder->files() as $file) {
189
            $relativePathName = $this->normalizePathName($file->getRelativePathName());
190
            $target = $this->config->get('dotfiles.home_dir').DIRECTORY_SEPARATOR.$relativePathName;
191
            $patch = file_get_contents($file->getRealPath());
192
            if (!isset($this->patches[$target])) {
193
                $this->patches[$target] = array();
194
            }
195
            $this->patches[$target][] = $patch;
196
        }
197
    }
198
199
    private function doProcessTemplates($templateDir, $overwrite = false): void
0 ignored issues
show
Unused Code introduced by
The parameter $overwrite is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

199
    private function doProcessTemplates($templateDir, /** @scrutinizer ignore-unused */ $overwrite = false): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
200
    {
201
        $targetDir = $this->config->get('dotfiles.home_dir');
202
        if (!is_dir($templateDir)) {
203
            $this->debug("Template directory <comment>$templateDir</comment> not found");
204
205
            return;
206
        }
207
208
        $finder = Finder::create()
209
            ->in($templateDir)
210
            ->ignoreVCS(true)
211
            ->ignoreDotFiles(false)
212
            ->files()
213
        ;
214
        /* @var \Symfony\Component\Finder\SplFileInfo $file */
215
        foreach ($finder->files() as $file) {
216
            $source = $file->getRealPath();
217
            $relativePathName = $this->normalizePathName($file->getRelativePathname());
218
219
            $target = $targetDir.DIRECTORY_SEPARATOR.$relativePathName;
220
            $this->copy($source, $target);
221
        }
222
    }
223
224
    private function normalizePathName(string $relativePathName)
225
    {
226
        if (0 !== strpos($relativePathName, '.')) {
227
            $relativePathName = '.'.$relativePathName;
228
        }
229
230
        return $relativePathName;
231
    }
232
233
    private function processSection(OutputInterface $output, $section): void
234
    {
235
        $config = $this->config;
236
        $baseDir = $config->get('dotfiles.base_dir');
237
        $output->writeln("Processing <comment>$section</comment> section");
238
        $this->doProcessTemplates($baseDir.'/'.$section.'/templates');
239
        $this->doProcessPatch($baseDir.'/'.$section.'/patch');
240
        $this->doProcessBin($baseDir.'/'.$section.'/bin');
241
        $this->doProcessInstallHook($baseDir.'/'.$section.'/hooks');
242
    }
243
}
244