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

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.
@@ -23,10 +23,11 @@ async function assembleContent (playbook, contentCatalog, converter, { configSou
23
23
  backend: targetBackend = targetExtname.substring(1),
24
24
  embedReferenceStyle = 'relative',
25
25
  mediaType: targetMediaType,
26
- loggerName = PACKAGE_NAME,
26
+ loggerName = `${PACKAGE_NAME} [${targetBackend}-exporter]`,
27
+ xmlCompliant,
27
28
  } = converter ?? {}
28
- const assemblerConfig = await loadConfig.call(this, playbook, configSource, '-' + targetBackend)
29
- if (assemblerConfig.enabled === false) return []
29
+ const assemblerConfig = await loadConfig.call(this, configSource, playbook, '-' + targetBackend)
30
+ if (assemblerConfig.enabled === false || !contentCatalog.publishableFamilies?.has('export')) return []
30
31
  const context = isBound(this)
31
32
  ? this
32
33
  : {
@@ -38,9 +39,10 @@ async function assembleContent (playbook, contentCatalog, converter, { configSou
38
39
  const { loadAsciiDoc = require('@antora/asciidoc-loader') } = generatorFunctions
39
40
  const { assembly: assemblyConfig, build: buildConfig } = assemblerConfig
40
41
  assemblyConfig.embedReferenceStyle = embedReferenceStyle
42
+ if (xmlCompliant) assemblyConfig.xmlIds ??= true
41
43
  const profile = (assemblyConfig.profile ??= targetBackend)
42
- const intrinsicAttributes = { 'loader-assembler': '' }
43
- buildConfig.cwd ??= process.cwd()
44
+ const intrinsicAttributes = { 'loader-assembler': '', 'assembler-root-level': assemblyConfig.rootLevel }
45
+ const cwd = (buildConfig.cwd ??= process.cwd())
44
46
  if (profile) {
45
47
  buildConfig.dir ??= ospath.join(playbook.dir ?? process.cwd(), `build/assembler/${profile}`)
46
48
  intrinsicAttributes[`assembler-profile-${profile}`] = ''
@@ -65,7 +67,7 @@ async function assembleContent (playbook, contentCatalog, converter, { configSou
65
67
  generateSelectAssemblyProfile(context, contentCatalog, assemblyConfig, intrinsicAttributes, navigationCatalog)
66
68
  )
67
69
  if (!(assemblyFiles.length && typeof convert === 'function')) return assemblyFiles
68
- buildConfig.command ??= typeof getDefaultCommand === 'function' ? await getDefaultCommand(buildConfig.cwd) : null
70
+ buildConfig.command ??= typeof getDefaultCommand === 'function' ? await getDefaultCommand(cwd, playbook) : null
69
71
  const { publishSite: publishFiles = require('@antora/site-publisher') } = generatorFunctions
70
72
  await prepareWorkspace(publishFiles, assemblyFiles, buildConfig)
71
73
  const helpers = { logCommand: logCommand.bind(context, loggerName), runCommand }
@@ -150,16 +152,21 @@ function generateSelectAssemblyProfile (context, contentCatalog, baseModel, intr
150
152
  const attributes = Object.assign({}, baseModel.attributes)
151
153
  const model = Object.assign({}, baseModel, overrides, { attributes, logger })
152
154
  if (!overrides) return Object.assign(model, { navigation: navigation || componentVersion.navigation })
155
+ if ('rootLevel' in overrides) attributes['assembler-root-level'] = overrides.rootLevel
153
156
  delete model.navFiles
154
157
  delete model.messages
155
- if (overrides.attributes) Object.entries(overrides.attributes).forEach(([name, val]) => (attributes[name] = val))
158
+ if (overrides.attributes) {
159
+ for (const [name, val] of Object.entries(overrides.attributes)) {
160
+ if (!(name in intrinsicAttributes)) attributes[name] = val
161
+ }
162
+ }
156
163
  if (navigation) return Object.assign(model, { navigation })
157
164
  overrides.messages?.forEach(boundSendToLog)
158
165
  const navFiles = overrides.navFiles
159
166
  if (!navFiles) return Object.assign(model, { navigation: componentVersion.navigation })
160
167
  if (!navFiles.length) return Object.assign(model, { navigation: [] })
161
168
  model.navigation = buildAlternateNavigation(contentCatalog, componentVersion, navFiles, {
162
- attributes: intrinsicAttributes,
169
+ attributes: model.attributes,
163
170
  })
164
171
  return model
165
172
  }
@@ -182,7 +189,7 @@ function prepareConvertAttributes (doc, targetExtname, relativeToOutput, assembl
182
189
  docdir,
183
190
  docfile,
184
191
  docfilesuffix,
185
- 'docname@': docname,
192
+ docname: `${docname}@`,
186
193
  imagesdir: '',
187
194
  outdir,
188
195
  outfile,
@@ -206,6 +213,17 @@ function prepareConvertAttributes (doc, targetExtname, relativeToOutput, assembl
206
213
  return args
207
214
  },
208
215
  })
216
+ Object.defineProperty(attributes, 'outfilesuffix', {
217
+ get () {
218
+ return ospath.extname(this.outfile)
219
+ },
220
+ set (value) {
221
+ const outfile = this.outfile
222
+ const extname = ospath.extname(outfile)
223
+ if (extname && outfile.endsWith(extname)) this.outfile = outfile.substring(0, outfile.length - extname.length)
224
+ this.outfile += value
225
+ },
226
+ })
209
227
  return Object.defineProperty(attributes, 'toArgs', { enumerable: false })
210
228
  }
211
229
 
@@ -265,12 +283,14 @@ async function resolveFileOrContents (convertResult, convertAttributes, buildCon
265
283
  fileOrContents = convertResult.stdout
266
284
  }
267
285
  let logger, match
268
- if (buildConfig.stderrSink === 'log' && convertResult.stderr.length && (logger = this.getLogger(loggerName))) {
286
+ if (buildConfig.stderrSink === 'log' && convertResult.stderr?.length && (logger = this.getLogger(loggerName))) {
269
287
  const docfile = convertAttributes.docfile
270
288
  const command = buildConfig.command
289
+ const file = { path: docfile }
271
290
  const stderr = convertResult.stderr.toString().trimEnd()
291
+ const additionalLines = ['Additional stderr lines from command:']
272
292
  stderr.split(NEWLINE_RX).forEach((line) => {
273
- const ctx = { command, file: { path: docfile } }
293
+ const ctx = { command, file }
274
294
  if (line.charAt() === '{' && line.charAt(line.length - 1) === '}') {
275
295
  const entry = JSON.parse(line)
276
296
  if (entry.name) ctx.program = entry.name
@@ -286,9 +306,10 @@ async function resolveFileOrContents (convertResult, convertAttributes, buildCon
286
306
  if (lineno) ctx.line = parseInt(lineno, 10)
287
307
  logger[level === 'WARNING' ? 'warn' : level.toLowerCase()](ctx, msg)
288
308
  } else {
289
- logger.info(ctx, line)
309
+ additionalLines.push(line)
290
310
  }
291
311
  })
312
+ if (additionalLines.length > 1) logger.info({ command, file }, additionalLines.join('\n'))
292
313
  }
293
314
  } else if (convertResult !== undefined) {
294
315
  return convertResult
package/lib/configure.js CHANGED
@@ -17,7 +17,8 @@ function internalConfigure (converter, config = {}, providers = {}) {
17
17
 
18
18
  this.once('navigationBuilt', async ({ playbook, contentCatalog }) => {
19
19
  const { assembleContent = require('./assemble-content'), ...assembleContentConfig } = providers
20
- if (assembleContentConfig.configSource?.constructor === Object) {
20
+ if (config.configSource?.constructor === Object) {
21
+ assembleContentConfig.configSource = config.configSource
21
22
  await assembleContent.call(this, playbook, contentCatalog, converter, assembleContentConfig)
22
23
  } else {
23
24
  const singleConfig = !('configFiles' in config)
@@ -9,8 +9,9 @@ const yaml = require('js-yaml')
9
9
  const { LEGACY_ASSEMBLY_KEYS } = require('./constants')
10
10
  const CAMEL_CASE_STOP_PATHS = ['asciidoc.attributes', 'assembly.attributes']
11
11
  const PACKAGE_NAME = require('../package.json').name
12
+ const YAML_SCHEMA = yaml.CORE_SCHEMA.withTags(yaml.mergeTag)
12
13
 
13
- function loadConfig (playbook, configSource, preferredQualifier = '') {
14
+ function loadConfig (configSource, playbook, preferredQualifier = '') {
14
15
  let resolvedConfigSource
15
16
  return (
16
17
  configSource?.constructor === Object
@@ -36,11 +37,11 @@ function loadConfig (playbook, configSource, preferredQualifier = '') {
36
37
  }
37
38
  return {}
38
39
  }
39
- return fsp
40
- .readFile(resolvedConfigSource)
41
- .then((data) =>
42
- Object.assign(camelCaseKeys(yaml.load(data), CAMEL_CASE_STOP_PATHS), { file: resolvedConfigSource })
43
- )
40
+ return fsp.readFile(resolvedConfigSource).then((data) =>
41
+ Object.assign(camelCaseKeys(yaml.load(data, { schema: YAML_SCHEMA }), CAMEL_CASE_STOP_PATHS), {
42
+ file: resolvedConfigSource,
43
+ })
44
+ )
44
45
  })
45
46
  ).then((config) => {
46
47
  if (config.enabled === false) return config
@@ -198,6 +198,7 @@ function mergeAsciiDoc (
198
198
  Object.assign({}, page, { contents: trimAsciiDoc(contents), mediaType: page.src.mediaType })
199
199
  )
200
200
  const doc = loadAsciiDoc(pageAsAsciiDoc, contentCatalog, asciidocConfig)
201
+ doc.catalog.syntheticIds = {}
201
202
  doc.logger = logger
202
203
  ? Object.assign(doc.getLogger().$dup(), {
203
204
  delegate: {
@@ -365,10 +366,10 @@ function mergeAsciiDoc (
365
366
  }
366
367
  }
367
368
  }
368
- if (htitleAsciiDoc != null) {
369
+ if (htitleAsciiDoc) {
369
370
  if (atDocumentRoot) {
370
- // NOTE use pageStyle as fallback because this section must have a unique ID
371
- pageFragment = `#${pageIdLeader}${doc.getId() ?? pageStyle}`
371
+ // Q: should we use htitleAsciiDoc to generate ID if not {empty} instead of pageStyle
372
+ pageFragment = `#_${idSeparators.coordinate}${pageIdLeader}${pageStyle}`
372
373
  buffer.unshift(`[#${pageId}]`)
373
374
  } else {
374
375
  pageFragment = `#${pageId}`
@@ -393,31 +394,30 @@ function mergeAsciiDoc (
393
394
  } else if (atBookRoot) {
394
395
  nextSectionLevel = undefined
395
396
  }
396
- let enclosed
397
397
  if (heading) {
398
398
  const rolesAttr = pageRoles.reduce((str, it) => str + '.' + it, '')
399
399
  const notitleAttrs = heading.notitle ? '%notitle,toclevels=0,outlinelevels=0' : ''
400
400
  buffer.push(`[${heading.style ?? pageStyle}${pageFragment}${rolesAttr}${notitleAttrs}]`)
401
401
  buffer.push(`${'='.repeat(heading.level)} ${heading.title}`)
402
- } else {
403
- if (atDocumentRoot) buffer.unshift(`[#${pageId}]`)
404
- if (sectionMergeStrategy === 'enclose' && hasItems && hasSections) {
405
- enclosed = true
406
- // TODO: make overview section title configurable
407
- //let overviewTitle = doc.getDocumentTitle()
408
- //if (overviewTitle === navtitle) overviewTitle = doc.getAttribute('overview-title', 'Overview')
409
- const overviewTitle = doc.getAttribute('overview-title', 'Overview')
410
- const syntheticId = idSeparators.generateIdFromTitle(navtitleAsciiDoc, idSeparators)
411
- let hlevel = level + 2
412
- if (hlevel > 6) {
413
- const blockStyle = `discrete.h${hlevel}`
414
- hlevel = 6
415
- buffer.push(`[${blockStyle}#${syntheticId}]`)
416
- } else {
417
- buffer.push(`[#${syntheticId}]`)
418
- }
419
- buffer.push(`${'='.repeat(hlevel)} ${overviewTitle}`)
402
+ } else if (atDocumentRoot) {
403
+ buffer.unshift(`[#${pageId}]`)
404
+ }
405
+ if (sectionMergeStrategy === 'enclose' && hasItems && hasSections && !(atDocumentRoot && heading)) {
406
+ // TODO: make overview section title configurable
407
+ //let overviewTitle = doc.getDocumentTitle()
408
+ //if (overviewTitle === navtitle) overviewTitle = doc.getAttribute('overview-title', 'Overview')
409
+ const overviewTitle = doc.getAttribute('overview-title', 'Overview')
410
+ const syntheticId = idSeparators.generateIdFromTitle(overviewTitle, idSeparators, pageIdLeader)
411
+ if (heading) buffer.push('')
412
+ let hlevel = level + (nextSectionLevel = 2)
413
+ if (hlevel > 6) {
414
+ const blockStyle = `discrete.h${hlevel}`
415
+ hlevel = 6
416
+ buffer.push(`[${blockStyle}#${syntheticId}]`)
417
+ } else {
418
+ buffer.push(`[#${syntheticId}]`)
420
419
  }
420
+ buffer.push(`${'='.repeat(hlevel)} ${overviewTitle}`)
421
421
  }
422
422
  assembled.pages.set(page, pageFragment)
423
423
  if (hasSections) fixSectionLevels(doc.getSections(), nextSectionLevel)
@@ -476,7 +476,7 @@ function mergeAsciiDoc (
476
476
  if (~ignoreLines.indexOf(idx)) continue
477
477
  let line = lines[idx]
478
478
  if (line.startsWith('//')) {
479
- if (line[2] !== '/') continue
479
+ if (line.charAt(2) !== '/') continue
480
480
  if (line.length > 3 && line === '/'.repeat(line.length)) {
481
481
  if (skipping) {
482
482
  if (line === skipping) skipping = undefined
@@ -545,7 +545,7 @@ function mergeAsciiDoc (
545
545
  if (block.getSectionName() === 'header') return
546
546
  let blockStyle = sectionMergeStrategy === 'discrete' ? 'discrete' : undefined
547
547
  lines[idx] = lines[idx].replace(/^=+ (.+)/, (_, rest) => {
548
- let targetMarkerLength = block.level + 1 + level + (enclosed ? 1 : 0)
548
+ let targetMarkerLength = block.level + 1 + level
549
549
  if (targetMarkerLength > 6) {
550
550
  blockStyle = `discrete.h${targetMarkerLength}`
551
551
  targetMarkerLength = 6
@@ -806,11 +806,20 @@ function safePush (onto, entries) {
806
806
  }
807
807
  }
808
808
 
809
- function generateIdFromTitle (titleAsciiDoc, idSeparators) {
809
+ function generateIdFromTitle (titleAsciiDoc, idSeparators, idLeader) {
810
810
  const Section = this.$class().$const_get('::Asciidoctor::Section')
811
- const baseId = Section.$generate_id(titleAsciiDoc, this)
812
- this.getCatalog().refs['$[]='](baseId, true)
813
- return `_${idSeparators.coordinate}${baseId}`
811
+ const baseId = Section.$generate_id(titleAsciiDoc, this) // refs in catalog will always be empty
812
+ let id = `_${idSeparators.coordinate}${idLeader ?? ''}${baseId}`
813
+ const syntheticIds = (this.catalog.syntheticIds ??= {})
814
+ if (id in syntheticIds) {
815
+ const sep = (this.getAttribute('idseparator') ?? '_').charAt()
816
+ let cnt = this.$class().$const_get('::Asciidoctor::Compliance').unique_id_start_index
817
+ let candidate
818
+ while ((candidate = `${id}${sep}${cnt}`) in syntheticIds) cnt++
819
+ id = candidate
820
+ }
821
+ syntheticIds[id] = true
822
+ return id
814
823
  }
815
824
 
816
825
  module.exports = produceAssemblyFile
@@ -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') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antora/assembler",
3
- "version": "1.0.0-rc.3",
3
+ "version": "1.0.0-rc.5",
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)",
@@ -41,7 +41,7 @@
41
41
  "@asciidoctor/reducer": "~1.1",
42
42
  "@antora/expand-path-helper": "~3.0",
43
43
  "@antora/run-command-helper": "~1.1",
44
- "js-yaml": "~4.3"
44
+ "js-yaml": "~5.2"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@antora/asciidoc-loader": "3.2.0-rc.2",