UNPKG

@antora/content-classifier

Version:

Organizes aggregated content into a virtual file catalog for use in an Antora documentation pipeline.

159 lines (151 loc) 6.68 kB
'use strict' const ContentCatalog = require('./content-catalog') const collateAsciiDocAttributes = require('@antora/asciidoc-loader/config/collate-asciidoc-attributes') const logger = require('./logger') const summarizeFileLocation = require('./util/summarize-file-location') /** * Organizes the raw aggregate of virtual files into a {ContentCatalog}. * * @memberof content-classifier * * @param {Object} playbook - The configuration object for Antora. * @param {Object} playbook.site - Site-related configuration data. * @param {String} playbook.site.startPage - The start page for the site; redirects from base URL. * @param {Object} playbook.urls - URL settings for the site. * @param {String} playbook.urls.htmlExtensionStyle - The style to use when computing page URLs. * @param {Object} aggregate - The raw aggregate of virtual file objects to be classified. * @param {Object} [siteAsciiDocConfig={}] - Site-wide AsciiDoc processor configuration options. * @returns {ContentCatalog} A structured catalog of content components and virtual content files. */ function classifyContent (playbook, aggregate, siteAsciiDocConfig = {}) { const contentCatalog = new ContentCatalog(playbook) aggregate .reduce((accum, componentVersionData) => { // drop files since they aren't needed to register component version // drop startPage to defer registration of start page const { name, version, files, startPage, ...descriptor } = Object.assign({}, componentVersionData, { asciidoc: resolveAsciiDocConfig(siteAsciiDocConfig, componentVersionData), }) return new Map(accum).set( contentCatalog.registerComponentVersion(name, version, descriptor), componentVersionData ) }, new Map()) .forEach((componentVersionData, componentVersion) => { const { name, version } = componentVersion const { files, nav, startPage } = componentVersionData const navResolved = nav && (nav.resolved = new Set()) componentVersionData.files = undefined // clean up memory files.forEach((file) => allocateSrc(file, name, version, nav) && contentCatalog.addFile(file)) if (navResolved && nav.length > navResolved.size && new Set(nav).size > navResolved.size) { const loc = summarizeFileLocation({ path: 'antora.yml', src: { origin: nav.origin } }) for (const filepath of nav) { if (navResolved.has(filepath)) continue logger.warn('Could not resolve nav entry for %s@%s defined in %s: %s', version, name, loc, filepath) } } contentCatalog.registerComponentVersionStartPage(name, componentVersion, startPage) }) contentCatalog.registerSiteStartPage(playbook.site.startPage) return contentCatalog } function allocateSrc (file, component, version, nav) { const extname = file.src.extname const filepath = file.path const pathSegments = filepath.split('/') let navInfo if (nav && (navInfo = getNavInfo(filepath, nav))) { if (extname !== '.adoc') return // ignore file file.nav = navInfo file.src.family = 'nav' if (pathSegments[0] === 'modules' && pathSegments.length > 2) { file.src.module = pathSegments[1] // relative to modules/<module> file.src.relative = pathSegments.slice(2).join('/') file.src.moduleRootPath = calculateRootPath(pathSegments.length - 3) } else { // relative to content source root file.src.relative = filepath } } else if (pathSegments[0] === 'modules') { let familyFolder = pathSegments[2] switch (familyFolder) { case 'pages': // pages/_partials location for partials is @deprecated; special designation scheduled for removal in Antora 4 if (pathSegments[3] === '_partials') { file.src.family = 'partial' // relative to modules/<module>/pages/_partials file.src.relative = pathSegments.slice(4).join('/') } else if (extname === '.adoc') { file.src.family = 'page' // relative to modules/<module>/pages file.src.relative = pathSegments.slice(3).join('/') } else { return // ignore file } break case 'assets': switch ((familyFolder = pathSegments[3])) { case 'attachments': case 'images': if (!extname) return // ignore file file.src.family = familyFolder.substr(0, familyFolder.length - 1) // relative to modules/<module>/assets/<family>s file.src.relative = pathSegments.slice(4).join('/') break default: return // ignore file } break case 'attachments': case 'images': if (!extname) return file.src.family = familyFolder.substr(0, familyFolder.length - 1) // relative to modules/<module>/<family>s file.src.relative = pathSegments.slice(3).join('/') break case 'examples': case 'partials': file.src.family = familyFolder.substr(0, familyFolder.length - 1) // relative to modules/<module>/<family>s file.src.relative = pathSegments.slice(3).join('/') break default: return // ignore file } file.src.module = pathSegments[1] file.src.moduleRootPath = calculateRootPath(pathSegments.length - 3) } else { return // ignore file } file.src.component = component file.src.version = version return true } /** * Return navigation properties if this file is registered as a navigation file. * * @param {String} filepath - The path of the virtual file to match. * @param {Array} nav - The array of navigation entries from the component descriptor. * * @returns {Object} An object of properties, which includes the navigation * index, if this file is a navigation file, or undefined if it's not. */ function getNavInfo (filepath, nav) { const index = nav.findIndex((candidate) => candidate === filepath) if (~index) return nav.resolved.add(filepath) && { index } } function resolveAsciiDocConfig (siteAsciiDocConfig, { asciidoc, origins = [] }) { const scopedAttributes = asciidoc?.attributes if (scopedAttributes) { const initial = siteAsciiDocConfig.attributes const mdc = { file: { path: 'antora.yml', origin: origins[origins.length - 1] } } const attributes = collateAsciiDocAttributes(scopedAttributes, { initial, mdc, merge: true }) if (attributes !== initial) siteAsciiDocConfig = Object.assign({}, siteAsciiDocConfig, { attributes }) } return siteAsciiDocConfig } function calculateRootPath (depth) { return depth ? Array(depth).fill('..').join('/') : '.' } module.exports = classifyContent