Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
16.07% covered (danger)
16.07%
9 / 56
16.67% covered (danger)
16.67%
1 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
FileCopyScanner
16.07% covered (danger)
16.07%
9 / 56
16.67% covered (danger)
16.67%
1 / 6
457.98
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 / 25
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->isTargetDirectoryVendor()) {
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->getAbsoluteVendorDirectory(), $file->getSourcePath()),
80//                ]);
81            }
82
83            $file->setDoCopy($copy);
84
85            if ($copy) {
86                $target = $file instanceof FileWithDependency
87                    ?  $this->config->getAbsoluteTargetDirectory() . '/' . $file->getDependency()->getRelativePath() . '/'. $file->getPackageRelativePath()
88                    : $file->getSourcePath();
89                $file->setAbsoluteTargetPath(FileSystem::normalizeDirSeparator($target));
90            }
91
92            $shouldDelete = $this->config->isDeleteVendorFiles() && ! $this->filesystem->isSymlinked($file->getSourcePath());
93            $file->setDoDelete($shouldDelete);
94
95            // If a file isn't copied, don't unintentionally edit the source file.
96            if (!$file->isDoCopy() && !$this->config->isTargetDirectoryVendor()) {
97                $file->setDoPrefix(false);
98            }
99//            // If the file is marked not to copy, mark the symbol not to be renamed
100//            if (!$copy && !$this->config->isTargetDirectoryVendor()) {
101//                foreach ($file->getDiscoveredSymbols() as $symbol) {
102//                    // Only make this change if the symbol is only in one file (i.e. namespaces will be in many).
103//                    if (count($symbol->getSourceFiles()) === 1) {
104//                        $symbol->setDoRename(false);
105//                    }
106//                }
107//            }
108            // To make step-debugging easier.
109            unset($copy, $target, $shouldDelete);
110        };
111    }
112
113    protected function isPackageExcluded(ComposerPackage $package): bool
114    {
115        if (in_array(
116            $package->getPackageName(),
117            $this->config->getExcludePackagesFromCopy(),
118            true
119        )) {
120            return true;
121        }
122        return false;
123    }
124
125    protected function isNamespaceExcluded(FileBase $file): bool
126    {
127        /** @var DiscoveredSymbol $symbol */
128        foreach ($file->getDiscoveredSymbols() as $symbol) {
129            if (!($symbol instanceof NamespaceSymbol)) {
130                continue;
131            }
132            foreach ($this->config->getExcludeNamespacesFromCopy() as $namespace) {
133                $namespace = rtrim($namespace, '\\');
134                if (in_array($file->getSourcePath(), array_keys($symbol->getSourceFiles()), true)
135                    // TODO: case insensitive check. People might write BrianHenryIE\API instead of BrianHenryIE\Api.
136                    && str_starts_with($symbol->getOriginalSymbol(), $namespace)
137                ) {
138                    $this->logger->debug("File {$file->getSourcePath()} will not be copied because namespace {$namespace} is excluded from copy.");
139                    return true;
140                }
141            }
142        }
143        return false;
144    }
145
146    /**
147     * Compares the vendor relative path with `exclude_file_patterns` config.
148     *
149     * I.e. `my/package/src/file.php`.
150     *
151     * @param FileBase $file
152     */
153    protected function isFilePathExcluded(FileBase $file): bool
154    {
155        $path = $file->getVendorRelativePath();
156
157        foreach ($this->config->getExcludeFilePatternsFromCopy() as $pattern) {
158            $escapedPattern = $this->preparePattern($pattern);
159            if (1 === preg_match($escapedPattern, $path)) {
160                $this->logger->debug("File {$path} will not be copied because it matches pattern {$pattern}.");
161                return true;
162            }
163        }
164        return false;
165    }
166
167    private function preparePattern(string $pattern): string
168    {
169        $delimiter = '#';
170
171        if (substr($pattern, 0, 1) !== substr($pattern, - 1, 1)) {
172            $pattern = $delimiter . $pattern . $delimiter;
173        }
174
175        return $pattern;
176    }
177}