@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.
@@ -0,0 +1,215 @@
1
+ 'use strict'
2
+
3
+ const computeOut = require('./util/compute-out')
4
+ const createAsciiDocFile = require('./util/create-asciidoc-file')
5
+ const filterComponentVersions = require('./filter-component-versions')
6
+ const produceAssemblyFile = require('./produce-assembly-file')
7
+ const selectMutableAttributes = require('./select-mutable-attributes')
8
+
9
+ const ATTR_REF_RX = /\\?\{(\w[\w-]*)\}/g
10
+ const IMAGE_MACRO_RX = /^image::?(.+?)\[(.*?)\]$/
11
+
12
+ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, resolveAssemblyModel) {
13
+ const { asciidoc: assemblerAsciiDocConfig, assembly: assemblyConfig } = assemblerConfig
14
+ resolveAssemblyModel ??= (componentVersion) => ({
15
+ doctype: assemblyConfig.doctype,
16
+ insertStartPage: assemblyConfig.insertStartPage,
17
+ rootLevel: assemblyConfig.rootLevel,
18
+ sectionMergeStrategy: assemblyConfig.sectionMergeStrategy,
19
+ navigation: componentVersion.navigation,
20
+ xmlIds: assemblyConfig.xmlIds,
21
+ embedReferenceStyle: assemblyConfig.embedReferenceStyle,
22
+ linkReferenceStyle: assemblyConfig.linkReferenceStyle,
23
+ dropExplicitXrefText: assemblyConfig.dropExplicitXrefText,
24
+ })
25
+ const assemblerAsciiDocAttributes = Object.assign({}, assemblerAsciiDocConfig.attributes)
26
+ const { revdate, 'source-highlighter': sourceHighlighter } = assemblerAsciiDocAttributes
27
+ delete assemblerAsciiDocAttributes.revdate
28
+ delete assemblerAsciiDocAttributes['source-highlighter']
29
+ const publishableFiles = contentCatalog.getFiles().filter((file) => file.out)
30
+ let siteRoot
31
+ const configMdc = assemblerConfig.file ? { file: { path: assemblerConfig.file } } : {}
32
+ return filterComponentVersions(contentCatalog.getComponents(), assemblerConfig.componentVersionFilter.names).reduce(
33
+ (accum, componentVersion) => {
34
+ const assemblyModel = resolveAssemblyModel(componentVersion)
35
+ if (!assemblyModel.navigation) return accum
36
+ const { name: componentName, version, title } = componentVersion
37
+ const componentVersionAsciiDocConfig = getAsciiDocConfigWithAsciidoctorReducerExtension(componentVersion)
38
+ const mergedAsciiDocAttributes = collateAsciiDocAttributes(
39
+ Object.assign({ revdate }, componentVersionAsciiDocConfig.attributes),
40
+ assemblerAsciiDocAttributes,
41
+ { logger: assemblyModel.logger, mdc: configMdc }
42
+ )
43
+ const mergedAsciiDocConfig = Object.assign({}, componentVersionAsciiDocConfig, {
44
+ attributes: mergedAsciiDocAttributes,
45
+ })
46
+ assemblyModel.outDirname = computeOut.call(contentCatalog, {
47
+ component: componentName,
48
+ version,
49
+ family: 'export',
50
+ relative: '.index.adoc',
51
+ }).dirname
52
+ assemblyModel.filetype = assemblerAsciiDocAttributes['assembler-filetype']
53
+ assemblyModel.siteRoot =
54
+ siteRoot === undefined
55
+ ? (siteRoot ??= ((val) => {
56
+ if (!val) return null
57
+ if (val.charAt(val.length - 1) === '/') val = val.slice(0, val.length - 1)
58
+ if (!val || val.charAt() === '/') return { path: val }
59
+ return { url: val, path: extractUrlPath(val) }
60
+ })(mergedAsciiDocAttributes['site-url'] || mergedAsciiDocAttributes['primary-site-url']))
61
+ : siteRoot
62
+ if (assemblyModel.filetype === 'html') {
63
+ let linkRefStyle = assemblyModel.linkReferenceStyle
64
+ if (linkRefStyle === 'absolute' && siteRoot?.url == null) linkRefStyle = 'root-relative'
65
+ if (linkRefStyle === 'root-relative' && siteRoot?.path == null) linkRefStyle = 'relative'
66
+ assemblyModel.linkReferenceStyle = linkRefStyle
67
+ } else {
68
+ assemblyModel.linkReferenceStyle = 'absolute'
69
+ }
70
+ const auxiliaryImages = new Set()
71
+ Object.entries(mergedAsciiDocAttributes).forEach(([name, val]) => {
72
+ const match = name.endsWith('-image') && val.startsWith('image:') && IMAGE_MACRO_RX.exec(val)
73
+ if (!(match && isResourceRef(match[1]))) return
74
+ // Q should we allow image to be resolved relative to component version?
75
+ const image = contentCatalog.resolveResource(match[1], undefined, 'image', ['image'])
76
+ if (!image?.out) return
77
+ mergedAsciiDocAttributes[name] = `image:${image.out.path}[${match[2]}]`
78
+ auxiliaryImages.add(image)
79
+ })
80
+ const rootEntry = { content: title }
81
+ const { navigation, rootLevel } = assemblyModel
82
+ let startPage =
83
+ 'startPage' in componentVersion
84
+ ? componentVersion.startPage
85
+ : contentCatalog.resolvePage('index.adoc', { component: componentName, version })
86
+ if (startPage && startPage.src.component === componentName && startPage.src.version === version) {
87
+ if (assemblyModel.insertStartPage) {
88
+ const navtitle = startPage.asciidoc?.navtitle || rootEntry.content
89
+ Object.assign(rootEntry, { navtitle, url: startPage.pub.url, urlType: 'internal' })
90
+ }
91
+ } else {
92
+ // Q: should we always use a reference page as startPage for computing mutableAttributes?
93
+ startPage = createAsciiDocFile(contentCatalog, {
94
+ src: {
95
+ component: componentVersion.name,
96
+ version: componentVersion.version,
97
+ module: 'ROOT',
98
+ family: 'page',
99
+ relative: '.start-page.adoc',
100
+ origin: (componentVersion.origins || [])[0],
101
+ },
102
+ })
103
+ }
104
+ const mutableAttributes = selectMutableAttributes(loadAsciiDoc, contentCatalog, startPage, mergedAsciiDocConfig)
105
+ delete mutableAttributes.doctype // Q: should this be in selectMutableAttributes?
106
+ prepareOutlines(navigation, rootEntry, rootLevel).reduce((any, outline) => {
107
+ const assemblyFile = produceAssemblyFile(
108
+ loadAsciiDoc,
109
+ contentCatalog,
110
+ componentVersion,
111
+ outline,
112
+ publishableFiles,
113
+ mergedAsciiDocConfig,
114
+ mutableAttributes,
115
+ assemblyModel
116
+ )
117
+ if (!assemblyFile) return any
118
+ if (!any && auxiliaryImages.size) {
119
+ const assembledAssets = assemblyFile.assembler.assembled.assets
120
+ auxiliaryImages.forEach((asset) => assembledAssets.add(asset))
121
+ }
122
+ accum.push(assemblyFile)
123
+ return true
124
+ }, false)
125
+ sourceHighlighter
126
+ ? (mergedAsciiDocAttributes['source-highlighter'] = sourceHighlighter)
127
+ : delete mergedAsciiDocAttributes['source-highlighter']
128
+ return accum
129
+ },
130
+ []
131
+ )
132
+ }
133
+
134
+ function getAsciiDocConfigWithAsciidoctorReducerExtension (componentVersion) {
135
+ const asciidoctorReducerExtension = require('@asciidoctor/reducer') // NOTE: must be required lazily
136
+ const asciidocConfig = componentVersion.asciidoc
137
+ const extensions = [asciidoctorReducerExtension]
138
+ const configuredExtensions = asciidocConfig.extensions || []
139
+ if (!configuredExtensions.length) return Object.assign({}, asciidocConfig, { extensions, sourcemap: true })
140
+ return Object.assign({}, asciidocConfig, {
141
+ extensions: configuredExtensions.reduce((accum, candidate) => {
142
+ if (candidate !== asciidoctorReducerExtension) accum.push(candidate)
143
+ return accum
144
+ }, extensions),
145
+ sourcemap: true,
146
+ })
147
+ }
148
+
149
+ function includedInNav (items, url) {
150
+ return items.find((it) => it.url === url || includedInNav(it.items || [], url))
151
+ }
152
+
153
+ function isResourceRef (target) {
154
+ return ~target.indexOf(':') && !(~target.indexOf('://') || (target.startsWith('data:') && ~target.indexOf(',')))
155
+ }
156
+
157
+ function prepareOutlines (navigation, rootEntry, rootLevel) {
158
+ const singleEntry = navigation.length === 1
159
+ const items = navigation.reduce((accum, it) => {
160
+ if (!it.content || (singleEntry && it.url ? it.url === rootEntry.url : it.content === rootEntry.content)) {
161
+ accum.push(...it.items)
162
+ } else {
163
+ accum.push(it)
164
+ }
165
+ return accum
166
+ }, [])
167
+ if (rootLevel === 0) {
168
+ if (rootEntry.url && includedInNav(items, rootEntry.url)) {
169
+ for (const p of ['url', 'urlType']) delete rootEntry[p]
170
+ }
171
+ return [Object.assign(rootEntry, { index: true, items })]
172
+ }
173
+ if (rootEntry.url && !includedInNav(items, rootEntry.url)) {
174
+ rootEntry.content = singleEntry && rootEntry.url === navigation[0].url ? navigation[0].content : rootEntry.navtitle
175
+ items.unshift(rootEntry)
176
+ }
177
+ return items
178
+ }
179
+
180
+ function collateAsciiDocAttributes (collated, additional, { logger, mdc }) {
181
+ Object.entries(additional).forEach(([name, val]) => {
182
+ if (val && val.constructor === String) {
183
+ let alias
184
+ val = val.replace(ATTR_REF_RX, (ref, refname) => {
185
+ if (ref.charAt() === '\\') return ref.substr(1)
186
+ const refval = collated[refname]
187
+ if (refval == null || refval === false) {
188
+ if (refname in collated && ref === val) {
189
+ alias = refval
190
+ } else if (collated['attribute-missing'] === 'warn') {
191
+ if (logger) {
192
+ logger.warn(mdc, "Skipping reference to missing attribute '%s' in value of '%s' attribute", refname, name)
193
+ }
194
+ }
195
+ return ref
196
+ }
197
+ if (refval.constructor === String) return refval
198
+ if (ref !== val) return refval.toString()
199
+ alias = refval
200
+ return ref
201
+ })
202
+ if (alias !== undefined) val = alias
203
+ }
204
+ collated[name] = val
205
+ })
206
+ return collated
207
+ }
208
+
209
+ function extractUrlPath (url) {
210
+ if (!url) return ''
211
+ const urlPath = new URL(url).pathname
212
+ return urlPath === '/' ? '' : urlPath
213
+ }
214
+
215
+ 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_ = 'ROOT', family, relative } = src
7
+ const outRelative = family === 'page' ? relative.replace(/\.adoc$/, '.html') : relative
8
+ const { dir: dirname, base: basename } = 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 _ 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,17 @@
1
+ 'use strict'
2
+
3
+ const computeOut = require('./compute-out')
4
+
5
+ function createAsciiDocFile (contentCatalog, file) {
6
+ file.mediaType = 'text/asciidoc'
7
+ const src = file.src
8
+ const out = computeOut.call(contentCatalog, src)
9
+ if (src.family === 'export') {
10
+ contentCatalog.removeFile((file = contentCatalog.addFile(Object.assign(file, { path: out.path, out: null }))))
11
+ return file
12
+ }
13
+ const pub = { url: '/' + out.path, moduleRootPath: out.moduleRootPath, rootPath: out.rootPath }
14
+ return { contents: src.contents ?? Buffer.alloc(0), src, out, pub }
15
+ }
16
+
17
+ module.exports = createAsciiDocFile
@@ -0,0 +1,36 @@
1
+ 'use strict'
2
+
3
+ function parseResourceRef (ref, ctx = {}, family = undefined, contentCatalog = undefined) {
4
+ const atIdx = ref.indexOf('@')
5
+ let firstColonIdx = ref.indexOf(':')
6
+ let component, version, module_
7
+ if (~atIdx && (~firstColonIdx ? atIdx < firstColonIdx : true)) {
8
+ if ((version = ref.slice(0, atIdx)) === '_') version = ''
9
+ ref = ref.slice(atIdx + 1)
10
+ if (~firstColonIdx) firstColonIdx -= atIdx + 1
11
+ }
12
+ const addColons = ~firstColonIdx ? (~ref.indexOf(':', firstColonIdx + 1) ? '' : ':') : '::'
13
+ const segments = (addColons + ref).split(':')
14
+ if ((component = segments[0])) {
15
+ module_ = segments[1] || 'ROOT'
16
+ version ??= contentCatalog?.getComponent(component)?.latest.version
17
+ } else {
18
+ component = ctx.component
19
+ version ??= ctx.version
20
+ module_ = segments[1] || ctx.module || 'ROOT'
21
+ }
22
+ let relative = segments.length > 3 ? segments.slice(2).join(':') : segments[2]
23
+ const dollarIdx = relative.indexOf('$')
24
+ if (~dollarIdx) {
25
+ family = relative.slice(0, dollarIdx) || family
26
+ relative = relative.slice(dollarIdx + 1)
27
+ }
28
+ if (relative.charAt() === '.' && relative.charAt(1) === '/') {
29
+ const ctxRelative = ctx.relative
30
+ const topic = ctxRelative ? ctxRelative.slice(0, (ctxRelative.lastIndexOf('/') + 1 || 1) - 1) : undefined
31
+ relative = (topic ? topic + '/' : '') + relative.slice(2)
32
+ }
33
+ return { component, version, module: module_, family, relative }
34
+ }
35
+
36
+ module.exports = parseResourceRef
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.10",
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,43 +9,51 @@
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
+ "./parse-resource-ref": "./lib/util/parse-resource-ref.js",
31
+ "./produce-assembly-file": "./lib/produce-assembly-file.js",
32
+ "./produce-assembly-files": "./lib/produce-assembly-files.js",
29
33
  "./select-mutable-attributes": "./lib/select-mutable-attributes.js"
