Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
FileEnumerator
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 4
462
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 compileFileList
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
210
 addFile
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 findFilesInDirectory
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Build a list of files from the composer autoloaders.
4 *
5 * Also record the `files` autoloaders.
6 */
7
8namespace BrianHenryIE\Strauss;
9
10use BrianHenryIE\Strauss\Composer\ComposerPackage;
11use BrianHenryIE\Strauss\Composer\Extra\StraussConfig;
12use BrianHenryIE\Strauss\Helpers\Path;
13use League\Flysystem\Filesystem;
14use League\Flysystem\Local\LocalFilesystemAdapter;
15use RecursiveDirectoryIterator;
16use RecursiveIteratorIterator;
17use RegexIterator;
18use Symfony\Component\Finder\Finder;
19
20class FileEnumerator
21{
22    /**
23     * The only path variable with a leading slash.
24     * All directories in project end with a slash.
25     *
26     * @var string
27     */
28    protected string $workingDir;
29
30    /** @var string */
31    protected string $vendorDir;
32
33    /** @var ComposerPackage[] */
34    protected array $dependencies;
35
36    /** @var string[]  */
37    protected array $excludePackageNames = array();
38
39    /** @var string[]  */
40    protected array $excludeNamespaces = array();
41
42    /** @var string[]  */
43    protected array $excludeFilePatterns = array();
44
45    /** @var Filesystem */
46    protected Filesystem $filesystem;
47
48    protected DiscoveredFiles $discoveredFiles;
49
50    /**
51     * Record the files autoloaders for later use in building our own autoloader.
52     *
53     * Package-name: [ dir1, file1, file2, ... ].
54     *
55     * @var array<string, string[]>
56     */
57    protected array $filesAutoloaders = [];
58
59    /**
60     * Copier constructor.
61     * @param ComposerPackage[] $dependencies
62     * @param string $workingDir
63     */
64    public function __construct(
65        array $dependencies,
66        string $workingDir,
67        StraussConfig $config
68    ) {
69        $this->discoveredFiles = new DiscoveredFiles();
70
71        $this->workingDir = $workingDir;
72        $this->vendorDir = $config->getVendorDirectory();
73
74        $this->dependencies = $dependencies;
75
76        $this->excludeNamespaces = $config->getExcludeNamespacesFromCopy();
77        $this->excludePackageNames = $config->getExcludePackagesFromCopy();
78        $this->excludeFilePatterns = $config->getExcludeFilePatternsFromCopy();
79
80        $this->filesystem = new Filesystem(new LocalFilesystemAdapter($this->workingDir));
81    }
82
83    /**
84     * Read the autoload keys of the dependencies and generate a list of the files referenced.
85     *
86     * Includes all files in the directories and subdirectories mentioned in the autoloaders.
87     */
88    public function compileFileList(): DiscoveredFiles
89    {
90        foreach ($this->dependencies as $dependency) {
91            if (in_array($dependency->getPackageName(), $this->excludePackageNames)) {
92                continue;
93            }
94
95            /**
96             * Where $dependency->autoload is ~
97             *
98             * [ "psr-4" => [ "BrianHenryIE\Strauss" => "src" ] ]
99             * Exclude "exclude-from-classmap"
100             * @see https://getcomposer.org/doc/04-schema.md#exclude-files-from-classmaps
101             */
102            $autoloaders = array_filter($dependency->getAutoload(), function ($type) {
103                return 'exclude-from-classmap' !== $type;
104            }, ARRAY_FILTER_USE_KEY);
105
106            foreach ($autoloaders as $type => $value) {
107                // Might have to switch/case here.
108
109                if ('files' === $type) {
110                    $this->filesAutoloaders[$dependency->getRelativePath()] = $value;
111                }
112
113                foreach ($value as $namespace => $namespace_relative_paths) {
114                    if (!empty($namespace) && in_array($namespace, $this->excludeNamespaces)) {
115                        continue;
116                    }
117
118                    if (! is_array($namespace_relative_paths)) {
119                        $namespace_relative_paths = array( $namespace_relative_paths );
120                    }
121
122                    foreach ($namespace_relative_paths as $namespaceRelativePath) {
123                        $sourceAbsolutePath = $dependency->getPackageAbsolutePath() . $namespaceRelativePath;
124
125                        if (is_file($sourceAbsolutePath)) {
126                            $this->addFile($dependency, $namespaceRelativePath, $type);
127                        } elseif (is_dir($sourceAbsolutePath)) {
128                            // trailingslashit(). (to remove duplicates).
129                            $sourcePath = Path::normalize($sourceAbsolutePath);
130
131//                          $this->findFilesInDirectory()
132                            $finder = new Finder();
133                            $finder->files()->in($sourcePath)->followLinks();
134
135                            foreach ($finder as $foundFile) {
136                                $sourceAbsoluteFilepath = $foundFile->getPathname();
137
138                                // No need to record the directory itself.
139                                if (is_dir($sourceAbsoluteFilepath)) {
140                                    continue;
141                                }
142
143                                $namespaceRelativePath = Path::normalize($namespaceRelativePath);
144
145                                $this->addFile(
146                                    $dependency,
147                                    $namespaceRelativePath . str_replace($sourcePath, '', $sourceAbsoluteFilepath),
148                                    $type
149                                );
150                            }
151                        }
152                    }
153                }
154            }
155        }
156
157        return $this->discoveredFiles;
158    }
159
160    /**
161     * @uses \BrianHenryIE\Strauss\DiscoveredFiles::add()
162     *
163     * @param ComposerPackage $dependency
164     * @param string $packageRelativePath
165     * @param string $autoloaderType
166     * @throws \League\Flysystem\FilesystemException
167     */
168    protected function addFile(ComposerPackage $dependency, string $packageRelativePath, string $autoloaderType): void
169    {
170        $sourceAbsoluteFilepath = $dependency->getPackageAbsolutePath() . $packageRelativePath;
171        $outputRelativeFilepath = $dependency->getRelativePath() . $packageRelativePath;
172        $projectRelativePath    = $this->vendorDir . $outputRelativeFilepath;
173        $isOutsideProjectDir    = 0 !== strpos($sourceAbsoluteFilepath, $this->workingDir);
174
175        $f = $this->discoveredFiles->getFiles()[$outputRelativeFilepath]
176            ?? new File($dependency, $packageRelativePath, $sourceAbsoluteFilepath);
177
178        $f->addAutoloader($autoloaderType);
179        $f->setDoDelete($isOutsideProjectDir);
180
181        foreach ($this->excludeFilePatterns as $excludePattern) {
182            if (1 === preg_match($excludePattern, $outputRelativeFilepath)) {
183                $f->setDoCopy(false);
184            }
185        }
186
187        if ('<?php // This file was deleted by {@see https://github.com/BrianHenryIE/strauss}.'
188            ===
189            $this->filesystem->read($projectRelativePath)
190        ) {
191            $f->setDoCopy(false);
192        }
193
194        $this->discoveredFiles->add($f);
195    }
196
197    /**
198     * @param string $workingDir Absolute path to the working directory, results will be relative to this.
199     * @param string $relativeDirectory
200     * @param string $regexPattern Default to PHP files.
201     *
202     * @return string[]
203     */
204    public function findFilesInDirectory(string $workingDir, string $relativeDirectory = '.', string $regexPattern = '/.+\.php$/'): array
205    {
206        $dir = new RecursiveDirectoryIterator($workingDir . $relativeDirectory);
207        $ite = new RecursiveIteratorIterator($dir);
208        $files = new RegexIterator($ite, $regexPattern, RegexIterator::GET_MATCH);
209        $fileList = array();
210        foreach ($files as $file) {
211            $fileList = array_merge($fileList, str_replace($workingDir, '', $file));
212        }
213        return $fileList;
214    }
215}