@yuanhua/cli
Version:
@yuanhua/cli
173 lines (152 loc) • 4.77 kB
JavaScript
const chalk = require('chalk');
// https://www.helplib.com/GitHub/article_108070
const Metalsmith = require('metalsmith');
const Handlebars = require('handlebars');
const async = require('async');
const render = require('consolidate').handlebars.render;
const path = require('path');
const multimatch = require('multimatch');
const getOptions = require('./options');
const ask = require('./ask');
const filter = require('./filter');
const logger = require('./logger');
// register handlebars helper
Handlebars.registerHelper('if_eq', function(a, b, opts) {
return a === b ? opts.fn(this) : opts.inverse(this);
});
Handlebars.registerHelper('unless_eq', function(a, b, opts) {
return a === b ? opts.inverse(this) : opts.fn(this);
});
/**
* Generate a template given a `src` and `dest`.
*
* @param {String} name
* @param {String} src
* @param {String} dest
* @param {Function} done
*/
module.exports = function generate(name, src, dest, done) {
// 处理npm名和作者信息
const opts = getOptions(name, src);
// 1、 一个极其简单,易于使用的可以插入 static 站点发生器
const metalsmith = Metalsmith(path.join(src, 'template'));
const data = Object.assign(metalsmith.metadata(), {
destDirName: name,
inPlace: dest === process.cwd(),
noEscape: true,
});
opts.helpers &&
Object.keys(opts.helpers).map(key => {
Handlebars.registerHelper(key, opts.helpers[key]);
});
const helpers = { chalk, logger };
if (opts.metalsmith && typeof opts.metalsmith.before === 'function') {
opts.metalsmith.before(metalsmith, opts, helpers);
}
// 2、use
metalsmith
.use(askQuestions(opts.prompts) /* 将给定的plugin 函数添加到中间件堆栈*/)
.use(filterFiles(opts.filters)) // 将给定的plugin 函数添加到中间件堆栈
.use(renderTemplateFiles(opts.skipInterpolation)); // 将给定的plugin 函数添加到中间件堆栈
if (typeof opts.metalsmith === 'function') {
opts.metalsmith(metalsmith, opts, helpers);
} else if (opts.metalsmith && typeof opts.metalsmith.after === 'function') {
opts.metalsmith.after(metalsmith, opts, helpers);
}
metalsmith
.clean(false)
.source('.') // 模板文件 path // start from template root instead of `./src` which is Metalsmith's default for `source`
.destination(dest) // 最终编译好的文件存放位置
.build((err, files) => {
done(err);
if (typeof opts.complete === 'function') {
const helpers = { chalk, logger, files };
opts.complete(data, helpers);
} else {
logMessage(opts.completeMessage, data);
}
});
return data;
};
/**
* Create a middleware for asking questions.
*
* @param {Object} prompts
* @return {Function}
*/
function askQuestions(prompts) {
return (files, metalsmith, done) => {
ask(prompts, metalsmith.metadata(), done);
};
}
/**
* Create a middleware for filtering files.
*
* @param {Object} filters
* @return {Function}
*/
function filterFiles(filters) {
return (files, metalsmith, done) => {
filter(files, filters, metalsmith.metadata(), done);
};
}
/**
* Template in place plugin.
*
* @param {Object} files
* @param {Metalsmith} metalsmith
* @param {Function} done
*/
function renderTemplateFiles(skipInterpolation) {
skipInterpolation =
typeof skipInterpolation === 'string' ? [skipInterpolation] : skipInterpolation;
return (files, metalsmith, done) => {
const keys = Object.keys(files);
const metalsmithMetadata = metalsmith.metadata();
async.each(
keys,
(file, next) => {
// skipping files with skipInterpolation option
if (skipInterpolation && multimatch([file], skipInterpolation, { dot: true }).length) {
return next();
}
const str = files[file].contents.toString();
// do not attempt to render files that do not have mustaches
if (!/{{([^{}]+)}}/g.test(str)) {
return next();
}
render(str, metalsmithMetadata, (err, res) => {
if (err) {
err.message = `[${file}] ${err.message}`;
return next(err);
}
files[file].contents = new Buffer(res);
next();
});
},
done
);
};
}
/**
* Display template complete message.
*
* @param {String} message
* @param {Object} data
*/
function logMessage(message, data) {
if (!message) return;
render(message, data, (err, res) => {
if (err) {
console.error('\n Error when rendering template complete message: ' + err.message.trim());
} else {
console.log(
'\n' +
res
.split(/\r?\n/g)
.map(line => ' ' + line)
.join('\n')
);
}
});
}