@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.
@@ -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, assemblerAsciiDocConfig.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
- const startPage = contentCatalog.getComponentVersionStartPage(componentName, version)
19
- let startPageUrl
20
- if (startPage && insertStartPage) {
21
- if (includedInNav(navigation, (startPageUrl = startPage.pub.url))) {
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
- // Q: should we use an artificial page instead or as fallback?
28
- const mutableAttributes = startPage
29
- ? selectMutableAttributes(loadAsciiDoc, contentCatalog, startPage, mergedAsciiDocConfig)
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('./asciidoctor/reducer-extension') // NOTE: must be required lazily
104
+ const asciidoctorReducerExtension = require('@asciidoctor/reducer') // NOTE: must be required lazily
52
105
  const asciidocConfig = componentVersion.asciidoc
53
- const extensions = asciidocConfig.extensions || []
54
- if (extensions.length) {
55
- return Object.assign({}, asciidocConfig, {
56
- extensions: extensions.reduce(
57
- (accum, candidate) => {
58
- if (candidate !== asciidoctorReducerExtension) accum.push(candidate)
59
- return accum
60
- },
61
- [asciidoctorReducerExtension]
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
- const navBranch =
75
- navigation.length === 1
76
- ? navigation[0]
77
- : { items: navigation.reduce((navTree, it) => navTree.concat(it.content ? it : it.items), []) }
78
- return [Object.assign(rootEntry, navBranch)]
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 = { '&lt;': '<', '&gt;': '>', '&amp;': '&' }
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.1",
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": "_mocha test",
18
- "prepublishOnly": "node $npm_config_local_prefix/npm/prepublishOnly.js",
19
- "postpublish": "node $npm_config_local_prefix/npm/postpublish.js"
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
- "camelcase-keys": "~7.0",
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.0.1",
40
- "@antora/site-publisher": "3.0.1"
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
@@ -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