Skip to content

Commit 48bc41d

Browse files
Merge pull request #433 from inertiajs/llsm-txt
add .md to any doc url
2 parents d8d7b21 + 94a3556 commit 48bc41d

File tree

3 files changed

+149
-128
lines changed

3 files changed

+149
-128
lines changed

app/Console/Commands/GenerateLlmsTxt.php

Lines changed: 2 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,10 @@
22

33
namespace App\Console\Commands;
44

5+
use App\Support\Markdown;
56
use DOMDocument;
67
use DOMNode;
78
use Illuminate\Console\Command;
8-
use League\HTMLToMarkdown\Converter\ConverterInterface;
9-
use League\HTMLToMarkdown\ElementInterface;
10-
use League\HTMLToMarkdown\HtmlConverter;
11-
use Illuminate\Support\Str;
12-
use SebastianBergmann\CodeCoverage\Report\PHP;
139

1410
class GenerateLlmsTxt extends Command
1511
{
@@ -51,127 +47,7 @@ public function writePage(string $url)
5147

5248
$page = file_get_contents(resource_path('js/Pages/' . $url . '.jsx'));
5349

54-
$replace = [
55-
'<>' => '',
56-
'</>' => '',
57-
"{' '}" => ' ',
58-
'<Link' => '<a',
59-
'</Link>' => '</a>',
60-
'<Notice>' => '<p>',
61-
'</Notice>' => '</p>',
62-
];
63-
64-
$codeBlockType = null;
65-
$inFencedCodeBlock = false;
66-
67-
$page = str($page)
68-
->after('return (')
69-
->beforeLast(')')
70-
->replace(array_keys($replace), array_values($replace))
71-
->replaceMatches('/className=\{[^}]*\}/', '')
72-
->explode(PHP_EOL)
73-
->map(function ($line) use (&$codeBlockType, &$inFencedCodeBlock) {
74-
if (str_contains($line, '<TabbedCode')) {
75-
$codeBlockType = 'tabbedcode';
76-
77-
$this->currentCodeBlockId = Str::random(10);
78-
$this->tabbedCodeBlocks[$this->currentCodeBlockId] = $line;
79-
80-
return '<p><tabbedcode>' . $this->currentCodeBlockId . '</tabbedcode></p>';
81-
}
82-
83-
if (str_contains($line, '<CodeBlock')) {
84-
$codeBlockType = 'codeblock';
85-
86-
$this->currentCodeBlockId = Str::random(10);
87-
$this->codeBlocks[$this->currentCodeBlockId] = $line;
88-
89-
return '<p><codeblock>' . $this->currentCodeBlockId . '</codeblock></p>';
90-
}
91-
92-
if ($codeBlockType === null) {
93-
return str($line)->replaceMatches('/\s+/', ' ')->replaceMatches('/\{\'<(.+)>\'\}/', '&lt;$1&gt;')->trim()->toString();
94-
}
95-
96-
if (str_contains($line, '`')) {
97-
$inFencedCodeBlock = !$inFencedCodeBlock;
98-
}
99-
100-
try {
101-
if ($codeBlockType === 'tabbedcode') {
102-
$this->tabbedCodeBlocks[$this->currentCodeBlockId] .= $line;
103-
} else {
104-
$this->codeBlocks[$this->currentCodeBlockId] .= $line;
105-
}
106-
} catch (\Exception $e) {
107-
dd($this->tabbedCodeBlocks, $this->codeBlocks, $codeBlockType, $line);
108-
}
109-
110-
if ($inFencedCodeBlock) {
111-
// Don't look for ending tag inside fenced code blocks
112-
return null;
113-
}
114-
115-
if (str_contains($line, '/>')) {
116-
$codeBlockType = null;
117-
}
118-
119-
return null;
120-
})
121-
->filter(fn($line) => $line !== null && trim($line) !== '')
122-
->implode(PHP_EOL);
123-
124-
$converter = new HtmlConverter(['header_style' => 'atx', 'hard_break' => true, 'remove_nodes' => 'div']);
125-
$converter->getEnvironment()->addConverter(new class($this->tabbedCodeBlocks, $this->codeBlocks) implements ConverterInterface {
126-
public function __construct(protected array $tabbedBlocks, protected array $codeBlocks)
127-
{
128-
//
129-
}
130-
131-
public function convert(ElementInterface $node): string
132-
{
133-
if ($node->getTagName() === 'tabbedcode') {
134-
$content = $this->tabbedBlocks[$node->getValue()];
135-
136-
return str($content)->after('examples={')->beforeLast('}')->explode('`,')->map(function ($example) {
137-
if (!str_contains($example, 'code:')) {
138-
return null;
139-
}
140-
141-
preg_match("/language: '([^']+)'/", $example, $matches);
142-
$language = $matches[1];
143-
144-
preg_match("/name: '([^']+)'/", $example, $matches);
145-
$name = $matches[1];
146-
147-
preg_match("/description: '([^']+)'/m", $example, $matches);
148-
$description = $matches[1] ?? null;
149-
150-
$code = str($example)->after('dedent`')->beforeLast('`,')->trim()->replaceMatches('/\s{7,}/', PHP_EOL)->toString();
151-
152-
return sprintf("%s%s:\n\n```%s\n%s\n```", $name, $description ? ' (' . $description . ')' : '', $language, $code);
153-
})->filter()->implode(PHP_EOL . PHP_EOL);
154-
}
155-
156-
$content = $this->codeBlocks[$node->getValue()];
157-
preg_match('/language="([^"]+)"/', $content, $matches);
158-
$language = $matches[1];
159-
preg_match('/dedent`([^`]+)`/', $content, $matches);
160-
$code = str($matches[1])->trim()->replaceMatches('/\s{7,}/', PHP_EOL)->toString();
161-
162-
return sprintf("```%s\n%s\n```", $language, $code);
163-
}
164-
165-
public function getSupportedTags(): array
166-
{
167-
return [
168-
'tabbedcode',
169-
'codeblock',
170-
];
171-
}
172-
});
173-
174-
$markdown = $converter->convert($page);
50+
$markdown = Markdown::fromJsx($page);
17551

17652
$this->full[] = $markdown;
17753
}

