├── .editorconfig ├── .gitignore ├── .jscsrc ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── lib └── hasbin.js ├── package.json └── test └── unit ├── lib └── hasbin.js ├── mock └── fs.js └── setup.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | indent_style = tab 8 | indent_size = 4 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.json] 13 | insert_final_newline = false 14 | 15 | [*.md] 16 | indent_style = spaces 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowDanglingUnderscores": true, 3 | "disallowEmptyBlocks": true, 4 | "disallowImplicitTypeConversion": [ 5 | "binary", 6 | "boolean", 7 | "numeric", 8 | "string" 9 | ], 10 | "disallowMixedSpacesAndTabs": true, 11 | "disallowMultipleSpaces": true, 12 | "disallowMultipleVarDecl": true, 13 | "disallowNewlineBeforeBlockStatements": true, 14 | "disallowQuotedKeysInObjects": true, 15 | "disallowSpaceAfterObjectKeys": true, 16 | "disallowSpaceAfterPrefixUnaryOperators": true, 17 | "disallowSpaceBeforeComma": true, 18 | "disallowSpaceBeforeSemicolon": true, 19 | "disallowSpacesInCallExpression": true, 20 | "disallowTrailingComma": true, 21 | "disallowTrailingWhitespace": true, 22 | "disallowYodaConditions": true, 23 | "maximumLineLength": 100, 24 | "requireBlocksOnNewline": true, 25 | "requireCamelCaseOrUpperCaseIdentifiers": true, 26 | "requireCapitalizedConstructors": true, 27 | "requireCommaBeforeLineBreak": true, 28 | "requireCurlyBraces": true, 29 | "requireDotNotation": true, 30 | "requireFunctionDeclarations": true, 31 | "requireKeywordsOnNewLine": [ 32 | "else" 33 | ], 34 | "requireLineBreakAfterVariableAssignment": true, 35 | "requireLineFeedAtFileEnd": true, 36 | "requireObjectKeysOnNewLine": true, 37 | "requireParenthesesAroundIIFE": true, 38 | "requireSemicolons": true, 39 | "requireSpaceAfterBinaryOperators": true, 40 | "requireSpaceAfterKeywords": true, 41 | "requireSpaceAfterLineComment": true, 42 | "requireSpaceBeforeBinaryOperators": true, 43 | "requireSpaceBeforeBlockStatements": true, 44 | "requireSpaceBeforeObjectValues": true, 45 | "requireSpaceBetweenArguments": true, 46 | "requireSpacesInConditionalExpression": true, 47 | "requireSpacesInForStatement": true, 48 | "requireSpacesInFunction": { 49 | "beforeOpeningRoundBrace": true, 50 | "beforeOpeningCurlyBrace": true 51 | }, 52 | "validateIndentation": "\t", 53 | "validateLineBreaks": "LF", 54 | "validateParameterSeparator": ", ", 55 | "validateQuoteMarks": "'", 56 | 57 | "excludeFiles": [ 58 | "coverage", 59 | "node_modules" 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "after": true, 4 | "afterEach": true, 5 | "before": true, 6 | "beforeEach": true, 7 | "describe": true, 8 | "it": true 9 | }, 10 | "latedef": "nofunc", 11 | "maxparams": 5, 12 | "maxdepth": 2, 13 | "maxstatements": 5, 14 | "maxcomplexity": 5, 15 | "node": true, 16 | "strict": true, 17 | "unused": true 18 | } 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | # Language/versions 3 | language: node_js 4 | matrix: 5 | include: 6 | 7 | # Run linter once 8 | - node_js: '6' 9 | env: LINT=true 10 | 11 | # Run tests 12 | - node_js: '0.10' 13 | - node_js: '0.12' 14 | - node_js: '4' 15 | - node_js: '5' 16 | - node_js: '6' 17 | 18 | # Build only master (and pull-requests) 19 | branches: 20 | only: 21 | - master 22 | 23 | # Before install 24 | before_install: 25 | #- npm install coveralls 26 | 27 | # Build script 28 | script: 29 | - 'if [ $LINT ]; then make lint; fi' 30 | - 'if [ ! $LINT ]; then make test; fi' 31 | #- 'if [ ! $LINT ]; then cat ./coverage/lcov.info | ./node_modules/.bin/coveralls; fi' 32 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # Changelog 3 | 4 | ## 1.2.3 (2016-05-27) 5 | 6 | * Fix Windows 10 quoted PATH entries 7 | 8 | ## 1.2.2 (2016-05-05) 9 | 10 | * Support Node.js 6.x 11 | 12 | ## 1.2.1 (2016-02-09) 13 | 14 | * Update repository references to springernature 15 | * Update the license 16 | 17 | ## 1.2.0 (2016-01-06) 18 | 19 | * Add `hasbin.first` and `hasbin.first.sync` for finding the first available binary 20 | 21 | ## 1.1.3 (2015-11-09) 22 | 23 | * Support Node.js 5.x 24 | * Update dependencies 25 | 26 | ## 1.1.2 (2015-09-09) 27 | 28 | * Support Node.js 4.x 29 | 30 | ## 1.1.1 (2015-09-08) 31 | 32 | * Update dependencies 33 | * Add code coverage reporting 34 | 35 | ## 1.1.0 (2015-07-06) 36 | 37 | * Use the `PATHEXT` environment variable in Windows when looking for binaries 38 | * Fix an issue which broke Windows support 39 | * Update dependencies 40 | 41 | ## 1.0.0 (2015-06-23) 42 | 43 | * Stable release 44 | * Add io.js support 45 | * Use JSCS to check for code-style issues 46 | * Update dependencies 47 | 48 | ## 0.2.1 pre-release (2015-05-19) 49 | 50 | * Move the repository 51 | * Assign the copyright to Nature Publishing Group 52 | 53 | ## 0.2.0 pre-release (2015-05-14) 54 | 55 | * Catch errors 56 | 57 | ## 0.1.0 pre-release (2015-05-14) 58 | 59 | * Initial release 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2015, Nature Publishing Group 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Color helpers 3 | C_CYAN=\x1b[34;01m 4 | C_RESET=\x1b[0m 5 | 6 | # Group targets 7 | all: deps lint test 8 | ci: lint test 9 | 10 | # Install dependencies 11 | deps: 12 | @echo "$(C_CYAN)> installing dependencies$(C_RESET)" 13 | @npm install 14 | 15 | # Lint JavaScript 16 | lint: jshint jscs 17 | 18 | # Run JSHint 19 | jshint: 20 | @echo "$(C_CYAN)> linting javascript$(C_RESET)" 21 | @./node_modules/.bin/jshint . 22 | 23 | # Run JavaScript Code Style 24 | jscs: 25 | @echo "$(C_CYAN)> checking javascript code style$(C_RESET)" 26 | @./node_modules/.bin/jscs . 27 | 28 | # Run all tests 29 | test: test-coverage 30 | 31 | # Run unit tests 32 | test-unit: 33 | @echo "$(C_CYAN)> running unit tests$(C_RESET)" 34 | @./node_modules/.bin/mocha ./test/unit --reporter spec --recursive 35 | 36 | # Run unit tests with coverage 37 | test-coverage: 38 | @echo "$(C_CYAN)> running unit tests with coverage$(C_RESET)" 39 | @./node_modules/.bin/istanbul cover node_modules/mocha/bin/_mocha -- ./test/unit --reporter spec --recursive 40 | @./node_modules/.bin/istanbul check-coverage --statement 90 --branch 90 --function 90 41 | 42 | .PHONY: test 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | HasBin 3 | ====== 4 | 5 | Check whether a binary exists in the `PATH` environment variable. 6 | 7 | [![NPM version][shield-npm]][info-npm] 8 | [![Node.js version support][shield-node]][info-node] 9 | [![Build status][shield-build]][info-build] 10 | [![Dependencies][shield-dependencies]][info-dependencies] 11 | [![MIT licensed][shield-license]][info-license] 12 | 13 | ```js 14 | var hasbin = require('hasbin'); 15 | 16 | // Check if a binary exists 17 | hasbin('node', function (result) { 18 | // result === true 19 | }); 20 | hasbin('wtf', function (result) { 21 | // result === false 22 | }); 23 | 24 | // Check if all binaries exist 25 | hasbin.all(['node', 'npm'], function (result) { 26 | // result === true 27 | }); 28 | 29 | // Check if at least one binary exists 30 | hasbin.some(['node', 'wtf'], function (result) { 31 | // result === true 32 | }); 33 | 34 | // Find the first available binary 35 | hasbin.first(['node', 'npm'], function (result) { 36 | // result === 'node' 37 | }); 38 | ``` 39 | 40 | 41 | Table Of Contents 42 | ----------------- 43 | 44 | - [Install](#install) 45 | - [Usage](#usage) 46 | - [Contributing](#contributing) 47 | - [License](#license) 48 | 49 | 50 | Install 51 | ------- 52 | 53 | Install HasBin with [npm][npm]: 54 | 55 | ```sh 56 | npm install hasbin 57 | ``` 58 | 59 | 60 | Usage 61 | ----- 62 | 63 | ### `hasbin(binaryName, callback)` 64 | 65 | Check whether a binary exists on one of the paths in `process.env.PATH`. Calls back with `true` if it does. 66 | 67 | ```js 68 | // Arguments 69 | binaryName = String 70 | callback = Function(Boolean) 71 | ``` 72 | 73 | ```js 74 | // Example 75 | hasbin('node', function (result) { 76 | // result === true 77 | }); 78 | ``` 79 | 80 | ### `hasbin.sync(binaryName)` 81 | 82 | Synchronous `hasbin`. 83 | 84 | ```js 85 | // Arguments 86 | binaryName = String 87 | return Boolean 88 | ``` 89 | 90 | ```js 91 | // Example 92 | result = hasbin.sync('node'); 93 | ``` 94 | 95 | ### `hasbin.all(binaryNames, callback)` 96 | 97 | Check whether all of a set of binaries exist on one of the paths in `process.env.PATH`. Calls back with `true` if all of the binaries do. Aliased as `hasbin.every`. 98 | 99 | ```js 100 | // Arguments 101 | binaryNames = Array(String) 102 | callback = Function(Boolean) 103 | ``` 104 | 105 | ```js 106 | // Example 107 | hasbin.all(['node', 'npm'], function (result) { 108 | // result === true 109 | }); 110 | ``` 111 | 112 | ### `hasbin.all.sync(binaryNames)` 113 | 114 | Synchronous `hasbin.all`. Aliased as `hasbin.every.sync`. 115 | 116 | ```js 117 | // Arguments 118 | binaryNames = Array(String) 119 | return Boolean 120 | ``` 121 | 122 | ```js 123 | // Example 124 | result = hasbin.all.sync(['node', 'npm']); 125 | ``` 126 | 127 | ### `hasbin.some(binaryNames, callback)` 128 | 129 | Check whether at least one of a set of binaries exists on one of the paths in `process.env.PATH`. Calls back with `true` if at least one of the binaries does. Aliased as `hasbin.any`. 130 | 131 | ```js 132 | // Arguments 133 | binaryNames = Array(String) 134 | callback = Function(Boolean) 135 | ``` 136 | 137 | ```js 138 | // Example 139 | hasbin.some(['node', 'npm'], function (result) { 140 | // result === true 141 | }); 142 | ``` 143 | 144 | ### `hasbin.some.sync(binaryNames)` 145 | 146 | Synchronous `hasbin.some`. Aliased as `hasbin.any.sync`. 147 | 148 | ```js 149 | // Arguments 150 | binaryNames = Array(String) 151 | return Boolean 152 | ``` 153 | 154 | ```js 155 | // Example 156 | result = hasbin.some.sync(['node', 'npm']); 157 | ``` 158 | 159 | ### `hasbin.first(binaryNames, callback)` 160 | 161 | Checks the list of `binaryNames` to find the first binary that exists on one of the paths in `process.env.PATH`. Calls back with the name of the first matched binary if one exists and `false` otherwise. 162 | 163 | ```js 164 | // Arguments 165 | binaryNames = Array(String) 166 | callback = Function(String|false) 167 | ``` 168 | 169 | ```js 170 | // Example 171 | hasbin.first(['node', 'npm'], function (result) { 172 | // result === 'node' 173 | }); 174 | ``` 175 | 176 | ### `hasbin.first.sync(binaryNames)` 177 | 178 | Synchronous `hasbin.first`. 179 | 180 | ```js 181 | // Arguments 182 | binaryNames = Array(String) 183 | return String|false 184 | ``` 185 | 186 | ```js 187 | // Example 188 | result = hasbin.first.sync(['node', 'npm']); 189 | ``` 190 | 191 | 192 | Contributing 193 | ------------ 194 | 195 | To contribute to HasBin, clone this repo locally and commit your code on a separate branch. 196 | 197 | Please write unit tests for your code, and check that everything works by running the following before opening a pull-request: 198 | 199 | ```sh 200 | make ci 201 | ``` 202 | 203 | 204 | License 205 | ------- 206 | 207 | HasBin is licensed under the [MIT][info-license] license. 208 | Copyright © 2015, Springer Nature 209 | 210 | 211 | 212 | [npm]: https://npmjs.org/ 213 | [info-coverage]: https://coveralls.io/github/springernature/hasbin 214 | [info-dependencies]: https://gemnasium.com/springernature/hasbin 215 | [info-license]: LICENSE 216 | [info-node]: package.json 217 | [info-npm]: https://www.npmjs.com/package/hasbin 218 | [info-build]: https://travis-ci.org/springernature/hasbin 219 | [shield-coverage]: https://img.shields.io/coveralls/springernature/hasbin.svg 220 | [shield-dependencies]: https://img.shields.io/gemnasium/springernature/hasbin.svg 221 | [shield-license]: https://img.shields.io/badge/license-MIT-blue.svg 222 | [shield-node]: https://img.shields.io/badge/node.js%20support-0.10–6-brightgreen.svg 223 | [shield-npm]: https://img.shields.io/npm/v/hasbin.svg 224 | [shield-build]: https://img.shields.io/travis/springernature/hasbin/master.svg 225 | -------------------------------------------------------------------------------- /lib/hasbin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var async = require('async'); 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | 7 | module.exports = hasbin; 8 | hasbin.async = hasbin; 9 | hasbin.sync = hasbinSync; 10 | hasbin.all = hasbinAll; 11 | hasbin.all.sync = hasbinAllSync; 12 | hasbin.some = hasbinSome; 13 | hasbin.some.sync = hasbinSomeSync; 14 | hasbin.first = hasbinFirst; 15 | hasbin.first.sync = hasbinFirstSync; 16 | 17 | hasbin.every = hasbin.all; 18 | hasbin.any = hasbin.some; 19 | 20 | function hasbin (bin, done) { 21 | async.some(getPaths(bin), fileExists, done); 22 | } 23 | 24 | function hasbinSync (bin) { 25 | return getPaths(bin).some(fileExistsSync); 26 | } 27 | 28 | function hasbinAll (bins, done) { 29 | async.every(bins, hasbin.async, done); 30 | } 31 | 32 | function hasbinAllSync (bins) { 33 | return bins.every(hasbin.sync); 34 | } 35 | 36 | function hasbinSome (bins, done) { 37 | async.some(bins, hasbin.async, done); 38 | } 39 | 40 | function hasbinSomeSync (bins) { 41 | return bins.some(hasbin.sync); 42 | } 43 | 44 | function hasbinFirst (bins, done) { 45 | async.detect(bins, hasbin.async, function (result) { 46 | done(result || false); 47 | }); 48 | } 49 | 50 | function hasbinFirstSync (bins) { 51 | var matched = bins.filter(hasbin.sync); 52 | return matched.length ? matched[0] : false; 53 | } 54 | 55 | function getPaths (bin) { 56 | var envPath = (process.env.PATH || ''); 57 | var envExt = (process.env.PATHEXT || ''); 58 | return envPath.replace(/["]+/g, '').split(path.delimiter).map(function (chunk) { 59 | return envExt.split(path.delimiter).map(function (ext) { 60 | return path.join(chunk, bin + ext); 61 | }); 62 | }).reduce(function (a, b) { 63 | return a.concat(b); 64 | }); 65 | } 66 | 67 | function fileExists (filePath, done) { 68 | fs.stat(filePath, function (error, stat) { 69 | if (error) { 70 | return done(false); 71 | } 72 | done(stat.isFile()); 73 | }); 74 | } 75 | 76 | function fileExistsSync (filePath) { 77 | try { 78 | return fs.statSync(filePath).isFile(); 79 | } catch (error) { 80 | return false; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hasbin", 3 | "version": "1.2.3", 4 | 5 | "description": "Check whether a binary exists in the PATH environment variable", 6 | "keywords": [ "bin", "check", "path" ], 7 | 8 | "author": "Nature Publishing Group", 9 | "contributors": [ 10 | "Rowan Manning (http://rowanmanning.com/)", 11 | "Andrew Walker (http://www.moddular.org/)" 12 | ], 13 | 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/springernature/hasbin.git" 17 | }, 18 | "homepage": "https://github.com/springernature/hasbin", 19 | "bugs": "https://github.com/springernature/hasbin/issues", 20 | "license": "MIT", 21 | 22 | "engines": { 23 | "node": ">=0.10" 24 | }, 25 | "dependencies": { 26 | "async": "~1.5" 27 | }, 28 | "devDependencies": { 29 | "istanbul": "~0.3", 30 | "jscs": "^2", 31 | "jshint": "^2", 32 | "mocha": "^2", 33 | "mockery": "~1.4", 34 | "proclaim": "^3", 35 | "sinon": "^1" 36 | }, 37 | 38 | "main": "./lib/hasbin.js", 39 | "scripts": { 40 | "test": "make ci" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/unit/lib/hasbin.js: -------------------------------------------------------------------------------- 1 | // jshint maxstatements: false 2 | // jscs:disable disallowMultipleVarDecl, maximumLineLength 3 | 'use strict'; 4 | 5 | var assert = require('proclaim'); 6 | var mockery = require('mockery'); 7 | var sinon = require('sinon'); 8 | var path = require('path'); 9 | 10 | describe('lib/hasbin', function () { 11 | var fs, hasbin; 12 | 13 | beforeEach(function () { 14 | 15 | process.env.PATH = ['/bin', '/usr/bin', '/usr/local/bin'].join(path.delimiter); 16 | 17 | fs = require('../mock/fs'); 18 | mockery.registerMock('fs', fs); 19 | 20 | fs.stat.withArgs('/usr/bin/foo').yieldsAsync(null, fs.mockStatIsFile); 21 | fs.stat.withArgs('/usr/bin/baz').yieldsAsync(null, fs.mockStatIsFile); 22 | fs.stat.withArgs('/usr/bin/error').yieldsAsync(new Error()); 23 | fs.statSync.withArgs('/usr/bin/foo').returns(fs.mockStatIsFile); 24 | fs.statSync.withArgs('/usr/bin/baz').returns(fs.mockStatIsFile); 25 | fs.statSync.withArgs('/usr/bin/error').throws(new Error()); 26 | 27 | hasbin = require('../../../lib/hasbin'); 28 | 29 | }); 30 | 31 | it('should be a function', function () { 32 | assert.isFunction(hasbin); 33 | }); 34 | 35 | it('should have an `async` method which aliases `hasbin`', function () { 36 | assert.strictEqual(hasbin.async, hasbin); 37 | }); 38 | 39 | describe('hasbin()', function () { 40 | var passingResult, failingResult, errorResult; 41 | 42 | beforeEach(function (done) { 43 | hasbin('foo', function (result) { 44 | passingResult = result; 45 | hasbin('bar', function (result) { 46 | failingResult = result; 47 | hasbin('error', function (result) { 48 | errorResult = result; 49 | done(); 50 | }); 51 | }); 52 | }); 53 | }); 54 | 55 | it('should call `fs.stat()` and `stat.isFile()` for the binary on each path in `process.env.PATH`', function () { 56 | assert.callCount(fs.stat, 9); 57 | assert.calledWith(fs.stat, '/bin/foo'); 58 | assert.calledWith(fs.stat, '/usr/bin/foo'); 59 | assert.calledWith(fs.stat, '/usr/local/bin/foo'); 60 | assert.calledWith(fs.stat, '/bin/bar'); 61 | assert.calledWith(fs.stat, '/usr/bin/bar'); 62 | assert.calledWith(fs.stat, '/usr/local/bin/bar'); 63 | assert.calledWith(fs.stat, '/bin/error'); 64 | assert.calledWith(fs.stat, '/usr/bin/error'); 65 | assert.calledWith(fs.stat, '/usr/local/bin/error'); 66 | }); 67 | 68 | it('should callback with `true` if a matching binary is found', function () { 69 | assert.isTrue(passingResult); 70 | }); 71 | 72 | it('should callback with `false` if a matching binary is not found', function () { 73 | assert.isFalse(failingResult); 74 | }); 75 | 76 | it('should callback with `false` if an error occurs', function () { 77 | assert.isFalse(errorResult); 78 | }); 79 | 80 | }); 81 | 82 | describe('hasbin() without PATH', function () { 83 | var oldPath; 84 | 85 | beforeEach(function (done) { 86 | oldPath = process.env.PATH; 87 | delete process.env.PATH; 88 | hasbin('foo', function () { 89 | done(); 90 | }); 91 | }); 92 | 93 | afterEach(function () { 94 | process.env.PATH = oldPath; 95 | }); 96 | 97 | it('should call `fs.stat()` for the binary alone', function () { 98 | assert.callCount(fs.stat, 1); 99 | assert.calledWith(fs.stat, 'foo'); 100 | }); 101 | }); 102 | 103 | describe('hasbin() with PATHEXT', function () { 104 | beforeEach(function (done) { 105 | process.env.PATHEXT = ['.COM', '.EXE'].join(path.delimiter); 106 | hasbin('foo', function () { 107 | done(); 108 | }); 109 | }); 110 | 111 | afterEach(function () { 112 | process.env.PATHEXT = ''; 113 | }); 114 | 115 | it('should call `fs.stat()` for the binary on each path in `process.env.PATH` with each extension in `process.env.PATHEXT`', function () { 116 | assert.callCount(fs.stat, 6); 117 | assert.calledWith(fs.stat, '/bin/foo.COM'); 118 | assert.calledWith(fs.stat, '/bin/foo.EXE'); 119 | assert.calledWith(fs.stat, '/usr/bin/foo.COM'); 120 | assert.calledWith(fs.stat, '/usr/bin/foo.EXE'); 121 | assert.calledWith(fs.stat, '/usr/local/bin/foo.COM'); 122 | assert.calledWith(fs.stat, '/usr/local/bin/foo.EXE'); 123 | }); 124 | }); 125 | 126 | it('should have a `sync` method', function () { 127 | assert.isFunction(hasbin.sync); 128 | }); 129 | 130 | describe('hasbin.sync()', function () { 131 | var passingResult, failingResult, errorResult; 132 | 133 | beforeEach(function () { 134 | passingResult = hasbin.sync('foo'); 135 | failingResult = hasbin.sync('bar'); 136 | errorResult = hasbin.sync('error'); 137 | }); 138 | 139 | it('should call `fs.statSync()` and `stat.isFile()` for the binary on each path in `process.env.PATH`', function () { 140 | assert.callCount(fs.statSync, 8); 141 | assert.calledWith(fs.statSync, '/bin/foo'); 142 | assert.calledWith(fs.statSync, '/usr/bin/foo'); 143 | assert.neverCalledWith(fs.statSync, '/usr/local/bin/foo'); 144 | assert.calledWith(fs.statSync, '/bin/bar'); 145 | assert.calledWith(fs.statSync, '/usr/bin/bar'); 146 | assert.calledWith(fs.statSync, '/usr/local/bin/bar'); 147 | assert.calledWith(fs.statSync, '/bin/error'); 148 | assert.calledWith(fs.statSync, '/usr/bin/error'); 149 | assert.calledWith(fs.statSync, '/usr/local/bin/error'); 150 | }); 151 | 152 | it('should return `true` if a matching binary is found', function () { 153 | assert.isTrue(passingResult); 154 | }); 155 | 156 | it('should return `false` if a matching binary is not found', function () { 157 | assert.isFalse(failingResult); 158 | }); 159 | 160 | it('should return `false` if an error occurs', function () { 161 | assert.isFalse(errorResult); 162 | }); 163 | 164 | }); 165 | 166 | it('should have an `all` method', function () { 167 | assert.isFunction(hasbin.all); 168 | }); 169 | 170 | it('should have an `every` method which aliases `all`', function () { 171 | assert.strictEqual(hasbin.every, hasbin.all); 172 | }); 173 | 174 | describe('hasbin.all()', function () { 175 | var passingResult, failingResult; 176 | 177 | beforeEach(function (done) { 178 | hasbin.async = sinon.stub(); 179 | hasbin.async.withArgs('foo').yieldsAsync(true); 180 | hasbin.async.withArgs('bar').yieldsAsync(true); 181 | hasbin.async.withArgs('baz').yieldsAsync(true); 182 | hasbin.async.withArgs('qux').yieldsAsync(false); 183 | hasbin.all(['foo', 'bar'], function (result) { 184 | passingResult = result; 185 | hasbin.all(['baz', 'qux'], function (result) { 186 | failingResult = result; 187 | done(); 188 | }); 189 | }); 190 | }); 191 | 192 | it('should call `hasbin.async()` for each binary', function () { 193 | assert.callCount(hasbin.async, 4); 194 | assert.calledWith(hasbin.async, 'foo'); 195 | assert.calledWith(hasbin.async, 'bar'); 196 | assert.calledWith(hasbin.async, 'baz'); 197 | assert.calledWith(hasbin.async, 'qux'); 198 | }); 199 | 200 | it('should callback with `true` if matching binaries are all found', function () { 201 | assert.isTrue(passingResult); 202 | }); 203 | 204 | it('should callback with `false` if a matching binary is not found', function () { 205 | assert.isFalse(failingResult); 206 | }); 207 | 208 | }); 209 | 210 | it('should have an `all.sync` method', function () { 211 | assert.isFunction(hasbin.all.sync); 212 | }); 213 | 214 | it('should have an `every.sync` method which aliases `all.sync`', function () { 215 | assert.strictEqual(hasbin.every.sync, hasbin.all.sync); 216 | }); 217 | 218 | describe('hasbin.all.sync()', function () { 219 | var passingResult, failingResult; 220 | 221 | beforeEach(function () { 222 | hasbin.sync = sinon.stub(); 223 | hasbin.sync.withArgs('foo').returns(true); 224 | hasbin.sync.withArgs('bar').returns(true); 225 | hasbin.sync.withArgs('baz').returns(true); 226 | hasbin.sync.withArgs('qux').returns(false); 227 | passingResult = hasbin.all.sync(['foo', 'bar']); 228 | failingResult = hasbin.all.sync(['baz', 'qux']); 229 | }); 230 | 231 | it('should call `hasbin.sync()` for each binary', function () { 232 | assert.callCount(hasbin.sync, 4); 233 | assert.calledWith(hasbin.sync, 'foo'); 234 | assert.calledWith(hasbin.sync, 'bar'); 235 | assert.calledWith(hasbin.sync, 'baz'); 236 | assert.calledWith(hasbin.sync, 'qux'); 237 | }); 238 | 239 | it('should return `true` if matching binaries are all found', function () { 240 | assert.isTrue(passingResult); 241 | }); 242 | 243 | it('should return `false` if a matching binary is not found', function () { 244 | assert.isFalse(failingResult); 245 | }); 246 | 247 | }); 248 | 249 | it('should have a `some` method', function () { 250 | assert.isFunction(hasbin.some); 251 | }); 252 | 253 | it('should have an `any` method which aliases `some`', function () { 254 | assert.strictEqual(hasbin.any, hasbin.some); 255 | }); 256 | 257 | describe('hasbin.some()', function () { 258 | var passingResult, failingResult; 259 | 260 | beforeEach(function (done) { 261 | hasbin.async = sinon.stub(); 262 | hasbin.async.withArgs('foo').yieldsAsync(true); 263 | hasbin.async.withArgs('bar').yieldsAsync(false); 264 | hasbin.async.withArgs('baz').yieldsAsync(false); 265 | hasbin.async.withArgs('qux').yieldsAsync(false); 266 | hasbin.some(['foo', 'bar'], function (result) { 267 | passingResult = result; 268 | hasbin.some(['baz', 'qux'], function (result) { 269 | failingResult = result; 270 | done(); 271 | }); 272 | }); 273 | }); 274 | 275 | it('should call `hasbin.async()` for each binary', function () { 276 | assert.callCount(hasbin.async, 4); 277 | assert.calledWith(hasbin.async, 'foo'); 278 | assert.calledWith(hasbin.async, 'bar'); 279 | assert.calledWith(hasbin.async, 'baz'); 280 | assert.calledWith(hasbin.async, 'qux'); 281 | }); 282 | 283 | it('should return `true` if some matching binaries are found', function () { 284 | assert.isTrue(passingResult); 285 | }); 286 | 287 | it('should return `false` if no matching binaries are found', function () { 288 | assert.isFalse(failingResult); 289 | }); 290 | 291 | }); 292 | 293 | it('should have a `some.sync` method', function () { 294 | assert.isFunction(hasbin.some.sync); 295 | }); 296 | 297 | it('should have an `any.sync` method which aliases `some.sync`', function () { 298 | assert.strictEqual(hasbin.any.sync, hasbin.some.sync); 299 | }); 300 | 301 | describe('hasbin.some.sync()', function () { 302 | var passingResult, failingResult; 303 | 304 | beforeEach(function () { 305 | hasbin.sync = sinon.stub(); 306 | hasbin.sync.withArgs('foo').returns(true); 307 | hasbin.sync.withArgs('bar').returns(false); 308 | hasbin.sync.withArgs('baz').returns(false); 309 | hasbin.sync.withArgs('qux').returns(false); 310 | passingResult = hasbin.some.sync(['foo', 'bar']); 311 | failingResult = hasbin.some.sync(['baz', 'qux']); 312 | }); 313 | 314 | it('should call `hasbin.sync()` for each binary', function () { 315 | assert.callCount(hasbin.sync, 3); 316 | assert.calledWith(hasbin.sync, 'foo'); 317 | assert.calledWith(hasbin.sync, 'baz'); 318 | assert.calledWith(hasbin.sync, 'qux'); 319 | }); 320 | 321 | it('should return `true` if some matching binaries are found', function () { 322 | assert.isTrue(passingResult); 323 | }); 324 | 325 | it('should return `false` if no matching binaries are found', function () { 326 | assert.isFalse(failingResult); 327 | }); 328 | 329 | }); 330 | 331 | it('should have a `first` method', function () { 332 | assert.isFunction(hasbin.first); 333 | }); 334 | 335 | describe('hasbin.first()', function () { 336 | var passingResult, failingResult; 337 | 338 | beforeEach(function (done) { 339 | hasbin.async = sinon.stub(); 340 | hasbin.async.withArgs('foo').yieldsAsync(true); 341 | hasbin.async.withArgs('bar').yieldsAsync(false); 342 | hasbin.async.withArgs('baz').yieldsAsync(false); 343 | hasbin.async.withArgs('qux').yieldsAsync(false); 344 | hasbin.first(['foo', 'bar'], function (result) { 345 | passingResult = result; 346 | hasbin.first(['baz', 'qux'], function (result) { 347 | failingResult = result; 348 | done(); 349 | }); 350 | }); 351 | }); 352 | 353 | it('should call `hasbin.async()` for each binary', function () { 354 | assert.callCount(hasbin.async, 4); 355 | assert.calledWith(hasbin.async, 'foo'); 356 | assert.calledWith(hasbin.async, 'bar'); 357 | assert.calledWith(hasbin.async, 'baz'); 358 | assert.calledWith(hasbin.async, 'qux'); 359 | }); 360 | 361 | it('should return the name of the first binary if some matching binaries are found', function () { 362 | assert.strictEqual('foo', passingResult); 363 | }); 364 | 365 | it('should return `false` if no matching binaries are found', function () { 366 | assert.isFalse(failingResult); 367 | }); 368 | }); 369 | 370 | it('should have a `first.sync` method', function () { 371 | assert.isFunction(hasbin.first.sync); 372 | }); 373 | 374 | describe('hasbin.first.sync()', function () { 375 | var passingResult, failingResult; 376 | 377 | beforeEach(function () { 378 | hasbin.sync = sinon.stub(); 379 | hasbin.sync.withArgs('foo').returns(true); 380 | hasbin.sync.withArgs('bar').returns(false); 381 | hasbin.sync.withArgs('baz').returns(false); 382 | hasbin.sync.withArgs('qux').returns(false); 383 | passingResult = hasbin.first.sync(['foo', 'bar']); 384 | failingResult = hasbin.first.sync(['baz', 'qux']); 385 | }); 386 | 387 | it('should call `hasbin.sync()` for each binary', function () { 388 | assert.callCount(hasbin.sync, 4); 389 | assert.calledWith(hasbin.sync, 'foo'); 390 | assert.calledWith(hasbin.sync, 'bar'); 391 | assert.calledWith(hasbin.sync, 'baz'); 392 | assert.calledWith(hasbin.sync, 'qux'); 393 | }); 394 | 395 | it('should return the name of the first binary if some matching binaries are found', function () { 396 | assert.strictEqual('foo', passingResult); 397 | }); 398 | 399 | it('should return `false` if no matching binaries are found', function () { 400 | assert.isFalse(failingResult); 401 | }); 402 | 403 | }); 404 | 405 | describe('hasbin() with quotes in PATH', function () { 406 | var passingResult, oldPath, pathArr; 407 | 408 | beforeEach(function (done) { 409 | oldPath = process.env.PATH; 410 | pathArr = process.env.PATH.split(path.delimiter); 411 | pathArr.push('"/win"'); 412 | process.env.PATH = pathArr.join(path.delimiter); 413 | 414 | fs.stat.withArgs('/win/norf').yieldsAsync(null, fs.mockStatIsFile); 415 | fs.statSync.withArgs('/win/norf').returns(fs.mockStatIsFile); 416 | 417 | hasbin('norf', function (result) { 418 | passingResult = result; 419 | done(); 420 | }); 421 | }); 422 | 423 | afterEach(function () { 424 | process.env.PATH = oldPath; 425 | }); 426 | 427 | it('should return `true` if PATH entries are wrapped in quotes and binaries are found', function () { 428 | assert.isTrue(passingResult); 429 | }); 430 | 431 | }); 432 | }); 433 | -------------------------------------------------------------------------------- /test/unit/mock/fs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var sinon = require('sinon'); 4 | 5 | var fs = module.exports = { 6 | stat: sinon.stub(), 7 | statSync: sinon.stub(), 8 | mockStatIsFile: { 9 | isFile: sinon.stub().returns(true) 10 | }, 11 | mockStatIsNotFile: { 12 | isFile: sinon.stub().returns(false) 13 | } 14 | }; 15 | 16 | fs.stat.yieldsAsync(null, fs.mockStatIsNotFile); 17 | fs.statSync.returns(fs.mockStatIsNotFile); 18 | -------------------------------------------------------------------------------- /test/unit/setup.js: -------------------------------------------------------------------------------- 1 | // jshint maxstatements: false 2 | // jscs:disable disallowMultipleVarDecl, maximumLineLength 3 | 'use strict'; 4 | 5 | var assert = require('proclaim'); 6 | var mockery = require('mockery'); 7 | var sinon = require('sinon'); 8 | 9 | sinon.assert.expose(assert, { 10 | includeFail: false, 11 | prefix: '' 12 | }); 13 | 14 | beforeEach(function () { 15 | mockery.enable({ 16 | useCleanCache: true, 17 | warnOnUnregistered: false, 18 | warnOnReplace: false 19 | }); 20 | }); 21 | 22 | afterEach(function () { 23 | mockery.deregisterAll(); 24 | mockery.disable(); 25 | }); 26 | --------------------------------------------------------------------------------