@antora/assembler 1.0.0-alpha.9 → 1.0.0-beta.2
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/lib/assemble-content.js +214 -26
- package/lib/configure.js +96 -0
- package/lib/index.js +2 -2
- package/lib/load-config.js +41 -24
- package/lib/{produce-aggregate-document.js → produce-assembly-file.js} +309 -153
- package/lib/produce-assembly-files.js +146 -0
- package/lib/util/compute-out.js +57 -0
- package/lib/util/create-asciidoc-file.js +18 -0
- package/package.json +15 -13
- package/lib/asciidoctor/reducer-extension.js +0 -235
- package/lib/produce-aggregate-documents.js +0 -145
- package/lib/util/run-command.js +0 -60
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const createAsciiDocFile = require('./util/create-asciidoc-file')
|
|
4
|
+
const filterComponentVersions = require('./filter-component-versions')
|
|
5
|
+
const produceAssemblyFile = require('./produce-assembly-file')
|
|
6
|
+
const selectMutableAttributes = require('./select-mutable-attributes')
|
|
7
|
+
|
|
8
|
+
const IMAGE_MACRO_RX = /^image::?(.+?)\[(.*?)\]$/
|
|
9
|
+
|
|
10
|
+
function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, resolveAssemblyModel) {
|
|
11
|
+
const { asciidoc: assemblerAsciiDocConfig, assembly: assemblyConfig } = assemblerConfig
|
|
12
|
+
resolveAssemblyModel ??= (componentVersion) => ({
|
|
13
|
+
doctype: assemblyConfig.doctype,
|
|
14
|
+
insertStartPage: assemblyConfig.insertStartPage,
|
|
15
|
+
rootLevel: assemblyConfig.rootLevel,
|
|
16
|
+
sectionMergeStrategy: assemblyConfig.sectionMergeStrategy,
|
|
17
|
+
navigation: componentVersion.navigation,
|
|
18
|
+
xmlIds: assemblyConfig.xmlIds,
|
|
19
|
+
})
|
|
20
|
+
const assemblerAsciiDocAttributes = Object.assign({}, assemblerAsciiDocConfig.attributes)
|
|
21
|
+
const { revdate, 'source-highlighter': sourceHighlighter } = assemblerAsciiDocAttributes
|
|
22
|
+
delete assemblerAsciiDocAttributes.revdate
|
|
23
|
+
delete assemblerAsciiDocAttributes['source-highlighter']
|
|
24
|
+
const publishableFiles = contentCatalog.getFiles().filter((file) => file.out)
|
|
25
|
+
return filterComponentVersions(contentCatalog.getComponents(), assemblerConfig.componentVersionFilter.names).reduce(
|
|
26
|
+
(accum, componentVersion) => {
|
|
27
|
+
const assemblyModel = resolveAssemblyModel(componentVersion)
|
|
28
|
+
if (!assemblyModel.navigation) return accum
|
|
29
|
+
const { name: componentName, version, title } = componentVersion
|
|
30
|
+
const componentVersionAsciiDocConfig = getAsciiDocConfigWithAsciidoctorReducerExtension(componentVersion)
|
|
31
|
+
const mergedAsciiDocConfig = Object.assign({}, componentVersionAsciiDocConfig, {
|
|
32
|
+
attributes: Object.assign({ revdate }, componentVersionAsciiDocConfig.attributes, assemblerAsciiDocAttributes),
|
|
33
|
+
})
|
|
34
|
+
const mergedAsciiDocAttributes = mergedAsciiDocConfig.attributes
|
|
35
|
+
const auxiliaryImages = new Set()
|
|
36
|
+
Object.entries(mergedAsciiDocAttributes).forEach(([name, val]) => {
|
|
37
|
+
const match = name.endsWith('-image') && val.startsWith('image:') && IMAGE_MACRO_RX.exec(val)
|
|
38
|
+
if (!(match && isResourceRef(match[1]))) return
|
|
39
|
+
// Q should we allow image to be resolved relative to component version?
|
|
40
|
+
const image = contentCatalog.resolveResource(match[1], undefined, 'image', ['image'])
|
|
41
|
+
if (!image?.out) return
|
|
42
|
+
mergedAsciiDocAttributes[name] = `image:${image.out.path}[${match[2]}]`
|
|
43
|
+
auxiliaryImages.add(image)
|
|
44
|
+
})
|
|
45
|
+
const rootEntry = { content: title }
|
|
46
|
+
const { navigation, rootLevel } = assemblyModel
|
|
47
|
+
let startPage =
|
|
48
|
+
'startPage' in componentVersion
|
|
49
|
+
? componentVersion.startPage
|
|
50
|
+
: contentCatalog.resolvePage('index.adoc', { component: componentName, version })
|
|
51
|
+
if (startPage && startPage.src.component === componentName && startPage.src.version === version) {
|
|
52
|
+
if (assemblyModel.insertStartPage) {
|
|
53
|
+
const navtitle = startPage.asciidoc?.navtitle || rootEntry.content
|
|
54
|
+
Object.assign(rootEntry, { navtitle, url: startPage.pub.url, urlType: 'internal' })
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
// Q: should we always use a reference page as startPage for computing mutableAttributes?
|
|
58
|
+
startPage = createAsciiDocFile(contentCatalog, {
|
|
59
|
+
src: {
|
|
60
|
+
component: componentVersion.name,
|
|
61
|
+
version: componentVersion.version,
|
|
62
|
+
module: 'ROOT',
|
|
63
|
+
family: 'page',
|
|
64
|
+
relative: '.start-page.adoc',
|
|
65
|
+
origin: (componentVersion.origins || [])[0],
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
const mutableAttributes = selectMutableAttributes(loadAsciiDoc, contentCatalog, startPage, mergedAsciiDocConfig)
|
|
70
|
+
delete mutableAttributes.doctype // Q: should this be in selectMutableAttributes?
|
|
71
|
+
prepareOutlines(navigation, rootEntry, rootLevel).reduce((any, outline) => {
|
|
72
|
+
const assemblyFile = produceAssemblyFile(
|
|
73
|
+
loadAsciiDoc,
|
|
74
|
+
contentCatalog,
|
|
75
|
+
componentVersion,
|
|
76
|
+
outline,
|
|
77
|
+
publishableFiles,
|
|
78
|
+
mergedAsciiDocConfig,
|
|
79
|
+
mutableAttributes,
|
|
80
|
+
assemblyModel
|
|
81
|
+
)
|
|
82
|
+
if (!assemblyFile) return any
|
|
83
|
+
if (!any && auxiliaryImages.size) {
|
|
84
|
+
const assembledAssets = assemblyFile.assembler.assembled.assets
|
|
85
|
+
auxiliaryImages.forEach((asset) => assembledAssets.add(asset))
|
|
86
|
+
}
|
|
87
|
+
accum.push(assemblyFile)
|
|
88
|
+
return true
|
|
89
|
+
}, false)
|
|
90
|
+
mergedAsciiDocAttributes.doctype = assemblyModel.doctype
|
|
91
|
+
sourceHighlighter
|
|
92
|
+
? (mergedAsciiDocAttributes['source-highlighter'] = sourceHighlighter)
|
|
93
|
+
: delete mergedAsciiDocAttributes['source-highlighter']
|
|
94
|
+
return accum
|
|
95
|
+
},
|
|
96
|
+
[]
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getAsciiDocConfigWithAsciidoctorReducerExtension (componentVersion) {
|
|
101
|
+
const asciidoctorReducerExtension = require('@asciidoctor/reducer') // NOTE: must be required lazily
|
|
102
|
+
const asciidocConfig = componentVersion.asciidoc
|
|
103
|
+
const extensions = [asciidoctorReducerExtension]
|
|
104
|
+
const configuredExtensions = asciidocConfig.extensions || []
|
|
105
|
+
if (!configuredExtensions.length) return Object.assign({}, asciidocConfig, { extensions, sourcemap: true })
|
|
106
|
+
return Object.assign({}, asciidocConfig, {
|
|
107
|
+
extensions: configuredExtensions.reduce((accum, candidate) => {
|
|
108
|
+
if (candidate !== asciidoctorReducerExtension) accum.push(candidate)
|
|
109
|
+
return accum
|
|
110
|
+
}, extensions),
|
|
111
|
+
sourcemap: true,
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function includedInNav (items, url) {
|
|
116
|
+
return items.find((it) => it.url === url || includedInNav(it.items || [], url))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isResourceRef (target) {
|
|
120
|
+
return ~target.indexOf(':') && !(~target.indexOf('://') || (target.startsWith('data:') && ~target.indexOf(',')))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function prepareOutlines (navigation, rootEntry, rootLevel) {
|
|
124
|
+
const singleEntry = navigation.length === 1
|
|
125
|
+
const items = navigation.reduce((accum, it) => {
|
|
126
|
+
if (!it.content || (singleEntry && it.url ? it.url === rootEntry.url : it.content === rootEntry.content)) {
|
|
127
|
+
accum.push(...it.items)
|
|
128
|
+
} else {
|
|
129
|
+
accum.push(it)
|
|
130
|
+
}
|
|
131
|
+
return accum
|
|
132
|
+
}, [])
|
|
133
|
+
if (rootLevel === 0) {
|
|
134
|
+
if (rootEntry.url && includedInNav(items, rootEntry.url)) {
|
|
135
|
+
for (const p of ['url', 'urlType']) delete rootEntry[p]
|
|
136
|
+
}
|
|
137
|
+
return [Object.assign(rootEntry, { index: true, items })]
|
|
138
|
+
}
|
|
139
|
+
if (rootEntry.url && !includedInNav(items, rootEntry.url)) {
|
|
140
|
+
rootEntry.content = singleEntry && rootEntry.url === navigation[0].url ? navigation[0].content : rootEntry.navtitle
|
|
141
|
+
items.unshift(rootEntry)
|
|
142
|
+
}
|
|
143
|
+
return items
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = produceAssemblyFiles
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { posix: path } = require('node:path')
|
|
4
|
+
|
|
5
|
+
function computeOut (src) {
|
|
6
|
+
const { component, version, module: module_, family, relative } = src
|
|
7
|
+
const outRelative = family === 'page' ? relative.replace(/\.adoc$/, '.html') : relative
|
|
8
|
+
const { dir: dirname, base: basename, ext: extname, name: stem } = path.parse(outRelative)
|
|
9
|
+
const componentVersion = this.getComponentVersion(component, version)
|
|
10
|
+
const versionSegment =
|
|
11
|
+
'activeVersionSegment' in componentVersion
|
|
12
|
+
? componentVersion.activeVersionSegment
|
|
13
|
+
: resolveActiveVersionSegment.call(this, component, version)
|
|
14
|
+
const outDirSegments = []
|
|
15
|
+
const moduleRootPathSegments = []
|
|
16
|
+
if (component !== 'ROOT') outDirSegments.push(component)
|
|
17
|
+
if (versionSegment) outDirSegments.push(versionSegment)
|
|
18
|
+
if (module_ !== 'ROOT') outDirSegments.push(module_)
|
|
19
|
+
const outModuleDirSegments = outDirSegments.slice()
|
|
20
|
+
if (family !== 'page') {
|
|
21
|
+
outDirSegments.push(`_${family}s`)
|
|
22
|
+
moduleRootPathSegments.push('..')
|
|
23
|
+
}
|
|
24
|
+
if (dirname) {
|
|
25
|
+
outDirSegments.push(dirname)
|
|
26
|
+
for (const _ of dirname.split('/')) moduleRootPathSegments.push('..')
|
|
27
|
+
}
|
|
28
|
+
const rootPathSegments = moduleRootPathSegments.slice()
|
|
29
|
+
for (const _ of outModuleDirSegments) rootPathSegments.push('..')
|
|
30
|
+
const outDirname = outDirSegments.join('/')
|
|
31
|
+
const result = {
|
|
32
|
+
dirname: outDirname,
|
|
33
|
+
basename,
|
|
34
|
+
path: outDirname + '/' + basename,
|
|
35
|
+
moduleRootPath: moduleRootPathSegments.length ? moduleRootPathSegments.join('/') : '.',
|
|
36
|
+
rootPath: rootPathSegments.length ? rootPathSegments.join('/') : '.',
|
|
37
|
+
}
|
|
38
|
+
return result
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function resolveActiveVersionSegment (component, version) {
|
|
42
|
+
let startPage = this.resolvePage('index.adoc', { component, version })
|
|
43
|
+
let startPageSrc
|
|
44
|
+
if (startPage) {
|
|
45
|
+
startPageSrc = startPage.src
|
|
46
|
+
} else {
|
|
47
|
+
startPageSrc = { component, version, module: 'ROOT', family: 'page', relative: 'index.adoc' }
|
|
48
|
+
this.removeFile((startPage = this.addFile({ src: startPageSrc })))
|
|
49
|
+
}
|
|
50
|
+
const outPathSegments = startPage.out.path.split('/')
|
|
51
|
+
for (const depth of startPage.out.moduleRootPath.split('/')) outPathSegments.pop()
|
|
52
|
+
if (startPageSrc.module !== 'ROOT') outPathSegments.pop()
|
|
53
|
+
if (startPageSrc.component !== 'ROOT') outPathSegments.shift()
|
|
54
|
+
return outPathSegments.length ? outPathSegments[0] : ''
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = computeOut
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const computeOut = require('./compute-out')
|
|
4
|
+
const { posix: path } = require('node:path')
|
|
5
|
+
|
|
6
|
+
function createAsciiDocFile (contentCatalog, file) {
|
|
7
|
+
file.mediaType = 'text/asciidoc'
|
|
8
|
+
const src = file.src
|
|
9
|
+
const out = computeOut.call(contentCatalog, src)
|
|
10
|
+
if (src.family === 'export') {
|
|
11
|
+
contentCatalog.removeFile((file = contentCatalog.addFile(Object.assign(file, { path: out.path, out: null }))))
|
|
12
|
+
return file
|
|
13
|
+
}
|
|
14
|
+
const pub = { url: '/' + out.path, moduleRootPath: out.moduleRootPath, rootPath: out.rootPath }
|
|
15
|
+
return { contents: src.contents ?? Buffer.alloc(0), src, out, pub }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = createAsciiDocFile
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antora/assembler",
|
|
3
|
-
"version": "1.0.0-
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.0-beta.2",
|
|
4
|
+
"description": "A JavaScript library that merges AsciiDoc content from multiple pages in an Antora site into assembly files and delegates to an exporter to convert those files to another format, such as PDF.",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"author": "OpenDevise Inc. (https://opendevise.com)",
|
|
7
7
|
"contributors": [
|
|
@@ -9,37 +9,42 @@
|
|
|
9
9
|
"Sarah White <sarah@opendevise.com>"
|
|
10
10
|
],
|
|
11
11
|
"homepage": "https://antora.org",
|
|
12
|
-
"repository":
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://gitlab.com/antora/antora-assembler.git",
|
|
15
|
+
"directory": "packages/assembler"
|
|
16
|
+
},
|
|
13
17
|
"bugs": {
|
|
14
18
|
"url": "https://gitlab.com/antora/antora-assembler/issues"
|
|
15
19
|
},
|
|
16
20
|
"scripts": {
|
|
17
|
-
"test": "
|
|
21
|
+
"test": "node --test test/*-test.js",
|
|
18
22
|
"prepublishOnly": "npx -y downdoc --prepublish",
|
|
19
23
|
"postpublish": "npx -y downdoc --postpublish"
|
|
20
24
|
},
|
|
21
25
|
"main": "lib/index.js",
|
|
22
26
|
"exports": {
|
|
23
27
|
".": "./lib/index.js",
|
|
24
|
-
"./asciidoctor/reducer-extension": "./lib/asciidoctor/reducer-extension.js",
|
|
25
28
|
"./filter-component-versions": "./lib/filter-component-versions.js",
|
|
26
29
|
"./load-config": "./lib/load-config.js",
|
|
27
|
-
"./produce-
|
|
28
|
-
"./produce-
|
|
30
|
+
"./produce-assembly-file": "./lib/produce-assembly-file.js",
|
|
31
|
+
"./produce-assembly-files": "./lib/produce-assembly-files.js",
|
|
29
32
|
"./select-mutable-attributes": "./lib/select-mutable-attributes.js"
|
|
30
33
|
},
|
|
31
34
|
"imports": {
|
|
35
|
+
"#run-command": "@antora/run-command-helper",
|
|
32
36
|
"#unconvert-inline-asciidoc": "./lib/util/unconvert-inline-asciidoc.js"
|
|
33
37
|
},
|
|
34
38
|
"dependencies": {
|
|
35
|
-
"@
|
|
39
|
+
"@asciidoctor/reducer": "~1.1",
|
|
40
|
+
"@antora/expand-path-helper": "~3.0",
|
|
36
41
|
"braces": "~3.0",
|
|
37
42
|
"picomatch": "~3.0",
|
|
38
|
-
"vinyl": "~2.2",
|
|
39
43
|
"js-yaml": "~4.1"
|
|
40
44
|
},
|
|
41
45
|
"devDependencies": {
|
|
42
46
|
"@antora/asciidoc-loader": "~3.1",
|
|
47
|
+
"@antora/navigation-builder": "~3.1",
|
|
43
48
|
"@antora/site-publisher": "~3.1"
|
|
44
49
|
},
|
|
45
50
|
"engines": {
|
|
@@ -53,8 +58,5 @@
|
|
|
53
58
|
"antora-extension",
|
|
54
59
|
"asciidoc",
|
|
55
60
|
"documentation"
|
|
56
|
-
]
|
|
57
|
-
"publishConfig": {
|
|
58
|
-
"access": "public"
|
|
59
|
-
}
|
|
61
|
+
]
|
|
60
62
|
}
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const Opal = global.Opal
|
|
4
|
-
const Asciidoctor = Opal.Asciidoctor
|
|
5
|
-
const $Reducer = function $Reducer () {}
|
|
6
|
-
|
|
7
|
-
const DocumentExt = (() => {
|
|
8
|
-
const parentScope = Opal.module(Asciidoctor, 'Reducer', $Reducer)
|
|
9
|
-
const scope = Opal.module(parentScope, 'DocumentExt', function $DocumentExt () {})
|
|
10
|
-
|
|
11
|
-
Opal.defn(scope, '$save_attributes', function saveAttributes () {
|
|
12
|
-
this.attributes_defined_in_header = this.attributes_modified.$to_a().reduce((accum, name) => {
|
|
13
|
-
accum[name] = this.getAttribute(name)
|
|
14
|
-
return accum
|
|
15
|
-
}, {})
|
|
16
|
-
return Opal.send(this, Opal.find_super_dispatcher(this, 'save_attributes', saveAttributes), [])
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
return scope
|
|
20
|
-
})()
|
|
21
|
-
|
|
22
|
-
const ConditionalDirectiveTracker = (() => {
|
|
23
|
-
const parentScope = Opal.module(Asciidoctor, 'Reducer', $Reducer)
|
|
24
|
-
const scope = Opal.module(parentScope, 'ConditionalDirectiveTracker', function $ConditionalDirectiveTracker () {})
|
|
25
|
-
|
|
26
|
-
Opal.defn(
|
|
27
|
-
scope,
|
|
28
|
-
'$preprocess_conditional_directive',
|
|
29
|
-
function preprocessConditionalDirective (keyword, target, delimiter, text) {
|
|
30
|
-
const skipActive = this.skipping
|
|
31
|
-
const depth = this.conditional_stack.length
|
|
32
|
-
let directiveLineno = this.lineno
|
|
33
|
-
const result = Opal.send(
|
|
34
|
-
this,
|
|
35
|
-
Opal.find_super_dispatcher(this, 'preprocess_conditional_directive', preprocessConditionalDirective),
|
|
36
|
-
[keyword, target, delimiter, text]
|
|
37
|
-
)
|
|
38
|
-
if (this.skipping && skipActive) return result
|
|
39
|
-
const currIncReplacement = this.includeReplacements.$current()
|
|
40
|
-
const drop = currIncReplacement.drop || (currIncReplacement.drop = [])
|
|
41
|
-
directiveLineno -= currIncReplacement.offset || 0
|
|
42
|
-
const depthChange = this.conditional_stack.length - depth
|
|
43
|
-
if (depthChange < 0) {
|
|
44
|
-
if (skipActive) {
|
|
45
|
-
for (let n = drop.pop(); n <= directiveLineno; n++) drop.push(n)
|
|
46
|
-
} else {
|
|
47
|
-
drop.push(directiveLineno)
|
|
48
|
-
}
|
|
49
|
-
} else if (depthChange > 0 || directiveLineno === this.lineno) {
|
|
50
|
-
drop.push(directiveLineno)
|
|
51
|
-
} else {
|
|
52
|
-
drop.push([directiveLineno, text])
|
|
53
|
-
}
|
|
54
|
-
return result
|
|
55
|
-
}
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
return scope
|
|
59
|
-
})()
|
|
60
|
-
|
|
61
|
-
const CurrentPosition = (() => {
|
|
62
|
-
const parentScope = Opal.module(Opal.Asciidoctor, 'Reducer', $Reducer)
|
|
63
|
-
const scope = Opal.module(parentScope, 'CurrentPosition', function $CurrentPosition () {})
|
|
64
|
-
|
|
65
|
-
Opal.defn(Opal.get_singleton_class(scope), '$extended', function $extended (instance) {
|
|
66
|
-
instance.$to_end()
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
Opal.defn(scope, '$current', function $current () {
|
|
70
|
-
return this[this.pointer]
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
Opal.defn(scope, '$to_end', function $toEnd () {
|
|
74
|
-
this.pointer = this.length - 1
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
Opal.defn(scope, '$up', function $up () {
|
|
78
|
-
this.pointer = this.$current().into
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
return scope
|
|
82
|
-
})()
|
|
83
|
-
|
|
84
|
-
const IncludeDirectiveTracker = (() => {
|
|
85
|
-
const parentScope = Opal.module(Opal.Asciidoctor, 'Reducer', $Reducer)
|
|
86
|
-
const scope = Opal.module(parentScope, 'IncludeDirectiveTracker', function $IncludeDirectiveTracker () {})
|
|
87
|
-
|
|
88
|
-
Opal.defn(Opal.get_singleton_class(scope), '$extended', function $extended (instance) {
|
|
89
|
-
instance.includeReplacements = [{}].$extend(CurrentPosition)
|
|
90
|
-
instance.$$reducer = {}
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
Opal.defn(scope, '$preprocess_include_directive', function preprocessIncludeDirective (target, attrlist) {
|
|
94
|
-
this.$$reducer.includeDirectiveLine = `include::${target}[${attrlist}]`
|
|
95
|
-
this.$$reducer.includePushed = false
|
|
96
|
-
const directiveLineno = this.lineno // we're currently on the include line, which is 1-based
|
|
97
|
-
const result = Opal.send(
|
|
98
|
-
this,
|
|
99
|
-
Opal.find_super_dispatcher(this, 'preprocess_include_directive', preprocessIncludeDirective),
|
|
100
|
-
[target, attrlist]
|
|
101
|
-
)
|
|
102
|
-
if (!this.$$reducer.includePushed) {
|
|
103
|
-
let ln = this.peekLine(true)
|
|
104
|
-
let unresolved
|
|
105
|
-
if (
|
|
106
|
-
ln &&
|
|
107
|
-
ln.charAt(ln.length - 1) === ']' &&
|
|
108
|
-
!(unresolved = ln.startsWith('Unresolved directive in ')) &&
|
|
109
|
-
directiveLineno === this.lineno &&
|
|
110
|
-
(unresolved = ln.startsWith('link:'))
|
|
111
|
-
) {
|
|
112
|
-
ln = `${ln.slice(0, ln.length - 1)}role=include]`
|
|
113
|
-
}
|
|
114
|
-
pushIncludeReplacement.call(this, directiveLineno, unresolved ? [ln] : [], 0, unresolved)
|
|
115
|
-
}
|
|
116
|
-
this.$$reducer = {}
|
|
117
|
-
return result
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
Opal.defn(scope, '$push_include', function pushInclude (data, file, path, lineno, attrs) {
|
|
121
|
-
this.$$reducer.includePushed = true
|
|
122
|
-
const directiveLineno = this.lineno - 1 // we're below the include line, which is 1-based
|
|
123
|
-
const prevIncDepth = this.include_stack.length
|
|
124
|
-
let offset = lineno > 1 ? lineno - 1 : 0
|
|
125
|
-
const result = Opal.send(this, Opal.find_super_dispatcher(this, 'push_include', pushInclude), [
|
|
126
|
-
data,
|
|
127
|
-
file,
|
|
128
|
-
path,
|
|
129
|
-
lineno,
|
|
130
|
-
attrs,
|
|
131
|
-
])
|
|
132
|
-
let incLines = []
|
|
133
|
-
if (this.include_stack.length > prevIncDepth) {
|
|
134
|
-
incLines = this.$lines()
|
|
135
|
-
if (attrs['$key?']('leveloffset') && incLines[0].startsWith(':leveloffset: ') && incLines[1] === '') offset -= 2
|
|
136
|
-
}
|
|
137
|
-
pushIncludeReplacement.call(this, directiveLineno, incLines, offset)
|
|
138
|
-
return result
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
Opal.defn(scope, '$pop_include', function popInclude () {
|
|
142
|
-
if (!this.$$reducer.includePushed) this.includeReplacements.$up()
|
|
143
|
-
return Opal.send(this, Opal.find_super_dispatcher(this, 'pop_include', popInclude), [])
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
function pushIncludeReplacement (lineno, lines, offset, unresolved) {
|
|
147
|
-
const incReplacements = this.includeReplacements
|
|
148
|
-
const into = incReplacements.pointer
|
|
149
|
-
const line = this.$$reducer.includeDirectiveLine
|
|
150
|
-
incReplacements.push({ into, lineno: lineno - (incReplacements.$current().offset || 0), line, lines, offset })
|
|
151
|
-
if (!unresolved && lines.length) incReplacements.$to_end()
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return scope
|
|
155
|
-
})()
|
|
156
|
-
|
|
157
|
-
function preprocessor () {
|
|
158
|
-
this.process((doc, reader) =>
|
|
159
|
-
doc.getOptions().preserve_conditionals
|
|
160
|
-
? reader.$extend(IncludeDirectiveTracker)
|
|
161
|
-
: reader.$extend(ConditionalDirectiveTracker, IncludeDirectiveTracker)
|
|
162
|
-
)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function treeProcessor () {
|
|
166
|
-
this.process((doc) => {
|
|
167
|
-
const incReplacements = doc.reader.includeReplacements
|
|
168
|
-
if (incReplacements.length > 1 || (incReplacements[0].drop || []).length) {
|
|
169
|
-
const sourceLines = doc.getSourceLines()
|
|
170
|
-
incReplacements[0].lines = sourceLines.slice()
|
|
171
|
-
incReplacements
|
|
172
|
-
.slice()
|
|
173
|
-
.reverse()
|
|
174
|
-
.forEach(({ into, lineno, lines, line, drop }) => {
|
|
175
|
-
let targetLines, idx
|
|
176
|
-
if (into != null) {
|
|
177
|
-
targetLines = incReplacements[into].lines
|
|
178
|
-
// adds extra assurance that the program is replacing the correct line
|
|
179
|
-
if (targetLines[(idx = lineno - 1)] !== line) {
|
|
180
|
-
const msg = `include directive to reduce not found; expected: "${line}"; got: "${targetLines[idx]}"`
|
|
181
|
-
doc.getLogger().error(msg)
|
|
182
|
-
return
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
if ((drop || []).length) {
|
|
186
|
-
drop
|
|
187
|
-
.slice()
|
|
188
|
-
.reverse()
|
|
189
|
-
.forEach((dropIt) => {
|
|
190
|
-
Array.isArray(dropIt) ? (lines[dropIt[0] - 1] = dropIt[1]) : lines.splice(dropIt - 1, 1)
|
|
191
|
-
})
|
|
192
|
-
}
|
|
193
|
-
if (targetLines) targetLines[idx] = lines
|
|
194
|
-
})
|
|
195
|
-
const reducedSourceLines = flattenDeep(incReplacements[0].lines)
|
|
196
|
-
if (doc.getSourcemap()) {
|
|
197
|
-
const logger = Asciidoctor.LoggerManager.getLogger()
|
|
198
|
-
const opts = Object.assign(doc.getOptions(), { logger: undefined, parse: false, reduced: true })
|
|
199
|
-
if (opts.extension_registry) {
|
|
200
|
-
opts.extension_registry = Asciidoctor.Extensions.Registry.$new(opts.extension_registry.groups)
|
|
201
|
-
}
|
|
202
|
-
const includes = doc.getCatalog().includes
|
|
203
|
-
doc = Asciidoctor.load(reducedSourceLines, opts)
|
|
204
|
-
doc.catalog.$send('[]=', 'includes', includes)
|
|
205
|
-
doc.parse()
|
|
206
|
-
Asciidoctor.LoggerManager.setLogger(logger)
|
|
207
|
-
} else {
|
|
208
|
-
while (reducedSourceLines[reducedSourceLines.length - 1] === '') reducedSourceLines.pop()
|
|
209
|
-
sourceLines.splice(0, sourceLines.length, ...reducedSourceLines)
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return doc
|
|
213
|
-
})
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function flattenDeep (array, accum = []) {
|
|
217
|
-
const len = array.length
|
|
218
|
-
for (let i = 0, it; i < len; i++) Array.isArray((it = array[i])) ? flattenDeep(it, accum) : accum.push(it)
|
|
219
|
-
return accum
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function toProc (fn) {
|
|
223
|
-
return Object.defineProperty(fn, '$$arity', { value: fn.length })
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
module.exports.register = (registry) => {
|
|
227
|
-
const extGroup = toProc(function () {
|
|
228
|
-
const doc = this.document
|
|
229
|
-
doc.$extend(DocumentExt)
|
|
230
|
-
if (doc.getOptions().reduced) return
|
|
231
|
-
this.preprocessor(preprocessor)
|
|
232
|
-
this.treeProcessor(treeProcessor)
|
|
233
|
-
})
|
|
234
|
-
registry.groups.$send('[]=', 'reducer', extGroup)
|
|
235
|
-
}
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const File = require('vinyl')
|
|
4
|
-
const filterComponentVersions = require('./filter-component-versions')
|
|
5
|
-
const produceAggregateDocument = require('./produce-aggregate-document')
|
|
6
|
-
const selectMutableAttributes = require('./select-mutable-attributes')
|
|
7
|
-
|
|
8
|
-
const IMAGE_MACRO_RX = /^image::?(.+?)\[(.*?)\]$/
|
|
9
|
-
|
|
10
|
-
function produceAggregateDocuments (loadAsciiDoc, contentCatalog, assemblerConfig) {
|
|
11
|
-
const { insertStartPage, rootLevel, sectionMergeStrategy, asciidoc: assemblerAsciiDocConfig } = assemblerConfig
|
|
12
|
-
const assemblerAsciiDocAttributes = Object.assign({}, assemblerAsciiDocConfig.attributes)
|
|
13
|
-
const { doctype, revdate, 'source-highlighter': sourceHighlighter } = assemblerAsciiDocAttributes
|
|
14
|
-
delete assemblerAsciiDocAttributes.doctype
|
|
15
|
-
delete assemblerAsciiDocAttributes.revdate
|
|
16
|
-
delete assemblerAsciiDocAttributes['source-highlighter']
|
|
17
|
-
return filterComponentVersions(contentCatalog.getComponents(), assemblerConfig.componentVersions).reduce(
|
|
18
|
-
(accum, componentVersion) => {
|
|
19
|
-
const { name: componentName, version, title, navigation } = componentVersion
|
|
20
|
-
if (!navigation) return accum
|
|
21
|
-
const componentVersionAsciiDocConfig = getAsciiDocConfigWithAsciidoctorReducerExtension(componentVersion)
|
|
22
|
-
const mergedAsciiDocConfig = Object.assign({}, componentVersionAsciiDocConfig, {
|
|
23
|
-
attributes: Object.assign({ revdate }, componentVersionAsciiDocConfig.attributes, assemblerAsciiDocAttributes),
|
|
24
|
-
})
|
|
25
|
-
const mergedAsciiDocAttributes = mergedAsciiDocConfig.attributes
|
|
26
|
-
Object.entries(mergedAsciiDocAttributes).forEach(([name, val]) => {
|
|
27
|
-
const match = name.endsWith('-image') && val.startsWith('image:') && IMAGE_MACRO_RX.exec(val)
|
|
28
|
-
if (!(match && isResourceRef(match[1]))) return
|
|
29
|
-
// Q should we allow image to be resolved relative to component version?
|
|
30
|
-
const image = contentCatalog.resolveResource(match[1], undefined, 'image', ['image'])
|
|
31
|
-
if (!image?.out) return
|
|
32
|
-
mergedAsciiDocAttributes[name] = `image:${image.out.path}[${match[2]}]`
|
|
33
|
-
image.out.assembled = true
|
|
34
|
-
})
|
|
35
|
-
const rootEntry = { content: title }
|
|
36
|
-
let startPage = contentCatalog.getComponentVersionStartPage(componentName, version)
|
|
37
|
-
if (startPage && startPage.src.component === componentName && startPage.src.version === version) {
|
|
38
|
-
if (insertStartPage && !includedInNav(navigation, startPage.pub.url)) {
|
|
39
|
-
Object.assign(rootEntry, { url: startPage.pub.url, urlType: 'internal' })
|
|
40
|
-
}
|
|
41
|
-
} else {
|
|
42
|
-
// Q: should we always use a reference page as startPage for computing mutableAttributes?
|
|
43
|
-
startPage = createFile({
|
|
44
|
-
component: componentVersion.name,
|
|
45
|
-
version: componentVersion.version,
|
|
46
|
-
relative: '.reference-page.adoc',
|
|
47
|
-
origin: (componentVersion.origins || [])[0],
|
|
48
|
-
})
|
|
49
|
-
}
|
|
50
|
-
const mutableAttributes = selectMutableAttributes(loadAsciiDoc, contentCatalog, startPage, mergedAsciiDocConfig)
|
|
51
|
-
delete mutableAttributes.doctype
|
|
52
|
-
accum = accum.concat(
|
|
53
|
-
prepareOutlines(navigation, rootEntry, rootLevel).map((outline) =>
|
|
54
|
-
produceAggregateDocument(
|
|
55
|
-
loadAsciiDoc,
|
|
56
|
-
contentCatalog,
|
|
57
|
-
componentVersion,
|
|
58
|
-
outline,
|
|
59
|
-
doctype,
|
|
60
|
-
contentCatalog.getPages((page) => page.out),
|
|
61
|
-
mergedAsciiDocConfig,
|
|
62
|
-
mutableAttributes,
|
|
63
|
-
sectionMergeStrategy
|
|
64
|
-
)
|
|
65
|
-
)
|
|
66
|
-
)
|
|
67
|
-
mergedAsciiDocAttributes.doctype = doctype
|
|
68
|
-
sourceHighlighter
|
|
69
|
-
? (mergedAsciiDocAttributes['source-highlighter'] = sourceHighlighter)
|
|
70
|
-
: delete mergedAsciiDocAttributes['source-highlighter']
|
|
71
|
-
return accum
|
|
72
|
-
},
|
|
73
|
-
[]
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function createFile (src) {
|
|
78
|
-
const familySegment = (src.family ??= 'page') + 's'
|
|
79
|
-
const path = `modules/${(src.module ??= 'ROOT')}/${familySegment}/${src.relative}`
|
|
80
|
-
const moduleRootPath = Array(src.relative.split('/').length - 1)
|
|
81
|
-
.fill('..')
|
|
82
|
-
.join('/')
|
|
83
|
-
const outPath = [
|
|
84
|
-
src.component === 'ROOT' ? '' : src.component,
|
|
85
|
-
src.version,
|
|
86
|
-
src.module === 'ROOT' ? '' : src.module,
|
|
87
|
-
src.family === 'page' ? '' : '_' + familySegment,
|
|
88
|
-
src.family === 'page' ? src.relative.replace(/\.adoc$/, '.html') : src.relative,
|
|
89
|
-
]
|
|
90
|
-
.filter((it) => it)
|
|
91
|
-
.join('/')
|
|
92
|
-
return new File({
|
|
93
|
-
path,
|
|
94
|
-
contents: src.contents ?? Buffer.alloc(0),
|
|
95
|
-
src,
|
|
96
|
-
out: { path: outPath },
|
|
97
|
-
pub: { url: '/' + outPath, moduleRootPath },
|
|
98
|
-
})
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function getAsciiDocConfigWithAsciidoctorReducerExtension (componentVersion) {
|
|
102
|
-
const asciidoctorReducerExtension = require('./asciidoctor/reducer-extension') // NOTE: must be required lazily
|
|
103
|
-
const asciidocConfig = componentVersion.asciidoc
|
|
104
|
-
const extensions = asciidocConfig.extensions || []
|
|
105
|
-
if (extensions.length) {
|
|
106
|
-
return Object.assign({}, asciidocConfig, {
|
|
107
|
-
extensions: extensions.reduce(
|
|
108
|
-
(accum, candidate) => {
|
|
109
|
-
if (candidate !== asciidoctorReducerExtension) accum.push(candidate)
|
|
110
|
-
return accum
|
|
111
|
-
},
|
|
112
|
-
[asciidoctorReducerExtension]
|
|
113
|
-
),
|
|
114
|
-
sourcemap: true,
|
|
115
|
-
})
|
|
116
|
-
}
|
|
117
|
-
return Object.assign({}, asciidocConfig, { extensions: [asciidoctorReducerExtension], sourcemap: true })
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function includedInNav (items, url) {
|
|
121
|
-
return items.find((it) => it.url === url || includedInNav(it.items || [], url))
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function isResourceRef (target) {
|
|
125
|
-
return ~target.indexOf(':') && !(~target.indexOf('://') || (target.startsWith('data:') && ~target.indexOf(',')))
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// when root level is 0, merge the navigation into the rootEntry
|
|
129
|
-
// when root level is 1, create navigation per navigation menu
|
|
130
|
-
// in this case, if there's only a single navigation menu with no title, promote each top-level item to a menu
|
|
131
|
-
function prepareOutlines (navigation, rootEntry, rootLevel) {
|
|
132
|
-
if (rootLevel === 0 || navigation.length === 1) {
|
|
133
|
-
let navBranch
|
|
134
|
-
if (navigation.length === 1) {
|
|
135
|
-
navBranch = navigation[0]
|
|
136
|
-
} else {
|
|
137
|
-
const items = navigation.reduce((accum, it) => accum.concat(it.content ? it : it.items), [])
|
|
138
|
-
navBranch = items.length ? { items } : {}
|
|
139
|
-
}
|
|
140
|
-
return rootLevel === 0 || navBranch.content ? [Object.assign(rootEntry, navBranch)] : navBranch.items
|
|
141
|
-
}
|
|
142
|
-
return navigation.reduce((navTree, it) => navTree.concat(it.content ? it : it.items), [rootEntry])
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
module.exports = produceAggregateDocuments
|