@asciidoctor/core 3.0.3 → 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.
- package/README.md +42 -10
- package/build/browser/index.js +24154 -0
- package/build/node/index.cjs +24735 -0
- package/{dist/css/asciidoctor.css → data/asciidoctor-default.css} +54 -53
- package/package.json +53 -100
- package/src/abstract_block.js +849 -0
- package/src/abstract_node.js +954 -0
- package/src/attribute_entry.js +12 -0
- package/src/attribute_list.js +380 -0
- package/src/block.js +168 -0
- package/src/browser/asset.js +22 -0
- package/src/browser/reader.js +138 -0
- package/src/browser.js +121 -0
- package/src/callouts.js +85 -0
- package/src/compliance.js +54 -0
- package/src/constants.js +665 -0
- package/src/convert.js +370 -0
- package/src/converter/composite.js +83 -0
- package/src/converter/docbook5.js +1031 -0
- package/src/converter/html5.js +1899 -0
- package/src/converter/manpage.js +935 -0
- package/src/converter/template.js +459 -0
- package/src/converter.js +478 -0
- package/src/data/stylesheet-data.js +2 -0
- package/src/document.js +2134 -0
- package/src/extensions.js +1952 -0
- package/src/footnote.js +28 -0
- package/src/helpers.js +355 -0
- package/src/index.js +138 -0
- package/src/inline.js +158 -0
- package/src/list.js +240 -0
- package/src/load.js +276 -0
- package/src/logging.js +526 -0
- package/src/parser.js +3661 -0
- package/src/path_resolver.js +472 -0
- package/src/reader.js +1755 -0
- package/src/rx.js +829 -0
- package/src/section.js +354 -0
- package/src/stylesheets.js +30 -0
- package/src/substitutors.js +2241 -0
- package/src/syntaxHighlighter/highlightjs.js +90 -0
- package/src/syntaxHighlighter/html_pipeline.js +33 -0
- package/src/syntax_highlighter.js +304 -0
- package/src/table.js +952 -0
- package/src/timings.js +78 -0
- package/types/abstract_block.d.ts +343 -0
- package/types/abstract_node.d.ts +471 -0
- package/types/attribute_entry.d.ts +7 -0
- package/types/attribute_list.d.ts +52 -0
- package/types/block.d.ts +55 -0
- package/types/browser/asset.d.ts +7 -0
- package/types/browser/reader.d.ts +29 -0
- package/types/callouts.d.ts +36 -0
- package/types/compliance.d.ts +23 -0
- package/types/constants.d.ts +268 -0
- package/types/convert.d.ts +34 -0
- package/types/converter/composite.d.ts +20 -0
- package/types/converter/docbook5.d.ts +41 -0
- package/types/converter/html5.d.ts +51 -0
- package/types/converter/manpage.d.ts +59 -0
- package/types/converter/template.d.ts +83 -0
- package/types/converter.d.ts +150 -0
- package/types/data/stylesheet-data.d.ts +2 -0
- package/types/document.d.ts +495 -0
- package/types/extensions.d.ts +876 -0
- package/types/footnote.d.ts +18 -0
- package/types/helpers.d.ts +146 -0
- package/types/index.d.ts +73 -3731
- package/types/inline.d.ts +69 -0
- package/types/list.d.ts +114 -0
- package/types/load.d.ts +39 -0
- package/types/logging.d.ts +187 -0
- package/types/parser.d.ts +114 -0
- package/types/path_resolver.d.ts +103 -0
- package/types/reader.d.ts +184 -0
- package/types/rx.d.ts +513 -0
- package/types/section.d.ts +122 -0
- package/types/stylesheets.d.ts +10 -0
- package/types/substitutors.d.ts +208 -0
- package/types/syntaxHighlighter/highlightjs.d.ts +33 -0
- package/types/syntaxHighlighter/html_pipeline.d.ts +16 -0
- package/types/syntax_highlighter.d.ts +167 -0
- package/types/table.d.ts +231 -0
- package/types/timings.d.ts +25 -0
- package/types/tsconfig.json +9 -0
- package/LICENSE +0 -21
- package/dist/browser/asciidoctor.js +0 -47654
- package/dist/browser/asciidoctor.min.js +0 -1452
- package/dist/graalvm/asciidoctor.js +0 -47402
- package/dist/node/asciidoctor.cjs +0 -21567
- 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
|
+
}
|