Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
16.67% covered (danger)
16.67%
9 / 54
16.67% covered (danger)
16.67%
1 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
FileCopyScanner
16.67% covered (danger)
16.67%
9 / 54
16.67% covered (danger)
16.67%
1 / 6
448.88
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 scanFiles
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
182
 isPackageExcluded
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 isNamespaceExcluded
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
42
 isFilePathExcluded
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
 preparePattern
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Loop over the discovered files and mark the file to be copied or not.
4 *
5 * ```
6 * "exclude_from_copy": {
7 *   "packages": [
8 *   ],
9 *   "namespaces": [
10 *   ],
11 *   "file_patterns": [
12 *   ]
13 * },
14 * ```
15 */
16
17namespace BrianHenryIE\Strauss\Pipeline;
18
19use BrianHenryIE\Strauss\Composer\ComposerPackage;
20use BrianHenryIE\Strauss\Config\FileCopyScannerConfigInterface;
21use BrianHenryIE\Strauss\Files\DiscoveredFiles;
22use BrianHenryIE\Strauss\Files\File;
23use BrianHenryIE\Strauss\Files\FileBase;
24use BrianHenryIE\Strauss\Files\FileWithDependency;
25use BrianHenryIE\Strauss\Helpers\FileSystem;
26use BrianHenryIE\Strauss\Types\DiscoveredSymbol;
27use BrianHenryIE\Strauss\Types\NamespaceSymbol;
28use Psr\Log\LoggerAwareTrait;
29use Psr\Log\LoggerInterface;
30use Psr\Log\NullLogger;
31
32class FileCopyScanner
33{
34    use LoggerAwareTrait;
35
36    protected FileCopyScannerConfigInterface $config;
37
38    protected FileSystem $filesystem;
39
40    public function __construct(
41        FileCopyScannerConfigInterface $config,
42        FileSystem $filesystem,
43        ?LoggerInterface $logger = null
44    ) {
45        $this->config = $config;
46        $this->filesystem = $filesystem;
47
48        $this->setLogger($logger ?? new NullLogger());
49    }
50
51    public function scanFiles(DiscoveredFiles $files): void
52    {
53        /** @var FileBase $file */
54        foreach ($files->getFiles() as $file) {
55            $copy = true;
56
57            if ($this->config->getTargetDirectory() === $this->config->getVendorDirectory()) {
58                $this->logger->debug("The target directory is the same as the vendor directory."); // TODO: surely this should be outside the loop/class.
59                $copy = false;
60            }
61
62            if ($file instanceof FileWithDependency) {
63                if ($this->isPackageExcluded($file->getDependency())) {
64                    $copy = false;
65                    $this->logger->debug("File {$file->getSourcePath()} will not be copied because {$file->getDependency()->getPackageName()} is excluded from copy.");
66                }
67            }
68
69            if ($this->isNamespaceExcluded($file)) {
70                $copy = false;
71            }
72
73            if ($this->isFilePathExcluded($file)) {
74                $copy = false;
75            }
76
77            if ($copy) {
78//                $this->logger->debug("Marking file {relativeFilePath} to be copied.", [
79//                    'relativeFilePath' => $this->filesystem->getRelativePath($this->config->getVendorDirectory(), $file->getSourcePath()),
80//                ]);
81            }
82
83            $file->setDoCopy($copy);
84
85            $target = $copy && $file instanceof FileWithDependency
86                ? $this->config->getTargetDirectory() . $file->getVendorRelativePath()
87                : $file->getSourcePath();
88
89            $file->setAbsoluteTargetPath($target);
90
91            $shouldDelete = $this->config->isDeleteVendorFiles() && ! $this->filesystem->isSymlinked($file->getSourcePath());
92            $file->setDoDelete($shouldDelete);
93
94            // If a file isn't copied, don't unintentionally edit the source file.
95            if (!$file->isDoCopy() && $this->config->getTargetDirectory() !== $this->config->getVendorDirectory()) {
96                $file->setDoPrefix(false);
97            }
98//            // If the file is marked not to copy, mark the symbol not to be renamed
99//            if (!$copy && $this->config->getTargetDirectory() !== $this->config->getVendorDirectory()) {
100//                foreach ($file->getDiscoveredSymbols() as $symbol) {
101//                    // Only make this change if the symbol is only in one file (i.e. namespaces will be in many).
102//                    if (count($symbol->getSourceFiles()) === 1) {
103//                        $symbol->setDoRename(false);
104//                    }
105//                }
106//            }
107        };
108    }
109
110    protected function isPackageExcluded(ComposerPackage $package): bool
111    {
112        if (in_array(
113            $package->getPackageName(),
114            $this->config->getExcludePackagesFromCopy(),
115            true
116        )) {
117            return true;
118        }
119        return false;
120    }
121
122    protected function isNamespaceExcluded(FileBase $file): bool
123    {
124        /** @var DiscoveredSymbol $symbol */
125        foreach ($file->getDiscoveredSymbols() as $symbol) {
126            if (!($symbol instanceof NamespaceSymbol)) {
127                continue;
128            }
129            foreach ($this->config->getExcludeNamespacesFromCopy() as $namespace) {
130                $namespace = rtrim($namespace, '\\');
131                if (in_array($file->getSourcePath(), array_keys($symbol->getSourceFiles()), true)
132                    // TODO: case insensitive check. People might write BrianHenryIE\API instead of BrianHenryIE\Api.
133                    && str_starts_with($symbol->getOriginalSymbol(), $namespace)
134                ) {
135                    $this->logger->debug("File {$file->getSourcePath()} will not be copied because namespace {$namespace} is excluded from copy.");
136                    return true;
137                }
138            }
139        }
140        return false;
141    }
142
143    /**
144     * Compares the vendor relative path with `exclude_file_patterns` config.
145     *
146     * I.e. `my/package/src/file.php`.
147     *
148     * @param FileBase $file
149     */
150    protected function isFilePathExcluded(FileBase $file): bool
151    {
152        $path = $file->getVendorRelativePath();
153
154        foreach ($this->config->getExcludeFilePatternsFromCopy() as $pattern) {
155            $escapedPattern = $this->preparePattern($pattern);
156            if (1 === preg_match($escapedPattern, $path)) {
157                $this->logger->debug("File {$path} will not be copied because it matches pattern {$pattern}.");
158                return true;
159            }
160        }
161        return false;
162    }
163
164    private function preparePattern(string $pattern): string
165    {
166        $delimiter = '#';
167
168        if (substr($pattern, 0, 1) !== substr($pattern, - 1, 1)) {
169            $pattern = $delimiter . $pattern . $delimiter;
170        }
171
172        return $pattern;
173    }
174}