@asciidoctor/core 3.0.4 → 4.0.0-alpha.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.
Files changed (91) hide show
  1. package/README.md +42 -10
  2. package/build/browser/index.js +24154 -0
  3. package/build/node/index.cjs +24735 -0
  4. package/{dist/css/asciidoctor.css → data/asciidoctor-default.css} +54 -53
  5. package/package.json +53 -100
  6. package/src/abstract_block.js +849 -0
  7. package/src/abstract_node.js +954 -0
  8. package/src/attribute_entry.js +12 -0
  9. package/src/attribute_list.js +380 -0
  10. package/src/block.js +168 -0
  11. package/src/browser/asset.js +22 -0
  12. package/src/browser/reader.js +138 -0
  13. package/src/browser.js +121 -0
  14. package/src/callouts.js +85 -0
  15. package/src/compliance.js +54 -0
  16. package/src/constants.js +665 -0
  17. package/src/convert.js +370 -0
  18. package/src/converter/composite.js +83 -0
  19. package/src/converter/docbook5.js +1031 -0
  20. package/src/converter/html5.js +1899 -0
  21. package/src/converter/manpage.js +935 -0
  22. package/src/converter/template.js +459 -0
  23. package/src/converter.js +478 -0
  24. package/src/data/stylesheet-data.js +2 -0
  25. package/src/document.js +2134 -0
  26. package/src/extensions.js +1952 -0
  27. package/src/footnote.js +28 -0
  28. package/src/helpers.js +355 -0
  29. package/src/index.js +138 -0
  30. package/src/inline.js +158 -0
  31. package/src/list.js +240 -0
  32. package/src/load.js +276 -0
  33. package/src/logging.js +526 -0
  34. package/src/parser.js +3661 -0
  35. package/src/path_resolver.js +472 -0
  36. package/src/reader.js +1755 -0
  37. package/src/rx.js +829 -0
  38. package/src/section.js +354 -0
  39. package/src/stylesheets.js +30 -0
  40. package/src/substitutors.js +2241 -0
  41. package/src/syntaxHighlighter/highlightjs.js +90 -0
  42. package/src/syntaxHighlighter/html_pipeline.js +33 -0
  43. package/src/syntax_highlighter.js +304 -0
  44. package/src/table.js +952 -0
  45. package/src/timings.js +78 -0
  46. package/types/abstract_block.d.ts +343 -0
  47. package/types/abstract_node.d.ts +471 -0
  48. package/types/attribute_entry.d.ts +7 -0
  49. package/types/attribute_list.d.ts +52 -0
  50. package/types/block.d.ts +55 -0
  51. package/types/browser/asset.d.ts +7 -0
  52. package/types/browser/reader.d.ts +29 -0
  53. package/types/callouts.d.ts +36 -0
  54. package/types/compliance.d.ts +23 -0
  55. package/types/constants.d.ts +268 -0
  56. package/types/convert.d.ts +34 -0
  57. package/types/converter/composite.d.ts +20 -0
  58. package/types/converter/docbook5.d.ts +41 -0
  59. package/types/converter/html5.d.ts +51 -0
  60. package/types/converter/manpage.d.ts +59 -0
  61. package/types/converter/template.d.ts +83 -0
  62. package/types/converter.d.ts +150 -0
  63. package/types/data/stylesheet-data.d.ts +2 -0
  64. package/types/document.d.ts +495 -0
  65. package/types/extensions.d.ts +876 -0
  66. package/types/footnote.d.ts +18 -0
  67. package/types/helpers.d.ts +146 -0
  68. package/types/index.d.ts +73 -3731
  69. package/types/inline.d.ts +69 -0
  70. package/types/list.d.ts +114 -0
  71. package/types/load.d.ts +39 -0
  72. package/types/logging.d.ts +187 -0
  73. package/types/parser.d.ts +114 -0
  74. package/types/path_resolver.d.ts +103 -0
  75. package/types/reader.d.ts +184 -0
  76. package/types/rx.d.ts +513 -0
  77. package/types/section.d.ts +122 -0
  78. package/types/stylesheets.d.ts +10 -0
  79. package/types/substitutors.d.ts +208 -0
  80. package/types/syntaxHighlighter/highlightjs.d.ts +33 -0
  81. package/types/syntaxHighlighter/html_pipeline.d.ts +16 -0
  82. package/types/syntax_highlighter.d.ts +167 -0
  83. package/types/table.d.ts +231 -0
  84. package/types/timings.d.ts +25 -0
  85. package/types/tsconfig.json +9 -0
  86. package/LICENSE +0 -21
  87. package/dist/browser/asciidoctor.js +0 -47654
  88. package/dist/browser/asciidoctor.min.js +0 -1452
  89. package/dist/graalvm/asciidoctor.js +0 -47402
  90. package/dist/node/asciidoctor.cjs +0 -21567
  91. package/dist/node/asciidoctor.js +0 -23037
