@antora/assembler 1.0.0-beta.9 → 1.0.0-rc.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/adapters/asciidoctor/jsonl-logger.rb +25 -0
- package/lib/assemble-content.js +141 -135
- package/lib/compile-conversion-attributes.js +76 -0
- package/lib/configure.js +44 -16
- package/lib/constants.js +24 -0
- package/lib/filter-component-versions.js +25 -62
- package/lib/index.js +1 -1
- package/lib/load-config.js +84 -43
- package/lib/log-command.js +18 -0
- package/lib/produce-assembly-file.js +539 -509
- package/lib/produce-assembly-files.js +128 -157
- package/lib/util/collate-asciidoc-attributes.js +32 -0
- package/lib/util/create-resource-key.js +7 -0
- package/lib/util/deep-clone.js +18 -0
- package/lib/util/generate-scoped-id.js +26 -0
- package/lib/util/identify-mutable-attributes.js +25 -0
- package/lib/util/lazy-readable.js +12 -3
- package/lib/util/matcher.js +150 -0
- package/lib/util/parse-resource-ref.js +36 -0
- package/lib/util/resolver.js +36 -0
- package/lib/util/rewriter.js +231 -0
- package/lib/util/rx.js +5 -0
- package/lib/util/to-hash.js +7 -0
- package/lib/util/unconvert-inline-asciidoc.js +22 -14
- package/package.json +15 -15
- package/lib/select-mutable-attributes.js +0 -26
- package/lib/util/compute-out.js +0 -57
- package/lib/util/create-asciidoc-file.js +0 -17
|
@@ -1,164 +1,165 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const createAsciiDocFile = require('./util/create-asciidoc-file')
|
|
3
|
+
const collateAsciiDocAttributes = require('./util/collate-asciidoc-attributes')
|
|
5
4
|
const filterComponentVersions = require('./filter-component-versions')
|
|
6
5
|
const produceAssemblyFile = require('./produce-assembly-file')
|
|
7
|
-
const
|
|
6
|
+
const identifyMutableAttributes = require('./util/identify-mutable-attributes')
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
function produceAssemblyFiles (
|
|
9
|
+
loadAsciiDoc,
|
|
10
|
+
contentCatalog,
|
|
11
|
+
assemblerConfig,
|
|
12
|
+
selectAssemblerProfile,
|
|
13
|
+
selectComponentVersions
|
|
14
|
+
) {
|
|
15
|
+
const { assembly: assemblyConfig } = assemblerConfig
|
|
16
|
+
selectAssemblerProfile ??= (componentVersion) => ({
|
|
17
|
+
attributes: assemblyConfig.attributes,
|
|
15
18
|
doctype: assemblyConfig.doctype,
|
|
16
19
|
insertStartPage: assemblyConfig.insertStartPage,
|
|
17
20
|
rootLevel: assemblyConfig.rootLevel,
|
|
21
|
+
rootPageStyle: assemblyConfig.rootPageStyle,
|
|
18
22
|
sectionMergeStrategy: assemblyConfig.sectionMergeStrategy,
|
|
19
23
|
navigation: componentVersion.navigation,
|
|
20
24
|
xmlIds: assemblyConfig.xmlIds,
|
|
21
25
|
embedReferenceStyle: assemblyConfig.embedReferenceStyle,
|
|
22
26
|
linkReferenceStyle: assemblyConfig.linkReferenceStyle,
|
|
23
27
|
dropExplicitXrefText: assemblyConfig.dropExplicitXrefText,
|
|
28
|
+
revdate: assemblyConfig.revdate,
|
|
29
|
+
logger: assemblyConfig.logger, // for tests
|
|
24
30
|
})
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
delete assemblerAsciiDocAttributes['source-highlighter']
|
|
29
|
-
const publishableFiles = contentCatalog.getFiles().filter((file) => file.out)
|
|
31
|
+
const baseAssemblyAttributes = assemblyConfig.attributes
|
|
32
|
+
const publishableFiles = contentCatalog.getFiles().filter((file) => file.out && file.pub)
|
|
33
|
+
const reducerExtension = require('@asciidoctor/reducer') // require lazily to wait for Opal to be defined
|
|
30
34
|
let siteRoot
|
|
31
35
|
const configMdc = assemblerConfig.file ? { file: { path: assemblerConfig.file } } : {}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
36
|
+
selectComponentVersions ??= filterComponentVersions.bind(null, assemblerConfig.componentVersionFilter)
|
|
37
|
+
return selectComponentVersions(contentCatalog.getComponents()).reduce((accum, componentVersion) => {
|
|
38
|
+
const assemblyModel = selectAssemblerProfile(componentVersion)
|
|
39
|
+
const { attributes: assemblyAttributes, logger, navigation, rootLevel } = assemblyModel
|
|
40
|
+
if (!navigation) return accum
|
|
41
|
+
const contextualLogger = logger ? { warn: logger.warn.bind(logger, configMdc) } : undefined
|
|
42
|
+
const { name: componentName, version, title } = componentVersion
|
|
43
|
+
const asciidocConfig = getAsciiDocConfigWithAsciidoctorReducerExtension(componentVersion, reducerExtension)
|
|
44
|
+
let sourceHighlighter
|
|
45
|
+
if ('source-highlighter' in assemblyAttributes) {
|
|
46
|
+
sourceHighlighter = assemblyAttributes['source-highlighter']
|
|
47
|
+
delete assemblyAttributes['source-highlighter']
|
|
48
|
+
}
|
|
49
|
+
const mergedAsciiDocAttributes = collateAsciiDocAttributes(
|
|
50
|
+
Object.assign({}, asciidocConfig.attributes),
|
|
51
|
+
assemblyAttributes,
|
|
52
|
+
contextualLogger
|
|
53
|
+
)
|
|
54
|
+
const mergedAsciiDocConfig = Object.assign({}, asciidocConfig, {
|
|
55
|
+
attributes: mergedAsciiDocAttributes,
|
|
56
|
+
})
|
|
57
|
+
assemblyModel.filetype = baseAssemblyAttributes['assembler-filetype']
|
|
58
|
+
const outDirname = (assemblyModel.outDirname = contentCatalog.createFile({
|
|
59
|
+
src: {
|
|
47
60
|
component: componentName,
|
|
48
61
|
version,
|
|
62
|
+
componentVersion,
|
|
63
|
+
module: 'ROOT',
|
|
49
64
|
family: 'export',
|
|
50
|
-
relative: '
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
})
|
|
65
|
+
relative: 'index.adoc',
|
|
66
|
+
},
|
|
67
|
+
}).out.dirname)
|
|
68
|
+
assemblyModel.pubRoot = outDirname ? '/' + outDirname : ''
|
|
69
|
+
assemblyModel.siteRoot =
|
|
70
|
+
siteRoot === undefined
|
|
71
|
+
? (siteRoot ??= ((val) => {
|
|
72
|
+
if (!val) return null
|
|
73
|
+
if (val.charAt(val.length - 1) === '/') val = val.substring(0, val.length - 1)
|
|
74
|
+
if (!val || val.charAt() === '/') return { path: val }
|
|
75
|
+
return { url: val, path: extractUrlPath(val) }
|
|
76
|
+
})(mergedAsciiDocAttributes['site-url'] || mergedAsciiDocAttributes['primary-site-url']))
|
|
77
|
+
: siteRoot
|
|
78
|
+
if (assemblyModel.filetype === 'html') {
|
|
79
|
+
let linkRefStyle = assemblyModel.linkReferenceStyle
|
|
80
|
+
if (linkRefStyle === 'absolute' && siteRoot?.url == null) linkRefStyle = 'root-relative'
|
|
81
|
+
if (linkRefStyle === 'root-relative' && siteRoot?.path == null) linkRefStyle = 'relative'
|
|
82
|
+
assemblyModel.linkReferenceStyle = linkRefStyle
|
|
83
|
+
} else if (!(assemblyModel.filetype === 'pdf' && assemblyModel.linkReferenceStyle === 'relative')) {
|
|
84
|
+
assemblyModel.linkReferenceStyle = 'absolute'
|
|
85
|
+
}
|
|
86
|
+
const outlineRoot = { content: title }
|
|
87
|
+
const startPage = resolveStartPage(contentCatalog, componentVersion, assemblyModel.insertStartPage, outlineRoot)
|
|
88
|
+
const mutableAttributes = identifyMutableAttributes(loadAsciiDoc, contentCatalog, startPage, mergedAsciiDocConfig)
|
|
89
|
+
prepareOutlines(navigation, outlineRoot, rootLevel).reduce((any, outline) => {
|
|
90
|
+
const assemblyFile = produceAssemblyFile(
|
|
91
|
+
loadAsciiDoc,
|
|
92
|
+
contentCatalog,
|
|
93
|
+
componentVersion,
|
|
94
|
+
outline,
|
|
95
|
+
publishableFiles,
|
|
96
|
+
mergedAsciiDocConfig,
|
|
97
|
+
mutableAttributes,
|
|
98
|
+
assemblyModel
|
|
99
|
+
)
|
|
100
|
+
if (!assemblyFile) return any
|
|
101
|
+
// NOTE restore source highlighter for conversion if defined in Assembler config
|
|
102
|
+
if (sourceHighlighter !== undefined) {
|
|
103
|
+
assemblyFile.asciidoc.attributes['source-highlighter'] = sourceHighlighter
|
|
104
|
+
} else if (assemblyModel.filetype !== 'html') {
|
|
105
|
+
delete assemblyFile.asciidoc.attributes['source-highlighter']
|
|
103
106
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
)
|
|
107
|
+
return !!accum.push(assemblyFile)
|
|
108
|
+
}, false)
|
|
109
|
+
return accum
|
|
110
|
+
}, [])
|
|
132
111
|
}
|
|
133
112
|
|
|
134
|
-
function getAsciiDocConfigWithAsciidoctorReducerExtension (componentVersion) {
|
|
135
|
-
const asciidoctorReducerExtension = require('@asciidoctor/reducer') // NOTE: must be required lazily
|
|
113
|
+
function getAsciiDocConfigWithAsciidoctorReducerExtension (componentVersion, reducerExtension) {
|
|
136
114
|
const asciidocConfig = componentVersion.asciidoc
|
|
137
|
-
const extensions = [
|
|
115
|
+
const extensions = [reducerExtension]
|
|
138
116
|
const configuredExtensions = asciidocConfig.extensions || []
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
117
|
+
return configuredExtensions.length
|
|
118
|
+
? Object.assign({}, asciidocConfig, {
|
|
119
|
+
extensions: configuredExtensions.reduce((accum, candidate) => {
|
|
120
|
+
if (candidate !== reducerExtension) accum.push(candidate)
|
|
121
|
+
return accum
|
|
122
|
+
}, extensions),
|
|
123
|
+
sourcemap: true,
|
|
124
|
+
})
|
|
125
|
+
: Object.assign({}, asciidocConfig, { extensions, sourcemap: true })
|
|
147
126
|
}
|
|
148
127
|
|
|
149
128
|
function includedInNav (items, url) {
|
|
150
129
|
return items.find((it) => it.url === url || includedInNav(it.items || [], url))
|
|
151
130
|
}
|
|
152
131
|
|
|
153
|
-
function
|
|
154
|
-
|
|
132
|
+
function resolveStartPage (contentCatalog, componentVersion, insertStartPage, outlineRoot) {
|
|
133
|
+
const { name: componentName, version } = componentVersion
|
|
134
|
+
const startPage =
|
|
135
|
+
'startPage' in componentVersion
|
|
136
|
+
? componentVersion.startPage
|
|
137
|
+
: contentCatalog.resolvePage('index.adoc', { component: componentName, version })
|
|
138
|
+
if (startPage && startPage.src.component === componentName && startPage.src.version === version) {
|
|
139
|
+
if (insertStartPage) {
|
|
140
|
+
const navtitle = startPage.asciidoc?.navtitle || outlineRoot.content
|
|
141
|
+
Object.assign(outlineRoot, { navtitle, url: startPage.pub.url, urlType: 'internal', roles: ['page'] })
|
|
142
|
+
}
|
|
143
|
+
return startPage
|
|
144
|
+
}
|
|
145
|
+
return contentCatalog.createFile({
|
|
146
|
+
src: {
|
|
147
|
+
component: componentName,
|
|
148
|
+
version,
|
|
149
|
+
componentVersion,
|
|
150
|
+
module: 'ROOT',
|
|
151
|
+
family: 'page',
|
|
152
|
+
relative: 'index.adoc',
|
|
153
|
+
origin: componentVersion.origins?.values().next().value,
|
|
154
|
+
},
|
|
155
|
+
})
|
|
155
156
|
}
|
|
156
157
|
|
|
157
158
|
function prepareOutlines (navigation, rootEntry, rootLevel) {
|
|
158
159
|
const singleEntry = navigation.length === 1
|
|
159
160
|
const items = navigation.reduce((accum, it) => {
|
|
160
161
|
if (!it.content || (singleEntry && it.url ? it.url === rootEntry.url : it.content === rootEntry.content)) {
|
|
161
|
-
accum.push(...it.items)
|
|
162
|
+
it.items && accum.push(...it.items)
|
|
162
163
|
} else {
|
|
163
164
|
accum.push(it)
|
|
164
165
|
}
|
|
@@ -166,7 +167,7 @@ function prepareOutlines (navigation, rootEntry, rootLevel) {
|
|
|
166
167
|
}, [])
|
|
167
168
|
if (rootLevel === 0) {
|
|
168
169
|
if (rootEntry.url && includedInNav(items, rootEntry.url)) {
|
|
169
|
-
for (const p of ['url', 'urlType']) delete rootEntry[p]
|
|
170
|
+
for (const p of ['roles', 'url', 'urlType']) delete rootEntry[p]
|
|
170
171
|
}
|
|
171
172
|
return [Object.assign(rootEntry, { index: true, items })]
|
|
172
173
|
}
|
|
@@ -177,39 +178,9 @@ function prepareOutlines (navigation, rootEntry, rootLevel) {
|
|
|
177
178
|
return items
|
|
178
179
|
}
|
|
179
180
|
|
|
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
181
|
function extractUrlPath (url) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return urlPath === '/' ? '' : urlPath
|
|
182
|
+
const parsedUrl = url ? URL.parse(url) : null
|
|
183
|
+
return parsedUrl == null || parsedUrl.pathname === '/' ? '' : parsedUrl.pathname
|
|
213
184
|
}
|
|
214
185
|
|
|
215
186
|
module.exports = produceAssemblyFiles
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const ATTR_REF_RX = /\\?\{(\w[\w-]*)\}/g
|
|
4
|
+
|
|
5
|
+
function collateAsciiDocAttributes (collated, additional = {}, logger = undefined) {
|
|
6
|
+
Object.entries(additional).forEach(([name, val]) => {
|
|
7
|
+
if (val && val.constructor === String) {
|
|
8
|
+
let alias
|
|
9
|
+
val = val.replace(ATTR_REF_RX, (ref, refname) => {
|
|
10
|
+
if (ref.charAt() === '\\') return ref.substring(1)
|
|
11
|
+
const refval = collated[refname]
|
|
12
|
+
if (refval == null || refval === false) {
|
|
13
|
+
if (refname in collated && ref === val) {
|
|
14
|
+
alias = refval
|
|
15
|
+
} else if (collated['attribute-missing'] === 'warn') {
|
|
16
|
+
logger?.warn("Skipping reference to missing attribute '%s' in value of '%s' attribute", refname, name)
|
|
17
|
+
}
|
|
18
|
+
return ref
|
|
19
|
+
}
|
|
20
|
+
if (refval.constructor === String) return refval
|
|
21
|
+
if (ref !== val) return refval.toString()
|
|
22
|
+
alias = refval
|
|
23
|
+
return ref
|
|
24
|
+
})
|
|
25
|
+
if (alias !== undefined) val = alias
|
|
26
|
+
}
|
|
27
|
+
collated[name] = val
|
|
28
|
+
})
|
|
29
|
+
return collated
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = collateAsciiDocAttributes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function deepClone (o) {
|
|
4
|
+
switch (o.constructor) {
|
|
5
|
+
case Object:
|
|
6
|
+
return Object.keys(o).reduce((accum, k) => {
|
|
7
|
+
const v = o[k]
|
|
8
|
+
accum[k] = !v || typeof v !== 'object' ? v : deepClone(v)
|
|
9
|
+
return accum
|
|
10
|
+
}, {})
|
|
11
|
+
case Array:
|
|
12
|
+
return o.map((it) => (!it || typeof it !== 'object' ? it : deepClone(it)))
|
|
13
|
+
default:
|
|
14
|
+
return o
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = deepClone
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const HTML_RESERVED_ID_NAMES = 'content header footnotes footer footer-text premable toc toctitle'.split(' ')
|
|
4
|
+
|
|
5
|
+
function generateScopedId (componentSrc, componentVersion, separators, filetype, asXrefTarget) {
|
|
6
|
+
let { component, module: mod, relative } = componentSrc
|
|
7
|
+
let id = relative.replace(/\.adoc$/, '').replace(/[/.]/g, '-')
|
|
8
|
+
let { coordinate: coordinateSeparator, prefix: prefixSeparator } = separators
|
|
9
|
+
if (mod !== 'ROOT' && ~mod.indexOf('.')) mod = mod.replace(/[.]/g, '-')
|
|
10
|
+
if (component !== componentVersion.name) {
|
|
11
|
+
id = [component, mod === 'ROOT' ? '' : mod, id].join(coordinateSeparator)
|
|
12
|
+
} else if (mod !== 'ROOT') {
|
|
13
|
+
if (asXrefTarget && coordinateSeparator === ':' && /(?:pass|stem)$/.test(mod) && /^[a-z]+(?:,[a-z-]+)*$/.test(id)) {
|
|
14
|
+
prefixSeparator = ''
|
|
15
|
+
mod = mod.replace(/(?:pass|stem)$/, '\\$&')
|
|
16
|
+
}
|
|
17
|
+
id = mod + coordinateSeparator + id
|
|
18
|
+
} else if (filetype === 'html' && HTML_RESERVED_ID_NAMES.includes(id)) {
|
|
19
|
+
id = prefixSeparator + id
|
|
20
|
+
prefixSeparator = ''
|
|
21
|
+
}
|
|
22
|
+
if (prefixSeparator && !/^[\p{Alpha}_:]/u.test(id)) id = prefixSeparator + id
|
|
23
|
+
return id
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = generateScopedId
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Identifies AsciiDoc attributes that can be modified in the body of the page.
|
|
5
|
+
*
|
|
6
|
+
* This determination is made by loading a reference page with empty contents, retrieving the list of document attribute
|
|
7
|
+
* names, then removing any attributes that were passed via the API with some exceptions. The reason for this function
|
|
8
|
+
* is to be more prudent with which attributes to manage at the boundaries of an assembled page.
|
|
9
|
+
*/
|
|
10
|
+
function identifyMutableAttributes (loadAsciiDoc, contentCatalog, referencePage, asciidocConfig) {
|
|
11
|
+
const doc = loadAsciiDoc(
|
|
12
|
+
new referencePage.constructor(
|
|
13
|
+
Object.assign({}, referencePage, { contents: Buffer.alloc(0), mediaType: 'text/asciidoc' })
|
|
14
|
+
),
|
|
15
|
+
contentCatalog,
|
|
16
|
+
Object.assign({}, asciidocConfig, { extensions: [], headerOnly: true })
|
|
17
|
+
)
|
|
18
|
+
const immutableAttributeNames = doc.attribute_overrides.$keys()['$+'](['doctype'])
|
|
19
|
+
return Object.entries(doc.getAttributes()).reduce((accum, [name, val]) => {
|
|
20
|
+
if (!immutableAttributeNames.includes(name)) accum[name] = val
|
|
21
|
+
return accum
|
|
22
|
+
}, {})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = identifyMutableAttributes
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const fs = require('node:fs')
|
|
3
4
|
const { PassThrough } = require('node:stream')
|
|
4
5
|
|
|
5
6
|
// adapted from https://github.com/jpommerening/node-lazystream/blob/master/lib/lazystream.js | license: MIT
|
|
6
7
|
class LazyReadable extends PassThrough {
|
|
7
|
-
constructor (
|
|
8
|
-
super(
|
|
8
|
+
constructor (fnOrPath) {
|
|
9
|
+
super()
|
|
10
|
+
const fn = typeof fnOrPath === 'function' ? fnOrPath : fs.createReadStream.bind(null, fnOrPath)
|
|
9
11
|
this._read = function () {
|
|
10
12
|
delete this._read // restores original method
|
|
11
|
-
fn
|
|
13
|
+
const readable = fn().on('error', (err) => {
|
|
14
|
+
let msg = 'Failed to create stream'
|
|
15
|
+
if (err.syscall === 'open') msg += ` for path '${err.path}'`
|
|
16
|
+
this.emit('error', new Error(msg, { cause: err }))
|
|
17
|
+
})
|
|
18
|
+
readable.pipe(this)
|
|
19
|
+
this.fd = readable.fd
|
|
20
|
+
this.path = readable.path
|
|
12
21
|
return this._read.apply(this, arguments)
|
|
13
22
|
}
|
|
14
23
|
this.emit('readable')
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const SPECIAL = { '.': true, '[': true, ']': true, '^': true, $: true, '}': true, ')': true, '|': true }
|
|
4
|
+
const QUANTIFIER_ALTERNATE = { '*': '.*?', '+': '\\+', '?': '.' }
|
|
5
|
+
const QUANTIFIERS = { '*': true, '+': true, '?': true, '@': true, '!': true }
|
|
6
|
+
const RS = '\x1e'
|
|
7
|
+
|
|
8
|
+
const BRACE_RANGE_RX = /^\{(?:([a-z])\.\.([a-z])|(0|[1-9][0-9]*)\.\.(0|[1-9][0-9]*))\}/
|
|
9
|
+
|
|
10
|
+
function filterCollection (candidates, patterns, opts = {}) {
|
|
11
|
+
const rxs = patterns.map(opts.compilePattern ?? compilePattern)
|
|
12
|
+
const test = opts.test ?? ((rx, candidate) => rx.test(candidate))
|
|
13
|
+
return candidates.filter((candidate) => {
|
|
14
|
+
let matched = false
|
|
15
|
+
for (const rx of rxs) {
|
|
16
|
+
let voteIfMatched = true
|
|
17
|
+
if (matched) {
|
|
18
|
+
if (!rx.negated) continue
|
|
19
|
+
voteIfMatched = false
|
|
20
|
+
} else if (rx.negated) {
|
|
21
|
+
voteIfMatched = false
|
|
22
|
+
}
|
|
23
|
+
if (test(rx, candidate)) matched = voteIfMatched
|
|
24
|
+
}
|
|
25
|
+
return matched
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function compilePattern (str) {
|
|
30
|
+
const negated = str.charAt() === '!' ? (str = str.substring(1)).constructor === String : false
|
|
31
|
+
if (str === '**') return Object.assign(new RegExp(), { globstar: true, negated })
|
|
32
|
+
if (str === '*') return Object.assign(new RegExp(), { star: true, negated })
|
|
33
|
+
const buffer = { result: '', brace: undefined, group: undefined }
|
|
34
|
+
let target = 'result'
|
|
35
|
+
let nestedTarget
|
|
36
|
+
for (let i = 0, len = str.length; i < len; i++) {
|
|
37
|
+
const c = str[i]
|
|
38
|
+
if (target === 'brace') {
|
|
39
|
+
if (c === '}') {
|
|
40
|
+
const braceBuffer = buffer.brace
|
|
41
|
+
let braceResult
|
|
42
|
+
if (~braceBuffer.indexOf(RS)) {
|
|
43
|
+
braceResult = braceBuffer === RS ? '' : '(?:' + braceBuffer.replace(/\x1e/g, '|') + ')'
|
|
44
|
+
} else {
|
|
45
|
+
const m = BRACE_RANGE_RX.exec('{' + braceBuffer.replace('\\.\\.', '..') + '}')
|
|
46
|
+
braceResult = m[1] != null ? `[${m[1]}-${m[2]}]` : rangeRe(+m[3], +m[4])
|
|
47
|
+
}
|
|
48
|
+
buffer[(target = nestedTarget === 'group' ? 'group' : 'result')] += braceResult
|
|
49
|
+
buffer.brace = nestedTarget = undefined
|
|
50
|
+
continue
|
|
51
|
+
} else if (c === ',') {
|
|
52
|
+
buffer.brace += RS
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
} else if (target === 'group') {
|
|
56
|
+
if (c === ')') {
|
|
57
|
+
const { group: groupBuffer, groupQuantifier } = buffer
|
|
58
|
+
let groupResult = '(?:' + groupBuffer + ')'
|
|
59
|
+
if (groupQuantifier === '!') {
|
|
60
|
+
groupResult = '(?!' + groupResult + '.*?)'
|
|
61
|
+
} else if (groupQuantifier !== '@') {
|
|
62
|
+
groupResult += groupQuantifier
|
|
63
|
+
}
|
|
64
|
+
buffer[(target = nestedTarget === 'brace' ? 'brace' : 'result')] += groupResult
|
|
65
|
+
buffer.group = buffer.groupQuantifier = nestedTarget = undefined
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (SPECIAL[c]) {
|
|
70
|
+
buffer[target] += '\\' + c
|
|
71
|
+
} else if (c === '(' || QUANTIFIERS[c]) {
|
|
72
|
+
const hasQuantifier = c !== '('
|
|
73
|
+
const atGroupStart = hasQuantifier ? str[i + 1] === '(' : true
|
|
74
|
+
if (target !== 'group' && atGroupStart && isGroup(str.substring(hasQuantifier ? i + 1 : i))) {
|
|
75
|
+
if (target === 'brace') nestedTarget = 'brace'
|
|
76
|
+
buffer[(target = 'group')] = ''
|
|
77
|
+
buffer.groupQuantifier = hasQuantifier ? ++i > 0 && c : ''
|
|
78
|
+
} else if (hasQuantifier) {
|
|
79
|
+
buffer[target] += (QUANTIFIER_ALTERNATE[c] ?? c) + (atGroupStart ? ++i > 0 && '\\(' : '')
|
|
80
|
+
} else {
|
|
81
|
+
buffer[target] += '\\('
|
|
82
|
+
}
|
|
83
|
+
} else if (c === '{') {
|
|
84
|
+
if (target !== 'brace' && isBrace(str.substring(i))) {
|
|
85
|
+
if (target === 'group') nestedTarget = 'group'
|
|
86
|
+
buffer[(target = 'brace')] = ''
|
|
87
|
+
} else {
|
|
88
|
+
buffer[target] += '\\' + c
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
buffer[target] += c
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return Object.assign(new RegExp('^' + cleanup(buffer, target, nestedTarget) + '$'), { negated })
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function cleanup (buffer, target, nestedTarget) {
|
|
98
|
+
if (target === 'result') return buffer.result
|
|
99
|
+
if (target === 'brace') {
|
|
100
|
+
buffer.result += '\\{' + buffer.brace.replace(/\x1e/g, ',')
|
|
101
|
+
buffer.brace = undefined
|
|
102
|
+
} else if (target === 'group') {
|
|
103
|
+
const groupQuantifier = buffer.groupQuantifier
|
|
104
|
+
buffer.result += (QUANTIFIER_ALTERNATE[groupQuantifier] ?? groupQuantifier) + '\\(' + buffer.group
|
|
105
|
+
buffer.group = buffer.groupQuantifier = undefined
|
|
106
|
+
}
|
|
107
|
+
return nestedTarget ? cleanup(buffer, nestedTarget) : buffer.result
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isBrace (str) {
|
|
111
|
+
const endBraceIdx = str.indexOf('}')
|
|
112
|
+
const candidate = ~endBraceIdx ? str.substring(0, endBraceIdx + 1) : ''
|
|
113
|
+
if (!candidate || ~candidate.indexOf('{', 1)) return false
|
|
114
|
+
return ~candidate.indexOf(',') || BRACE_RANGE_RX.test(candidate)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isGroup (str) {
|
|
118
|
+
const endBraceIdx = str.indexOf(')')
|
|
119
|
+
const candidate = ~endBraceIdx ? str.substring(0, endBraceIdx + 1) : ''
|
|
120
|
+
return candidate ? !~candidate.indexOf('(', 1) : false
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function rangeRe (min, max) {
|
|
124
|
+
if (min === max) return String(min)
|
|
125
|
+
min > max && ([min, max] = [max, min])
|
|
126
|
+
if (min < 10 && max < 10) return `[${min}${min + 1 === max ? '' : '-'}${max}]`
|
|
127
|
+
const result = []
|
|
128
|
+
let minStr, minStrLen, toStr
|
|
129
|
+
while (min <= max) {
|
|
130
|
+
let to = (minStrLen = (minStr = String(min)).length) < String(max).length ? 10 ** minStrLen - 1 : max
|
|
131
|
+
const limit = 10 ** minStrLen
|
|
132
|
+
for (let i = 10; i <= limit; i *= 10) {
|
|
133
|
+
const candidate = min + (i - (min % i) || i) - 1
|
|
134
|
+
if (candidate > max) break
|
|
135
|
+
to = candidate
|
|
136
|
+
}
|
|
137
|
+
toStr = String(to)
|
|
138
|
+
let rangesForChunk = ''
|
|
139
|
+
for (let i = 0, len = minStr.length; i < len; i++) {
|
|
140
|
+
const lhs = minStr[i]
|
|
141
|
+
const rhs = toStr[i]
|
|
142
|
+
rangesForChunk += lhs === rhs ? lhs : `[${lhs}${+lhs + 1 === +rhs ? '' : '-'}${rhs}]`
|
|
143
|
+
}
|
|
144
|
+
result.push(rangesForChunk)
|
|
145
|
+
min = to + 1
|
|
146
|
+
}
|
|
147
|
+
return result.length > 1 ? `(?:${result.join('|')})` : result[0]
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = { filterCollection, compilePattern }
|