Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
73.60% covered (warning)
73.60%
131 / 178
48.08% covered (danger)
48.08%
25 / 52
CRAP
0.00% covered (danger)
0.00%
0 / 1
StraussConfig
73.60% covered (warning)
73.60%
131 / 178
48.08% covered (danger)
48.08%
25 / 52
353.25
0.00% covered (danger)
0.00%
0 / 1
 __construct
93.67% covered (success)
93.67%
74 / 79
0.00% covered (danger)
0.00%
0 / 1
28.20
 getTargetDirectory
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setTargetDirectory
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getVendorDirectory
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setVendorDirectory
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNamespacePrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 setNamespacePrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getClassmapPrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setClassmapPrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFunctionsPrefix
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 setFunctionsPrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getConstantsPrefix
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setConstantsPrefix
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUpdateCallSites
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setUpdateCallSites
61.54% covered (warning)
61.54%
8 / 13
0.00% covered (danger)
0.00%
0 / 1
15.69
 setExcludeFromCopy
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 getExcludePackagesFromCopy
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExcludeNamespacesFromCopy
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExcludeFilePatternsFromCopy
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setExcludeFromPrefix
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
4.59
 getExcludePackagesFromPrefixing
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setExcludePackagesFromPrefixing
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExcludeNamespacesFromPrefixing
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getExcludeFilePatternsFromPrefixing
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getOverrideAutoload
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setOverrideAutoload
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isDeleteVendorFiles
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isDeleteVendorPackages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDeleteVendorFiles
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDeleteVendorPackages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPackages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPackages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPackagesToCopy
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setPackagesToCopy
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPackagesToPrefix
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setPackagesToPrefix
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isClassmapOutput
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setClassmapOutput
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setExcludePackages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNamespaceReplacementPatterns
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setNamespaceReplacementPatterns
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isIncludeModifiedDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setIncludeModifiedDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isIncludeAuthor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setIncludeAuthor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isDryRun
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDryRun
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isIncludeRootAutoload
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setIncludeRootAutoload
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 updateFromCli
61.11% covered (warning)
61.11%
11 / 18
0.00% covered (danger)
0.00%
0 / 1
28.23
 isCreateAliases
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
12
 getProjectDirectory
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
1<?php
2/**
3 * The extra/strauss key in composer.json.
4 */
5
6namespace BrianHenryIE\Strauss\Composer\Extra;
7
8use BrianHenryIE\Strauss\Composer\ComposerPackage;
9use BrianHenryIE\Strauss\Config\AliasesConfigInterface;
10use BrianHenryIE\Strauss\Config\AutoloadConfigInterface;
11use BrianHenryIE\Strauss\Config\AutoloadFilesEnumeratorConfigInterface;
12use BrianHenryIE\Strauss\Config\ChangeEnumeratorConfigInterface;
13use BrianHenryIE\Strauss\Config\CleanupConfigInterface;
14use BrianHenryIE\Strauss\Config\CopierConfigInterface;
15use BrianHenryIE\Strauss\Config\MarkSymbolsForRenamingConfigInterface;
16use BrianHenryIE\Strauss\Config\FileCopyScannerConfigInterface;
17use BrianHenryIE\Strauss\Config\FileEnumeratorConfig;
18use BrianHenryIE\Strauss\Config\FileSymbolScannerConfigInterface;
19use BrianHenryIE\Strauss\Config\PrefixerConfigInterface;
20use BrianHenryIE\Strauss\Console\Commands\DependenciesCommand;
21use BrianHenryIE\Strauss\Pipeline\Autoload\DumpAutoload;
22use Composer\Composer;
23use Exception;
24use InvalidArgumentException;
25use JsonMapper\Enums\TextNotation;
26use JsonMapper\JsonMapperFactory;
27use JsonMapper\Middleware\CaseConversion;
28use JsonMapper\Middleware\Rename\Rename;
29use Symfony\Component\Console\Input\InputInterface;
30
31class StraussConfig implements
32    AliasesConfigInterface,
33    AutoloadConfigInterface,
34    AutoloadFilesEnumeratorConfigInterface,
35    ChangeEnumeratorConfigInterface,
36    CleanupConfigInterface,
37    CopierConfigInterface,
38    MarkSymbolsForRenamingConfigInterface,
39    FileSymbolScannerConfigInterface,
40    FileEnumeratorConfig,
41    FileCopyScannerConfigInterface,
42    PrefixerConfigInterface,
43    ReplaceConfigInterface
44{
45    /**
46     * The directory containing `composer.json`. Probably `cwd()`.
47     */
48    protected string $projectDirectory;
49
50    /**
51     * The output directory.
52     */
53    protected string $targetDirectory = 'vendor-prefixed';
54
55    /**
56     * The vendor directory.
57     *
58     * Probably 'vendor/'
59     */
60    protected string $vendorDirectory = 'vendor';
61
62    /**
63     * `namespacePrefix` is the prefix to be given to any namespaces.
64     * Presumably this will take the form `My_Project_Namespace\dep_directory`.
65     *
66     * @link https://www.php-fig.org/psr/psr-4/
67     */
68    protected ?string $namespacePrefix = null;
69
70    /**
71     *
72     */
73    protected ?string $classmapPrefix = null;
74
75    /**
76     * Null to disable. Otherwise, suggested it is all lowercase with a trailing underscore.
77     *
78     * @var string|bool|null
79     */
80    protected $functionsPrefix;
81
82    /**
83     * @var ?string
84     */
85    protected ?string $constantsPrefix = null;
86
87    /**
88     * Should replacements be performed in project files?
89     *
90     * When null, files in the project's `autoload` key are scanned and changes which have been performed on the
91     * vendor packages are reflected in the project files.
92     *
93     * When an array of relative file paths are provided, the files in those directories are updated.
94     *
95     * An empty array disables updating project files.
96     *
97     * @var ?string[]
98     */
99    protected ?array $updateCallSites = array();
100
101    /**
102     * Packages to copy and (maybe) prefix.
103     *
104     * If this is empty, the "requires" list in the project composer.json is used.
105     *
106     * @var string[]
107     */
108    protected array $packages = [];
109
110    /**
111     * @var array<string,ComposerPackage>
112     */
113    protected array $packagesToCopy = [];
114
115    /**
116     *
117     * @var array<string,ComposerPackage>
118     */
119    protected array $packagesToPrefix = [];
120
121    /**
122     * Back-compatibility with Mozart.
123     *
124     * @var string[]
125     */
126    private array $excludePackages;
127
128    /**
129     * 'exclude_from_copy' in composer/extra config.
130     *
131     * @var array{packages: string[], namespaces: string[], file_patterns: string[]}
132     */
133    protected array $excludeFromCopy = array('file_patterns'=>array(),'namespaces'=>array(),'packages'=>array());
134
135    /**
136     * @var array{packages: string[], namespaces: string[], file_patterns: string[]}
137     */
138    protected array $excludeFromPrefix = array('file_patterns'=>array(),'namespaces'=>array(),'packages'=>array());
139
140    /**
141     * An array of autoload keys to replace packages' existing autoload key.
142     *
143     * e.g. when
144     * * A package has no autoloader
145     * * A package specified both a PSR-4 and a classmap but only needs one
146     * ...
147     *
148     * @var array<string, array{files?:array<string>,classmap?:array<string>,"psr-4":array<string|array<string>>}>|array{} $overrideAutoload
149     */
150    protected array $overrideAutoload = [];
151
152    /**
153     * After completing prefixing should the source files be deleted?
154     * This does not affect symlinked directories.
155     */
156    protected bool $deleteVendorFiles = false;
157
158    /**
159     * After completing prefixing should the source packages be deleted?
160     * This does not affect symlinked directories.
161     */
162    protected bool $deleteVendorPackages = false;
163
164    protected bool $classmapOutput;
165
166    /**
167     * A dictionary of regex captures => regex replacements.
168     *
169     * E.g. used to avoid repetition of the plugin vendor name in namespaces.
170     * `"~BrianHenryIE\\\\(.*)~" : "BrianHenryIE\\WC_Cash_App_Gateway\\\\$1"`.
171     *
172     * @var array<string, string> $namespaceReplacementPatterns
173     */
174    protected array $namespaceReplacementPatterns = array();
175
176    /**
177     * Should a modified date be included in the header for modified files?
178     */
179    protected bool $includeModifiedDate = true;
180
181    /**
182     * Should the author name be included in the header for modified files?
183     */
184    protected bool $includeAuthor = true;
185
186    /**
187     * Should the changes be printed to console rather than files modified?
188     */
189    protected bool $dryRun = false;
190
191    /**
192     * Should the root autoload be included when generating the strauss autoloader?
193     */
194    protected bool $includeRootAutoload = false;
195
196    /**
197     * Read any existing Mozart config.
198     * Overwrite it with any Strauss config.
199     * Provide sensible defaults.
200     *
201     * @param ?Composer $composer
202     *
203     * @throws Exception
204     */
205    public function __construct(?Composer $composer = null)
206    {
207
208        $configExtraSettings = null;
209
210        // Backwards compatibility with Mozart.
211        if (isset($composer, $composer->getPackage()->getExtra()['mozart'])) {
212            $configExtraSettings = (object)$composer->getPackage()->getExtra()['mozart'];
213
214            // Default setting for Mozart.
215            $this->setDeleteVendorFiles(true);
216        }
217
218        if (isset($composer, $composer->getPackage()->getExtra()['strauss'])) {
219            $configExtraSettings = (object)$composer->getPackage()->getExtra()['strauss'];
220        }
221
222        if (!is_null($configExtraSettings)) {
223            $mapper = (new JsonMapperFactory())->bestFit();
224
225            $rename = new Rename();
226            $rename->addMapping(StraussConfig::class, 'dep_directory', 'targetDirectory');
227            $rename->addMapping(StraussConfig::class, 'dep_namespace', 'namespacePrefix');
228
229            $rename->addMapping(StraussConfig::class, 'exclude_packages', 'excludePackages');
230            $rename->addMapping(StraussConfig::class, 'delete_vendor_files', 'deleteVendorFiles');
231            $rename->addMapping(StraussConfig::class, 'delete_vendor_packages', 'deleteVendorPackages');
232
233            $rename->addMapping(StraussConfig::class, 'exclude_prefix_packages', 'excludePackagesFromPrefixing');
234
235            $rename->addMapping(StraussConfig::class, 'include_root_autoload', 'includeRootAutoload');
236
237            $rename->addMapping(StraussConfig::class, 'function_prefix', 'functionsPrefix');
238
239            $mapper->unshift($rename);
240            $mapper->push(new CaseConversion(TextNotation::UNDERSCORE(), TextNotation::CAMEL_CASE()));
241
242            $mapper->mapObject($configExtraSettings, $this);
243        }
244
245        // Defaults.
246        // * Use PSR-4 autoloader key
247        // * Use PSR-0 autoloader key
248        // * Use the package name
249        if (! isset($this->namespacePrefix)) {
250            if (isset($composer, $composer->getPackage()->getAutoload()['psr-4'])) {
251                $this->setNamespacePrefix(array_key_first($composer->getPackage()->getAutoload()['psr-4']));
252            } elseif (isset($composer, $composer->getPackage()->getAutoload()['psr-0'])) {
253                $this->setNamespacePrefix(array_key_first($composer->getPackage()->getAutoload()['psr-0']));
254            } elseif (isset($composer) && '__root__' !== $composer->getPackage()->getName()) {
255                $packageName = $composer->getPackage()->getName();
256                $namespacePrefix = preg_replace('/[^\w\/]+/', '_', $packageName);
257                $namespacePrefix = str_replace('/', '\\', $namespacePrefix) . '\\';
258                $namespacePrefix = preg_replace_callback('/(?<=^|_|\\\\)[a-z]/', function ($match) {
259                    return strtoupper($match[0]);
260                }, $namespacePrefix);
261                $this->setNamespacePrefix($namespacePrefix);
262            } elseif (isset($this->classmapPrefix)) {
263                $namespacePrefix = rtrim($this->getClassmapPrefix(), '_');
264                $this->setNamespacePrefix($namespacePrefix);
265            }
266        }
267
268        if (! isset($this->classmapPrefix)) {
269            if (isset($composer, $composer->getPackage()->getAutoload()['psr-4'])) {
270                $autoloadKey = array_key_first($composer->getPackage()->getAutoload()['psr-4']);
271                $classmapPrefix = str_replace("\\", "_", $autoloadKey);
272                $this->setClassmapPrefix($classmapPrefix);
273            } elseif (isset($composer, $composer->getPackage()->getAutoload()['psr-0'])) {
274                $autoloadKey = array_key_first($composer->getPackage()->getAutoload()['psr-0']);
275                $classmapPrefix = str_replace("\\", "_", $autoloadKey);
276                $this->setClassmapPrefix($classmapPrefix);
277            } elseif (isset($composer) && '__root__' !== $composer->getPackage()->getName()) {
278                $packageName = $composer->getPackage()->getName();
279                $classmapPrefix = preg_replace('/[^\w\/]+/', '_', $packageName);
280                $classmapPrefix = str_replace('/', '\\', $classmapPrefix);
281                // Uppercase the first letter of each word.
282                $classmapPrefix = preg_replace_callback('/(?<=^|_|\\\\)[a-z]/', function ($match) {
283                    return strtoupper($match[0]);
284                }, $classmapPrefix);
285                $classmapPrefix = str_replace("\\", "_", $classmapPrefix);
286                $this->setClassmapPrefix($classmapPrefix);
287            } elseif (isset($this->namespacePrefix)) {
288                $classmapPrefix = preg_replace('/[^\w\/]+/', '_', $this->getNamespacePrefix());
289                $classmapPrefix = rtrim($classmapPrefix, '_') . '_';
290                $this->setClassmapPrefix($classmapPrefix);
291            }
292        }
293
294//        if (!isset($this->namespacePrefix) || !isset($this->classmapPrefix)) {
295//            throw new Exception('Prefix not set. Please set `namespace_prefix`, `classmap_prefix` in composer.json/extra/strauss.');
296//        }
297
298        if (isset($composer) && empty($this->packages)) {
299            $this->packages = array_map(function (\Composer\Package\Link $element) {
300                return $element->getTarget();
301            }, $composer->getPackage()->getRequires());
302        }
303
304        // If the bool flag for classmapOutput wasn't set in the JSON config.
305        if (!isset($this->classmapOutput)) {
306            $this->classmapOutput = true;
307            // Check each autoloader.
308            if (isset($composer)) {
309                foreach ($composer->getPackage()->getAutoload() as $autoload) {
310                    // To see if one of its paths.
311                    foreach ($autoload as $entry) {
312                        $paths = (array) $entry;
313                        foreach ($paths as $path) {
314                            // Matches the target directory.
315                            if (trim($path, '\\/') . '/' === $this->getTargetDirectory()) {
316                                $this->classmapOutput = false;
317                                break 3;
318                            }
319                        }
320                    }
321                }
322            }
323        }
324
325        // TODO: Throw an exception if any regex patterns in config are invalid.
326        // https://stackoverflow.com/questions/4440626/how-can-i-validate-regex
327        // preg_match('~Valid(Regular)Expression~', null) === false);
328
329        if (isset($configExtraSettings, $configExtraSettings->updateCallSites)) {
330            if (true === $configExtraSettings->updateCallSites) {
331                $this->updateCallSites = null;
332            } elseif (false === $configExtraSettings->updateCallSites) {
333                $this->updateCallSites = array();
334            } elseif (is_array($configExtraSettings->updateCallSites)) {
335                $this->updateCallSites = $configExtraSettings->updateCallSites;
336            } else {
337                // uh oh.
338            }
339        }
340    }
341
342    /**
343     * `target_directory` will always be returned without a leading slash and with a trailing slash.
344     *
345     * @return string
346     */
347    public function getTargetDirectory(): string
348    {
349        return $this->getProjectDirectory() . trim($this->targetDirectory, '\\/') . '/';
350    }
351
352    /**
353     * @param string $targetDirectory
354     */
355    public function setTargetDirectory(string $targetDirectory): void
356    {
357        $this->targetDirectory = $targetDirectory;
358    }
359
360    /**
361     * @return string
362     */
363    public function getVendorDirectory(): string
364    {
365        return $this->getProjectDirectory() . trim($this->vendorDirectory, '\\/') . '/';
366    }
367
368    /**
369     * @param string $vendorDirectory
370     */
371    public function setVendorDirectory(string $vendorDirectory): void
372    {
373        $this->vendorDirectory = $vendorDirectory;
374    }
375
376    /**
377     * With no trailing slash and no leading slash.
378     */
379    public function getNamespacePrefix(): ?string
380    {
381        return !isset($this->namespacePrefix) ? null : trim($this->namespacePrefix, '\\');
382    }
383
384    /**
385     * @param string $namespacePrefix
386     */
387    public function setNamespacePrefix(string $namespacePrefix): void
388    {
389        $this->namespacePrefix = $namespacePrefix;
390    }
391
392    /**
393     * @return string
394     */
395    public function getClassmapPrefix(): ?string
396    {
397        return $this->classmapPrefix;
398    }
399
400    /**
401     * @param string $classmapPrefix
402     */
403    public function setClassmapPrefix(string $classmapPrefix): void
404    {
405        $this->classmapPrefix = $classmapPrefix;
406    }
407
408    public function getFunctionsPrefix(): ?string
409    {
410        if (!isset($this->functionsPrefix)) {
411            return strtolower($this->getClassmapPrefix());
412        }
413        if (empty($this->functionsPrefix)) {
414            return null;
415        }
416        if (is_string($this->functionsPrefix)) {
417            return $this->functionsPrefix;
418        }
419        return null;
420    }
421
422    /**
423     * @param string|bool|null $functionsPrefix
424     */
425    public function setFunctionsPrefix($functionsPrefix): void
426    {
427        $this->functionsPrefix = $functionsPrefix;
428    }
429
430    /**
431     * @return string
432     */
433    public function getConstantsPrefix(): ?string
434    {
435        return $this->constantsPrefix;
436    }
437
438    /**
439     * @param string $constantsPrefix
440     */
441    public function setConstantsPrefix(string $constantsPrefix): void
442    {
443        $this->constantsPrefix = $constantsPrefix;
444    }
445
446    /**
447     * List of files and directories to update call sites in. Empty to disable. Null infers from the project's autoload key.
448     *
449     * @return string[]|null
450     */
451    public function getUpdateCallSites(): ?array
452    {
453        return $this->updateCallSites;
454    }
455
456    /**
457     * @param string[]|array{0:bool}|null $updateCallSites
458     * @throws InvalidArgumentException
459     */
460    public function setUpdateCallSites($updateCallSites): void
461    {
462        if (is_array($updateCallSites) && count($updateCallSites) === 1 && $updateCallSites[0] === true) {
463            // Setting `null` instructs Strauss to update call sites in the project's autoload key.
464            $this->updateCallSites = null;
465            return;
466        } elseif (is_array($updateCallSites) && count($updateCallSites) === 1 && $updateCallSites[0] === false) {
467            $this->updateCallSites = array();
468            return;
469        } elseif (is_array($updateCallSites) && isset($updateCallSites[0]) && !is_bool($updateCallSites[0])) {
470            $this->updateCallSites = array_filter(
471                $updateCallSites,
472                'is_string'
473            );
474            return;
475        }
476        throw new InvalidArgumentException('Unexpected value for updateCallSites');
477    }
478
479    /**
480     * @param array{packages?:array<string>, namespaces?:array<string>, file_patterns?:array<string>} $excludeFromCopy
481     */
482    public function setExcludeFromCopy(array $excludeFromCopy): void
483    {
484        foreach (array( 'packages', 'namespaces', 'file_patterns' ) as $key) {
485            if (isset($excludeFromCopy[$key])) {
486                $this->excludeFromCopy[$key] = $excludeFromCopy[$key];
487            }
488        }
489    }
490
491    /**
492     * @return string[]
493     */
494    public function getExcludePackagesFromCopy(): array
495    {
496        return $this->excludeFromCopy['packages'] ?? array();
497    }
498
499    /**
500     * @return string[]
501     */
502    public function getExcludeNamespacesFromCopy(): array
503    {
504        return $this->excludeFromCopy['namespaces'] ?? array();
505    }
506
507    /**
508     * @return string[]
509     */
510    public function getExcludeFilePatternsFromCopy(): array
511    {
512        return $this->excludeFromCopy['file_patterns'] ?? array();
513    }
514
515    /**
516     * @param array{packages?:array<string>, namespaces?:array<string>, file_patterns?:array<string>} $excludeFromPrefix
517     */
518    public function setExcludeFromPrefix(array $excludeFromPrefix): void
519    {
520        if (isset($excludeFromPrefix['packages'])) {
521            $this->excludeFromPrefix['packages'] = $excludeFromPrefix['packages'];
522        }
523        if (isset($excludeFromPrefix['namespaces'])) {
524            $this->excludeFromPrefix['namespaces'] = $excludeFromPrefix['namespaces'];
525        }
526        if (isset($excludeFromPrefix['file_patterns'])) {
527            $this->excludeFromPrefix['file_patterns'] = $excludeFromPrefix['file_patterns'];
528        }
529    }
530
531    /**
532     * When prefixing, do not prefix these packages (which have been copied).
533     *
534     * @return string[]
535     */
536    public function getExcludePackagesFromPrefixing(): array
537    {
538        return $this->excludeFromPrefix['packages'] ?? [];
539    }
540
541    /**
542     * @param string[] $excludePackagesFromPrefixing
543     */
544    public function setExcludePackagesFromPrefixing(array $excludePackagesFromPrefixing): void
545    {
546        $this->excludeFromPrefix['packages'] = $excludePackagesFromPrefixing;
547    }
548
549    /**
550     * @return string[]
551     */
552    public function getExcludeNamespacesFromPrefixing(): array
553    {
554        return array_map(
555            fn(string $packageName) => trim($packageName, '\\/'),
556            $this->excludeFromPrefix['namespaces'] ?? []
557        );
558    }
559
560    /**
561     * @return string[]
562     */
563    public function getExcludeFilePatternsFromPrefixing(): array
564    {
565        return $this->excludeFromPrefix['file_patterns'] ?? array();
566    }
567
568
569    /**
570     * @return array{}|array<string, array{files?:array<string>,classmap?:array<string>,"psr-4":array<string|array<string>>}> $overrideAutoload Dictionary of package name: autoload rules.
571     */
572    public function getOverrideAutoload(): array
573    {
574        return $this->overrideAutoload;
575    }
576
577    /**
578     * @param array<string, array{files?:array<string>,classmap?:array<string>,"psr-4":array<string|array<string>>}> $overrideAutoload Dictionary of package name: autoload rules.
579     */
580    public function setOverrideAutoload(array $overrideAutoload): void
581    {
582        $this->overrideAutoload = $overrideAutoload;
583    }
584
585    /**
586     * @return bool
587     */
588    public function isDeleteVendorFiles(): bool
589    {
590        return $this->deleteVendorFiles;
591    }
592
593    /**
594     * @return bool
595     */
596    public function isDeleteVendorPackages(): bool
597    {
598        return $this->deleteVendorPackages;
599    }
600
601    /**
602     * @param bool $deleteVendorFiles
603     */
604    public function setDeleteVendorFiles(bool $deleteVendorFiles): void
605    {
606        $this->deleteVendorFiles = $deleteVendorFiles;
607    }
608
609    /**
610     * @param bool $deleteVendorPackages
611     */
612    public function setDeleteVendorPackages(bool $deleteVendorPackages): void
613    {
614        $this->deleteVendorPackages = $deleteVendorPackages;
615    }
616
617    /**
618     * @return string[]
619     */
620    public function getPackages(): array
621    {
622        return $this->packages;
623    }
624
625    /**
626     * @param string[] $packages
627     */
628    public function setPackages(array $packages): void
629    {
630        $this->packages = $packages;
631    }
632
633    /**
634     * @used-by DumpAutoload::createInstalledVersionsFiles()
635     * @return array<string,ComposerPackage>
636     */
637    public function getPackagesToCopy(): array
638    {
639        return $this->packagesToCopy;
640    }
641
642    /**
643     * @used-by DependenciesCommand::buildDependencyList()
644     *
645     * @param array<string,ComposerPackage> $packagesToCopy
646     */
647    public function setPackagesToCopy(array $packagesToCopy): void
648    {
649        $this->packagesToCopy = $packagesToCopy;
650    }
651
652    /**
653     * @return array<string,ComposerPackage>
654     */
655    public function getPackagesToPrefix(): array
656    {
657        return $this->packagesToPrefix;
658    }
659
660    /**
661     * @param array<string,ComposerPackage> $packagesToPrefix
662     */
663    public function setPackagesToPrefix(array $packagesToPrefix): void
664    {
665        $this->packagesToPrefix = $packagesToPrefix;
666    }
667    /**
668     * TODO: Can we name this `isClassmapOutputEnabled`?
669     */
670    public function isClassmapOutput(): bool
671    {
672        return $this->classmapOutput;
673    }
674
675    /**
676     * @param bool $classmapOutput
677     */
678    public function setClassmapOutput(bool $classmapOutput): void
679    {
680        $this->classmapOutput = $classmapOutput;
681    }
682
683    /**
684     * Backwards compatibility with Mozart.
685     *
686     * @param string[] $excludePackages
687     */
688    public function setExcludePackages(array $excludePackages): void
689    {
690        $this->excludeFromPrefix['packages'] = $excludePackages;
691    }
692
693    /**
694     * @return array<string,string>
695     */
696    public function getNamespaceReplacementPatterns(): array
697    {
698        return $this->namespaceReplacementPatterns;
699    }
700
701    /**
702     * @param array<string,string> $namespaceReplacementPatterns
703     */
704    public function setNamespaceReplacementPatterns(array $namespaceReplacementPatterns): void
705    {
706        $this->namespaceReplacementPatterns = $namespaceReplacementPatterns;
707    }
708
709    /**
710     * @return bool
711     */
712    public function isIncludeModifiedDate(): bool
713    {
714        return $this->includeModifiedDate;
715    }
716
717    /**
718     * @param bool $includeModifiedDate
719     */
720    public function setIncludeModifiedDate(bool $includeModifiedDate): void
721    {
722        $this->includeModifiedDate = $includeModifiedDate;
723    }
724
725
726    /**
727     * @return bool
728     */
729    public function isIncludeAuthor(): bool
730    {
731        return $this->includeAuthor;
732    }
733
734    /**
735     * @param bool $includeAuthor
736     */
737    public function setIncludeAuthor(bool $includeAuthor): void
738    {
739        $this->includeAuthor = $includeAuthor;
740    }
741
742    /**
743     * Should expected changes be printed to console rather than files modified?
744     */
745    public function isDryRun(): bool
746    {
747        return $this->dryRun;
748    }
749
750    /**
751     * Disable making changes to files; output changes to console instead.
752     */
753    public function setDryRun(bool $dryRun): void
754    {
755        $this->dryRun = $dryRun;
756    }
757
758    /**
759     * Should the root autoload be included when generating the strauss autoloader?
760     */
761    public function isIncludeRootAutoload(): bool
762    {
763        return $this->includeRootAutoload;
764    }
765
766    /**
767     * @param bool $includeRootAutoload Include the project root autoload in the strauss autoloader.
768     */
769    public function setIncludeRootAutoload(bool $includeRootAutoload): void
770    {
771        $this->includeRootAutoload = $includeRootAutoload;
772    }
773
774    /**
775     * @param InputInterface $input To access the command line options.
776     */
777    public function updateFromCli(InputInterface $input): void
778    {
779
780        // strauss --updateCallSites=false (default)
781        // strauss --updateCallSites=true
782        // strauss --updateCallSites=src,input,extra
783
784        if ($input->hasOption('updateCallSites') && $input->getOption('updateCallSites') !== null) {
785            $updateCallSitesInput = $input->getOption('updateCallSites');
786
787            if ('false' === $updateCallSitesInput) {
788                $this->updateCallSites = array();
789            } elseif ('true' === $updateCallSitesInput) {
790                $this->updateCallSites = null;
791            } elseif (is_string($updateCallSitesInput)) {
792                $this->updateCallSites = explode(',', $updateCallSitesInput);
793            }
794        }
795
796        if ($input->hasOption('deleteVendorPackages')  && $input->getOption('deleteVendorPackages') !== false) {
797            $isDeleteVendorPackagesCommandLine = $input->getOption('deleteVendorPackages') === 'true'
798                || $input->getOption('deleteVendorPackages') === null;
799            $this->setDeleteVendorPackages($isDeleteVendorPackagesCommandLine);
800        } elseif ($input->hasOption('delete_vendor_packages') && $input->getOption('delete_vendor_packages') !== false) {
801            $isDeleteVendorPackagesCommandLine = $input->getOption('delete_vendor_packages') === 'true'
802                || $input->getOption('delete_vendor_packages') === null;
803            $this->setDeleteVendorPackages($isDeleteVendorPackagesCommandLine);
804        }
805
806        if ($input->hasOption('dry-run') && $input->getOption('dry-run') !== false) {
807            // If we're here, the parameter was passed in the CLI command.
808            $this->dryRun = empty($input->getOption('dry-run')) || (bool)filter_var($input->getOption('dry-run'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
809        }
810    }
811
812    /**
813     * Should we create the `autoload_aliases.php` file in `vendor/composer`?
814     *
815     * TODO:
816     * [x] YES when we are deleting vendor packages or files
817     * [ ] NO when we are running composer install `--no-dev`
818     * [ ] SOMETIMES: see https://github.com/BrianHenryIE/strauss/issues/144
819     * [ ] Add `aliases` to `extra` in `composer.json`
820     * [ ] Add `--aliases=true` CLI option
821     */
822    public function isCreateAliases(): bool
823    {
824        return $this->deleteVendorPackages || $this->deleteVendorFiles || trim($this->targetDirectory, '\\/') === 'vendor';
825    }
826
827    public function getProjectDirectory(): string
828    {
829        $projectDirectory = $this->projectDirectory ?? getcwd() . '/';
830
831        return $this->isDryRun()
832            ? 'mem:/' . $projectDirectory
833            : $projectDirectory;
834    }
835}