@antora/content-classifier 3.0.0-alpha.6 → 3.0.0-beta.1

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 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 pipeline aggregates documents from versioned content repositories and processes them using [Asciidoctor](https://asciidoctor.org).
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
 
@@ -43,10 +43,12 @@ function classifyContent (playbook, aggregate, siteAsciiDocConfig = {}) {
43
43
  }
44
44
 
45
45
  function allocateSrc (file, component, version, nav) {
46
+ const extname = file.src.extname
46
47
  const filepath = file.path
47
- const pathSegments = filepath.split('/')
48
48
  const navInfo = nav && getNavInfo(filepath, nav)
49
+ const pathSegments = filepath.split('/')
49
50
  if (navInfo) {
51
+ if (extname !== '.adoc') return // ignore file
50
52
  file.nav = navInfo
51
53
  file.src.family = 'nav'
52
54
  if (pathSegments[0] === 'modules' && pathSegments.length > 2) {
@@ -67,45 +69,47 @@ function allocateSrc (file, component, version, nav) {
67
69
  file.src.family = 'partial'
68
70
  // relative to modules/<module>/pages/_partials (deprecated)
69
71
  file.src.relative = pathSegments.slice(4).join('/')
70
- } else if (file.src.extname === '.adoc') {
72
+ } else if (extname === '.adoc') {
71
73
  file.src.family = 'page'
72
74
  // relative to modules/<module>/pages
73
75
  file.src.relative = pathSegments.slice(3).join('/')
74
76
  } else {
75
- // ignore file
76
- return
77
+ return // ignore file
77
78
  }
78
79
  break
79
80
  case 'assets':
80
81
  switch ((familyFolder = pathSegments[3])) {
81
82
  case 'attachments':
82
83
  case 'images':
84
+ if (!extname) return // ignore file
83
85
  file.src.family = familyFolder.substr(0, familyFolder.length - 1)
84
86
  // relative to modules/<module>/assets/<family>s
85
87
  file.src.relative = pathSegments.slice(4).join('/')
86
88
  break
87
89
  default:
88
- // ignore file
89
- return
90
+ return // ignore file
90
91
  }
91
92
  break
92
93
  case 'attachments':
93
- case 'examples':
94
94
  case 'images':
95
+ if (!extname) return
96
+ file.src.family = familyFolder.substr(0, familyFolder.length - 1)
97
+ // relative to modules/<module>/<family>s
98
+ file.src.relative = pathSegments.slice(3).join('/')
99
+ break
100
+ case 'examples':
95
101
  case 'partials':
96
102
  file.src.family = familyFolder.substr(0, familyFolder.length - 1)
97
103
  // relative to modules/<module>/<family>s
98
104
  file.src.relative = pathSegments.slice(3).join('/')
99
105
  break
100
106
  default:
101
- // ignore file
102
- return
107
+ return // ignore file
103
108
  }
104
109
  file.src.module = pathSegments[1]
105
110
  file.src.moduleRootPath = calculateRootPath(pathSegments.length - 3)
106
111
  } else {
107
- // ignore file
108
- return
112
+ return // ignore file
109
113
  }
110
114
  file.src.component = component
111
115
  file.src.version = version
@@ -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')
@@ -59,7 +60,7 @@ class ContentCatalog {
59
60
  Object.defineProperty(componentVersion, 'name', { value: name, enumerable: true })
60
61
  if (prerelease) {
61
62
  componentVersion.prerelease = prerelease
62
- if (!displayVersion && (typeof prerelease === 'string' || prerelease instanceof String)) {
63
+ if (!displayVersion && prerelease.constructor === String) {
63
64
  if (version) {
64
65
  const ch0 = prerelease.charAt()
65
66
  componentVersion.displayVersion = `${version}${ch0 === '-' || ch0 === '.' ? '' : ' '}${prerelease}`
@@ -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
- const insertIdx = componentVersions.findIndex(({ version: candidate }) => {
76
- if (candidate === version) throw new Error(`Duplicate version detected for component ${name}: ${version}`)
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 (this[$files].has(key)) {
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 = [this.getById(src), file].map((it, idx) => ` ${idx + 1}: ${getFileLocation(it)}`).join('\n')
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.bind(this)(src.component, src.version)
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.bind(this)(src.component, src.version)
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
- this[$files].set(key, file)
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 candidate of this[$files].values()) {
180
- const candidateSrc = candidate.src
181
- if (criteriaEntries.every(([key, val]) => candidateSrc[key] === val)) accum.push(candidate)
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 ({ component, version, module: module_, family, relative }) {
187
- return this[$files].get(generateKey({ component, version, module: module_, family, relative }))
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 candidate of this[$files].values()) {
192
- if (candidate.path === path_ && candidate.src.component === component && candidate.src.version === version) {
193
- return candidate
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,21 +228,23 @@ class ContentCatalog {
214
228
  }
215
229
 
216
230
  getFiles () {
217
- return [...this[$files].values()]
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 accum = []
239
+ const candidates = this[$files].get('page')
240
+ if (!candidates) return []
222
241
  if (filter) {
223
- for (const candidate of this[$files].values()) {
224
- if (candidate.src.family === 'page' && filter(candidate)) accum.push(candidate)
225
- }
242
+ const accum = []
243
+ for (const candidate of candidates.values()) filter(candidate) && accum.push(candidate)
244
+ return accum
226
245
  } else {
227
- for (const candidate of this[$files].values()) {
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
@@ -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.bind(this)(name, version)
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.bind(this)(name, version, 'alias'),
305
+ computeVersionSegment.call(this, name, version, 'alias'),
290
306
  this.latestVersionUrlSegmentStrategy
291
307
  )
292
308
  if (symbolicVersionAlias) this.addFile(symbolicVersionAlias)
@@ -328,18 +344,17 @@ class ContentCatalog {
328
344
  ` existing page: ${getFileLocation(existingPage)}`
329
345
  )
330
346
  }
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
347
  } else if (src.version == null) {
339
348
  // QUESTION should we skip registering alias in this case?
340
349
  src.version = ''
341
350
  }
342
351
  src.family = 'alias'
352
+ const existingAlias = this.getById(src)
353
+ if (existingAlias) {
354
+ throw new Error(
355
+ `Duplicate alias: ${generateResourceSpec(src)} (specified as: ${spec})\n source: ${getFileLocation(target)}`
356
+ )
357
+ }
343
358
  // NOTE the redirect producer will populate contents when the redirect facility is 'static'
344
359
  const alias = this.addFile({ src, rel: target })
345
360
  // NOTE record the first alias this target claims as the preferred one
@@ -394,8 +409,8 @@ class ContentCatalog {
394
409
  */
395
410
  ContentCatalog.prototype.getAll = ContentCatalog.prototype.getFiles
396
411
 
397
- function generateKey ({ component, version, module: module_, family, relative }) {
398
- return `${version}@${component}:${module_}:${family}$${relative}`
412
+ function generateKey ({ component, version, module: module_, relative }) {
413
+ return `${version}@${component}:${module_}:${relative}`
399
414
  }
400
415
 
401
416
  function generateResourceSpec ({ component, version, module: module_, family, relative }, shorthand = true) {
@@ -56,9 +56,16 @@ function parseResourceId (spec, ctx = {}, defaultFamily = 'page', permittedFamil
56
56
  }
57
57
 
58
58
  if (~relative.indexOf('/')) {
59
- relative = relative
60
- .split('/')
61
- .filter((it) => it && it !== '.' && it !== '..')
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-alpha.6",
3
+ "version": "3.0.0-beta.1",
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-alpha.6",
19
+ "@antora/logger": "3.0.0-beta.1",
20
20
  "mime-types": "~2.1",
21
21
  "vinyl": "~2.2"
22
22
  },
23
23
  "engines": {
24
- "node": ">=10.17.0"
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": "38ec002e88eede3ce5c401a6e226d1a0356945c5"
37
+ "gitHead": "7c5ef1ea93dd489af533c80a936c736013c41769"
38
38
  }