@antora/assembler
Version:
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.
153 lines (145 loc) • 5.78 kB
JavaScript
const expandPath = require('@antora/expand-path-helper')
const fsp = require('node:fs/promises')
const os = require('node:os')
const yaml = require('js-yaml')
const ASSEMBLY_KEYS = [
'rootLevel',
'insertStartPage',
'sectionMergeStrategy',
'linkReferenceStyle',
'dropExplicitXrefText',
]
const CAMEL_CASE_STOP_PATHS = ['asciidoc.attributes', 'assembly.attributes']
const PACKAGE_NAME = require('../package.json').name
function loadConfig (playbook, configSource, preferredQualifier = '') {
let resolvedConfigSource
return (
configSource?.constructor === Object
? Promise.resolve(configSource)
: fileExists(
(resolvedConfigSource = expandPath(configSource ?? `./antora-assembler${preferredQualifier}.yml`, {
dot: playbook.dir,
}))
)
.then((exists) => {
if (exists || configSource || !preferredQualifier) return exists
return fileExists(
(resolvedConfigSource = resolvedConfigSource.slice(0, -(preferredQualifier.length + 4)) + '.yml')
)
})
.then((exists) => {
if (!exists) {
let logger
if (configSource && (logger = this?.getLogger?.(PACKAGE_NAME))) {
const ctx = { file: { path: configSource } }
logger.warn(ctx, 'Could not resolve config file; reverting to default settings')
}
return {}
}
return fsp
.readFile(resolvedConfigSource)
.then((data) =>
Object.assign(camelCaseKeys(yaml.load(data), CAMEL_CASE_STOP_PATHS), { file: resolvedConfigSource })
)
})
).then((config) => {
if (config.enabled === false) return config
const remapComponentVersionsKey = !('componentVersionFilter' in config)
const componentVersionFilter = (config.componentVersionFilter ??= {})
if (remapComponentVersionsKey && 'componentVersions' in config) {
componentVersionFilter.names = config.componentVersions
delete config.componentVersions
}
if (componentVersionFilter.names == null) {
componentVersionFilter.names = ['*']
} else if (typeof componentVersionFilter.names === 'string') {
componentVersionFilter.names = componentVersionFilter.names.split(', ')
}
const remapAssemblyKeys = !('assembly' in config)
const assembly = (config.assembly ??= {})
if (remapAssemblyKeys) {
for (const key of ASSEMBLY_KEYS) {
if (!(key in config)) continue
assembly[key] = config[key]
delete config[key]
}
}
let assemblyAttrs
if ('attributes' in assembly) {
assemblyAttrs = assembly.attributes ?? {}
if ('asciidoc' in config) delete config.asciidoc
} else if ('asciidoc' in config) {
assemblyAttrs = assembly.attributes = config.asciidoc?.attributes ?? {}
delete config.asciidoc
} else {
assemblyAttrs = assembly.attributes = {}
}
assemblyAttrs['page-partial'] = null
config.asciidoc = Object.defineProperty({}, 'attributes', {
get: function () {
return this.assembly.attributes
}.bind(config),
})
if (!('doctype' in assembly)) assembly.doctype = 'doctype' in assemblyAttrs ? assemblyAttrs.doctype : 'book'
delete assemblyAttrs.doctype
if (!('rootLevel' in assembly)) assembly.rootLevel = 0
if (!('insertStartPage' in assembly)) assembly.insertStartPage = true
if (['discrete', 'fuse', 'enclose'].indexOf(assembly.sectionMergeStrategy) < 0) {
assembly.sectionMergeStrategy = 'discrete'
}
if (['relative', 'root-relative', 'absolute'].indexOf(assembly.linkReferenceStyle) < 0) {
assembly.linkReferenceStyle = 'absolute'
}
if (['always', 'if-redundant', 'never'].indexOf(assembly.dropExplicitXrefText) < 0) {
assembly.dropExplicitXrefText = 'never'
}
assembly.revdate = getLocalDate()
const build = (config.build ??= {})
if (build.dir === '$' + '{playbook.output.dir}') {
throw new Error('Not implemented')
}
build.dir &&= expandPath(build.dir, { dot: playbook.dir })
// used as cwd of command (and any scripts it requires)
build.cwd = build.cwd == null ? playbook.dir : expandPath(build.cwd, { dot: playbook.dir })
if (!('clean' in build) && 'output' in playbook) build.clean = playbook.output.clean
if (!('publish' in build)) build.publish = true
if ('keepAggregateSource' in build) {
build.keepSource = build.keepAggregateSource
delete build.keepAggregateSource
}
if (!build.processLimit) {
build.processLimit = 'processLimit' in build ? Infinity : Math.round(os.cpus().length * 0.5)
}
if ('stderr' in build) {
if (build.stderr === 'log') {
build.stderr = 'buffer'
build.stderrSink = 'log'
} else if (!['ignore', 'print'].includes(build.stderr)) {
delete build.stderr
}
}
return config
})
}
function camelCaseKeys (o, stopPaths = [], p = undefined) {
if (Array.isArray(o)) return o.map((it) => camelCaseKeys(it, stopPaths, p))
if (o == null || o.constructor !== Object) return o
const pathPrefix = p ? p + '.' : ''
const accum = {}
for (const [k, v] of Object.entries(o)) {
const camelKey = k.charAt() + k.slice(1).replace(/_([a-z])/g, (_, l) => l.toUpperCase())
accum[camelKey] = ~stopPaths.indexOf(pathPrefix + camelKey) ? v : camelCaseKeys(v, stopPaths, pathPrefix + camelKey)
}
return accum
}
function fileExists (path) {
return fsp.access(path).then(
() => true,
() => false
)
}
function getLocalDate (now = new Date()) {
return new Date(now - now.getTimezoneOffset() * 60000).toISOString().split('T')[0]
}
module.exports = loadConfig