spectaql
Version:
A powerful library for autogenerating static GraphQL API documentation
292 lines (246 loc) • 8.6 kB
JavaScript
/*
* grunt-compile-handlebars
* https://github.com/patrickkettner/grunt-compile-handlebars
*
* Copyright (c) 2014 Patrick Kettner, contributors
* Licensed under the MIT license.
*/
;
module.exports = function(grunt) {
var _merge = require('lodash/merge');
var _toArray = require('lodash/toArray');
var JSON5 = require('json5');
var path = require('path');
var handlebarsPath;
var handlebars;
var usedPartials = [];
var usedHelpers = [];
// Normalizes the input so that it is always an array for the forEach loop
var getConfig = function(config) {
if (!config) {
return [];
}
var files = grunt.file.expand(config);
if (files.length) {
return files;
}
return [config];
};
// Gets the final representation of the input, whether it be object, or string
var parseData = function(data, dontParse) {
// grunt.file chokes on objects, so we check for it immiedietly
if (typeof data === 'object') {
return data;
}
// `data` isn't an object, so its probably a file
try {
// JSON5 allows us to parse single-quote JSON (which is tehcnically invalid, and thereby chokes JSON.parse)
data = grunt.file.read(data);
if (!dontParse) {
data = JSON5.parse(data);
}
}
catch (e) {}
return data;
};
// Checks if the input is a glob and if so, returns the unglobbed version of the filename
var isGlob = function(filename) {
if (!filename || typeof filename === 'object') {
return;
}
var match = filename.match(/[^\*]*/);
if (match[0] !== filename) {
return match.pop();
}
};
// Figures out the name of the file before any globs are used, so the globbed outputs can be generated
var getBasename = function(filename, template, outputInInput) {
var basename;
var glob;
template = Array.isArray(template) ? filename : template;
glob = isGlob(template);
if (outputInInput) {
basename = _toArray(filename.split('.').pop()) ;
} else if (glob) {
basename = filename.slice(glob.length, filename.length).split('.');
basename.pop();
} else {
basename = filename.split('/').pop().split('.');
basename.pop();
}
return basename.join('.');
};
var mergeJson = function(source, globals) {
var json = {}
var fragment;
globals.forEach(function(global) {
if (grunt.util.kindOf(global) === 'object') {
fragment = global;
} else {
if (!grunt.file.exists(global)) {
return grunt.log.error('JSON file ' + global + ' not found.');
} else {
try {
fragment = grunt.file.readJSON(global);
}
catch (e) {
return grunt.fail.warn(e);
}
}
}
_merge(json, fragment);
});
if (typeof source === 'object') {
_merge(json, source);
}
return json;
};
var shouldRegisterFullPaths = function(itShould, type) {
if (itShould && typeof itShould === 'string') {
itShould = itShould.toLowerCase().indexOf(type) === 0;
}
return itShould;
};
var getTemplateData = function(templateData, filepath, index) {
var data;
if (templateData === undefined) {
return {};
}
if (Array.isArray(templateData)) {
data = templateData[index];
if (data) {
return data;
}
grunt.log.error('You need to assign the same number of data files as source files when using array notation.');
return;
}
if (typeof templateData === 'object') {
return templateData;
}
if (grunt.file.exists(templateData)) {
return templateData;
}
if (isGlob(templateData) !== undefined) {
data = filepath.replace(path.extname(filepath), path.extname(templateData));
if (data) {
return data;
}
return grunt.log.error('No matching data file for ' + filepath + '.');
}
return templateData;
};
var getDest = function(destConfig, index) {
var dest;
if (Array.isArray(destConfig)) {
dest = destConfig[index];
if (dest) {
return dest;
}
grunt.log.error('You need to assign the same number of destination files as source files when using array notation.');
return;
}
if (isGlob(destConfig) !== undefined) {
dest = grunt.file.expand(destConfig)[index];
if (dest) {
return dest;
}
grunt.log.error('You need to assign the same number of destination files as source files when using glob notation.');
return;
}
return destConfig;
};
grunt.registerMultiTask('compile-handlebars', 'Compile Handlebars templates ', function() {
var fs = require('fs');
var path = require('path');
var files = this.files;
var config = this.data;
var helpers = getConfig(config.helpers);
var partials = getConfig(config.partials);
var done = this.async();
handlebarsPath = config.handlebars ? path.resolve(config.handlebars) : 'handlebars';
// Create a new instance so that we don't clobber our old defs when tasks are re-run
// in the same process lifetime
handlebars = require(handlebarsPath).create();
helpers.forEach(function(helper) {
var fullPath = helper.replace(/\.[^/.]+$/, '');
var name = shouldRegisterFullPaths(config.registerFullPath, 'helpers') ?
// full path, minus extention
fullPath :
// just the file's name
getBasename(helper, config.helpers);
if (handlebars.helpers[name] && usedHelpers.indexOf(fullPath) === -1) {
grunt.log.error(name + ' is already registered, clobbering with the new value. Consider setting `registerFullPath` to true');
} else {
usedHelpers.push(fullPath);
}
handlebars.registerHelper(name, require(fs.realpathSync(helper)));
});
partials.forEach(function(partial) {
var fullPath = partial.replace(/\.[^/.]+$/, '');
var name = shouldRegisterFullPaths(config.registerFullPath, 'partials') ?
// full path, minus extention
fullPath :
// just the file's name
getBasename(partial, config.partials);
if (handlebars.partials[name] && usedPartials.indexOf(fullPath) === -1) {
grunt.log.error(name + ' is already registered, clobbering with the new value. Consider setting `registerFullPath` to true');
} else {
usedPartials.push(fullPath);
}
handlebars.registerPartial(name, fs.readFileSync(fs.realpathSync(partial), 'utf8'));
});
var compile = function(file, filepath, index) {
var dest = file.dest || '';
var template = filepath;
var compiledTemplate = handlebars.compile(parseData(template, true));
var templateData = getTemplateData(config.templateData, filepath, index);
var outputPath = getDest(dest, index);
var appendToFile = (outputPath === file.orig.dest && grunt.file.exists(outputPath));
var operation = appendToFile ? 'appendFileSync' : 'writeFileSync';
var html = '';
var json;
if (config.preHTML) {
html += parseData(config.preHTML);
}
json = mergeJson(parseData(templateData), config.globals || []);
html += compiledTemplate(json);
if (config.postHTML) {
html += parseData(config.postHTML);
}
// shamefully copied straight out of grunt's file command, to allow for appending
// http://git.io/ZYGLEw
grunt.file.mkdir(path.dirname(outputPath));
try {
fs[operation](outputPath, html);
grunt.verbose.ok();
return true;
} catch (e) {
grunt.verbose.error();
throw grunt.util.error('Unable to write "' + outputPath + '" file (Error code: ' + e.code + ').', e);
}
};
files.forEach(function(file) {
if (Array.isArray(file.dest) && file.dest.length > file.src.length) {
if (file.src.length > 1) {
grunt.log.error('You may only have one source file when there are more destination files than source files.');
return;
} else {
file.dest.forEach(function(destpath, index) {
compile(file, file.src[0], index);
});
}
} else {
if (file.src.length > 0) {
file.src.forEach(function(filepath, index) {
compile(file, filepath, index);
});
} else {
file.orig.src.forEach(function(template, index) {
compile(file, template, index);
});
}
}
});
process.nextTick(done);
});
};