Completed
Push — master ( 944618...6c9c0e )
by Théo
02:44
created

AddPrefixCommand::retrievePaths()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 6
nop 2
dl 0
loc 18
ccs 9
cts 9
cp 1
crap 4
rs 9.2
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\Console\Configuration;
18
use Humbug\PhpScoper\Handler\HandleAddPrefix;
19
use Humbug\PhpScoper\Logger\ConsoleLogger;
20
use Symfony\Component\Console\Command\Command;
21
use Symfony\Component\Console\Exception\RuntimeException;
22
use Symfony\Component\Console\Input\InputArgument;
23
use Symfony\Component\Console\Input\InputInterface;
24
use Symfony\Component\Console\Input\InputOption;
25
use Symfony\Component\Console\Output\OutputInterface;
26
use Symfony\Component\Console\Style\OutputStyle;
27
use Symfony\Component\Console\Style\SymfonyStyle;
28
use Symfony\Component\Filesystem\Filesystem;
29
use Throwable;
30
31
final class AddPrefixCommand extends Command
32
{
33
    /** @internal */
34
    const PATH_ARG = 'paths';
35
    /** @internal */
36
    const PREFIX_OPT = 'prefix';
37
    /** @internal */
38
    const OUTPUT_DIR_OPT = 'output-dir';
39
    /** @internal */
40
    const FORCE_OPT = 'force';
41
    /** @internal */
42
    const STOP_ON_FAILURE_OPT = 'stop-on-failure';
43
    /** @internal */
44
    const CONFIG_FILE_OPT = 'config';
45
    /** @internal */
46
    const CONFIG_FILE_DEFAULT = 'scoper.inc.php';
47
    /** @internal */
48
    const WORKING_DIR_OPT = 'working-dir';
49
50
    private $fileSystem;
51
    private $handle;
52
53
    /**
54
     * @inheritdoc
55
     */
56 23
    public function __construct(Filesystem $fileSystem, HandleAddPrefix $handle)
57
    {
58 23
        parent::__construct();
59
60 23
        $this->fileSystem = $fileSystem;
61 23
        $this->handle = $handle;
62
    }
63
64
    /**
65
     * @inheritdoc
66
     */
67 23
    protected function configure()
68
    {
69
        $this
70 23
            ->setName('add-prefix')
71 23
            ->setDescription('Goes through all the PHP files found in the given paths to apply the given prefix to namespaces & FQNs.')
72 23
            ->addArgument(
73 23
                self::PATH_ARG,
74 23
                InputArgument::IS_ARRAY,
75 23
                'The path(s) to process.'
76
            )
77 23
            ->addOption(
78 23
                self::PREFIX_OPT,
79 23
                'p',
80 23
                InputOption::VALUE_REQUIRED,
81 23
                'The namespace prefix to add.'
82
            )
83 23
            ->addOption(
84 23
                self::OUTPUT_DIR_OPT,
85 23
                'o',
86 23
                InputOption::VALUE_REQUIRED,
87 23
                'The output directory in which the prefixed code will be dumped.',
88 23
                'build'
89
            )
90 23
            ->addOption(
91 23
                self::FORCE_OPT,
92 23
                'f',
93 23
                InputOption::VALUE_NONE,
94 23
                'Deletes any existing content in the output directory without any warning.'
95
            )
96 23
            ->addOption(
97 23
                self::STOP_ON_FAILURE_OPT,
98 23
                's',
99 23
                InputOption::VALUE_NONE,
100 23
                'Stops on failure.'
101
            )
102 23
            ->addOption(
103 23
                self::CONFIG_FILE_OPT,
104 23
                'c',
105 23
                InputOption::VALUE_REQUIRED,
106 23
                sprintf(
107 23
                    'Configuration file. Will use "%s" if found by default',
108 23
                    self::CONFIG_FILE_DEFAULT
109
                ),
110 23
                null
111
            )
112 23
            ->addOption(
113 23
                self::WORKING_DIR_OPT,
114 23
                'd',
115 23
                InputOption::VALUE_REQUIRED,
116 23
                'If specified, use the given directory as working directory.',
117 23
                null
118
            )
119
        ;
120
    }
121
122
    /**
123
     * @inheritdoc
124
     */
125 21
    protected function execute(InputInterface $input, OutputInterface $output)
126
    {
127 21
        $io = new SymfonyStyle($input, $output);
128
129 21
        $workingDir = $input->getOption(self::WORKING_DIR_OPT);
130
131 21
        if (null !== $workingDir) {
132
            chdir($workingDir);
133
        }
134
135 21
        $this->validatePrefix($input);
136 16
        $this->validatePaths($input);
137 16
        $this->validateOutputDir($input, $io);
138
139 16
        $config = $this->retrieveConfig($input, $io);
140
141 14
        $logger = new ConsoleLogger(
142 14
            $this->getApplication(),
143 14
            $io
144
        );
145
146 14
        $logger->outputScopingStart(
147 14
            $input->getOption(self::PREFIX_OPT),
148 14
            $input->getArgument(self::PATH_ARG)
149
        );
150
151 14
        $paths = $this->retrievePaths($input, $config);
152
153
        try {
154 14
            $this->handle->__invoke(
155 14
                $input->getOption(self::PREFIX_OPT),
156 14
                $paths,
157 14
                $input->getOption(self::OUTPUT_DIR_OPT),
158 14
                $config->getPatchers(),
159 14
                $config->getWhitelist(),
160 14
                $config->getGlobalNamespaceWhitelisters(),
161 14
                $input->getOption(self::STOP_ON_FAILURE_OPT),
162 14
                $logger
163
            );
164 1
        } catch (Throwable $throwable) {
165 1
            $logger->outputScopingEndWithFailure();
166
167 1
            throw $throwable;
168
        }
169
170 13
        $logger->outputScopingEnd();
171
    }
172
173 21
    private function validatePrefix(InputInterface $input)
174
    {
175 21
        $prefix = $input->getOption(self::PREFIX_OPT);
176
177 21
        if (null === $prefix) {
178 1
            $prefix = uniqid('PhpScoper');
179
        } else {
180 20
            $prefix = trim($prefix);
181
        }
182
183 21
        if (1 === preg_match('/(?<prefix>.*?)\\\\*$/', $prefix, $matches)) {
184 21
            $prefix = $matches['prefix'];
185
        }
186
187 21
        if ('' === $prefix) {
188 5
            throw new RuntimeException(
189 5
                sprintf(
190 5
                    'Expected "%s" argument to be a non empty string.',
191 5
                    self::PREFIX_OPT
192
                )
193
            );
194
        }
195
196 16
        $input->setOption(self::PREFIX_OPT, $prefix);
197
    }
198
199 16
    private function validatePaths(InputInterface $input)
200
    {
201 16
        $cwd = getcwd();
202 16
        $fileSystem = $this->fileSystem;
203
204 16
        $paths = array_map(
205 16
            function (string $path) use ($cwd, $fileSystem) {
206 14
                if (false === $fileSystem->isAbsolutePath($path)) {
207 1
                    return $cwd.DIRECTORY_SEPARATOR.$path;
208
                }
209
210 14
                return $path;
211 16
            },
212 16
            $input->getArgument(self::PATH_ARG)
213
        );
214
215 16
        $input->setArgument(self::PATH_ARG, $paths);
216
    }
217
218 16
    private function validateOutputDir(InputInterface $input, OutputStyle $io)
219
    {
220 16
        $outputDir = $input->getOption(self::OUTPUT_DIR_OPT);
221
222 16 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...
223 1
            $outputDir = getcwd().DIRECTORY_SEPARATOR.$outputDir;
224
        }
225
226 16
        $input->setOption(self::OUTPUT_DIR_OPT, $outputDir);
227
228 16
        if (false === $this->fileSystem->exists($outputDir)) {
229 16
            return;
230
        }
231
232
        if (false === is_writable($outputDir)) {
233
            throw new RuntimeException(
234
                sprintf(
235
                    'Expected "<comment>%s</comment>" to be writeable.',
236
                    $outputDir
237
                )
238
            );
239
        }
240
241
        if ($input->getOption(self::FORCE_OPT)) {
242
            $this->fileSystem->remove($outputDir);
243
244
            return;
245
        }
246
247
        if (false === is_dir($outputDir)) {
248
            $canDeleteFile = $io->confirm(
249
                sprintf(
250
                    'Expected "<comment>%s</comment>" to be a directory but found a file instead. It will be '
251
                    .'removed, do you wish to proceed?',
252
                    $outputDir
253
                ),
254
                false
255
            );
256
257
            if (false === $canDeleteFile) {
258
                return;
259
            }
260
261
            $this->fileSystem->remove($outputDir);
262
        } else {
263
            $canDeleteFile = $io->confirm(
264
                sprintf(
265
                    'The output directory "<comment>%s</comment>" already exists. Continuing will erase its'
266
                    .' content, do you wish to proceed?',
267
                    $outputDir
268
                ),
269
                false
270
            );
271
272
            if (false === $canDeleteFile) {
273
                return;
274
            }
275
276
            $this->fileSystem->remove($outputDir);
277
        }
278
    }
