@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/list.js ADDED
@@ -0,0 +1,240 @@
1
+ // ESM conversion of list.rb
2
+
3
+ import { AbstractBlock } from './abstract_block.js'
4
+ import { LF, NORMAL_SUBS } from './constants.js'
5
+
6
+ /**
7
+ * @extends {AbstractBlock<any[]>}
8
+ */
9
+ export class List extends AbstractBlock {
10
+ constructor(parent, context, opts = {}) {
11
+ super(parent, context, opts)
12
+ }
13
+
14
+ /** Alias for blocks — the list content. */
15
+ async content() {
16
+ return this.blocks
17
+ }
18
+
19
+ /**
20
+ * Alias for {@link getItems}.
21
+ * @returns {ListItem[]}
22
+ * @see {getItems}
23
+ */
24
+ get items() {
25
+ return this.blocks
26
+ }
27
+
28
+ /**
29
+ * Check whether this list has items (blocks).
30
+ * @returns {boolean}
31
+ */
32
+ hasItems() {
33
+ return this.blocks.length > 0
34
+ }
35
+
36
+ /**
37
+ * Check whether this list is an outline list (unordered or ordered).
38
+ * @returns {boolean}
39
+ */
40
+ outline() {
41
+ return this.context === 'ulist' || this.context === 'olist'
42
+ }
43
+
44
+ /**
45
+ * Convert this list, advancing the callout list pointer if a colist.
46
+ * @returns {Promise<string>}
47
+ */
48
+ async convert() {
49
+ const result = await super.convert()
50
+ if (this.context === 'colist') this.document.callouts.nextList()
51
+ return result
52
+ }
53
+
54
+ /**
55
+ * @deprecated Use {@link convert} instead.
56
+ */
57
+ render() {
58
+ return this.convert()
59
+ }
60
+
61
+ // ── JavaScript-style accessors ────────────────────────────────────────────────
62
+
63
+ /**
64
+ * Return the list items.
65
+ * @returns {ListItem[]}
66
+ */
67
+ getItems() {
68
+ return this.blocks
69
+ }
70
+
71
+ toString() {
72
+ return `#<List {context: '${this.context}', style: ${JSON.stringify(this.style ?? null)}, items: ${this.blocks.length}}>`
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Methods for managing items for AsciiDoc olists, ulists, and dlists.
78
+ *
79
+ * In a description list (dlist), each item is a tuple: `[[term, term, ...], desc]`.
80
+ * If a description is not set, the second entry is null.
81
+ */
82
+ export class ListItem extends AbstractBlock {
83
+ /**
84
+ * The string marker used for this list item.
85
+ * @type {string|null}
86
+ */
87
+ marker
88
+
89
+ /** @internal @type {string|null} */
90
+ _text
91
+
92
+ /** @internal */
93
+ _convertedText
94
+
95
+ /** @internal @type {string[]} */
96
+ _subsSnapshot
97
+
98
+ /**
99
+ * @param {List} parent - The parent List block.
100
+ * @param {string|null} [text=null] - The text of this item.
101
+ */
102
+ constructor(parent, text = null) {
103
+ super(parent, 'list_item')
104
+ this._text = text
105
+ this.level = parent.level
106
+ this.subs = [...NORMAL_SUBS]
107
+ this.marker = null
108
+ }
109
+
110
+ /**
111
+ * Contextual alias for parent.
112
+ * @see {getParent}
113
+ */
114
+ get list() {
115
+ return this.getParent()
116
+ }
117
+
118
+ /**
119
+ * Alias for {@link getText}.
120
+ * @see {getText}
121
+ */
122
+ get text() {
123
+ if (this._convertedText != null && this._subsSnapshot != null) {
124
+ const cur = this.subs
125
+ if (
126
+ cur.length !== this._subsSnapshot.length ||
127
+ cur.some((s, i) => s !== this._subsSnapshot[i])
128
+ ) {
129
+ return this._text ?? null
130
+ }
131
+ }
132
+ return this._convertedText ?? this._text ?? null
133
+ }
134
+
135
+ /**
136
+ * Alias for {@link setText}.
137
+ * @see {setText}
138
+ */
139
+ set text(val) {
140
+ this._text = val
141
+ this._convertedText = null
142
+ this._subsSnapshot = null
143
+ }
144
+
145
+ /**
146
+ * Check whether the text of this list item is non-blank.
147
+ * @returns {boolean}
148
+ */
149
+ hasText() {
150
+ return !!(this._text && this._text.length > 0)
151
+ }
152
+
153
+ /**
154
+ * Pre-compute the converted text asynchronously.
155
+ * Called during `Document.parse()` so the synchronous getter works during conversion.
156
+ * @returns {Promise<void>}
157
+ */
158
+ async precomputeText() {
159
+ if (this._text != null && this._convertedText == null) {
160
+ this._convertedText = await this.applySubs(this._text, this.subs)
161
+ this._subsSnapshot = [...this.subs]
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Check whether this list item has simple content.
167
+ * @returns {boolean} `true` if the item has no blocks or only a single nested outline list.
168
+ */
169
+ simple() {
170
+ return (
171
+ this.blocks.length === 0 ||
172
+ (this.blocks.length === 1 &&
173
+ this.blocks[0] instanceof List &&
174
+ this.blocks[0].outline())
175
+ )
176
+ }
177
+
178
+ /**
179
+ * Check whether this list item has compound content.
180
+ * @returns {boolean} `true` if the item contains blocks other than a single nested outline list.
181
+ */
182
+ compound() {
183
+ return !this.simple()
184
+ }
185
+
186
+ /** @internal Fold the adjacent paragraph block into the list item text. */
187
+ foldFirst() {
188
+ const src = this.blocks.shift().source
189
+ this._text =
190
+ !this._text || this._text.length === 0 ? src : `${this._text}${LF}${src}`
191
+ }
192
+
193
+ // ── JavaScript-style accessors ────────────────────────────────────────────────
194
+
195
+ /**
196
+ * Return the parent List block (alias of {@link getParent}).
197
+ * @returns {List}
198
+ * @see {getParent}
199
+ */
200
+ getList() {
201
+ return this.list
202
+ }
203
+
204
+ /**
205
+ * Return the list marker string for this item (e.g. '.', '..', '*').
206
+ * @returns {string|null}
207
+ */
208
+ getMarker() {
209
+ return this.marker
210
+ }
211
+
212
+ /**
213
+ * Return the text of this list item with substitutions applied.
214
+ * The result is pre-computed during `Document.parse()` via {@link precomputeText}.
215
+ * Falls back to the raw text if {@link precomputeText} has not been called yet.
216
+ *
217
+ * In Ruby, text is lazy (`apply_subs` on first access), so API callers can modify
218
+ * subs before accessing text and get the result they expect. Here we replicate
219
+ * that by invalidating the pre-computed value when subs have changed since it
220
+ * was computed: returning raw text mirrors what Ruby would produce when subs are
221
+ * cleared or reduced to a no-op set (since `applySubs` is async and cannot be
222
+ * re-run synchronously).
223
+ * @returns {string|null}
224
+ */
225
+ getText() {
226
+ return this.text
227
+ }
228
+
229
+ /**
230
+ * Set the raw text of this list item.
231
+ * @param {string|null} val
232
+ */
233
+ setText(val) {
234
+ this.text = val
235
+ }
236
+
237
+ toString() {
238
+ return `#<ListItem {list_context: '${this.getParent().context}', text: ${JSON.stringify(this._text)}, blocks: ${(this.blocks ?? []).length}}>`
239
+ }
240
+ }
package/src/load.js ADDED
@@ -0,0 +1,276 @@
1
+ // ESM conversion of load.rb
2
+ //
3
+ // Ruby-to-JavaScript notes:
4
+ // - Ruby module methods on Asciidoctor → named exports load() and loadFile().
5
+ // - Ruby File === input branch → Node.js fs.createReadStream / fs.readFileSync
6
+ // adapted to check for an object with a .read() method (duck-typing).
7
+ // - Ruby File.absolute_path / File.dirname / Helpers.basename / Helpers.extname
8
+ // → implemented using Node's node:path and the helpers.js module.
9
+ // - The timings option is passed through but its start/record calls are no-ops
10
+ // unless a real Timings object is supplied (interface: { start(label), record(label) }).
11
+ // - LoggerManager from logging.js is used to honour the :logger option.
12
+ // - SpaceDelimiterRx / EscapedSpaceRx / NULL are imported from rx.js / constants.js
13
+ // for string-form attributes parsing (mirrors the Ruby gsub/split dance).
14
+ // - Circular dependencies (Document ↔ Parser, etc.) are resolved via static ESM imports
15
+ // with live bindings; no lazy import() needed. Built-in converters are still loaded
16
+ // lazily by Converter.create() and self-register on first use.
17
+
18
+ import { SpaceDelimiterRx, EscapedSpaceRx } from './rx.js'
19
+ import { NULL, BACKEND_ALIASES } from './constants.js'
20
+ import { LoggerManager, NullLogger } from './logging.js'
21
+ import { basename, extname } from './helpers.js'
22
+ import { Document } from './document.js'
23
+ import { Converter } from './converter.js'
24
+ import { SyntaxHighlighter } from './syntax_highlighter.js'
25
+ import HighlightJsAdapter from './syntaxHighlighter/highlightjs.js'
26
+ import HtmlPipelineAdapter from './syntaxHighlighter/html_pipeline.js'
27
+ import { AbstractNode } from './abstract_node.js'
28
+ import { Substitutors } from './substitutors.js'
29
+
30
+ // Apply the Substitutors mixin to AbstractNode so that all nodes (Document,
31
+ // Section, Block, etc.) have subSpecialchars, subAttributes, etc. available.
32
+ Object.assign(AbstractNode.prototype, Substitutors)
33
+
34
+ // Register built-in syntax highlighters (mirrors Ruby's `register_for`).
35
+ SyntaxHighlighter.register(HighlightJsAdapter, 'highlightjs', 'highlight.js')
36
+ SyntaxHighlighter.register(HtmlPipelineAdapter, 'html-pipeline')
37
+
38
+ // ── load ──────────────────────────────────────────────────────────────────────
39
+
40
+ /**
41
+ * Parse the AsciiDoc source input into a Document.
42
+ *
43
+ * Accepts input as a Node.js Readable stream (or any object with a read()
44
+ * method), a String, or a String Array. If the input is a file descriptor
45
+ * object produced by openFile() / Node's fs.openSync(), pass a plain object
46
+ * with { path, read() } instead; the function sets docfile/docdir/docname
47
+ * attributes automatically.
48
+ *
49
+ * @param {Buffer|string|string[]|{path?: string, read(): string|Promise<string>, mtime?: Date}} input - The AsciiDoc source.
50
+ * @param {Object} [options={}] - Options to control processing. See Document for the full list.
51
+ * @param {string|string[]|Object} [options.attributes] - Document attributes.
52
+ * @param {boolean} [options.parse] - Set to false to skip parsing after Document creation.
53
+ * @param {Object} [options.logger] - Logger instance to use for this call.
54
+ * @param {{start(label: string): void, record(label: string): void}} [options.timings] - Timings object.
55
+ * @returns {Promise<Document>} A Promise that resolves to the Document.
56
+ */
57
+ export async function load(input, options = {}) {
58
+ // Shallow-copy options so we don't mutate the caller's object.
59
+ options = Object.assign({}, options)
60
+
61
+ const timings = options.timings ?? null
62
+ if (timings) timings.start('read')
63
+
64
+ // ── Logger override ───────────────────────────────────────────────────────
65
+ if ('logger' in options) {
66
+ const logger = options.logger
67
+ if (logger !== LoggerManager.logger) {
68
+ LoggerManager.logger = logger ?? new NullLogger()
69
+ }
70
+ }
71
+
72
+ // ── Attributes normalisation ──────────────────────────────────────────────
73
+ let attrs = options.attributes
74
+ if (!attrs) {
75
+ attrs = {}
76
+ } else if (typeof attrs === 'string') {
77
+ // Condense non-escaped whitespace runs to NULL, unescape escaped spaces, split on NULL.
78
+ attrs = _parseAttributeString(attrs)
79
+ } else if (Array.isArray(attrs)) {
80
+ attrs = _parseAttributeArray(attrs)
81
+ } else if (typeof attrs === 'object') {
82
+ attrs = Object.assign({}, attrs)
83
+ } else {
84
+ throw new TypeError(`illegal type for attributes option: ${typeof attrs}`)
85
+ }
86
+
87
+ // ── Input reading ─────────────────────────────────────────────────────────
88
+ let source
89
+ if (input && typeof input === 'object' && typeof input.read === 'function') {
90
+ // Duck-typed file-like object: { path?, mtime?, read() }
91
+ if (input.path) {
92
+ // Treat it like a File object: resolve path, set docfile/docdir/docname.
93
+ const nodePath = await _requirePath()
94
+ const inputPath = nodePath.resolve(input.path)
95
+ if (input.mtime) options.input_mtime = input.mtime
96
+ attrs.docfile = inputPath
97
+ attrs.docdir = nodePath.dirname(inputPath)
98
+ const docfilesuffix = extname(inputPath)
99
+ attrs.docfilesuffix = docfilesuffix
100
+ attrs.docname = basename(inputPath, docfilesuffix)
101
+ }
102
+ source = await _readStream(input)
103
+ } else if (
104
+ typeof input === 'object' &&
105
+ input?.constructor?.name === 'Buffer'
106
+ ) {
107
+ source = input.toString('utf8')
108
+ } else if (typeof input === 'string') {
109
+ source = input
110
+ } else if (Array.isArray(input)) {
111
+ source = input.slice()
112
+ } else if (input) {
113
+ throw new TypeError(`unsupported input type: ${typeof input}`)
114
+ }
115
+
116
+ if (timings) {
117
+ timings.record('read')
118
+ timings.start('parse')
119
+ }
120
+
121
+ options.attributes = attrs
122
+
123
+ // ── Document construction + optional parse ────────────────────────────────
124
+ let doc
125
+ try {
126
+ let backend = String(attrs.backend || options.backend || 'html5')
127
+ // Strip soft-set modifier (@) and value-based soft-set (ending with @)
128
+ if (backend.endsWith('@')) backend = backend.slice(0, -1)
129
+ if (backend.startsWith('xhtml')) backend = `html${backend.slice(5)}` // xhtml5 → html5
130
+ backend = BACKEND_ALIASES[backend] ?? backend
131
+ await Converter.create(backend, {})
132
+ // If template dirs are requested, pre-create the async template converter
133
+ // so that _createConverter() can use it synchronously during Document construction.
134
+ // (In Ruby, Converter.create is synchronous; in JS we bridge the gap here.)
135
+ if (options.template_dir || options.template_dirs) {
136
+ const templateDirs = [].concat(
137
+ options.template_dirs ?? options.template_dir
138
+ )
139
+ const converterOpts = {
140
+ template_dirs: templateDirs,
141
+ template_cache: options.template_cache ?? true,
142
+ template_engine: options.template_engine,
143
+ template_engine_options: options.template_engine_options,
144
+ }
145
+ options._preCreatedConverter = await Converter.create(
146
+ backend,
147
+ converterOpts
148
+ )
149
+ }
150
+ if (options.parse !== false) {
151
+ doc = await Document.create(source, options)
152
+ } else {
153
+ doc = new Document(source, options)
154
+ }
155
+ } catch (e) {
156
+ const docfile = attrs.docfile || '<stdin>'
157
+ const context = `asciidoctor: FAILED: ${docfile}: Failed to load AsciiDoc document`
158
+ let wrapped
159
+ try {
160
+ wrapped = new Error(`${context} - ${e.message}`)
161
+ wrapped.stack = e.stack
162
+ wrapped.cause = e
163
+ } catch {
164
+ wrapped = e
165
+ }
166
+ throw wrapped
167
+ }
168
+
169
+ if (timings) timings.record('parse')
170
+ return doc
171
+ }
172
+
173
+ // ── loadFile ──────────────────────────────────────────────────────────────────
174
+
175
+ /**
176
+ * Parse the contents of the AsciiDoc source file into a Document.
177
+ *
178
+ * @param {string} filename - The path to the AsciiDoc source file.
179
+ * @param {Object} [options={}] - Options to control processing.
180
+ * @returns {Promise<Document>} A Promise that resolves to the Document.
181
+ */
182
+ export async function loadFile(filename, options = {}) {
183
+ const { readFile } = await import('node:fs/promises')
184
+ const nodePath = await _requirePath()
185
+ const absPath = nodePath.resolve(filename)
186
+ const content = await readFile(absPath, 'utf8')
187
+ // Build a file-like object so load() can set docfile/docdir/docname.
188
+ const { stat } = await import('node:fs/promises')
189
+ let mtime
190
+ try {
191
+ const s = await stat(absPath)
192
+ mtime = s.mtime
193
+ } catch {
194
+ /* ignore */
195
+ }
196
+ const fileObj = {
197
+ path: absPath,
198
+ mtime,
199
+ read() {
200
+ return content
201
+ },
202
+ }
203
+ return load(fileObj, options)
204
+ }
205
+
206
+ // ── Helpers ───────────────────────────────────────────────────────────────────
207
+
208
+ /**
209
+ * Parse a whitespace-delimited attribute string into a plain object.
210
+ *
211
+ * Mirrors the Ruby idiom:
212
+ * attrs.gsub(SpaceDelimiterRx, '\1' + NULL).gsub(EscapedSpaceRx, '\1').split(NULL)
213
+ *
214
+ * @param {string} str - The attribute string to parse.
215
+ * @returns {Object} A plain object mapping attribute keys to values.
216
+ * @internal
217
+ */
218
+ function _parseAttributeString(str) {
219
+ const condensed = str
220
+ .replace(SpaceDelimiterRx, `$1${NULL}`)
221
+ .replace(EscapedSpaceRx, '$1')
222
+ const result = {}
223
+ for (const entry of condensed.split(NULL)) {
224
+ if (!entry) continue
225
+ const eqIdx = entry.indexOf('=')
226
+ if (eqIdx < 0) {
227
+ result[entry] = ''
228
+ } else {
229
+ result[entry.slice(0, eqIdx)] = entry.slice(eqIdx + 1)
230
+ }
231
+ }
232
+ return result
233
+ }
234
+
235
+ /**
236
+ * Parse an array of "key=value" entries into a plain object.
237
+ *
238
+ * @param {string[]} arr - Array of "key=value" strings.
239
+ * @returns {Object} A plain object mapping attribute keys to values.
240
+ * @internal
241
+ */
242
+ function _parseAttributeArray(arr) {
243
+ const result = {}
244
+ for (const entry of arr) {
245
+ const eqIdx = entry.indexOf('=')
246
+ if (eqIdx < 0) {
247
+ result[entry] = ''
248
+ } else {
249
+ result[entry.slice(0, eqIdx)] = entry.slice(eqIdx + 1)
250
+ }
251
+ }
252
+ return result
253
+ }
254
+
255
+ /**
256
+ * Read all data from an object that has a .read() method.
257
+ * Supports both synchronous (returns string) and async (returns Promise) variants.
258
+ *
259
+ * @param {{read(): string|Promise<string>}} readable - The readable object.
260
+ * @returns {Promise<string>} A Promise that resolves to a String.
261
+ * @internal
262
+ */
263
+ async function _readStream(readable) {
264
+ const data = readable.read()
265
+ return data instanceof Promise ? data : Promise.resolve(data ?? '')
266
+ }
267
+
268
+ /**
269
+ * Lazily import node:path to avoid issues in browser / Opal environments.
270
+ *
271
+ * @returns {Promise<typeof import('node:path')>} A Promise that resolves to the node:path module.
272
+ * @internal
273
+ */
274
+ async function _requirePath() {
275
+ return import('node:path')
276
+ }