@d10f/asciidoc-astro-loader 0.0.2 → 0.0.4

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/README.md CHANGED
@@ -8,39 +8,39 @@ This package will allow you to load Asciidoc files (with either an `.asciidoc` o
8
8
  - [x] Support for custom templating engines.
9
9
  - [x] Support for custom converters for maximum control over the output HTML.
10
10
  - [x] Full TypeScript support.
11
+ - [x] Restructure configuration options regarding Shiki transformer integration.
11
12
 
12
13
  ## Roadmap
13
14
 
14
15
  - [ ] Async support on custom template and converter class render methods.
15
16
  - [ ] Include support for more template engines out of the box.
16
- - [ ] Restructure configuration options regarding Shiki transformer integration.
17
17
 
18
18
  ## Getting Started
19
19
 
20
- 1. Install the loader from `npm`:
20
+ Install the package from `npm`:
21
21
 
22
22
  ```console
23
23
  npm install @d10f/asciidoc-astro-loader
24
24
  ```
25
25
 
26
- 1. Import and run the loader function inside your `content.config.ts` file to define a new collection:
26
+ And import the loader function inside your `content.config.ts` file to define a new collection:
27
27
 
28
- ```ts
29
- import { defineCollection, z } from "astro:content";
30
- import { asciidocLoader } from "asciidoc-astro-loader";
28
+ ```ts
29
+ import { defineCollection, z } from "astro:content";
30
+ import { asciidocLoader } from "asciidoc-astro-loader";
31
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
- ```
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
44
 
45
45
 
46
46
  ## Configuration
@@ -64,15 +64,15 @@ You can specify an object for different light and dark themes, as well:
64
64
 
65
65
  ```ts
66
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
- }),
67
+ loader: asciidocLoader({
68
+ base: ".src/content/blog",
69
+ syntaxHighlighting: {
70
+ theme: {
71
+ light: "everforest-light",
72
+ dark: "everforest-dark",
73
+ },
74
+ }
75
+ }),
76
76
  });
77
77
  ```
78
78
 
@@ -80,16 +80,16 @@ And even provide additional themes. Note that you will have to take care of impl
80
80
 
81
81
  ```ts
82
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
- }),
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
93
  });
94
94
  ```
95
95
 
@@ -99,26 +99,66 @@ One of the coolest features from Shiki are [transformers](https://shiki.style/gu
99
99
 
100
100
  ```ts
101
101
  import {
102
- transformerNotationDiff,
103
- transformerNotationFocus,
104
- transformerNotationHighlight,
102
+ transformerNotationDiff,
103
+ transformerNotationFocus,
104
+ transformerNotationHighlight,
105
105
  } from '@shikijs/transformers';
106
106
 
107
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
- }),
108
+ loader: asciidocLoader({
109
+ base: ".src/content/blog",
110
+ syntaxHighlighting: {
111
+ transformers: [
112
+ transformerNotationDiff(),
113
+ transformerNotationHighlight(),
114
+ transformerNotationFocus(),
115
+ ],
116
+ }
117
+ }),
118
118
  });
119
119
  ```
120
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.
121
+ If you want to write your own transformers, you can just follow Shiki's documentation and provide them here. However, you might also be interested in performing some conditional logic based on the type of Asciidoc node you're working with. To gain access to the node, define the transformer as a factory function using the `ShikiTransformerFactory` type:
122
+
123
+ ```typescript
124
+ import type { ShikiTransformerFactory } from '../../../types/index.js';
125
+
126
+ type TranformerOptions = {
127
+ cssClasses: string;
128
+ };
129
+
130
+ export const transformerAsciidocSomething: ShikiTransformerFactory<
131
+ TransformerOptions
132
+ > = ({ cssClasses }) => {
133
+ return (node) => {
134
+ return {
135
+ preprocess() {
136
+ if (node.getAttribute('custom-attribute')) {
137
+ // Maybe you only want to do something based on
138
+ // some custom attribute?
139
+ }
140
+ }
141
+ }
142
+ }
143
+ }
144
+ ```
145
+
146
+ ```ts
147
+ import { transformerAsciidocSomething } from './usr/share/transformers';
148
+
149
+ const blog = defineCollection({
150
+ loader: asciidocLoader({
151
+ base: ".src/content/blog",
152
+ syntaxHighlighting: {
153
+ transformers: [
154
+ transformerAsciidocSomethign({
155
+ cssClasses: 'text-red-500'
156
+ }),
157
+ ],
158
+ }
159
+ }),
160
+ });
161
+ ```
122
162
 
123
163
  ## Custom Templates
124
164
 
@@ -126,12 +166,12 @@ A nice feature of Asciidoctor.js is the use of default templates for rendering t
126
166
 
127
167
  ```ts
