Lines 90.47% 19 / 21
Methods 50.00% 1 / 2
Classes 0.00% 0 / 1
Covered by tests of size
Name Lines Methods CRAP
 getNonWritablePaths 87.50% 14 / 16 0.00% 0 / 1 6.07
 formatPathList 100.00% 5 / 5 100.00% 1 / 1 2
27final class WritablePathScanner
28{
29    /**
30     * Returns all paths inside the given directory the current process cannot
31     * write to. Paths inside the excluded directory are skipped.
32     *
33     * @return string[]
34     */
35    public static function getNonWritablePaths(string $directory, string $excludedDirectory): array
36    {
37        $nonWritablePaths = [];
38        $realExcludedDirectory = realpath($excludedDirectory);
39
40        $items = new RecursiveIteratorIterator(
41            new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS),
42            RecursiveIteratorIterator::SELF_FIRST,
43            RecursiveIteratorIterator::CATCH_GET_CHILD,
44        );
45
46        foreach ($items as $item) {
47            if (!$item instanceof SplFileInfo) {
48                continue;
49            }
50
51            $path = $item->getPathname();
52
53            if ($realExcludedDirectory !== false && str_starts_with($path, $realExcludedDirectory)) {
54                continue;
55            }
56
57            if (!$item->isWritable()) {
58                $nonWritablePaths[] = $path;
59            }
60        }
61
62        return $nonWritablePaths;
63    }
64
65    /**
66     * Formats a list of paths for an error message, truncated to the first
67     * five entries.
68     *
69     * @param string[] $paths
70     */
71    public static function formatPathList(array $paths): string
72    {
73        $additionalPathCount = count($paths) - 5;
74
75        return (
76            implode(', ', array_slice($paths, offset: 0, length: 5))
77            . ($additionalPathCount > 0 ? sprintf(' and %d more', $additionalPathCount) : '')
78        );
79    }
80}