Passed
Push — master ( 3a3a08...a5b8f2 )
by Théo
02:09
created

AddPrefixCommand::makeAbsolutePath()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 3
Ratio 37.5 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 3
loc 8
ccs 4
cts 4
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the humbug/php-scoper package.
7
 *
8
 * Copyright (c) 2017 Théo FIDRY <[email protected]>,
9
 *                    Pádraic Brady <[email protected]>
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace Humbug\PhpScoper\Console\Command;
16
17
use Humbug\PhpScoper\Handler\HandleAddPrefix;
18
use Humbug\PhpScoper\Logger\ConsoleLogger;
19
use Symfony\Component\Console\Command\Command;
20
use Symfony\Component\Console\Exception\RuntimeException;
21
use Symfony\Component\Console\Input\InputArgument;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Input\InputOption;
24
use Symfony\Component\Console\Output\OutputInterface;
25
use Symfony\Component\Console\Style\OutputStyle;
26
use Symfony\Component\Console\Style\SymfonyStyle;
27
use Symfony\Component\Filesystem\Filesystem;
28
use Throwable;
29
30
final class AddPrefixCommand extends Command
31
{
32
    /** @internal */
33
    const PATH_ARG = 'paths';
34
    /** @internal */
35
    const PREFIX_OPT = 'prefix';
36
    /** @internal */
37
    const OUTPUT_DIR_OPT = 'output-dir';
38
    /** @internal */
39
    const FORCE_OPT = 'force';
40
    /** @internal */
41
    const STOP_ON_FAILURE_OPT = 'stop-on-failure';
42
    /** @internal */
43
    const PATCH_FILE = 'patch-file';
44
    /** @internal */
45
    const PATCH_FILE_DEFAULT = 'scoper.inc.php';
46
47
    private $fileSystem;
48
    private $handle;
49
50
    /**
51
     * @inheritdoc
52
     */
53 21
    public function __construct(Filesystem $fileSystem, HandleAddPrefix $handle)
54
    {
55 21
        parent::__construct();
56
57 21
        $this->fileSystem = $fileSystem;
58 21
        $this->handle = $handle;
59 21
    }
60
61
    /**
62
     * @inheritdoc
63
     */
64 21
    protected function configure()
65
    {
66
        $this
67 21
            ->setName('add-prefix')
68 21
            ->setDescription('Goes through all the PHP files found in the given paths to apply the given prefix to namespaces & FQNs.')
69 21
            ->addArgument(
70 21
                self::PATH_ARG,
71 21
                InputArgument::IS_ARRAY,
72 21
                'The path(s) to process.'
73
            )
74 21
            ->addOption(
75 21
                self::PREFIX_OPT,
76 21
                'p',
77 21
                InputOption::VALUE_REQUIRED,
78 21
                'The namespace prefix to add.'
79
            )
80 21
            ->addOption(
81 21
                self::OUTPUT_DIR_OPT,
82 21
                'o',
83 21
                InputOption::VALUE_REQUIRED,
84 21
                'The output directory in which the prefixed code will be dumped.',
85 21
                'build'
86
            )
87 21
            ->addOption(
88 21
                self::FORCE_OPT,
89 21
                'f',
90 21
                InputOption::VALUE_NONE,
91 21
                'Deletes any existing content in the output directory without any warning.'
92
            )
93 21
            ->addOption(
94 21
                self::STOP_ON_FAILURE_OPT,
95 21
                's',
96 21
                InputOption::VALUE_NONE,
97 21
                'Stops on failure.'
98
            )
99 21
            ->addOption(
100 21
                self::PATCH_FILE,
101 21
                'c',
102 21
                InputOption::VALUE_REQUIRED,
103 21
                sprintf(
104 21
                    'Configuration file for the patchers. Will use "%s" if found by default',
105 21
                    self::PATCH_FILE_DEFAULT
106
                ),
107 21
                null
108
            )
109
        ;
110 21
    }
111
112
    /**
113
     * @inheritdoc
114
     */
115 19
    protected function execute(InputInterface $input, OutputInterface $output)
116
    {
117 19
        $io = new SymfonyStyle($input, $output);
118
119 19
        $this->validatePrefix($input);
120 14
        $this->validatePaths($input);
121 14
        $this->validateOutputDir($input, $io);
122 14
        $patchers = $this->validatePatchers($input, $io);
123
124 12
        $logger = new ConsoleLogger(
125 12
            $this->getApplication(),
126 12
            $io
127
        );
128
129 12
        $logger->outputScopingStart(
130 12
            $input->getOption(self::PREFIX_OPT),
131 12
            $input->getArgument(self::PATH_ARG)
132
        );
133
134
        try {
135 12
            $this->handle->__invoke(
136 12
                $input->getOption(self::PREFIX_OPT),
137 12
                $input->getArgument(self::PATH_ARG),
138 12
                $input->getOption(self::OUTPUT_DIR_OPT),
139 12
                $patchers,
140 12
                $input->getOption(self::STOP_ON_FAILURE_OPT),
141 12
                $logger
142
            );
143 1
        } catch (Throwable $throwable) {
144 1
            $logger->outputScopingEndWithFailure();
145
146 1
            throw $throwable;
147
        }
148
149 11
        $logger->outputScopingEnd();
150 11
    }
151
152 19
    private function validatePrefix(InputInterface $input)
153
    {
154 19
        $prefix = $input->getOption(self::PREFIX_OPT);
155
156 19
        if (null === $prefix) {
157 1
            $prefix = uniqid('PhpScoper');
158
        } else {
159 18
            $prefix = trim($prefix);
160
        }
161
162 19
        if (1 === preg_match('/(?<prefix>.*?)\\\\*$/', $prefix, $matches)) {
163 19
            $prefix = $matches['prefix'];
164
        }
165
166 19
        if ('' === $prefix) {
167 5
            throw new RuntimeException(
168 5
                sprintf(
169 5
                    'Expected "%s" argument to be a non empty string.',
170 5
                    self::PREFIX_OPT
171
                )
172
            );
173
        }
174
175 14
        $input->setOption(self::PREFIX_OPT, $prefix);
176 14
    }
177
178 14
    private function validatePaths(InputInterface $input)
179
    {
180 14
        $cwd = getcwd();
181 14
        $fileSystem = $this->fileSystem;
182
183 14
        $paths = array_map(
184 14
            function (string $path) use ($cwd, $fileSystem) {
185 12
                if (false === $fileSystem->isAbsolutePath($path)) {
186 1
                    return $cwd.DIRECTORY_SEPARATOR.$path;
187
                }
188
189 12
                return $path;
190 14
            },
191 14
            $input->getArgument(self::PATH_ARG)
192
        );
193
194 14
        if (0 === count($paths)) {
195 2
            $paths[] = $cwd;
196
        }
197
198 14
        $input->setArgument(self::PATH_ARG, $paths);
199 14
    }
200
201 14
    private function validateOutputDir(InputInterface $input, OutputStyle $io)
202
    {
203 14
        $outputDir = $input->getOption(self::OUTPUT_DIR_OPT);
204
205 14 View Code Duplication
        if (false === $this->fileSystem->isAbsolutePath($outputDir)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
206 1
            $outputDir = getcwd().DIRECTORY_SEPARATOR.$outputDir;
207
        }
208
209 14
        $input->setOption(self::OUTPUT_DIR_OPT, $outputDir);
210
211 14
        if (false === $this->fileSystem->exists($outputDir)) {
212 14
            return;
213
        }
214
215
        if (false === is_writable($outputDir)) {
216
            throw new RuntimeException(
217
                sprintf(
218
                    'Expected "<comment>%s</comment>" to be writeable.',
219
                    $outputDir
220
                )
221
            );
222
        }
223
224
        if ($input->getOption(self::FORCE_OPT)) {
225
            $this->fileSystem->remove($outputDir);
226
227
            return;
228
        }
229
230
        if (false === is_dir($outputDir)) {
231
            $canDeleteFile = $io->confirm(
232
                sprintf(
233
                    'Expected "<comment>%s</comment>" to be a directory but found a file instead. It will be '
234
                    .'removed, do you wish to proceed?',
235
                    $outputDir
236
                ),
237
                false
238
            );
239
240
            if (false === $canDeleteFile) {
241
                return;
242
            }
243
244
            $this->fileSystem->remove($outputDir);
245
        } else {
246
            $canDeleteFile = $io->confirm(
247
                sprintf(
248
                    'The output directory "<comment>%s</comment>" already exists. Continuing will erase its'
249
                    .' content, do you wish to proceed?',
250
                    $outputDir
251
                ),
252
                false
253
            );
254
255
            if (false === $canDeleteFile) {
256
                return;
257
            }
258
259
            $this->fileSystem->remove($outputDir);
260
        }
261
    }
262
263
    /**
264
     * @param InputInterface $input
265
     * @param OutputStyle    $io
266
     *
267
     * @return callable[]
268
     */
269 14
    private function validatePatchers(InputInterface $input, OutputStyle $io): array
270
    {
271 14
        $patchFile = $input->getOption(self::PATCH_FILE);
272
273 14
        if (null === $patchFile) {
274 13
            $patchFile = $this->makeAbsolutePath(self::PATCH_FILE_DEFAULT);
275
276 13
            if (false === file_exists($patchFile)) {
277 3
                $io->writeln(
278 3
                    sprintf(
279 3
                        'Patch file "%s" not found. Skipping.',
280 3
                        $patchFile
281
                    ),
282 3
                    OutputStyle::VERBOSITY_DEBUG
283
                );
284
285 13
                return [];
286
            }
287
        } else {
288 1
            $patchFile = $this->makeAbsolutePath($patchFile);
289
        }
290
291 11
        if (false === file_exists($patchFile)) {
292 1
            throw new RuntimeException(
293 1
                sprintf(
294 1
                    'Could not find the file "%s".',
295 1
                    $patchFile
296
                )
297
            );
298
        }
299
300 10
        $io->writeln(
301 10
            sprintf(
302 10
                'Using the configuration file "%s".',
303 10
                $patchFile
304
            ),
305 10
            OutputStyle::VERBOSITY_DEBUG
306
        );
307
308 10
        $patchers = include $patchFile;
309
310 10
        if (false === is_array($patchers)) {
311
            throw new RuntimeException(
312
                sprintf(
313
                    'Expected patchers to be an array of callables, found "%s" instead.',
314
                    gettype($patchers)
315
                )
316
            );
317
        }
318
319 10
        foreach ($patchers as $index => $patcher) {
320 10
            if (false === is_callable($patcher)) {
321 1
                throw new RuntimeException(
322 1
                    sprintf(
323 1
                        'Expected patchers to be an array of callables, the "%d" element is not.',
324 10
                        $index
325
                    )
326
                );
327
            }
328
        }
329
330 9
        return $patchers;
331
    }
332
333 14
    private function makeAbsolutePath(string $path): string
334
    {
335 14 View Code Duplication
        if (false === $this->fileSystem->isAbsolutePath($path)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
336 11
            $path = getcwd().DIRECTORY_SEPARATOR.$path;
337
        }
338
339 14
        return $path;
340
    }
341
}
342