Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.42% covered (warning)
75.42%
135 / 179
51.92% covered (warning)
51.92%
27 / 52
CRAP
0.00% covered (danger)
0.00%
0 / 1
StraussConfig
75.42% covered (warning)
75.42%
135 / 179
51.92% covered (warning)
51.92%
27 / 52
307.02
0.00% covered (danger)
0.00%
0 / 1
 __construct
95.00% covered (success)
95.00%
76 / 80
0.00% covered (danger)
0.00%
0 / 1
28
 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
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setConstantsPrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 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            $rename->addMapping(StraussConfig::class, 'constant_prefix', 'constantsPrefix');
240
241            $mapper->unshift($rename);
242            $mapper->push(new CaseConversion(TextNotation::UNDERSCORE(), TextNotation::CAMEL_CASE()));
243
244            $mapper->mapObject($configExtraSettings, $this);
245        }
246
247        // Defaults.
248        // * Use PSR-4 autoloader key
249        // * Use PSR-0 autoloader key
250        // * Use the package name
251        if (! isset($this->namespacePrefix)) {
252            if (isset($composer, $composer->getPackage()->getAutoload()['psr-4'])) {
253                $this->setNamespacePrefix(array_key_first($composer->getPackage()->getAutoload()['psr-4']));
254            } elseif (isset($composer, $composer->getPackage()->getAutoload()['psr-0'])) {
255                $this->setNamespacePrefix(array_key_first($composer->getPackage()->getAutoload()['psr-0']));
256            } elseif (isset($composer) && '__root__' !== $composer->getPackage()->getName()) {
257                $packageName = $composer->getPackage()->getName();
258                $namespacePrefix = preg_replace('/[^\w\/]+/', '_', $packageName);
259                $namespacePrefix = str_replace('/', '\\', $namespacePrefix) . '\\';
260                $namespacePrefix = preg_replace_callback('/(?<=^|_|\\\\)[a-z]/', function ($match) {
261                    return strtoupper($match[0]);
262                }, $namespacePrefix);
263                $this->setNamespacePrefix($namespacePrefix);
264            } elseif (isset($this->classmapPrefix)) {
265                $namespacePrefix = rtrim($this->getClassmapPrefix(), '_');
266                $this->setNamespacePrefix($namespacePrefix);
267            }
268        }
269
270        if (! isset($this->classmapPrefix)) {
271            if (isset($composer, $composer->getPackage()->getAutoload()['psr-4'])) {
272                $autoloadKey = array_key_first($composer->getPackage()->getAutoload()['psr-4']);
273                $classmapPrefix = str_replace("\\", "_", $autoloadKey);
274                $this->setClassmapPrefix($classmapPrefix);
275            } elseif (isset($composer, $composer->getPackage()->getAutoload()['psr-0'])) {
276                $autoloadKey = array_key_first($composer->getPackage()->getAutoload()['psr-0']);
277                $classmapPrefix = str_replace("\\", "_", $autoloadKey);
278                $this->setClassmapPrefix($classmapPrefix);
279            } elseif (isset($composer) && '__root__' !== $composer->getPackage()->getName()) {
280                $packageName = $composer->getPackage()->getName();
281                $classmapPrefix = preg_replace('/[^\w\/]+/', '_', $packageName);
282                $classmapPrefix = str_replace('/', '\\', $classmapPrefix);
283                // Uppercase the first letter of each word.
284                $classmapPrefix = preg_replace_callback('/(?<=^|_|\\\\)[a-z]/', function ($match) {
285                    return strtoupper($match[0]);
286                }, $classmapPrefix);
287                $classmapPrefix = str_replace("\\", "_", $classmapPrefix);
288                $this->setClassmapPrefix($classmapPrefix);
289            } elseif (isset($this->namespacePrefix)) {
290                $classmapPrefix = preg_replace('/[^\w\/]+/', '_', $this->getNamespacePrefix());
291                $classmapPrefix = rtrim($classmapPrefix, '_') . '_';
292                $this->setClassmapPrefix($classmapPrefix);
293            }
294        }
295
296//        if (!isset($this->namespacePrefix) || !isset($this->classmapPrefix)) {
297//            throw new Exception('Prefix not set. Please set `namespace_prefix`, `classmap_prefix` in composer.json/extra/strauss.');
298//        }
299
300        if (isset($composer) && empty($this->packages)) {
301            $this->packages = array_map(function (\Composer\Package\Link $element) {
302                return $element->getTarget();
303            }, $composer->getPackage()->getRequires());
304        }
305
306        // If the bool flag for classmapOutput wasn't set in the JSON config.
307        if (!isset($this->classmapOutput)) {
308            $this->classmapOutput = true;
309            // Check each autoloader.
310            if (isset($composer)) {
311                foreach ($composer->getPackage()->getAutoload() as $autoload) {
312                    // To see if one of its paths.
313                    foreach ($autoload as $entry) {
314                        $paths = (array) $entry;
315                        foreach ($paths as $path) {
316                            // Matches the target directory.
317                            if (trim($path, '\\/') . '/' === $this->getTargetDirectory()) {
318                                $this->classmapOutput = false;
319                                break 3;
320                            }
321                        }
322                    }
323                }
324            }
325        }
326
327        // TODO: Throw an exception if any regex patterns in config are invalid.
328        // https://stackoverflow.com/questions/4440626/how-can-i-validate-regex
329        // preg_match('~Valid(Regular)Expression~', null) === false);
330
331        if (isset($configExtraSettings, $configExtraSettings->updateCallSites)) {
332            if (true === $configExtraSettings->updateCallSites) {
333                $this->updateCallSites = null;
334            } elseif (false === $configExtraSettings->updateCallSites) {
335                $this->updateCallSites = array();
336            } elseif (is_array($configExtraSettings->updateCallSites)) {
337                $this->updateCallSites = $configExtraSettings->updateCallSites;
338            } else {
339                // uh oh.
340            }
341        }
342    }
343
344    /**
345     * `target_directory` will always be returned without a leading slash and with a trailing slash.
346     *
347     * @return string
348     */
349    public function getTargetDirectory(): string
350    {
351        return $this->getProjectDirectory() . trim($this->targetDirectory, '\\/') . '/';
352    }
353
354    /**
355     * @param string $targetDirectory
356     */
357    public function setTargetDirectory(string $targetDirectory): void
358    {
359        $this->targetDirectory = $targetDirectory;
360    }
361
362    /**
363     * @return string
364     */
365    public function getVendorDirectory(): string
366    {
367        return $this->getProjectDirectory() . trim($this->vendorDirectory, '\\/') . '/';
368    }
369
370    /**
371     * @param string $vendorDirectory
372     */
373    public function setVendorDirectory(string $vendorDirectory): void
374    {
375        $this->vendorDirectory = $vendorDirectory;
376    }
377
378    /**
379     * With no trailing slash and no leading slash.
380     */
381    public function getNamespacePrefix(): ?string
382    {
383        return !isset($this->namespacePrefix) ? null : trim($this->namespacePrefix, '\\');
384    }
385
386    /**
387     * @param string $namespacePrefix
388     */
389    public function setNamespacePrefix(string $namespacePrefix): void
390    {
391        $this->namespacePrefix = $namespacePrefix;
392    }
393
394    /**
395     * @return string
396     */
397    public function getClassmapPrefix(): ?string
398    {
399        return $this->classmapPrefix;
400    }
401
402    /**
403     * @param string $classmapPrefix
404     */
405    public function setClassmapPrefix(string $classmapPrefix): void
406    {
407        $this->classmapPrefix = $classmapPrefix;
408    }
409
410    public function getFunctionsPrefix(): ?string
411    {
412        if (!isset($this->functionsPrefix)) {
413            return strtolower($this->getClassmapPrefix());
414        }
415        if (empty($this->functionsPrefix)) {
416            return null;
417        }
418        if (is_string($this->functionsPrefix)) {
419            return $this->functionsPrefix;
420        }
421        return null;
422    }
423
424    /**
425     * @param string|bool|null $functionsPrefix
426     */
427    public function setFunctionsPrefix($functionsPrefix): void
428    {
429        $this->functionsPrefix = $functionsPrefix;
430    }
431
432    /**
433     * @return string
434     */
435    public function getConstantsPrefix(): ?string
436    {
437        return $this->constantsPrefix;
438    }
439
440    /**
441     * @param string $constantsPrefix
442     */
443    public function setConstantsPrefix(string $constantsPrefix): void
444    {
445        $this->constantsPrefix = $constantsPrefix;
446    }
447
448    /**
449     * List of files and directories to update call sites in. Empty to disable. Null infers from the project's autoload key.
450     *
451     * @return string[]|null
452     */
453    public function getUpdateCallSites(): ?array
454    {
455        return $this->updateCallSites;
456    }
457
458    /**
459     * @param string[]|array{0:bool}|null $updateCallSites
460     * @throws InvalidArgumentException
461     */
462    public function setUpdateCallSites($updateCallSites): void
463    {
464        if (is_array($updateCallSites) && count($updateCallSites) === 1 && $updateCallSites[0] === true) {
465            // Setting `null` instructs Strauss to update call sites in the project's autoload key.
466            $this->updateCallSites = null;
467            return;
468        } elseif (is_array($updateCallSites) && count($updateCallSites) === 1 && $updateCallSites[0] === false) {
469            $this->updateCallSites = array();
470            return;
471        } elseif (is_array($updateCallSites) && isset($updateCallSites[0]) && !is_bool($updateCallSites[0])) {
472            $this->updateCallSites = array_filter(
473                $updateCallSites,
474                'is_string'
475            );
476            return;
477        }
478        throw new InvalidArgumentException('Unexpected value for updateCallSites');
479    }
480
481    /**
482     * @param array{packages?:array<string>, namespaces?:array<string>, file_patterns?:array<string>} $excludeFromCopy
483     */
484    public function setExcludeFromCopy(array $excludeFromCopy): void
485    {
486        foreach (array( 'packages', 'namespaces', 'file_patterns' ) as $key) {
487            if (isset($excludeFromCopy[$key])) {
488                $this->excludeFromCopy[$key] = $excludeFromCopy[$key];
489            }
490        }
491    }
492
493    /**
494     * @return string[]
495     */
496    public function getExcludePackagesFromCopy(): array
497    {
498        return $this->excludeFromCopy['packages'] ?? array();
499    }
500
501    /**
502     * @return string[]
503     */
504    public function getExcludeNamespacesFromCopy(): array
505    {
506        return $this->excludeFromCopy['namespaces'] ?? array();
507    }
508
509    /**
510     * @return string[]
511     */
512    public function getExcludeFilePatternsFromCopy(): array
513    {
514        return $this->excludeFromCopy['file_patterns'] ?? array();
515    }
516
517    /**
518     * @param array{packages?:array<string>, namespaces?:array<string>, file_patterns?:array<string>} $excludeFromPrefix
519     */
520    public function setExcludeFromPrefix(array $excludeFromPrefix): void
521    {
522        if (isset($excludeFromPrefix['packages'])) {
523            $this->excludeFromPrefix['packages'] = $excludeFromPrefix['packages'];
524        }
525        if (isset($excludeFromPrefix['namespaces'])) {
526            $this->excludeFromPrefix['namespaces'] = $excludeFromPrefix['namespaces'];
527        }
528        if (isset($excludeFromPrefix['file_patterns'])) {
529            $this->excludeFromPrefix['file_patterns'] = $excludeFromPrefix['file_patterns'];
530        }
531    }
532
533    /**
534     * When prefixing, do not prefix these packages (which have been copied).
535     *
536     * @return string[]
537     */
538    public function getExcludePackagesFromPrefixing(): array
539    {
540        return $this->excludeFromPrefix['packages'] ?? [];
541    }
542
543    /**
544     * @param string[] $excludePackagesFromPrefixing
545     */
546    public function setExcludePackagesFromPrefixing(array $excludePackagesFromPrefixing): void
547    {
548        $this->excludeFromPrefix['packages'] = $excludePackagesFromPrefixing;
549    }
550
551    /**
552     * @return string[]
553     */
554    public function getExcludeNamespacesFromPrefixing(): array
555    {
556        return array_map(
557            fn(string $packageName) => trim($packageName, '\\/'),
558            $this->excludeFromPrefix['namespaces'] ?? []
559        );
560    }
561
562    /**
563     * @return string[]
564     */
565    public function getExcludeFilePatternsFromPrefixing(): array
566    {
567        return $this->excludeFromPrefix['file_patterns'] ?? array();
568    }
569
570
571    /**
572     * @return array{}|array<string, array{files?:array<string>,classmap?:array<string>,"psr-4":array<string|array<string>>}> $overrideAutoload Dictionary of package name: autoload rules.
573     */
574    public function getOverrideAutoload(): array
575    {
576        return $this->overrideAutoload;
577    }
578
579    /**
580     * @param array<string, array{files?:array<string>,classmap?:array<string>,"psr-4":array<string|array<string>>}> $overrideAutoload Dictionary of package name: autoload rules.
581     */
582    public function setOverrideAutoload(array $overrideAutoload): void
583    {
584        $this->overrideAutoload = $overrideAutoload;
585    }
586
587    /**
588     * @return bool
589     */
590    public function isDeleteVendorFiles(): bool
591    {
592        return $this->deleteVendorFiles;
593    }
594
595    /**
596     * @return bool
597     */
598    public function isDeleteVendorPackages(): bool
599    {
600        return $this->deleteVendorPackages;
601    }
602
603    /**
604     * @param bool $deleteVendorFiles
605     */
606    public function setDeleteVendorFiles(bool $deleteVendorFiles): void
607    {
608        $this->deleteVendorFiles = $deleteVendorFiles;
609    }
610
611    /**
612     * @param bool $deleteVendorPackages
613     */
614    public function setDeleteVendorPackages(bool $deleteVendorPackages): void
615    {
616        $this->deleteVendorPackages = $deleteVendorPackages;
617    }
618
619    /**
620     * @return string[]
621     */
622    public function getPackages(): array
623    {
624        return $this->packages;
625    }
626
627    /**
628     * @param string[] $packages
629     */
630    public function setPackages(array $packages): void
631    {
632        $this->packages = $packages;
633    }
634
635    /**
636     * @used-by DumpAutoload::createInstalledVersionsFiles()
637     * @return array<string,ComposerPackage>
638     */
639    public function getPackagesToCopy(): array
640    {
641        return $this->packagesToCopy;
642    }
643
644    /**
645     * @used-by DependenciesCommand::buildDependencyList()
646     *
647     * @param array<string,ComposerPackage> $packagesToCopy
648     */
649    public function setPackagesToCopy(array $packagesToCopy): void
650    {
651        $this->packagesToCopy = $packagesToCopy;
652    }
653
654    /**
655     * @return array<string,ComposerPackage>
656     */
657    public function getPackagesToPrefix(): array
658    {
659        return $this->packagesToPrefix;
660    }
661
662    /**
663     * @param array<string,ComposerPackage> $packagesToPrefix
664     */
665    public function setPackagesToPrefix(array $packagesToPrefix): void
666    {
667        $this->packagesToPrefix = $packagesToPrefix;
668    }
669    /**
670     * TODO: Can we name this `isClassmapOutputEnabled`?
671     */
672    public function isClassmapOutput(): bool
673    {
674        return $this->classmapOutput;
675    }
676
677    /**
678     * @param bool $classmapOutput
679     */
680    public function setClassmapOutput(bool $classmapOutput): void
681    {
682        $this->classmapOutput = $classmapOutput;
683    }
684
685    /**
686     * Backwards compatibility with Mozart.
687     *
688     * @param string[] $excludePackages
689     */
690    public function setExcludePackages(array $excludePackages): void
691    {
692        $this->excludeFromPrefix['packages'] = $excludePackages;
693    }
694
695    /**
696     * @return array<string,string>
697     */
698    public function getNamespaceReplacementPatterns(): array
699    {
700        return $this->namespaceReplacementPatterns;
701    }
702
703    /**
704     * @param array<string,string> $namespaceReplacementPatterns
705     */
706    public function setNamespaceReplacementPatterns(array $namespaceReplacementPatterns): void
707    {
708        $this->namespaceReplacementPatterns = $namespaceReplacementPatterns;
709    }
710
711    /**
712     * @return bool
713     */
714    public function isIncludeModifiedDate(): bool
715    {
716        return $this->includeModifiedDate;
717    }
718
719    /**
720     * @param bool $includeModifiedDate
721     */
722    public function setIncludeModifiedDate(bool $includeModifiedDate): void
723    {
724        $this->includeModifiedDate = $includeModifiedDate;
725    }
726
727
728    /**
729     * @return bool
730     */
731    public function isIncludeAuthor(): bool
732    {
733        return $this->includeAuthor;
734    }
735
736    /**
737     * @param bool $includeAuthor
738     */
739    public function setIncludeAuthor(bool $includeAuthor): void
740    {
741        $this->includeAuthor = $includeAuthor;
742    }
743
744    /**
745     * Should expected changes be printed to console rather than files modified?
746     */
747    public function isDryRun(): bool
748    {
749        return $this->dryRun;
750    }
751
752    /**
753     * Disable making changes to files; output changes to console instead.
754     */
755    public function setDryRun(bool $dryRun): void
756    {
757        $this->dryRun = $dryRun;
758    }
759
760    /**
761     * Should the root autoload be included when generating the strauss autoloader?
762     */
763    public function isIncludeRootAutoload(): bool
764    {
765        return $this->includeRootAutoload;
766    }
767
768    /**
769     * @param bool $includeRootAutoload Include the project root autoload in the strauss autoloader.
770     */
771    public function setIncludeRootAutoload(bool $includeRootAutoload): void
772    {
773        $this->includeRootAutoload = $includeRootAutoload;
774    }
775
776    /**
777     * @param InputInterface $input To access the command line options.
778     */
779    public function updateFromCli(InputInterface $input): void
780    {
781
782        // strauss --updateCallSites=false (default)
783        // strauss --updateCallSites=true
784        // strauss --updateCallSites=src,input,extra
785
786        if ($input->hasOption('updateCallSites') && $input->getOption('updateCallSites') !== null) {
787            $updateCallSitesInput = $input->getOption('updateCallSites');
788
789            if ('false' === $updateCallSitesInput) {
790                $this->updateCallSites = array();
791            } elseif ('true' === $updateCallSitesInput) {
792                $this->updateCallSites = null;
793            } elseif (is_string($updateCallSitesInput)) {
794                $this->updateCallSites = explode(',', $updateCallSitesInput);
795            }
796        }
797
798        if ($input->hasOption('deleteVendorPackages')  && $input->getOption('deleteVendorPackages') !== false) {
799            $isDeleteVendorPackagesCommandLine = $input->getOption('deleteVendorPackages') === 'true'
800                || $input->getOption('deleteVendorPackages') === null;
801            $this->setDeleteVendorPackages($isDeleteVendorPackagesCommandLine);
802        } elseif ($input->hasOption('delete_vendor_packages') && $input->getOption('delete_vendor_packages') !== false) {
803            $isDeleteVendorPackagesCommandLine = $input->getOption('delete_vendor_packages') === 'true'
804                || $input->getOption('delete_vendor_packages') === null;
805            $this->setDeleteVendorPackages($isDeleteVendorPackagesCommandLine);
806        }
807
808        if ($input->hasOption('dry-run') && $input->getOption('dry-run') !== false) {
809            // If we're here, the parameter was passed in the CLI command.
810            $this->dryRun = empty($input->getOption('dry-run')) || (bool)filter_var($input->getOption('dry-run'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
811        }
812    }
813
814    /**
815     * Should we create the `autoload_aliases.php` file in `vendor/composer`?
816     *
817     * TODO:
818     * [x] YES when we are deleting vendor packages or files
819     * [ ] NO when we are running composer install `--no-dev`
820     * [ ] SOMETIMES: see https://github.com/BrianHenryIE/strauss/issues/144
821     * [ ] Add `aliases` to `extra` in `composer.json`
822     * [ ] Add `--aliases=true` CLI option
823     */
824    public function isCreateAliases(): bool
825    {
826        return $this->deleteVendorPackages || $this->deleteVendorFiles || trim($this->targetDirectory, '\\/') === 'vendor';
827    }
828
829    public function getProjectDirectory(): string
830    {
831        $projectDirectory = $this->projectDirectory ?? getcwd() . '/';
832
833        return $this->isDryRun()
834            ? 'mem:/' . $projectDirectory
835            : $projectDirectory;
836    }
837}