@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.
@@ -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-alpha.9",
4
- "description": "An extension library for Antora that assembles content from multiple pages into a single AsciiDoc file to converted and publish.",
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": "gitlab:antora/antora-assembler",
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": "_mocha 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-aggregate-document": "./lib/produce-aggregate-document.js",
28
- "./produce-aggregate-documents": "./lib/produce-aggregate-documents.js",
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
- "@antora/expand-path-helper": "~2.0",
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