Skip to content

Commit dca9b94

Browse files
ChemaclassJesusValeraDev
authored andcommitted
ref: extract methods in ApiMarkdownGenerator for better readability
1 parent 6f367ac commit dca9b94

File tree

4 files changed

+272
-74
lines changed

4 files changed

+272
-74
lines changed

build/src/ApiGenerator/Application/ApiMarkdownGenerator.php

Lines changed: 188 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace PhelWeb\ApiGenerator\Application;
66

77
use Phel\Api\Transfer\PhelFunction;
8+
use Phel\Lang\Symbol;
89
use Phel\Shared\Facade\ApiFacadeInterface;
910

1011
final readonly class ApiMarkdownGenerator
@@ -19,68 +20,206 @@ public function __construct(
1920
*/
2021
public function generate(): array
2122
{
22-
$result = $this->zolaHeaders();
23-
24-
/** @var list<PhelFunction> $phelFns */
23+
$result = $this->buildZolaHeaders();
2524
$phelFns = $this->apiFacade->getPhelFunctions();
25+
$groupedByNamespace = $this->groupFunctionsByNamespace($phelFns);
26+
27+
foreach ($groupedByNamespace as $namespace => $functions) {
28+
$result = array_merge($result, $this->buildNamespaceSection($namespace, $functions));
29+
}
30+
31+
return $result;
32+
}
2633

27-
$groupedByNamespace = [];
34+
/**
35+
* @param list<PhelFunction> $phelFns
36+
* @return array<string, list<PhelFunction>>
37+
*/
38+
private function groupFunctionsByNamespace(array $phelFns): array
39+
{
40+
$grouped = [];
2841
foreach ($phelFns as $fn) {
29-
$groupedByNamespace[$fn->namespace][] = $fn;
42+
$grouped[$fn->namespace][] = $fn;
3043
}
44+
return $grouped;
45+
}
3146

32-
foreach ($groupedByNamespace as $namespace => $fns) {
33-
34-
$result[] = "";
35-
$result[] = "---";
36-
$result[] = "";
37-
$result[] = "## `{$namespace}`";
38-
39-
/** @var PhelFunction $fn */
40-
foreach ($fns as $fn) {
41-
$result[] = "### `{$fn->nameWithNamespace()}`";
42-
if (isset($fn->meta['deprecated'])) {
43-
$deprecatedMessage = sprintf(
44-
'<small><span style="color: red; font-weight: bold;">Deprecated</span>: %s',
45-
$fn->meta['deprecated']
46-
);
47-
if (isset($fn->meta['superseded-by'])) {
48-
$supersededBy = $fn->meta['superseded-by'];
49-
$deprecatedMessage .= sprintf(
50-
' &mdash; Use [`%s`](#%s) instead',
51-
$supersededBy,
52-
$supersededBy
53-
);
54-
}
55-
$deprecatedMessage .= '</small>';
56-
$result[] = $deprecatedMessage;
57-
}
58-
$result[] = $fn->doc;
59-
if ($fn->githubUrl !== '') {
60-
$result[] = sprintf('<small>[[View source](%s)]</small>', $fn->githubUrl);
61-
} elseif ($fn->docUrl !== '') {
62-
$result[] = sprintf('<small>[[Read more](%s)]</small>', $fn->docUrl);
63-
}
64-
}
47+
/**
48+
* @param list<PhelFunction> $functions
49+
* @return list<string>
50+
*/
51+
private function buildNamespaceSection(string $namespace, array $functions): array
52+
{
53+
$lines = [
54+
'',
55+
'---',
56+
'',
57+
"## `{$namespace}`",
58+
];
59+
60+
foreach ($functions as $fn) {
61+
$lines = array_merge($lines, $this->buildFunctionSection($fn));
6562
}
6663

67-
return $result;
64+
return $lines;
6865
}
6966

7067
/**
7168
* @return list<string>
7269
*/
73-
private function zolaHeaders(): array
70+
private function buildFunctionSection(PhelFunction $fn): array
7471
{
75-
$result = [];
76-
$result[] = '+++';
77-
$result[] = 'title = "API"';
78-
$result[] = 'weight = 110';
79-
$result[] = 'template = "page-api.html"';
80-
$result[] = 'aliases = [ "/api" ]';
81-
$result[] = '+++';
82-
$result[] = '';
72+
$lines = ["### `{$fn->nameWithNamespace()}`"];
8373

84-
return $result;
74+
if ($deprecation = $this->buildDeprecationNotice($fn)) {
75+
$lines[] = $deprecation;
76+
}
77+
78+
$lines[] = $fn->doc;
79+
80+
if ($example = $this->buildExampleSection($fn)) {
81+
$lines = array_merge($lines, $example);
82+
}
83+
84+
if ($seeAlso = $this->buildSeeAlsoSection($fn)) {
85+
$lines = array_merge($lines, $seeAlso);
86+
}
87+
88+
if ($sourceLink = $this->buildSourceLink($fn)) {
89+
$lines[] = $sourceLink;
90+
}
91+
92+
return $lines;
93+
}
94+
95+
private function buildDeprecationNotice(PhelFunction $fn): ?string
96+
{
97+
if (!isset($fn->meta['deprecated'])) {
98+
return null;
99+
}
100+
101+
$message = sprintf(
102+
'<small><span style="color: red; font-weight: bold;">Deprecated</span>: %s',
103+
$fn->meta['deprecated']
104+
);
105+
106+
if (isset($fn->meta['superseded-by'])) {
107+
$supersededBy = $fn->meta['superseded-by'];
108+
$anchor = $this->sanitizeAnchor($supersededBy);
109+
$message .= sprintf(
110+
' &mdash; Use [`%s`](#%s) instead',
111+
$supersededBy,
112+
$anchor
113+
);
114+
}
115+
116+
return $message . '</small>';
117+
}
118+
119+
/**
120+
* @return list<string>|null
121+
*/
122+
private function buildExampleSection(PhelFunction $fn): ?array
123+
{
124+
if (!isset($fn->meta['example'])) {
125+
return null;
126+
}
127+
128+
return [
129+
'',
130+
'**Example:**',
131+
'',
132+
'```phel',
133+
$fn->meta['example'],
134+
'```',
135+
];
136+
}
137+
138+
/**
139+
* @return list<string>|null
140+
*/
141+
private function buildSeeAlsoSection(PhelFunction $fn): ?array
142+
{
143+
if (!isset($fn->meta['see-also'])) {
144+
return null;
145+
}
146+
147+
$functionNames = $this->extractFunctionNames($fn->meta['see-also']);
148+
$links = $this->buildFunctionLinks($functionNames);
149+
150+
return [
151+
'',
152+
'**See also:** ' . implode(', ', $links),
153+
];
154+
}
155+
156+
/**
157+
* @return list<string>
158+
*/
159+
private function extractFunctionNames(mixed $seeAlso): array
160+
{
161+
return array_map(
162+
fn(Symbol $symbol) => $symbol->getName(),
163+
iterator_to_array($seeAlso)
164+
);
165+
}
166+
167+
/**
168+
* @param list<string> $functionNames
169+
* @return list<string>
170+
*/
171+
private function buildFunctionLinks(array $functionNames): array
172+
{
173+
return array_map(
174+
fn(string $func) => sprintf(
175+
'[`%s`](#%s)',
176+
$func,
177+
$this->sanitizeAnchor($func)
178+
),
179+
$functionNames
180+
);
181+
}
182+
183+
private function buildSourceLink(PhelFunction $fn): ?string
184+
{
185+
if ($fn->githubUrl !== '') {
186+
return sprintf('<small>[[View source](%s)]</small>', $fn->githubUrl);
187+
}
188+
189+
if ($fn->docUrl !== '') {
190+
return sprintf('<small>[[Read more](%s)]</small>', $fn->docUrl);
191+
}
192+
193+
return null;
194+
}
195+
196+
/**
197+
* Sanitize function name to match Zola's anchor generation.
198+
* Removes special characters that Zola doesn't include in anchors.
199+
*
200+
* Examples:
201+
* "empty?" becomes "empty"
202+
* "set!" becomes "set"
203+
* "php-array-to-map" stays "php-array-to-map"
204+
*/
205+
private function sanitizeAnchor(string $funcName): string
206+
{
207+
return preg_replace('/[^a-zA-Z0-9_-]/', '', $funcName);
208+
}
209+
210+
/**
211+
* @return list<string>
212+
*/
213+
private function buildZolaHeaders(): array
214+
{
215+
return [
216+
'+++',
217+
'title = "API"',
218+
'weight = 110',
219+
'template = "page-api.html"',
220+
'aliases = [ "/api" ]',
221+
'+++',
222+
'',
223+
];
85224
}
86225
}

composer.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"require": {
1212
"php": ">=8.3",
1313
"ext-json": "*",
14-
"phel-lang/phel-lang": "^0.26",
14+
"phel-lang/phel-lang": "dev-main",
1515
"gacela-project/gacela": "^1.12"
1616
},
1717
"require-dev": {
@@ -46,5 +46,12 @@
4646
"post-update-cmd": [
4747
"php build/update-phel-version.php"
4848
]
49+
},
50+
"repositories": [
51+
{
52+
"type": "path",
53+
"url": "/Users/chema/Code/phel-lang/phel-lang"
4954
}
55+
]
56+
5057
}

0 commit comments

Comments
 (0)