@antora/assembler 1.0.0-rc.1 → 1.0.0-rc.11

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,18 +1,24 @@
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,
15
20
  rootLevel: assemblyConfig.rootLevel,
21
+ rootPageStyle: assemblyConfig.rootPageStyle,
16
22
  sectionMergeStrategy: assemblyConfig.sectionMergeStrategy,
17
23
  navigation: componentVersion.navigation,
18
24
  xmlIds: assemblyConfig.xmlIds,
@@ -24,129 +30,131 @@ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, se
24
30
  })
25
31
  const baseAssemblyAttributes = assemblyConfig.attributes
26
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
27
34
  let siteRoot
28
35
  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
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: {
60
+ component: componentName,
61
+ version,
62
+ componentVersion,
63
+ module: 'ROOT',
64
+ family: 'export',
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
46
99
  )
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'
78
- }
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('/')
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
- 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
- )
107
+ return !!accum.push(assemblyFile)
108
+ }, false)
109
+ return accum
110
+ }, [])
129
111
  }
130
112
 
131
- function getAsciiDocConfigWithAsciidoctorReducerExtension (componentVersion) {
132
- const asciidoctorReducerExtension = require('@asciidoctor/reducer') // NOTE: must be required lazily
113
+ function getAsciiDocConfigWithAsciidoctorReducerExtension (componentVersion, reducerExtension) {
133
114
  const asciidocConfig = componentVersion.asciidoc
134
- const extensions = [asciidoctorReducerExtension]
115
+ const extensions = [reducerExtension]
135
116
  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
- })
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 })
144
126
  }
145
127
 
146
128
  function includedInNav (items, url) {
147
129
  return items.find((it) => it.url === url || includedInNav(it.items || [], url))
148
130
  }
149
131
 
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
+ })
156
+ }
157
+
150
158
  function prepareOutlines (navigation, rootEntry, rootLevel) {
151
159
  const singleEntry = navigation.length === 1
152
160
  const items = navigation.reduce((accum, it) => {
@@ -170,37 +178,9 @@ function prepareOutlines (navigation, rootEntry, rootLevel) {
170
178
  return items
171
179
  }
172
180
 
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
181
  function extractUrlPath (url) {
201
- if (!url) return ''
202
- const urlPath = new URL(url).pathname
203
- return urlPath === '/' ? '' : urlPath
182
+ const parsedUrl = url ? URL.parse(url) : null
183
+ return parsedUrl == null || parsedUrl.pathname === '/' ? '' : parsedUrl.pathname
204
184
  }
205
185
 
206
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
@@ -6,6 +6,7 @@ function generateScopedId (componentSrc, componentVersion, separators, filetype,
6
6
  let { component, module: mod, relative } = componentSrc
7
7
  let id = relative.replace(/\.adoc$/, '').replace(/[/.]/g, '-')
8
8
  let { coordinate: coordinateSeparator, prefix: prefixSeparator } = separators
9
+ if (mod !== 'ROOT' && ~mod.indexOf('.')) mod = mod.replace(/[.]/g, '-')
9
10
  if (component !== componentVersion.name) {
10
11
  id = [component, mod === 'ROOT' ? '' : mod, id].join(coordinateSeparator)
11
12
  } else if (mod !== 'ROOT') {
@@ -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 (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).on('error', this.emit.bind(this, 'error')).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
12
21
  return this._read.apply(this, arguments)
13
22
  }
14
23
  this.emit('readable')
@@ -27,7 +27,7 @@ function filterCollection (candidates, patterns, opts = {}) {
27
27
  }
28
28
 
29
29
  function compilePattern (str) {
30
- const negated = str.charAt() === '!' ? typeof (str = str.substring(1)) === 'string' : false
30
+ const negated = str.charAt() === '!' ? (str = str.substring(1)).constructor === String : false
31
31
  if (str === '**') return Object.assign(new RegExp(), { globstar: true, negated })
32
32
  if (str === '*') return Object.assign(new RegExp(), { star: true, negated })
33
33
  const buffer = { result: '', brace: undefined, group: undefined }
@@ -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
- doc.logger.warn(doc.createLogMessage(msg, { source_location: sourceLocation }))
89
+ doc.getLogger().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
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const ATTRIBUTE_REFERENCE_RX = /\{[a-z0-9_][a-z0-9_-]*\}/g
3
+ const ATTRIBUTE_REFERENCE_RX = /\{[A-Za-z0-9_][A-Za-z0-9_-]*\}/g
4
4
  const STRICT_WORD_CHAR_RX = /[\p{L}\d_]/u
5
5
  const WORD_CHAR_RX = /[\p{L}\d_;}:<>]/u
6
6
  const XML_SPECIAL_CHARS = { '&lt;': '<', '&gt;': '>', '&amp;': '&' }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antora/assembler",
3
- "version": "1.0.0-rc.1",
3
+ "version": "1.0.0-rc.11",
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,26 +27,24 @@
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",
44
- "js-yaml": "~4.2"
41
+ "@asciidoctor/reducer": "~1.1",
42
+ "js-yaml": "~5.3"
45
43
  },
46
44
  "devDependencies": {
47
- "@antora/asciidoc-loader": "3.2.0-rc.2",
48
- "@antora/navigation-builder": "3.2.0-rc.2",
49
- "@antora/site-publisher": "3.2.0-rc.2"
45
+ "@antora/asciidoc-loader": "3.2.0-rc.3",
46
+ "@antora/navigation-builder": "3.2.0-rc.3",
47
+ "@antora/site-publisher": "3.2.0-rc.3"
50
48
  },
51
49
  "engines": {
52
50
  "node": ">=20.0.0"
@@ -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