237 lines
6.0 KiB
JavaScript
237 lines
6.0 KiB
JavaScript
/**
|
|
* grunt-includes
|
|
* https://github.com/vanetix/grunt-includes
|
|
*
|
|
* Copyright (c) 2013 Matt McFarland
|
|
* Licensed under the MIT license.
|
|
*/
|
|
|
|
module.exports = function(grunt) {
|
|
|
|
/**
|
|
* Dependencies
|
|
*/
|
|
|
|
var path = require('path');
|
|
|
|
// Regex for matching new lines
|
|
var newlineRegexp = /\r?\n/g;
|
|
|
|
// Regex for parsing includes
|
|
var defaultRegexp = /^(\s*)include\s+"(\S+)"\s*$/;
|
|
|
|
// Regexp to replace with the file inside a template
|
|
var defaultTemplateFileRegexp = /\{\{\s?file\s?\}\}/;
|
|
|
|
// Regexp for inerpolating the filename in the template
|
|
var templateFilenameRegexp = /\{\{\s?fileName\s?\}\}/;
|
|
|
|
/**
|
|
* Core `grunt-includes` task
|
|
* Iterates over all source files and calls `recurse(path)` on each
|
|
*/
|
|
|
|
grunt.registerMultiTask('includes', 'Include other files within files.', function() {
|
|
var banner;
|
|
|
|
// Default options
|
|
var opts = this.options({
|
|
debug: false,
|
|
banner: '',
|
|
silent: false,
|
|
duplicates: true,
|
|
includeRegexp: defaultRegexp,
|
|
includePath: '',
|
|
filenamePrefix: '',
|
|
filenameSuffix: '',
|
|
template: '',
|
|
templateFileRegexp: defaultTemplateFileRegexp
|
|
});
|
|
|
|
if(grunt.util.kindOf(opts.includeRegexp) === 'string') {
|
|
opts.includeRegexp = new RegExp(opts.includeRegexp);
|
|
}
|
|
|
|
if(grunt.util.kindOf(opts.templateFileRegexp) === 'string') {
|
|
opts.templateFileRegexp = new RegExp(opts.templateFileRegexp);
|
|
}
|
|
|
|
// Render banner
|
|
banner = grunt.template.process(opts.banner);
|
|
|
|
this.files.forEach(function(f) {
|
|
var src, cwd = f.cwd;
|
|
|
|
src = f.src.filter(function(p) {
|
|
if(cwd) {
|
|
p = path.join(f.cwd, p);
|
|
}
|
|
|
|
if(grunt.file.isFile(p)) {
|
|
return true;
|
|
} else {
|
|
grunt.fail.fatal('Source "' + p + '" is not a file');
|
|
return false;
|
|
}
|
|
});
|
|
|
|
if(src.length > 1 && isFilename(f.dest)) {
|
|
grunt.fail.fatal('Source file cannot be more than one when dest is a file.');
|
|
}
|
|
|
|
src.forEach(function(p) {
|
|
var fileName = f.flatten ? path.basename(p) : p;
|
|
var outFile = isFilename(f.dest) ? f.dest : path.join(f.dest, fileName);
|
|
|
|
if(cwd) {
|
|
p = path.join(cwd, p);
|
|
}
|
|
|
|
grunt.file.write(outFile, banner + recurse(p, opts));
|
|
|
|
if(!opts.silent) {
|
|
grunt.log.oklns('Saved ' + outFile);
|
|
}
|
|
});
|
|
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Checks if `p` is a filepath, being it has an extension.
|
|
*
|
|
* @param {String} p
|
|
* @return {Boolean}
|
|
*/
|
|
|
|
function isFilename(p) {
|
|
return !!path.extname(p);
|
|
}
|
|
|
|
/**
|
|
* Returns the comment style for file `p`
|
|
*
|
|
* @param {String} p
|
|
* @return {String}
|
|
*/
|
|
|
|
function commentStyle(p) {
|
|
var comments,
|
|
ext = path.extname(p).slice(1);
|
|
|
|
comments = {
|
|
js: "/* %s */",
|
|
css: "/* %s */",
|
|
html: "<!-- %s -->"
|
|
};
|
|
|
|
return comments[ext] || '/* %s */';
|
|
}
|
|
|
|
/**
|
|
* Returns the new line style for file `p`
|
|
*
|
|
* @param {String} p
|
|
* @return {String}
|
|
*/
|
|
|
|
function newlineStyle(p) {
|
|
var matches = grunt.file.read(p).match(newlineRegexp);
|
|
|
|
return (matches && matches[0]) || grunt.util.linefeed;
|
|
}
|
|
|
|
/**
|
|
* Helper for `includes` builds all includes for `p`
|
|
*
|
|
* @param {String} p
|
|
* @return {String}
|
|
*/
|
|
|
|
function recurse(p, opts, included, indents) {
|
|
var src, next, match, error, comment, content,
|
|
newline, compiled, indent, fileLocation,
|
|
currentTemplate;
|
|
|
|
if(!grunt.file.isFile(p)) {
|
|
grunt.fail.warn('Included file "' + p + '" not found.');
|
|
return 'Error including "' + p + '".';
|
|
}
|
|
|
|
indents = indents || '';
|
|
comment = commentStyle(p);
|
|
newline = newlineStyle(p);
|
|
included = included || [];
|
|
|
|
// If `opts.duplicates` is false and file has been included, error
|
|
if(!opts.duplicates && ~included.indexOf(p)) {
|
|
error = 'Duplicate include: ' + p + ' skipping.';
|
|
grunt.log.error(error);
|
|
|
|
if(opts.debug) {
|
|
return comment.replace(/%s/g, error);
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
// At this point the file is considered included
|
|
included.push(p);
|
|
|
|
// Split the file on newlines
|
|
src = grunt.file.read(p).split(newline);
|
|
|
|
// Loop through the file calling `recurse` if an include is found
|
|
compiled = src.map(function(line) {
|
|
match = line.match(opts.includeRegexp);
|
|
|
|
// If the line has an include statement, recurse
|
|
if(match) {
|
|
indent = match[1];
|
|
fileLocation = match[2];
|
|
|
|
if (!fileLocation) {
|
|
fileLocation = indent;
|
|
indent = '';
|
|
}
|
|
|
|
fileLocation = opts.filenamePrefix + fileLocation + opts.filenameSuffix;
|
|
next = path.join((opts.includePath || path.dirname(p)), fileLocation);
|
|
content = recurse(next, opts, included, indents + indent);
|
|
|
|
// Wrap file around in template if `opts.template` has '{{file}}' in it.
|
|
if(opts.template !== '' && opts.template.match(opts.templateFileRegexp)) {
|
|
currentTemplate = opts.template.split(newline).map(function(line) {
|
|
line = line.replace(templateFilenameRegexp, fileLocation);
|
|
|
|
if (line.match(opts.templateFileRegexp)) {
|
|
return line;
|
|
} else {
|
|
return indent + indents + line;
|
|
}
|
|
});
|
|
|
|
// Safe guard against $ replacements - this can probably be improved
|
|
content = content.replace(/\$/g, '$$$$');
|
|
content = currentTemplate.join(newline).replace(opts.templateFileRegexp, content);
|
|
}
|
|
|
|
// Safe guard against $ replacements, change $ to $$
|
|
content = content.replace(/\$/g, '$$$$');
|
|
line = line.replace(opts.includeRegexp, content);
|
|
|
|
// Include debug comments if `opts.debug`
|
|
if(opts.debug) {
|
|
line = comment.replace(/%s/g, 'Begin: ' + next) +
|
|
newline + line + comment.replace(/%s/g, 'End: ' + next);
|
|
}
|
|
}
|
|
|
|
// If there are indents and not a match, add them to the line
|
|
return line && indents && !match ? indents + line : line;
|
|
});
|
|
|
|
return compiled.join(newline);
|
|
}
|
|
};
|