config.requires = function() { var p = grunt.util.pluralize; var props = grunt.util.toArray(arguments).map(config.getPropString); var msg = 'Verifying propert' + p(props.length, 'y/ies') + ' ' + grunt.log.wordlist(props) + ' exist' + p(props.length, 's') + ' in config...'; grunt.verbose.write(msg); var failProps = config.data && props.filter(function(prop) { return config.get(prop) == null; }).map(function(prop) { return '"' + prop + '"'; }); if (config.data && failProps.length === 0) { grunt.verbose.ok(); return true; } else { grunt.verbose.or.write(msg); grunt.log.error().error('Unable to process task.'); if (!config.data) { throw grunt.util.error('Unable to load config.'); } else { throw grunt.util.error('Required config propert' + p(failProps.length, 'y/ies') + ' ' + failProps.join(', ') + ' missing.'); } } };
this.options = function() { var args = [{}].concat(grunt.util.toArray(arguments)).concat([ grunt.config([name, 'options']) ]); var options = grunt.util._.extend.apply(null, args); grunt.verbose.writeflags(options, 'Options'); return options; };
this.options = function() { var targetObj = grunt.config([name, target]); var args = [{}].concat(grunt.util.toArray(arguments)).concat([ grunt.config([name, 'options']), grunt.util.kindOf(targetObj) === 'object' ? targetObj.options : {} ]); var options = grunt.util._.extend.apply(null, args); grunt.verbose.writeflags(options, 'Options'); return options; };
task.registerTask(name, info, function(target) { // Guaranteed to always be the actual task name. var name = thisTask.name; // Arguments (sans target) as an array. this.args = grunt.util.toArray(arguments).slice(1); // If a target wasn't specified, run this task once for each target. if (!target || target === '*') { return task.runAllTargets(name, this.args); } else if (!isValidMultiTaskTarget(target)) { throw new Error('Invalid target "' + target + '" specified.'); } // Fail if any required config properties have been omitted. this.requiresConfig([name, target]); // Return an options object with the specified defaults overwritten by task- // and/or target-specific overrides, via the "options" property. this.options = function() { var targetObj = grunt.config([name, target]); var args = [{}].concat(grunt.util.toArray(arguments)).concat([ grunt.config([name, 'options']), grunt.util.kindOf(targetObj) === 'object' ? targetObj.options : {} ]); var options = grunt.util._.extend.apply(null, args); grunt.verbose.writeflags(options, 'Options'); return options; }; // Expose the current target. this.target = target; // Recreate flags object so that the target isn't set as a flag. this.flags = {}; this.args.forEach(function(arg) { this.flags[arg] = true; }, this); // Expose data on `this` (as well as task.current). this.data = grunt.config([name, target]); // Expose normalized files object. this.files = task.normalizeMultiTaskFiles(this.data, target); // Expose normalized, flattened, uniqued array of src files. Object.defineProperty(this, 'filesSrc', { enumerable: true, get: function() { return grunt.util._(this.files).chain().pluck('src').flatten().uniq().value(); }.bind(this) }); // Call original task function, passing in the target and any other args. return fn.apply(this, this.args); });
grunt.registerMultiTask('cacheBustKey', 'Bust static assets from the cache using content hashing', function() { var opts = this.options(DEFAULT_OPTIONS); if (opts.jsonOnly === true) { opts.jsonOutput = true; opts.queryString = false; } if (opts.jsonDir === undefined && typeof opts.baseDir === 'string' && opts.baseDir.length > 1) { opts.jsonDir = opts.baseDir; } var discoveryOpts = { cwd: path.resolve(opts.baseDir), filter: 'isFile' }; // Support object maps var assetArr = opts.assets; if (opts.jsonOnly && typeof assetArr === 'object' && !Array.isArray(assetArr)) { assetArr = grunt.util.toArray(assetArr); } // Generate an asset map var assetMap = grunt.file .expand(discoveryOpts, assetArr) .sort() .reverse() .reduce(hashFile, {}); grunt.verbose.write('Assets found:', assetMap); // Write out assetMap if (opts.jsonOutput === true) { grunt.file.write(path.resolve(opts.jsonDir, opts.jsonOutputFilename), JSON.stringify(assetMap)); } if (!opts.jsonOnly) { // Go through each source file and replace terms getFilesToBeRenamed(this.files).forEach(replaceInFile); } function replaceInFile(filepath) { var markup = grunt.file.read(filepath); _.each(assetMap, function(hashed, original) { markup = markup.split(original).join(hashed); }); grunt.file.write(filepath, markup); } function hashFile(obj, file) { var absPath = path.resolve(opts.baseDir, file); var hash = generateFileHash(grunt.file.read(absPath, { encoding: null })); var newFilename = addFileHash(file, hash, opts.separator); if (!opts.queryString) { if (opts.createCopies) { grunt.file.copy(absPath, path.resolve(opts.baseDir, newFilename)); } if (opts.deleteOriginals) { grunt.file.delete(absPath); } } // This is probably a horrific way to remap the files back to the keys, and I'm very sorry. if (opts.jsonOnly && typeof opts.assets === 'object' && !Array.isArray(opts.assets)) { for (var i in opts.assets) { if (!Object.prototype.hasOwnProperty.call(opts.assets, i)) { continue; } if (opts.assets[i] === file) { obj[i] = newFilename; } } } else { obj[file] = newFilename; } return obj; } function generateFileHash(data) { return opts.hash || crypto.createHash(opts.algorithm).update(data, opts.encoding).digest('hex').substring(0, opts.length); } function addFileHash(str, hash, separator) { if (opts.queryString) { return str + '?' + hash; } else { var parsed = url.parse(str); var ext = path.extname(parsed.pathname); return (parsed.hostname ? parsed.protocol + parsed.hostname : '') + parsed.pathname.replace(ext, '') + separator + hash + ext; } } function getFilesToBeRenamed(files) { var originalConfig = files[0].orig; return grunt.file .expand(originalConfig, originalConfig.src) .map(function (file) { grunt.log.ok('Busted:', file); return path.resolve((originalConfig.cwd ? originalConfig.cwd + path.sep : '') + file); }); } });