1: <?php declare(strict_types = 1);
2:
3: namespace ApiGen\Renderer\Latte;
4:
5: use ApiGen\Index\Index;
6: use ApiGen\Renderer\Filter;
7: use ApiGen\Renderer\Latte\Template\ClassLikeTemplate;
8: use ApiGen\Renderer\Latte\Template\ConfigParameters;
9: use ApiGen\Renderer\Latte\Template\FunctionTemplate;
10: use ApiGen\Renderer\Latte\Template\IndexTemplate;
11: use ApiGen\Renderer\Latte\Template\LayoutParameters;
12: use ApiGen\Renderer\Latte\Template\NamespaceTemplate;
13: use ApiGen\Renderer\Latte\Template\SitemapTemplate;
14: use ApiGen\Renderer\Latte\Template\SourceTemplate;
15: use ApiGen\Renderer\Latte\Template\TreeTemplate;
16: use ApiGen\Renderer\UrlGenerator;
17: use ApiGen\Task\Task;
18: use ApiGen\Task\TaskHandler;
19: use Latte;
20: use Nette\Utils\FileSystem;
21: use Nette\Utils\Json;
22: use ReflectionClass;
23:
24: use function array_key_first;
25: use function assert;
26: use function basename;
27: use function lcfirst;
28: use function sprintf;
29: use function substr;
30:
31:
32: /**
33: * @implements TaskHandler<LatteRenderTask, string>
34: */
35: class LatteRenderTaskHandler implements TaskHandler
36: {
37: protected Index $index;
38:
39: protected ConfigParameters $config;
40:
41: public function __construct(
42: protected Latte\Engine $latte,
43: protected UrlGenerator $urlGenerator,
44: protected Filter $filter,
45: protected string $outputDir,
46: mixed $context,
47: ) {
48: assert($context instanceof LatteRenderTaskContext);
49: $this->index = $context->index;
50: $this->config = $context->config;
51: }
52:
53:
54: /**
55: * @param LatteRenderTask $task
56: */
57: public function handle(Task $task): string
58: {
59: return match ($task->type) {
60: LatteRenderTaskType::Asset => $this->copyAsset($task->key),
61:
62: LatteRenderTaskType::ElementsJs => $this->renderElementsJs(),
63: LatteRenderTaskType::Index => $this->renderIndex(),
64: LatteRenderTaskType::Tree => $this->renderTree(),
65: LatteRenderTaskType::Sitemap => $this->renderSitemap(),
66:
67: LatteRenderTaskType::Namespace => $this->renderNamespace($task->key),
68: LatteRenderTaskType::ClassLike => $this->renderClassLike($task->key),
69: LatteRenderTaskType::Function => $this->renderFunction($task->key),
70: LatteRenderTaskType::Source => $this->renderSource($task->key),
71: };
72: }
73:
74:
75: protected function copyAsset(string $key): string
76: {
77: $assetPath = $this->urlGenerator->getAssetPath(basename($key));
78: FileSystem::copy($key, "$this->outputDir/$assetPath");
79:
80: return $assetPath;
81: }
82:
83:
84: protected function renderElementsJs(): string
85: {
86: $elements = [];
87:
88: foreach ($this->index->namespace as $namespace) {
89: if ($this->filter->filterNamespacePage($namespace)) {
90: $elements['namespace'][] = [$namespace->name->full, $this->urlGenerator->getNamespaceUrl($namespace)];
91: }
92: }
93:
94: foreach ($this->index->classLike as $classLike) {
95: if (!$this->filter->filterClassLikePage($classLike)) {
96: continue;
97: }
98:
99: $members = [];
100:
101: foreach ($classLike->constants as $constant) {
102: $members['constant'][] = [$constant->name, $this->urlGenerator->getMemberAnchor($constant)];
103: }
104:
105: foreach ($classLike->properties as $property) {
106: $members['property'][] = [$property->name, $this->urlGenerator->getMemberAnchor($property)];
107: }
108:
109: foreach ($classLike->methods as $method) {
110: $members['method'][] = [$method->name, $this->urlGenerator->getMemberAnchor($method)];
111: }
112:
113: $elements['classLike'][] = [$classLike->name->full, $this->urlGenerator->getClassLikeUrl($classLike), $members];
114: }
115:
116: foreach ($this->index->function as $function) {
117: if ($this->filter->filterFunctionPage($function)) {
118: $elements['function'][] = [$function->name->full, $this->urlGenerator->getFunctionUrl($function)];
119: }
120: }
121:
122: $js = sprintf('window.ApiGen?.resolveElements(%s)', Json::encode($elements));
123: $assetPath = $this->urlGenerator->getAssetPath('elements.js');
124: FileSystem::write("$this->outputDir/$assetPath", $js);
125:
126: return $assetPath;
127: }
128:
129:
130: protected function renderIndex(): string
131: {
132: return $this->renderTemplate($this->urlGenerator->getIndexPath(), new IndexTemplate(
133: index: $this->index,
134: config: $this->config,
135: layout: new LayoutParameters(activePage: 'index'),
136: ));
137: }
138:
139:
140: protected function renderTree(): string
141: {
142: return $this->renderTemplate($this->urlGenerator->getTreePath(), new TreeTemplate(
143: index: $this->index,
144: config: $this->config,
145: layout: new LayoutParameters(activePage: 'tree'),
146: ));
147: }
148:
149:
150: protected function renderSitemap(): string
151: {
152: return $this->renderTemplate($this->urlGenerator->getSitemapPath(), new SitemapTemplate(
153: index: $this->index,
154: config: $this->config,
155: ));
156: }
157:
158:
159: protected function renderNamespace(string $key): string
160: {
161: $info = $this->index->namespace[$key];
162:
163: return $this->renderTemplate($this->urlGenerator->getNamespacePath($info), new NamespaceTemplate(
164: index: $this->index,
165: config: $this->config,
166: layout: new LayoutParameters('namespace', activeNamespace: $info, noindex: !$info->primary),
167: namespace: $info,
168: ));
169: }
170:
171:
172: protected function renderClassLike(string $key): string
173: {
174: $info = $this->index->classLike[$key];
175: $activeNamespace = $this->index->namespace[$info->name->namespaceLower];
176:
177: return $this->renderTemplate($this->urlGenerator->getClassLikePath($info), new ClassLikeTemplate(
178: index: $this->index,
179: config: $this->config,
180: layout: new LayoutParameters('classLike', $activeNamespace, $info, noindex: !$info->primary),
181: classLike: $info,
182: ));
183: }
184:
185:
186: protected function renderFunction(string $key): string
187: {
188: $info = $this->index->function[$key];
189: $activeNamespace = $this->index->namespace[$info->name->namespaceLower];
190:
191: return $this->renderTemplate($this->urlGenerator->getFunctionPath($info), new FunctionTemplate(
192: index: $this->index,
193: config: $this->config,
194: layout: new LayoutParameters('function', $activeNamespace, $info, noindex: !$info->primary),
195: function: $info,
196: ));
197: }
198:
199:
200: protected function renderSource(string $key): string
201: {
202: $info = $this->index->files[$key];
203: $activeElement = $info->classLike[array_key_first($info->classLike)] ?? $info->function[array_key_first($info->function)] ?? null;
204: $activeNamespace = $activeElement ? $this->index->namespace[$activeElement->name->namespaceLower] : null;
205:
206: return $this->renderTemplate($this->urlGenerator->getSourcePath($info->path), new SourceTemplate(
207: index: $this->index,
208: config: $this->config,
209: layout: new LayoutParameters('source', $activeNamespace, $activeElement, noindex: !$info->primary),
210: path: $info->path,
211: ));
212: }
213:
214:
215: protected function renderTemplate(string $outputPath, object $template): string
216: {
217: $className = (new ReflectionClass($template))->getShortName();
218: $lattePath = 'pages/' . lcfirst(substr($className, 0, -8)) . '.latte';
219: FileSystem::write("$this->outputDir/$outputPath", $this->latte->renderToString($lattePath, $template));
220:
221: return $outputPath;
222: }
223: }
224: