poops 1.1.0 → 1.2.0

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/lib/markups.js CHANGED
@@ -1,133 +1,80 @@
1
- import { pathExists, pathIsDirectory, readDataFile, deleteDirectory, mkDir, parseFrontMatter, clearFrontMatterCache } from './utils/helpers.js'
1
+ import { pathExists, pathIsDirectory, readDataFile, mkDir, buildTime } from './utils/helpers.js'
2
+ import { replaceOutExtensions, getRelativePathPrefix, getPageUrl, getPageUrlRelativeToOutput, parseFrontMatter, clearFrontMatterCache } from './markup/helpers.js'
3
+ import { collectionAutoDiscovery, getCollectionDataBasedOnConfig, buildCollectionPaginationData, generateCollectionPaginationPages } from './markup/collections.js'
4
+ import { generateIndexFiles } from './markup/indexer.js'
5
+ import NunjucksEngine from './markup/engines/nunjucks.js'
6
+ import LiquidEngine from './markup/engines/liquid.js'
2
7
  import fs from 'node:fs'
3
8
  import { globSync } from 'glob'
4
- import { parse as parseMarkdown } from 'marked'
5
- import moment from 'moment'
6
- import nunjucks from 'nunjucks'
7
9
  import path from 'node:path'
8
- import PrintStyle from './utils/print-style.js'
9
- import yaml from 'yaml'
10
+ import log from './utils/log.js'
10
11
 
