/* * grunt * http://gruntjs.com/ * * Copyright (c) 2016 "Cowboy" Ben Alman * Licensed under the MIT license. * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT */ 'use strict'; // Nodejs libs. var spawn = require('child_process').spawn; var nodeUtil = require('util'); var path = require('path'); // The module to be exported. var util = module.exports = {}; util.namespace = require('getobject'); // External libs. util.hooker = require('hooker'); util.async = require('async'); // Dont pollute other lodash: https://github.com/gruntjs/grunt-legacy-util/issues/17 var _ = util._ = require('lodash').runInContext(); var which = require('which').sync; // Instead of process.exit. See https://github.com/cowboy/node-exit util.exit = require('exit'); // Mixin Underscore.string methods. _.str = require('underscore.string'); _.mixin(_.str.exports()); // Return a function that normalizes the given function either returning a // value or accepting a "done" callback that accepts a single value. util.callbackify = function(fn) { return function callbackable() { // Invoke original function, getting its result. var result = fn.apply(this, arguments); // If the same number or less arguments were specified than fn accepts, // assume the "done" callback was already handled. var length = arguments.length; if (length === fn.length) { return; } // Otherwise, if the last argument is a function, assume it is a "done" // callback and call it. var done = arguments[length - 1]; if (typeof done === 'function') { done(result); } }; }; // Create a new Error object, with an origError property that will be dumped // if grunt was run with the --debug=9 option. util.error = function(err, origError) { if (!nodeUtil.isError(err)) { err = new Error(err); } if (origError) { err.origError = origError; } return err; }; // The line feed char for the current system. util.linefeed = process.platform === 'win32' ? '\r\n' : '\n'; // Normalize linefeeds in a string. util.normalizelf = function(str) { return str.replace(/\r\n|\n/g, util.linefeed); }; // What "kind" is a value? // I really need to rework https://github.com/cowboy/javascript-getclass var kindsOf = {}; 'Number String Boolean Function RegExp Array Date Error'.split(' ').forEach(function(k) { kindsOf['[object ' + k + ']'] = k.toLowerCase(); }); util.kindOf = function(value) { // Null or undefined. if (value == null) { return String(value); } // Everything else. return kindsOf[kindsOf.toString.call(value)] || 'object'; }; // Coerce something to an Array. util.toArray = _.toArray; // Return the string `str` repeated `n` times. util.repeat = function(n, str) { return new Array(n + 1).join(str || ' '); }; // Given str of "a/b", If n is 1, return "a" otherwise "b". util.pluralize = function(n, str, separator) { var parts = str.split(separator || '/'); return n === 1 ? (parts[0] || '') : (parts[1] || ''); }; // Recurse through objects and arrays, executing fn for each non-object. util.recurse = function(value, fn, fnContinue) { function recurse(value, fn, fnContinue, state) { var error; if (state.objs.indexOf(value) !== -1) { error = new Error('Circular reference detected (' + state.path + ')'); error.path = state.path; throw error; } var obj, key; if (fnContinue && fnContinue(value) === false) { // Skip value if necessary. return value; } else if (util.kindOf(value) === 'array') { // If value is an array, recurse. return value.map(function(item, index) { return recurse(item, fn, fnContinue, { objs: state.objs.concat([value]), path: state.path + '[' + index + ']', }); }); } else if (util.kindOf(value) === 'object' && !Buffer.isBuffer(value)) { // If value is an object, recurse. obj = {}; for (key in value) { obj[key] = recurse(value[key], fn, fnContinue, { objs: state.objs.concat([value]), path: state.path + (/\W/.test(key) ? '["' + key + '"]' : '.' + key), }); } return obj; } else { // Otherwise pass value into fn and return. return fn(value); } } return recurse(value, fn, fnContinue, {objs: [], path: ''}); }; // Spawn a child process, capturing its stdout and stderr. util.spawn = function(opts, done) { // Build a result object and pass it (among other things) into the // done function. var callDone = function(code, stdout, stderr) { // Remove trailing whitespace (newline) stdout = _.rtrim(stdout); stderr = _.rtrim(stderr); // Create the result object. var result = { stdout: stdout, stderr: stderr, code: code, toString: function() { if (code === 0) { return stdout; } else if ('fallback' in opts) { return opts.fallback; } else if (opts.grunt) { // grunt.log.error uses standard out, to be fixed in 0.5. return stderr || stdout; } return stderr; } }; // On error (and no fallback) pass an error object, otherwise pass null. done(code === 0 || 'fallback' in opts ? null : new Error(stderr), result, code); }; var cmd, args; var pathSeparatorRe = /[\\\/]/g; if (opts.grunt) { cmd = process.execPath; args = process.execArgv.concat(process.argv[1], opts.args); } else { // On Windows, child_process.spawn will only file .exe files in the PATH, // not other executable types (grunt issue #155). try { if (!pathSeparatorRe.test(opts.cmd)) { // Only use which if cmd has no path component. cmd = which(opts.cmd); } else { cmd = opts.cmd.replace(pathSeparatorRe, path.sep); } } catch (err) { callDone(127, '', String(err)); return; } args = opts.args || []; } var child = spawn(cmd, args, opts.opts); var stdout = new Buffer(''); var stderr = new Buffer(''); if (child.stdout) { child.stdout.on('data', function(buf) { stdout = Buffer.concat([stdout, new Buffer(buf)]); }); } if (child.stderr) { child.stderr.on('data', function(buf) { stderr = Buffer.concat([stderr, new Buffer(buf)]); }); } child.on('close', function(code) { callDone(code, stdout.toString(), stderr.toString()); }); return child; };