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