@antora/content-classifier 3.0.0-alpha.7 → 3.0.0-beta.2
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.
- package/README.md +1 -1
- package/lib/constants.js +2 -2
- package/lib/content-catalog.js +71 -50
- package/lib/util/parse-resource-id.js +10 -3
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
The Content Classifier is a component in Antora responsible for organizing aggregated content into a virtual file catalog for use in an Antora documentation pipeline.
|
|
4
4
|
|
|
5
5
|
[Antora](https://antora.org) is a modular static site generator designed for creating documentation sites from AsciiDoc documents.
|
|
6
|
-
Its site generator
|
|
6
|
+
Its site generator aggregates documents from versioned content repositories and processes them using [Asciidoctor](https://asciidoctor.org).
|
|
7
7
|
|
|
8
8
|
## Copyright and License
|
|
9
9
|
|
package/lib/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
module.exports = Object.freeze({
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
ROOT_INDEX_ALIAS_ID: { component: 'ROOT', version: '', module: 'ROOT', family: 'alias', relative: 'index.adoc' },
|
|
5
|
+
ROOT_INDEX_PAGE_ID: { component: 'ROOT', version: '', module: 'ROOT', family: 'page', relative: 'index.adoc' },
|
|
6
6
|
})
|
package/lib/content-catalog.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const File = require('./file')
|
|
4
|
+
const invariably = { void: () => undefined }
|
|
4
5
|
const logger = require('./logger')
|
|
5
6
|
const { lookup: resolveMimeType } = require('./mime-types-with-asciidoc')
|
|
6
7
|
const parseResourceId = require('./util/parse-resource-id')
|
|
@@ -8,7 +9,7 @@ const { posix: path } = require('path')
|
|
|
8
9
|
const resolveResource = require('./util/resolve-resource')
|
|
9
10
|
const versionCompare = require('./util/version-compare-desc')
|
|
10
11
|
|
|
11
|
-
const {
|
|
12
|
+
const { ROOT_INDEX_ALIAS_ID, ROOT_INDEX_PAGE_ID } = require('./constants')
|
|
12
13
|
const SPACE_RX = / /g
|
|
13
14
|
|
|
14
15
|
const $components = Symbol('components')
|
|
@@ -72,15 +73,17 @@ class ContentCatalog {
|
|
|
72
73
|
const component = this[$components].get(name)
|
|
73
74
|
if (component) {
|
|
74
75
|
const componentVersions = component.versions
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return versionCompare(candidate, version) > 0
|
|
78
|
-
})
|
|
79
|
-
if (~insertIdx) {
|
|
80
|
-
componentVersions.splice(insertIdx, 0, componentVersion)
|
|
81
|
-
} else {
|
|
82
|
-
componentVersions.push(componentVersion)
|
|
76
|
+
if (componentVersions.find(({ version: candidate }) => candidate === version)) {
|
|
77
|
+
throw new Error(`Duplicate version detected for component ${name}: ${version}`)
|
|
83
78
|
}
|
|
79
|
+
const insertIdx = prerelease
|
|
80
|
+
? componentVersions.findIndex(({ version: candidateVersion, prerelease: candidatePrerelease }) =>
|
|
81
|
+
candidatePrerelease ? versionCompare(candidateVersion, version) > 0 : true
|
|
82
|
+
)
|
|
83
|
+
: componentVersions.findIndex(({ version: candidateVersion, prerelease: candidatePrerelease }) =>
|
|
84
|
+
candidatePrerelease ? false : versionCompare(candidateVersion, version) > 0
|
|
85
|
+
)
|
|
86
|
+
~insertIdx ? componentVersions.splice(insertIdx, 0, componentVersion) : componentVersions.push(componentVersion)
|
|
84
87
|
if ((component.latest = componentVersions.find((candidate) => !candidate.prerelease))) {
|
|
85
88
|
if (componentVersions[0] !== component.latest) component.latestPrerelease = componentVersions[0]
|
|
86
89
|
} else {
|
|
@@ -120,12 +123,16 @@ class ContentCatalog {
|
|
|
120
123
|
addFile (file) {
|
|
121
124
|
const src = file.src
|
|
122
125
|
let family = src.family
|
|
126
|
+
let filesForFamily = this[$files].get(family)
|
|
127
|
+
if (!filesForFamily) this[$files].set(family, (filesForFamily = new Map()))
|
|
123
128
|
const key = generateKey(src)
|
|
124
|
-
if (
|
|
129
|
+
if (filesForFamily.has(key)) {
|
|
125
130
|
if (family === 'alias') {
|
|
126
131
|
throw new Error(`Duplicate alias: ${generateResourceSpec(src)}`)
|
|
127
132
|
} else {
|
|
128
|
-
const details = [
|
|
133
|
+
const details = [filesForFamily.get(key), file]
|
|
134
|
+
.map((it, idx) => ` ${idx + 1}: ${getFileLocation(it)}`)
|
|
135
|
+
.join('\n')
|
|
129
136
|
if (family === 'nav') {
|
|
130
137
|
throw new Error(`Duplicate nav in ${src.version}@${src.component}: ${file.path}\n${details}`)
|
|
131
138
|
} else {
|
|
@@ -162,35 +169,42 @@ class ContentCatalog {
|
|
|
162
169
|
('/' + src.relative).indexOf('/_') < 0
|
|
163
170
|
) {
|
|
164
171
|
publishable = true
|
|
165
|
-
versionSegment = computeVersionSegment.
|
|
172
|
+
versionSegment = computeVersionSegment.call(this, src.component, src.version)
|
|
166
173
|
file.out = computeOut(src, family, versionSegment, this.htmlUrlExtensionStyle)
|
|
167
174
|
}
|
|
168
175
|
if (!file.pub && (publishable || family === 'nav')) {
|
|
169
|
-
if (versionSegment == null) versionSegment = computeVersionSegment.
|
|
176
|
+
if (versionSegment == null) versionSegment = computeVersionSegment.call(this, src.component, src.version)
|
|
170
177
|
file.pub = computePub(src, file.out, family, versionSegment, this.htmlUrlExtensionStyle)
|
|
171
178
|
}
|
|
172
|
-
|
|
179
|
+
filesForFamily.set(key, file)
|
|
173
180
|
return file
|
|
174
181
|
}
|
|
175
182
|
|
|
176
183
|
findBy (criteria) {
|
|
177
184
|
const criteriaEntries = Object.entries(criteria)
|
|
185
|
+
const family = criteria.family
|
|
186
|
+
if (criteriaEntries.length === 1 && family) {
|
|
187
|
+
const filesForFamily = this[$files].get(family)
|
|
188
|
+
return filesForFamily ? [...filesForFamily.values()] : []
|
|
189
|
+
}
|
|
178
190
|
const accum = []
|
|
179
|
-
for (const
|
|
180
|
-
const
|
|
181
|
-
|
|
191
|
+
for (const filesForFamily of this[$files].values()) {
|
|
192
|
+
for (const candidate of filesForFamily.values()) {
|
|
193
|
+
const candidateSrc = candidate.src
|
|
194
|
+
if (criteriaEntries.every(([key, val]) => candidateSrc[key] === val)) accum.push(candidate)
|
|
195
|
+
}
|
|
182
196
|
}
|
|
183
197
|
return accum
|
|
184
198
|
}
|
|
185
199
|
|
|
186
|
-
getById (
|
|
187
|
-
return this[$files].get(
|
|
200
|
+
getById (id) {
|
|
201
|
+
return (this[$files].get(id.family) || { get: invariably.void }).get(generateKey(id))
|
|
188
202
|
}
|
|
189
203
|
|
|
190
204
|
getByPath ({ component, version, path: path_ }) {
|
|
191
|
-
for (const
|
|
192
|
-
|
|
193
|
-
return
|
|
205
|
+
for (const filesForFamily of this[$files].values()) {
|
|
206
|
+
for (const it of filesForFamily.values()) {
|
|
207
|
+
if (it.path === path_ && it.src.component === component && it.src.version === version) return it
|
|
194
208
|
}
|
|
195
209
|
}
|
|
196
210
|
}
|
|
@@ -214,26 +228,28 @@ class ContentCatalog {
|
|
|
214
228
|
}
|
|
215
229
|
|
|
216
230
|
getFiles () {
|
|
217
|
-
|
|
231
|
+
const accum = []
|
|
232
|
+
for (const filesForFamily of this[$files].values()) {
|
|
233
|
+
for (const file of filesForFamily.values()) accum.push(file)
|
|
234
|
+
}
|
|
235
|
+
return accum
|
|
218
236
|
}
|
|
219
237
|
|
|
220
238
|
getPages (filter) {
|
|
221
|
-
const
|
|
239
|
+
const candidates = this[$files].get('page')
|
|
240
|
+
if (!candidates) return []
|
|
222
241
|
if (filter) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
242
|
+
const accum = []
|
|
243
|
+
for (const candidate of candidates.values()) filter(candidate) && accum.push(candidate)
|
|
244
|
+
return accum
|
|
226
245
|
} else {
|
|
227
|
-
|
|
228
|
-
if (candidate.src.family === 'page') accum.push(candidate)
|
|
229
|
-
}
|
|
246
|
+
return [...candidates.values()]
|
|
230
247
|
}
|
|
231
|
-
return accum
|
|
232
248
|
}
|
|
233
249
|
|
|
234
250
|
// TODO add `follow` argument to control whether alias is followed
|
|
235
251
|
getSiteStartPage () {
|
|
236
|
-
return this.getById(
|
|
252
|
+
return this.getById(ROOT_INDEX_PAGE_ID) || (this.getById(ROOT_INDEX_ALIAS_ID) || {}).rel
|
|
237
253
|
}
|
|
238
254
|
|
|
239
255
|
registerComponentVersionStartPage (name, componentVersion, startPageSpec = undefined) {
|
|
@@ -245,7 +261,7 @@ class ContentCatalog {
|
|
|
245
261
|
}
|
|
246
262
|
let startPage
|
|
247
263
|
let startPageSrc
|
|
248
|
-
const indexPageId = {
|
|
264
|
+
const indexPageId = Object.assign({}, ROOT_INDEX_PAGE_ID, { component: name, version })
|
|
249
265
|
if (startPageSpec) {
|
|
250
266
|
if (
|
|
251
267
|
(startPage = this.resolvePage(startPageSpec, indexPageId)) &&
|
|
@@ -273,7 +289,7 @@ class ContentCatalog {
|
|
|
273
289
|
componentVersion.url = startPage.pub.url
|
|
274
290
|
} else {
|
|
275
291
|
// QUESTION: should we warn if the default start page cannot be found?
|
|
276
|
-
const versionSegment = computeVersionSegment.
|
|
292
|
+
const versionSegment = computeVersionSegment.call(this, name, version)
|
|
277
293
|
componentVersion.url = computePub(
|
|
278
294
|
(startPageSrc = prepareSrc(Object.assign({}, indexPageId, { family: 'page' }))),
|
|
279
295
|
computeOut(startPageSrc, startPageSrc.family, versionSegment, this.htmlUrlExtensionStyle),
|
|
@@ -286,7 +302,7 @@ class ContentCatalog {
|
|
|
286
302
|
const symbolicVersionAlias = createSymbolicVersionAlias(
|
|
287
303
|
name,
|
|
288
304
|
version,
|
|
289
|
-
computeVersionSegment.
|
|
305
|
+
computeVersionSegment.call(this, name, version, 'alias'),
|
|
290
306
|
this.latestVersionUrlSegmentStrategy
|
|
291
307
|
)
|
|
292
308
|
if (symbolicVersionAlias) this.addFile(symbolicVersionAlias)
|
|
@@ -296,7 +312,8 @@ class ContentCatalog {
|
|
|
296
312
|
if (!startPageSpec) return
|
|
297
313
|
const rel = this.resolvePage(startPageSpec)
|
|
298
314
|
if (rel) {
|
|
299
|
-
|
|
315
|
+
if (!(this.getSiteStartPage() || { synthetic: true }).synthetic) return
|
|
316
|
+
return this.addFile({ src: Object.assign({}, ROOT_INDEX_ALIAS_ID), rel, synthetic: true })
|
|
300
317
|
} else if (rel === false) {
|
|
301
318
|
logger.warn('Start page specified for site has invalid syntax: %s', startPageSpec)
|
|
302
319
|
} else if (~startPageSpec.indexOf(':')) {
|
|
@@ -328,18 +345,17 @@ class ContentCatalog {
|
|
|
328
345
|
` existing page: ${getFileLocation(existingPage)}`
|
|
329
346
|
)
|
|
330
347
|
}
|
|
331
|
-
const existingAlias = this.getById(Object.assign({}, src, { family: 'alias' }))
|
|
332
|
-
if (existingAlias) {
|
|
333
|
-
throw new Error(
|
|
334
|
-
`Duplicate alias: ${generateResourceSpec(src)} (specified as: ${spec})\n` +
|
|
335
|
-
` source: ${getFileLocation(target)}`
|
|
336
|
-
)
|
|
337
|
-
}
|
|
338
348
|
} else if (src.version == null) {
|
|
339
349
|
// QUESTION should we skip registering alias in this case?
|
|
340
350
|
src.version = ''
|
|
341
351
|
}
|
|
342
352
|
src.family = 'alias'
|
|
353
|
+
const existingAlias = this.getById(src)
|
|
354
|
+
if (existingAlias) {
|
|
355
|
+
throw new Error(
|
|
356
|
+
`Duplicate alias: ${generateResourceSpec(src)} (specified as: ${spec})\n source: ${getFileLocation(target)}`
|
|
357
|
+
)
|
|
358
|
+
}
|
|
343
359
|
// NOTE the redirect producer will populate contents when the redirect facility is 'static'
|
|
344
360
|
const alias = this.addFile({ src, rel: target })
|
|
345
361
|
// NOTE record the first alias this target claims as the preferred one
|
|
@@ -352,9 +368,10 @@ class ContentCatalog {
|
|
|
352
368
|
*
|
|
353
369
|
* Parses the specified contextual page ID spec into a page ID object, then attempts to lookup a
|
|
354
370
|
* file with this page ID in the catalog. If a component is specified, but not a version, the
|
|
355
|
-
* latest version of the component stored in the catalog is used. If a
|
|
356
|
-
* the
|
|
357
|
-
*
|
|
371
|
+
* latest version of the component stored in the catalog is used. If a page cannot be resolved,
|
|
372
|
+
* the search is attempted again for an "alias". If neither a page or alias can be resolved, the
|
|
373
|
+
* function returns undefined. If the spec does not match the page ID syntax, this function throws
|
|
374
|
+
* an error.
|
|
358
375
|
*
|
|
359
376
|
* @param {String} spec - The contextual page ID spec (e.g., version@component:module:topic/page.adoc).
|
|
360
377
|
* @param {ContentCatalog} catalog - The content catalog in which to resolve the page file.
|
|
@@ -394,8 +411,8 @@ class ContentCatalog {
|
|
|
394
411
|
*/
|
|
395
412
|
ContentCatalog.prototype.getAll = ContentCatalog.prototype.getFiles
|
|
396
413
|
|
|
397
|
-
function generateKey ({ component, version, module: module_,
|
|
398
|
-
return `${version}@${component}:${module_}:${
|
|
414
|
+
function generateKey ({ component, version, module: module_, relative }) {
|
|
415
|
+
return `${version}@${component}:${module_}:${relative}`
|
|
399
416
|
}
|
|
400
417
|
|
|
401
418
|
function generateResourceSpec ({ component, version, module: module_, family, relative }, shorthand = true) {
|
|
@@ -435,6 +452,7 @@ function prepareSrc (src) {
|
|
|
435
452
|
|
|
436
453
|
function computeOut (src, family, version, htmlUrlExtensionStyle) {
|
|
437
454
|
let { component, module: module_, basename, extname, relative, stem } = src
|
|
455
|
+
if (component === 'ROOT') component = ''
|
|
438
456
|
if (module_ === 'ROOT') module_ = ''
|
|
439
457
|
let indexifyPathSegment = ''
|
|
440
458
|
let familyPathSegment = ''
|
|
@@ -465,8 +483,11 @@ function computePub (src, out, family, version, htmlUrlExtensionStyle) {
|
|
|
465
483
|
const pub = {}
|
|
466
484
|
let url
|
|
467
485
|
if (family === 'nav') {
|
|
468
|
-
const
|
|
469
|
-
|
|
486
|
+
const component = src.component || 'ROOT'
|
|
487
|
+
const urlSegments = component === 'ROOT' ? [] : [component]
|
|
488
|
+
if (version) urlSegments.push(version)
|
|
489
|
+
const module_ = src.module || 'ROOT'
|
|
490
|
+
if (module_ !== 'ROOT') urlSegments.push(module_)
|
|
470
491
|
// an artificial URL used for resolving page references in navigation model
|
|
471
492
|
url = '/' + urlSegments.join('/') + '/'
|
|
472
493
|
pub.moduleRootPath = '.'
|
|
@@ -56,9 +56,16 @@ function parseResourceId (spec, ctx = {}, defaultFamily = 'page', permittedFamil
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
if (~relative.indexOf('/')) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
const relativeSegments = relative.split('/')
|
|
60
|
+
let from
|
|
61
|
+
if (relativeSegments[0] === '.' && (from = ctx.relative)) {
|
|
62
|
+
relativeSegments[0] = from.substr(0, (from.lastIndexOf('/') + 1 || 1) - 1)
|
|
63
|
+
}
|
|
64
|
+
relative = relativeSegments
|
|
65
|
+
.reduce((accum, segment) => {
|
|
66
|
+
segment === '..' ? accum.pop() : (segment || '.') !== '.' && accum.push(segment)
|
|
67
|
+
return accum
|
|
68
|
+
}, [])
|
|
62
69
|
.join('/')
|
|
63
70
|
}
|
|
64
71
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antora/content-classifier",
|
|
3
|
-
"version": "3.0.0-
|
|
3
|
+
"version": "3.0.0-beta.2",
|
|
4
4
|
"description": "Organizes aggregated content into a virtual file catalog for use in an Antora documentation pipeline.",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"author": "OpenDevise Inc. (https://opendevise.com)",
|
|
@@ -16,12 +16,12 @@
|
|
|
16
16
|
},
|
|
17
17
|
"main": "lib/index.js",
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@antora/logger": "3.0.0-
|
|
19
|
+
"@antora/logger": "3.0.0-beta.2",
|
|
20
20
|
"mime-types": "~2.1",
|
|
21
21
|
"vinyl": "~2.2"
|
|
22
22
|
},
|
|
23
23
|
"engines": {
|
|
24
|
-
"node": ">=
|
|
24
|
+
"node": ">=12.21.0"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"lib/"
|
|
@@ -34,5 +34,5 @@
|
|
|
34
34
|
"static site",
|
|
35
35
|
"web publishing"
|
|
36
36
|
],
|
|
37
|
-
"gitHead": "
|
|
37
|
+
"gitHead": "5cd3f9cc70622e465cb44daf1aa2035ed5a35f54"
|
|
38
38
|
}
|