package/src/convert.js ADDED
@@ -0,0 +1,370 @@
1
+ // ESM conversion of convert.rb
2
+ //
3
+ // Ruby-to-JavaScript notes:
4
+ // - Ruby module methods on Asciidoctor → named exports convert() and convertFile()
5
+ // (deprecated aliases: render, renderFile).
6
+ // - Ruby File === input → duck-type: any object with a .path property is treated as a file.
7
+ // - Ruby File.absolute_path / File.dirname / File.expand_path → node:path.resolve / .dirname.
8
+ // - Ruby Dir.pwd → process.cwd().
9
+ // - Ruby File.directory? → async _isDirectory() helper via node:fs/promises.
10
+ // - Ruby File.file? → async _isFile() helper via node:fs/promises.
11
+ // - Ruby File.write → async writeFile() via node:fs/promises.
12
+ // - Ruby Helpers.mkdir_p → mkdirP() from helpers.js.
13
+ // - Ruby Helpers.uriish? → isUriish() from helpers.js.
14
+ // - Ruby Stylesheets.instance.write_primary_stylesheet → not yet ported to JS; skipped.
15
+ // - Ruby doc.syntax_highlighter → doc.syntaxHighlighter.
16
+ // - Ruby syntax_hl.write_stylesheet? doc → syntaxHl.writeStylesheet(doc).
17
+ // - Ruby syntax_hl.write_stylesheet doc, dir → syntaxHl.writeStylesheetToDisk(doc, dir).
18
+ // - Ruby doc.normalize_system_path → doc.normalizeSystemPath.
19
+ // - Ruby doc.attr? 'x' → doc.hasAttribute('x').
20
+ // - Ruby doc.attr 'x' → doc.getAttribute('x').
21
+ // - Ruby doc.basebackend? 'html' → doc.basebackend('html').
22
+ // - The entire function is async because load() is async.
23
+
24
+ import { load } from './load.js'
25
+ import { isUriish, mkdirP } from './helpers.js'
26
+ import { SafeMode, DEFAULT_STYLESHEET_KEYS } from './constants.js'
27
+
28
+ // ── convert ───────────────────────────────────────────────────────────────────
29
+
30
+ /**
31
+ * Parse the AsciiDoc source input into a Document and convert it to the specified backend format.
32
+ *
33
+ * Accepts input as a Node.js Readable stream (or any object with a read() method), a String,
34
+ * or a String Array. If the input is a file-like object with a `.path` property, it is treated
35
+ * as a file: the output is written to a file adjacent to the input by default.
36
+ *
37
+ * If `to_file` is true or omitted and the input is a file-like object, the output is written
38
+ * next to the input file. If `to_file` is a String path, the output is written there.
39
+ * If `to_file` is false, the converted String is returned.
40
+ * If `to_file` is `'/dev/null'`, the document is loaded but neither converted nor written.
41
+ *
42
+ * @param {string|string[]|object} input - the AsciiDoc source (String, Array, Readable, or
43
+ * file-like object with a `.path` property)
44
+ * @param {object} [options={}] - a plain Object of options (mirrors Ruby API):
45
+ * - `to_file` {string|boolean|object} - String path, Boolean, stream object, or `'/dev/null'`
46
+ * - `to_dir` {string} - output directory
47
+ * - `mkdirs` {boolean} - create missing directories if true
48
+ * - `standalone` {boolean} - include header/footer
49
+ * - `header_footer` {boolean} - deprecated alias for `standalone`
50
+ * - `base_dir` {string} - base directory
51
+ * @returns {Promise<import('./document.js').Document|string>} the Document if output was written to a file, otherwise the converted String
52
+ */
53
+ export async function convert(input, options = {}) {
54
+ options = Object.assign({}, options)
55
+ delete options.parse
56
+ const toDir = options.to_dir
57
+ delete options.to_dir
58
+ const mkdirs = options.mkdirs
59
+ delete options.mkdirs
60
+
61
+ let toFile = options.to_file
62
+ delete options.to_file
63
+ let siblingPath = null
64
+ let writeToTarget = null
65
+ let streamOutput = false
66
+
67
+ if (toFile === true || toFile == null) {
68
+ writeToTarget = toDir || null
69
+ if (!writeToTarget && input && typeof input === 'object' && input.path) {
70
+ const nodePath = await _requirePath()
71
+ siblingPath = nodePath.resolve(input.path)
72
+ }
73
+ toFile = null
74
+ } else if (toFile === false) {
75
+ toFile = null
76
+ } else if (toFile === '/dev/null') {
77
+ return load(input, options)
78
+ } else {
79
+ if (typeof toFile === 'object' && typeof toFile.write === 'function') {
80
+ streamOutput = true
81
+ } else {
82
+ options.to_file = toFile
83
+ writeToTarget = toFile
84
+ }
85
+ }
86
+
87
+ // Normalise :header_footer → :standalone when writing to a target.
88
+ if (!('standalone' in options)) {
89
+ if (siblingPath || writeToTarget) {
90
+ options.standalone =
91
+ 'header_footer' in options ? options.header_footer : true
92
+ } else if ('header_footer' in options) {
93
+ options.standalone = options.header_footer
94
+ }
95
+ }
96
+
97
+ // NOTE outfile may be controlled by document attributes, so resolve outfile after loading.
98
+ // NOTE the :to_dir option is always set when outputting to a file.
99
+ // NOTE the :to_file option is only passed if assigned an explicit path.
100
+ if (siblingPath) {
101
+ const nodePath = await _requirePath()
102
+ options.to_dir = nodePath.dirname(siblingPath)
103
+ } else if (writeToTarget) {
104
+ const nodePath = await _requirePath()
105
+ if (toDir) {
106
+ if (toFile) {
107
+ options.to_dir = nodePath.dirname(nodePath.resolve(toDir, toFile))
108
+ } else {
109
+ options.to_dir = nodePath.resolve(toDir)
110
+ }
111
+ } else if (toFile) {
112
+ options.to_dir = nodePath.dirname(nodePath.resolve(toFile))
113
+ }
114
+ }
115
+
116
+ const doc = await load(input, options)
117
+
118
+ let outfile, outdir
119
+
120
+ if (siblingPath) {
121
+ const nodePath = await _requirePath()
122
+ outdir = nodePath.dirname(siblingPath)
123
+ outfile = nodePath.join(
124
+ outdir,
125
+ `${doc.attributes.docname}${doc.outfilesuffix}`
126
+ )
127
+ if (outfile === siblingPath) {
128
+ throw new Error(
129
+ `input file and output file cannot be the same: ${outfile}`
130
+ )
131
+ }
132
+ } else if (writeToTarget) {
133
+ const nodePath = await _requirePath()
134
+ const workingDir = options.base_dir
135
+ ? nodePath.resolve(options.base_dir)
136
+ : process.cwd()
137
+ // QUESTION should the jail be workingDir or doc.baseDir?
138
+ const jail = doc.safe >= SafeMode.SAFE ? workingDir : null
139
+
140
+ if (toDir) {
141
+ outdir = doc.normalizeSystemPath(toDir, workingDir, jail, {
142
+ targetName: 'to_dir',
143
+ recover: false,
144
+ })
145
+ if (toFile) {
146
+ outfile = doc.normalizeSystemPath(toFile, outdir, null, {
147
+ targetName: 'to_dir',
148
+ recover: false,
149
+ })
150
+ // reestablish outdir as the final target directory (in case to_file had directory segments)
151
+ outdir = nodePath.dirname(outfile)
152
+ } else {
153
+ outfile = nodePath.join(
154
+ outdir,
155
+ `${doc.attributes.docname}${doc.outfilesuffix}`
156
+ )
157
+ }
158
+ } else if (toFile) {
159
+ outfile = doc.normalizeSystemPath(toFile, workingDir, jail, {
160
+ targetName: 'to_dir',
161
+ recover: false,
162
+ })
163
+ // establish outdir as the final target directory (in case to_file had directory segments)
164
+ outdir = nodePath.dirname(outfile)
165
+ }
166
+
167
+ if (input && typeof input === 'object' && input.path) {
168
+ const absInputPath = nodePath.resolve(input.path)
169
+ if (nodePath.normalize(outfile) === nodePath.normalize(absInputPath)) {
170
+ throw new Error(
171
+ `input file and output file cannot be the same: ${outfile}`
172
+ )
173
+ }
174
+ }
175
+
176
+ if (mkdirs) {
177
+ await mkdirP(outdir)
178
+ } else {
179
+ if (!(await _isDirectory(outdir))) {
180
+ // NOTE we intentionally refer to the directory as it was passed to the API
181
+ throw new Error(
182
+ `target directory does not exist: ${toDir} (hint: set mkdirs option)`
183
+ )
184
+ }
185
+ }
186
+ } else {
187
+ // write to stream
188
+ outfile = streamOutput ? toFile : null
189
+ outdir = null
190
+ }
191
+
192
+ let output
193
+ if (outfile && !streamOutput) {
194
+ output = await doc.convert({ outfile, outdir })
195
+ } else {
196
+ output = await doc.convert()
197
+ }
198
+
199
+ if (outfile) {
200
+ await doc.write(output, outfile)
201
+
202
+ // NOTE document cannot control this behavior if safe >= SafeMode.SERVER
203
+ // NOTE skip if stylesdir is a URI
204
+ if (
205
+ !streamOutput &&
206
+ doc.safe < SafeMode.SECURE &&
207
+ doc.hasAttribute('linkcss') &&
208
+ doc.hasAttribute('copycss') &&
209
+ doc.basebackend('html') &&
210
+ !(
211
+ doc.getAttribute('stylesdir') && isUriish(doc.getAttribute('stylesdir'))
212
+ )
213
+ ) {
214
+ let copyAsciidoctorStylesheet = false
215
+ let copyUserStylesheet = false
216
+ const stylesheet = doc.getAttribute('stylesheet')
217
+ if (stylesheet) {
218
+ if (DEFAULT_STYLESHEET_KEYS.has(stylesheet)) {
219
+ copyAsciidoctorStylesheet = true
220
+ } else if (!isUriish(stylesheet)) {
221
+ copyUserStylesheet = true
222
+ }
223
+ }
224
+ const syntaxHl = doc.syntaxHighlighter
225
+ const copySyntaxHlStylesheet = syntaxHl?.writeStylesheet(doc)
226
+
227
+ if (
228
+ copyAsciidoctorStylesheet ||
229
+ copyUserStylesheet ||
230
+ copySyntaxHlStylesheet
231
+ ) {
232
+ const stylesdir = doc.getAttribute('stylesdir')
233
+ const stylesoutdir = doc.normalizeSystemPath(
234
+ stylesdir,
235
+ outdir,
236
+ doc.safe >= SafeMode.SAFE ? outdir : null
237
+ )
238
+ if (mkdirs) {
239
+ await mkdirP(stylesoutdir)
240
+ } else {
241
+ if (!(await _isDirectory(stylesoutdir))) {
242
+ throw new Error(
243
+ `target stylesheet directory does not exist: ${stylesoutdir} (hint: set mkdirs option)`
244
+ )
245
+ }
246
+ }
247
+
248
+ if (copyAsciidoctorStylesheet) {
249
+ // NOTE Stylesheets.instance.write_primary_stylesheet is not yet ported to JS
250
+ } else if (copyUserStylesheet) {
251
+ let stylesheetSrc = doc.getAttribute('copycss')
252
+ if (stylesheetSrc === '' || stylesheetSrc === true) {
253
+ stylesheetSrc = doc.normalizeSystemPath(stylesheet)
254
+ } else {
255
+ // NOTE in this case, copycss is a source location (but cannot be a URI)
256
+ stylesheetSrc = doc.normalizeSystemPath(String(stylesheetSrc))
257
+ }
258
+ const stylesheetDest = doc.normalizeSystemPath(
259
+ stylesheet,
260
+ stylesoutdir,
261
+ doc.safe >= SafeMode.SAFE ? outdir : null
262
+ )
263
+ // NOTE don't warn if src can't be read and dest already exists (see #2323)
264
+ if (stylesheetSrc !== stylesheetDest) {
265
+ const warnOnFailure = !(await _isFile(stylesheetDest))
266
+ const stylesheetData = await doc.readAsset(stylesheetSrc, {
267
+ warnOnFailure,
268
+ label: 'stylesheet',
269
+ })
270
+ if (stylesheetData) {
271
+ const { writeFile } = await import('node:fs/promises')
272
+ const nodePath = await _requirePath()
273
+ const stylesheetOutdir = nodePath.dirname(stylesheetDest)
274
+ if (
275
+ stylesheetOutdir !== stylesoutdir &&
276
+ !(await _isDirectory(stylesheetOutdir))
277
+ ) {
278
+ if (!mkdirs) {
279
+ throw new Error(
280
+ `target stylesheet directory does not exist: ${stylesheetOutdir} (hint: set mkdirs option)`
281
+ )
282
+ }
283
+ await mkdirP(stylesheetOutdir)
284
+ }
285
+ await writeFile(stylesheetDest, stylesheetData, 'utf8')
286
+ }
287
+ }
288
+ }
289
+ if (copySyntaxHlStylesheet) {
290
+ await syntaxHl.writeStylesheetToDisk(doc, stylesoutdir)
291
+ }
292
+ }
293
+ }
294
+ return doc
295
+ } else {
296
+ return output
297
+ }
298
+ }
299
+
300
+ // ── convertFile ───────────────────────────────────────────────────────────────
301
+
302
+ /**
303
+ * Parse the contents of the AsciiDoc source file into a Document and convert it
304
+ * to the specified backend format.
305
+ *
306
+ * @param {string} filename - the path to the AsciiDoc source file
307
+ * @param {object} [options={}] - a plain Object of options (see {@link convert})
308
+ * @returns {Promise<import('./document.js').Document|string>} the Document if output was written to a file, otherwise the converted String
309
+ */
310
+ export async function convertFile(filename, options = {}) {
311
+ const { readFile, stat } = await import('node:fs/promises')
312
+ const nodePath = await _requirePath()
313
+ const absPath = nodePath.resolve(filename)
314
+ const content = await readFile(absPath, 'utf8')
315
+ let mtime
316
+ try {
317
+ mtime = (await stat(absPath)).mtime
318
+ } catch {
319
+ /* ignore */
320
+ }
321
+ const fileObj = {
322
+ path: absPath,
323
+ mtime,
324
+ read() {
325
+ return content
326
+ },
327
+ }
328
+ return convert(fileObj, options)
329
+ }
330
+
331
+ // Deprecated aliases
332
+ export { convert as render, convertFile as renderFile }
333
+
334
+ // ── Helpers ───────────────────────────────────────────────────────────────────
335
+
336
+ /**
337
+ * @internal
338
+ * Lazily import node:path to avoid issues in browser / Opal environments.
339
+ */
340
+ async function _requirePath() {
341
+ return import('node:path')
342
+ }
343
+
344
+ /**
345
+ * @internal
346
+ * @param {string} dir
347
+ * @returns {Promise<boolean>}
348
+ */
349
+ async function _isDirectory(dir) {
350
+ try {
351
+ const { stat } = await import('node:fs/promises')
352
+ return (await stat(dir)).isDirectory()
353
+ } catch {
354
+ return false
355
+ }
356
+ }
357
+
358
+ /**
359
+ * @internal
360
+ * @param {string} path
361
+ * @returns {Promise<boolean>}
362
+ */
363
+ async function _isFile(path) {
364
+ try {
365
+ const { stat } = await import('node:fs/promises')
366
+ return (await stat(path)).isFile()
367
+ } catch {
368
+ return false
369
+ }
370
+ }
@@ -0,0 +1,83 @@
1
+ // ESM conversion of converter/composite.rb
2
+ //
3
+ // Ruby-to-JavaScript notes:
4
+ // - Ruby Hash.new { |h,k| h[k] = find_converter(k) } → Map with lazy population in converterFor().
5
+ // - Ruby respond_to?(:composed) → typeof converter.composed === 'function'.
6
+ // - Ruby raise → throw new Error(…).
7
+ // - backend_traits_source keyword arg → options object { backendTraitsSource }.
8
+ // - init_backend_traits(source.backend_traits) → this.initBackendTraits(source.backendInfo()).
9
+
10
+ import { applyBackendTraits } from '../converter.js'
11
+
12
+ // ── CompositeConverter ────────────────────────────────────────────────────────
13
+ // Delegates to the first converter in the chain that handles a given transform.
14
+
15
+ export class CompositeConverter {
16
+ constructor(backend, ...args) {
17
+ // Last argument may be an options object { backendTraitsSource }
18
+ let opts = {}
19
+ if (
20
+ args.length > 0 &&
21
+ args[args.length - 1] !== null &&
22
+ typeof args[args.length - 1] === 'object' &&
23
+ !args[args.length - 1].convert
24
+ ) {
25
+ opts = args.pop()
26
+ }
27
+ this.backend = backend
28
+ this.converters = args
29
+ applyBackendTraits(this)
30
+ for (const converter of this.converters) {
31
+ if (typeof converter.composed === 'function') converter.composed(this)
32
+ }
33
+ if (opts.backendTraitsSource) {
34
+ this.initBackendTraits(opts.backendTraitsSource.backendInfo())
35
+ }
36
+ this._converterCache = new Map()
37
+ }
38
+
39
+ /**
40
+ * Delegates to the first converter that handles the given transform.
41
+ * @param {object} node - the AbstractNode to convert
42
+ * @param {string|null} [transform=null] - the optional transform (default: node.nodeName)
43
+ * @param {object|null} [opts=null] - optional hints passed to the delegate's convert method
44
+ * @returns {Promise<string>} the result from the delegate's convert method
45
+ */
46
+ convert(node, transform = null, opts = null) {
47
+ const t = transform ?? node.nodeName
48
+ return this.converterFor(t).convert(node, t, opts)
49
+ }
50
+
51
+ /**
52
+ * Retrieve the converter for the specified transform (cached).
53
+ * @param {string} transform
54
+ * @returns {object} the matching converter
55
+ */
56
+ converterFor(transform) {
57
+ if (this._converterCache.has(transform))
58
+ return this._converterCache.get(transform)
59
+ const converter = this._findConverter(transform)
60
+ this._converterCache.set(transform, converter)
61
+ return converter
62
+ }
63
+
64
+ /**
65
+ * Find the converter for the specified transform.
66
+ * @param {string} transform
67
+ * @returns {object} the matching converter
68
+ * @throws {Error} if no converter handles the transform
69
+ * @internal
70
+ */
71
+ _findConverter(transform) {
72
+ for (const candidate of this.converters) {
73
+ if (
74
+ typeof candidate.handles === 'function' &&
75
+ candidate.handles(transform)
76
+ )
77
+ return candidate
78
+ }
79
+ throw new Error(
80
+ `Could not find a converter to handle transform: ${transform}`
81
+ )
82
+ }
83
+ }