Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 53
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
FileCopyScanner
0.00% covered (danger)
0.00%
0 / 53
0.00% covered (danger)
0.00%
0 / 6
756
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 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
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 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->getSourcePath())) {
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->isSymlinkedFile($file);
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 relative path from the vendor dir with `exclude_file_patterns` config.
145     *
146     * @param string $absoluteFilePath
147     * @return bool
148     */
149    protected function isFilePathExcluded(string $absoluteFilePath): bool
150    {
151        foreach ($this->config->getExcludeFilePatternsFromCopy() as $pattern) {
152            $escapedPattern = $this->preparePattern($pattern);
153            if (1 === preg_match($escapedPattern, $absoluteFilePath)) {
154                $this->logger->debug("File {$absoluteFilePath} will not be copied because it matches pattern {$pattern}.");
155                return true;
156            }
157        }
158        return false;
159    }
160
161    private function preparePattern(string $pattern): string
162    {
163        $delimiter = '#';
164
165        if (substr($pattern, 0, 1) !== substr($pattern, - 1, 1)) {
166            $pattern = $delimiter . $pattern . $delimiter;
167        }
168
169        return $pattern;
170    }
171}