128
168
  const blog = defineCollection({
129
- loader: asciidocLoader({
130
- base: ".src/content/blog",
131
- document: {
132
- template: "./usr/share/templates"
133
- }
134
- }),
169
+ loader: asciidocLoader({
170
+ base: ".src/content/blog",
171
+ document: {
172
+ template: "./usr/share/templates"
173
+ }
174
+ }),
135
175
  });
136
176
  ```
137
177
 
@@ -162,7 +202,7 @@ export class PhpEngine extends AbstractEngine implements AsciidocTemplate, Files
162
202
  /**
163
203
  * This method is enforced by the FilesystemTemplate interface.
164
204
  */
165
- renderFile(filepath: string, options?: Record<string, unknown>) {}
205
+ renderFile(filepath: string, options?: Record<string, unknown>) {}
166
206
  }
167
207
  ```
168
208
 
@@ -177,15 +217,15 @@ When your engine is ready, you can import it and pass it to the loader configura
177
217
  import { PhpEngine } from './usr/share/engines';
178
218
 
179
219
  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
- }),
220
+ loader: asciidocLoader({
221
+ base: ".src/content/blog",
222
+ document: {
223
+ template: "./usr/share/templates",
224
+ templateEngines: [
225
+ New PhpEngine({ name: 'php', extensions: ['php'], root: './usr/share/templates' })
226
+ ],
227
+ }
228
+ }),
189
229
  });
190
230
  ```
191
231
 
@@ -197,40 +237,40 @@ import { AbstractEngine } from '@d10f/asciidoc-astro-loader/engines';
197
237
 
198
238
  import type { AbstractBlock } from 'asciidoctor';
199
239
  import type {
200
- AsciidocTemplate,
201
- FilesystemTemplate,
202
- NodeContext
240
+ AsciidocTemplate,
241
+ FilesystemTemplate,
242
+ NodeContext
203
243
  } from '@d10f/asciidoc-astro-loader';
204
244
 
205
245
  export class PhpEngine extends AbstractEngine implements AsciidocTemplate, FilesystemTemplate {
206
- private server: Php;
246
+ private server: Php;
207
247
 
208
248
  constructor({ name = 'php', extensions = ['php'], root: string }) {
209
249
  super(name, extensions);
210
250
  this.server = new Php({ docroot: root });
211
251
  }
212
252
 
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
- }
253
+ renderNode(node: AbstractBlock, options?: Record<string, unknown>) {
254
+ const context = node.getNodeName() as NodeContext;
255
+ const content = node.getContent();
256
+ const templateFile = this.templateList.get(context);
257
+
258
+ if (templateFile) {
259
+ const filepath = templateFile.replace(/^.+\/(.+)$/, '$1');
260
+ return this.renderFile(filepath, { content });
261
+ }
262
+ }
263
+
264
+ renderFile(filepath: string, options?: Record<string, unknown>): string {
265
+ const request = new Request({
266
+ method: 'POST',
267
+ url: 'http://localhost/' + filepath,
268
+ body: Buffer.from(JSON.stringify(options)),
269
+ });
270
+
271
+ const response = this.server.handleRequestSync(request);
272
+ return response.body.toString();
273
+ }
234
274
  }
235
275
  ```
236
276
 
@@ -250,16 +290,33 @@ Custom converters are refined versions of templates. The main difference is that
250
290
 
251
291
  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
292
 
253
- In general, prefer using converters for anything that requires heavy use of logic, and templates for simpler HTML output.
293
+ In general, prefer using converters for anything that requires heavy use of logic or that reads configuration options provided by the user, and templates for simpler HTML output.
294
+
295
+ Custom converters are provided as an array to the `document.converters` option.
296
+
297
+ ```ts
298
+ // src/content.config.ts
299
+
300
+ const blog = defineCollection({
301
+ loader: asciidocLoader({
302
+ base: ".src/content/blog",
303
+ document: {
304
+ converters: [
305
+ myCustomConverter({ /* ... */ })
306
+ ]
307
+ },
308
+ }),
309
+ });
310
+ ```
254
311
 
255
312
  ### Registering a custom converter
256
313
 
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.
314
+ A custom converter is declared as a factory function that accepts a configuration object, and returns an inner function that gets called automatically, receiving the options provided to the loader and the instance of the Shiki highlighter.
258
315
 
