src/commands/init/generateTemplate/index.js
import path from 'path';
import Metalsmith from 'metalsmith';
import Handlebars from 'handlebars';
import async from 'async';
import log from '../../../log/default/small';
import getOptions from './options';
import ask from './ask';
import collect from './collect';
import filter from './filter';
// Register default 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);
});
export default function generateTemplate(name, src, dest, done) {
const opts = getOptions(name, src);
const metalsmith = new Metalsmith(path.join(src, 'template'));
metalsmith.metadata({
destDirName: path.relative(process.cwd(), dest),
inPlace: dest === process.cwd(),
});
if (opts.helpers) {
Object.keys(opts.helpers).forEach((key) => {
Handlebars.registerHelper(key, opts.helpers[key]);
});
}
metalsmith
.use(askQuestions(opts.prompts))
.use(collectData(opts.data))
.use(filterFiles(opts.filters))
.use(renderTemplateFiles)
.clean(false)
.source('.') // start from template root instead of `./src` which is Metalsmith's default for `source`
.destination(dest)
.build((error) => {
if (error) {
log.error('An error happened when creating the template', error);
}
log.success('Project created');
done();
logMessage(opts.completionMessage, metalsmith.metadata());
});
}
function askQuestions(prompts) {
return (files, metalsmith, done) => ask(prompts, metalsmith.metadata(), done);
}
function collectData(data) {
return (files, metalsmith, done) => collect(data, metalsmith.metadata(), done);
}
function filterFiles(filters) {
return (files, metalsmith, done) => filter(files, filters, metalsmith.metadata(), done);
}
function render(template, data, callback) {
let rendered;
try {
rendered = Handlebars.compile(template, { noEscape: true })(data);
} catch (e) {
return callback(e);
}
return callback(null, rendered);
}
function renderTemplateFiles(files, metalsmith, done) {
const filenames = Object.keys(files);
const metalsmithMetadata = metalsmith.metadata();
async.each(filenames, (filename, next) => { // eslint-disable-line
// Rename files that that might use mustaches
render(filename, metalsmithMetadata, (renameError, newFilename) => {
if (renameError) return next(renameError);
// If we got a new filename we replace the old file with the new
if (newFilename !== filename) {
files[newFilename] = files[filename]; // eslint-disable-line
delete files[filename]; // eslint-disable-line
}
const str = files[newFilename].contents.toString();
// do not attempt to render files that do not have mustaches
if (!/{{([^{}]+)}}/g.test(str)) {
return next();
}
render(str, metalsmithMetadata, (contentError, res) => {
if (contentError) return next(contentError);
files[newFilename].contents = new Buffer(res); // eslint-disable-line
return next();
});
});
}, done);
}
function logMessage(message, data) {
if (!message) {
return;
}
render(message, data, (err, res) => {
if (err) {
log.warn('\n Error when rendering template complete message: ' + err.message.trim());
} else {
console.log('\n' + res.split(/\r?\n/g).map((line) => ' ' + line).join('\n'));
}
});
}