Skip to content

Commit 82efd60

Browse files
authored
refactor(support): improve path utilities (#1080)
1 parent fb8977b commit 82efd60

File tree

52 files changed

+913
-611
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+913
-611
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@
128128
"src/Tempest/Support/src/Html/functions.php",
129129
"src/Tempest/Support/src/Language/functions.php",
130130
"src/Tempest/Support/src/Namespace/functions.php",
131+
"src/Tempest/Support/src/Path/functions.php",
131132
"src/Tempest/Support/src/Random/functions.php",
132133
"src/Tempest/Support/src/Regex/functions.php",
133134
"src/Tempest/Support/src/Str/functions.php",

src/Tempest/Console/src/Highlight/TempestConsoleLanguage/Injections/FileInjection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public function parse(string $content, Highlighter $highlighter): ParsedInjectio
2323
$href = $matches['file'];
2424
$exists = realpath($href) !== false;
2525
$file = $exists
26-
? str(realpath($href))->replace('\\', '/')->replaceStart(root_path('/'), '')
26+
? str(realpath($href))->replace('\\', '/')->stripStart(root_path())->stripStart('/')
2727
: $href;
2828

2929
return TerminalStyle::UNDERLINE((string) $file);

src/Tempest/Core/src/Composer.php

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@
44

55
namespace Tempest\Core;
66

7+
use Tempest\Support\Namespace\Psr4Namespace;
8+
79
use function Tempest\Support\arr;
8-
use function Tempest\Support\path;
10+
use function Tempest\Support\Arr\wrap;
11+
use function Tempest\Support\Path\normalize;
12+
use function Tempest\Support\Str\ensure_ends_with;
13+
use function Tempest\Support\Str\starts_with;
914

1015
final class Composer
1116
{
12-
/** @var array<ComposerNamespace> */
17+
/** @var array<Psr4Namespace> */
1318
public array $namespaces;
1419

15-
public ?ComposerNamespace $mainNamespace = null;
20+
public ?Psr4Namespace $mainNamespace = null;
1621

1722
private string $composerPath;
1823

@@ -25,16 +30,17 @@ public function __construct(
2530

2631
public function load(): self
2732
{
28-
$this->composerPath = path($this->root, 'composer.json')->toString();
33+
$this->composerPath = normalize($this->root, 'composer.json');
2934
$this->composer = $this->loadComposerFile($this->composerPath);
3035
$this->namespaces = arr($this->composer)
3136
->get('autoload.psr-4', default: arr())
32-
->map(fn (string $path, string $namespace) => new ComposerNamespace($namespace, $path))
37+
->map(fn (string $path, string $namespace) => new Psr4Namespace($namespace, $path))
38+
->sortByCallback(fn (Psr4Namespace $ns1, Psr4Namespace $ns2) => strlen($ns1->path) <=> strlen($ns2->path))
3339
->values()
3440
->toArray();
3541

3642
foreach ($this->namespaces as $namespace) {
37-
if (str_starts_with($namespace->path, 'app/') || str_starts_with($namespace->path, 'src/')) {
43+
if (starts_with(ensure_ends_with($namespace->path, '/'), ['app/', 'src/', 'source/', 'lib/'])) {
3844
$this->mainNamespace = $namespace;
3945

4046
break;
@@ -45,16 +51,31 @@ public function load(): self
4551
$this->mainNamespace = $this->namespaces[0];
4652
}
4753

54+
$this->namespaces = arr([
55+
$this->mainNamespace,
56+
...$this->namespaces,
57+
])
58+
->filter()
59+
->unique(fn (Psr4Namespace $ns) => $ns->namespace)
60+
->toArray();
61+
4862
return $this;
4963
}
5064

51-
public function setMainNamespace(ComposerNamespace $namespace): self
65+
public function setMainNamespace(Psr4Namespace $namespace): self
5266
{
5367
$this->mainNamespace = $namespace;
5468

5569
return $this;
5670
}
5771

72+
public function setNamespaces(Psr4Namespace|array $namespaces): self
73+
{
74+
$this->namespaces = wrap($namespaces);
75+
76+
return $this;
77+
}
78+
5879
public function setShellExecutor(ShellExecutor $executor): self
5980
{
6081
$this->executor = $executor;

src/Tempest/Core/src/ComposerNamespace.php

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/Tempest/Core/src/FrameworkKernel.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public static function boot(
5050
discoveryLocations: $discoveryLocations,
5151
container: $container,
5252
)
53+
->validateRoot()
5354
->loadEnv()
5455
->registerEmergencyErrorHandler()
5556
->registerShutdownFunction()
@@ -63,6 +64,19 @@ public static function boot(
6364
->event(KernelEvent::BOOTED);
6465
}
6566

67+
public function validateRoot(): self
68+
{
69+
$root = realpath($this->root);
70+
71+
if (! is_dir($root)) {
72+
throw new \RuntimeException('The specified root directory is not valid.');
73+
}
74+
75+
$this->root = $root;
76+
77+
return $this;
78+
}
79+
6680
public function shutdown(int|string $status = ''): never
6781
{
6882
$this->finishDeferredTasks()

src/Tempest/Core/src/Kernel/LoadDiscoveryLocations.php

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
use Tempest\Discovery\DiscoveryException;
1010
use Tempest\Discovery\DiscoveryLocation;
1111

12-
use function Tempest\Support\path;
12+
use function Tempest\Support\Path\normalize;
1313

1414
/** @internal */
1515
final readonly class LoadDiscoveryLocations
@@ -34,8 +34,8 @@ public function __invoke(): void
3434
*/
3535
private function discoverCorePackages(): array
3636
{
37-
$composerPath = path($this->kernel->root, 'vendor/composer');
38-
$installed = $this->loadJsonFile(path($composerPath, 'installed.json')->toString());
37+
$composerPath = normalize($this->kernel->root, 'vendor/composer');
38+
$installed = $this->loadJsonFile(normalize($composerPath, 'installed.json'));
3939
$packages = $installed['packages'] ?? [];
4040

4141
$discoveredLocations = [];
@@ -48,12 +48,12 @@ private function discoverCorePackages(): array
4848
continue;
4949
}
5050

51-
$packagePath = path($composerPath, $package['install-path'] ?? '');
51+
$packagePath = normalize($composerPath, $package['install-path'] ?? '');
5252

5353
foreach ($package['autoload']['psr-4'] as $namespace => $namespacePath) {
54-
$namespacePath = path($packagePath, $namespacePath);
54+
$namespacePath = normalize($packagePath, $namespacePath);
5555

56-
$discoveredLocations[] = new DiscoveryLocation($namespace, $namespacePath->toString());
56+
$discoveredLocations[] = new DiscoveryLocation($namespace, $namespacePath);
5757
}
5858
}
5959

@@ -68,9 +68,9 @@ private function discoverAppNamespaces(): array
6868
$discoveredLocations = [];
6969

7070
foreach ($this->composer->namespaces as $namespace) {
71-
$path = path($this->kernel->root, $namespace->path);
71+
$path = normalize($this->kernel->root, $namespace->path);
7272

73-
$discoveredLocations[] = new DiscoveryLocation($namespace->namespace, $path->toString());
73+
$discoveredLocations[] = new DiscoveryLocation($namespace->namespace, $path);
7474
}
7575

7676
return $discoveredLocations;
@@ -81,8 +81,8 @@ private function discoverAppNamespaces(): array
8181
*/
8282
private function discoverVendorPackages(): array
8383
{
84-
$composerPath = path($this->kernel->root, 'vendor/composer');
85-
$installed = $this->loadJsonFile(path($composerPath, 'installed.json')->toString());
84+
$composerPath = normalize($this->kernel->root, 'vendor/composer');
85+
$installed = $this->loadJsonFile(normalize($composerPath, 'installed.json'));
8686
$packages = $installed['packages'] ?? [];
8787

8888
$discoveredLocations = [];
@@ -95,7 +95,7 @@ private function discoverVendorPackages(): array
9595
continue;
9696
}
9797

98-
$packagePath = path($composerPath, $package['install-path'] ?? '');
98+
$packagePath = normalize($composerPath, $package['install-path'] ?? '');
9999
$requiresTempest = isset($package['require']['tempest/framework']) || isset($package['require']['tempest/core']);
100100
$hasPsr4Namespaces = isset($package['autoload']['psr-4']);
101101

@@ -104,9 +104,9 @@ private function discoverVendorPackages(): array
104104
}
105105

106106
foreach ($package['autoload']['psr-4'] as $namespace => $namespacePath) {
107-
$path = path($packagePath, $namespacePath);
107+
$path = normalize($packagePath, $namespacePath);
108108

109-
$discoveredLocations[] = new DiscoveryLocation($namespace, $path->toString());
109+
$discoveredLocations[] = new DiscoveryLocation($namespace, $path);
110110
}
111111
}
112112

src/Tempest/Core/src/PublishesFiles.php

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
use function Tempest\root_path;
2727
use function Tempest\Support\Namespace\to_base_class_name;
2828
use function Tempest\Support\path;
29+
use function Tempest\Support\Path\to_absolute_path;
30+
use function Tempest\Support\Path\to_relative_path;
2931
use function Tempest\Support\str;
3032

3133
use const JSON_PRETTY_PRINT;
@@ -147,18 +149,14 @@ public function getSuggestedPath(string $className, ?string $pathPrefix = null,
147149
{
148150
// Separate input path and classname
149151
$inputClassName = to_base_class_name($className);
150-
$inputPath = str(path($className))->replaceLast($inputClassName, '')->toString();
152+
$inputPath = path($className)->stripEnd($inputClassName);
151153
$className = str($inputClassName)
152154
->pascal()
153155
->finish($classSuffix ?? '')
154156
->toString();
155157

156158
// Prepare the suggested path from the project namespace
157-
return str(path(
158-
$this->composer->mainNamespace->path,
159-
$pathPrefix ?? '',
160-
$inputPath,
161-
))
159+
return str(to_absolute_path($this->composer->mainNamespace->path, $pathPrefix ?? '', $inputPath))
162160
->finish('/')
163161
->append($className . '.php')
164162
->toString();
@@ -266,7 +264,7 @@ function (string $content) use ($callback) {
266264

267265
private function friendlyFileName(string $path): string
268266
{
269-
return str_replace(str(root_path())->finish('/')->toString(), '', $path);
267+
return to_relative_path(root_path(), $path);
270268
}
271269

272270
private function detectIndent(string $raw): string

src/Tempest/Core/src/functions.php

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,51 +4,63 @@
44

55
namespace Tempest {
66
use Closure;
7+
use ReflectionException;
8+
use RuntimeException;
9+
use Stringable;
10+
use Tempest\Container\Exceptions\CannotInstantiateDependencyException;
11+
use Tempest\Container\Exceptions\CannotResolveTaggedDependency;
12+
use Tempest\Container\Exceptions\CircularDependencyException;
713
use Tempest\Core\Composer;
814
use Tempest\Core\DeferredTasks;
915
use Tempest\Core\Kernel;
16+
use Tempest\Support\Namespace\PathCouldNotBeMappedToNamespaceException;
17+
use Tempest\Support\Regex\InvalidPatternException;
1018

11-
use function Tempest\Support\path;
12-
use function Tempest\Support\str;
19+
use function Tempest\Support\Namespace\to_psr4_namespace;
20+
use function Tempest\Support\Path\to_absolute_path;
1321

1422
/**
15-
* Creates a path scoped within the root of the project
23+
* Creates an absolute path scoped to the root of the project.
1624
*/
17-
function root_path(string ...$parts): string
25+
function root_path(Stringable|string ...$parts): string
1826
{
19-
return path(realpath(get(Kernel::class)->root), ...$parts)->toString();
27+
return to_absolute_path(get(Kernel::class)->root, ...$parts);
2028
}
2129

2230
/**
23-
* Creates an absolute path scoped within the framework's internal storage directory.
31+
* Creates an absolute path scoped to the main directory of the project.
2432
*/
25-
function internal_storage_path(string ...$parts): string
33+
function src_path(Stringable|string ...$parts): string
2634
{
27-
return path(get(Kernel::class)->internalStorage, ...$parts)->toString();
35+
return root_path(get(Composer::class)->mainNamespace->path, ...$parts);
2836
}
2937

3038
/**
31-
* Creates a relative path scoped within the main directory of the project.
39+
* Creates an absolute path scoped to the framework's internal storage directory.
3240
*/
33-
function src_path(string ...$parts): string
41+
function internal_storage_path(Stringable|string ...$parts): string
3442
{
35-
$composer = get(Composer::class);
36-
37-
return path($composer->mainNamespace->path, ...$parts)->toString();
43+
return root_path(get(Kernel::class)->internalStorage, ...$parts);
3844
}
3945

4046
/**
41-
* Creates a namespace scoped within the main namespace of the project.
47+
* Converts the given path to a registered namespace. The path is expected to be absolute, or relative to the root of the project.
48+
*
49+
* @throws PathCouldNotBeMappedToNamespaceException If the path cannot be mapped to registered namespace
4250
*/
43-
function src_namespace(?string $append = null): string
51+
function registered_namespace(Stringable|string ...$parts): string
4452
{
45-
$composer = get(Composer::class);
53+
return to_psr4_namespace(get(Composer::class)->namespaces, root_path(...$parts), root: root_path());
54+
}
4655

47-
return str($composer->mainNamespace->namespace)
48-
->append($append ?? '')
49-
->replace('\\\\', '\\')
50-
->trim('\\')
51-
->toString();
56+
/**
57+
* Converts the given path to the main namespace. The path is expected to be absolute, or relative to the root of the project.
58+
*
59+
* @throws PathCouldNotBeMappedToNamespaceException If the path cannot be mapped to the main namespace
60+
*/
61+
function src_namespace(Stringable|string ...$parts): string
62+
{
63+
return to_psr4_namespace(get(Composer::class)->mainNamespace, root_path(...$parts), root: root_path());
5264
}
5365

5466
/**

0 commit comments

Comments
 (0)