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