@d10f/asciidoc-astro-loader 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +286 -0
- package/dist/chunk-2F52PMNV.js +132 -0
- package/dist/chunk-DDIUST2Z.js +113 -0
- package/dist/chunk-HAZIHU2Y.js +56 -0
- package/dist/index-Cf7MF6tZ.d.cts +325 -0
- package/dist/index-Cf7MF6tZ.d.ts +325 -0
- package/dist/index.cjs +708 -0
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +402 -0
- package/dist/lib/asciidoc/converters/index.cjs +178 -0
- package/dist/lib/asciidoc/converters/index.d.cts +13 -0
- package/dist/lib/asciidoc/converters/index.d.ts +13 -0
- package/dist/lib/asciidoc/converters/index.js +7 -0
- package/dist/lib/asciidoc/templates/engines/index.cjs +191 -0
- package/dist/lib/asciidoc/templates/engines/index.d.cts +30 -0
- package/dist/lib/asciidoc/templates/engines/index.d.ts +30 -0
- package/dist/lib/asciidoc/templates/engines/index.js +30 -0
- package/dist/lib/shiki/transformers/index.cjs +127 -0
- package/dist/lib/shiki/transformers/index.d.cts +59 -0
- package/dist/lib/shiki/transformers/index.d.ts +59 -0
- package/dist/lib/shiki/transformers/index.js +8 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright © 2025 D10f
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# Astro + Asciidoc combined
|
|
2
|
+
|
|
3
|
+
This package will allow you to load Asciidoc files (with either an `.asciidoc` or `.adoc` extension) into an Astro Collection. It leverages [Asciidoctor.js](https://docs.asciidoctor.org/asciidoctor.js/latest) to do the heavy lifting, with a few but substantial configuration options added to make it more versatile.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- [x] Out of the box syntax highlighting, powered by [Shiki](https://shiki.style).
|
|
8
|
+
- [x] Support for custom templating engines.
|
|
9
|
+
- [x] Support for custom converters for maximum control over the output HTML.
|
|
10
|
+
- [x] Full TypeScript support.
|
|
11
|
+
|
|
12
|
+
## Roadmap
|
|
13
|
+
|
|
14
|
+
- [ ] Async support on custom template and converter class render methods.
|
|
15
|
+
- [ ] Include support for more template engines out of the box.
|
|
16
|
+
- [ ] Restructure configuration options regarding Shiki transformer integration.
|
|
17
|
+
|
|
18
|
+
## Getting Started
|
|
19
|
+
|
|
20
|
+
1. Install the loader from `npm`:
|
|
21
|
+
|
|
22
|
+
```console
|
|
23
|
+
npm install @d10f/asciidoc-astro-loader
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
1. Import and run the loader function inside your `content.config.ts` file to define a new collection:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { defineCollection, z } from "astro:content";
|
|
30
|
+
import { asciidocLoader } from "asciidoc-astro-loader";
|
|
31
|
+
|
|
32
|
+
const blog = defineCollection({
|
|
33
|
+
loader: asciidocLoader({
|
|
34
|
+
base: ".src/content/blog",
|
|
35
|
+
}),
|
|
36
|
+
schema: z.object({
|
|
37
|
+
title: z.string(),
|
|
38
|
+
preamble: z.string().optional(),
|
|
39
|
+
createdAt: z.coerce.date(),
|
|
40
|
+
updatedAt: z.coerce.date().optional(),
|
|
41
|
+
}),
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
### Syntax Highlighting
|
|
49
|
+
|
|
50
|
+
Syntax highlighting is already taken care of out of the box, but you can provide additional options to tweak things to your liking. For example, the default theme is Catppuccin, but you might want to use something else:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
const blog = defineCollection({
|
|
54
|
+
loader: asciidocLoader({
|
|
55
|
+
base: ".src/content/blog",
|
|
56
|
+
syntaxHighlighting: {
|
|
57
|
+
theme: 'one-dark-pro',
|
|
58
|
+
}
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
You can specify an object for different light and dark themes, as well:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const blog = defineCollection({
|
|
67
|
+
loader: asciidocLoader({
|
|
68
|
+
base: ".src/content/blog",
|
|
69
|
+
syntaxHighlighting: {
|
|
70
|
+
theme: {
|
|
71
|
+
light: "everforest-light",
|
|
72
|
+
dark: "everforest-dark",
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
And even provide additional themes. Note that you will have to take care of implementing the logic to switch to that theme. Checkout [Shiki's documentation](https://shiki.style/guide/dual-themes#multiple-themes) to learn more.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const blog = defineCollection({
|
|
83
|
+
loader: asciidocLoader({
|
|
84
|
+
base: ".src/content/blog",
|
|
85
|
+
syntaxHighlighting: {
|
|
86
|
+
theme: {
|
|
87
|
+
light: "gruvbox-light-hard",
|
|
88
|
+
dark: "gruvbox-dark-hard",
|
|
89
|
+
dim: "gruvbox-dark-medium"
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Shiki Trasnformers
|
|
97
|
+
|
|
98
|
+
One of the coolest features from Shiki are [transformers](https://shiki.style/guide/transformers). You can provide a list of the transformers that you want to use at the loader configuration:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import {
|
|
102
|
+
transformerNotationDiff,
|
|
103
|
+
transformerNotationFocus,
|
|
104
|
+
transformerNotationHighlight,
|
|
105
|
+
} from '@shikijs/transformers';
|
|
106
|
+
|
|
107
|
+
const blog = defineCollection({
|
|
108
|
+
loader: asciidocLoader({
|
|
109
|
+
base: ".src/content/blog",
|
|
110
|
+
syntaxHighlighting: {
|
|
111
|
+
transformers: [
|
|
112
|
+
transformerNotationDiff(),
|
|
113
|
+
transformerNotationHighlight(),
|
|
114
|
+
transformerNotationFocus(),
|
|
115
|
+
],
|
|
116
|
+
}
|
|
117
|
+
}),
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
These transformers from the example above are already used by default, simply because I use them frequently, so this example is redundant and only for illustration purposes. Providing an array here will overwrite these defaults, however.
|
|
122
|
+
|
|
123
|
+
## Custom Templates
|
|
124
|
+
|
|
125
|
+
A nice feature of Asciidoctor.js is the use of default templates for rendering the different nodes, which can be overwritten by simply providing your own. With `asciidoc-astro-loader` you can easily provide a directory that contains your custom templates:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const blog = defineCollection({
|
|
129
|
+
loader: asciidocLoader({
|
|
130
|
+
base: ".src/content/blog",
|
|
131
|
+
document: {
|
|
132
|
+
template: "./usr/share/templates"
|
|
133
|
+
}
|
|
134
|
+
}),
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Just like regular Asciidoctor.js, any files located here will be used to replace content for nodes whose context matches the name of the template. That is, for a paragraph node, you would have a file named "paragraph.hbs", for example. This would use the Handlebars templating engine.
|
|
139
|
+
|
|
140
|
+
### Registering A Templating Engine
|
|
141
|
+
|
|
142
|
+
By default, only Handlebars and Nunjucks are available for processing templates, but you can create your own as well to leverage whatever other template engine you like. A template engine in the context of this package a class that extends the `AbstractEngine` class.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { Php, Request } from '@platformatic/php-node';
|
|
146
|
+
import { AbstractEngine } from '@d10f/asciidoc-astro-loader/engines';
|
|
147
|
+
import type { AsciidocTemplate, FilesystemTemplate } from '@d10f/asciidoc-astro-loader';
|
|
148
|
+
|
|
149
|
+
export class PhpEngine extends AbstractEngine implements AsciidocTemplate, FilesystemTemplate {
|
|
150
|
+
private server: Php;
|
|
151
|
+
|
|
152
|
+
constructor({ name = 'php', extensions = ['php'], root: string }) {
|
|
153
|
+
super(name, extensions);
|
|
154
|
+
this.server = new Php({ docroot: root });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* This method is enforced by the AsciidocTemplate interface.
|
|
159
|
+
*/
|
|
160
|
+
renderNode(node: AbstractBlock, options?: Record<string, unknown>) {}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* This method is enforced by the FilesystemTemplate interface.
|
|
164
|
+
*/
|
|
165
|
+
renderFile(filepath: string, options?: Record<string, unknown>) {}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Implementing these template interfaces is completely optional, but they help keeping things organized, predictable, easy to test, etc. The `AsciidocTemplate` interface is particularly important, however, as it enforces the implementation of the `renderNode` method, which will be invoked automatically whenever a template file exists with a file extension supported by this engine.
|
|
170
|
+
|
|
171
|
+
> [!WARNING]
|
|
172
|
+
> (WIP): The render methods must not return a Promise.
|
|
173
|
+
|
|
174
|
+
When your engine is ready, you can import it and pass it to the loader configuration object:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { PhpEngine } from './usr/share/engines';
|
|
178
|
+
|
|
179
|
+
const blog = defineCollection({
|
|
180
|
+
loader: asciidocLoader({
|
|
181
|
+
base: ".src/content/blog",
|
|
182
|
+
document: {
|
|
183
|
+
template: "./usr/share/templates",
|
|
184
|
+
templateEngines: [
|
|
185
|
+
New PhpEngine({ name: 'php', extensions: ['php'], root: './usr/share/templates' })
|
|
186
|
+
],
|
|
187
|
+
}
|
|
188
|
+
}),
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
For completeness, this is what a basic implementation of the above example might look like:
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
import { Php, Request } from '@platformatic/php-node';
|
|
196
|
+
import { AbstractEngine } from '@d10f/asciidoc-astro-loader/engines';
|
|
197
|
+
|
|
198
|
+
import type { AbstractBlock } from 'asciidoctor';
|
|
199
|
+
import type {
|
|
200
|
+
AsciidocTemplate,
|
|
201
|
+
FilesystemTemplate,
|
|
202
|
+
NodeContext
|
|
203
|
+
} from '@d10f/asciidoc-astro-loader';
|
|
204
|
+
|
|
205
|
+
export class PhpEngine extends AbstractEngine implements AsciidocTemplate, FilesystemTemplate {
|
|
206
|
+
private server: Php;
|
|
207
|
+
|
|
208
|
+
constructor({ name = 'php', extensions = ['php'], root: string }) {
|
|
209
|
+
super(name, extensions);
|
|
210
|
+
this.server = new Php({ docroot: root });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
renderNode(node: AbstractBlock, options?: Record<string, unknown>) {
|
|
214
|
+
const context = node.getNodeName() as NodeContext;
|
|
215
|
+
const content = node.getContent();
|
|
216
|
+
const templateFile = this.templateList.get(context);
|
|
217
|
+
|
|
218
|
+
if (templateFile) {
|
|
219
|
+
const filepath = templateFile.replace(/^.+\/(.+)$/, '$1');
|
|
220
|
+
return this.renderFile(filepath, { content });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
renderFile(filepath: string, options?: Record<string, unknown>): string {
|
|
225
|
+
const request = new Request({
|
|
226
|
+
method: 'POST',
|
|
227
|
+
url: 'http://localhost/' + filepath,
|
|
228
|
+
body: Buffer.from(JSON.stringify(options)),
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const response = this.server.handleRequestSync(request);
|
|
232
|
+
return response.body.toString();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
We can now have a template file such as "paragraph.php" that will be processed by this engine. Leveraging Platformatic's module, we can write template files in native PHP via WebAssembly!
|
|
238
|
+
|
|
239
|
+
```php
|
|
240
|
+
<?php
|
|
241
|
+
$body = json_decode(file_get_contents('php://input'));
|
|
242
|
+
?>
|
|
243
|
+
|
|
244
|
+
<p><?= strtoupper($body->content) ?></p>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Custom Converters
|
|
248
|
+
|
|
249
|
+
Custom converters are refined versions of templates. The main difference is that they can be configured further, which gives more granular control over the conversion of Asciidoc blocks. An example of custom converters in action comes from the syntax highlighting that's built into `asciidoc-astro-loader`, which processes the node through Shiki.
|
|
250
|
+
|
|
251
|
+
For maximum flexibility, both custom converters and templates can be used at the same time. You can even define templates that aren't designed to be used to render nodes directly, but called directly from within your custom converters. This is why the use of interfaces when defining custom templates is important, to ensure consistency and type-safety.
|
|
252
|
+
|
|
253
|
+
In general, prefer using converters for anything that requires heavy use of logic, and templates for simpler HTML output.
|
|
254
|
+
|
|
255
|
+
### Registering a custom converter
|
|
256
|
+
|
|
257
|
+
A custom converter is declared as a factory function that returns another function. This allows you to provide whatever configuration options you want when registering this converter in the loader configuration.
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
import type { CustomConverterFactoryFn, NodeContext } from '@d10f/asciidoc-astro-loader';
|
|
261
|
+
|
|
262
|
+
export const customConverter: CustomConverterFactoryFn = ({ nodeContext }) => {
|
|
263
|
+
return (options, highlighter) => {
|
|
264
|
+
return {
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* The type of node that this converter will act upon. It
|
|
268
|
+
* can either be hard-coded here, or passed as an option.
|
|
269
|
+
*/
|
|
270
|
+
nodeContext,
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* The convert function that will produce the HTML output.
|
|
274
|
+
* It receives the node that it will convert, and an
|
|
275
|
+
* instance of the template engine registry, which you can
|
|
276
|
+
* use to access any and all available templat engines to
|
|
277
|
+
* customize the output even further.
|
|
278
|
+
*/
|
|
279
|
+
convert(node, templateEngine) {
|
|
280
|
+
return '<p>Result!</p>';
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// src/lib/asciidoc/templates/engines/Base.ts
|
|
2
|
+
var AbstractEngine = class {
|
|
3
|
+
constructor(_name, _extensions) {
|
|
4
|
+
this._name = _name;
|
|
5
|
+
this._extensions = _extensions;
|
|
6
|
+
this.templateList = /* @__PURE__ */ new Map();
|
|
7
|
+
}
|
|
8
|
+
templateList;
|
|
9
|
+
/**
|
|
10
|
+
* Returns the given name to this template engine.
|
|
11
|
+
*/
|
|
12
|
+
get name() {
|
|
13
|
+
return this._name;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Returns the list of extensions this template engine supports.
|
|
17
|
+
*/
|
|
18
|
+
get extensions() {
|
|
19
|
+
return this._extensions;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Accessor method to check if instance implements the
|
|
23
|
+
* TemplateModule interface.
|
|
24
|
+
*/
|
|
25
|
+
get canLoad() {
|
|
26
|
+
return "load" in this && typeof this.load === "function";
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Accessor method to check if instance implements the
|
|
30
|
+
* AsciidocTemplate interface.
|
|
31
|
+
*/
|
|
32
|
+
get canRenderNode() {
|
|
33
|
+
return "renderNode" in this && typeof this.renderNode === "function";
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Accessor method to check if instance implements the
|
|
37
|
+
* FilesystemTemplate interface.
|
|
38
|
+
*/
|
|
39
|
+
get canRenderFile() {
|
|
40
|
+
return "renderFile" in this && typeof this.renderFile === "function";
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Accessor method to check if instance implements the
|
|
44
|
+
* RawTemplate interface.
|
|
45
|
+
*/
|
|
46
|
+
get canRenderString() {
|
|
47
|
+
return "renderString" in this && typeof this.renderString === "function";
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Appends a new context that this template engine will act upon.
|
|
51
|
+
*/
|
|
52
|
+
addContext(context, filepath) {
|
|
53
|
+
this.templateList.set(context, filepath);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Verifies whether the specified context is being tracked or not.
|
|
57
|
+
*/
|
|
58
|
+
hasContext(context) {
|
|
59
|
+
return typeof context === "string" ? this.templateList.has(context) : this.templateList.has(context.getNodeName());
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Verifies if the specified file extension is supported
|
|
63
|
+
* by this template engine.
|
|
64
|
+
*/
|
|
65
|
+
supportsExt(extension) {
|
|
66
|
+
return this.extensions.includes(extension);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// src/lib/asciidoc/templates/engines/Handlebars.ts
|
|
71
|
+
import { readFileSync } from "fs";
|
|
72
|
+
var HandlebarsEngine = class extends AbstractEngine {
|
|
73
|
+
render;
|
|
74
|
+
constructor(name = "handlebars", extensions = ["handlebars", "hbs"]) {
|
|
75
|
+
super(name, extensions);
|
|
76
|
+
this.render = null;
|
|
77
|
+
}
|
|
78
|
+
async load() {
|
|
79
|
+
const Handlebars = await import("handlebars");
|
|
80
|
+
this.render = (input, opts) => {
|
|
81
|
+
return Handlebars.default.compile(input)(opts);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
renderNode(node, options = {}) {
|
|
85
|
+
const context = node.getNodeName();
|
|
86
|
+
return this.renderFile(this.templateList.get(context), options);
|
|
87
|
+
}
|
|
88
|
+
renderFile(filepath, options = {}) {
|
|
89
|
+
const fileContents = readFileSync(filepath, { encoding: "utf8" });
|
|
90
|
+
return this.renderString(fileContents, options);
|
|
91
|
+
}
|
|
92
|
+
renderString(str, options = {}) {
|
|
93
|
+
if (this.render === null) {
|
|
94
|
+
throw new Error("This template doesn't have a render method!");
|
|
95
|
+
}
|
|
96
|
+
return this.render(str, options);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// src/lib/asciidoc/templates/engines/Nunjucks.ts
|
|
101
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
102
|
+
var NunjucksEngine = class extends AbstractEngine {
|
|
103
|
+
render;
|
|
104
|
+
constructor(name = "nunjucks", extensions = ["nunjucks", "njk"]) {
|
|
105
|
+
super(name, extensions);
|
|
106
|
+
this.render = null;
|
|
107
|
+
}
|
|
108
|
+
async load() {
|
|
109
|
+
const nunjucks = await import("nunjucks");
|
|
110
|
+
this.render = nunjucks.default.renderString;
|
|
111
|
+
}
|
|
112
|
+
renderNode(node, options = {}) {
|
|
113
|
+
const context = node.getNodeName();
|
|
114
|
+
return this.renderFile(this.templateList.get(context), options);
|
|
115
|
+
}
|
|
116
|
+
renderFile(filepath, options = {}) {
|
|
117
|
+
const fileContents = readFileSync2(filepath, { encoding: "utf8" });
|
|
118
|
+
return this.renderString(fileContents, options);
|
|
119
|
+
}
|
|
120
|
+
renderString(str, options = {}) {
|
|
121
|
+
if (this.render === null) {
|
|
122
|
+
throw new Error("This template doesn't have a render method!");
|
|
123
|
+
}
|
|
124
|
+
return this.render(str, options);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export {
|
|
129
|
+
AbstractEngine,
|
|
130
|
+
HandlebarsEngine,
|
|
131
|
+
NunjucksEngine
|
|
132
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// src/lib/utils.ts
|
|
2
|
+
function slugify(text) {
|
|
3
|
+
return text.trim().normalize().toLowerCase().replace(/\s+/g, "-").replace(/[^\w-]+/g, "").replace(/--+/g, "-").replace(/^-+/, "").replace(/-+$/, "");
|
|
4
|
+
}
|
|
5
|
+
function escapeRegexCharacters(str) {
|
|
6
|
+
const re = /[-\\^$*+?.()|\[\]{}]/g;
|
|
7
|
+
return str.replace(re, "\\$&");
|
|
8
|
+
}
|
|
9
|
+
function splitFilenameComponents(filename) {
|
|
10
|
+
const match = filename.match(/^(?<path>.*\/)*(?<name>[^\.]+)\.(?<ext>.*)$/);
|
|
11
|
+
return {
|
|
12
|
+
filepath: match?.groups?.path ?? null,
|
|
13
|
+
filename: match?.groups?.name ?? null,
|
|
14
|
+
extension: match?.groups?.ext ?? null
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/lib/shiki/transformers/transformAsciidocCallout.ts
|
|
19
|
+
function transformAsciidocCallout({
|
|
20
|
+
node,
|
|
21
|
+
cssClasses = "pointer-events-none select-none ml-2"
|
|
22
|
+
}) {
|
|
23
|
+
const lineComments = ["//", "#", ";;"];
|
|
24
|
+
const customLineComment = node.getAttribute("line-comment");
|
|
25
|
+
if (customLineComment) {
|
|
26
|
+
lineComments.push(escapeRegexCharacters(customLineComment));
|
|
27
|
+
}
|
|
28
|
+
const calloutReList = [
|
|
29
|
+
// Handles C-style and similar comments like Perl, Python...
|
|
30
|
+
new RegExp(`(?:${lineComments.join("|")})((?:\\s+<(\\d+)>)+)`),
|
|
31
|
+
// Handles XML comments
|
|
32
|
+
new RegExp(/((?:\s*<!--(\d+)-->)+)/)
|
|
33
|
+
];
|
|
34
|
+
const linesWithCallout = {};
|
|
35
|
+
return {
|
|
36
|
+
preprocess(code) {
|
|
37
|
+
return code.split("\n").map((line, idx) => {
|
|
38
|
+
for (const re of calloutReList) {
|
|
39
|
+
const match = line.match(re);
|
|
40
|
+
if (!match) continue;
|
|
41
|
+
const callouts = match[0].trim().replaceAll(/(?:<!--|-->|[<>])/g, "").split(" ");
|
|
42
|
+
linesWithCallout[idx + 1] = callouts;
|
|
43
|
+
return line.replace(re, "");
|
|
44
|
+
}
|
|
45
|
+
return line;
|
|
46
|
+
}).join("\n");
|
|
47
|
+
},
|
|
48
|
+
line(hast, line) {
|
|
49
|
+
const callouts = linesWithCallout[line];
|
|
50
|
+
if (!callouts) return;
|
|
51
|
+
callouts.forEach((calloutId) => {
|
|
52
|
+
hast.properties[`data-callout-${calloutId}`] = "";
|
|
53
|
+
hast.children.push({
|
|
54
|
+
type: "element",
|
|
55
|
+
tagName: "i",
|
|
56
|
+
properties: {
|
|
57
|
+
class: `conum ${cssClasses}`,
|
|
58
|
+
"data-value": calloutId
|
|
59
|
+
},
|
|
60
|
+
children: [
|
|
61
|
+
{
|
|
62
|
+
type: "element",
|
|
63
|
+
tagName: "b",
|
|
64
|
+
properties: {},
|
|
65
|
+
children: [
|
|
66
|
+
{
|
|
67
|
+
type: "text",
|
|
68
|
+
value: calloutId
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/lib/shiki/transformers/transformConsoleCodeBlock.ts
|
|
80
|
+
function transformConsoleCodeBlock(options = {
|
|
81
|
+
cssClasses: "pointer-events-none select-none mr-2 opacity-50"
|
|
82
|
+
}) {
|
|
83
|
+
const unselectablePrompt = `<span $1 class="${options.cssClasses}">$</span>`;
|
|
84
|
+
const linePrefix = '<span class="line[^>]+?>';
|
|
85
|
+
const splitPrompt = new RegExp(
|
|
86
|
+
`(?<=${linePrefix})(?:<span (style="[^"]*?")>\\s*?\\$\\s+?([^<]))`
|
|
87
|
+
);
|
|
88
|
+
const trimWhitespace = new RegExp(
|
|
89
|
+
`(?<=${linePrefix})(?:<span (style="[^"]*?")>\\s*?\\$\\s*?<\\/span>(?:<span>\\s+<\\/span>)?)`
|
|
90
|
+
);
|
|
91
|
+
const trimWhitespaceAhead = new RegExp(
|
|
92
|
+
`(?<=${linePrefix}<span [^>]+?>\\$<\\/span>)(<span style="[^"]+?">)\\s+?`
|
|
93
|
+
);
|
|
94
|
+
return {
|
|
95
|
+
postprocess: (html, { lang }) => {
|
|
96
|
+
if (lang === "console") {
|
|
97
|
+
return html.split("\n").map((line) => {
|
|
98
|
+
return line.replace(
|
|
99
|
+
splitPrompt,
|
|
100
|
+
unselectablePrompt + "<span $1>$2"
|
|
101
|
+
).replace(trimWhitespace, unselectablePrompt).replace(trimWhitespaceAhead, "$1");
|
|
102
|
+
}).join("\n");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
slugify,
|
|
110
|
+
splitFilenameComponents,
|
|
111
|
+
transformAsciidocCallout,
|
|
112
|
+
transformConsoleCodeBlock
|
|
113
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
splitFilenameComponents,
|
|
3
|
+
transformAsciidocCallout,
|
|
4
|
+
transformConsoleCodeBlock
|
|
5
|
+
} from "./chunk-DDIUST2Z.js";
|
|
6
|
+
|
|
7
|
+
// src/lib/asciidoc/converters/sourceCodeConverter.ts
|
|
8
|
+
import {
|
|
9
|
+
transformerNotationDiff,
|
|
10
|
+
transformerNotationFocus,
|
|
11
|
+
transformerNotationHighlight
|
|
12
|
+
} from "@shikijs/transformers";
|
|
13
|
+
import { resolve } from "path";
|
|
14
|
+
var sourceCodeConverter = ({ nodeContext, transformers, template }) => {
|
|
15
|
+
return (options, highlighter) => {
|
|
16
|
+
return {
|
|
17
|
+
nodeContext: nodeContext ?? "listing",
|
|
18
|
+
convert(node, templateEngine) {
|
|
19
|
+
const input = node.getSourceLines().join("\n");
|
|
20
|
+
const lang = node.getAttribute("language");
|
|
21
|
+
const output = highlighter.codeToHtml(input, {
|
|
22
|
+
...options.syntaxHighlighting,
|
|
23
|
+
lang,
|
|
24
|
+
transformers: [
|
|
25
|
+
...transformers ?? [],
|
|
26
|
+
transformerNotationDiff(),
|
|
27
|
+
transformerNotationHighlight(),
|
|
28
|
+
transformerNotationFocus(),
|
|
29
|
+
transformAsciidocCallout({ node }),
|
|
30
|
+
transformConsoleCodeBlock()
|
|
31
|
+
]
|
|
32
|
+
});
|
|
33
|
+
if (templateEngine && template) {
|
|
34
|
+
const { extension } = splitFilenameComponents(template);
|
|
35
|
+
const engine = templateEngine.getEngineByExtension(
|
|
36
|
+
extension || ""
|
|
37
|
+
);
|
|
38
|
+
if (engine) {
|
|
39
|
+
const templateFile = resolve(process.cwd(), template);
|
|
40
|
+
if (engine.canRenderFile) {
|
|
41
|
+
return engine.renderFile(templateFile, {
|
|
42
|
+
content: output,
|
|
43
|
+
lang
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return output;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
sourceCodeConverter
|
|
56
|
+
};
|