@antora/assembler 1.0.0-alpha.9 → 1.0.0-beta.10
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 +7 -4
- package/adapters/asciidoctor/jsonl-logger.rb +25 -0
- package/lib/assemble-content.js +277 -26
- package/lib/configure.js +93 -0
- package/lib/index.js +2 -2
- package/lib/load-config.js +67 -24
- package/lib/produce-assembly-file.js +825 -0
- package/lib/produce-assembly-files.js +215 -0
- package/lib/util/compute-out.js +57 -0
- package/lib/util/create-asciidoc-file.js +17 -0
- package/lib/util/parse-resource-ref.js +36 -0
- package/package.json +18 -13
- package/lib/asciidoctor/reducer-extension.js +0 -235
- package/lib/produce-aggregate-document.js +0 -599
- package/lib/produce-aggregate-documents.js +0 -145
- package/lib/util/run-command.js +0 -60
|
@@ -1,599 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const File = require('vinyl')
|
|
4
|
-
const path = require('node:path/posix')
|
|
5
|
-
const sanitize = require('./util/sanitize')
|
|
6
|
-
const unconvertInlineAsciiDoc = require('./util/unconvert-inline-asciidoc')
|
|
7
|
-
|
|
8
|
-
function produceAggregateDocument (
|
|
9
|
-
loadAsciiDoc,
|
|
10
|
-
contentCatalog,
|
|
11
|
-
componentVersion,
|
|
12
|
-
outline,
|
|
13
|
-
doctype,
|
|
14
|
-
pages,
|
|
15
|
-
asciidocConfig,
|
|
16
|
-
mutableAttributes,
|
|
17
|
-
sectionMergeStrategy = 'discrete'
|
|
18
|
-
) {
|
|
19
|
-
const pagesInOutline = selectPagesInOutline(outline, pages)
|
|
20
|
-
const navtitle = outline.content
|
|
21
|
-
const templateFile = contentCatalog.addFile({
|
|
22
|
-
src: {
|
|
23
|
-
component: componentVersion.name,
|
|
24
|
-
version: componentVersion.version,
|
|
25
|
-
module: 'ROOT',
|
|
26
|
-
family: 'page',
|
|
27
|
-
relative: generateSlug(navtitle),
|
|
28
|
-
},
|
|
29
|
-
})
|
|
30
|
-
const { dir: outDir, name: outName } = path.parse(templateFile.out.path)
|
|
31
|
-
const path_ = outName === templateFile.src.relative ? path.join(outDir, outName + '.adoc') : outDir + '.adoc'
|
|
32
|
-
contentCatalog.removeFile(templateFile)
|
|
33
|
-
const header = buildAsciiDocHeader(componentVersion, navtitle, doctype)
|
|
34
|
-
const body = aggregateAsciiDoc(
|
|
35
|
-
loadAsciiDoc,
|
|
36
|
-
contentCatalog,
|
|
37
|
-
header,
|
|
38
|
-
componentVersion,
|
|
39
|
-
outline,
|
|
40
|
-
pagesInOutline,
|
|
41
|
-
asciidocConfig,
|
|
42
|
-
mutableAttributes,
|
|
43
|
-
sectionMergeStrategy
|
|
44
|
-
)
|
|
45
|
-
return new File({
|
|
46
|
-
aggregate: true,
|
|
47
|
-
asciidoc: asciidocConfig,
|
|
48
|
-
contents: Buffer.from([...header, ...body].join('\n') + '\n'),
|
|
49
|
-
mediaType: 'text/asciidoc',
|
|
50
|
-
path: path_,
|
|
51
|
-
src: {
|
|
52
|
-
component: componentVersion.name,
|
|
53
|
-
version: componentVersion.version,
|
|
54
|
-
basename: path.basename(path_),
|
|
55
|
-
stem: path.basename(path_, '.adoc'),
|
|
56
|
-
extname: '.adoc',
|
|
57
|
-
},
|
|
58
|
-
})
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function buildAsciiDocHeader (componentVersion, navtitle, doctype = 'book') {
|
|
62
|
-
const navtitlePlain = sanitize(navtitle)
|
|
63
|
-
const navtitleAsciiDoc = unconvertInlineAsciiDoc(navtitle)
|
|
64
|
-
let doctitle = navtitleAsciiDoc
|
|
65
|
-
if (navtitlePlain !== componentVersion.title) doctitle = `${componentVersion.title}: ${doctitle}`
|
|
66
|
-
const version = componentVersion.version === 'master' ? '' : componentVersion.version
|
|
67
|
-
return [
|
|
68
|
-
`= ${doctitle}`,
|
|
69
|
-
...(version ? [`:revnumber: ${version}`] : []),
|
|
70
|
-
...(doctype === 'article' ? [] : [`:doctype: ${doctype}`]),
|
|
71
|
-
':underscore: _',
|
|
72
|
-
// Q: should we pass these via the CLI so they cannot be modified?
|
|
73
|
-
`:page-component-name: ${componentVersion.name}`,
|
|
74
|
-
`:page-component-version:${version ? ' ' + version : ''}`,
|
|
75
|
-
':page-version: {page-component-version}',
|
|
76
|
-
`:page-component-display-version: ${componentVersion.displayVersion}`,
|
|
77
|
-
`:page-component-title: ${componentVersion.title}`,
|
|
78
|
-
]
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function selectPagesInOutline (outlineEntry, pages) {
|
|
82
|
-
const page = outlineEntry.urlType === 'internal' ? pages.find((it) => it.pub.url === outlineEntry.url) : undefined
|
|
83
|
-
return (outlineEntry.items || []).reduce(
|
|
84
|
-
(accum, item) => new Map([...accum, ...selectPagesInOutline(item, pages)]),
|
|
85
|
-
new Map(
|
|
86
|
-
page && [
|
|
87
|
-
[`${page.src.module === 'ROOT' ? '' : page.src.module + ':'}${page.src.relative}`, page],
|
|
88
|
-
[page.pub.url, page],
|
|
89
|
-
]
|
|
90
|
-
)
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function aggregateAsciiDoc (
|
|
95
|
-
loadAsciiDoc,
|
|
96
|
-
contentCatalog,
|
|
97
|
-
header,
|
|
98
|
-
componentVersion,
|
|
99
|
-
outlineEntry,
|
|
100
|
-
pagesInOutline,
|
|
101
|
-
asciidocConfig,
|
|
102
|
-
mutableAttributes,
|
|
103
|
-
sectionMergeStrategy,
|
|
104
|
-
lastComponentVersion = componentVersion,
|
|
105
|
-
level = 0
|
|
106
|
-
) {
|
|
107
|
-
const buffer = []
|
|
108
|
-
// TODO: we could try to be smart about it and make sure the page with fragment is included at least once
|
|
109
|
-
if (outlineEntry.hash) return buffer
|
|
110
|
-
const { content: navtitle, items = [], unresolved, urlType, url } = outlineEntry
|
|
111
|
-
const hasItems = items.length > 0
|
|
112
|
-
const navtitlePlain = sanitize(navtitle)
|
|
113
|
-
const navtitleAsciiDoc = unconvertInlineAsciiDoc(navtitle)
|
|
114
|
-
const siteUrl = ((val) => {
|
|
115
|
-
if (!val || val === '/') return ''
|
|
116
|
-
return val.charAt(val.length - 1) === '/' ? val.slice(0, val.length - 1) : val
|
|
117
|
-
})(asciidocConfig.attributes['site-url'])
|
|
118
|
-
// FIXME: ideally, resource ID would be stored in navigation so we can look up the page more efficiently
|
|
119
|
-
let page = urlType === 'internal' && !unresolved ? pagesInOutline.get(url) : undefined
|
|
120
|
-
if (page && pagesInOutline.aggregated?.includes(page)) page = undefined
|
|
121
|
-
if (page) {
|
|
122
|
-
let contents = page.src.contents
|
|
123
|
-
if (contents == null) return buffer
|
|
124
|
-
// NOTE: blank lines at top and bottom of document create mismatch when using line numbers to navigate source lines
|
|
125
|
-
// IMPORTANT: this must not leave behind lines the parser will drop!
|
|
126
|
-
// IDEA: another option is to capture initial lineno of reader and use as offset (but preseves those blank lines)
|
|
127
|
-
contents = Buffer.from(
|
|
128
|
-
contents
|
|
129
|
-
.toString()
|
|
130
|
-
.replace(/^(?:[ \t]*\r\n?|[ \t]*\n)+/, '')
|
|
131
|
-
.trimRight()
|
|
132
|
-
)
|
|
133
|
-
;(pagesInOutline.aggregated ??= []).push(page)
|
|
134
|
-
page = new page.constructor(Object.assign({}, page, { contents, mediaType: 'text/asciidoc' }))
|
|
135
|
-
const { component, version, module: module_, relative, origin } = page.src
|
|
136
|
-
const doc = loadAsciiDoc(page, contentCatalog, asciidocConfig)
|
|
137
|
-
const refs = doc.getCatalog().refs
|
|
138
|
-
// NOTE: in Antora, docname is relative src path from module without file extension
|
|
139
|
-
const docname = doc.getAttribute('docname')
|
|
140
|
-
const docnameForId = docname.replace(/[/]/g, '::').replace(/[.]/g, '-')
|
|
141
|
-
const scopeId = component !== componentVersion.name
|
|
142
|
-
const idprefix =
|
|
143
|
-
(scopeId ? component + ':' : '') +
|
|
144
|
-
(module_ === 'ROOT' ? (scopeId ? ':' : '') : module_ + ':') +
|
|
145
|
-
docnameForId +
|
|
146
|
-
':::'
|
|
147
|
-
buffer.push('')
|
|
148
|
-
buffer.push(`:docname: ${docname}`)
|
|
149
|
-
if (component !== lastComponentVersion.name) {
|
|
150
|
-
const thisComponentVersion =
|
|
151
|
-
component === componentVersion.name && version === componentVersion.version
|
|
152
|
-
? componentVersion
|
|
153
|
-
: contentCatalog.getComponentVersion(component, version)
|
|
154
|
-
if (thisComponentVersion) {
|
|
155
|
-
buffer.push(`:page-component-name: ${thisComponentVersion.name}`)
|
|
156
|
-
buffer.push(`:page-component-version:${thisComponentVersion.version ? ' ' + thisComponentVersion.version : ''}`)
|
|
157
|
-
buffer.push(':page-version: {page-component-version}')
|
|
158
|
-
buffer.push(`:page-component-display-version: ${thisComponentVersion.displayVersion}`)
|
|
159
|
-
buffer.push(`:page-component-title: ${thisComponentVersion.title}`)
|
|
160
|
-
lastComponentVersion = thisComponentVersion
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
buffer.push(`:page-module: ${module_}`)
|
|
164
|
-
buffer.push(`:page-relative-src-path: ${relative}`)
|
|
165
|
-
//buffer.push(`:page-origin-type: ${origin.type}`)
|
|
166
|
-
buffer.push(`:page-origin-url: ${origin.url}`)
|
|
167
|
-
buffer.push(`:page-origin-start-path:${origin.startPath && ' '}${origin.startPath}`)
|
|
168
|
-
buffer.push(`:page-origin-refname: ${origin.branch || origin.tag}`)
|
|
169
|
-
buffer.push(`:page-origin-reftype: ${origin.branch ? 'branch' : 'tag'}`)
|
|
170
|
-
buffer.push(`:page-origin-refhash: ${origin.worktree ? '(worktree)' : origin.refhash}`)
|
|
171
|
-
let enclosed
|
|
172
|
-
// NOTE: if level is 0, doctitle has already been added and we're in the document header
|
|
173
|
-
if (level) {
|
|
174
|
-
if (level === 1 && navtitlePlain === componentVersion.title) {
|
|
175
|
-
level--
|
|
176
|
-
} else {
|
|
177
|
-
let hlevel = level + 1
|
|
178
|
-
if (hlevel > 6) {
|
|
179
|
-
hlevel = 6
|
|
180
|
-
buffer.push(`[discrete#${idprefix}]`)
|
|
181
|
-
} else {
|
|
182
|
-
buffer.push(`[#${idprefix}]`)
|
|
183
|
-
}
|
|
184
|
-
buffer.push(`${'='.repeat(hlevel)} ${navtitleAsciiDoc}`)
|
|
185
|
-
}
|
|
186
|
-
} else {
|
|
187
|
-
header.unshift(`[#${idprefix}]`)
|
|
188
|
-
}
|
|
189
|
-
if (sectionMergeStrategy === 'enclose' && hasItems && doc.hasSections()) {
|
|
190
|
-
enclosed = true
|
|
191
|
-
// TODO: make overview section title configurable
|
|
192
|
-
//let overviewTitle = doc.getDocumentTitle()
|
|
193
|
-
//if (overviewTitle === navtitle) overviewTitle = doc.getAttribute('overview-title', 'Overview')
|
|
194
|
-
const overviewTitle = doc.getAttribute('overview-title', 'Overview')
|
|
195
|
-
buffer.push('')
|
|
196
|
-
// NOTE: try to toggle sectids; otherwise, fallback to globally unique synthetic ID
|
|
197
|
-
let toggleSectids, syntheticId
|
|
198
|
-
if (doc.isAttribute('sectids')) {
|
|
199
|
-
if (doc.isAttributeLocked('sectids')) {
|
|
200
|
-
syntheticId = `__object-id-${getObjectId(outlineEntry)}`
|
|
201
|
-
} else {
|
|
202
|
-
buffer.push(':!sectids:')
|
|
203
|
-
toggleSectids = true
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
let hlevel = level + 2
|
|
207
|
-
if (hlevel > 6) {
|
|
208
|
-
hlevel = 6
|
|
209
|
-
buffer.push(syntheticId ? `[discrete#${syntheticId}]` : '[discrete]')
|
|
210
|
-
} else if (syntheticId) {
|
|
211
|
-
buffer.push(`[#${syntheticId}]`)
|
|
212
|
-
}
|
|
213
|
-
buffer.push(`${'='.repeat(hlevel)} ${overviewTitle}`)
|
|
214
|
-
if (toggleSectids) buffer.push(':sectids:')
|
|
215
|
-
}
|
|
216
|
-
const lines = doc.getSourceLines()
|
|
217
|
-
const ignoreLines = []
|
|
218
|
-
// TODO: think more about when multipart is allowed; perhaps configurable
|
|
219
|
-
if (doc.hasSections()) fixSectionLevels(doc.getSections(), level === 0)
|
|
220
|
-
const allBlocks = doc.findBy({ traverse_documents: true }, (it) =>
|
|
221
|
-
it.getContext() === 'document'
|
|
222
|
-
? it.getDocument().isNested()
|
|
223
|
-
: !(it.getContext() === 'table_cell' && it.getStyle() === 'asciidoc')
|
|
224
|
-
)
|
|
225
|
-
allBlocks.forEach((block) => {
|
|
226
|
-
const contentModel = block.content_model
|
|
227
|
-
if (
|
|
228
|
-
((contentModel === 'verbatim' && block.getContext() !== 'table_cell') ||
|
|
229
|
-
contentModel === 'simple' ||
|
|
230
|
-
contentModel === 'pass') &&
|
|
231
|
-
!block.hasSubstitution('macros')
|
|
232
|
-
) {
|
|
233
|
-
const lineno = block.getLineNumber()
|
|
234
|
-
const idx = typeof lineno === 'number' ? lineno - 1 : undefined
|
|
235
|
-
const startLine = lines[idx]
|
|
236
|
-
// NOTE: one case this happens if when sourcemap isn't enabled when reducing
|
|
237
|
-
if (startLine == null) {
|
|
238
|
-
console.log(`null startLine for ${block.getContext()} at ${lineno} in ${relative}`)
|
|
239
|
-
return
|
|
240
|
-
}
|
|
241
|
-
const char0 = startLine.charAt()
|
|
242
|
-
// FIXME: needs to be more robust; move logic to helper
|
|
243
|
-
const delimited =
|
|
244
|
-
startLine.length > 3 &&
|
|
245
|
-
startLine === char0.repeat(startLine.length) &&
|
|
246
|
-
(char0 === '-' || char0 === '.' || char0 === '+')
|
|
247
|
-
// QUESTION: exclude block attribute lines too? what about attribute entries?
|
|
248
|
-
for (let i = idx; i < block.lines.length + (delimited ? idx + 2 : idx); i++) ignoreLines.push(i)
|
|
249
|
-
}
|
|
250
|
-
})
|
|
251
|
-
let skipping
|
|
252
|
-
for (let idx = 0, len = lines.length; idx < len; idx++) {
|
|
253
|
-
if (~ignoreLines.indexOf(idx)) continue
|
|
254
|
-
let line = lines[idx]
|
|
255
|
-
if (line.startsWith('//')) {
|
|
256
|
-
if (line[2] !== '/') continue
|
|
257
|
-
if (line.length > 3 && line === '/'.repeat(line.length)) {
|
|
258
|
-
if (skipping) {
|
|
259
|
-
if (line === skipping) skipping = undefined
|
|
260
|
-
} else {
|
|
261
|
-
skipping = line
|
|
262
|
-
}
|
|
263
|
-
continue
|
|
264
|
-
}
|
|
265
|
-
} else if (skipping) {
|
|
266
|
-
continue
|
|
267
|
-
}
|
|
268
|
-
if (line.charAt() === ':' && /^:(?:leveloffset: .*|!leveloffset:|leveloffset!:)$/.test(line)) {
|
|
269
|
-
if (lines[idx - 1] === '') lines[idx - 1] = undefined
|
|
270
|
-
lines[idx] = undefined
|
|
271
|
-
continue
|
|
272
|
-
}
|
|
273
|
-
if (~line.indexOf('<<')) {
|
|
274
|
-
line = line.replace(/(?<![\\+])<<#?([\p{Alpha}0-9_/.:{][^>,]*?)(?:|, *([^>]+?))?>>/gu, (m, refid, text) => {
|
|
275
|
-
// support natural xref
|
|
276
|
-
if (!refs['$key?'](refid) && (~refid.indexOf(' ') || refid.toLowerCase() !== refid)) {
|
|
277
|
-
if ((refid = doc.$resolve_id(refid))['$nil?']()) return m
|
|
278
|
-
}
|
|
279
|
-
return `<<${idprefix}${refid}${text ? ',' + text : ''}>>`
|
|
280
|
-
})
|
|
281
|
-
}
|
|
282
|
-
// NOTE: the next check takes care of inline and block anchors
|
|
283
|
-
if (~line.indexOf('[[')) {
|
|
284
|
-
line = line.replace(/\[\[([\p{Alpha}_:][\p{Alpha}0-9_\-:.]*)(|, *.+?)\]\]/gu, `[[${idprefix}$1$2]]`)
|
|
285
|
-
}
|
|
286
|
-
if (~line.indexOf('xref:')) {
|
|
287
|
-
// Q: should we allow : as first character of target?
|
|
288
|
-
line = line.replace(/(?<![\\+])xref:([\p{Alpha}0-9_/.{#].*?)\[(|.*?[^\\])\]/gu, (m, target, text) => {
|
|
289
|
-
let pagePart, fragment, targetPage
|
|
290
|
-
const hashIdx = target.indexOf('#')
|
|
291
|
-
if (~hashIdx) {
|
|
292
|
-
pagePart = target.slice(0, hashIdx)
|
|
293
|
-
fragment = target.slice(hashIdx + 1)
|
|
294
|
-
// TODO: for now, assume .adoc; in the future, consider other file extensions
|
|
295
|
-
if (pagePart && !pagePart.endsWith('.adoc')) pagePart += '.adoc'
|
|
296
|
-
} else if (target.endsWith('.adoc')) {
|
|
297
|
-
pagePart = target
|
|
298
|
-
fragment = ''
|
|
299
|
-
} else {
|
|
300
|
-
fragment = target
|
|
301
|
-
}
|
|
302
|
-
if (!pagePart) {
|
|
303
|
-
// Q: should we validate the internal ID here?
|
|
304
|
-
return text && ~text.indexOf('=')
|
|
305
|
-
? `xref:${idprefix}${fragment}[${text}]`
|
|
306
|
-
: `<<${idprefix}${fragment}${text ? ',' + text.replace(/\\]/g, ']') : ''}>>`
|
|
307
|
-
}
|
|
308
|
-
if (~pagePart.indexOf('@') || /:.*:/.test(pagePart)) {
|
|
309
|
-
if (siteUrl && (targetPage = contentCatalog.resolvePage(pagePart, page.src)) && targetPage.out) {
|
|
310
|
-
text ||= targetPage.asciidoc?.xreftext || target
|
|
311
|
-
return `${siteUrl}${targetPage.pub.url}${fragment && '#' + fragment}[${text}]`
|
|
312
|
-
}
|
|
313
|
-
// TODO: handle unresolved page better
|
|
314
|
-
return m
|
|
315
|
-
} else if (pagePart.indexOf(':') < 0) {
|
|
316
|
-
if (module_ !== 'ROOT') pagePart = `${module_}:${pagePart}`
|
|
317
|
-
} else if (pagePart.startsWith('ROOT:')) {
|
|
318
|
-
pagePart = pagePart.slice(5)
|
|
319
|
-
}
|
|
320
|
-
if (!(targetPage = pagesInOutline.get(pagePart))) {
|
|
321
|
-
if (siteUrl && (targetPage = contentCatalog.resolvePage(pagePart, page.src)) && targetPage.out) {
|
|
322
|
-
text ||= targetPage.asciidoc?.xreftext || target
|
|
323
|
-
return `${siteUrl}${targetPage.pub.url}${fragment && '#' + fragment}[${text}]`
|
|
324
|
-
}
|
|
325
|
-
// TODO: handle unresolved page better
|
|
326
|
-
return m
|
|
327
|
-
}
|
|
328
|
-
pagePart = pagePart
|
|
329
|
-
.replace(/[/]/g, '::')
|
|
330
|
-
.replace(/\.adoc$/, '')
|
|
331
|
-
.replace(/[.]/g, '-')
|
|
332
|
-
const refid = `${pagePart}:::${fragment}`
|
|
333
|
-
return `<<${refid}${text && text !== targetPage.title ? ',' + text.replace(/\\]/g, ']') : ''}>>`
|
|
334
|
-
})
|
|
335
|
-
}
|
|
336
|
-
if (~line.indexOf('link:{attachmentsdir}/')) {
|
|
337
|
-
line = line.replace(/(?<![\\+])link:\{attachmentsdir\}\/([^\s[]+)\[(|.*?[^\\])\]/g, (m, relative, text) => {
|
|
338
|
-
const attachment =
|
|
339
|
-
siteUrl &&
|
|
340
|
-
contentCatalog.getById({
|
|
341
|
-
component: componentVersion.name,
|
|
342
|
-
version: componentVersion.version,
|
|
343
|
-
module: module_,
|
|
344
|
-
family: 'attachment',
|
|
345
|
-
relative,
|
|
346
|
-
})
|
|
347
|
-
// TODO: handle unresolved attachment page
|
|
348
|
-
return attachment?.out ? `${siteUrl}${attachment.pub.url.replace(/_/g, '{underscore}')}[${text}]` : m
|
|
349
|
-
})
|
|
350
|
-
}
|
|
351
|
-
if (~line.indexOf('image:') && !line.startsWith('image::')) {
|
|
352
|
-
line = line.replace(/(?<![\\+])image:([^:\s[](?:[^[]*[^\s[])?)\[([^\]]*)\]/g, (m, target, attrlist) => {
|
|
353
|
-
if (isResourceSpec(target)) {
|
|
354
|
-
const image = contentCatalog.resolveResource(target, page.src, 'image', ['image'])
|
|
355
|
-
// TODO: handle (or report) unresolved image better
|
|
356
|
-
if (image?.out) {
|
|
357
|
-
image.out.assembled = true
|
|
358
|
-
return `image:${image.out.path.replace(/_/g, '{underscore}')}[${attrlist}]`
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
return m
|
|
362
|
-
})
|
|
363
|
-
}
|
|
364
|
-
lines[idx] = line
|
|
365
|
-
}
|
|
366
|
-
// NOTE: need to do this last since it modifies the line numbers
|
|
367
|
-
// we could remap the line numbers to make them resilient
|
|
368
|
-
// or we could mark which lines to remove and filter them after
|
|
369
|
-
let lastImageMacroAt
|
|
370
|
-
;[...allBlocks].reverse().forEach((block) => {
|
|
371
|
-
const lineno = block.getLineNumber()
|
|
372
|
-
// NOTE: lineno is not defined for preamble
|
|
373
|
-
if (typeof lineno !== 'number') return
|
|
374
|
-
const context = block.getContext()
|
|
375
|
-
let idx = lineno - 1
|
|
376
|
-
if (context === 'section' && !block.getDocument().isNested()) {
|
|
377
|
-
if (block.getSectionName() === 'header') {
|
|
378
|
-
lines[idx] = undefined
|
|
379
|
-
return
|
|
380
|
-
}
|
|
381
|
-
let blockStyle = sectionMergeStrategy === 'discrete' ? 'discrete' : undefined
|
|
382
|
-
lines[idx] = lines[idx].replace(/^=+ (.+)/, (_, rest) => {
|
|
383
|
-
let targetMarkerLength = block.level + 1 + level + (enclosed ? 1 : 0)
|
|
384
|
-
if (targetMarkerLength > 6) {
|
|
385
|
-
targetMarkerLength = 6
|
|
386
|
-
blockStyle = 'discrete'
|
|
387
|
-
}
|
|
388
|
-
return '='.repeat(targetMarkerLength) + ' ' + rest
|
|
389
|
-
})
|
|
390
|
-
// NOTE: ID will be undefined if sectids are turned off
|
|
391
|
-
if (block.getId()) rewriteStyleAttribute(block, lines, idx, idprefix, blockStyle)
|
|
392
|
-
} else {
|
|
393
|
-
if (context === 'image') {
|
|
394
|
-
let line = lines[idx] || ''
|
|
395
|
-
let prefix = ''
|
|
396
|
-
// Q: can we use startsWith('image::') in certain cases?
|
|
397
|
-
let imageMacroOffset = (
|
|
398
|
-
lastImageMacroAt?.[0] === idx ? line.slice(0, lastImageMacroAt[1]) : line
|
|
399
|
-
).lastIndexOf('image::')
|
|
400
|
-
if (imageMacroOffset > 0) {
|
|
401
|
-
if (
|
|
402
|
-
block.getDocument().isNested() &&
|
|
403
|
-
(prefix = line.slice(0, imageMacroOffset)).trimRight().endsWith('|')
|
|
404
|
-
) {
|
|
405
|
-
line = line.slice(prefix.length)
|
|
406
|
-
} else {
|
|
407
|
-
imageMacroOffset = -1
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
if (imageMacroOffset >= 0) {
|
|
411
|
-
const target = block.getAttribute('target')
|
|
412
|
-
if (isResourceSpec(target)) {
|
|
413
|
-
const image = contentCatalog.resolveResource(target, page.src, 'image', ['image'])
|
|
414
|
-
// FIXME: handle (or report) case when image is not resolved
|
|
415
|
-
if (image?.out) {
|
|
416
|
-
const boxedAttrlist = line.slice(line.indexOf('['))
|
|
417
|
-
image.out.assembled = true
|
|
418
|
-
lines[idx] = `${prefix}image::${image.out.path}${boxedAttrlist}`
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
lastImageMacroAt = [idx, imageMacroOffset]
|
|
422
|
-
}
|
|
423
|
-
} else if (context === 'document' && block.hasHeader()) {
|
|
424
|
-
// nested document
|
|
425
|
-
idx = (block.getHeader().getLineNumber() || idx + 1) - 1
|
|
426
|
-
}
|
|
427
|
-
if (block.getId()) rewriteStyleAttribute(block, lines, idx, idprefix)
|
|
428
|
-
}
|
|
429
|
-
})
|
|
430
|
-
buffer.push(...lines.filter((it) => it !== undefined))
|
|
431
|
-
const attributeEntries = Object.entries(doc.attributes_defined_in_header || {})
|
|
432
|
-
if (attributeEntries.length) {
|
|
433
|
-
const resolvedAttributeEntries = attributeEntries.reduce(
|
|
434
|
-
(accum, [name, val]) => {
|
|
435
|
-
// Q: couldn't we just check if attribute is locked?
|
|
436
|
-
if (name in mutableAttributes) {
|
|
437
|
-
const initialVal = mutableAttributes[name]
|
|
438
|
-
if (initialVal == null) {
|
|
439
|
-
if (val != null) accum.push(`:!${name}:`)
|
|
440
|
-
} else if (val !== initialVal) {
|
|
441
|
-
accum.push(`:${name}:${initialVal ? ' ' + initialVal : ''}`)
|
|
442
|
-
}
|
|
443
|
-
} else if (
|
|
444
|
-
!(
|
|
445
|
-
val == null ||
|
|
446
|
-
doc.isAttributeLocked(name) ||
|
|
447
|
-
name === 'doctype' ||
|
|
448
|
-
name === 'leveloffset' ||
|
|
449
|
-
name === 'underscore'
|
|
450
|
-
)
|
|
451
|
-
) {
|
|
452
|
-
accum.push(`:!${name}:`)
|
|
453
|
-
}
|
|
454
|
-
return accum
|
|
455
|
-
},
|
|
456
|
-
['']
|
|
457
|
-
)
|
|
458
|
-
if (resolvedAttributeEntries.length > 1) buffer.push(...resolvedAttributeEntries)
|
|
459
|
-
}
|
|
460
|
-
} else if (level) {
|
|
461
|
-
if (level === 1 && navtitlePlain === componentVersion.title) {
|
|
462
|
-
level--
|
|
463
|
-
} else {
|
|
464
|
-
buffer.push('')
|
|
465
|
-
// NOTE: try to toggle sectids; otherwise, fallback to globally unique synthetic ID
|
|
466
|
-
// Q: should we unset docname, page-module, etc?
|
|
467
|
-
let toggleSectids, syntheticId
|
|
468
|
-
if (!('sectids' in asciidocConfig.attributes)) {
|
|
469
|
-
buffer.push(':!sectids:')
|
|
470
|
-
toggleSectids = true
|
|
471
|
-
} else if (typeof asciidocConfig.attributes.sectids === 'string') {
|
|
472
|
-
if ('sectids' in mutableAttributes) {
|
|
473
|
-
buffer.push(':!sectids:')
|
|
474
|
-
toggleSectids = true
|
|
475
|
-
} else {
|
|
476
|
-
syntheticId = `__object-id-${global.Opal.hash(outlineEntry).$object_id()}`
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
let sectionTitle = navtitleAsciiDoc
|
|
480
|
-
if (urlType === 'external') {
|
|
481
|
-
sectionTitle = `${url}[${navtitleAsciiDoc.replace(/\]/g, '\\]')}]`
|
|
482
|
-
} else if (urlType === 'internal' && !unresolved && siteUrl) {
|
|
483
|
-
const resource = contentCatalog.getFiles().find((it) => it.out && it.pub.url === url)
|
|
484
|
-
if (resource) sectionTitle = `${siteUrl}${resource.pub.url}[${navtitleAsciiDoc.replace(/\]/g, '\\]')}]`
|
|
485
|
-
}
|
|
486
|
-
let hlevel = level + 1
|
|
487
|
-
if (hlevel > 6) {
|
|
488
|
-
hlevel = 6
|
|
489
|
-
buffer.push(syntheticId ? `[discrete#${syntheticId}]` : '[discrete]')
|
|
490
|
-
} else if (syntheticId) {
|
|
491
|
-
buffer.push(`[#${syntheticId}]`)
|
|
492
|
-
}
|
|
493
|
-
buffer.push(`${'='.repeat(hlevel)} ${sectionTitle}`)
|
|
494
|
-
if (toggleSectids) buffer.push(':sectids:')
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
const nextLevel = level + 1
|
|
499
|
-
if (hasItems) {
|
|
500
|
-
// NOTE: drop first child if same as parent; should we keep if content is different?
|
|
501
|
-
;(urlType === 'internal' && urlType === items[0].urlType && url === items[0].url && !items[0].items
|
|
502
|
-
? items.slice(1)
|
|
503
|
-
: items
|
|
504
|
-
).forEach((item) => {
|
|
505
|
-
buffer.push(
|
|
506
|
-
...aggregateAsciiDoc(
|
|
507
|
-
loadAsciiDoc,
|
|
508
|
-
contentCatalog,
|
|
509
|
-
header,
|
|
510
|
-
componentVersion,
|
|
511
|
-
item,
|
|
512
|
-
pagesInOutline,
|
|
513
|
-
asciidocConfig,
|
|
514
|
-
mutableAttributes,
|
|
515
|
-
sectionMergeStrategy,
|
|
516
|
-
lastComponentVersion,
|
|
517
|
-
nextLevel
|
|
518
|
-
)
|
|
519
|
-
)
|
|
520
|
-
})
|
|
521
|
-
}
|
|
522
|
-
return buffer
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function generateSlug (title) {
|
|
526
|
-
return title
|
|
527
|
-
.toLowerCase()
|
|
528
|
-
.replace(/&.+?;|[^ \p{Alpha}0-9_\-.]/gu, '')
|
|
529
|
-
.replace(/[ _.]/g, '-')
|
|
530
|
-
.replace(/--+/g, '-')
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
function fixSectionLevels (sections, multipart) {
|
|
534
|
-
sections.forEach((sect) => {
|
|
535
|
-
const targetLevel = sect.getParent().getLevel() + 1
|
|
536
|
-
if (multipart ? sect.getLevel() > targetLevel : sect.getLevel() !== targetLevel) sect.level = targetLevel
|
|
537
|
-
if (sect.hasSections()) fixSectionLevels(sect.getSections(), multipart)
|
|
538
|
-
})
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
function rewriteStyleAttribute (block, lines, idx, idprefix, replacementStyle = '') {
|
|
542
|
-
let prevLine = lines[idx - 1]
|
|
543
|
-
const char0 = prevLine?.charAt()
|
|
544
|
-
if (char0) {
|
|
545
|
-
if (
|
|
546
|
-
(char0 === '.' && /^\.\.?[^ \t.]/.test(prevLine)) ||
|
|
547
|
-
(char0 === '[' &&
|
|
548
|
-
prevLine.charAt(1) === '[' &&
|
|
549
|
-
/^\[\[(?:|[\p{Alpha}_:][\p{Alpha}0-9_\-:.]*(?:, *.+)?)\]\]$/u.test(prevLine))
|
|
550
|
-
) {
|
|
551
|
-
return rewriteStyleAttribute(block, lines, idx - 1, idprefix, replacementStyle)
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
let cellSpec
|
|
555
|
-
if (
|
|
556
|
-
char0 &&
|
|
557
|
-
(char0 === '[' || (block.getDocument().isNested() && (cellSpec = prevLine.match(/^([^[|]*)\| *(\[.+)/)))) &&
|
|
558
|
-
prevLine.charAt(prevLine.length - 1) === ']'
|
|
559
|
-
) {
|
|
560
|
-
if (cellSpec) {
|
|
561
|
-
prevLine = cellSpec[2]
|
|
562
|
-
cellSpec = cellSpec[1]
|
|
563
|
-
}
|
|
564
|
-
let rawStyle
|
|
565
|
-
const commaIdx = prevLine.indexOf(',')
|
|
566
|
-
if (~commaIdx) {
|
|
567
|
-
rawStyle = prevLine.slice(1, commaIdx - 1)
|
|
568
|
-
if (~rawStyle.indexOf('=')) rawStyle = undefined
|
|
569
|
-
} else if (!~prevLine.indexOf('=')) {
|
|
570
|
-
rawStyle = prevLine.slice(1, prevLine.length - 2)
|
|
571
|
-
}
|
|
572
|
-
if (rawStyle) {
|
|
573
|
-
if (~rawStyle.indexOf('#')) {
|
|
574
|
-
prevLine = prevLine.replace(/#[^.%,\]]+/, `#${idprefix}${block.getId()}`)
|
|
575
|
-
if (replacementStyle) prevLine = prevLine.replace(/^[^#.%,\]]+/, `[${replacementStyle}`)
|
|
576
|
-
} else {
|
|
577
|
-
prevLine = `[${
|
|
578
|
-
replacementStyle ? rawStyle.replace(/^[^.%]*/, replacementStyle) : rawStyle
|
|
579
|
-
}#${idprefix}${block.getId()}${prevLine.slice(rawStyle.length + 1)}`
|
|
580
|
-
}
|
|
581
|
-
} else {
|
|
582
|
-
prevLine = `[${replacementStyle}#${idprefix}${block.getId()}${rawStyle == null ? ',' : ''}${prevLine.slice(1)}`
|
|
583
|
-
}
|
|
584
|
-
if (cellSpec) prevLine = `${cellSpec}|${prevLine}`
|
|
585
|
-
lines[idx - 1] = prevLine
|
|
586
|
-
} else {
|
|
587
|
-
lines.splice(idx, 0, `[${replacementStyle}#${idprefix}${block.getId()}]`)
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
function isResourceSpec (str) {
|
|
592
|
-
return !(~str.indexOf(':') && (~str.indexOf('://') || (str.startsWith('data:') && ~str.indexOf(','))))
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
function getObjectId (obj) {
|
|
596
|
-
return global.Opal.uid()
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
module.exports = produceAggregateDocument
|