/* * grunt-execute * https://github.com/Bartvds/grunt-execute * * Copyright (c) 2013 Bart van der Schoor * Licensed under the MIT license. */ module.exports = function (grunt) { 'use strict'; var path = require('path'); function executeModuleFunction(func, context, callback) { // private var isFinalised = false; var isAsync = false; var funcDone = false; var calledDone = false; var finalise = function (err, msg) { if (isFinalised) { grunt.fail.warn('executeModuleFunction() detected async flow error'); return null; } isFinalised = true; if (typeof msg !== 'undefined') { grunt.log.writeln(msg); } process.nextTick(function () { callback(err); }); }; var async = function () { if (funcDone) { grunt.fail.warn('executeModuleFunction() call async() in main body'); return null; } if (isAsync) { grunt.fail.warn('executeModuleFunction() detected multiple calls to async()'); return null; } isAsync = true; return function (err, msg) { if (calledDone) { grunt.fail.warn('executeModuleFunction() detected multiple calls to the async callback'); return null; } calledDone = true; if (typeof msg !== 'undefined') { grunt.log.writeln(msg); } finalise(err, msg); }; }; var msg = func(grunt, context.options, async); funcDone = true; if (!isAsync && !calledDone) { finalise(null, msg); } else { if (typeof msg !== 'undefined') { grunt.log.writeln(msg); } } } function executeModule(func, context, callback) { grunt.log.writeln('-> '.cyan + 'module call ' + (func.name ? func.cyan : '')); var start = Date.now(); executeModuleFunction(func, context, function (err) { if (err) { grunt.fail.warn('-> '.cyan + 'error '.red + '(' + (Date.now() - start) + 'ms)'); } else { context.counter += 1; grunt.log.writeln('-> '.cyan + 'completed ' + '(' + (Date.now() - start) + 'ms)'); } callback(err); }); } function executeSrcAsModule(src, context, callback) { grunt.log.writeln('-> '.cyan + 'executing module ' + src.cyan); var mod; try { mod = require(src); } catch (err) { callback(new Error('not a function')); return; } if (!grunt.util._.isFunction(mod)) { grunt.fail.warn('-> '.cyan + 'error '.red + src.cyan + ' should export a Function'); callback(new Error('not a function')); return; } var start = Date.now(); executeModuleFunction(mod, context, function (err) { if (err) { grunt.fail.warn('-> '.cyan + 'error '.red + src.cyan + ' (' + (Date.now() - start) + 'ms)'); } else { context.counter += 1; grunt.log.writeln('-> '.cyan + 'completed ' + src.cyan + ' (' + (Date.now() - start) + 'ms)'); } callback(err); }); } function executeSrcAsChild(src, context, callback) { grunt.log.writeln('-> '.cyan + 'executing ' + src.cyan); var start = Date.now(); var args = context.options.args ? [src].concat(context.options.args) : [src]; if (context.options.nodeargs) { args = context.options.nodeargs.concat(args); } //use spawn so we don't have to depend on process.exit(); var child = grunt.util.spawn( { cmd: 'node', args: args, opts: { cwd: (context.options.cwd !== null) ? context.options.cwd : path.dirname(src) } }, function (error, result, code) { if (error) { grunt.fail.warn('-> '.cyan + 'error '.red + ('' + code).red + ' ' + src.cyan + ' (' + (Date.now() - start) + 'ms)'); callback(error); } else if (code !== 0) { grunt.fail.warn('-> '.cyan + 'exitcode '.red + ('' + code).red + ' ' + src.cyan + ' (' + (Date.now() - start) + 'ms)'); callback(new Error('bad exit code ' + code), code); } else { context.counter += 1; grunt.log.writeln('-> '.cyan + 'completed ' + src.cyan + ' (' + (Date.now() - start) + 'ms)'); callback(); } } ); child.stdout.on('data', function (data) { grunt.log.write(data); }); child.stderr.on('data', function (data) { grunt.log.write(('' + data).red); }); } function pluralise(count, str) { return count + ' ' + str + (count === 1 ? '' : 's'); } grunt.registerMultiTask('execute', 'execute code in node', function () { var options = this.options({ cwd: '.' }); //var self = this; var done = this.async(); var context = { timer: Date.now(), calls: 0, files: 0, counter: 0, options: options }; var checkModuleFunc = function (func, callback) { if (func) { context.calls++; executeModule(func, context, callback); } else { callback(); } }; var self = this; grunt.util.async.series( [ function (callback) { checkModuleFunc(options.before, callback); }, function (callback) { checkModuleFunc(self.data.before, callback); }, function (callback) { checkModuleFunc(self.data.call, callback); }, function (callback) { grunt.util.async.forEachSeries(self.filesSrc, function (src, callback) { context.files++; src = path.resolve(src); if (!src) { grunt.fail.warn('undefined src parameter'); return false; } if (!grunt.file.exists(src)) { grunt.fail.warn('file does not exist ' + src); return false; } if (options.module) { executeSrcAsModule(src, context, callback); } else { executeSrcAsChild(src, context, callback); } }, callback, self); }, function (callback) { checkModuleFunc(self.data.after, callback); }, function (callback) { checkModuleFunc(options.after, callback); } ], function (err) { grunt.log.writeln(''); if (err) { grunt.log.writeln(err); done(false); } else { grunt.log.ok('' + pluralise(context.files, 'file') + ' and ' + pluralise(context.calls, 'call') + ' executed (' + (Date.now() - context.timer) + 'ms)\n'); done(); } } ); }); };