Parse and validate AT Protocol Lexicons with DTO generation for Laravel
1<?php
2
3namespace SocialDept\AtpSchema\Generator;
4
5use SocialDept\AtpSchema\Exceptions\GenerationException;
6
7class StubRenderer
8{
9 /**
10 * Path to stub files.
11 */
12 protected string $stubPath;
13
14 /**
15 * Cached stub contents.
16 *
17 * @var array<string, string>
18 */
19 protected array $stubs = [];
20
21 /**
22 * Create a new StubRenderer.
23 */
24 public function __construct(?string $stubPath = null)
25 {
26 $this->stubPath = $stubPath ?? $this->getDefaultStubPath();
27 }
28
29 /**
30 * Render a stub with variables.
31 *
32 * @param array<string, mixed> $variables
33 */
34 public function render(string $stub, array $variables): string
35 {
36 $template = $this->loadStub($stub);
37
38 return $this->replaceVariables($template, $variables);
39 }
40
41 /**
42 * Load a stub file.
43 */
44 protected function loadStub(string $name): string
45 {
46 if (isset($this->stubs[$name])) {
47 return $this->stubs[$name];
48 }
49
50 $path = $this->getStubPath($name);
51
52 if (! file_exists($path)) {
53 throw GenerationException::templateNotFound($name);
54 }
55
56 $content = file_get_contents($path);
57
58 if ($content === false) {
59 throw GenerationException::cannotReadFile($path);
60 }
61
62 $this->stubs[$name] = $content;
63
64 return $content;
65 }
66
67 /**
68 * Replace variables in template.
69 *
70 * @param array<string, mixed> $variables
71 */
72 protected function replaceVariables(string $template, array $variables): string
73 {
74 $result = $template;
75
76 foreach ($variables as $key => $value) {
77 // Convert value to string
78 $stringValue = $this->valueToString($value);
79
80 // Replace {{ key }} with value
81 $result = str_replace('{{ '.$key.' }}', $stringValue, $result);
82 }
83
84 // Remove any remaining unreplaced variables
85 $result = preg_replace('/\{\{\s*\w+\s*\}\}/', '', $result);
86
87 return $result;
88 }
89
90 /**
91 * Convert a value to string for replacement.
92 */
93 protected function valueToString(mixed $value): string
94 {
95 if (is_array($value)) {
96 return implode("\n", array_filter($value));
97 }
98
99 if (is_bool($value)) {
100 return $value ? 'true' : 'false';
101 }
102
103 if ($value === null) {
104 return '';
105 }
106
107 return (string) $value;
108 }
109
110 /**
111 * Get the path for a stub file.
112 */
113 protected function getStubPath(string $name): string
114 {
115 // Check for published stubs first (in Laravel app)
116 $publishedPath = base_path('stubs/schema/'.$name.'.stub');
117 if (file_exists($publishedPath)) {
118 return $publishedPath;
119 }
120
121 // Fall back to package stubs
122 return $this->stubPath.'/'.$name.'.stub';
123 }
124
125 /**
126 * Get default stub path.
127 */
128 protected function getDefaultStubPath(): string
129 {
130 return __DIR__.'/../../stubs';
131 }
132
133 /**
134 * Clear cached stubs.
135 */
136 public function clearCache(): void
137 {
138 $this->stubs = [];
139 }
140
141 /**
142 * Set custom stub path.
143 */
144 public function setStubPath(string $path): void
145 {
146 $this->stubPath = $path;
147 $this->clearCache();
148 }
149
150 /**
151 * Get available stubs.
152 *
153 * @return array<string>
154 */
155 public function getAvailableStubs(): array
156 {
157 $stubs = [];
158
159 foreach (glob($this->stubPath.'/*.stub') as $file) {
160 $stubs[] = basename($file, '.stub');
161 }
162
163 return $stubs;
164 }
165}