|
| 1 | +<?php |
| 2 | + |
| 3 | +namespace Tempest\Framework\Commands; |
| 4 | + |
| 5 | +use Tempest\Console\ConsoleArgument; |
| 6 | +use Tempest\Console\ConsoleCommand; |
| 7 | +use Tempest\Console\HasConsole; |
| 8 | +use Tempest\Support\Arr\ImmutableArray; |
| 9 | +use Tempest\Support\Str\ImmutableString; |
| 10 | +use Tempest\View\Components\AnonymousViewComponent; |
| 11 | +use Tempest\View\Slot; |
| 12 | +use Tempest\View\ViewComponent; |
| 13 | +use Tempest\View\ViewConfig; |
| 14 | + |
| 15 | +use function Tempest\Support\arr; |
| 16 | +use function Tempest\Support\Filesystem\is_file; |
| 17 | +use function Tempest\Support\str; |
| 18 | + |
| 19 | +final class MetaViewComponentCommand |
| 20 | +{ |
| 21 | + use HasConsole; |
| 22 | + |
| 23 | + public function __construct( |
| 24 | + private readonly ViewConfig $viewConfig, |
| 25 | + ) {} |
| 26 | + |
| 27 | + #[ConsoleCommand(name: 'meta:view-component', hidden: true)] |
| 28 | + public function __invoke( |
| 29 | + #[ConsoleArgument(description: "The view component's name or the path to a view component file")] |
| 30 | + ?string $viewComponent = null, |
| 31 | + ): void { |
| 32 | + if ($viewComponent) { |
| 33 | + $viewComponentName = $viewComponent; |
| 34 | + |
| 35 | + $viewComponent = $this->resolveViewComponent($viewComponentName); |
| 36 | + |
| 37 | + if ($viewComponent === null) { |
| 38 | + $this->error('Unknown view component `' . $viewComponentName . '`'); |
| 39 | + return; |
| 40 | + } |
| 41 | + |
| 42 | + $data = $this->makeData($viewComponent); |
| 43 | + } else { |
| 44 | + $data = arr($this->viewConfig->viewComponents) |
| 45 | + ->filter(fn (string|ViewComponent $viewComponent) => $viewComponent instanceof AnonymousViewComponent) |
| 46 | + ->map(fn (AnonymousViewComponent $viewComponent) => $this->makeData($viewComponent)->toArray()); |
| 47 | + } |
| 48 | + |
| 49 | + $this->writeln($data->encodeJson(pretty: true)); |
| 50 | + } |
| 51 | + |
| 52 | + private function makeData(AnonymousViewComponent $viewComponent): ImmutableArray |
| 53 | + { |
| 54 | + return arr([ |
| 55 | + 'file' => $viewComponent->file, |
| 56 | + 'name' => $viewComponent->name, |
| 57 | + 'slots' => $this->resolveSlots($viewComponent)->toArray(), |
| 58 | + 'variables' => $this->resolveVariables($viewComponent)->toArray(), |
| 59 | + ]); |
| 60 | + } |
| 61 | + |
| 62 | + private function resolveViewComponent(string $viewComponent): ?AnonymousViewComponent |
| 63 | + { |
| 64 | + if (is_file($viewComponent)) { |
| 65 | + foreach ($this->viewConfig->viewComponents as $registeredViewComponent) { |
| 66 | + if (! ($registeredViewComponent instanceof AnonymousViewComponent)) { |
| 67 | + continue; |
| 68 | + } |
| 69 | + |
| 70 | + if ($registeredViewComponent->file !== $viewComponent) { |
| 71 | + continue; |
| 72 | + } |
| 73 | + |
| 74 | + $viewComponent = $registeredViewComponent; |
| 75 | + |
| 76 | + break; |
| 77 | + } |
| 78 | + } else { |
| 79 | + $viewComponent = $this->viewConfig->viewComponents[$viewComponent] ?? null; |
| 80 | + } |
| 81 | + |
| 82 | + if ($viewComponent === null) { |
| 83 | + return null; |
| 84 | + } |
| 85 | + |
| 86 | + if (! ($viewComponent instanceof AnonymousViewComponent)) { |
| 87 | + return null; |
| 88 | + } |
| 89 | + |
| 90 | + return $viewComponent; |
| 91 | + } |
| 92 | + |
| 93 | + private function resolveSlots(AnonymousViewComponent $viewComponent): ImmutableArray |
| 94 | + { |
| 95 | + preg_match_all('/<x-slot\s*(name="(?<name>[\w-]+)")?((\s*\/>)|>(?<default>(.|\n)*?)<\/x-slot>)/', $viewComponent->contents, $matches); |
| 96 | + |
| 97 | + return arr($matches['name']) |
| 98 | + ->mapWithKeys(fn (string $name) => yield $name => $name === '' ? Slot::DEFAULT : $name) |
| 99 | + ->values(); |
| 100 | + } |
| 101 | + |
| 102 | + private function resolveVariables(AnonymousViewComponent $viewComponent): ImmutableArray |
| 103 | + { |
| 104 | + return str($viewComponent->contents) |
| 105 | + ->matchAll('/^\s*\*\s*@var.*$/m') |
| 106 | + ->map(fn (array $matches) => str($matches[0])) |
| 107 | + ->map(fn (ImmutableString $line) => $line->replaceRegex('/^\s*\*\s*@var\s*/', '')) |
| 108 | + ->map(fn (ImmutableString $line) => $line->trim()) |
| 109 | + ->map(fn (ImmutableString $line) => $line->explode(limit: 3)) |
| 110 | + ->mapWithKeys( |
| 111 | + fn (ImmutableArray $parts) => yield $parts[1] => [ |
| 112 | + 'type' => $parts[0], |
| 113 | + 'name' => $parts[1], |
| 114 | + 'attributeName' => str($parts[1])->kebab()->ltrim('$'), |
| 115 | + 'description' => $parts[2] ?? null, |
| 116 | + ], |
| 117 | + ) |
| 118 | + ->filter(fn (array $parts) => $parts['name'] !== '$this') |
| 119 | + ->values(); |
| 120 | + } |
| 121 | +} |
0 commit comments