Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
16.36% covered (danger)
16.36%
9 / 55
16.67% covered (danger)
16.67%
1 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
FileCopyScanner
16.36% covered (danger)
16.36%
9 / 55
16.67% covered (danger)
16.67%
1 / 6
421.49
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 / 24
0.00% covered (danger)
0.00%
0 / 1
156
 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\FileBase;
23use BrianHenryIE\Strauss\Files\FileWithDependency;
24use BrianHenryIE\Strauss\Helpers\FileSystem;
25use BrianHenryIE\Strauss\Types\DiscoveredSymbol;
26use BrianHenryIE\Strauss\Types\NamespaceSymbol;
27use Psr\Log\LoggerAwareTrait;
28use Psr\Log\LoggerInterface;
29use Psr\Log\NullLogger;
30
31class FileCopyScanner
32{
33    use LoggerAwareTrait;
34
35    protected FileCopyScannerConfigInterface $config;
36
37    protected FileSystem $filesystem;
38
39    public function __construct(
40        FileCopyScannerConfigInterface $config,
41        FileSystem $filesystem,
42        ?LoggerInterface $logger = null
43    ) {
44        $this->config = $config;
45        $this->filesystem = $filesystem;
46
47        $this->setLogger($logger ?? new NullLogger());
48    }
49
50    public function scanFiles(DiscoveredFiles $files): void
51    {
52        /** @var FileBase $file */
53        foreach ($files->getFiles() as $file) {
54            $copy = true;
55
56            if ($this->config->isTargetDirectoryVendor()) {
57                $this->logger->debug("The target directory is the same as the vendor directory."); // TODO: surely this should be outside the loop/class.
58                $copy = false;
59            }
60
61            if ($file instanceof FileWithDependency) {
62                if ($this->isPackageExcluded($file->getDependency())) {
63                    $copy = false;
64                    $this->logger->debug("File {$file->getSourcePath()} will not be copied because {$file->getDependency()->getPackageName()} is excluded from copy.");
65                }
66            }
67
68            if ($this->isNamespaceExcluded($file)) {
69                $copy = false;
70            }
71
72            if ($this->isFilePathExcluded($file)) {
73                $copy = false;
74            }
75
76//            if ($copy) {
77//                $this->logger->debug("Marking file {relativeFilePath} to be copied.", [
78//                    'relativeFilePath' => $this->filesystem->getRelativePath($this->config->getAbsoluteVendorDirectory(), $file->getSourcePath()),
79//                ]);
80//            }
81
82            $file->setDoCopy($copy);
83
84            if ($copy) {
85                $target = $file instanceof FileWithDependency
86                    ?  $this->config->getAbsoluteTargetDirectory() . '/' . $file->getDependency()->getRelativePath() . '/'. $file->getPackageRelativePath()
87                    : $file->getSourcePath();
88                $file->setTargetAbsolutePath(FileSystem::normalizeDirSeparator($target));
89            }
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->isTargetDirectoryVendor()) {
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->isTargetDirectoryVendor()) {
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            // To make step-debugging easier.
108            unset($copy, $target, $shouldDelete);
109        };
110    }
111
112    protected function isPackageExcluded(ComposerPackage $package): bool
113    {
114        if (in_array(
115            $package->getPackageName(),
116            $this->config->getExcludePackagesFromCopy(),
117            true
118        )) {
119            return true;
120        }
121        return false;
122    }
123
124    protected function isNamespaceExcluded(FileBase $file): bool
125    {
126        /** @var DiscoveredSymbol $symbol */
127        foreach ($file->getDiscoveredSymbols() as $symbol) {
128            if (!($symbol instanceof NamespaceSymbol)) {
129                continue;
130            }
131            foreach ($this->config->getExcludeNamespacesFromCopy() as $namespace) {
132                $namespace = rtrim($namespace, '\\');
133                if (in_array($file->getSourcePath(), array_keys($symbol->getSourceFiles()), true)
134                    // TODO: case insensitive check. People might write BrianHenryIE\API instead of BrianHenryIE\Api.
135                    && str_starts_with($symbol->getOriginalSymbol(), $namespace)
136                ) {
137                    $this->logger->debug("File {$file->getSourcePath()} will not be copied because namespace {$namespace} is excluded from copy.");
138                    return true;
139                }
140            }
141        }
142        return false;
143    }
144
145    /**
146     * Compares the vendor relative path with `exclude_file_patterns` config.
147     *
148     * I.e. `my/package/src/file.php`.
149     *
150     * @param FileBase $file
151     */
152    protected function isFilePathExcluded(FileBase $file): bool
153    {
154        $path = $file->getVendorRelativePath();
155
156        foreach ($this->config->getExcludeFilePatternsFromCopy() as $pattern) {
157            $escapedPattern = $this->preparePattern($pattern);
158            if (1 === preg_match($escapedPattern, $path)) {
159                $this->logger->debug("File {$path} will not be copied because it matches pattern {$pattern}.");
160                return true;
161            }
162        }
163        return false;
164    }
165
166    private function preparePattern(string $pattern): string
167    {
168        $delimiter = '#';
169
170        if (substr($pattern, 0, 1) !== substr($pattern, - 1, 1)) {
171            $pattern = $delimiter . $pattern . $delimiter;
172        }
173
174        return $pattern;
175    }
176}