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