259
316
  ```ts
260
317
  import type { CustomConverterFactoryFn, NodeContext } from '@d10f/asciidoc-astro-loader';
261
318
 
262
- export const customConverter: CustomConverterFactoryFn = ({ nodeContext }) => {
319
+ export const myCustomConverter: CustomConverterFactoryFn = ({ nodeContext }) => {
263
320
  return (options, highlighter) => {
264
321
  return {
265
322
 
@@ -284,3 +341,4 @@ export const customConverter: CustomConverterFactoryFn = ({ nodeContext }) => {
284
341
  }
285
342
  ```
286
343
 
344
+ In addition, the `convert` method receives the node that is being processed, as well as an instance of the template engine registry. You can use this to render the HTML from a template as well, combining both to get the best of both worlds.
@@ -0,0 +1,22 @@
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
+ export {
19
+ slugify,
20
+ escapeRegexCharacters,
21
+ splitFilenameComponents
22
+ };
@@ -0,0 +1,65 @@
1
+ import {
2
+ escapeRegexCharacters
3
+ } from "./chunk-2UGTFP4R.js";
4
+
5
+ // src/lib/shiki/transformers/transformAsciidocCallout.ts
6
+ var transformAsciidocCallout = (options) => {
7
+ return (node) => {
8
+ const lineComments = ["//", "#", ";;"];
9
+ const customLineComment = node.getAttribute("line-comment");
10
+ if (customLineComment) {
11
+ lineComments.push(escapeRegexCharacters(customLineComment));
12
+ }
13
+ const calloutReList = [
14
+ // Handles C-style and similar comments like Perl, Python...
15
+ new RegExp(`\\s+(?:${lineComments.join("|")})((?:\\s+<(\\d+)>)+)`),
16
+ // Handles XML comments
17
+ new RegExp(/((?:\s*<!--(\d+)-->)+)/)
18
+ ];
19
+ const commentTokensRe = new RegExp(
20
+ `(?:${lineComments.join("|")}|<!--|-->|[<>])`,
21
+ "g"
22
+ );
23
+ const linesWithCallout = {};
24
+ return {
25
+ preprocess(code) {
26
+ return code.split("\n").map((line, idx) => {
27
+ for (const re of calloutReList) {
28
+ const match = line.match(re);
29
+ if (!match) continue;
30
+ const callouts = match[0].replaceAll(commentTokensRe, "").trim().split(" ");
31
+ linesWithCallout[idx + 1] = callouts;
32
+ return line.replace(re, "");
33
+ }
34
+ return line;
35
+ }).join("\n");
36
+ },
37
+ line(hast, line) {
38
+ const callouts = linesWithCallout[line];
39
+ if (!callouts) return;
40
+ callouts.forEach((calloutId) => {
41
+ hast.properties[`data-callout-${calloutId}`] = "";
42
+ hast.children.push({
43
+ type: "element",
44
+ tagName: "span",
45
+ properties: {
46
+ class: options?.cssClasses ?? "conum",
47
+ style: options?.cssClasses ? "" : "user-select:none; pointer-events:none; opacity:0.5; margin-inline:8px;",
48
+ "data-value": calloutId
49
+ },
50
+ children: [
51
+ {
52
+ type: "text",
53
+ value: calloutId
54
+ }
55
+ ]
56
+ });
57
+ });
58
+ }
59
+ };
60
+ };
61
+ };
62
+
63
+ export {
64
+ transformAsciidocCallout
65
+ };
@@ -1,12 +1,12 @@
1
1
  import {
2
- splitFilenameComponents,
3
- transformAsciidocCallout,
4
- transformConsoleCodeBlock
5
- } from "./chunk-DDIUST2Z.js";
2
+ splitFilenameComponents
3
+ } from "./chunk-2UGTFP4R.js";
6
4
 
7
5
  // src/lib/asciidoc/converters/sourceCodeConverter.ts
8
6
  import { resolve } from "path";
9
- var sourceCodeConverter = ({ transformers, template }) => {
7
+ var sourceCodeConverter = (converterOptions) => {
8
+ const transformers = converterOptions?.transformers;
9
+ const template = converterOptions?.template;
10
10
  return (options, highlighter) => {
11
11
  return {
12
12
  nodeContext: "listing",
@@ -17,11 +17,9 @@ var sourceCodeConverter = ({ transformers, template }) => {
17
17
  const output = highlighter.codeToHtml(input, {
18
18
  ...options.syntaxHighlighting,
19
19
  lang,
20
- transformers: [
21
- ...transformers ?? [],
22
- transformAsciidocCallout({ node }),
23
- transformConsoleCodeBlock()
24
- ]
20
+ transformers: (transformers ?? []).map((transformer) => {
21
+ return typeof transformer === "function" ? transformer(node) : transformer;
22
+ })
25
23
  });
26
24
  if (templateEngine && template) {
27
25
  const { extension } = splitFilenameComponents(template);