which.js
  1  module.exports = which
  2  which.sync = whichSync
  3  
  4  var isWindows = process.platform === 'win32' ||
  5      process.env.OSTYPE === 'cygwin' ||
  6      process.env.OSTYPE === 'msys'
  7  
  8  var path = require('path')
  9  var COLON = isWindows ? ';' : ':'
 10  var isexe = require('isexe')
 11  
 12  function getNotFoundError (cmd) {
 13    var er = new Error('not found: ' + cmd)
 14    er.code = 'ENOENT'
 15  
 16    return er
 17  }
 18  
 19  function getPathInfo (cmd, opt) {
 20    var colon = opt.colon || COLON
 21    var pathEnv = opt.path || process.env.PATH || ''
 22    var pathExt = ['']
 23  
 24    pathEnv = pathEnv.split(colon)
 25  
 26    var pathExtExe = ''
 27    if (isWindows) {
 28      pathEnv.unshift(process.cwd())
 29      pathExtExe = (opt.pathExt || process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM')
 30      pathExt = pathExtExe.split(colon)
 31  
 32  
 33      // Always test the cmd itself first.  isexe will check to make sure
 34      // it's found in the pathExt set.
 35      if (cmd.indexOf('.') !== -1 && pathExt[0] !== '')
 36        pathExt.unshift('')
 37    }
 38  
 39    // If it has a slash, then we don't bother searching the pathenv.
 40    // just check the file itself, and that's it.
 41    if (cmd.match(/\//) || isWindows && cmd.match(/\\/))
 42      pathEnv = ['']
 43  
 44    return {
 45      env: pathEnv,
 46      ext: pathExt,
 47      extExe: pathExtExe
 48    }
 49  }
 50  
 51  function which (cmd, opt, cb) {
 52    if (typeof opt === 'function') {
 53      cb = opt
 54      opt = {}
 55    }
 56  
 57    var info = getPathInfo(cmd, opt)
 58    var pathEnv = info.env
 59    var pathExt = info.ext
 60    var pathExtExe = info.extExe
 61    var found = []
 62  
 63    ;(function F (i, l) {
 64      if (i === l) {
 65        if (opt.all && found.length)
 66          return cb(null, found)
 67        else
 68          return cb(getNotFoundError(cmd))
 69      }
 70  
 71      var pathPart = pathEnv[i]
 72      if (pathPart.charAt(0) === '"' && pathPart.slice(-1) === '"')
 73        pathPart = pathPart.slice(1, -1)
 74  
 75      var p = path.join(pathPart, cmd)
 76      if (!pathPart && (/^\.[\\\/]/).test(cmd)) {
 77        p = cmd.slice(0, 2) + p
 78      }
 79      ;(function E (ii, ll) {
 80        if (ii === ll) return F(i + 1, l)
 81        var ext = pathExt[ii]
 82        isexe(p + ext, { pathExt: pathExtExe }, function (er, is) {
 83          if (!er && is) {
 84            if (opt.all)
 85              found.push(p + ext)
 86            else
 87              return cb(null, p + ext)
 88          }
 89          return E(ii + 1, ll)
 90        })
 91      })(0, pathExt.length)
 92    })(0, pathEnv.length)
 93  }
 94  
 95  function whichSync (cmd, opt) {
 96    opt = opt || {}
 97  
 98    var info = getPathInfo(cmd, opt)
 99    var pathEnv = info.env
100    var pathExt = info.ext
101    var pathExtExe = info.extExe
102    var found = []
103  
104    for (var i = 0, l = pathEnv.length; i < l; i ++) {
105      var pathPart = pathEnv[i]
106      if (pathPart.charAt(0) === '"' && pathPart.slice(-1) === '"')
107        pathPart = pathPart.slice(1, -1)
108  
109      var p = path.join(pathPart, cmd)
110      if (!pathPart && /^\.[\\\/]/.test(cmd)) {
111        p = cmd.slice(0, 2) + p
112      }
113      for (var j = 0, ll = pathExt.length; j < ll; j ++) {
114        var cur = p + pathExt[j]
115        var is
116        try {
117          is = isexe.sync(cur, { pathExt: pathExtExe })
118          if (is) {
119            if (opt.all)
120              found.push(cur)
121            else
122              return cur
123          }
124        } catch (ex) {}
125      }
126    }
127  
128    if (opt.all && found.length)
129      return found
130  
131    if (opt.nothrow)
132      return null
133  
134    throw getNotFoundError(cmd)
135  }