279
280 16
    private function retrieveConfig(InputInterface $input, OutputStyle $io): Configuration
281
    {
282 16
        $configFile = $input->getOption(self::CONFIG_FILE_OPT);
283
284 16
        if (null === $configFile) {
285 15
            $configFile = $this->makeAbsolutePath(self::CONFIG_FILE_DEFAULT);
286
287 15
            if (false === file_exists($configFile)) {
288 12
                $io->writeln(
289 12
                    sprintf(
290 12
                        'Config file "%s" not found. Skipping.',
291 12
                        $configFile
292
                    ),
293 12
                    OutputStyle::VERBOSITY_DEBUG
294
                );
295
296 12
                return Configuration::load(null);
297
            }
298
        } else {
299 1
            $configFile = $this->makeAbsolutePath($configFile);
300
        }
301
302 4
        if (false === file_exists($configFile)) {
303 1
            throw new RuntimeException(
304 1
                sprintf(
305 1
                    'Could not find the file "%s".',
306 1
                    $configFile
307
                )
308
            );
309
        }
310
311 3
        $io->writeln(
312 3
            sprintf(
313 3
                'Using the configuration file "%s".',
314 3
                $configFile
315
            ),
316 3
            OutputStyle::VERBOSITY_DEBUG
317
        );
318
319 3
        return Configuration::load($configFile);
320
    }
321
322
    /**
323
     * @param InputInterface $input
324
     * @param Configuration  $configuration
325
     *
326
     * @return string[] List of absolute paths
327
     */
328 14
    private function retrievePaths(InputInterface $input, Configuration $configuration): array
329
    {
330 14
        $paths = $input->getArgument(self::PATH_ARG);
331
332 14
        $finders = $configuration->getFinders();
333
334 14
        foreach ($finders as $finder) {
335 1
            foreach ($finder as $file) {
336 1
                $paths[] = $file->getRealPath();
337
            }
338
        }
339
340 14
        if (0 === count($paths)) {
341 2
            return [getcwd()];
342
        }
343
344 12
        return array_unique($paths);
345
    }
346
347 16
    private function makeAbsolutePath(string $path): string
348
    {
349 16 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...
350 13
            $path = getcwd().DIRECTORY_SEPARATOR.$path;
351
        }
352
353 16
        return $path;
354
    }
355
}
356