app/Support/Markdown.php

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<?php
2+
3+
namespace App\Support;
4+
5+
use Illuminate\Support\Str;
6+
use League\HTMLToMarkdown\Converter\ConverterInterface;
7+
use League\HTMLToMarkdown\ElementInterface;
8+
use League\HTMLToMarkdown\HtmlConverter;
9+
10+
class Markdown
11+
{
12+
public static function fromJsx(string $jsx): string
13+
{
14+
$replace = [
15+
'<>' => '',
16+
'</>' => '',
17+
"{' '}" => ' ',
18+
'<Link' => '<a',
19+
'</Link>' => '</a>',
20+
'<Notice>' => '<p>',
21+
'</Notice>' => '</p>',
22+
];
23+
24+
$codeBlockType = null;
25+
$inFencedCodeBlock = false;
26+
$currentCodeBlockId = '';
27+
$tabbedCodeBlocks = [];
28+
$codeBlocks = [];
29+
30+
$page = str($jsx)
31+
->after('return (')
32+
->beforeLast(')')
33+
->replace(array_keys($replace), array_values($replace))
34+
->replaceMatches('/className=\{[^}]*\}/', '')
35+
->explode(PHP_EOL)
36+
->map(function ($line) use (&$codeBlockType, &$inFencedCodeBlock, &$currentCodeBlockId, &$tabbedCodeBlocks, &$codeBlocks) {
37+
if (str_contains($line, '<TabbedCode')) {
38+
$codeBlockType = 'tabbedcode';
39+
40+
$currentCodeBlockId = Str::random(10);
41+
$tabbedCodeBlocks[$currentCodeBlockId] = $line;
42+
43+
return '<p><tabbedcode>' . $currentCodeBlockId . '</tabbedcode></p>';
44+
}
45+
46+
if (str_contains($line, '<CodeBlock')) {
47+
$codeBlockType = 'codeblock';
48+
49+
$currentCodeBlockId = Str::random(10);
50+
$codeBlocks[$currentCodeBlockId] = $line;
51+
52+
return '<p><codeblock>' . $currentCodeBlockId . '</codeblock></p>';
53+
}
54+
55+
if ($codeBlockType === null) {
56+
return str($line)->replaceMatches('/\s+/', ' ')->replaceMatches('/\{\'<(.+)>\'\}/', '&lt;$1&gt;')->trim()->toString();
57+
}
58+
59+
if (str_contains($line, '`')) {
60+
$inFencedCodeBlock = !$inFencedCodeBlock;
61+
}
62+
63+
if ($codeBlockType === 'tabbedcode') {
64+
$tabbedCodeBlocks[$currentCodeBlockId] .= $line;
65+
} else {
66+
$codeBlocks[$currentCodeBlockId] .= $line;
67+
}
68+
69+
if ($inFencedCodeBlock) {
70+
// Don't look for ending tag inside fenced code blocks
71+
return null;
72+
}
73+
74+
if (str_contains($line, '/>')) {
75+
$codeBlockType = null;
76+
}
77+
78+
return null;
79+
})
80+
->filter(fn($line) => $line !== null && trim($line) !== '')
81+
->implode(PHP_EOL);
82+
83+
$converter = new HtmlConverter(['header_style' => 'atx', 'hard_break' => true, 'remove_nodes' => 'div']);
84+
$converter->getEnvironment()->addConverter(new class($tabbedCodeBlocks, $codeBlocks) implements ConverterInterface {
85+
public function __construct(protected array $tabbedBlocks, protected array $codeBlocks)
86+
{
87+
//
88+
}
89+
90+
public function convert(ElementInterface $node): string
91+
{
92+
if ($node->getTagName() === 'tabbedcode') {
93+
$content = $this->tabbedBlocks[$node->getValue()];
94+
95+
return str($content)->after('examples={')->beforeLast('}')->explode('`,')->map(function ($example) {
96+
if (!str_contains($example, 'code:')) {
97+
return null;
98+
}
99+
100+
preg_match("/language: '([^']+)'/", $example, $matches);
101+
$language = $matches[1];
102+
103+
preg_match("/name: '([^']+)'/", $example, $matches);
104+
$name = $matches[1];
105+
106+
preg_match("/description: '([^']+)'/m", $example, $matches);
107+
$description = $matches[1] ?? null;
108+
109+
$code = str($example)->after('dedent`')->beforeLast('`,')->trim()->replaceMatches('/\s{7,}/', PHP_EOL)->toString();
110+
111+
return sprintf("%s%s:\n\n```%s\n%s\n```", $name, $description ? ' (' . $description . ')' : '', $language, $code);
112+
})->filter()->implode(PHP_EOL . PHP_EOL);
113+
}
114+
115+
$content = $this->codeBlocks[$node->getValue()];
116+
preg_match('/language="([^"]+)"/', $content, $matches);
117+
$language = $matches[1];
118+
preg_match('/dedent`([^`]+)`/', $content, $matches);
119+
$code = str($matches[1])->trim()->replaceMatches('/\s{7,}/', PHP_EOL)->toString();
120+
121+
return sprintf("```%s\n%s\n```", $language, $code);
122+
}
123+
124+
public function getSupportedTags(): array
125+
{
126+
return [
127+
'tabbedcode',
128+
'codeblock',
129+
];
130+
}
131+
});
132+
133+
return $converter->convert($page);
134+
}
135+
}

routes/web.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php
22

3+
use App\Support\Markdown;
34
use Illuminate\Support\Facades\App;
45
use Illuminate\Support\Facades\Route;
56
use Inertia\Inertia;
@@ -16,8 +17,17 @@
1617
*/
1718

1819
Route::get('{page?}', function ($page = 'index') {
19-
if (! file_exists(resource_path("js/Pages/$page.jsx"))) {
20-
App::abort(404);
20+
$template = str($page)->before('.md')->toString();
21+
$templatePath = resource_path("js/Pages/$template.jsx");
22+
23+
abort_if(! str_ends_with($templatePath, '.jsx'), 404);
24+
25+
if (str_ends_with($page, '.md')) {
26+
return response(Markdown::fromJsx(
27+
file_get_contents($templatePath)
28+
), 200, [
29+
'Content-Type' => 'text/markdown',
30+
]);
2131
}
2232

2333
return Inertia::render($page);

0 commit comments

Comments
 (0)