@antora/assembler 1.0.0-beta.6 → 1.0.0-beta.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.
@@ -13,6 +13,7 @@ const { stringify: toJSON } = JSON
13
13
 
14
14
  const invariably = { new: () => ({}), void: () => undefined }
15
15
  const PACKAGE_NAME = require('../package.json').name
16
+ const NEWLINE_RX = /(?:\r?\n)+/
16
17
 
17
18
  async function assembleContent (playbook, contentCatalog, converter, { configSource, navigationCatalog }) {
18
19
  const assemblerConfig = await loadConfig(playbook, configSource)
@@ -26,10 +27,20 @@ async function assembleContent (playbook, contentCatalog, converter, { configSou
26
27
  }
27
28
  const generatorFunctions = context.getFunctions()
28
29
  const { loadAsciiDoc = require('@antora/asciidoc-loader') } = generatorFunctions
29
- const targetBackend = converter == null ? undefined : (converter.backend ?? converter.extname?.slice(1))
30
+ const {
31
+ convert = converter,
32
+ getDefaultCommand,
33
+ extname: targetExtname = '',
34
+ backend: targetBackend = targetExtname.slice(1),
35
+ embedReferenceStyle = 'relative',
36
+ mediaType: targetMediaType,
37
+ loggerName = PACKAGE_NAME,
38
+ } = converter ?? {}
30
39
  const { assembly: assemblyConfig, build: buildConfig } = assemblerConfig
40
+ assemblyConfig.embedReferenceStyle = embedReferenceStyle
31
41
  const { profile = targetBackend } = assemblyConfig
32
42
  const intrinsicAttributes = { 'loader-assembler': '' }
43
+ buildConfig.cwd ??= process.cwd()
33
44
  if (profile) {
34
45
  buildConfig.dir ??= ospath.join(playbook.dir ?? process.cwd(), `build/assembler-${profile}`)
35
46
  intrinsicAttributes[`assembler-profile-${profile}`] = ''
@@ -41,6 +52,11 @@ async function assembleContent (playbook, contentCatalog, converter, { configSou
41
52
  } else {
42
53
  buildConfig.dir ??= ospath.join(playbook.dir ?? process.cwd(), 'build/assembler')
43
54
  }
55
+ if (targetExtname) {
56
+ const targetFiletype = targetExtname.slice(1)
57
+ intrinsicAttributes[`assembler-filetype-${targetFiletype}`] = ''
58
+ intrinsicAttributes['assembler-filetype'] = targetFiletype
59
+ }
44
60
  Object.assign(assemblerConfig.asciidoc.attributes, intrinsicAttributes)
45
61
  const assemblyFiles = produceAssemblyFiles(
46
62
  loadAsciiDoc,
@@ -48,21 +64,23 @@ async function assembleContent (playbook, contentCatalog, converter, { configSou
48
64
  assemblerConfig,
49
65
  createResolveAssemblyModel(context, contentCatalog, assemblyConfig, intrinsicAttributes, navigationCatalog)
50
66
  )
51
- const { convert = converter, extname: targetExtname, mediaType: targetMediaType } = converter ?? {}
52
- if (!(convert && assemblyFiles.length)) return assemblyFiles
67
+ if (!(assemblyFiles.length && typeof convert === 'function')) return assemblyFiles
68
+ if (typeof getDefaultCommand === 'function') {
69
+ buildConfig.command = await getDefaultCommand(buildConfig.cwd)
70
+ }
53
71
  const { publishSite: publishFiles = require('@antora/site-publisher') } = generatorFunctions
54
72
  await prepareWorkspace(publishFiles, assemblyFiles, buildConfig)
55
73
  const boundConvert = convert.bind(context)
56
74
  return new PromiseQueue({ concurrency: buildConfig.processLimit })
57
75
  .add(
58
76
  assemblyFiles.map((doc) => async () => {
59
- const convertAttributes = prepareConvertAttributes(doc, targetExtname, buildConfig)
77
+ const relativeToOutput = embedReferenceStyle === 'output-relative'
78
+ const convertAttributes = prepareConvertAttributes(doc, targetExtname, relativeToOutput, buildConfig)
60
79
  if (buildConfig.mkdirs) await fsp.mkdir(convertAttributes.outdir, { recursive: true, force: true })
61
- return boundConvert(doc, convertAttributes, buildConfig).then(
62
- (fileOrContents = new LazyReadable(() => fs.createReadStream(convertAttributes.outfile))) => {
63
- return coerceToExportFormat(doc, targetBackend, targetExtname, targetMediaType, fileOrContents)
64
- }
65
- )
80
+ return boundConvert(doc, convertAttributes, buildConfig).then((result) => {
81
+ const fileOrContents = resolveFileOrContents.call(context, result, convertAttributes, buildConfig, loggerName)
82
+ return coerceToExportFormat(doc, targetBackend, targetExtname, targetMediaType, fileOrContents)
83
+ })
66
84
  })
67
85
  )
68
86
  .toPromise()
@@ -99,14 +117,14 @@ async function assembleContent (playbook, contentCatalog, converter, { configSou
99
117
  })
100
118
  }
101
119
 
102
- function createResolveAssemblyModel (context, contentCatalog, shared, intrinsicAttributes, navigationCatalog) {
103
- const logger = context.getLogger ? context.getLogger(PACKAGE_NAME) : undefined
120
+ function createResolveAssemblyModel (context, contentCatalog, common, intrinsicAttributes, navigationCatalog) {
121
+ const logger = context.getLogger?.(PACKAGE_NAME)
104
122
  const { assemblerProfiles } = context.getVariables()
105
123
  if (!assemblerProfiles) {
106
124
  return (componentVersion) => {
107
125
  const navigation =
108
126
  navigationCatalog?.getNavigation(componentVersion.name, componentVersion.version) ?? componentVersion.navigation
109
- return Object.assign({ logger }, shared, { navigation })
127
+ return Object.assign({ logger }, common, { navigation })
110
128
  }
111
129
  }
112
130
  const boundSendToLog = sendToLog.bind(logger)
@@ -116,7 +134,7 @@ function createResolveAssemblyModel (context, contentCatalog, shared, intrinsicA
116
134
  const overrides =
117
135
  componentVersionProfiles?.get(intrinsicAttributes['assembler-profile']) ?? componentVersionProfiles?.get() ?? {}
118
136
  const { navFiles, messages } = overrides
119
- const model = Object.assign({ logger }, shared, overrides)
137
+ const model = Object.assign({ logger }, common, overrides)
120
138
  delete model.navFiles
121
139
  delete model.messages
122
140
  const navigationOverride = navigationCatalog?.getNavigation(componentVersion.name, componentVersion.version)
@@ -145,7 +163,7 @@ function createResolveAssemblyModel (context, contentCatalog, shared, intrinsicA
145
163
  }
146
164
  }
147
165
 
148
- function prepareConvertAttributes (doc, targetExtname, buildConfig) {
166
+ function prepareConvertAttributes (doc, targetExtname, relativeToOutput, buildConfig) {
149
167
  const {
150
168
  asciidoc: { attributes: docAttributes } = { attributes: {} },
151
169
  extname: docfilesuffix,
@@ -155,7 +173,8 @@ function prepareConvertAttributes (doc, targetExtname, buildConfig) {
155
173
  const { cwd = process.cwd(), dir = cwd } = buildConfig
156
174
  const docname = family + '$' + relative.slice(0, relative.length - docfilesuffix.length)
157
175
  const docfile = ospath.join(dir, reldocfile)
158
- const docdir = dir
176
+ const outdir = ospath.dirname(docfile)
177
+ const docdir = relativeToOutput ? dir : outdir
159
178
  const imagesdir = ''
160
179
  const outfile = docfile.slice(0, docfile.length - docfilesuffix.length) + targetExtname
161
180
  const attributes = Object.assign({}, docAttributes, {
@@ -164,7 +183,7 @@ function prepareConvertAttributes (doc, targetExtname, buildConfig) {
164
183
  docfilesuffix,
165
184
  'docname@': docname,
166
185
  imagesdir,
167
- outdir: ospath.dirname(docfile),
186
+ outdir,
168
187
  outfile,
169
188
  outfilesuffix: targetExtname,
170
189
  toArgs (optionFlag, command) {
@@ -222,6 +241,39 @@ function prepareWorkspace (publishFiles, assemblyFiles, buildConfig) {
222
241
  return publishFiles({ output: { clean, dir } }, { getFiles: () => files })
223
242
  }
224
243
 
244
+ function resolveFileOrContents (convertResult, convertAttributes, buildConfig, loggerName) {
245
+ let fileOrContents
246
+ if (convertResult?.status != null) {
247
+ fileOrContents = 'file' in convertResult ? convertResult.file : convertResult.contents
248
+ let logger, match
249
+ if (buildConfig.stderrSink === 'log' && convertResult.stderr.length && (logger = this.getLogger(loggerName))) {
250
+ const docfile = convertAttributes.docfile
251
+ const command = buildConfig.command
252
+ const stderr = convertResult.stderr.toString().trimEnd()
253
+ stderr.split(NEWLINE_RX).forEach((line) => {
254
+ const ctx = { command, file: { path: docfile } }
255
+ if (line.charAt() === '{' && line.charAt(line.length - 1) === '}') {
256
+ const entry = JSON.parse(line)
257
+ if (entry.name) ctx.program = entry.name
258
+ if (entry.file?.line) ctx.line = entry.file.line
259
+ logger[entry.level](ctx, entry.msg)
260
+ } else if ((match = /^(.+):(\d+): warning: (.+)/.exec(line))) {
261
+ const [, scriptPath, lineno, msg] = match
262
+ ctx.stack = [{ file: { path: scriptPath }, line: parseInt(lineno, 10) }]
263
+ logger.warn(ctx, msg)
264
+ } else {
265
+ logger.info(ctx, line)
266
+ }
267
+ })
268
+ }
269
+ } else if (convertResult !== undefined) {
270
+ return convertResult
271
+ }
272
+ return fileOrContents === undefined
273
+ ? new LazyReadable(() => fs.createReadStream(convertAttributes.outfile))
274
+ : fileOrContents
275
+ }
276
+
225
277
  function isBound (obj) {
226
278
  if (obj == null) return false
227
279
  for (const _ in obj) return true
@@ -5,6 +5,14 @@ const fsp = require('node:fs/promises')
5
5
  const os = require('node:os')
6
6
  const yaml = require('js-yaml')
7
7
 
8
+ const ASSEMBLY_KEYS = [
9
+ 'rootLevel',
10
+ 'insertStartPage',
11
+ 'sectionMergeStrategy',
12
+ 'linkReferenceStyle',
13
+ 'dropExplicitXrefText',
14
+ ]
15
+
8
16
  function loadConfig (playbook, configSource = './antora-assembler.yml') {
9
17
  return (
10
18
  configSource.constructor === Object
@@ -46,7 +54,7 @@ function loadConfig (playbook, configSource = './antora-assembler.yml') {
46
54
  const remapAssemblyKeys = !('assembly' in config)
47
55
  const assembly = (config.assembly ??= {})
48
56
  if (remapAssemblyKeys) {
49
- for (const key of ['rootLevel', 'insertStartPage', 'sectionMergeStrategy']) {
57
+ for (const key of ASSEMBLY_KEYS) {
50
58
  if (!(key in config)) continue
51
59
  assembly[key] = config[key]
52
60
  delete config[key]
@@ -59,6 +67,12 @@ function loadConfig (playbook, configSource = './antora-assembler.yml') {
59
67
  if (['discrete', 'fuse', 'enclose'].indexOf(assembly.sectionMergeStrategy) < 0) {
60
68
  assembly.sectionMergeStrategy = 'discrete'
61
69
  }
70
+ if (['relative', 'root-relative', 'absolute'].indexOf(assembly.linkReferenceStyle) < 0) {
71
+ assembly.linkReferenceStyle = 'absolute'
72
+ }
73
+ if (['always', 'if-redundant', 'never'].indexOf(assembly.dropExplicitXrefText) < 0) {
74
+ assembly.dropExplicitXrefText = 'never'
75
+ }
62
76
  const build = (config.build ??= {})
63
77
  if (build.dir === '$' + '{playbook.output.dir}') {
64
78
  throw new Error('Not implemented')
@@ -74,6 +88,14 @@ function loadConfig (playbook, configSource = './antora-assembler.yml') {
74
88
  if (!build.processLimit) {
75
89
  build.processLimit = 'processLimit' in build ? Infinity : Math.round(os.cpus().length * 0.5)
76
90
  }
91
+ if ('stderr' in build) {
92
+ if (build.stderr === 'log') {
93
+ build.stderr = 'buffer'
94
+ build.stderrSink = 'log'
95
+ } else if (!['ignore', 'print'].includes(build.stderr)) {
96
+ delete build.stderr
97
+ }
98
+ }
77
99
  return config
78
100
  })
79
101
  }
@@ -114,16 +114,22 @@ function mergeAsciiDoc (
114
114
  let navtitlePlain = sanitize(navtitle)
115
115
  let navtitleAsciiDoc = unconvertInlineAsciiDoc(navtitle)
116
116
  const { items = [], unresolved, urlType, url } = outlineEntry
117
+ const {
118
+ doctype,
119
+ filetype,
120
+ embedReferenceStyle: embedRefStyle,
121
+ linkReferenceStyle: linkRefStyle,
122
+ outDirname,
123
+ siteRoot,
124
+ xmlIds,
125
+ } = assemblyModel
117
126
  // FIXME: ideally, resource ID would be stored in navigation so we can look up the page more efficiently
118
127
  const page = urlType === 'internal' && !unresolved ? pagesInOutline.get(url) : undefined
119
128
  const atDocumentRoot = !buffer.inBody
120
- const atBookRoot = atDocumentRoot && !level && assemblyModel.doctype === 'book' && (supportsParts = true)
129
+ const atBookRoot = atDocumentRoot && !level && doctype === 'book' && (supportsParts = true)
121
130
  const hasItems = items.length > 0
122
- const siteUrl = ((val) => {
123
- if (!val || val === '/') return ''
124
- return val.charAt(val.length - 1) === '/' ? val.slice(0, val.length - 1) : val
125
- })(asciidocConfig.attributes['primary-site-url'] || asciidocConfig.attributes['site-url'])
126
- const idSeparator = assemblyModel.xmlIds ? '-' : ':'
131
+ const pubRoot = outDirname ? '/' + outDirname : ''
132
+ const idSeparator = xmlIds ? '-' : ':'
127
133
  const idScopeSeparator = idSeparator.repeat(3)
128
134
  const idCoordinateSeparator = idSeparator === '-' ? '----' : idSeparator
129
135
  if (page && !pagesInOutline.assembled.pages.has(page)) {
@@ -141,7 +147,15 @@ function mergeAsciiDoc (
141
147
  if (doc.hasAttribute('assembly-navtitle')) {
142
148
  navtitleAsciiDoc = doc.getAttribute('assembly-navtitle')
143
149
  navtitlePlain = sanitize((navtitle = doc.$apply_reftext_subs(navtitleAsciiDoc)))
144
- if (buffer.inBody == null) buffer.navtitle = navtitle
150
+ if (buffer.inBody == null) {
151
+ buffer.navtitle = navtitle
152
+ // Q do we need to assert !level here?
153
+ if (assemblyModel.rootLevel) {
154
+ let doctitle = navtitleAsciiDoc
155
+ if (navtitlePlain !== componentVersion.title) doctitle = `${componentVersion.title}: ${doctitle}`
156
+ buffer[0] = `= ${doctitle}`
157
+ }
158
+ }
145
159
  }
146
160
  if (atDocumentRoot) {
147
161
  const authors = doc.getAuthors()
@@ -374,7 +388,7 @@ function mergeAsciiDoc (
374
388
  if (~line.indexOf('xref:')) {
375
389
  // Q: should we allow : as first character of target?
376
390
  line = line.replace(/(?<![\\+])xref:((?:\.\/)?[\p{Alpha}0-9_/.{#].*?)\[(|.*?[^\\])\]/gu, (m, target, text) => {
377
- let relativePart, fragment, targetResource, isPage
391
+ let relativePart, fragment, resource, isPage
378
392
  const hashIdx = target.indexOf('#')
379
393
  const dollarIdx = target.indexOf('$')
380
394
  if (~hashIdx) {
@@ -403,9 +417,9 @@ function mergeAsciiDoc (
403
417
  if (~hashIdx && !relativePart.endsWith('.adoc')) relativePart += '.adoc'
404
418
  }
405
419
  if (!isPage || ~relativePart.indexOf('@') || /:.*:/.test(relativePart)) {
406
- if (siteUrl && (targetResource = contentCatalog.resolveResource(relativePart, page.src, 'page'))?.pub) {
407
- text ||= targetResource.asciidoc?.xreftext || target
408
- return `${siteUrl}${targetResource.pub.url.replace(/_/g, '{underscore}')}${fragment && '#' + fragment}[${text}]`
420
+ if (siteRoot && (resource = contentCatalog.resolveResource(relativePart, page.src, 'page'))?.pub) {
421
+ text ||= resource.asciidoc?.xreftext || target
422
+ return `${resolveLinkTarget(resource, siteRoot, pubRoot, linkRefStyle)}${fragment && '#' + fragment}[${text}]`
409
423
  }
410
424
  // TODO: handle unresolved resource better
411
425
  return m
@@ -420,14 +434,10 @@ function mergeAsciiDoc (
420
434
  targetModule = module_
421
435
  }
422
436
  const pageResourceRef = targetModule === 'ROOT' ? relativePart : `${targetModule}:${relativePart}`
423
- if (!(targetResource = pagesInOutline.get(pageResourceRef))) {
424
- if (
425
- siteUrl &&
426
- (targetResource = contentCatalog.resolvePage(pageResourceRef, page.src)) &&
427
- targetResource.out
428
- ) {
429
- text ||= targetResource.asciidoc?.xreftext || target
430
- return `${siteUrl}${targetResource.pub.url}${fragment && '#' + fragment}[${text}]`
437
+ if (!(resource = pagesInOutline.get(pageResourceRef))) {
438
+ if (siteRoot && (resource = contentCatalog.resolvePage(pageResourceRef, page.src)) && resource.out) {
439
+ text ||= resource.asciidoc?.xreftext || target
440
+ return `${resolveLinkTarget(resource, siteRoot, pubRoot, linkRefStyle)}${fragment && '#' + fragment}[${text}]`
431
441
  }
432
442
  // TODO: handle unresolved page better
433
443
  return m
@@ -437,13 +447,20 @@ function mergeAsciiDoc (
437
447
  const refid = fragment
438
448
  ? `${relativePart}${idScopeSeparator}${fragment}`
439
449
  : relativePart + (ReservedIdNames.includes(relativePart) ? idScopeSeparator : '')
440
- return `<<${refid}${text && text !== targetResource.title ? ',' + text.replace(/\\]/g, ']') : ''}>>`
450
+ if (
451
+ text &&
452
+ (assemblyModel.dropExplicitXrefText === 'always' ||
453
+ (assemblyModel.dropExplicitXrefText === 'if-redundant' && text === resource.title))
454
+ ) {
455
+ text = ''
456
+ }
457
+ return `<<${refid}${text ? ',' + text.replace(/\\]/g, ']') : ''}>>`
441
458
  })
442
459
  }
443
460
  if (~line.indexOf('link:{attachmentsdir}/')) {
444
461
  line = line.replace(/(?<![\\+])link:\{attachmentsdir\}\/([^\s[]+)\[(|.*?[^\\])\]/g, (m, relative, text) => {
445
462
  const attachment =
446
- siteUrl &&
463
+ siteRoot &&
447
464
  contentCatalog.getById({
448
465
  component: componentVersion.name,
449
466
  version: componentVersion.version,
@@ -452,7 +469,7 @@ function mergeAsciiDoc (
452
469
  relative,
453
470
  })
454
471
  // TODO: handle unresolved attachment page
455
- return attachment?.out ? `${siteUrl}${attachment.pub.url.replace(/_/g, '{underscore}')}[${text}]` : m
472
+ return attachment?.out ? `${resolveLinkTarget(attachment, siteRoot, pubRoot, linkRefStyle)}[${text}]` : m
456
473
  })
457
474
  }
458
475
  if (~line.indexOf('image:') && !line.startsWith('image::')) {
@@ -460,9 +477,11 @@ function mergeAsciiDoc (
460
477
  if (isResourceSpec(target)) {
461
478
  const image = contentCatalog.resolveResource(target, page.src, 'image', ['image'])
462
479
  // TODO: handle (or report) unresolved image better
463
- if (image?.out) {
480
+ if (image?.out && (filetype !== 'html' || siteRoot)) {
464
481
  pagesInOutline.assembled.assets.add(image)
465
- return `image:${image.out.path.replace(/_/g, '{underscore}')}[${attrlist}]`
482
+ return filetype === 'html'
483
+ ? `image:${resolveLinkTarget(image, siteRoot, pubRoot, linkRefStyle)}[${attrlist}]`
484
+ : `image:${resolveEmbedTarget(image, outDirname, embedRefStyle, true)}[${attrlist}]`
466
485
  }
467
486
  }
468
487
  return m
@@ -516,10 +535,13 @@ function mergeAsciiDoc (
516
535
  if (isResourceSpec(target)) {
517
536
  const image = contentCatalog.resolveResource(target, page.src, 'image', ['image'])
518
537
  // FIXME: handle (or report) case when image is not resolved
519
- if (image?.out) {
520
- const boxedAttrlist = line.slice(line.indexOf('['))
538
+ if (image?.out && (filetype !== 'html' || siteRoot)) {
539
+ const attrlist = line.slice(line.indexOf('[') + 1, -1)
521
540
  pagesInOutline.assembled.assets.add(image)
522
- lines[idx] = `${prefix}image::${image.out.path}${boxedAttrlist}`
541
+ lines[idx] =
542
+ filetype === 'html'
543
+ ? `${prefix}image::${resolveLinkTarget(image, siteRoot, pubRoot, linkRefStyle, false)}[${attrlist}]`
544
+ : `${prefix}image::${resolveEmbedTarget(image, outDirname, embedRefStyle)}[${attrlist}]`
523
545
  }
524
546
  }
525
547
  lastImageMacroAt = [idx, imageMacroOffset]
@@ -580,9 +602,18 @@ function mergeAsciiDoc (
580
602
  let sectionTitle = navtitleAsciiDoc
581
603
  if (urlType === 'external') {
582
604
  sectionTitle = `${url}[${navtitleAsciiDoc.replace(/\]/g, '\\]')}]`
583
- } else if (urlType === 'internal' && !unresolved && siteUrl) {
605
+ } else if (urlType === 'internal' && !unresolved) {
584
606
  const resource = files.find((it) => it.pub.url === url)
585
- if (resource) sectionTitle = `${siteUrl}${resource.pub.url}[${navtitleAsciiDoc.replace(/\]/g, '\\]')}]`
607
+ if (resource) {
608
+ if (resource.src.family === 'page' && pagesInOutline.has(resource.pub.url)) {
609
+ let refid = resource.src.relative.replace(/\.adoc$/, '').replace(/[/.]/g, '-')
610
+ if (refid.startsWith('./')) refid = topicPrefix + refid.slice(2)
611
+ if (resource.src.module !== 'ROOT') refid = `${resource.src.module}${idCoordinateSeparator}${refid}`
612
+ sectionTitle = `<<${refid},${navtitleAsciiDoc}>>`
613
+ } else if (siteRoot) {
614
+ sectionTitle = `${resolveLinkTarget(resource, siteRoot, pubRoot, linkRefStyle)}[${navtitleAsciiDoc.replace(/\]/g, '\\]')}]`
615
+ }
616
+ }
586
617
  }
587
618
  let hlevel = level + 1
588
619
  if (hlevel > 6) {
@@ -787,4 +818,35 @@ function safePush (onto, entries) {
787
818
  }
788
819
  }
789
820
 
821
+ function resolveEmbedTarget (resource, outDirname, referenceStyle, escapeForInline) {
822
+ const target =
823
+ referenceStyle === 'output-relative' ? resource.out.path : path.relative(outDirname + '/', resource.out.path)
824
+ return escapeForInline ? target.replace(/_/g, '{underscore}') : target
825
+ }
826
+
827
+ function resolveLinkTarget (resource, siteRoot, pubRoot, referenceStyle, escapeForInline = true) {
828
+ let target
829
+ if (resource.site?.url) {
830
+ target = ['', resource.pub.url]
831
+ } else {
832
+ switch (referenceStyle) {
833
+ case 'absolute':
834
+ target = ['', siteRoot.url + resource.pub.url]
835
+ break
836
+ case 'root-relative':
837
+ target = ['link:', siteRoot.path + resource.pub.url]
838
+ break
839
+ default:
840
+ target = ['link:', computeRelativeUrl(pubRoot + '/', resource.pub.url)]
841
+ }
842
+ }
843
+ if (escapeForInline) target[1] = target[1].replace(/_/g, '{underscore}')
844
+ return target.join('')
845
+ }
846
+
847
+ function computeRelativeUrl (from, to) {
848
+ const rel = path.relative(from, to)
849
+ return to.charAt(to.length - 1) === '/' ? rel + '/' : rel
850
+ }
851
+
790
852
  module.exports = produceAssemblyFile
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const computeOut = require('./util/compute-out')
3
4
  const createAsciiDocFile = require('./util/create-asciidoc-file')
4
5
  const filterComponentVersions = require('./filter-component-versions')
5
6
  const produceAssemblyFile = require('./produce-assembly-file')
@@ -17,12 +18,16 @@ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, re
17
18
  sectionMergeStrategy: assemblyConfig.sectionMergeStrategy,
18
19
  navigation: componentVersion.navigation,
19
20
  xmlIds: assemblyConfig.xmlIds,
21
+ embedReferenceStyle: assemblyConfig.embedReferenceStyle,
22
+ linkReferenceStyle: assemblyConfig.linkReferenceStyle,
23
+ dropExplicitXrefText: assemblyConfig.dropExplicitXrefText,
20
24
  })
21
25
  const assemblerAsciiDocAttributes = Object.assign({}, assemblerAsciiDocConfig.attributes)
22
26
  const { revdate, 'source-highlighter': sourceHighlighter } = assemblerAsciiDocAttributes
23
27
  delete assemblerAsciiDocAttributes.revdate
24
28
  delete assemblerAsciiDocAttributes['source-highlighter']
25
29
  const publishableFiles = contentCatalog.getFiles().filter((file) => file.out)
30
+ let siteRoot
26
31
  const configMdc = assemblerConfig.file ? { file: { path: assemblerConfig.file } } : {}
27
32
  return filterComponentVersions(contentCatalog.getComponents(), assemblerConfig.componentVersionFilter.names).reduce(
28
33
  (accum, componentVersion) => {
@@ -38,6 +43,30 @@ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, re
38
43
  const mergedAsciiDocConfig = Object.assign({}, componentVersionAsciiDocConfig, {
39
44
  attributes: mergedAsciiDocAttributes,
40
45
  })
46
+ assemblyModel.outDirname = computeOut.call(contentCatalog, {
47
+ component: componentName,
48
+ version,
49
+ family: 'export',
50
+ relative: '.index.adoc',
51
+ }).dirname
52
+ assemblyModel.filetype = assemblerAsciiDocAttributes['assembler-filetype']
53
+ assemblyModel.siteRoot =
54
+ siteRoot === undefined
55
+ ? (siteRoot ??= ((val) => {
56
+ if (!val) return null
57
+ if (val.charAt(val.length - 1) === '/') val = val.slice(0, val.length - 1)
58
+ if (!val || val.charAt() === '/') return { path: val }
59
+ return { url: val, path: extractUrlPath(val) }
60
+ })(mergedAsciiDocAttributes['site-url'] || mergedAsciiDocAttributes['primary-site-url']))
61
+ : siteRoot
62
+ if (assemblyModel.filetype === 'html') {
63
+ let linkRefStyle = assemblyModel.linkReferenceStyle
64
+ if (linkRefStyle === 'absolute' && siteRoot?.url == null) linkRefStyle = 'root-relative'
65
+ if (linkRefStyle === 'root-relative' && siteRoot?.path == null) linkRefStyle = 'relative'
66
+ assemblyModel.linkReferenceStyle = linkRefStyle
67
+ } else {
68
+ assemblyModel.linkReferenceStyle = 'absolute'
69
+ }
41
70
  const auxiliaryImages = new Set()
42
71
  Object.entries(mergedAsciiDocAttributes).forEach(([name, val]) => {
43
72
  const match = name.endsWith('-image') && val.startsWith('image:') && IMAGE_MACRO_RX.exec(val)
@@ -177,4 +206,10 @@ function collateAsciiDocAttributes (collated, additional, { logger, mdc }) {
177
206
  return collated
178
207
  }
179
208
 
209
+ function extractUrlPath (url) {
210
+ if (!url) return ''
211
+ const urlPath = new URL(url).pathname
212
+ return urlPath === '/' ? '' : urlPath
213
+ }
214
+
180
215
  module.exports = produceAssemblyFiles
@@ -3,7 +3,7 @@
3
3
  const { posix: path } = require('node:path')
4
4
 
5
5
  function computeOut (src) {
6
- const { component, version, module: module_, family, relative } = src
6
+ const { component, version, module: module_ = 'ROOT', family, relative } = src
7
7
  const outRelative = family === 'page' ? relative.replace(/\.adoc$/, '.html') : relative
8
8
  const { dir: dirname, base: basename } = path.parse(outRelative)
9
9
  const componentVersion = this.getComponentVersion(component, version)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antora/assembler",
3
- "version": "1.0.0-beta.6",
3
+ "version": "1.0.0-beta.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)",