@antora/assembler 1.0.0-rc.5 → 1.0.0-rc.7

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.
@@ -1,14 +1,19 @@
1
1
  'use strict'
2
2
 
3
+ const collateAsciiDocAttributes = require('./util/collate-asciidoc-attributes')
3
4
  const filterComponentVersions = require('./filter-component-versions')
4
5
  const produceAssemblyFile = require('./produce-assembly-file')
5
- const selectMutableAttributes = require('./select-mutable-attributes')
6
+ const identifyMutableAttributes = require('./util/identify-mutable-attributes')
6
7
 
7
- const ATTR_REF_RX = /\\?\{(\w[\w-]*)\}/g
8
-
9
- function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, selectAssemblyProfile) {
8
+ function produceAssemblyFiles (
9
+ loadAsciiDoc,
10
+ contentCatalog,
11
+ assemblerConfig,
12
+ selectAssemblerProfile,
13
+ selectComponentVersions
14
+ ) {
10
15
  const { assembly: assemblyConfig } = assemblerConfig
11
- selectAssemblyProfile ??= (componentVersion) => ({
16
+ selectAssemblerProfile ??= (componentVersion) => ({
12
17
  attributes: assemblyConfig.attributes,
13
18
  doctype: assemblyConfig.doctype,
14
19
  insertStartPage: assemblyConfig.insertStartPage,
@@ -24,129 +29,134 @@ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, se
24
29
  })
25
30
  const baseAssemblyAttributes = assemblyConfig.attributes
26
31
  const publishableFiles = contentCatalog.getFiles().filter((file) => file.out && file.pub)
32
+ const reducerExtension = require('@asciidoctor/reducer') // require lazily to wait for Opal to be defined
27
33
  let siteRoot
28
34
  const configMdc = assemblerConfig.file ? { file: { path: assemblerConfig.file } } : {}
29
- return filterComponentVersions(contentCatalog.getComponents(), assemblerConfig.componentVersionFilter).reduce(
30
- (accum, componentVersion) => {
31
- const assemblyModel = selectAssemblyProfile(componentVersion)
32
- const { attributes: assemblyAttributes, logger, navigation, rootLevel } = assemblyModel
33
- if (!navigation) return accum
34
- const contextualLogger = logger ? { warn: logger.warn.bind(logger, configMdc) } : undefined
35
- const { name: componentName, version, title } = componentVersion
36
- const componentVersionAsciiDocConfig = getAsciiDocConfigWithAsciidoctorReducerExtension(componentVersion)
37
- let sourceHighlighter
38
- if ('source-highlighter' in assemblyAttributes) {
39
- sourceHighlighter = assemblyAttributes['source-highlighter']
40
- delete assemblyAttributes['source-highlighter']
41
- }
42
- const mergedAsciiDocAttributes = collateAsciiDocAttributes(
43
- Object.assign({}, componentVersionAsciiDocConfig.attributes),
44
- assemblyAttributes,
45
- contextualLogger
35
+ selectComponentVersions ??= filterComponentVersions.bind(null, assemblerConfig.componentVersionFilter)
36
+ return selectComponentVersions(contentCatalog.getComponents()).reduce((accum, componentVersion) => {
37
+ const assemblyModel = selectAssemblerProfile(componentVersion)
38
+ const { attributes: assemblyAttributes, logger, navigation, rootLevel } = assemblyModel
39
+ if (!navigation) return accum
40
+ const contextualLogger = logger ? { warn: logger.warn.bind(logger, configMdc) } : undefined
41
+ const { name: componentName, version, title } = componentVersion
42
+ const asciidocConfig = getAsciiDocConfigWithAsciidoctorReducerExtension(componentVersion, reducerExtension)
43
+ let sourceHighlighter
44
+ if ('source-highlighter' in assemblyAttributes) {
45
+ sourceHighlighter = assemblyAttributes['source-highlighter']
46
+ delete assemblyAttributes['source-highlighter']
47
+ }
48
+ const mergedAsciiDocAttributes = collateAsciiDocAttributes(
49
+ Object.assign({}, asciidocConfig.attributes),
50
+ assemblyAttributes,
51
+ contextualLogger
52
+ )
53
+ const mergedAsciiDocConfig = Object.assign({}, asciidocConfig, {
54
+ attributes: mergedAsciiDocAttributes,
55
+ })
56
+ assemblyModel.filetype = baseAssemblyAttributes['assembler-filetype']
57
+ const outDirname = (assemblyModel.outDirname = contentCatalog.createFile({
58
+ src: {
59
+ component: componentName,
60
+ version,
61
+ componentVersion,
62
+ module: 'ROOT',
63
+ family: 'export',
64
+ relative: 'index.adoc',
65
+ },
66
+ }).out.dirname)
67
+ assemblyModel.pubRoot = outDirname ? '/' + outDirname : ''
68
+ assemblyModel.siteRoot =
69
+ siteRoot === undefined
70
+ ? (siteRoot ??= ((val) => {
71
+ if (!val) return null
72
+ if (val.charAt(val.length - 1) === '/') val = val.substring(0, val.length - 1)
73
+ if (!val || val.charAt() === '/') return { path: val }
74
+ return { url: val, path: extractUrlPath(val) }
75
+ })(mergedAsciiDocAttributes['site-url'] || mergedAsciiDocAttributes['primary-site-url']))
76
+ : siteRoot
77
+ if (assemblyModel.filetype === 'html') {
78
+ let linkRefStyle = assemblyModel.linkReferenceStyle
79
+ if (linkRefStyle === 'absolute' && siteRoot?.url == null) linkRefStyle = 'root-relative'
80
+ if (linkRefStyle === 'root-relative' && siteRoot?.path == null) linkRefStyle = 'relative'
81
+ assemblyModel.linkReferenceStyle = linkRefStyle
82
+ } else if (!(assemblyModel.filetype === 'pdf' && assemblyModel.linkReferenceStyle === 'relative')) {
83
+ assemblyModel.linkReferenceStyle = 'absolute'
84
+ }
85
+ const outlineRoot = { content: title }
86
+ const startPage = resolveStartPage(contentCatalog, componentVersion, assemblyModel.insertStartPage, outlineRoot)
87
+ const mutableAttributes = identifyMutableAttributes(loadAsciiDoc, contentCatalog, startPage, mergedAsciiDocConfig)
88
+ prepareOutlines(navigation, outlineRoot, rootLevel).reduce((any, outline) => {
89
+ const assemblyFile = produceAssemblyFile(
90
+ loadAsciiDoc,
91
+ contentCatalog,
92
+ componentVersion,
93
+ outline,
94
+ publishableFiles,
95
+ mergedAsciiDocConfig,
96
+ mutableAttributes,
97
+ assemblyModel
46
98
  )
47
- const mergedAsciiDocConfig = Object.assign({}, componentVersionAsciiDocConfig, {
48
- attributes: mergedAsciiDocAttributes,
49
- })
50
- assemblyModel.filetype = baseAssemblyAttributes['assembler-filetype']
51
- const outDirname = (assemblyModel.outDirname = contentCatalog.createFile({
52
- src: {
53
- component: componentName,
54
- version,
55
- componentVersion,
56
- module: 'ROOT',
57
- family: 'export',
58
- relative: 'index.adoc',
59
- },
60
- }).out.dirname)
61
- assemblyModel.pubRoot = outDirname ? '/' + outDirname : ''
62
- assemblyModel.siteRoot =
63
- siteRoot === undefined
64
- ? (siteRoot ??= ((val) => {
65
- if (!val) return null
66
- if (val.charAt(val.length - 1) === '/') val = val.substring(0, val.length - 1)
67
- if (!val || val.charAt() === '/') return { path: val }
68
- return { url: val, path: extractUrlPath(val) }
69
- })(mergedAsciiDocAttributes['site-url'] || mergedAsciiDocAttributes['primary-site-url']))
70
- : siteRoot
71
- if (assemblyModel.filetype === 'html') {
72
- let linkRefStyle = assemblyModel.linkReferenceStyle
73
- if (linkRefStyle === 'absolute' && siteRoot?.url == null) linkRefStyle = 'root-relative'
74
- if (linkRefStyle === 'root-relative' && siteRoot?.path == null) linkRefStyle = 'relative'
75
- assemblyModel.linkReferenceStyle = linkRefStyle
76
- } else if (!(assemblyModel.filetype === 'pdf' && assemblyModel.linkReferenceStyle === 'relative')) {
77
- assemblyModel.linkReferenceStyle = 'absolute'
99
+ if (!assemblyFile) return any
100
+ // NOTE restore source highlighter for conversion if defined in Assembler config
101
+ if (sourceHighlighter !== undefined) {
102
+ assemblyFile.asciidoc.attributes['source-highlighter'] = sourceHighlighter
103
+ } else if (assemblyModel.filetype !== 'html') {
104
+ delete assemblyFile.asciidoc.attributes['source-highlighter']
78
105
  }
79
- const rootEntry = { content: title }
80
- let startPage =
81
- 'startPage' in componentVersion
82
- ? componentVersion.startPage
83
- : contentCatalog.resolvePage('index.adoc', { component: componentName, version })
84
- if (startPage && startPage.src.component === componentName && startPage.src.version === version) {
85
- if (assemblyModel.insertStartPage) {
86
- const navtitle = startPage.asciidoc?.navtitle || rootEntry.content
87
- Object.assign(rootEntry, { navtitle, url: startPage.pub.url, urlType: 'internal', roles: ['page'] })
88
- }
89
- } else {
90
- // Q: should we always use a reference page as startPage for computing mutableAttributes?
91
- startPage = contentCatalog.createFile({
92
- src: {
93
- component: componentVersion.name,
94
- version: componentVersion.version,
95
- componentVersion,
96
- module: 'ROOT',
97
- family: 'page',
98
- relative: 'index.adoc',
99
- origin: (componentVersion.origins || [])[0],
100
- },
101
- })
102
- startPage.path = ['modules', startPage.src.module, startPage.src.family + 's', startPage.src.relative].join('/')
103
- }
104
- const mutableAttributes = selectMutableAttributes(loadAsciiDoc, contentCatalog, startPage, mergedAsciiDocConfig)
105
- prepareOutlines(navigation, rootEntry, rootLevel).reduce((any, outline) => {
106
- const assemblyFile = produceAssemblyFile(
107
- loadAsciiDoc,
108
- contentCatalog,
109
- componentVersion,
110
- outline,
111
- publishableFiles,
112
- mergedAsciiDocConfig,
113
- mutableAttributes,
114
- assemblyModel
115
- )
116
- if (!assemblyFile) return any
117
- // NOTE restore source highlighter for conversion if defined in Assembler config
118
- if (sourceHighlighter !== undefined) {
119
- assemblyFile.asciidoc.attributes['source-highlighter'] = sourceHighlighter
120
- } else if (assemblyModel.filetype !== 'html') {
121
- delete assemblyFile.asciidoc.attributes['source-highlighter']
122
- }
123
- return !!accum.push(assemblyFile)
124
- }, false)
125
- return accum
126
- },
127
- []
128
- )
106
+ return !!accum.push(assemblyFile)
107
+ }, false)
108
+ return accum
109
+ }, [])
129
110
  }
130
111
 
131
- function getAsciiDocConfigWithAsciidoctorReducerExtension (componentVersion) {
132
- const asciidoctorReducerExtension = require('@asciidoctor/reducer') // NOTE: must be required lazily
112
+ function getAsciiDocConfigWithAsciidoctorReducerExtension (componentVersion, reducerExtension) {
133
113
  const asciidocConfig = componentVersion.asciidoc
134
- const extensions = [asciidoctorReducerExtension]
114
+ const extensions = [reducerExtension]
135
115
  const configuredExtensions = asciidocConfig.extensions || []
136
- if (!configuredExtensions.length) return Object.assign({}, asciidocConfig, { extensions, sourcemap: true })
137
- return Object.assign({}, asciidocConfig, {
138
- extensions: configuredExtensions.reduce((accum, candidate) => {
139
- if (candidate !== asciidoctorReducerExtension) accum.push(candidate)
140
- return accum
141
- }, extensions),
142
- sourcemap: true,
143
- })
116
+ return configuredExtensions.length
117
+ ? Object.assign({}, asciidocConfig, {
118
+ extensions: configuredExtensions.reduce((accum, candidate) => {
119
+ if (candidate !== reducerExtension) accum.push(candidate)
120
+ return accum
121
+ }, extensions),
122
+ sourcemap: true,
123
+ })
124
+ : Object.assign({}, asciidocConfig, { extensions, sourcemap: true })
144
125
  }
145
126
 
146
127
  function includedInNav (items, url) {
147
128
  return items.find((it) => it.url === url || includedInNav(it.items || [], url))
148
129
  }
149
130
 
131
+ function resolveStartPage (contentCatalog, componentVersion, insertStartPage, outlineRoot) {
132
+ const { name: componentName, version } = componentVersion
133
+ let startPage =
134
+ 'startPage' in componentVersion
135
+ ? componentVersion.startPage
136
+ : contentCatalog.resolvePage('index.adoc', { component: componentName, version })
137
+ if (startPage && startPage.src.component === componentName && startPage.src.version === version) {
138
+ if (insertStartPage) {
139
+ const navtitle = startPage.asciidoc?.navtitle || outlineRoot.content
140
+ Object.assign(outlineRoot, { navtitle, url: startPage.pub.url, urlType: 'internal', roles: ['page'] })
141
+ }
142
+ } else {
143
+ startPage = contentCatalog.createFile({
144
+ src: {
145
+ component: componentName,
146
+ version,
147
+ componentVersion,
148
+ module: 'ROOT',
149
+ family: 'page',
150
+ relative: 'index.adoc',
151
+ origin: componentVersion.origins?.values().next().value,
152
+ },
153
+ })
154
+ // FIXME remove once upgrading to Antora 3.2.0
155
+ startPage.path = ['modules', startPage.src.module, startPage.src.family + 's', startPage.src.relative].join('/')
156
+ }
157
+ return startPage
158
+ }
159
+
150
160
  function prepareOutlines (navigation, rootEntry, rootLevel) {
151
161
  const singleEntry = navigation.length === 1
152
162
  const items = navigation.reduce((accum, it) => {
@@ -170,37 +180,9 @@ function prepareOutlines (navigation, rootEntry, rootLevel) {
170
180
  return items
171
181
  }
172
182
 
173
- function collateAsciiDocAttributes (collated, additional, logger) {
174
- Object.entries(additional).forEach(([name, val]) => {
175
- if (val && val.constructor === String) {
176
- let alias
177
- val = val.replace(ATTR_REF_RX, (ref, refname) => {
178
- if (ref.charAt() === '\\') return ref.substring(1)
179
- const refval = collated[refname]
180
- if (refval == null || refval === false) {
181
- if (refname in collated && ref === val) {
182
- alias = refval
183
- } else if (collated['attribute-missing'] === 'warn') {
184
- logger?.warn("Skipping reference to missing attribute '%s' in value of '%s' attribute", refname, name)
185
- }
186
- return ref
187
- }
188
- if (refval.constructor === String) return refval
189
- if (ref !== val) return refval.toString()
190
- alias = refval
191
- return ref
192
- })
193
- if (alias !== undefined) val = alias
194
- }
195
- collated[name] = val
196
- })
197
- return collated
198
- }
199
-
200
183
  function extractUrlPath (url) {
201
- if (!url) return ''
202
- const urlPath = new URL(url).pathname
203
- return urlPath === '/' ? '' : urlPath
184
+ const parsedUrl = url ? URL.parse(url) : null
185
+ return parsedUrl == null || parsedUrl.pathname === '/' ? '' : parsedUrl.pathname
204
186
  }
205
187
 
206
188
  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,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
+ asciidocConfig
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,20 +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 (fn, options) {
8
- super(options)
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.call(this, options)
12
- .on('error', (err) => {
13
- let msg = 'Failed to create stream'
14
- if (err.syscall === 'open') msg += ` for path '${err.path}'`
15
- this.emit('error', new Error(msg, { cause: err }))
16
- })
17
- .pipe(this)
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
18
21
  return this._read.apply(this, arguments)
19
22
  }
20
23
  this.emit('readable')
@@ -4,6 +4,7 @@ const createResourceKey = require('./create-resource-key')
4
4
  const generateScopedId = require('./generate-scoped-id')
5
5
  const parseResourceRef = require('./parse-resource-ref')
6
6
  const { resolveEmbedTarget, resolveLinkTarget } = require('./resolver')
7
+ const toHash = require('./to-hash')
7
8
 
8
9
  const { NAMED_ID_ATTR_RX } = require('./rx')
9
10
 
@@ -19,7 +20,7 @@ function rewriteXrefs (
19
20
  doc,
20
21
  sourceLocation
21
22
  ) {
22
- return line.replace(/(?<![\\+])xref:((?:\.\/)?[\p{Alpha}0-9_/.{#].*?)\[(|[\s\S]*?[^\\])\]/gu, (_, target, text) =>
23
+ return line.replace(/(?<![\\+])xref:((?:\.\/|:)?[\p{Alpha}0-9_/.{#].*?)\[(|[\s\S]*?[^\\])\]/gu, (_, target, text) =>
23
24
  rewriteXref(
24
25
  target,
25
26
  text,
@@ -50,7 +51,9 @@ function rewriteXref (
50
51
  sourceLocation
51
52
  ) {
52
53
  const rawTarget = target
53
- if (doc && ~target.indexOf('{')) target = doc.$sub_attributes(target, global.Opal.hash({ attribute_missing: 'skip' }))
54
+ if (sourceLocation && ~target.indexOf('{')) {
55
+ target = doc.$sub_attributes(target, toHash({ attribute_missing: 'skip' }))
56
+ }
54
57
  let fragment, resourceRef
55
58
  const hashIdx = target.indexOf('#')
56
59
  if (~hashIdx) {
@@ -72,22 +75,24 @@ function rewriteXref (
72
75
  }
73
76
  }
74
77
  if (family !== 'page' || !(resource = pagesInOutline?.get(createResourceKey(resourceId)))) {
78
+ let defaultText
75
79
  if ((resource = contentCatalog.getById(resourceId))?.pub) {
76
80
  const { linkReferenceStyle, pubRoot, siteRoot } = assemblyModel
77
- text ||= resource.asciidoc?.xreftext || rawTarget
81
+ defaultText = resource.asciidoc?.xreftext || rawTarget
78
82
  if (siteRoot || linkReferenceStyle === 'relative') {
79
83
  const hash = fragment && !(family === 'page' && fragment === resource.asciidoc.id) ? '#' + fragment : ''
80
- return `${resolveLinkTarget(resource, siteRoot, pubRoot, linkReferenceStyle, escapeForInline)}${hash}[${text}]`
84
+ const linkTarget = resolveLinkTarget(resource, siteRoot, pubRoot, linkReferenceStyle, escapeForInline)
85
+ return `${linkTarget}${hash}[${updateAttrlist(doc, text, defaultText)}]`
81
86
  }
82
- if (doc) {
87
+ if (sourceLocation) {
83
88
  const msg = `Cannot create external ${family} reference in assembly because site URL is unknown: ${rawTarget}`
84
89
  doc.logger.warn(doc.createLogMessage(msg, { source_location: sourceLocation }))
85
90
  }
91
+ } else if (~target.indexOf(':')) {
92
+ defaultText = rawTarget // use explicit text so AsciiDoc processor displays it correctly
86
93
  }
87
- const linkAttrlist = text
88
- ? (~text.indexOf(',') ? `"${text}"` : text) + ',role=unresolved'
89
- : (~target.indexOf(':') ? `${rawTarget},` : '') + 'role=unresolved'
90
- return `link:${rawTarget}[${linkAttrlist}]`
94
+ const linkAttrlist = updateAttrlist(doc, text, defaultText, 'unresolved')
95
+ return `link:${rawTarget.charAt() === ':' ? resourceId.module + rawTarget : rawTarget}[${linkAttrlist}]`
91
96
  }
92
97
  if (fragment === resource.asciidoc.id) fragment = ''
93
98
  if (
@@ -123,7 +128,7 @@ function rewriteInlineImages (line, contentCatalog, assemblyModel, ctx, assets,
123
128
  }
124
129
 
125
130
  function rewriteImageRef (target, contentCatalog, assemblyModel, ctx, assets, escapeForInline, doc) {
126
- if (doc && ~target.indexOf('{')) target = doc.$sub_attributes(target, global.Opal.hash({ attribute_missing: 'skip' }))
131
+ if (doc && ~target.indexOf('{')) target = doc.$sub_attributes(target, toHash({ attribute_missing: 'skip' }))
127
132
  const image = isResourceRef(target) && contentCatalog.resolveResource(target, ctx, 'image', ['image'])
128
133
  if (!image?.out) return
129
134
  let newTarget
@@ -135,7 +140,6 @@ function rewriteImageRef (target, contentCatalog, assemblyModel, ctx, assets, es
135
140
  newTarget = resolveLinkTarget(image, siteRoot, pubRoot, linkReferenceStyle, escapeForInline ?? false, false)
136
141
  if (linkReferenceStyle === 'relative') assets.add(image)
137
142
  }
138
- if (!newTarget) return
139
143
  return newTarget
140
144
  }
141
145
 
@@ -196,6 +200,27 @@ function isResourceRef (str) {
196
200
  return !(~str.indexOf(':') && (~str.indexOf('://') || (str.startsWith('data:') && ~str.indexOf(','))))
197
201
  }
198
202
 
203
+ function updateAttrlist (doc, attrlist, defaultText, role) {
204
+ const attrs = { named: {}, pos: [] }
205
+ if (attrlist) {
206
+ const normalize = ~attrlist.indexOf('\n')
207
+ if (normalize) attrlist = attrlist.replace(/\n/g, '\x1e')
208
+ doc
209
+ .$parse_attributes(attrlist)
210
+ .$entries()
211
+ .forEach(([k, v]) => {
212
+ v = v['$nil?']() ? '' : normalize ? v.replace(/\x1e/g, '\n') : v
213
+ typeof k === 'number' ? attrs.pos.push(v) : (attrs.named[k] = v)
214
+ })
215
+ }
216
+ if (defaultText) attrs.pos[0] ||= defaultText
217
+ if (role) attrs.named.role = (attrs.named.role ? attrs.named.role + ' ' : '') + role
218
+ const entries = []
219
+ for (const v of attrs.pos) entries.push(~v.indexOf(',') || ~v.indexOf('=') ? `"${v}"` : v)
220
+ for (const [k, v] of Object.entries(attrs.named)) entries.push(`${k}=` + (~v.indexOf(',') ? `"${v}"` : v))
221
+ return entries.join(',')
222
+ }
223
+
199
224
  module.exports = {
200
225
  rewriteXref,
201
226
  rewriteXrefs,
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ function toHash (obj = {}) {
4
+ return global.Opal.hash(obj)
5
+ }
6
+
7
+ module.exports = toHash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antora/assembler",
3
- "version": "1.0.0-rc.5",
3
+ "version": "1.0.0-rc.7",
4
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)",
@@ -27,20 +27,18 @@
27
27
  ".": "./lib/index.js",
28
28
  "./filter-component-versions": "./lib/filter-component-versions.js",
29
29
  "./load-config": "./lib/load-config.js",
30
- "./log-command": "./lib/log-command.js",
31
- "./produce-assembly-file": "./lib/produce-assembly-file.js",
32
- "./produce-assembly-files": "./lib/produce-assembly-files.js",
33
- "./select-mutable-attributes": "./lib/select-mutable-attributes.js",
34
30
  "./package.json": "./package.json"
35
31
  },
36
32
  "imports": {
37
33
  "#asciidoctor-log-adapter": "./adapters/asciidoctor/jsonl-logger.rb",
34
+ "#identify-mutable-attributes": "./lib/util/identify-mutable-attributes.js",
35
+ "#produce-assembly-files": "./lib/produce-assembly-files.js",
38
36
  "#unconvert-inline-asciidoc": "./lib/util/unconvert-inline-asciidoc.js"
39
37
  },
40
38
  "dependencies": {
41
- "@asciidoctor/reducer": "~1.1",
42
39
  "@antora/expand-path-helper": "~3.0",
43
40
  "@antora/run-command-helper": "~1.1",
41
+ "@asciidoctor/reducer": "~1.1",
44
42
  "js-yaml": "~5.2"
45
43
  },
46
44
  "devDependencies": {
@@ -1,27 +0,0 @@
1
- 'use strict'
2
-
3
- function selectMutableAttributes (loadAsciiDoc, contentCatalog, referencePage, asciidocConfig) {
4
- const doc = loadAsciiDoc(
5
- new referencePage.constructor(
6
- Object.assign({}, referencePage, { contents: Buffer.alloc(0), mediaType: 'text/asciidoc' })
7
- ),
8
- contentCatalog,
9
- asciidocConfig
10
- )
11
- const additionalMutableNames = [
12
- 'page-component-name',
13
- 'page-component-version',
14
- 'page-version',
15
- 'page-component-display-version',
16
- 'page-component-title',
17
- ]
18
- // we could consider using an Asciidoctor extension here to grab attributes passed via the API instead
19
- const immutableAttributeNames = doc.attribute_overrides.$keys()['$-'](additionalMutableNames)
20
- immutableAttributeNames.push('doctype')
21
- return Object.entries(doc.getAttributes()).reduce((accum, [name, val]) => {
22
- if (!immutableAttributeNames.includes(name)) accum[name] = val
23
- return accum
24
- }, {})
25
- }
26
-
27
- module.exports = selectMutableAttributes