Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
58.33% covered (warning)
58.33%
14 / 24
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
ManagedWorkspace
58.33% covered (warning)
58.33%
14 / 24
75.00% covered (warning)
75.00%
3 / 4
28.18
0.00% covered (danger)
0.00%
0 / 1
 getOutputDirectory
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getCacheDirectory
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getWorkspaceRoot
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 getProjectRelativeWorkspaceRoot
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2
3declare(strict_types=1);
4
5/**
6 * Fast Forward Development Tools for PHP projects.
7 *
8 * This file is part of fast-forward/dev-tools project.
9 *
10 * @author   Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
11 * @license  https://opensource.org/licenses/MIT MIT License
12 *
13 * @see      https://github.com/php-fast-forward/
14 * @see      https://github.com/php-fast-forward/dev-tools
15 * @see      https://github.com/php-fast-forward/dev-tools/issues
16 * @see      https://php-fast-forward.github.io/dev-tools/
17 * @see      https://datatracker.ietf.org/doc/html/rfc2119
18 */
19
20namespace FastForward\DevTools\Path;
21
22use Symfony\Component\Filesystem\Path;
23
24/**
25 * Provides canonical repository-local paths for generated DevTools artifacts.
26 */
27 class ManagedWorkspace
28{
29    /**
30     * @var string the environment variable used to override the generated artifact workspace
31     */
32    public const string ENV_WORKSPACE_DIR = 'FAST_FORWARD_WORKSPACE_DIR';
33
34    /**
35     * @var string the output segment used for coverage artifacts
36     */
37    public const string COVERAGE = 'coverage';
38
39    /**
40     * @var string the output segment used for metrics artifacts
41     */
42    public const string METRICS = 'metrics';
43
44    /**
45     * @var string the cache segment used for phpDocumentor
46     */
47    public const string PHPDOC = 'phpdoc';
48
49    /**
50     * @var string the cache segment used for PHPUnit
51     */
52    public const string PHPUNIT = 'phpunit';
53
54    /**
55     * @var string the cache segment used for Rector
56     */
57    public const string RECTOR = 'rector';
58
59    /**
60     * @var string the cache segment used for PHP-CS-Fixer
61     */
62    public const string PHP_CS_FIXER = 'php-cs-fixer';
63
64    /**
65     * @var string the repository-local root directory for generated artifacts
66     */
67    public const string WORKSPACE_ROOT = '.dev-tools';
68
69    /**
70     * @var string the repository-local root directory for generated tool caches
71     */
72    private const string CACHE_ROOT = 'cache';
73
74    /**
75     * Returns a repository-local managed output directory.
76     *
77     * The optional $path MUST be a relative segment within the managed
78     * workspace, while $baseDir MAY provide the repository root used to
79     * materialize the same `.dev-tools` structure under a different base path.
80     *
81     * @param string $path the optional relative segment to append under the managed output root
82     * @param string $baseDir the optional repository root used to resolve the managed workspace path
83     */
84    public static function getOutputDirectory(string $path = '', string $baseDir = ''): string
85    {
86        $baseDir = self::getWorkspaceRoot($baseDir);
87
88        return '' === $path
89            ? $baseDir
90            : Path::join($baseDir, $path);
91    }
92
93    /**
94     * Returns a repository-local managed cache directory.
95     *
96     * The optional $path MUST be a relative cache segment, while $baseDir MAY
97     * resolve the managed workspace root before the `cache` directory is
98     * appended.
99     *
100     * @param string $path the optional relative cache segment to append under the managed cache root
101     * @param string $baseDir the optional repository root used to resolve the managed cache path
102     */
103    public static function getCacheDirectory(string $path = '', string $baseDir = ''): string
104    {
105        $baseDir = self::getOutputDirectory(self::CACHE_ROOT, $baseDir);
106
107        return '' === $path
108            ? $baseDir
109            : Path::join($baseDir, $path);
110    }
111
112    /**
113     * Returns the configured workspace root.
114     *
115     * Relative workspace paths stay relative when no base directory is provided.
116     * When a base directory is provided, relative workspaces are materialized
117     * under that base directory while absolute workspaces are used as-is.
118     *
119     * @param string $baseDir the optional repository root used to resolve a relative workspace
120     */
121    public static function getWorkspaceRoot(string $baseDir = ''): string
122    {
123        $workspaceRoot = getenv(self::ENV_WORKSPACE_DIR);
124
125        if (false === $workspaceRoot || '' === $workspaceRoot) {
126            $workspaceRoot = self::WORKSPACE_ROOT;
127        }
128
129        if ('' === $baseDir || Path::isAbsolute($workspaceRoot)) {
130            return $workspaceRoot;
131        }
132
133        return Path::join($baseDir, $workspaceRoot);
134    }
135
136    /**
137     * Returns the workspace root as a project-relative directory when tooling
138     * should skip generated artifacts during source scans.
139     *
140     * @param string $baseDir the optional repository root used to relativize absolute workspace paths
141     */
142    public static function getProjectRelativeWorkspaceRoot(string $baseDir = ''): ?string
143    {
144        $workspaceRoot = self::getWorkspaceRoot();
145
146        if (! Path::isAbsolute($workspaceRoot)) {
147            return $workspaceRoot;
148        }
149
150        if ('' === $baseDir) {
151            return null;
152        }
153
154        $baseDir = Path::canonicalize($baseDir);
155        $workspaceRoot = Path::canonicalize($workspaceRoot);
156
157        if ($baseDir === $workspaceRoot || ! str_starts_with($workspaceRoot, $baseDir . '/')) {
158            return null;
159        }
160
161        return Path::makeRelative($workspaceRoot, $baseDir);
162    }
163}