@antora/content-classifier
Advanced tools
Comparing version 1.0.3 to 1.1.0
@@ -18,15 +18,15 @@ 'use strict' | ||
* @param {Object} aggregate - The raw aggregate of virtual file objects to be classified. | ||
* @returns {ContentCatalog} An organized catalog of virtual content files. | ||
* @returns {ContentCatalog} A structured catalog of content components and virtual content files. | ||
*/ | ||
function classifyContent (playbook, aggregate) { | ||
const catalog = aggregate.reduce( | ||
(catalog, { name: component, version, title, start_page: startPage, nav, files }) => { | ||
files.forEach((file) => allocateSrc(file, component, version, nav) && catalog.addFile(file)) | ||
catalog.addComponentVersion(component, version, title, startPage) | ||
return catalog | ||
const contentCatalog = aggregate.reduce( | ||
(accum, { name, version, display_version: displayVersion, prerelease, title, start_page: startAt, nav, files }) => { | ||
files.forEach((file) => allocateSrc(file, name, version, nav) && accum.addFile(file)) | ||
accum.registerComponentVersion(name, version, { displayVersion, title, prerelease, startPage: startAt }) | ||
return accum | ||
}, | ||
new ContentCatalog(playbook) | ||
) | ||
registerSiteStartPage(playbook, catalog) | ||
return catalog | ||
registerSiteStartPage(playbook, contentCatalog) | ||
return contentCatalog | ||
} | ||
@@ -40,3 +40,3 @@ | ||
file.nav = navInfo | ||
file.src.family = 'navigation' | ||
file.src.family = 'nav' | ||
if (pathSegments[0] === 'modules' && pathSegments.length > 2) { | ||
@@ -52,35 +52,42 @@ file.src.module = pathSegments[1] | ||
} else if (pathSegments[0] === 'modules') { | ||
if (pathSegments[2] === 'pages') { | ||
if (pathSegments[3] === '_partials') { | ||
// QUESTION should this family be partial-page instead? | ||
switch (pathSegments[2]) { | ||
case 'pages': | ||
if (pathSegments[3] === '_partials') { | ||
file.src.family = 'partial' | ||
// relative to modules/<module>/pages/_partials; deprecated (in the future, warn) | ||
file.src.relative = pathSegments.slice(4).join('/') | ||
break | ||
} else if (file.src.mediaType === 'text/asciidoc') { | ||
file.src.family = 'page' | ||
// relative to modules/<module>/pages | ||
file.src.relative = pathSegments.slice(3).join('/') | ||
break | ||
} | ||
return | ||
case 'assets': | ||
if (pathSegments[3] === 'images') { | ||
file.src.family = 'image' | ||
// relative to modules/<module>/assets/images | ||
file.src.relative = pathSegments.slice(4).join('/') | ||
break | ||
} else if (pathSegments[3] === 'attachments') { | ||
file.src.family = 'attachment' | ||
// relative to modules/<module>/assets/attachments | ||
file.src.relative = pathSegments.slice(4).join('/') | ||
break | ||
} | ||
return | ||
case 'examples': | ||
file.src.family = 'example' | ||
// relative to modules/<module>/examples | ||
file.src.relative = pathSegments.slice(3).join('/') | ||
break | ||
case 'partials': | ||
file.src.family = 'partial' | ||
// relative to modules/<module>/pages/_partials | ||
file.src.relative = pathSegments.slice(4).join('/') | ||
} else if (file.src.mediaType === 'text/asciidoc') { | ||
file.src.family = 'page' | ||
// relative to modules/<module>/pages | ||
// relative to modules/<module>/partials | ||
file.src.relative = pathSegments.slice(3).join('/') | ||
} else { | ||
break | ||
default: | ||
return | ||
} | ||
} else if (pathSegments[2] === 'assets') { | ||
if (pathSegments[3] === 'images') { | ||
file.src.family = 'image' | ||
// relative to modules/<module>/assets/images | ||
file.src.relative = pathSegments.slice(4).join('/') | ||
} else if (pathSegments[3] === 'attachments') { | ||
file.src.family = 'attachment' | ||
// relative to modules/<module>/assets/attachments | ||
file.src.relative = pathSegments.slice(4).join('/') | ||
} else { | ||
return | ||
} | ||
} else if (pathSegments[2] === 'examples') { | ||
file.src.family = 'example' | ||
// relative to modules/<module>/examples | ||
file.src.relative = pathSegments.slice(3).join('/') | ||
} else { | ||
return | ||
} | ||
file.src.module = pathSegments[1] | ||
@@ -87,0 +94,0 @@ file.src.moduleRootPath = calculateRootPath(pathSegments.length - 3) |
@@ -5,5 +5,5 @@ 'use strict' | ||
const File = require('./file') | ||
const parsePageId = require('./util/parse-page-id') | ||
const parseResourceId = require('./util/parse-resource-id') | ||
const { posix: path } = require('path') | ||
const resolvePage = require('./util/resolve-page') | ||
const resolveResource = require('./util/resolve-resource') | ||
const versionCompare = require('./util/version-compare-desc') | ||
@@ -25,43 +25,60 @@ | ||
addComponentVersion (name, version, title, startPageSpec = undefined) { | ||
let startPage = this.resolvePage(startPageSpec || 'index.adoc', { component: name, version, module: 'ROOT' }) | ||
registerComponentVersion (name, version, { displayVersion, prerelease, title, startPage } = {}) { | ||
const startPageSpec = startPage | ||
startPage = this.resolvePage(startPageSpec || 'index.adoc', { component: name, version, module: 'ROOT' }) | ||
if (!startPage) { | ||
if (startPageSpec) { | ||
throw new Error(`Start page specified for ${version}@${name} not found: ` + startPageSpec) | ||
} else { | ||
// TODO throw error or report warning | ||
//throw new Error(`Start page for ${version}@${name} not specified and no index page found.`) | ||
const startPageSrc = expandPageSrc({ component: name, version, module: 'ROOT', relative: 'index.adoc' }) | ||
const startPageOut = computeOut(startPageSrc, startPageSrc.family, this.htmlUrlExtensionStyle) | ||
startPage = { pub: computePub(startPageSrc, startPageOut, startPageSrc.family, this.htmlUrlExtensionStyle) } | ||
if (startPageSpec) throw new Error(`Start page specified for ${version}@${name} not found: ` + startPageSpec) | ||
// TODO throw error or report warning; for now, we're just faking it | ||
//throw new Error(`Start page for ${version}@${name} not specified and no index page found.`) | ||
const startPageSrc = expandPageSrc({ component: name, version, module: 'ROOT', relative: 'index.adoc' }) | ||
const startPageOut = computeOut(startPageSrc, startPageSrc.family, this.htmlUrlExtensionStyle) | ||
const startPagePub = computePub(startPageSrc, startPageOut, startPageSrc.family, this.htmlUrlExtensionStyle) | ||
startPage = { pub: startPagePub } | ||
} | ||
const componentVersion = { | ||
version, | ||
displayVersion: displayVersion || version, | ||
title: title || name, | ||
url: startPage.pub.url, | ||
} | ||
if (prerelease) { | ||
componentVersion.prerelease = prerelease | ||
if (!displayVersion && (typeof prerelease === 'string' || prerelease instanceof String)) { | ||
const sep = prerelease.startsWith('-') || prerelease.startsWith('.') ? '' : ' ' | ||
componentVersion.displayVersion = `${version}${sep}${prerelease}` | ||
} | ||
} | ||
const url = startPage.pub.url | ||
const component = this[$components][name] | ||
if (component) { | ||
const versions = component.versions | ||
const insertIdx = versions.findIndex(({ version: candidateVersion }) => { | ||
if (candidateVersion === version) { | ||
throw new Error(`Duplicate version detected for component ${name}: ${version}`) | ||
} | ||
return versionCompare(candidateVersion, version) > 0 | ||
const componentVersions = component.versions | ||
const insertIdx = componentVersions.findIndex(({ version: candidate }) => { | ||
if (candidate === version) throw new Error(`Duplicate version detected for component ${name}: ${version}`) | ||
return versionCompare(candidate, version) > 0 | ||
}) | ||
const versionEntry = { title, version, url } | ||
if (insertIdx < 0) { | ||
versions.push(versionEntry) | ||
if (~insertIdx) { | ||
componentVersions.splice(insertIdx, 0, componentVersion) | ||
} else { | ||
versions.splice(insertIdx, 0, versionEntry) | ||
if (insertIdx === 0) { | ||
component.title = title | ||
component.url = url | ||
} | ||
componentVersions.push(componentVersion) | ||
} | ||
component.latest = componentVersions.find((candidate) => !candidate.prerelease) || componentVersions[0] | ||
} else { | ||
this[$components][name] = Object.defineProperty( | ||
{ name, title, url, versions: [{ title, version, url }] }, | ||
'latestVersion', | ||
this[$components][name] = Object.defineProperties( | ||
{ name, latest: componentVersion, versions: [componentVersion] }, | ||
{ | ||
get: function () { | ||
return this.versions[0] | ||
// NOTE alias latestVersion to latest for backwards compatibility | ||
latestVersion: { | ||
get: function () { | ||
return this.latest | ||
}, | ||
}, | ||
title: { | ||
get: function () { | ||
return this.latest.title | ||
}, | ||
}, | ||
url: { | ||
get: function () { | ||
return this.latest.url | ||
}, | ||
}, | ||
} | ||
@@ -74,4 +91,6 @@ ) | ||
addFile (file) { | ||
const id = this[$generateId](_.pick(file.src, 'component', 'version', 'module', 'family', 'relative')) | ||
if (this[$files][id]) throw new Error(`Duplicate ${file.src.family}: ${id.substr(id.indexOf('/') + 1)}`) | ||
const id = this[$generateId](file.src) | ||
if (this[$files][id]) { | ||
throw new Error(`Duplicate ${file.src.family}: ${id.replace(':' + file.src.family + '$', ':')}`) | ||
} | ||
if (!File.isVinyl(file)) file = new File(file) | ||
@@ -90,3 +109,3 @@ const family = file.src.family | ||
} | ||
if (!file.pub && (publishable || actingFamily === 'navigation')) { | ||
if (!file.pub && (publishable || actingFamily === 'nav')) { | ||
file.pub = computePub(file.src, file.out, actingFamily, this.htmlUrlExtensionStyle) | ||
@@ -97,5 +116,4 @@ } | ||
findBy (options) { | ||
const srcFilter = _.pick(options, 'component', 'version', 'module', 'family', 'relative', 'basename', 'extname') | ||
return _.filter(this[$files], { src: srcFilter }) | ||
findBy (criteria) { | ||
return _.filter(this[$files], { src: criteria }) | ||
} | ||
@@ -116,2 +134,16 @@ | ||
getComponentVersion (component, version) { | ||
return (component.versions || (this.getComponent(component) || {}).versions || []).find( | ||
(candidate) => candidate.version === version | ||
) | ||
} | ||
getComponentMap () { | ||
return Object.assign({}, this[$components]) | ||
} | ||
getComponentMapSortedBy (property) { | ||
return this.getComponentsSortedBy(property).reduce((accum, it) => (accum[it.name] = it) && accum, {}) | ||
} | ||
getComponents () { | ||
@@ -121,6 +153,5 @@ return Object.values(this[$components]) | ||
//getComponentVersion (name, version) { | ||
// const component = this.getComponent(name) | ||
// return component && component.versions.find((candidate) => candidate.version === version) | ||
//} | ||
getComponentsSortedBy (property) { | ||
return this.getComponents().sort((a, b) => a[property].localeCompare(b[property])) | ||
} | ||
@@ -139,3 +170,3 @@ getFiles () { | ||
registerPageAlias (aliasSpec, targetPage) { | ||
const src = parsePageId(aliasSpec, targetPage.src) | ||
const src = parseResourceId(aliasSpec, targetPage.src, ['page']) | ||
// QUESTION should we throw an error if alias is invalid or out of bounds? | ||
@@ -146,6 +177,5 @@ if (!src) return | ||
if (src.version) { | ||
const version = src.version | ||
if (!component.versions.find((candidate) => candidate.version === version)) return | ||
if (!this.getComponentVersion(component, src.version)) return | ||
} else { | ||
src.version = component.latestVersion.version | ||
src.version = component.latest.version | ||
} | ||
@@ -156,3 +186,3 @@ const existingPage = this.getById(src) | ||
let qualifiedSpec = this[$generateId](existingPage.src) | ||
qualifiedSpec = qualifiedSpec.substr(qualifiedSpec.indexOf('/') + 1) | ||
qualifiedSpec = qualifiedSpec.replace(':page$', ':') | ||
const message = | ||
@@ -174,8 +204,29 @@ existingPage === targetPage | ||
resolvePage (pageSpec, context = {}) { | ||
return resolvePage(pageSpec, this, context) | ||
/** | ||
* Attempts to resolve a string contextual page ID spec to a file in the catalog. | ||
* | ||
* Parses the specified contextual page ID spec into a page ID object, then attempts to lookup a | ||
* file with this page ID in the catalog. If a component is specified, but not a version, the | ||
* latest version of the component stored in the catalog is used. If a file cannot be resolved, | ||
* the function returns undefined. If the spec does not match the page ID syntax, this function | ||
* throws an error. | ||
* | ||
* @param {String} spec - The contextual page ID spec (e.g., | ||
* version@component:module:topic/page followed by optional .adoc extension). | ||
* @param {ContentCatalog} catalog - The content catalog in which to resolve the page file. | ||
* @param {Object} [ctx={}] - The context to use to qualified the contextual page ID. | ||
* | ||
* @return {File} The virtual file to which the contextual page ID spec refers, or undefined if the | ||
* file cannot be resolved. | ||
*/ | ||
resolvePage (spec, context = {}) { | ||
return resolveResource(spec, this, context, ['page']) | ||
} | ||
resolveResource (spec, context = {}, families = undefined) { | ||
return resolveResource(spec, this, context, families) | ||
} | ||
[$generateId] ({ component, version, module, family, relative }) { | ||
return `$${family}/${version}@${component}:${module}:${relative}` | ||
return `${version}@${component}:${module}:${family}$${relative}` | ||
} | ||
@@ -231,3 +282,3 @@ } | ||
let url | ||
if (family === 'navigation') { | ||
if (family === 'nav') { | ||
const urlSegments = [src.component] | ||
@@ -234,0 +285,0 @@ if (src.version !== 'master') urlSegments.push(src.version) |
{ | ||
"name": "@antora/content-classifier", | ||
"version": "1.0.3", | ||
"version": "1.1.0", | ||
"description": "Organizes aggregated content into a virtual file catalog for use in an Antora documentation pipeline.", | ||
@@ -23,3 +23,3 @@ "license": "MPL-2.0", | ||
"devDependencies": { | ||
"@antora/content-aggregator": "1.0.3" | ||
"@antora/content-aggregator": "1.1.0" | ||
}, | ||
@@ -26,0 +26,0 @@ "engines": { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
24648
579