30
34
  },
31
35
  "imports": {
36
+ "#asciidoctor-log-adapter": "./adapters/asciidoctor/jsonl-logger.rb",
37
+ "#run-command": "@antora/run-command-helper",
32
38
  "#unconvert-inline-asciidoc": "./lib/util/unconvert-inline-asciidoc.js"
33
39
  },
34
40
  "dependencies": {
35
- "@antora/expand-path-helper": "~2.0",
41
+ "@asciidoctor/reducer": "~1.1",
42
+ "@antora/expand-path-helper": "~3.0",
36
43
  "braces": "~3.0",
37
44
  "picomatch": "~3.0",
38
- "vinyl": "~2.2",
39
45
  "js-yaml": "~4.1"
40
46
  },
41
47
  "devDependencies": {
42
48
  "@antora/asciidoc-loader": "~3.1",
49
+ "@antora/navigation-builder": "~3.1",
43
50
  "@antora/site-publisher": "~3.1"
44
51
  },
45
52
  "engines": {
46
53
  "node": ">=16.0.0"
47
54
  },
48
55
  "files": [
56
+ "adapters/",
49
57
  "lib/"
50
58
  ],
51
59
  "keywords": [
@@ -53,8 +61,5 @@
53
61
  "antora-extension",
54
62
  "asciidoc",
55
63
  "documentation"
56
- ],
57
- "publishConfig": {
58
- "access": "public"
59
- }
64
+ ]
60
65
  }
@@ -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
- }