@antora/assembler 1.0.0-alpha.1 → 1.0.0-alpha.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/lib/assemble-content.js +6 -5
- package/lib/filter-component-versions.js +2 -2
- package/lib/index.js +1 -2
- package/lib/load-config.js +35 -23
- package/lib/produce-aggregate-document.js +233 -148
- package/lib/produce-aggregate-documents.js +92 -32
- package/lib/util/sanitize.js +13 -0
- package/lib/util/unconvert-inline-asciidoc.js +95 -0
- package/package.json +13 -9
- package/lib/asciidoctor/reducer-extension.js +0 -230
- package/lib/util/lazy-readable.js +0 -19
- package/lib/util/run-command.js +0 -56
|
@@ -4,37 +4,58 @@ const filterComponentVersions = require('./filter-component-versions')
|
|
|
4
4
|
const produceAggregateDocument = require('./produce-aggregate-document')
|
|
5
5
|
const selectMutableAttributes = require('./select-mutable-attributes')
|
|
6
6
|
|
|
7
|
+
const IMAGE_MACRO_RX = /^image::?(.+?)\[(.*?)\]$/
|
|
8
|
+
|
|
7
9
|
function produceAggregateDocuments (loadAsciiDoc, contentCatalog, assemblerConfig) {
|
|
8
10
|
const { insertStartPage, rootLevel, sectionMergeStrategy, asciidoc: assemblerAsciiDocConfig } = assemblerConfig
|
|
11
|
+
const assemblerAsciiDocAttributes = Object.assign({}, assemblerAsciiDocConfig.attributes)
|
|
12
|
+
const { doctype, revdate, 'source-highlighter': sourceHighlighter } = assemblerAsciiDocAttributes
|
|
13
|
+
delete assemblerAsciiDocAttributes.doctype
|
|
14
|
+
delete assemblerAsciiDocAttributes.revdate
|
|
15
|
+
delete assemblerAsciiDocAttributes['source-highlighter']
|
|
9
16
|
return filterComponentVersions(contentCatalog.getComponents(), assemblerConfig.componentVersions).reduce(
|
|
10
17
|
(accum, componentVersion) => {
|
|
11
18
|
const { name: componentName, version, title, navigation } = componentVersion
|
|
12
19
|
if (!navigation) return accum
|
|
13
20
|
const componentVersionAsciiDocConfig = getAsciiDocConfigWithAsciidoctorReducerExtension(componentVersion)
|
|
14
21
|
const mergedAsciiDocConfig = Object.assign({}, componentVersionAsciiDocConfig, {
|
|
15
|
-
attributes: Object.assign({}, componentVersionAsciiDocConfig.attributes,
|
|
22
|
+
attributes: Object.assign({ revdate }, componentVersionAsciiDocConfig.attributes, assemblerAsciiDocAttributes),
|
|
23
|
+
})
|
|
24
|
+
const mergedAsciiDocAttributes = mergedAsciiDocConfig.attributes
|
|
25
|
+
Object.entries(mergedAsciiDocAttributes).forEach(([name, val]) => {
|
|
26
|
+
const match = name.endsWith('-image') && val.startsWith('image:') && IMAGE_MACRO_RX.exec(val)
|
|
27
|
+
if (!(match && isResourceRef(match[1]))) return
|
|
28
|
+
// Q should we allow image to be resolved relative to component version?
|
|
29
|
+
const image = contentCatalog.resolveResource(match[1], undefined, 'image', ['image'])
|
|
30
|
+
if (!image?.out) return
|
|
31
|
+
mergedAsciiDocAttributes[name] = `image:${image.out.path}[${match[2]}]`
|
|
32
|
+
image.out.assembled = true
|
|
16
33
|
})
|
|
17
34
|
const rootEntry = { content: title }
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
startPageUrl = undefined
|
|
23
|
-
} else {
|
|
24
|
-
Object.assign(rootEntry, { url: startPageUrl, urlType: 'internal' })
|
|
35
|
+
let startPage = contentCatalog.getComponentVersionStartPage(componentName, version)
|
|
36
|
+
if (startPage && startPage.src.component === componentName && startPage.src.version === version) {
|
|
37
|
+
if (insertStartPage && !includedInNav(navigation, startPage.pub.url)) {
|
|
38
|
+
Object.assign(rootEntry, { url: startPage.pub.url, urlType: 'internal' })
|
|
25
39
|
}
|
|
40
|
+
} else {
|
|
41
|
+
// Q: should we always use a reference page as startPage for computing mutableAttributes?
|
|
42
|
+
startPage = createFile({
|
|
43
|
+
component: componentVersion.name,
|
|
44
|
+
version: componentVersion.version,
|
|
45
|
+
relative: '.reference-page.adoc',
|
|
46
|
+
origin: (componentVersion.origins || [])[0],
|
|
47
|
+
})
|
|
26
48
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
: {}
|
|
31
|
-
return accum.concat(
|
|
49
|
+
const mutableAttributes = selectMutableAttributes(loadAsciiDoc, contentCatalog, startPage, mergedAsciiDocConfig)
|
|
50
|
+
delete mutableAttributes.doctype
|
|
51
|
+
accum = accum.concat(
|
|
32
52
|
prepareOutlines(navigation, rootEntry, rootLevel).map((outline) =>
|
|
33
53
|
produceAggregateDocument(
|
|
34
54
|
loadAsciiDoc,
|
|
35
55
|
contentCatalog,
|
|
36
56
|
componentVersion,
|
|
37
57
|
outline,
|
|
58
|
+
doctype,
|
|
38
59
|
contentCatalog.getPages((page) => page.out),
|
|
39
60
|
mergedAsciiDocConfig,
|
|
40
61
|
mutableAttributes,
|
|
@@ -42,40 +63,79 @@ function produceAggregateDocuments (loadAsciiDoc, contentCatalog, assemblerConfi
|
|
|
42
63
|
)
|
|
43
64
|
)
|
|
44
65
|
)
|
|
66
|
+
mergedAsciiDocAttributes.doctype = doctype
|
|
67
|
+
sourceHighlighter
|
|
68
|
+
? (mergedAsciiDocAttributes['source-highlighter'] = sourceHighlighter)
|
|
69
|
+
: delete mergedAsciiDocAttributes['source-highlighter']
|
|
70
|
+
return accum
|
|
45
71
|
},
|
|
46
72
|
[]
|
|
47
73
|
)
|
|
48
74
|
}
|
|
49
75
|
|
|
76
|
+
function createFile (src) {
|
|
77
|
+
const familySegment = (src.family ??= 'page') + 's'
|
|
78
|
+
const relativeSegments = src.relative.split('/')
|
|
79
|
+
const segments = ['modules', (src.module ??= 'ROOT'), familySegment, ...relativeSegments]
|
|
80
|
+
const path = segments.join('/')
|
|
81
|
+
const moduleRootPath = Array(relativeSegments.length - 1)
|
|
82
|
+
.fill('..')
|
|
83
|
+
.join('/')
|
|
84
|
+
const outPath = [
|
|
85
|
+
src.component === 'ROOT' ? '' : src.component,
|
|
86
|
+
src.version,
|
|
87
|
+
src.module === 'ROOT' ? '' : src.module,
|
|
88
|
+
src.family === 'page' ? '' : '_' + familySegment,
|
|
89
|
+
src.family === 'page' ? src.relative.replace(/\.adoc$/, '.html') : src.relative,
|
|
90
|
+
]
|
|
91
|
+
.filter((it) => it)
|
|
92
|
+
.join('/')
|
|
93
|
+
return {
|
|
94
|
+
path,
|
|
95
|
+
dirname: path.slice(0, path.lastIndexOf('/')),
|
|
96
|
+
contents: src.contents ?? Buffer.alloc(0),
|
|
97
|
+
src,
|
|
98
|
+
out: { path: outPath },
|
|
99
|
+
pub: { url: '/' + outPath, moduleRootPath },
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
50
103
|
function getAsciiDocConfigWithAsciidoctorReducerExtension (componentVersion) {
|
|
51
|
-
const asciidoctorReducerExtension = require('
|
|
104
|
+
const asciidoctorReducerExtension = require('@asciidoctor/reducer') // NOTE: must be required lazily
|
|
52
105
|
const asciidocConfig = componentVersion.asciidoc
|
|
53
|
-
const extensions =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
})
|
|
64
|
-
}
|
|
65
|
-
return Object.assign({}, asciidocConfig, { extensions: [asciidoctorReducerExtension], sourcemap: true })
|
|
106
|
+
const extensions = [asciidoctorReducerExtension]
|
|
107
|
+
const configuredExtensions = asciidocConfig.extensions || []
|
|
108
|
+
if (!configuredExtensions.length) return Object.assign({}, asciidocConfig, { extensions, sourcemap: true })
|
|
109
|
+
return Object.assign({}, asciidocConfig, {
|
|
110
|
+
extensions: configuredExtensions.reduce((accum, candidate) => {
|
|
111
|
+
if (candidate !== asciidoctorReducerExtension) accum.push(candidate)
|
|
112
|
+
return accum
|
|
113
|
+
}, extensions),
|
|
114
|
+
sourcemap: true,
|
|
115
|
+
})
|
|
66
116
|
}
|
|
67
117
|
|
|
68
118
|
function includedInNav (items, url) {
|
|
69
119
|
return items.find((it) => it.url === url || includedInNav(it.items || [], url))
|
|
70
120
|
}
|
|
71
121
|
|
|
122
|
+
function isResourceRef (target) {
|
|
123
|
+
return ~target.indexOf(':') && !(~target.indexOf('://') || (target.startsWith('data:') && ~target.indexOf(',')))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// when root level is 0, merge the navigation into the rootEntry
|
|
127
|
+
// when root level is 1, create navigation per navigation menu
|
|
128
|
+
// in this case, if there's only a single navigation menu with no title, promote each top-level item to a menu
|
|
72
129
|
function prepareOutlines (navigation, rootEntry, rootLevel) {
|
|
73
130
|
if (rootLevel === 0 || navigation.length === 1) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
131
|
+
let navBranch
|
|
132
|
+
if (navigation.length === 1) {
|
|
133
|
+
navBranch = navigation[0]
|
|
134
|
+
} else {
|
|
135
|
+
const items = navigation.reduce((accum, it) => accum.concat(it.content ? it : it.items), [])
|
|
136
|
+
navBranch = items.length ? { items } : {}
|
|
137
|
+
}
|
|
138
|
+
return rootLevel === 0 || navBranch.content ? [Object.assign(rootEntry, navBranch)] : navBranch.items
|
|
79
139
|
}
|
|
80
140
|
return navigation.reduce((navTree, it) => navTree.concat(it.content ? it : it.items), [rootEntry])
|
|
81
141
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const XML_TAG_RX = /<[^>]+>/g
|
|
4
|
+
const XML_SPECIAL_CHARS = { '<': '<', '>': '>', '&': '&' }
|
|
5
|
+
const XML_SPECIAL_CHARS_RX = /&(?:[lg]t|amp);/g
|
|
6
|
+
|
|
7
|
+
function sanitize (str) {
|
|
8
|
+
if (~str.indexOf('<')) str = str.replace(XML_TAG_RX, '')
|
|
9
|
+
if (~str.indexOf('&')) str = str.replace(XML_SPECIAL_CHARS_RX, (m) => XML_SPECIAL_CHARS[m])
|
|
10
|
+
return str
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = sanitize
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const ATTRIBUTE_REFERENCE_RX = /\{[a-z0-9_][a-z0-9_-]*\}/g
|
|
4
|
+
const STRICT_WORD_CHAR_RX = /[\p{L}\d_]/u
|
|
5
|
+
const WORD_CHAR_RX = /[\p{L}\d_;}:<>]/u
|
|
6
|
+
|
|
7
|
+
const MARK_FOR_TAG = { code: '`', em: '_', mark: '#', span: '#', strong: '*' }
|
|
8
|
+
const SKIP_SPAN = { icon: '<i ', image: '<img ' }
|
|
9
|
+
|
|
10
|
+
module.exports = (str) => {
|
|
11
|
+
if (!str) return str
|
|
12
|
+
let matchIndex = str.indexOf('<')
|
|
13
|
+
if (!~matchIndex) return ~str.indexOf('{') ? str.replace(ATTRIBUTE_REFERENCE_RX, '\\$&') : str
|
|
14
|
+
let current = { contents: '' }
|
|
15
|
+
const stack = [current]
|
|
16
|
+
let lastIndex = 0
|
|
17
|
+
do {
|
|
18
|
+
if (matchIndex > lastIndex) {
|
|
19
|
+
const matched = str.slice(lastIndex, matchIndex)
|
|
20
|
+
current.contents += ~matched.indexOf('{') ? matched.replace(ATTRIBUTE_REFERENCE_RX, '\\$&') : matched
|
|
21
|
+
}
|
|
22
|
+
const isCloseTag = str[++matchIndex] === '/' ? ++matchIndex : false
|
|
23
|
+
let tagName = str.slice(matchIndex, (lastIndex = str.indexOf('>', matchIndex) + 1) - 1)
|
|
24
|
+
if (isCloseTag) {
|
|
25
|
+
const parent = current // TODO expect tagName to equal current.tagName
|
|
26
|
+
stack.pop()
|
|
27
|
+
current = stack[stack.length - 1]
|
|
28
|
+
if (parent.mark) {
|
|
29
|
+
let { contents, mark, id, role } = parent
|
|
30
|
+
const attrlist = (id ? '#' + id : '') + (role ? '.' + role.replace(/ /g, '.') : '')
|
|
31
|
+
if (
|
|
32
|
+
current.mark === mark ||
|
|
33
|
+
current.mark === '_' ||
|
|
34
|
+
isWordChar(str.charAt(lastIndex), true) ||
|
|
35
|
+
isWordChar(current.contents[current.contents.length - 1] || current.mark)
|
|
36
|
+
) {
|
|
37
|
+
mark = mark.repeat(2)
|
|
38
|
+
}
|
|
39
|
+
current.contents += (attrlist ? '[' + attrlist + ']' : '') + mark + contents + mark
|
|
40
|
+
} else {
|
|
41
|
+
current.contents += parent.contents
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
let attrs, attrlistIndex, role
|
|
45
|
+
if (~(attrlistIndex = tagName.indexOf(' '))) {
|
|
46
|
+
role = (attrs = parseAttrlist(tagName.slice(attrlistIndex))).class
|
|
47
|
+
tagName = tagName.slice(0, attrlistIndex)
|
|
48
|
+
}
|
|
49
|
+
if (tagName === 'img') {
|
|
50
|
+
current.contents += 'image:' + attrs.src + '[' + attrs.alt + ']'
|
|
51
|
+
} else if (tagName === 'i' && current.tagName === 'span' && current.role === 'icon') {
|
|
52
|
+
current.contents += 'icon:' + role.slice(6) + '[]'
|
|
53
|
+
lastIndex += 4
|
|
54
|
+
} else {
|
|
55
|
+
let check, mark
|
|
56
|
+
const id = attrs?.id
|
|
57
|
+
if (tagName !== 'span' || id || (role && !((check = SKIP_SPAN[role]) && str.startsWith(check, lastIndex)))) {
|
|
58
|
+
mark = MARK_FOR_TAG[tagName]
|
|
59
|
+
}
|
|
60
|
+
stack.push((current = { tagName, role, id, mark, contents: '' }))
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} while (~(matchIndex = str.indexOf('<', lastIndex)))
|
|
64
|
+
const rest = str.slice(lastIndex)
|
|
65
|
+
if (rest) current.contents += ~rest.indexOf('{') ? rest.replace(ATTRIBUTE_REFERENCE_RX, '\\$&') : rest
|
|
66
|
+
return current.contents
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function parseAttrlist (str) {
|
|
70
|
+
let lastIndex = 0
|
|
71
|
+
const attrs = {}
|
|
72
|
+
while (str.charAt(lastIndex++) === ' ') {
|
|
73
|
+
const spaceIndex = str.indexOf(' ', lastIndex)
|
|
74
|
+
const equalsIndex = str.indexOf('=', lastIndex)
|
|
75
|
+
if (~spaceIndex && spaceIndex < equalsIndex) {
|
|
76
|
+
attrs[str.slice(lastIndex, (lastIndex = spaceIndex))] = true
|
|
77
|
+
} else if (~equalsIndex) {
|
|
78
|
+
const name = str.slice(lastIndex, equalsIndex)
|
|
79
|
+
const valueIndex = equalsIndex + 1
|
|
80
|
+
attrs[name] =
|
|
81
|
+
str.charAt(valueIndex) === '"'
|
|
82
|
+
? str.slice(valueIndex + 1, (lastIndex = str.indexOf('"', valueIndex + 1) + 1) - 1)
|
|
83
|
+
: str.slice(valueIndex, (lastIndex = ~spaceIndex ? spaceIndex : str.length))
|
|
84
|
+
} else {
|
|
85
|
+
attrs[str.slice(lastIndex)] = true
|
|
86
|
+
break
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return attrs
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isWordChar (str, strict) {
|
|
93
|
+
if (!str) return false
|
|
94
|
+
return (strict ? STRICT_WORD_CHAR_RX : WORD_CHAR_RX).test(str)
|
|
95
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antora/assembler",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.10",
|
|
4
4
|
"description": "An extension library for Antora that assembles content from multiple pages into a single AsciiDoc file to converted and publish.",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"author": "OpenDevise Inc. (https://opendevise.com)",
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
"url": "https://gitlab.com/antora/antora-assembler/issues"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
|
-
"test": "
|
|
18
|
-
"prepublishOnly": "
|
|
19
|
-
"postpublish": "
|
|
17
|
+
"test": "node --test test/*-test.js",
|
|
18
|
+
"prepublishOnly": "npx -y downdoc --prepublish",
|
|
19
|
+
"postpublish": "npx -y downdoc --postpublish"
|
|
20
20
|
},
|
|
21
21
|
"main": "lib/index.js",
|
|
22
22
|
"exports": {
|
|
@@ -25,19 +25,23 @@
|
|
|
25
25
|
"./filter-component-versions": "./lib/filter-component-versions.js",
|
|
26
26
|
"./load-config": "./lib/load-config.js",
|
|
27
27
|
"./produce-aggregate-document": "./lib/produce-aggregate-document.js",
|
|
28
|
+
"./produce-aggregate-documents": "./lib/produce-aggregate-documents.js",
|
|
28
29
|
"./select-mutable-attributes": "./lib/select-mutable-attributes.js"
|
|
29
30
|
},
|
|
31
|
+
"imports": {
|
|
32
|
+
"#unconvert-inline-asciidoc": "./lib/util/unconvert-inline-asciidoc.js"
|
|
33
|
+
},
|
|
30
34
|
"dependencies": {
|
|
35
|
+
"@asciidoctor/reducer": "~1.1",
|
|
31
36
|
"@antora/expand-path-helper": "~2.0",
|
|
37
|
+
"@antora/run-command-helper": "~1.0",
|
|
32
38
|
"braces": "~3.0",
|
|
33
|
-
"
|
|
34
|
-
"picomatch": "~2.3",
|
|
35
|
-
"vinyl": "~2.2",
|
|
39
|
+
"picomatch": "~3.0",
|
|
36
40
|
"js-yaml": "~4.1"
|
|
37
41
|
},
|
|
38
42
|
"devDependencies": {
|
|
39
|
-
"@antora/asciidoc-loader": "3.
|
|
40
|
-
"@antora/site-publisher": "3.
|
|
43
|
+
"@antora/asciidoc-loader": "~3.1",
|
|
44
|
+
"@antora/site-publisher": "~3.1"
|
|
41
45
|
},
|
|
42
46
|
"engines": {
|
|
43
47
|
"node": ">=16.0.0"
|
|
@@ -1,230 +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
|
-
const result = Opal.send(this, Opal.find_super_dispatcher(this, 'push_include', pushInclude), [
|
|
125
|
-
data,
|
|
126
|
-
file,
|
|
127
|
-
path,
|
|
128
|
-
lineno,
|
|
129
|
-
attrs,
|
|
130
|
-
])
|
|
131
|
-
pushIncludeReplacement.call(
|
|
132
|
-
this,
|
|
133
|
-
directiveLineno,
|
|
134
|
-
this.include_stack.length > prevIncDepth ? this.$lines() : [],
|
|
135
|
-
lineno > 1 ? lineno - 1 : 0
|
|
136
|
-
)
|
|
137
|
-
return result
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
Opal.defn(scope, '$pop_include', function popInclude () {
|
|
141
|
-
if (!this.$$reducer.includePushed) this.includeReplacements.$up()
|
|
142
|
-
return Opal.send(this, Opal.find_super_dispatcher(this, 'pop_include', popInclude), [])
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
function pushIncludeReplacement (lineno, lines, offset, unresolved) {
|
|
146
|
-
const incReplacements = this.includeReplacements
|
|
147
|
-
const into = incReplacements.pointer
|
|
148
|
-
const line = this.$$reducer.includeDirectiveLine
|
|
149
|
-
incReplacements.push({ into, lineno: lineno - (incReplacements.$current().offset || 0), line, lines, offset })
|
|
150
|
-
if (!unresolved && lines.length) incReplacements.$to_end()
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return scope
|
|
154
|
-
})()
|
|
155
|
-
|
|
156
|
-
function preprocessor () {
|
|
157
|
-
this.process((doc, reader) =>
|
|
158
|
-
doc.getOptions().preserve_conditionals
|
|
159
|
-
? reader.$extend(IncludeDirectiveTracker)
|
|
160
|
-
: reader.$extend(ConditionalDirectiveTracker, IncludeDirectiveTracker)
|
|
161
|
-
)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function treeProcessor () {
|
|
165
|
-
this.process((doc) => {
|
|
166
|
-
const incReplacements = doc.reader.includeReplacements
|
|
167
|
-
if (incReplacements.length > 1 || (incReplacements[0].drop || []).length) {
|
|
168
|
-
const sourceLines = doc.getSourceLines()
|
|
169
|
-
incReplacements[0].lines = sourceLines.slice()
|
|
170
|
-
incReplacements
|
|
171
|
-
.slice()
|
|
172
|
-
.reverse()
|
|
173
|
-
.forEach(({ into, lineno, lines, line, drop }) => {
|
|
174
|
-
let targetLines, idx
|
|
175
|
-
if (into != null) {
|
|
176
|
-
targetLines = incReplacements[into].lines
|
|
177
|
-
// adds assurance that we're replacing the correct line
|
|
178
|
-
if (targetLines[(idx = lineno - 1)] !== line) return
|
|
179
|
-
}
|
|
180
|
-
if ((drop || []).length) {
|
|
181
|
-
drop
|
|
182
|
-
.slice()
|
|
183
|
-
.reverse()
|
|
184
|
-
.forEach((dropIt) => {
|
|
185
|
-
Array.isArray(dropIt) ? (lines[dropIt[0] - 1] = dropIt[1]) : lines.splice(dropIt - 1, 1)
|
|
186
|
-
})
|
|
187
|
-
}
|
|
188
|
-
if (targetLines) targetLines[idx] = lines
|
|
189
|
-
})
|
|
190
|
-
const reducedSourceLines = flattenDeep(incReplacements[0].lines)
|
|
191
|
-
if (doc.getSourcemap()) {
|
|
192
|
-
const logger = Asciidoctor.LoggerManager.getLogger()
|
|
193
|
-
const opts = Object.assign(doc.getOptions(), { logger: undefined, parse: false, reduced: true })
|
|
194
|
-
if (opts.extension_registry) {
|
|
195
|
-
opts.extension_registry = Asciidoctor.Extensions.Registry.$new(opts.extension_registry.groups)
|
|
196
|
-
}
|
|
197
|
-
const includes = doc.getCatalog().includes
|
|
198
|
-
doc = Asciidoctor.load(reducedSourceLines, opts)
|
|
199
|
-
doc.catalog.$send('[]=', 'includes', includes)
|
|
200
|
-
doc.parse()
|
|
201
|
-
Asciidoctor.LoggerManager.setLogger(logger)
|
|
202
|
-
} else {
|
|
203
|
-
while (reducedSourceLines[reducedSourceLines.length - 1] === '') reducedSourceLines.pop()
|
|
204
|
-
sourceLines.splice(0, sourceLines.length, ...reducedSourceLines)
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return doc
|
|
208
|
-
})
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function flattenDeep (array, accum = []) {
|
|
212
|
-
const len = array.length
|
|
213
|
-
for (let i = 0, it; i < len; i++) Array.isArray((it = array[i])) ? flattenDeep(it, accum) : accum.push(it)
|
|
214
|
-
return accum
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function toProc (fn) {
|
|
218
|
-
return Object.defineProperty(fn, '$$arity', { value: fn.length })
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
module.exports.register = (registry) => {
|
|
222
|
-
const extGroup = toProc(function () {
|
|
223
|
-
const doc = this.document
|
|
224
|
-
doc.$extend(DocumentExt)
|
|
225
|
-
if (doc.getOptions().reduced) return
|
|
226
|
-
this.preprocessor(preprocessor)
|
|
227
|
-
this.treeProcessor(treeProcessor)
|
|
228
|
-
})
|
|
229
|
-
registry.groups.$send('[]=', 'reducer', extGroup)
|
|
230
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { PassThrough } = require('stream')
|
|
4
|
-
|
|
5
|
-
// adapted from https://github.com/jpommerening/node-lazystream/blob/master/lib/lazystream.js | license: MIT
|
|
6
|
-
class LazyReadable extends PassThrough {
|
|
7
|
-
constructor (fn, options) {
|
|
8
|
-
super(options)
|
|
9
|
-
const _read = this._read
|
|
10
|
-
this._read = function () {
|
|
11
|
-
this._read = _read.bind(this)
|
|
12
|
-
fn.call(this, options).on('error', this.emit.bind(this, 'error')).pipe(this)
|
|
13
|
-
return this._read.apply(this, arguments)
|
|
14
|
-
}
|
|
15
|
-
this.emit('readable')
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
module.exports = LazyReadable
|
package/lib/util/run-command.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const fs = require('fs')
|
|
4
|
-
const LazyReadable = require('./lazy-readable')
|
|
5
|
-
const { spawn } = require('child_process')
|
|
6
|
-
|
|
7
|
-
const IS_WIN = process.platform === 'win32'
|
|
8
|
-
const DBL_QUOTE_RX = /"/g
|
|
9
|
-
|
|
10
|
-
const shellEscape = IS_WIN
|
|
11
|
-
? (val) => {
|
|
12
|
-
if (val.charAt() === '-') {
|
|
13
|
-
return val
|
|
14
|
-
} else if (~val.indexOf('"')) {
|
|
15
|
-
return ~val.indexOf(' ') ? `"${val.replace(DBL_QUOTE_RX, '"""')}"` : val.replace(DBL_QUOTE_RX, '""')
|
|
16
|
-
} else if (~val.indexOf(' ')) {
|
|
17
|
-
return `"${val}"`
|
|
18
|
-
}
|
|
19
|
-
return val
|
|
20
|
-
}
|
|
21
|
-
: (val) => val
|
|
22
|
-
|
|
23
|
-
async function runCommand (cmd, argv = [], opts = {}) {
|
|
24
|
-
if (!cmd) throw new TypeError('Command not specified')
|
|
25
|
-
const cmdv = cmd.split(' ').map(shellEscape)
|
|
26
|
-
const { input, output, implicitStdin, ...spawnOpts } = opts
|
|
27
|
-
if (input) input instanceof Buffer ? implicitStdin || argv.push('-') : argv.push(input)
|
|
28
|
-
if (IS_WIN) Object.assign(spawnOpts, { shell: true, windowsHide: true })
|
|
29
|
-
return new Promise((resolve, reject) => {
|
|
30
|
-
const stdout = []
|
|
31
|
-
const stderr = []
|
|
32
|
-
const ps = spawn(cmdv[0], [...cmdv.slice(1), ...argv.map(shellEscape)], spawnOpts)
|
|
33
|
-
ps.on('close', (code) => {
|
|
34
|
-
if (code === 0) {
|
|
35
|
-
if (stderr.length) process.stderr.write(stderr.join(''))
|
|
36
|
-
resolve(output ? new LazyReadable(() => fs.createReadStream(output)) : Buffer.from(stdout.join('')))
|
|
37
|
-
} else {
|
|
38
|
-
let msg = `Command failed: ${ps.spawnargs.join(' ')}`
|
|
39
|
-
if (stderr.length) msg += '\n' + stderr.join('')
|
|
40
|
-
reject(new Error(msg))
|
|
41
|
-
}
|
|
42
|
-
})
|
|
43
|
-
ps.on('error', (err) => reject(err.code === 'ENOENT' ? new Error(`Command not found: ${cmdv.join(' ')}`) : err))
|
|
44
|
-
ps.stdout.on('data', (data) => (output ? process.stdout.write(data) : stdout.push(data)))
|
|
45
|
-
ps.stderr.on('data', (data) => stderr.push(data))
|
|
46
|
-
try {
|
|
47
|
-
input instanceof Buffer ? ps.stdin.end(input) : ps.stdin.end()
|
|
48
|
-
} catch (err) {
|
|
49
|
-
reject(err)
|
|
50
|
-
} finally {
|
|
51
|
-
ps.stdin.end()
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
module.exports = runCommand
|