@d10f/asciidoc-astro-loader 0.0.1 → 0.0.3

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
 
@@ -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,34 +1,23 @@
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
- import {
9
- transformerNotationDiff,
10
- transformerNotationFocus,
11
- transformerNotationHighlight
12
- } from "@shikijs/transformers";
13
6
  import { resolve } from "path";
14
- var sourceCodeConverter = ({ nodeContext, transformers, template }) => {
7
+ var sourceCodeConverter = ({ transformers, template }) => {
15
8
  return (options, highlighter) => {
16
9
  return {
17
- nodeContext: nodeContext ?? "listing",
10
+ nodeContext: "listing",
11
+ nodeStyle: "source",
18
12
  convert(node, templateEngine) {
19
13
  const input = node.getSourceLines().join("\n");
20
14
  const lang = node.getAttribute("language");
21
15
  const output = highlighter.codeToHtml(input, {
22
16
  ...options.syntaxHighlighting,
23
17
  lang,
24
- transformers: [
25
- ...transformers ?? [],
26
- transformerNotationDiff(),
27
- transformerNotationHighlight(),
28
- transformerNotationFocus(),
29
- transformAsciidocCallout({ node }),
30
- transformConsoleCodeBlock()
31
- ]
18
+ transformers: (transformers ?? []).map((transformer) => {
19
+ return typeof transformer === "function" ? transformer(node) : transformer;
20
+ })
32
21
  });
33
22
  if (templateEngine && template) {
34
23
  const { extension } = splitFilenameComponents(template);