makestatic-core
Version:
Generic file processing library
202 lines (166 loc) • 5.06 kB
JavaScript
const when = require('when')
const sequence = require('when/sequence')
const mm = require('micromatch')
class Phase {
constructor (opts = {}) {
this.conf = opts.lifecycle[this.name]
}
/**
* Abstract function to validate a lifecycle phase.
*/
validate (context = {}) {
return when.resolve(context)
}
/**
* Get a human display name for the phase.
*/
get name () {
return this.constructor.name.toLowerCase()
}
run (key, context, plugins) {
// const log = context.log
let output = []
// log.info(
// '[%s] plugin: %s, files: %s', key, plugins.length, context.files.length)
return when.map(context.files, (file) => {
if (!file || (file && !file.name)) {
throw new Error(`bad file name input`)
}
// iterate and execute plugins for the file
return sequence(when.map(plugins, (plugin) => {
// closure to get the plugin promise
return () => {
// run plugin test patterns, if there is no match exclude the file
if (plugin.test && !plugin.test.test(file.name)) {
return when.resolve(file)
// if the file is explicitly excluded using a pattern in the exclude
// array, ignore it
} else {
if (Array.isArray(plugin.exclude)) {
for (let i = 0; i < plugin.exclude.length; i++) {
if (plugin.exclude[i].test(file.name)) {
return when.resolve(file)
}
}
}
}
if (typeof plugin.instance.sources === 'function') {
// pass context to the plugin
// options can be accessed via the context
return plugin.instance.sources(
file, context, plugin.options)
}
return when.resolve(file)
}
}))
.then(() => {
return context
})
}, output)
}
hooks (key, context, plugins) {
plugins = plugins.map((plugin) => {
// closure to get the plugin promise
return () => {
if (typeof plugin.instance[key] === 'function') {
// pass context to the plugin
// options can be accessed via the context
return plugin.instance[key](context, plugin.options)
}
return when.resolve()
}
})
return sequence(plugins)
}
before (context, plugins) {
return this.hooks(this.before.name, context, plugins)
}
sources (context, plugins) {
return this.run(this.sources.name, context, plugins)
}
after (context, plugins) {
return this.hooks(this.after.name, context, plugins)
}
getPlugins (context) {
let defs = this.conf
let plugins = []
let fields = ['plugin', 'test', 'exclude', 'options']
if (!defs) {
return plugins
}
if (defs && !Array.isArray(defs)) {
defs = [defs]
}
plugins = defs.map((def) => {
// wrap function pointers
if (typeof def === 'function') {
def = {plugin: def}
}
if (!def.plugin || typeof def.plugin !== 'function') {
throw new Error(`plugin constructor function expected`)
}
def.exclude = def.exclude || def.plugin.exclude
def.test = def.test || def.plugin.test || /.*/
def.options = def.options || def.plugin.options || {}
// options inheritance shortcut
for (let k in def) {
if (!~fields.indexOf(k)) {
def.options[k] = def[k]
}
}
// explicit list of files to exclude from processing
if (Array.isArray(def.exclude)) {
def.exclude = def.exclude.map((ex) => {
if (!(ex instanceof RegExp) && typeof ex !== 'string') {
throw new Error(`exclude pattern must be regexp or string`)
}
let ptn = ex
if (typeof ptn === 'string') {
ptn = mm.makeRe(ptn)
}
return ptn
})
}
// pre-compile glob patterns for the plugin
if (typeof def.test === 'string') {
def.test = mm.makeRe(def.test)
}
def.instance =
new def.plugin(context, def.options) // eslint-disable-line
return def
})
return plugins
}
process (context, options, name) {
let plugins
let timer
let phase = name.toLowerCase()
if (context.options.profile) {
timer = context.log.time(`[${phase}]`)
}
try {
plugins = this.getPlugins(context)
} catch (e) {
return when.reject(e)
}
// console.log('[%s] %s', this.name, plugins.length);
// no plugins for this phase
if (!plugins.length) {
return when.resolve(context)
}
// 1. before hooks
// 2. file processing
// 3. after hooks
return when.resolve()
.then(() => this.before(context, plugins))
.then(() => this.sources(context, plugins))
.then(() => this.after(context, plugins))
.then(() => {
context.lifecycle.phases.push(phase)
if (timer) {
timer()
}
})
}
}
module.exports = Phase