Completed
Push — master ( dcddf7...d2fb4d )
by Théo
05:16 queued 03:22
created

AddPrefixCommand::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 47
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 1

Importance

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