11
- const pstyle = new PrintStyle()
12
-
13
- class RelativeLoader extends nunjucks.Loader {
14
- constructor(templatesDir, includePaths) {
15
- super()
16
- this.templatesDir = templatesDir
17
- this.includePaths = includePaths || []
18
- this.includePaths.push('_*') // XXX: It is better to define templates and layouts directories in the config file? then all together include paths?
19
- }
20
-
21
- getSource(name) {
22
- let fullPath = name
23
- if (!fs.existsSync(name)) {
24
- let pattern = `**/${name}`
25
- if (this.includePaths) {
26
- pattern = `{${this.includePaths.join(',')}}/${pattern}`
27
- }
28
- fullPath = globSync(path.join(this.templatesDir, pattern))[0]
29
- }
30
- if (!fs.existsSync(fullPath)) {
31
- console.log(`${pstyle.cyanBright + pstyle.bold}[markup]${pstyle.reset} ${pstyle.redBright + pstyle.bold}[error]${pstyle.reset}${pstyle.dim} Template not found:${pstyle.reset} ${pstyle.italic + pstyle.underline}${name}${pstyle.reset}`)
32
- // throw new Error(`Template not found: ${name}`)
33
- return { src: '', path: fullPath, noCache: true }
34
- }
35
-
36
- let source = ''
37
- let frontMatter = {}
38
-
39
- try {
40
- const frontMatterResult = parseFrontMatter(fullPath)
41
- frontMatter = frontMatterResult.frontMatter
42
- source = frontMatterResult.content
43
- } catch (err) {
44
- console.log(`${pstyle.redBright + pstyle.bold}[error]${pstyle.reset}${pstyle.dim} Failed parsing front matter:${pstyle.reset} ${pstyle.italic + pstyle.underline}${fullPath}${pstyle.reset}`)
45
- console.log(err)
46
- }
47
-
48
- if (path.extname(fullPath) === '.md') {
49
- source = parseMarkdown(source)
50
- }
51
-
52
- if (frontMatter.layout) {
53
- source = `{% extends '${frontMatter.layout}.html' %}\n{% block content %}\n${source}\n{% endblock %}`
54
- }
55
-
56
- return { src: source, path: fullPath, noCache: true }
57
- }
58
-
59
- resolve(from, to) {
60
- return path.resolve(path.dirname(from), to)
61
- }
12
+ const ENGINES = {
13
+ nunjucks: NunjucksEngine,
14
+ liquid: LiquidEngine
62
15
  }
63
16
 
64
17
  export default class Markups {
65
18
  constructor(config) {
66
19
  this.config = config
67
- if (!this.config.markup || !this.config.markup.in) return
68
- if (!this.config.markup.options) this.config.markup.options = {}
20
+ const moduleConfig = this.config.markup
21
+
22
+ if (!moduleConfig || !moduleConfig.in) return
23
+ if (!moduleConfig.options) moduleConfig.options = {}
24
+
25
+ // Determine engine
26
+ const engineName = moduleConfig.engine || moduleConfig.options.engine || 'nunjucks'
27
+ this.logTag = 'markup'
28
+
29
+ // Normalize config
30
+ this.markupIn = moduleConfig.in
31
+ this.markupOut = moduleConfig.out || process.cwd()
32
+ this.siteData = moduleConfig.site || moduleConfig.options.site || {}
33
+ this.timeDateFormat = moduleConfig.options.timeDateFormat || moduleConfig.timeDateFormat
34
+ this.collectionsConfig = moduleConfig.options.collections || moduleConfig.collections
35
+ this.includePaths = moduleConfig.includePaths || moduleConfig.options.includePaths || []
36
+ this.searchIndexConfig = moduleConfig.options.searchIndex || moduleConfig.searchIndex
37
+ this.sitemapConfig = moduleConfig.options.sitemap || moduleConfig.sitemap
69
38
  this.dataFiles = []
70
- this.includePaths = this.config.markup.includePaths || this.config.markup.options.includePaths || []
71
-
72
- const options = {
73
- autoescape: false,
74
- watch: false,
75
- noCache: true
76
- }
77
39
 
78
- if (this.config.markup.autoescape) {
79
- options.autoescape = this.config.markup.autoescape || this.config.markup.options.autoescape
40
+ // Instantiate engine
41
+ const EngineClass = ENGINES[engineName]
42
+ if (!EngineClass) {
43
+ log({ tag: 'error', text: `Unknown markup engine: ${engineName}` })
44
+ return
80
45
  }
81
46
 
82
- this.nunjucksEnv = new nunjucks.Environment(new RelativeLoader(path.join(process.cwd(), this.config.markup.in), this.includePaths), options)
83
-
84
- // Add custom filters
85
- this.nunjucksEnv.addFilter('slugify', str => {
86
- return str.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-')
47
+ const templatesDir = path.join(process.cwd(), this.markupIn)
48
+ this.engine = new EngineClass(templatesDir, this.includePaths, {
49
+ autoescape: moduleConfig.autoescape || moduleConfig.options.autoescape || false
87
50
  })
88
51
 
89
- this.nunjucksEnv.addFilter('jsonify', obj => {
90
- return JSON.stringify(obj)
91
- })
92
-
93
- this.nunjucksEnv.addFilter('markdown', str => {
94
- return parseMarkdown(str)
95
- })
96
-
97
- this.nunjucksEnv.addFilter('date', (str, template) => {
98
- if (!template) template = this.config.markup.options.timeDateFormat || this.config.markup.timeDateFormat
99
- if (!template) return str
100
- const date = !str || str.trim() === '' ? new Date() : new Date(str)
101
- return moment(date).format(template)
102
- })
52
+ this.engine.registerFilters({ timeDateFormat: this.timeDateFormat, markupOut: this.markupOut })
53
+ this.engine.registerTags(() => path.join(process.cwd(), this.markupOut))
103
54
 
104
55
  // Load global variables
105
-
106
56
  const pkgPath = path.join(process.cwd(), 'package.json')
107
-
108
57
  if (fs.existsSync(pkgPath)) {
109
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
110
- this.nunjucksEnv.addGlobal('package', pkg)
58
+ this.engine.setGlobal('package', JSON.parse(fs.readFileSync(pkgPath, 'utf-8')))
111
59
  }
112
60
 
113
- const siteData = this.config.markup.site || this.config.markup.options.site || {}
114
- this.nunjucksEnv.addGlobal('site', siteData)
61
+ this.engine.setGlobal('site', this.siteData)
115
62
 
116
63
  if (this.config.livereload_port) {
117
- this.nunjucksEnv.addGlobal('livereload_port', this.config.livereload_port)
64
+ this.engine.setGlobal('livereload_port', this.config.livereload_port)
118
65
  }
119
66
 
120
- if (this.config.ssgData) {
121
- for (const [name, html] of Object.entries(this.config.ssgData)) {
122
- this.nunjucksEnv.addGlobal(name, html)
67
+ if (this.config.reactorData) {
68
+ for (const [name, html] of Object.entries(this.config.reactorData)) {
69
+ this.engine.setGlobal(name, html)
123
70
  }
124
71
  }
125
72
 
126
- const data = this.config.markup.data || this.config.markup.options.data
73
+ const data = moduleConfig.data || moduleConfig.options.data
127
74
  this.loadDataFiles(data)
128
75
 
129
- if (!this.config.markup.out) {
130
- this.config.markup.out = process.cwd()
76
+ if (!moduleConfig.out) {
77
+ moduleConfig.out = this.markupOut
131
78
  }
132
79
  }
133
80
 
@@ -139,30 +86,37 @@ export default class Markups {
139
86
  files = [files]
140
87
  }
141
88
 
142
- const promises = []
143
-
144
- // TODO: GLOB all files if it's a directory path
89
+ const dataDir = pathIsDirectory(this.markupIn) ? this.markupIn : path.dirname(this.markupIn)
90
+ const resolved = []
91
+ for (const file of files) {
92
+ const fullPath = path.join(process.cwd(), dataDir, file)
93
+ if (pathIsDirectory(fullPath)) {
94
+ const dirFiles = globSync(path.join(fullPath, '**/*.+(json|yml|yaml)'))
95
+ for (const f of dirFiles) {
96
+ resolved.push(path.relative(path.join(process.cwd(), dataDir), f))
97
+ }
98
+ } else {
99
+ resolved.push(file)
100
+ }
101
+ }
145
102
 
146
- if (!this.dataFiles.length) this.dataFiles = files
103
+ if (!this.dataFiles.length) this.dataFiles = resolved
147
104
 
148
- for (const dataFile of files) {
105
+ for (const dataFile of resolved) {
149
106
  try {
150
- const dataDir = pathIsDirectory(this.config.markup.in) ? this.config.markup.in : path.dirname(this.config.markup.in)
151
107
  const data = readDataFile(path.join(process.cwd(), dataDir, dataFile))
152
108
  const globalKeyName = path.basename(dataFile, path.extname(dataFile)).replace(/[.\-\s]/g, '_')
153
-
154
- promises.push(this.nunjucksEnv.addGlobal(globalKeyName, data))
109
+ this.engine.setGlobal(globalKeyName, data)
155
110
  } catch (err) {
156
- console.log(`${pstyle.redBright + pstyle.bold}[error]${pstyle.reset}${pstyle.dim} Data file not found:${pstyle.reset} ${pstyle.italic + pstyle.underline}${dataFile}${pstyle.reset}`)
111
+ log({ tag: 'error', text: 'Data file not found:', link: dataFile })
157
112
  continue
158
113
  }
159
114
  }
160
-
161
- return Promise.all(promises)
162
115
  }
163
116
 
164
117
  reloadDataFiles() {
165
- return this.loadDataFiles(this.dataFiles)
118
+ this.loadDataFiles(this.dataFiles)
119
+ return Promise.resolve()
166
120
  }
167
121
 
168
122
  generateMarkupGlobPattern(excludes) {
@@ -176,224 +130,13 @@ export default class Markups {
176
130
  markupDefaultExcludes.push(...this.config.includePaths)
177
131
  }
178
132
 
179
- markupDefaultExcludes.push('_*') // Ignore directories starting with underscore
180
-
181
- markupDefaultExcludes = [...new Set(markupDefaultExcludes)] // Remove duplicates
182
-
183
- return `!(${markupDefaultExcludes.join('|')})/**/*.+(html|xml|rss|atom|json|njk|md)`
184
- }
185
-
186
- getFrontMatter(file, fileName) {
187
- const source = fileName ? file : fs.readFileSync(file, 'utf-8')
188
- if (!fileName) fileName = file
189
-
190
- const match = source.match(/^\s*---\s*([\s\S]*?)---\s*/) // Front matter match
191
- let frontMatter = {}
192
-
193
- if (match) {
194
- try {
195
- frontMatter = yaml.parse(match[1]) // Pass front matter to the template
196
- } catch (err) {
197
- console.log(`${pstyle.redBright + pstyle.bold}[error]${pstyle.reset}${pstyle.dim} Failed parsing front matter:${pstyle.reset} ${pstyle.italic + pstyle.underline}${fileName}${pstyle.reset}`)
198
- console.log(err)
199
- }
200
- }
201
-
202
- return frontMatter
203
- }
204
-
205
- getSingleCollectionData(collectionName) {
206
- const collectionData = []
207
- globSync(path.join(process.cwd(), this.config.markup.in, collectionName, '**/*.+(html|njk|md)'), { ignore: ['**/index.+(html|njk|md)'] }).forEach((file) => {
208
- let frontMatter = {}
209
-
210
- try {
211
- const frontMatterResult = parseFrontMatter(file)
212
- frontMatter = frontMatterResult.frontMatter
213
- } catch (err) {
214
- console.log(`${pstyle.redBright + pstyle.bold}[error]${pstyle.reset}${pstyle.dim} Failed parsing front matter:${pstyle.reset} ${pstyle.italic + pstyle.underline}${file}${pstyle.reset}`)
215
- console.log(err)
216
- }
217
-
218
- if (frontMatter.published === false) return // Skip unpublished items
219
-
220
- if (!frontMatter.date) {
221
- frontMatter.date = fs.statSync(file).ctime.toISOString().slice(0, 16)
222
- }
223
- frontMatter.fileName = path.basename(file)
224
- frontMatter.filePath = path.relative(process.cwd(), file)
225
- frontMatter.collection = collectionName
226
- frontMatter.url = path.join(collectionName, path.basename(frontMatter.filePath))
227
-
228
- frontMatter.url = this.replaceOutExtensions(frontMatter.url)
229
-
230
- if (!frontMatter.title) {
231
- frontMatter.title = path.basename(frontMatter.filePath, path.extname(frontMatter.filePath))
232
- }
233
- collectionData.push(frontMatter)
234
- })
235
-
236
- return collectionData
237
- }
238
-
239
- collectionAutoDiscovery() {
240
- const indexFiles = globSync(path.join(process.cwd(), this.config.markup.in, '/**/index.+(html|njk|md)'))
241
-
242
- const collectionData = {}
243
-
244
- for (const indexFile of indexFiles) {
245
- let frontMatter = {}
246
-
247
- try {
248
- const frontMatterResult = parseFrontMatter(indexFile)
249
- frontMatter = frontMatterResult.frontMatter
250
- } catch (err) {
251
- console.log(`${pstyle.redBright + pstyle.bold}[error]${pstyle.reset}${pstyle.dim} Failed parsing front matter:${pstyle.reset} ${pstyle.italic + pstyle.underline}${indexFile}${pstyle.reset}`)
252
- console.log(err)
253
- }
254
-
255
- if (!frontMatter.collection) continue
256
-
257
- if (frontMatter.collection === true) {
258
- frontMatter.collection = path.basename(path.dirname(indexFile))
259
- }
260
-
261
- const collectionName = frontMatter.collection.trim()
262
-
263
- if (collectionName === '') continue
264
-
265
- frontMatter.name = collectionName
266
- const collection = this.buildCollectionObject(frontMatter)
267
- if (!collection) continue
268
- collectionData[collection.name] = collection
269
- }
270
-
271
- return collectionData
272
- }
273
-
274
- getCollectionDataBasedOnConfig(collectionConfig) {
275
- if (!collectionConfig) return {}
276
-
277
- const items = Array.isArray(collectionConfig)
278
- ? collectionConfig
279
- : [collectionConfig]
280
-
281
- const collectionData = {}
282
-
283
- for (let item of items) {
284
- if (typeof item === 'string') item = { name: item }
285
- if (!item || !item.name) continue
286
- const collection = this.buildCollectionObject(item)
287
- if (collection) collectionData[item.name] = collection
288
- }
289
-
290
- return collectionData
291
- }
292
-
293
- buildCollectionObject(collectionProtoObject) {
294
- const collection = {
295
- name: collectionProtoObject.name,
296
- items: this.getSingleCollectionData(collectionProtoObject.name)
297
- }
298
-
299
- if (collection.items.length === 0) return null
300
-
301
- if (collectionProtoObject.paginate && !isNaN(parseInt(collectionProtoObject.paginate))) {
302
- collection.paginate = parseInt(collectionProtoObject.paginate)
303
- }
304
-
305
- if (collectionProtoObject.sort) {
306
- collection.sort = collectionProtoObject.sort
307
- }
308
-
309
- if (typeof collection.sort === 'string') {
310
- collection.sort = { by: collection.sort }
311
- }
312
-
313
- if (!collection.sort) {
314
- collection.sort = { by: 'date' }
315
- }
316
-
317
- if (!collection.sort.by) {
318
- collection.sort.by = 'date'
319
- }
320
-
321
- if (collection.sort.by === 'date') {
322
- collection.sort.type = 'date'
323
- } else {
324
- collection.sort.type = 'alphabetical'
325
- }
326
-
327
- if (!collection.sort.order) {
328
- collection.sort.order = collection.sort.type === 'date' ? 'desc' : 'asc'
329
- }
330
-
331
- collection.items.sort((a, b) => {
332
- if (collection.sort.type === 'date') {
333
- if (collection.sort.order === 'asc') {
334
- return new Date(a[collection.sort.by]) - new Date(b[collection.sort.by])
335
- }
336
-
337
- return new Date(b[collection.sort.by]) - new Date(a[collection.sort.by])
338
- } else {
339
- if (collection.sort.order === 'asc') {
340
- return a[collection.sort.by] > b[collection.sort.by] ? 1 : -1
341
- }
342
-
343
- return a[collection.sort.by] < b[collection.sort.by] ? 1 : -1
344
- }
345
- })
346
-
347
- return collection
348
- }
349
-
350
- clearCollectionOutputDir(collectionName) {
351
- const collectionDirectoryPath = path.join(process.cwd(), this.config.markup.out, collectionName)
352
- deleteDirectory(collectionDirectoryPath) // Remove collection directory
353
- }
354
-
355
- getRelativePathPrefix(outputDir, fromDir) {
356
- let relativeDir = path.relative(process.cwd(), outputDir)
357
- const fromRelativeDir = fromDir ? path.relative(process.cwd(), fromDir) : ''
358
-
359
- if (fromRelativeDir && relativeDir.startsWith(fromRelativeDir)) {
360
- relativeDir = relativeDir.replace(fromRelativeDir, '')
361
- }
362
-
363
- return this.getUpDirPrefix(relativeDir)
364
- }
365
-
366
- getUpDirPrefix(relativeDir) {
367
- if (relativeDir.trim() === '') return ''
368
- if (relativeDir.startsWith(path.sep)) relativeDir = relativeDir.slice(1)
369
- if (relativeDir.endsWith(path.sep)) relativeDir = relativeDir.slice(0, -1)
370
- const relativePathParts = relativeDir.split(path.sep)
371
- let upDir = ''
372
- for (let i = 0; i < relativePathParts.length; i++) {
373
- upDir += `..${path.sep}`
374
- }
375
- return upDir
376
- }
133
+ markupDefaultExcludes.push('_*')
134
+ markupDefaultExcludes = [...new Set(markupDefaultExcludes)]
377
135
 
378
- getPageUrl(outputPath) {
379
- outputPath = this.replaceOutExtensions(outputPath)
380
- return /index\.[a-z]+$/.test(path.basename(outputPath)) ? path.relative(process.cwd(), path.dirname(outputPath)) : path.relative(process.cwd(), outputPath)
136
+ return `!(${markupDefaultExcludes.join('|')})/**/*.+(${this.engine.markupExtensions})`
381
137
  }
382
138
 
383
- replaceOutExtensions(outputPath) {
384
- switch (path.extname(outputPath)) {
385
- case '.md':
386
- outputPath = outputPath.replace(/\.md$/, '.html')
387
- break
388
- case '.njk':
389
- outputPath = outputPath.replace(/\.njk$/, '.html')
390
- break
391
- }
392
-
393
- return outputPath
394
- }
395
-
396
- compileEntry(templateName, additionalContext) {
139
+ async compileEntry(templateName, additionalContext) {
397
140
  const context = { page: {} }
398
141
  let pageUrl
399
142
 
@@ -409,184 +152,205 @@ export default class Markups {
409
152
  const frontMatterResult = parseFrontMatter(templateName)
410
153
  context.page = frontMatterResult.frontMatter
411
154
  } catch (err) {
412
- console.log(`${pstyle.redBright + pstyle.bold}[error]${pstyle.reset}${pstyle.dim} Failed parsing front matter:${pstyle.reset} ${pstyle.italic + pstyle.underline}${templateName}${pstyle.reset}`)
413
- console.log(err)
155
+ log({ tag: 'error', text: 'Failed parsing front matter:', link: templateName })
156
+ console.error(err)
414
157
  }
415
158
 
416
159
  if (pageUrl) context.page.url = pageUrl
417
160
 
418
- const env = this.nunjucksEnv
419
- return new Promise((resolve, reject) => {
420
- env.getTemplate(templateName).render(context, (error, result) => {
421
- if (!error) {
422
- resolve(result)
423
- } else {
424
- reject(error)
425
- }
426
- })
427
- })
428
- }
161
+ const frontMatter = context.page
429
162
 
430
- getCollectionIndexFile(collectionName) {
431
- const indexFiles = globSync(path.join(process.cwd(), this.config.markup.in, collectionName, 'index.+(html|njk|md)'))
432
- if (indexFiles.length === 0) return null
433
- return indexFiles[0]
434
- }
163
+ if (frontMatter && frontMatter.published === false) {
164
+ return { result: '', frontMatter, skipped: true }
165
+ }
435
166
 
436
- buildCollectionPaginationData(collectionData) {
437
- if (!collectionData) return
167
+ const result = await this.engine.render(templateName, context)
168
+ return { result, frontMatter }
169
+ }
438
170
 
439
- for (const collectionName of Object.keys(collectionData)) {
440
- const collection = collectionData[collectionName]
171
+ async compileDirectory(markupIn, collectionData, pageEntries) {
172
+ const markupStart = performance.now()
173
+ const markupFiles = [
174
+ ...globSync(path.join(markupIn, this.generateMarkupGlobPattern(this.includePaths))),
175
+ ...globSync(path.join(markupIn, `*.+(${this.engine.markupExtensions})`))
176
+ ]
177
+ const compilePromises = []
178
+ const indexableExtensions = this.engine.indexableExtensions
441
179
 
442
- if (!collection.paginate) continue
180
+ for (const file of markupFiles) {
181
+ const relativePath = path.relative(markupIn, file)
182
+ const relativePathParts = relativePath.split(path.sep)
443
183
 
444
- collection.pages = []
445
- let pageItems = []
446
- for (const item of collection.items) {
447
- if (pageItems.length === collection.paginate) {
448
- collection.pages.push(pageItems)
449
- pageItems = []
450
- }
451
- pageItems.push(item)
184
+ if (relativePathParts.length > 1 &&
185
+ collectionData[relativePathParts[0]] &&
186
+ relativePathParts[1].startsWith('index.') && indexableExtensions.has(path.extname(relativePathParts[1])) &&
187
+ collectionData[relativePathParts[0]].items.length > 0) {
188
+ continue
452
189
  }
453
- collection.pages.push(pageItems)
454
-
455
- collection.totalPages = collection.pages.length
456
- }
457
- }
458
190
 
459
- generateCollectionPaginationPages(collectionData, compilePromises) {
460
- if (!collectionData) return
191
+ let markupOut = path.join(process.cwd(), this.markupOut, relativePath)
192
+ const fromPath = path.join(process.cwd(), this.markupOut)
193
+ const markupOutDir = path.dirname(markupOut)
461
194
 
462
- for (const collectionName of Object.keys(collectionData)) {
463
- const collection = collectionData[collectionName]
464
- const file = this.getCollectionIndexFile(collectionName)
195
+ mkDir(markupOutDir)
465
196
 
466
- if (!collection.totalPages || collection.totalPages === 0) {
467
- collection.totalPages = 1
468
- collection.pages = [collection.items]
197
+ const fileContext = {
198
+ ...collectionData,
199
+ relativePathPrefix: getRelativePathPrefix(markupOutDir, fromPath),
200
+ _url: getPageUrl(markupOut)
469
201
  }
470
202
 
471
- for (let i = 0; i < collection.totalPages; i++) {
472
- collection.pageItems = collection.pages[i]
473
- collection.pageNumber = i + 1
474
- collection.pageUrl = collection.pageNumber === 1 ? collection.name : `${collection.name}/${collection.pageNumber}`
475
- collection.nextPage = collection.pageNumber === collection.totalPages ? null : collection.pageNumber + 1
476
- collection.nextPageUrl = collection.pageNumber === collection.totalPages ? null : `${collection.name}/${collection.pageNumber + 1}`
477
- collection.prevPage = collection.pageNumber === 1 ? null : collection.pageNumber - 1
478
- collection.prevPageUrl = collection.pageNumber === 1 ? null : `${collection.name}/${collection.pageNumber - 1}`
479
- if (collection.prevPage === 1) {
480
- collection.prevPageUrl = collection.name
203
+ const shouldIndex = pageEntries && indexableExtensions.has(path.extname(file))
204
+ const fileCollection = relativePathParts.length > 1 && collectionData[relativePathParts[0]]
205
+ ? relativePathParts[0]
206
+ : null
207
+
208
+ const compilePromise = this.compileEntry(file, fileContext).then(({ result, frontMatter, skipped }) => {
209
+ if (skipped) {
210
+ const outFile = replaceOutExtensions(markupOut)
211
+ if (fs.existsSync(outFile)) {
212
+ fs.unlinkSync(outFile)
213
+ log({ tag: this.logTag, text: 'Removed unpublished:', link: path.relative(process.cwd(), outFile) })
214
+ }
215
+ return
481
216
  }
217
+ markupOut = replaceOutExtensions(markupOut)
218
+ fs.writeFileSync(markupOut, result)
482
219
 
483
- const markupOut = path.join(process.cwd(), this.config.markup.out, collection.pageUrl, 'index.html')
484
- const fromPath = path.join(process.cwd(), this.config.markup.out)
485
- const markupOutDir = path.dirname(markupOut)
220
+ if (shouldIndex && frontMatter.published !== false) {
221
+ if (!frontMatter.title) frontMatter.title = path.basename(file, path.extname(file))
222
+ if (fileCollection && !frontMatter.collection) frontMatter.collection = fileCollection
486
223
 
487
- mkDir(markupOutDir)
224
+ pageEntries.push({
225
+ ...frontMatter,
226
+ url: getPageUrlRelativeToOutput(markupOut, this.markupOut),
227
+ content: result,
228
+ isIndex: false
229
+ })
230
+ }
231
+ })
232
+ compilePromises.push(compilePromise)
233
+ }
488
234
 
489
- collectionData.relativePathPrefix = this.getRelativePathPrefix(markupOutDir, fromPath)
490
- collectionData._url = this.getPageUrl(markupOut)
235
+ try {
236
+ await Promise.all(compilePromises)
237
+ const markupEnd = performance.now()
238
+ log({ tag: this.logTag, text: `Compiled: ${markupFiles.length} file${markupFiles.length > 1 ? 's' : ''} into`, link: this.markupOut, time: buildTime(markupStart, markupEnd) })
239
+ } catch (err) {
240
+ log({ tag: this.logTag, error: true, text: 'Failed compiling' })
241
+ console.error(err)
242
+ throw err
243
+ } finally {
244
+ clearFrontMatterCache()
245
+ }
246
+ }
247
+
248
+ async compileSingleFile(markupIn, collectionData, pageEntries) {
249
+ const markupStart = performance.now()
250
+ let markupOut = path.join(process.cwd(), this.markupOut)
251
+ const markupOutDir = path.dirname(markupOut)
252
+ const indexableExtensions = this.engine.indexableExtensions
253
+ mkDir(markupOutDir)
254
+
255
+ const fileContext = {
256
+ ...collectionData,
257
+ relativePathPrefix: getRelativePathPrefix(markupOutDir),
258
+ _url: getPageUrl(markupOut)
259
+ }
491
260
 
492
- if (!file) {
493
- continue
261
+ const shouldIndex = pageEntries && indexableExtensions.has(path.extname(markupIn))
262
+
263
+ try {
264
+ const { result, frontMatter, skipped } = await this.compileEntry(markupIn, fileContext)
265
+ markupOut = replaceOutExtensions(markupOut)
266
+
267
+ if (skipped) {
268
+ if (fs.existsSync(markupOut)) {
269
+ fs.unlinkSync(markupOut)
270
+ log({ tag: this.logTag, text: 'Removed unpublished:', link: path.relative(process.cwd(), markupOut) })
494
271
  }
272
+ return
273
+ }
274
+
275
+ fs.writeFileSync(markupOut, result)
495
276
 
496
- const compilePromise = this.compileEntry(file, collectionData).then((result) => {
497
- fs.writeFileSync(markupOut, result)
277
+ if (shouldIndex && frontMatter.published !== false) {
278
+ if (!frontMatter.title) frontMatter.title = path.basename(markupIn, path.extname(markupIn))
279
+ pageEntries.push({
280
+ ...frontMatter,
281
+ url: getPageUrlRelativeToOutput(markupOut, this.markupOut),
282
+ content: result,
283
+ isIndex: false
498
284
  })
499
- compilePromises.push(compilePromise)
500
285
  }
286
+
287
+ const markupEnd = performance.now()
288
+ log({ tag: this.logTag, text: 'Compiled:', link: path.relative(process.cwd(), path.join(process.cwd(), this.markupOut, path.basename(markupIn))), time: buildTime(markupStart, markupEnd) })
289
+ } catch (err) {
290
+ log({ tag: this.logTag, error: true, text: 'Failed compiling:', link: path.relative(process.cwd(), path.join(process.cwd(), this.markupOut, path.basename(markupIn))) })
291
+ console.error(err)
292
+ throw err
293
+ } finally {
294
+ clearFrontMatterCache()
501
295
  }
502
296
  }
503
297
 
504
- compile() {
505
- if (!this.config.markup || !this.config.markup.in) return
298
+ async compile() {
299
+ const moduleConfig = this.config.markup
300
+ if (!moduleConfig || !moduleConfig.in) return
506
301
 
507
- if (this.config.ssgData) {
508
- for (const [name, html] of Object.entries(this.config.ssgData)) {
509
- this.nunjucksEnv.addGlobal(name, html)
302
+ if (this.config.reactorData) {
303
+ for (const [name, html] of Object.entries(this.config.reactorData)) {
304
+ this.engine.setGlobal(name, html)
510
305
  }
511
306
  }
512
307
 
513
- const markupIn = path.join(process.cwd(), this.config.markup.in)
514
- const compilePromises = []
308
+ const markupIn = path.join(process.cwd(), this.markupIn)
515
309
 
516
310
  if (!pathExists(markupIn)) {
517
- console.log(`${pstyle.redBright + pstyle.bold}[error]${pstyle.reset} ${pstyle.dim}Markup path does not exist:${pstyle.reset} ${pstyle.italic + pstyle.underline}${markupIn}${pstyle.reset}`)
311
+ log({ tag: 'error', text: 'Markup path does not exist:', link: markupIn })
518
312
  return
519
313
  }
520
314
 
521
315
  const collectionData = {
522
- ...this.collectionAutoDiscovery(),
523
- ...this.getCollectionDataBasedOnConfig(this.config.markup.options.collections)
316
+ ...collectionAutoDiscovery(this.markupIn),
317
+ ...getCollectionDataBasedOnConfig(this.markupIn, this.collectionsConfig)
524
318
  }
525
319
 
526
- // Create collection pages
527
- this.buildCollectionPaginationData(collectionData)
528
- this.generateCollectionPaginationPages(collectionData, compilePromises)
320
+ const shouldIndex = this.searchIndexConfig || this.sitemapConfig
321
+ const pageEntries = shouldIndex ? [] : null
529
322
 
530
- if (pathIsDirectory(markupIn)) {
531
- const markupFiles = [...globSync(path.join(markupIn, this.generateMarkupGlobPattern(this.includePaths))), ...globSync(path.join(markupIn, '*.+(html|xml|rss|atom|json|njk|md)'))]
323
+ buildCollectionPaginationData(collectionData)
324
+ const collectionPromises = generateCollectionPaginationPages(collectionData, this.markupIn, this.markupOut, this.compileEntry.bind(this))
532
325
 
533
- markupFiles.forEach((file) => {
534
- const relativePath = path.relative(markupIn, file)
535
- const relativePathParts = relativePath.split(path.sep)
326
+ await Promise.all(collectionPromises)
536
327
 
537
- // Collection pages already generated, skip them
538
- if (relativePathParts.length > 1 &&
539
- collectionData[relativePathParts[0]] &&
540
- /^index\.(html|njk|md)$/.test(relativePathParts[1]) &&
541
- collectionData[relativePathParts[0]].items.length > 0) {
542
- return
543
- }
328
+ if (pageEntries) {
329
+ for (const collectionName of Object.keys(collectionData)) {
330
+ const collection = collectionData[collectionName]
331
+ const totalPages = collection.totalPages || 1
544
332
 
545
- let markupOut = path.join(process.cwd(), this.config.markup.out, relativePath)
546
- const fromPath = path.join(process.cwd(), this.config.markup.out)
547
- const markupOutDir = path.dirname(markupOut)
548
-
549
- mkDir(markupOutDir)
550
-
551
- collectionData.relativePathPrefix = this.getRelativePathPrefix(markupOutDir, fromPath)
552
- collectionData._url = this.getPageUrl(markupOut)
553
-
554
- const compilePromise = this.compileEntry(file, collectionData).then((result) => {
555
- markupOut = this.replaceOutExtensions(markupOut)
556
- fs.writeFileSync(markupOut, result)
557
- })
558
- compilePromises.push(compilePromise)
559
- })
333
+ for (let i = 0; i < totalPages; i++) {
334
+ const pageUrl = i === 0 ? collectionName : `${collectionName}/${i + 1}`
335
+ pageEntries.push({
336
+ url: pageUrl,
337
+ title: collectionName,
338
+ isIndex: true
339
+ })
340
+ }
341
+ }
342
+ }
560
343
 
561
- return new Promise((resolve, reject) => {
562
- Promise.all(compilePromises).then(() => {
563
- console.log(`${pstyle.cyanBright + pstyle.bold}[markup]${pstyle.reset} ${pstyle.dim}Compiled: ${pstyle.reset}${markupFiles.length} file${markupFiles.length > 1 ? 's' : ''} into ${pstyle.italic + pstyle.underline}${this.config.markup.out}${pstyle.reset}`)
564
- clearFrontMatterCache()
565
- resolve()
566
- }).catch((err) => {
567
- console.log(`${pstyle.cyanBright + pstyle.bold}[markup]${pstyle.reset} ${pstyle.redBright + pstyle.bold}[error]${pstyle.reset} ${pstyle.dim}Failed compiling${pstyle.reset + pstyle.bell}`)
568
- console.log(err)
569
- clearFrontMatterCache()
570
- reject(err)
571
- })
572
- })
344
+ if (pathIsDirectory(markupIn)) {
345
+ await this.compileDirectory(markupIn, collectionData, pageEntries)
573
346
  } else {
574
- let markupOut = path.join(process.cwd(), this.config.markup.out)
575
- const markupOutDir = path.dirname(markupOut)
576
- mkDir(markupOutDir)
577
-
578
- collectionData.relativePathPrefix = this.getRelativePathPrefix(markupOutDir)
579
- collectionData._url = this.getPageUrl(markupOut)
347
+ await this.compileSingleFile(markupIn, collectionData, pageEntries)
348
+ }
580
349
 
581
- return this.compileEntry(markupIn, collectionData).then((result) => {
582
- markupOut = this.replaceOutExtensions(markupOut)
583
- fs.writeFileSync(markupOut, result)
584
- console.log(`${pstyle.cyanBright + pstyle.bold}[markup]${pstyle.reset} ${pstyle.dim}Compiled:${pstyle.reset} ${pstyle.italic + pstyle.underline}${path.relative(process.cwd(), path.join(process.cwd(), this.config.markup.out, path.basename(markupIn)))}${pstyle.reset}`)
585
- clearFrontMatterCache()
586
- }).catch((err) => {
587
- console.log(`${pstyle.cyanBright + pstyle.bold}[markup]${pstyle.reset} ${pstyle.redBright + pstyle.bold}[error]${pstyle.reset} ${pstyle.dim}Failed compiling:${pstyle.reset} ${pstyle.italic + pstyle.underline}${path.relative(process.cwd(), path.join(process.cwd(), this.config.markup.out, path.basename(markupIn)))}${pstyle.reset + pstyle.bell}`)
588
- console.log(err)
589
- clearFrontMatterCache()
350
+ if (shouldIndex && pageEntries) {
351
+ generateIndexFiles(pageEntries, this.markupOut, this.siteData.url, {
352
+ searchIndex: this.searchIndexConfig,
353
+ sitemap: this.sitemapConfig
590
354
  })
591
355
  }
592
356
  }