UNPKG

lianghaijie-cli

Version:

A simple CLI for scaffolding futu5_ipo projects.

179 lines (159 loc) 5.74 kB
const chalk = require('chalk') // 高亮终端输出信息 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') // 自定义工具 - 日志打印 // 注册 handlerbars 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) }) // 主逻辑 // 获取模板配置 --> // 初始化 Metalsmith --> // 添加一些变量至 Metalsmith --> // handlebars模板注册 helper --> // 配置对象中是否有 before 函数,有则执行 --> // 询问问题 --> // 过滤文件 --> // 渲染模板文件 --> // 配置对象中是否有 after 函数,有则执行 --> // 最后构建项目内容 --> // 构建完成,成功若配置对象中有 complete 函数则执行,否则打印配置对象中的 completeMessage 息,如果有错误,执行回调函数 done(err) /** * 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) { const opts = getOptions(name, src) // 获取模板配置对象 const metalsmith = Metalsmith(path.join(src, 'template')) // 初始化 metalsmith 对象 const data = Object.assign(metalsmith.metadata(), { destDirName: name, inPlace: dest === process.cwd(), noEscape: true }) // 注册模板配置对象中的 helpers opts.helpers && Object.keys(opts.helpers).map(key => { Handlebars.registerHelper(key, opts.helpers[key]) }) const helpers = { chalk, logger } // 执行模板配置对象的 before 函数 if (opts.metalsmith && typeof opts.metalsmith.before === 'function') { opts.metalsmith.before(metalsmith, opts, helpers) } metalsmith.use(askQuestions(opts.prompts)) // 根据模板配置对象询问问题 .use(filterFiles(opts.filters)) // 根据问题答案集合集合模版配置对象中的 filters 对象过滤文件 .use(renderTemplateFiles(opts.skipInterpolation)) //渲染模板文件 // 执行模板配置对象的 after 函数 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('.') // start from template root instead of `./src` which is Metalsmith's default for `source` .destination(dest) .build((err, files) => { done(err) // 执行模板配置对象的 complete 函数 if (typeof opts.complete === 'function') { const helpers = { chalk, logger, files } opts.complete(data, helpers) } else { logMessage(opts.completeMessage, data) } }) return data } /** * 询问问题 - 中间件 * * @param {Object} prompts * @return {Function} */ function askQuestions (prompts) { return (files, metalsmith, done) => { ask(prompts, metalsmith.metadata(), done) } } /** * 过滤文件 - 中间件 * * @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) => { // 跳过符合模板配置对象中 skipInterpolation 数组中文件 // 如:跳过某个文件夹下所有 .vue 文件 以 避免 '{{}}' 与 handlebars 冲突 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 // 跳过没有使用 handlbers 语法的文件,即文件不需要引擎渲染 if (!/{{([^{}]+)}}/g.test(str)) { return next() } render(str, metalsmithMetadata, (err, res) => { if (err) { err.message = `[${file}] ${err.message}` return next(err) } // 使用了 handlerbars 语法的文件内容替换成渲染值 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')) } }) }