Repository: bcoe/yargs
Pull request #376 (2016-02-13 20:47:29)

Made help() only assign '--help' option if called without args
This takes care of @bcoe's idea in #367


Patch:
diff --git a/.gitignore b/.gitignore
index 2f3aff6..3bc79b8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,3 @@
 node_modules/
 .nyc_output/
 *.swp
-test.js
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d503f5f..6c0cb51 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,5 @@
 ## Change Log
 
-### 4.x
-
-- [#330](https://github.com/bcoe/yargs/pull/330) **breaking change**, fix inconsistencies with `.version()` API.
-
 ### v3.32.0 (2016/1/14 10:13 +07:00)
 
 - [#344](https://github.com/bcoe/yargs/pull/344) yargs now has a code of conduct and contributor guidelines (@bcoe)
diff --git a/README.md b/README.md
index d4f9711..c7d7f8e 100644
--- a/README.md
+++ b/README.md
@@ -733,7 +733,7 @@ when displaying usage instructions, e.g.,
 
 ```js
 var yargs = require('yargs')(['--help'])
-  .help('help')
+  .help()
   .group('batman', 'Heroes:')
   .describe('batman', "world's greatest detective")
   .wrap(null)
@@ -753,14 +753,15 @@ Add an option (e.g. `--help`) that displays the usage string and exits the
 process. If present, the `description` parameter customizes the description of
 the help option in the usage string.
 
-If invoked without parameters, `.help()` returns the generated usage string.
+If invoked without parameters, `.help()` will make `--help` the option to trigger
+help output.
 
 Example:
 
 ```js
-var yargs = require("yargs")
+var yargs = require("yargs")['--help']
   .usage("$0 -operand1 number -operand2 number -operation [add|subtract]");
-console.log(yargs.help());
+  .help()
 ```
 
 Later on, `argv` can be retrieved with `yargs.argv`.
@@ -865,11 +866,6 @@ parses as:
 
 Optionally `.nargs()` can take an object of `key`/`narg` pairs.
 
-<a name="normalize"></a>.normalize(key)
----------------
-
-The key provided represents a path and should have `path.normalize()` applied.
-
 .option(key, opt)
 -----------------
 .options(key, opt)
@@ -938,7 +934,6 @@ Valid `opt` keys include:
 - `desc`/`describe`/`description`: string, the option description for help content, see [`describe()`](#describe)
 - `group`: string, when displaying usage instructions place the option under an alternative group heading, see [`group()`](#group)
 - `nargs`: number, specify how many arguments should be consumed for the option, see [`nargs()`](#nargs)
-- `normalize`: The option should be normalized, see [`normalize()`](#normalize)
 - `requiresArg`: boolean, require the option be specified with a value, see [`requiresArg()`](#requiresArg)
 - `string`: boolean, interpret option as a string, see [`string()`](#string)
 - `type`: one of the following strings
@@ -1128,22 +1123,20 @@ present script similar to how `$0` works in bash or perl.
 
 `opts` is optional and acts like calling `.options(opts)`.
 
-.version([option], [description], [version])
+.version(version, [option], [description])
 ----------------------------------------
 
 Add an option (e.g. `--version`) that displays the version number (given by the
-`version` parameter) and exits the process.
-
-If no arguments are passed to `version` (`.version()`), yargs will parse the `package.json`
-of your module and use its `version` value. The default value of `option` is `--version`.
+`version` parameter) and exits the process. If present, the `description`
+parameter customizes the description of the version option in the usage string.
 
 You can provide a `function` for version, rather than a string.
-This is useful if you want to use a version stored in a location other than package.json:
+This is useful if you want to use the version from your package.json:
 
 ```js
 var argv = require('yargs')
   .version(function() {
-    return require('../lib/version').version;
+    return require('../package').version;
   })
   .argv;
 ```
@@ -1231,33 +1224,14 @@ To run the tests with npm, just do:
 
     npm test
 
-configuration
-=============
-
-Using the `yargs` stanza in your `package.json` you can turn on and off
-some of yargs' parsing features:
-
-```json
-{
-  "yargs": {
-    "short-option-groups": true,
-    "camel-case-expansion": true,
-    "dot-notation": true,
-    "parse-numbers": true,
-    "boolean-negation": true
-  }
-}
-```
-
-See the [yargs-parser](https://github.com/yargs/yargs-parser#configuration) module
-for detailed documentation of this feature.
-
 inspired by
 ===========
 
 This module is loosely inspired by Perl's
 [Getopt::Casual](http://search.cpan.org/~photo/Getopt-Casual-0.13.1/Casual.pm).
 
+
+
 [travis-url]: https://travis-ci.org/bcoe/yargs
 [travis-image]: https://img.shields.io/travis/bcoe/yargs.svg
 [gemnasium-url]: https://gemnasium.com/bcoe/yargs
diff --git a/index.js b/index.js
index 9034463..11263ad 100644
--- a/index.js
+++ b/index.js
@@ -1,13 +1,11 @@
 var assert = require('assert')
 var Completion = require('./lib/completion')
-var Parser = require('yargs-parser')
+var Parser = require('./lib/parser')
 var path = require('path')
+var tokenizeArgString = require('./lib/tokenize-arg-string')
 var Usage = require('./lib/usage')
 var Validation = require('./lib/validation')
 var Y18n = require('y18n')
-var readPkgUp = require('read-pkg-up')
-var pkgConf = require('pkg-conf')
-var requireMainFilename = require('require-main-filename')
 
 Argv(process.argv.slice(2))
 
@@ -177,7 +175,25 @@ function Argv (processArgs, cwd) {
         self.alias(key, x[key])
       })
     } else {
-      options.alias[x] = (options.alias[x] || []).concat(y)
+      // perhaps 'x' is already an alias in another list?
+      // if so we should append to x's list.
+      var aliases = null
+      Object.keys(options.alias).forEach(function (key) {
+        if (~options.alias[key].indexOf(x)) aliases = options.alias[key]
+      })
+
+      if (aliases) { // x was an alias itself.
+        aliases.push(y)
+      } else { // x is a new alias key.
+        options.alias[x] = (options.alias[x] || []).concat(y)
+      }
+
+      // wait! perhaps we've created two lists of aliases
+      // that reference each other?
+      if (options.alias[y]) {
+        Array.prototype.push.apply((options.alias[x] || aliases), options.alias[y])
+        delete options.alias[y]
+      }
     }
     return self
   }
@@ -291,8 +307,6 @@ function Argv (processArgs, cwd) {
         self.default(key, opt.default)
       } if ('nargs' in opt) {
         self.nargs(key, opt.nargs)
-      } if ('normalize' in opt) {
-        self.normalize(key)
       } if ('choices' in opt) {
         self.choices(key, opt.choices)
       } if ('group' in opt) {
@@ -370,36 +384,17 @@ function Argv (processArgs, cwd) {
   }
 
   var versionOpt = null
-  self.version = function (opt, msg, ver) {
-    if (arguments.length === 0) {
-      ver = guessVersion()
-      opt = 'version'
-    } else if (arguments.length === 1) {
-      ver = opt
-      opt = 'version'
-    } else if (arguments.length === 2) {
-      ver = msg
-    }
-
-    versionOpt = opt
-    msg = msg || usage.deferY18nLookup('Show version number')
-
-    usage.version(ver || undefined)
+  self.version = function (ver, opt, msg) {
+    versionOpt = opt || 'version'
+    usage.version(ver)
     self.boolean(versionOpt)
-    self.describe(versionOpt, msg)
+    self.describe(versionOpt, msg || usage.deferY18nLookup('Show version number'))
     return self
   }
 
-  function guessVersion () {
-    var obj = readPkgUp.sync({
-      cwd: requireMainFilename()
-    })
-
-    return obj.pkg ? obj.pkg.version : 'unknown'
-  }
-
   var helpOpt = null
-  self.addHelpOpt = function (opt, msg) {
+  self.addHelpOpt = self.help = function (opt, msg) {
+    opt = opt || 'help'
     helpOpt = opt
     self.boolean(opt)
     self.describe(opt, msg || usage.deferY18nLookup('Show help'))
@@ -423,14 +418,6 @@ function Argv (processArgs, cwd) {
     return exitProcess
   }
 
-  self.help = function () {
-    if (arguments.length > 0) return self.addHelpOpt.apply(self, arguments)
-
-    if (!self.parsed) parseArgs(processArgs) // run parser, if it has not already been executed.
-
-    return usage.help()
-  }
-
   var completionCommand = null
   self.completion = function (cmd, desc, fn) {
     // a function to execute when generating
@@ -513,16 +500,14 @@ function Argv (processArgs, cwd) {
   })
 
   function parseArgs (args) {
-    options.__ = y18n.__
-    options.configuration = pkgConf.sync('yargs', {
-      defaults: {},
-      cwd: requireMainFilename()
-    })
-    var parsed = Parser.detailed(args, options)
+    args = normalizeArgs(args)
+
+    var parsed = Parser(args, options, y18n)
     var argv = parsed.argv
     var aliases = parsed.aliases
 
     argv.$0 = self.$0
+
     self.parsed = parsed
 
     guessLocale() // guess locale lazily, so that it can be turned off in chain.
@@ -637,6 +622,13 @@ function Argv (processArgs, cwd) {
     })
   }
 
+  function normalizeArgs (args) {
+    if (typeof args === 'string') {
+      return tokenizeArgString(args)
+    }
+    return args
+  }
+
   singletonify(self)
   return self
 }
diff --git a/lib/parser.js b/lib/parser.js
new file mode 100644
index 0000000..f5f68e9
--- /dev/null
+++ b/lib/parser.js
@@ -0,0 +1,520 @@
+// fancy-pants parsing of argv, originally forked
+// from minimist: https://www.npmjs.com/package/minimist
+var camelCase = require('camelcase')
+var path = require('path')
+
+function increment (orig) {
+  return orig !== undefined ? orig + 1 : 0
+}
+
+module.exports = function (args, opts, y18n) {
+  if (!opts) opts = {}
+
+  var __ = y18n.__
+  var error = null
+  var flags = { arrays: {}, bools: {}, strings: {}, counts: {}, normalize: {}, configs: {}, defaulted: {} }
+
+  ;[].concat(opts['array']).filter(Boolean).forEach(function (key) {
+    flags.arrays[key] = true
+  })
+
+  ;[].concat(opts['boolean']).filter(Boolean).forEach(function (key) {
+    flags.bools[key] = true
+  })
+
+  ;[].concat(opts.string).filter(Boolean).forEach(function (key) {
+    flags.strings[key] = true
+  })
+
+  ;[].concat(opts.count).filter(Boolean).forEach(function (key) {
+    flags.counts[key] = true
+  })
+
+  ;[].concat(opts.normalize).filter(Boolean).forEach(function (key) {
+    flags.normalize[key] = true
+  })
+
+  Object.keys(opts.config).forEach(function (k) {
+    flags.configs[k] = opts.config[k]
+  })
+
+  var aliases = {}
+  var newAliases = {}
+
+  extendAliases(opts.key)
+  extendAliases(opts.alias)
+  extendAliases(opts.default)
+
+  var defaults = opts['default'] || {}
+  Object.keys(defaults).forEach(function (key) {
+    if (/-/.test(key) && !opts.alias[key]) {
+      aliases[key] = aliases[key] || []
+    }
+    (aliases[key] || []).forEach(function (alias) {
+      defaults[alias] = defaults[key]
+    })
+  })
+
+  var argv = { _: [] }
+
+  Object.keys(flags.bools).forEach(function (key) {
+    setArg(key, !(key in defaults) ? false : defaults[key])
+    setDefaulted(key)
+  })
+
+  var notFlags = []
+  if (args.indexOf('--') !== -1) {
+    notFlags = args.slice(args.indexOf('--') + 1)
+    args = args.slice(0, args.indexOf('--'))
+  }
+
+  for (var i = 0; i < args.length; i++) {
+    var arg = args[i]
+    var broken
+    var key
+    var letters
+    var m
+    var next
+    var value
+
+    // -- seperated by =
+    if (arg.match(/^--.+=/)) {
+      // Using [\s\S] instead of . because js doesn't support the
+      // 'dotall' regex modifier. See:
+      // http://stackoverflow.com/a/1068308/13216
+      m = arg.match(/^--([^=]+)=([\s\S]*)$/)
+
+      // nargs format = '--f=monkey washing cat'
+      if (checkAllAliases(m[1], opts.narg)) {
+        args.splice(i + 1, m[1], m[2])
+        i = eatNargs(i, m[1], args)
+      // arrays format = '--f=a b c'
+      } else if (checkAllAliases(m[1], flags.arrays) && args.length > i + 1) {
+        args.splice(i + 1, m[1], m[2])
+        i = eatArray(i, m[1], args)
+      } else {
+        setArg(m[1], m[2])
+      }
+    } else if (arg.match(/^--no-.+/)) {
+      key = arg.match(/^--no-(.+)/)[1]
+      setArg(key, false)
+
+    // -- seperated by space.
+    } else if (arg.match(/^--.+/)) {
+      key = arg.match(/^--(.+)/)[1]
+
+      // nargs format = '--foo a b c'
+      if (checkAllAliases(key, opts.narg)) {
+        i = eatNargs(i, key, args)
+      // array format = '--foo a b c'
+      } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) {
+        i = eatArray(i, key, args)
+      } else {
+        next = args[i + 1]
+
+        if (next !== undefined && !next.match(/^-/) &&
+          !checkAllAliases(key, flags.bools) &&
+          !checkAllAliases(key, flags.counts)) {
+          setArg(key, next)
+          i++
+        } else if (/^(true|false)$/.test(next)) {
+          setArg(key, next)
+          i++
+        } else {
+          setArg(key, defaultForType(guessType(key, flags)))
+        }
+      }
+
+    // dot-notation flag seperated by '='.
+    } else if (arg.match(/^-.\..+=/)) {
+      m = arg.match(/^-([^=]+)=([\s\S]*)$/)
+      setArg(m[1], m[2])
+
+    // dot-notation flag seperated by space.
+    } else if (arg.match(/^-.\..+/)) {
+      next = args[i + 1]
+      key = arg.match(/^-(.\..+)/)[1]
+
+      if (next !== undefined && !next.match(/^-/) &&
+        !checkAllAliases(key, flags.bools) &&
+        !checkAllAliases(key, flags.counts)) {
+        setArg(key, next)
+        i++
+      } else {
+        setArg(key, defaultForType(guessType(key, flags)))
+      }
+    } else if (arg.match(/^-[^-]+/)) {
+      letters = arg.slice(1, -1).split('')
+      broken = false
+
+      for (var j = 0; j < letters.length; j++) {
+        next = arg.slice(j + 2)
+
+        if (letters[j + 1] && letters[j + 1] === '=') {
+          value = arg.slice(j + 3)
+          key = letters[j]
+
+          // nargs format = '-f=monkey washing cat'
+          if (checkAllAliases(letters[j], opts.narg)) {
+            args.splice(i + 1, 0, value)
+            i = eatNargs(i, key, args)
+          // array format = '-f=a b c'
+          } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) {
+            args.splice(i + 1, 0, value)
+            i = eatArray(i, key, args)
+          } else {
+            setArg(key, value)
+          }
+
+          broken = true
+          break
+        }
+
+        if (next === '-') {
+          setArg(letters[j], next)
+          continue
+        }
+
+        if (/[A-Za-z]/.test(letters[j]) &&
+          /-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) {
+          setArg(letters[j], next)
+          broken = true
+          break
+        }
+
+        if (letters[j + 1] && letters[j + 1].match(/\W/)) {
+          setArg(letters[j], arg.slice(j + 2))
+          broken = true
+          break
+        } else {
+          setArg(letters[j], defaultForType(guessType(letters[j], flags)))
+        }
+      }
+
+      key = arg.slice(-1)[0]
+
+      if (!broken && key !== '-') {
+        // nargs format = '-f a b c'
+        if (checkAllAliases(key, opts.narg)) {
+          i = eatNargs(i, key, args)
+        // array format = '-f a b c'
+        } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) {
+          i = eatArray(i, key, args)
+        } else {
+          if (args[i + 1] && !/^(-|--)[^-]/.test(args[i + 1]) &&
+            !checkAllAliases(key, flags.bools) &&
+            !checkAllAliases(key, flags.counts)) {
+            setArg(key, args[i + 1])
+            i++
+          } else if (args[i + 1] && /true|false/.test(args[i + 1])) {
+            setArg(key, args[i + 1])
+            i++
+          } else {
+            setArg(key, defaultForType(guessType(key, flags)))
+          }
+        }
+      }
+    } else {
+      argv._.push(
+        flags.strings['_'] || !isNumber(arg) ? arg : Number(arg)
+      )
+    }
+  }
+
+  // order of precedence:
+  // 1. command line arg
+  // 2. value from config file
+  // 3. value from env var
+  // 4. configured default value
+  applyEnvVars(opts, argv, true) // special case: check env vars that point to config file
+  setConfig(argv)
+  applyEnvVars(opts, argv, false)
+  applyDefaultsAndAliases(argv, aliases, defaults)
+
+  Object.keys(flags.counts).forEach(function (key) {
+    setArg(key, defaults[key])
+  })
+
+  notFlags.forEach(function (key) {
+    argv._.push(key)
+  })
+
+  // how many arguments should we consume, based
+  // on the nargs option?
+  function eatNargs (i, key, args) {
+    var toEat = checkAllAliases(key, opts.narg)
+
+    if (args.length - (i + 1) < toEat) error = Error(__('Not enough arguments following: %s', key))
+
+    for (var ii = i + 1; ii < (toEat + i + 1); ii++) {
+      setArg(key, args[ii])
+    }
+
+    return (i + toEat)
+  }
+
+  // if an option is an array, eat all non-hyphenated arguments
+  // following it... YUM!
+  // e.g., --foo apple banana cat becomes ["apple", "banana", "cat"]
+  function eatArray (i, key, args) {
+    for (var ii = i + 1; ii < args.length; ii++) {
+      if (/^-/.test(args[ii])) break
+      i = ii
+      setArg(key, args[ii])
+    }
+
+    return i
+  }
+
+  function setArg (key, val) {
+    unsetDefaulted(key)
+
+    // handle parsing boolean arguments --foo=true --bar false.
+    if (checkAllAliases(key, flags.bools) || checkAllAliases(key, flags.counts)) {
+      if (typeof val === 'string') val = val === 'true'
+    }
+
+    if (/-/.test(key) && !(aliases[key] && aliases[key].length)) {
+      var c = camelCase(key)
+      aliases[key] = [c]
+      newAliases[c] = true
+    }
+
+    var value = !checkAllAliases(key, flags.strings) && isNumber(val) ? Number(val) : val
+
+    if (checkAllAliases(key, flags.counts)) {
+      value = increment
+    }
+
+    var splitKey = key.split('.')
+    setKey(argv, splitKey, value)
+
+    // alias references an inner-value within
+    // a dot-notation object. see #279.
+    if (~key.indexOf('.') && aliases[key]) {
+      aliases[key].forEach(function (x) {
+        x = x.split('.')
+        setKey(argv, x, value)
+      })
+    }
+
+    ;(aliases[splitKey[0]] || []).forEach(function (x) {
+      x = x.split('.')
+
+      // handle populating dot notation for both
+      // the key and its aliases.
+      if (splitKey.length > 1) {
+        var a = [].concat(splitKey)
+        a.shift() // nuke the old key.
+        x = x.concat(a)
+      }
+
+      setKey(argv, x, value)
+    })
+
+    var keys = [key].concat(aliases[key] || [])
+    for (var i = 0, l = keys.length; i < l; i++) {
+      if (flags.normalize[keys[i]]) {
+        keys.forEach(function (key) {
+          argv.__defineSetter__(key, function (v) {
+            val = path.normalize(v)
+          })
+
+          argv.__defineGetter__(key, function () {
+            return typeof val === 'string' ? path.normalize(val) : val
+          })
+        })
+        break
+      }
+    }
+  }
+
+  // set args from config.json file, this should be
+  // applied last so that defaults can be applied.
+  function setConfig (argv) {
+    var configLookup = {}
+
+    // expand defaults/aliases, in-case any happen to reference
+    // the config.json file.
+    applyDefaultsAndAliases(configLookup, aliases, defaults)
+
+    Object.keys(flags.configs).forEach(function (configKey) {
+      var configPath = argv[configKey] || configLookup[configKey]
+      if (configPath) {
+        try {
+          var config = null
+          var resolvedConfigPath = path.resolve(process.cwd(), configPath)
+
+          if (typeof flags.configs[configKey] === 'function') {
+            try {
+              config = flags.configs[configKey](resolvedConfigPath)
+            } catch (e) {
+              config = e
+            }
+            if (config instanceof Error) {
+              error = config
+              return
+            }
+          } else {
+            config = require(resolvedConfigPath)
+          }
+
+          Object.keys(config).forEach(function (key) {
+            // setting arguments via CLI takes precedence over
+            // values within the config file.
+            if (argv[key] === undefined || (flags.defaulted[key])) {
+              delete argv[key]
+              setArg(key, config[key])
+            }
+          })
+        } catch (ex) {
+          if (argv[configKey]) error = Error(__('Invalid JSON config file: %s', configPath))
+        }
+      }
+    })
+  }
+
+  function applyEnvVars (opts, argv, configOnly) {
+    if (typeof opts.envPrefix === 'undefined') return
+
+    var prefix = typeof opts.envPrefix === 'string' ? opts.envPrefix : ''
+    Object.keys(process.env).forEach(function (envVar) {
+      if (prefix === '' || envVar.lastIndexOf(prefix, 0) === 0) {
+        var key = camelCase(envVar.substring(prefix.length))
+        if (((configOnly && flags.configs[key]) || !configOnly) && (!(key in argv) || flags.defaulted[key])) {
+          setArg(key, process.env[envVar])
+        }
+      }
+    })
+  }
+
+  function applyDefaultsAndAliases (obj, aliases, defaults) {
+    Object.keys(defaults).forEach(function (key) {
+      if (!hasKey(obj, key.split('.'))) {
+        setKey(obj, key.split('.'), defaults[key])
+
+        ;(aliases[key] || []).forEach(function (x) {
+          if (hasKey(obj, x.split('.'))) return
+          setKey(obj, x.split('.'), defaults[key])
+        })
+      }
+    })
+  }
+
+  function hasKey (obj, keys) {
+    var o = obj
+    keys.slice(0, -1).forEach(function (key) {
+      o = (o[key] || {})
+    })
+
+    var key = keys[keys.length - 1]
+
+    if (typeof o !== 'object') return false
+    else return key in o
+  }
+
+  function setKey (obj, keys, value) {
+    var o = obj
+    keys.slice(0, -1).forEach(function (key) {
+      if (o[key] === undefined) o[key] = {}
+      o = o[key]
+    })
+
+    var key = keys[keys.length - 1]
+    if (value === increment) {
+      o[key] = increment(o[key])
+    } else if (o[key] === undefined && checkAllAliases(key, flags.arrays)) {
+      o[key] = Array.isArray(value) ? value : [value]
+    } else if (o[key] === undefined || typeof o[key] === 'boolean') {
+      o[key] = value
+    } else if (Array.isArray(o[key])) {
+      o[key].push(value)
+    } else {
+      o[key] = [ o[key], value ]
+    }
+  }
+
+  // extend the aliases list with inferred aliases.
+  function extendAliases (obj) {
+    Object.keys(obj || {}).forEach(function (key) {
+      // short-circuit if we've already added a key
+      // to the aliases array, for example it might
+      // exist in both 'opts.default' and 'opts.key'.
+      if (aliases[key]) return
+
+      aliases[key] = [].concat(opts.alias[key] || [])
+      // For "--option-name", also set argv.optionName
+      aliases[key].concat(key).forEach(function (x) {
+        if (/-/.test(x)) {
+          var c = camelCase(x)
+          aliases[key].push(c)
+          newAliases[c] = true
+        }
+      })
+      aliases[key].forEach(function (x) {
+        aliases[x] = [key].concat(aliases[key].filter(function (y) {
+          return x !== y
+        }))
+      })
+    })
+  }
+
+  // check if a flag is set for any of a key's aliases.
+  function checkAllAliases (key, flag) {
+    var isSet = false
+    var toCheck = [].concat(aliases[key] || [], key)
+
+    toCheck.forEach(function (key) {
+      if (flag[key]) isSet = flag[key]
+    })
+
+    return isSet
+  }
+
+  function setDefaulted (key) {
+    [].concat(aliases[key] || [], key).forEach(function (k) {
+      flags.defaulted[k] = true
+    })
+  }
+
+  function unsetDefaulted (key) {
+    [].concat(aliases[key] || [], key).forEach(function (k) {
+      delete flags.defaulted[k]
+    })
+  }
+
+  // return a default value, given the type of a flag.,
+  // e.g., key of type 'string' will default to '', rather than 'true'.
+  function defaultForType (type) {
+    var def = {
+      boolean: true,
+      string: '',
+      array: []
+    }
+
+    return def[type]
+  }
+
+  // given a flag, enforce a default type.
+  function guessType (key, flags) {
+    var type = 'boolean'
+
+    if (flags.strings && flags.strings[key]) type = 'string'
+    else if (flags.arrays && flags.arrays[key]) type = 'array'
+
+    return type
+  }
+
+  function isNumber (x) {
+    if (typeof x === 'number') return true
+    if (/^0x[0-9a-f]+$/i.test(x)) return true
+    return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x)
+  }
+
+  return {
+    argv: argv,
+    aliases: aliases,
+    error: error,
+    newAliases: newAliases
+  }
+}
diff --git a/lib/tokenize-arg-string.js b/lib/tokenize-arg-string.js
new file mode 100644
index 0000000..df43fa5
--- /dev/null
+++ b/lib/tokenize-arg-string.js
@@ -0,0 +1,32 @@
+// take an un-split argv string and tokenize it.
+module.exports = function (argString) {
+  var i = 0
+  var c = null
+  var opening = null
+  var args = []
+
+  for (var ii = 0; ii < argString.length; ii++) {
+    c = argString.charAt(ii)
+
+    // split on spaces unless we're in quotes.
+    if (c === ' ' && !opening) {
+      i++
+      continue
+    }
+
+    // don't split the string if we're in matching
+    // opening or closing single and double quotes.
+    if (c === opening) {
+      opening = null
+      continue
+    } else if ((c === "'" || c === '"') && !opening) {
+      opening = c
+      continue
+    }
+
+    if (!args[i]) args[i] = ''
+    args[i] += c
+  }
+
+  return args
+}
diff --git a/lib/usage.js b/lib/usage.js
index a1c18ec..b1b59bc 100644
--- a/lib/usage.js
+++ b/lib/usage.js
@@ -370,7 +370,7 @@ module.exports = function (yargs, y18n) {
 
   // logic for displaying application version.
   var version = null
-  self.version = function (ver) {
+  self.version = function (ver, opt, msg) {
     version = ver
   }
 
diff --git a/package.json b/package.json
index 03c3382..c410ddc 100644
--- a/package.json
+++ b/package.json
@@ -15,24 +15,18 @@
     "cliui": "^3.0.3",
     "decamelize": "^1.1.1",
     "os-locale": "^1.4.0",
-    "pkg-conf": "^1.1.1",
-    "read-pkg-up": "^1.0.1",
-    "require-main-filename": "^1.0.0",
     "string-width": "^1.0.1",
     "window-size": "^0.2.0",
-    "y18n": "^3.2.0",
-    "yargs-parser": "^2.0.0"
+    "y18n": "^3.2.0"
   },
   "devDependencies": {
     "chai": "^3.4.1",
     "chalk": "^1.1.1",
     "coveralls": "^2.11.4",
-    "cpr": "^1.0.0",
     "es6-promise": "^3.0.2",
     "hashish": "0.0.4",
     "mocha": "^2.4.5",
     "nyc": "^5.2.0",
-    "rimraf": "^2.5.0",
     "standard": "^5.4.1",
     "which": "^1.1.2",
     "win-spawn": "^2.0.0"
@@ -50,6 +44,9 @@
   "standard": {
     "ignore": [
       "**/example/**"
+    ],
+    "globals": [
+      "it"
     ]
   },
   "keywords": [
@@ -61,6 +58,44 @@
     "cli",
     "command"
   ],
+  "author": {
+    "name": "Alex Ford",
+    "email": "Alex.Ford@CodeTunnel.com",
+    "url": "http://CodeTunnel.com"
+  },
+  "contributors": [
+    {
+      "name": "Benjamin Coe",
+      "email": "ben@npmjs.com",
+      "url": "https://github.com/bcoe"
+    },
+    {
+      "name": "Andrew Goode",
+      "url": "https://github.com/nexdrew"
+    },
+    {
+      "name": "Chris Needham",
+      "email": "chris@chrisneedham.com",
+      "url": "http://chrisneedham.com"
+    },
+    {
+      "name": "James Nylen",
+      "email": "jnylen@gmail.com",
+      "url": "https://github.com/nylen"
+    },
+    {
+      "name": "Benjamin Horsleben",
+      "url": "https://github.com/fizker"
+    },
+    {
+      "name": "Lin Clark",
+      "url": "https://github.com/linclark"
+    },
+    {
+      "name": "Tim Schaub",
+      "url": "https://github.com/tschaub"
+    }
+  ],
   "license": "MIT",
   "engine": {
     "node": ">=0.10"


Test patch:
diff --git a/test/fixtures/config.txt b/test/fixtures/config.txt
new file mode 100644
index 0000000..8e6e5a4
--- /dev/null
+++ b/test/fixtures/config.txt
@@ -0,0 +1,2 @@
+AWESOME=banana
+BATMAN=grumpy
diff --git a/test/fixtures/normal-bin.js b/test/fixtures/normal-bin.js
deleted file mode 100755
index 6ad4e38..0000000
--- a/test/fixtures/normal-bin.js
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/usr/bin/env node
-var argv = require('./yargs/index.js')
-  .help('help')
-  .version()
-  .argv
-console.log(argv)
diff --git a/test/fixtures/package.json b/test/fixtures/package.json
deleted file mode 100644
index 808a84b..0000000
--- a/test/fixtures/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "version": "9.9.9",
-  "yargs": {
-    "dot-notation": false
-  }
-}
diff --git a/test/fixtures/settings.js b/test/fixtures/settings.js
new file mode 100644
index 0000000..225c910
--- /dev/null
+++ b/test/fixtures/settings.js
@@ -0,0 +1,6 @@
+module.exports = {
+  calculate: function (a) {
+    return a + 55
+  },
+  herp: 'derp'
+}
diff --git a/test/fixtures/symlink-bin.js b/test/fixtures/symlink-bin.js
deleted file mode 100755
index 14b7de1..0000000
--- a/test/fixtures/symlink-bin.js
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/usr/bin/env node
-var argv = require('./yargs-symlink/index.js')
-  .help('help')
-  .version()
-  .argv
-console.log(argv)
diff --git a/test/integration.js b/test/integration.js
index 9d02ca8..a0d1071 100644
--- a/test/integration.js
+++ b/test/integration.js
@@ -1,10 +1,7 @@
-/* global describe, it, before, after */
+/* global describe, it */
 
 var spawn = require('win-spawn')
 var which = require('which')
-var rimraf = require('rimraf')
-var cpr = require('cpr')
-var fs = require('fs')
 
 require('chai').should()
 
@@ -49,58 +46,6 @@ describe('integration tests', function () {
       return done()
     })
   })
-
-  describe('load root package.json', function () {
-    before(function (done) {
-      this.timeout(10000)
-      // create a symlinked, and a physical copy of yargs in
-      // our fixtures directory, so that we can test that the
-      // nearest package.json is appropriately loaded.
-      cpr('./', './test/fixtures/yargs', {
-        filter: /node_modules|example|test|package\.json/
-      }, function () {
-        fs.symlinkSync(process.cwd(), './test/fixtures/yargs-symlink')
-        return done()
-      })
-    })
-
-    describe('version #', function () {
-      it('defaults to appropriate version # when yargs is installed normally', function (done) {
-        testCmd('./normal-bin.js', [ '--version' ], function (buf) {
-          buf.should.match(/9\.9\.9/)
-          return done()
-        })
-      })
-
-      it('defaults to appropriate version # when yargs is symlinked', function (done) {
-        testCmd('./symlink-bin.js', [ '--version' ], function (buf) {
-          buf.should.match(/9\.9\.9/)
-          return done()
-        })
-      })
-    })
-
-    describe('parser settings', function (done) {
-      it('reads parser config settings when yargs is installed normally', function (done) {
-        testCmd('./normal-bin.js', [ '--foo.bar' ], function (buf) {
-          buf.should.match(/foo\.bar/)
-          return done()
-        })
-      })
-
-      it('reads parser config settings when yargs is installed as a symlink', function (done) {
-        testCmd('./symlink-bin.js', [ '--foo.bar' ], function (buf) {
-          buf.should.match(/foo\.bar/)
-          return done()
-        })
-      })
-    })
-
-    after(function () {
-      rimraf.sync('./test/fixtures/yargs')
-      fs.unlinkSync('./test/fixtures/yargs-symlink')
-    })
-  })
 })
 
 function testCmd (cmd, args, done) {
@@ -116,9 +61,7 @@ function testCmd (cmd, args, done) {
 
   bin.stdout.on('data', function (buf) {
     // hack to allow us to assert against completion suggestions.
-    if (~args.indexOf('--get-yargs-completions') ||
-      ~args.indexOf('--version') ||
-      ~args.indexOf('--foo.bar')) return done(buf.toString())
+    if (~args.indexOf('--get-yargs-completions')) return done(buf.toString())
 
     var _ = JSON.parse(buf.toString())
     _.map(String).should.deep.equal(args.map(String))
diff --git a/test/parser.js b/test/parser.js
new file mode 100644
index 0000000..78a34fa
--- /dev/null
+++ b/test/parser.js
@@ -0,0 +1,1431 @@
+/* global describe, it, beforeEach */
+
+var should = require('chai').should()
+var expect = require('chai').expect
+var fs = require('fs')
+var yargs = require('../')
+var path = require('path')
+
+describe('parser tests', function () {
+  beforeEach(function () {
+    yargs.reset()
+  })
+
+  it('should pass when specifying a "short boolean"', function () {
+    var parse = yargs.parse([ '-b' ])
+    parse.should.have.property('b').to.be.ok.and.be.a('boolean')
+    parse.should.have.property('_').with.length(0)
+  })
+
+  it('should pass when specifying a "long boolean"', function () {
+    var parse = yargs.parse(['--bool'])
+    parse.should.have.property('bool', true)
+    parse.should.have.property('_').with.length(0)
+  })
+
+  it('should place bare options in the _ array', function () {
+    var parse = yargs.parse(['foo', 'bar', 'baz'])
+    parse.should.have.property('_').and.deep.equal(['foo', 'bar', 'baz'])
+  })
+
+  it('should expand grouped short options to a hash with a key for each', function () {
+    var parse = yargs.parse(['-cats'])
+    parse.should.have.property('c', true)
+    parse.should.have.property('a', true)
+    parse.should.have.property('t', true)
+    parse.should.have.property('s', true)
+    parse.should.have.property('_').with.length(0)
+  })
+
+  it('should set the value of the final option in a group to the next supplied value', function () {
+    var parse = yargs.parse(['-cats', 'meow'])
+    parse.should.have.property('c', true)
+    parse.should.have.property('a', true)
+    parse.should.have.property('t', true)
+    parse.should.have.property('s', 'meow')
+    parse.should.have.property('_').with.length(0)
+  })
+
+  it('should set the value of a single short option to the next supplied value', function () {
+    var parse = yargs.parse(['-h', 'localhost'])
+    parse.should.have.property('h', 'localhost')
+    parse.should.have.property('_').with.length(0)
+  })
+
+  it('should set the value of multiple single short options to the next supplied values relative to each', function () {
+    var parse = yargs.parse(['-h', 'localhost', '-p', '555'])
+    parse.should.have.property('h', 'localhost')
+    parse.should.have.property('p', 555)
+    parse.should.have.property('_').with.length(0)
+  })
+
+  it('should set the value of a single long option to the next supplied value', function () {
+    var parse = yargs.parse(['--pow', 'xixxle'])
+    parse.should.have.property('pow', 'xixxle')
+    parse.should.have.property('_').with.length(0)
+  })
+
+  it('should set the value of a single long option if an = was used', function () {
+    var parse = yargs.parse(['--pow=xixxle'])
+    parse.should.have.property('pow', 'xixxle')
+    parse.should.have.property('_').with.length(0)
+  })
+
+  it('should set the value of multiple long options to the next supplied values relative to each', function () {
+    var parse = yargs.parse(['--host', 'localhost', '--port', '555'])
+    parse.should.have.property('host', 'localhost')
+    parse.should.have.property('port', 555)
+    parse.should.have.property('_').with.length(0)
+  })
+
+  it('should set the value of multiple long options if = signs were used', function () {
+    var parse = yargs.parse(['--host=localhost', '--port=555'])
+    parse.should.have.property('host', 'localhost')
+    parse.should.have.property('port', 555)
+    parse.should.have.property('_').with.length(0)
+  })
+
+  it('should still set values appropriately if a mix of short, long, and grouped short options are specified', function () {
+    var parse = yargs.parse(['-h', 'localhost', '-fp', '555', 'script.js'])
+    parse.should.have.property('f', true)
+    parse.should.have.property('p', 555)
+    parse.should.have.property('h', 'localhost')
+    parse.should.have.property('_').and.deep.equal(['script.js'])
+  })
+
+  it('should still set values appropriately if a mix of short and long options are specified', function () {
+    var parse = yargs.parse(['-h', 'localhost', '--port', '555'])
+    parse.should.have.property('h', 'localhost')
+    parse.should.have.property('port', 555)
+    parse.should.have.property('_').with.length(0)
+  })
+
+  it('should explicitly set a boolean option to false if preceeded by "--no-"', function () {
+    var parse = yargs.parse(['--no-moo'])
+    parse.should.have.property('moo', false)
+    parse.should.have.property('_').with.length(0)
+  })
+
+  it('should group values into an array if the same option is specified multiple times', function () {
+    var parse = yargs.parse(['-v', 'a', '-v', 'b', '-v', 'c'])
+    parse.should.have.property('v').and.deep.equal(['a', 'b', 'c'])
+    parse.should.have.property('_').with.length(0)
+  })
+
+  it('should still set values appropriately if we supply a comprehensive list of various types of options', function () {
+    var parse = yargs.parse([
+      '--name=meowmers', 'bare', '-cats', 'woo',
+      '-h', 'awesome', '--multi=quux',
+      '--key', 'value',
+      '-b', '--bool', '--no-meep', '--multi=baz',
+      '--', '--not-a-flag', '-', '-h', '-multi', '--', 'eek'
+    ])
+    parse.should.have.property('c', true)
+    parse.should.have.property('a', true)
+    parse.should.have.property('t', true)
+    parse.should.have.property('s', 'woo')
+    parse.should.have.property('h', 'awesome')
+    parse.should.have.property('b', true)
+    parse.should.have.property('bool', true)
+    parse.should.have.property('key', 'value')
+    parse.should.have.property('multi').and.deep.equal(['quux', 'baz'])
+    parse.should.have.property('meep', false)
+    parse.should.have.property('name', 'meowmers')
+    parse.should.have.property('_').and.deep.equal(['bare', '--not-a-flag', '-', '-h', '-multi', '--', 'eek'])
+  })
+
+  it('should parse numbers appropriately', function () {
+    var argv = yargs.parse([
+      '-x', '1234',
+      '-y', '5.67',
+      '-z', '1e7',
+      '-w', '10f',
+      '--hex', '0xdeadbeef',
+      '789'
+    ])
+    argv.should.have.property('x', 1234).and.be.a('number')
+    argv.should.have.property('y', 5.67).and.be.a('number')
+    argv.should.have.property('z', 1e7).and.be.a('number')
+    argv.should.have.property('w', '10f').and.be.a('string')
+    argv.should.have.property('hex', 0xdeadbeef).and.be.a('number')
+    argv.should.have.property('_').and.deep.equal([789])
+    argv._[0].should.be.a('number')
+  })
+
+  it('should not set the next value as the value of a short option if that option is explicitly defined as a boolean', function () {
+    var parse = yargs([ '-t', 'moo' ]).boolean(['t']).argv
+    parse.should.have.property('t', true).and.be.a('boolean')
+    parse.should.have.property('_').and.deep.equal(['moo'])
+  })
+
+  it('should set boolean options values if the next value is "true" or "false"', function () {
+    var parse = yargs(['--verbose', 'false', 'moo', '-t', 'true'])
+    .boolean(['t', 'verbose']).default('verbose', true).argv
+    parse.should.have.property('verbose', false).and.be.a('boolean')
+    parse.should.have.property('t', true).and.be.a('boolean')
+    parse.should.have.property('_').and.deep.equal(['moo'])
+  })
+
+  it('should allow defining options as boolean in groups', function () {
+    var parse = yargs([ '-x', '-z', 'one', 'two', 'three' ])
+    .boolean(['x', 'y', 'z']).argv
+    parse.should.have.property('x', true).and.be.a('boolean')
+    parse.should.have.property('y', false).and.be.a('boolean')
+    parse.should.have.property('z', true).and.be.a('boolean')
+    parse.should.have.property('_').and.deep.equal(['one', 'two', 'three'])
+  })
+
+  it('should preserve newlines in option values', function () {
+    var args = yargs.parse(['-s', 'X\nX'])
+    args.should.have.property('_').with.length(0)
+    args.should.have.property('s', 'X\nX')
+    // reproduce in bash:
+    // VALUE="new
+    // line"
+    // node program.js --s="$VALUE"
+    args = yargs.parse(['--s=X\nX'])
+    args.should.have.property('_').with.length(0)
+    args.should.have.property('s', 'X\nX')
+  })
+
+  it('should not convert numbers to type number if explicitly defined as strings', function () {
+    var s = yargs([ '-s', '0001234' ]).string('s').argv.s
+    s.should.be.a('string').and.equal('0001234')
+    var x = yargs([ '-x', '56' ]).string('x').argv.x
+    x.should.be.a('string').and.equal('56')
+  })
+
+  // Fixes: https://github.com/chevex/yargs/issues/68
+  it('should parse flag arguments with no right-hand-value as strings, if defined as strings', function () {
+    var s = yargs([ '-s' ]).string('s').argv.s
+    s.should.be.a('string').and.equal('')
+
+    s = yargs([ '-sf' ]).string('s').argv.s
+    s.should.be.a('string').and.equal('')
+
+    s = yargs([ '--string' ]).string('string').argv.string
+    s.should.be.a('string').and.equal('')
+  })
+
+  it('should leave all non-hyphenated values as strings if _ is defined as a string', function () {
+    var s = yargs([ '  ', '  ' ]).string('_').argv._
+    s.should.have.length(2)
+    s[0].should.be.a('string').and.equal('  ')
+    s[1].should.be.a('string').and.equal('  ')
+  })
+
+  it('should normalize redundant paths', function () {
+    var a = yargs([ '-s', ['', 'tmp', '..', ''].join(path.sep) ]).alias('s', 'save').normalize('s').argv
+    a.should.have.property('s', path.sep)
+    a.should.have.property('save', path.sep)
+  })
+
+  it('should normalize redundant paths when a value is later assigned', function () {
+    var a = yargs(['-s']).normalize('s').argv
+    a.should.have.property('s', true)
+    a.s = ['', 'path', 'to', 'new', 'dir', '..', '..', ''].join(path.sep)
+    a.s.should.equal(['', 'path', 'to', ''].join(path.sep))
+  })
+
+  it('should assign data after forward slash to the option before the slash', function () {
+    var parse = yargs.parse(['-I/foo/bar/baz'])
+    parse.should.have.property('_').with.length(0)
+    parse.should.have.property('I', '/foo/bar/baz')
+    parse = yargs.parse(['-xyz/foo/bar/baz'])
+    parse.should.have.property('x', true)
+    parse.should.have.property('y', true)
+    parse.should.have.property('z', '/foo/bar/baz')
+    parse.should.have.property('_').with.length(0)
+  })
+
+  it('should set alias value to the same value as the full option', function () {
+    var argv = yargs([ '-f', '11', '--zoom', '55' ])
+    .alias('z', 'zoom')
+    .argv
+    argv.should.have.property('zoom', 55)
+    argv.should.have.property('z', 55)
+    argv.should.have.property('f', 11)
+  })
+
+  describe('config', function () {
+    var jsonPath = path.resolve(__dirname, './fixtures/config.json')
+
+    // See: https://github.com/chevex/yargs/issues/12
+    it('should load options and values from default config if specified', function () {
+      var argv = yargs([ '--foo', 'bar' ])
+      .alias('z', 'zoom')
+      .config('settings')
+      .default('settings', jsonPath)
+      .argv
+
+      argv.should.have.property('herp', 'derp')
+      argv.should.have.property('zoom', 55)
+      argv.should.have.property('foo').and.deep.equal('bar')
+    })
+
+    it('should use value from config file, if argv value is using default value', function () {
+      var argv = yargs([])
+      .alias('z', 'zoom')
+      .config('settings')
+      .default('settings', jsonPath)
+      .default('foo', 'banana')
+      .argv
+
+      argv.should.have.property('herp', 'derp')
+      argv.should.have.property('zoom', 55)
+      argv.should.have.property('foo').and.deep.equal('baz')
+    })
+
+    it('should use value from config file, if argv key is a boolean', function () {
+      var argv = yargs([])
+      .config('settings')
+      .default('settings', jsonPath)
+      .boolean('truthy')
+      .argv
+
+      argv.should.have.property('truthy', true)
+    })
+
+    it('should use value from cli, if cli overrides boolean argv key', function () {
+      var argv = yargs(['--no-truthy'])
+      .config('settings')
+      .default('settings', jsonPath)
+      .boolean('truthy')
+      .argv
+
+      argv.should.have.property('truthy', false)
+    })
+
+    it('should use cli value, if cli value is set and both cli and default value match', function () {
+      var argv = yargs(['--foo', 'banana'])
+      .alias('z', 'zoom')
+      .config('settings')
+      .default('settings', jsonPath)
+      .default('foo', 'banana')
+      .argv
+
+      argv.should.have.property('herp', 'derp')
+      argv.should.have.property('zoom', 55)
+      argv.should.have.property('foo').and.deep.equal('banana')
+    })
+
+    it('should load options and values from a file when config is used', function () {
+      var argv = yargs([ '--settings', jsonPath, '--foo', 'bar' ])
+        .alias('z', 'zoom')
+        .config('settings')
+        .argv
+
+      argv.should.have.property('herp', 'derp')
+      argv.should.have.property('zoom', 55)
+      argv.should.have.property('foo').and.deep.equal('bar')
+    })
+
+    it("should allow config to be set as flag in 'option'", function () {
+      var argv = yargs([ '--settings', jsonPath, '--foo', 'bar' ])
+        .alias('z', 'zoom')
+        .option('settings', {
+          config: true
+        })
+        .argv
+
+      argv.should.have.property('herp', 'derp')
+      argv.should.have.property('zoom', 55)
+      argv.should.have.property('foo').and.deep.equal('bar')
+    })
+
+    it('should load options and values from a JS file when config has .js extention', function () {
+      var jsPath = path.resolve(__dirname, './fixtures/settings.js')
+      var argv = yargs([ '--settings', jsPath, '--foo', 'bar' ])
+      .config('settings')
+      .argv
+
+      argv.should.have.property('herp', 'derp')
+      argv.should.have.property('foo', 'bar')
+      argv.should.have.property('calculate').and.be.a('function')
+    })
+
+    it('should raise an appropriate error if JSON file is not found', function (done) {
+      yargs(['--settings', 'fake.json', '--foo', 'bar'])
+        .alias('z', 'zoom')
+        .config('settings')
+        .fail(function (msg) {
+          msg.should.eql('Invalid JSON config file: fake.json')
+          return done()
+        })
+        .argv
+    })
+
+    // see: https://github.com/bcoe/yargs/issues/172
+    it('should not raise an exception if config file is set as default argument value', function () {
+      var fail = false
+      yargs([])
+        .option('config', {
+          default: 'foo.json'
+        })
+        .config('config')
+        .fail(function () {
+          fail = true
+        })
+        .argv
+
+      fail.should.equal(false)
+    })
+
+    it('should be displayed in the help message', function () {
+      var checkUsage = require('./helpers/utils').checkOutput
+      var r = checkUsage(function () {
+        return yargs(['--help'])
+          .config('settings')
+          .help('help')
+          .wrap(null)
+          .argv
+      })
+      r.should.have.property('logs').with.length(1)
+      r.logs.join('\n').split(/\n+/).should.deep.equal([
+        'Options:',
+        '  --settings  Path to JSON config file',
+        '  --help      Show help  [boolean]',
+        ''
+      ])
+    })
+
+    it('should allow help message to be overridden', function () {
+      var checkUsage = require('./helpers/utils').checkOutput
+      var r = checkUsage(function () {
+        return yargs(['--help'])
+          .config('settings', 'pork chop sandwiches')
+          .help('help')
+          .wrap(null)
+          .argv
+      })
+      r.should.have.property('logs').with.length(1)
+      r.logs.join('\n').split(/\n+/).should.deep.equal([
+        'Options:',
+        '  --settings  pork chop sandwiches',
+        '  --help      Show help  [boolean]',
+        ''
+      ])
+    })
+
+    it('allows a custom parsing function to be provided', function () {
+      var jsPath = path.resolve(__dirname, './fixtures/config.txt')
+      var argv = yargs([ '--settings', jsPath, '--foo', 'bar' ])
+        .config('settings', function (configPath) {
+          // as an example, parse an environment
+          // variable style config:
+          // FOO=99
+          // BATMAN=grumpy
+          var config = {}
+          var txt = fs.readFileSync(configPath, 'utf-8')
+          txt.split(/\r?\n/).forEach(function (l) {
+            var kv = l.split('=')
+            config[kv[0].toLowerCase()] = kv[1]
+          })
+          return config
+        })
+        .argv
+
+      argv.batman.should.equal('grumpy')
+      argv.awesome.should.equal('banana')
+      argv.foo.should.equal('bar')
+    })
+
+    it('allows a custom parsing function to be provided as an alias', function () {
+      var jsPath = path.resolve(__dirname, './fixtures/config.json')
+      var argv = yargs([ '--settings', jsPath, '--foo', 'bar' ])
+        .config('s', function (configPath) {
+          return JSON.parse(fs.readFileSync(configPath, 'utf-8'))
+        })
+        .alias('s', 'settings')
+        .argv
+
+      argv.should.have.property('herp', 'derp')
+      argv.should.have.property('foo', 'bar')
+    })
+
+    it('allows a custom parsing function to be provided using option shorthand', function () {
+      var jsPath = path.resolve(__dirname, './fixtures/config.json')
+      var argv = yargs([ '--settings', jsPath, '--foo', 'bar' ])
+        .option('s', {
+          alias: 'settings',
+          config: true,
+          configParser: function (configPath) {
+            return JSON.parse(fs.readFileSync(configPath, 'utf-8'))
+          }
+        })
+        .argv
+
+      argv.should.have.property('herp', 'derp')
+      argv.should.have.property('foo', 'bar')
+    })
+
+    it('outputs an error returned by the parsing function', function () {
+      var checkUsage = require('./helpers/utils').checkOutput
+      var r = checkUsage(function () {
+        return yargs(['--settings=./package.json'])
+          .config('settings', 'path to config file', function (configPath) {
+            return Error('someone set us up the bomb')
+          })
+          .help('help')
+          .wrap(null)
+          .argv
+      })
+
+      r.errors.join('\n').split(/\n+/).should.deep.equal([
+        'Options:',
+        '  --settings  path to config file',
+        '  --help      Show help  [boolean]',
+        'someone set us up the bomb'
+      ])
+    })
+
+    it('outputs an error if thrown by the parsing function', function () {
+      var checkUsage = require('./helpers/utils').checkOutput
+      var r = checkUsage(function () {
+        return yargs(['--settings=./package.json'])
+          .config('settings', 'path to config file', function (configPath) {
+            throw Error('someone set us up the bomb')
+          })
+          .help('help')
+          .wrap(null)
+          .argv
+      })
+
+      r.errors.join('\n').split(/\n+/).should.deep.equal([
+        'Options:',
+        '  --settings  path to config file',
+        '  --help      Show help  [boolean]',
+        'someone set us up the bomb'
+      ])
+    })
+  })
+
+  it('should allow multiple aliases to be specified', function () {
+    var argv = yargs([ '-f', '11', '--zoom', '55' ])
+    .alias('z', [ 'zm', 'zoom' ])
+    .argv
+    argv.should.have.property('zoom', 55)
+    argv.should.have.property('z', 55)
+    argv.should.have.property('zm', 55)
+    argv.should.have.property('f', 11)
+  })
+
+  it('should allow transitive aliases to be specified', function () {
+    var argv = yargs([ '-f', '11', '--zoom', '55' ])
+      .alias('z', 'zm')
+      .alias('zm', 'zoom')
+      .argv
+
+    argv.should.have.property('zoom', 55)
+    argv.should.have.property('z', 55)
+    argv.should.have.property('zm', 55)
+    argv.should.have.property('f', 11)
+  })
+
+  it('should merge two lists of aliases if they collide', function () {
+    var argv = yargs([ '-f', '11', '--zoom', '55' ])
+      .alias('z', 'zm')
+      .alias('zoom', 'zoop')
+      .alias('zm', 'zoom')
+      .argv
+
+    argv.should.have.property('zoom', 55)
+    argv.should.have.property('zoop', 55)
+    argv.should.have.property('z', 55)
+    argv.should.have.property('zm', 55)
+    argv.should.have.property('f', 11)
+  })
+
+  describe('dot notation', function () {
+    it('should allow object graph traversal via dot notation', function () {
+      var argv = yargs([
+        '--foo.bar', '3', '--foo.baz', '4',
+        '--foo.quux.quibble', '5', '--foo.quux.o_O',
+        '--beep.boop'
+      ]).argv
+      argv.should.have.property('foo').and.deep.equal({
+        bar: 3,
+        baz: 4,
+        quux: {
+          quibble: 5,
+          o_O: true
+        }
+      })
+      argv.should.have.property('beep').and.deep.equal({ boop: true })
+    })
+
+    it('should apply defaults to dot notation arguments', function () {
+      var argv = yargs([])
+        .default('foo.bar', 99)
+        .argv
+      argv.foo.bar.should.eql(99)
+    })
+
+    // see #279
+    it('should allow default to be overridden when an alias is provided', function () {
+      var argv = yargs(['--foo.bar', '200'])
+        .option('foo.bar', {
+          default: 99,
+          alias: 'f'
+        })
+        .argv
+
+      argv.foo.bar.should.eql(200)
+    })
+
+    // see #279
+    it('should also override alias', function () {
+      var argv = yargs(['--foo.bar', '200'])
+        .option('foo.bar', {
+          default: 99,
+          alias: 'f'
+        })
+        .argv
+
+      argv.f.should.eql(200)
+    })
+
+    // see #279
+    it('should not set an undefined dot notation key', function () {
+      var argv = yargs(['--foo.bar', '200'])
+        .option('foo.bar', {
+          default: 99,
+          alias: 'f'
+        })
+        .argv
+
+      ;('foo.bar' in argv).should.equal(false)
+    })
+
+    it('should respect .string() for dot notation arguments', function () {
+      var argv = yargs(['--foo.bar', '99', '--bar.foo=99'])
+      .string('foo.bar')
+      .argv
+      argv.foo.bar.should.eql('99')
+      argv.bar.foo.should.eql(99)
+    })
+
+    it('should populate aliases when dot notation is used', function () {
+      var argv = yargs(['--foo.bar', '99'])
+      .alias('foo', 'f')
+      .argv
+      argv.f.bar.should.eql(99)
+    })
+
+    it('should populate aliases when nested dot notation is used', function () {
+      var argv = yargs(['--foo.bar.snuh', '99', '--foo.apple', '33', '--foo.bar.cool', '11'])
+      .alias('foo', 'f')
+      .argv
+      argv.f.bar.snuh.should.eql(99)
+      argv.foo.bar.snuh.should.eql(99)
+
+      argv.f.apple.should.eql(33)
+      argv.foo.apple.should.eql(33)
+
+      argv.f.bar.cool.should.eql(11)
+      argv.foo.bar.cool.should.eql(11)
+    })
+
+    it("should allow flags to use dot notation, when seperated by '='", function () {
+      var argv = yargs(['-f.foo=99'])
+      .argv
+      argv.f.foo.should.eql(99)
+    })
+
+    it("should allow flags to use dot notation, when seperated by ' '", function () {
+      var argv = yargs(['-f.foo', '99'])
+      .argv
+      argv.f.foo.should.eql(99)
+    })
+
+    it('should allow flags to use dot notation when no right-hand-side is given', function () {
+      var argv = yargs(['-f.foo', '99', '-f.bar'])
+      .argv
+
+      argv.f.foo.should.eql(99)
+      argv.f.bar.should.eql(true)
+    })
+  })
+
+  it('should allow booleans and aliases to be defined with chainable api', function () {
+    var aliased = [ '-h', 'derp' ]
+    var regular = [ '--herp', 'derp' ]
+    var aliasedArgv = yargs(aliased).boolean('herp').alias('h', 'herp').argv
+    var propertyArgv = yargs(regular).boolean('herp').alias('h', 'herp').argv
+
+    aliasedArgv.should.have.property('herp', true)
+    aliasedArgv.should.have.property('h', true)
+    aliasedArgv.should.have.property('_').and.deep.equal(['derp'])
+    propertyArgv.should.have.property('herp', true)
+    propertyArgv.should.have.property('h', true)
+    propertyArgv.should.have.property('_').and.deep.equal(['derp'])
+  })
+
+  it('should allow booleans and aliases to be defined with options hash', function () {
+    var aliased = [ '-h', 'derp' ]
+    var regular = [ '--herp', 'derp' ]
+    var opts = {
+      herp: { alias: 'h', boolean: true }
+    }
+    var aliasedArgv = yargs(aliased).options(opts).argv
+    var propertyArgv = yargs(regular).options(opts).argv
+
+    aliasedArgv.should.have.property('herp', true)
+    aliasedArgv.should.have.property('h', true)
+    aliasedArgv.should.have.property('_').and.deep.equal(['derp'])
+    propertyArgv.should.have.property('herp', true)
+    propertyArgv.should.have.property('h', true)
+    propertyArgv.should.have.property('_').and.deep.equal(['derp'])
+  })
+
+  it('should set boolean and alias using explicit true', function () {
+    var aliased = [ '-h', 'true' ]
+    var aliasedArgv = yargs(aliased).boolean('h').alias('h', 'herp').argv
+
+    aliasedArgv.should.have.property('herp', true)
+    aliasedArgv.should.have.property('h', true)
+    aliasedArgv.should.have.property('_').with.length(0)
+  })
+
+  // regression, see https://github.com/substack/node-optimist/issues/71
+  it('should set boolean and --x=true', function () {
+    var parsed = yargs(['--boool', '--other=true']).boolean('boool').argv
+    parsed.should.have.property('boool', true)
+    parsed.should.have.property('other', 'true')
+    parsed = yargs(['--boool', '--other=false']).boolean('boool').argv
+    parsed.should.have.property('boool', true)
+    parsed.should.have.property('other', 'false')
+  })
+
+  // regression, see https://github.com/chevex/yargs/issues/63
+  it('should not add the same key to argv multiple times, when creating camel-case aliases', function () {
+    var yargs = require('../')(['--health-check=banana', '--second-key', 'apple', '-t=blarg'])
+    .options('h', {
+      alias: 'health-check',
+      description: 'health check',
+      default: 'apple'
+    })
+    .options('second-key', {
+      alias: 's',
+      description: 'second key',
+      default: 'banana'
+    })
+    .options('third-key', {
+      alias: 't',
+      description: 'third key',
+      default: 'third'
+    })
+
+    // before this fix, yargs failed parsing
+    // one but not all forms of an arg.
+    yargs.argv.secondKey.should.eql('apple')
+    yargs.argv.s.should.eql('apple')
+    yargs.argv['second-key'].should.eql('apple')
+
+    yargs.argv.healthCheck.should.eql('banana')
+    yargs.argv.h.should.eql('banana')
+    yargs.argv['health-check'].should.eql('banana')
+
+    yargs.argv.thirdKey.should.eql('blarg')
+    yargs.argv.t.should.eql('blarg')
+    yargs.argv['third-key'].should.eql('blarg')
+  })
+
+  // regression, see https://github.com/chevex/yargs/issues/66
+  it('should set boolean options values if next value is "true" or "false" with = as separator', function () {
+    var argv = require('../')(['--bool=false'])
+    .options({'b': {
+      alias: 'bool',
+      boolean: true,
+      default: true
+    }})
+    .argv
+
+    argv.bool.should.eql(false)
+  })
+
+  describe('short options', function () {
+    it('should set n to the numeric value 123', function () {
+      var argv = yargs.parse([ '-n123' ])
+      should.exist(argv)
+      argv.should.have.property('n', 123)
+    })
+
+    it('should set option "1" to true, option "2" to true, and option "3" to numeric value 456', function () {
+      var argv = yargs.parse([ '-123', '456' ])
+      should.exist(argv)
+      argv.should.have.property('1', true)
+      argv.should.have.property('2', true)
+      argv.should.have.property('3', 456)
+    })
+  })
+
+  describe('whitespace', function () {
+    it('should be whitespace', function () {
+      var argv = yargs.parse([ '-x', '\t' ])
+      should.exist(argv)
+      argv.should.have.property('x', '\t')
+    })
+  })
+
+  describe('boolean modifier function', function () {
+    it('should prevent yargs from sucking in the next option as the value of the first option', function () {
+      // Arrange & Act
+      var result = yargs().boolean('b').parse([ '-b', '123' ])
+      // Assert
+      result.should.have.property('b').that.is.a('boolean').and.is.true
+      result.should.have.property('_').and.deep.equal([123])
+    })
+  })
+
+  describe('defaults', function () {
+    function checkNoArgs (argv, hasAlias) {
+      it('should set defaults if no args', function () {
+        var result = argv.parse([ ])
+        result.should.have.property('flag', true)
+        if (hasAlias) {
+          result.should.have.property('f', true)
+        }
+      })
+    }
+
+    function checkExtraArg (argv, hasAlias) {
+      it('should set defaults if one extra arg', function () {
+        var result = argv.parse([ 'extra' ])
+        result.should.have.property('flag', true)
+        result.should.have.property('_').and.deep.equal(['extra'])
+        if (hasAlias) {
+          result.should.have.property('f', true)
+        }
+      })
+    }
+
+    function checkStringArg (argv, hasAlias) {
+      it('should set defaults even if arg looks like a string', function () {
+        var result = argv.parse([ '--flag', 'extra' ])
+        result.should.have.property('flag', true)
+        result.should.have.property('_').and.deep.equal(['extra'])
+        if (hasAlias) {
+          result.should.have.property('f', true)
+        }
+      })
+    }
+
+    describe('for options with aliases', function () {
+      var args = yargs().options({
+        flag: {
+          alias: 'f',
+          default: true
+        }
+      })
+
+      checkNoArgs(args, true)
+      checkExtraArg(args, true)
+    })
+
+    describe('for typed options without aliases', function () {
+      var args = yargs().options({
+        flag: {
+          type: 'boolean',
+          default: true
+        }
+      })
+
+      checkNoArgs(args)
+      checkExtraArg(args)
+      checkStringArg(args)
+    })
+
+    describe('for typed options with aliases', function () {
+      var args = yargs().options({
+        flag: {
+          alias: 'f',
+          type: 'boolean',
+          default: true
+        }
+      })
+
+      checkNoArgs(args, true)
+      checkExtraArg(args, true)
+      checkStringArg(args, true)
+    })
+
+    describe('for boolean options', function () {
+      [true, false, undefined].forEach(function (def) {
+        describe('with explicit ' + def + ' default', function () {
+          var argv = yargs().options({
+            flag: {
+              type: 'boolean',
+              default: def
+            }
+          })
+          var argv2 = yargs()
+            .boolean(['flag'])
+            .default('flag', def)
+
+          it('should set true if --flag in arg', function () {
+            argv.parse(['--flag']).flag.should.be.true
+            argv2.parse(['--flag']).flag.should.be.true
+          })
+
+          it('should set false if --no-flag in arg', function () {
+            argv.parse(['--no-flag']).flag.should.be.false
+            argv2.parse(['--no-flag']).flag.should.be.false
+          })
+
+          it('should set ' + def + ' if no flag in arg', function () {
+            should.equal(argv.parse([ ]).flag, def)
+            should.equal(argv2.parse([ ]).flag, def)
+          })
+        })
+      })
+
+      describe('with implied false default', function () {
+        var argv = null
+        var argv2 = null
+
+        beforeEach(function () {
+          argv = yargs().options({
+            flag: {type: 'boolean'}
+          })
+
+          argv2 = yargs().boolean(['flag'])
+        })
+
+        it('should set true if --flag in arg', function () {
+          argv.parse(['--flag']).flag.should.be.true
+          argv2.parse(['--flag']).flag.should.be.true
+        })
+
+        it('should set false if --no-flag in arg', function () {
+          argv.parse(['--no-flag']).flag.should.be.false
+          argv2.parse(['--no-flag']).flag.should.be.false
+        })
+
+        it('should set false if no flag in arg', function () {
+          argv.parse([ ]).flag.should.be.false
+          argv2.parse([ ]).flag.should.be.false
+        })
+      })
+
+      // Fixes: https://github.com/bcoe/yargs/issues/341
+      it('should apply defaults to camel-case form of argument', function () {
+        var argv = yargs([])
+          .default('foo-bar', 99)
+          .argv
+
+        argv.fooBar.should.equal(99)
+      })
+    })
+
+    it('should define option as boolean and set default to true', function () {
+      var argv = yargs.options({
+        sometrue: {
+          boolean: true,
+          default: true
+        }
+      }).argv
+      argv.should.have.property('sometrue', true)
+    })
+
+    it('should define option as boolean and set default to false', function () {
+      var argv = yargs.options({
+        somefalse: {
+          boolean: true,
+          default: false
+        }
+      }).argv
+      argv.should.have.property('somefalse', false)
+    })
+
+    it('should set boolean options to false by default', function () {
+      var parse = yargs(['moo'])
+      .boolean(['t', 'verbose'])
+      .default('verbose', false)
+      .default('t', false).argv
+      parse.should.have.property('verbose', false).and.be.a('boolean')
+      parse.should.have.property('t', false).and.be.a('boolean')
+      parse.should.have.property('_').and.deep.equal(['moo'])
+    })
+
+    it('should allow function to be provided as default value', function () {
+      var argv = yargs([])
+      .default('file', function () {
+        return 'foo.txt'
+      })
+      .argv
+
+      argv.file.should.equal('foo.txt')
+    })
+  })
+
+  describe('camelCase', function () {
+    function runTests (yargs, strict) {
+      if (!strict) {
+        // Skip this test in strict mode because this option is not specified
+        it('should provide options with dashes as camelCase properties', function () {
+          var result = yargs()
+            .parse(['--some-option'])
+
+          result.should.have.property('some-option').that.is.a('boolean').and.is.true
+          result.should.have.property('someOption').that.is.a('boolean').and.is.true
+        })
+      }
+
+      it('should provide count options with dashes as camelCase properties', function () {
+        var result = yargs()
+        .option('some-option', {
+          describe: 'some option',
+          type: 'count'
+        })
+        .parse([ '--some-option', '--some-option', '--some-option' ])
+
+        result.should.have.property('some-option', 3)
+        result.should.have.property('someOption', 3)
+      })
+
+      it('should provide options with dashes and aliases as camelCase properties', function () {
+        var result = yargs()
+        .option('some-option', {
+          alias: 'o',
+          describe: 'some option'
+        })
+        .parse(['--some-option'])
+
+        result.should.have.property('some-option').that.is.a('boolean').and.is.true
+        result.should.have.property('someOption').that.is.a('boolean').and.is.true
+      })
+
+      it('should provide defaults of options with dashes as camelCase properties', function () {
+        var result = yargs()
+        .option('some-option', {
+          describe: 'some option',
+          default: 'asdf'
+        })
+        .parse([ ])
+
+        result.should.have.property('some-option', 'asdf')
+        result.should.have.property('someOption', 'asdf')
+      })
+
+      it('should provide aliases of options with dashes as camelCase properties', function () {
+        var result = yargs()
+        .option('some-option', {
+          alias: 'o',
+          describe: 'some option',
+          default: 'asdf'
+        })
+        .parse([ ])
+
+        result.should.have.property('o', 'asdf')
+        result.should.have.property('some-option', 'asdf')
+        result.should.have.property('someOption', 'asdf')
+      })
+
+      it('should provide aliases of options with dashes as camelCase properties', function () {
+        var result = yargs()
+        .option('o', {
+          alias: 'some-option',
+          describe: 'some option',
+          default: 'asdf'
+        })
+        .parse([ ])
+
+        result.should.have.property('o', 'asdf')
+        result.should.have.property('some-option', 'asdf')
+        result.should.have.property('someOption', 'asdf')
+      })
+
+      it('should provide aliases with dashes as camelCase properties', function () {
+        var result = yargs()
+        .option('o', {
+          alias: 'some-option',
+          describe: 'some option'
+        })
+        .parse(['--some-option', 'val'])
+
+        result.should.have.property('o').that.is.a('string').and.equals('val')
+        result.should.have.property('some-option').that.is.a('string').and.equals('val')
+        result.should.have.property('someOption').that.is.a('string').and.equals('val')
+      })
+    }
+
+    describe('dashes and camelCase', function () {
+      runTests(function () {
+        return yargs()
+      })
+    })
+
+    describe('dashes and camelCase (strict)', function () {
+      runTests(function () {
+        // Special handling for failure messages, because normally a
+        // failure calls process.exit(1)
+        return yargs().strict().fail(function (msg) {
+          throw new Error(msg)
+        })
+      }, true)
+
+      // See https://github.com/chevex/yargs/issues/31
+      it('should not fail when options with defaults are missing', function () {
+        yargs()
+          .fail(function (msg) {
+            throw new Error(msg)
+          })
+          .option('some-option', {
+            describe: 'some option',
+            default: 80
+          })
+          .strict()
+          .parse([])
+      })
+    })
+  })
+
+  describe('-', function () {
+    it('should set - as value of n', function () {
+      var argv = yargs.parse(['-n', '-'])
+      argv.should.have.property('n', '-')
+      argv.should.have.property('_').with.length(0)
+    })
+
+    it('should set - as a non-hyphenated value', function () {
+      var argv = yargs.parse(['-'])
+      argv.should.have.property('_').and.deep.equal(['-'])
+    })
+
+    it('should set - as a value of f', function () {
+      var argv = yargs.parse(['-f-'])
+      argv.should.have.property('f', '-')
+      argv.should.have.property('_').with.length(0)
+    })
+
+    it('should set b to true and set - as a non-hyphenated value when b is set as a boolean', function () {
+      var argv = yargs(['-b', '-']).boolean('b').argv
+      argv.should.have.property('b', true)
+      argv.should.have.property('_').and.deep.equal(['-'])
+    })
+
+    it('should set - as the value of s when s is set as a string', function () {
+      var argv = yargs([ '-s', '-' ]).string('s').argv
+      argv.should.have.property('s', '-')
+      argv.should.have.property('_').with.length(0)
+    })
+  })
+
+  describe('count', function () {
+    it('should count the number of times a boolean is present', function () {
+      var parsed
+
+      parsed = yargs(['-x']).count('verbose').argv
+      parsed.verbose.should.equal(0)
+
+      parsed = yargs(['--verbose']).count('verbose').argv
+      parsed.verbose.should.equal(1)
+
+      parsed = yargs(['--verbose', '--verbose']).count('verbose').argv
+      parsed.verbose.should.equal(2)
+
+      parsed = yargs(['-vvv']).alias('v', 'verbose').count('verbose').argv
+      parsed.verbose.should.equal(3)
+
+      parsed = yargs(['--verbose', '--verbose', '-v', '--verbose']).count('verbose').alias('v', 'verbose').argv
+      parsed.verbose.should.equal(4)
+
+      parsed = yargs(['--verbose', '--verbose', '-v', '-vv']).count('verbose').alias('v', 'verbose').argv
+      parsed.verbose.should.equal(5)
+    })
+
+    it('should not consume the next argument', function () {
+      var parsed
+      parsed = yargs([ '-v', 'moo' ]).count('v').argv
+      parsed.v.should.equal(1)
+      parsed.should.have.property('_').and.deep.equal(['moo'])
+
+      parsed = yargs([ '--verbose', 'moomoo', '--verbose' ]).count('verbose').argv
+      parsed.verbose.should.equal(2)
+      parsed.should.have.property('_').and.deep.equal(['moomoo'])
+    })
+  })
+
+  describe('array', function () {
+    it('should default argument to empty array if no value given', function () {
+      var result = yargs().array('b').parse([ '-b' ])
+      Array.isArray(result.b).should.equal(true)
+    })
+
+    it('should place value of argument in array, when one argument provided', function () {
+      var result = yargs().array('b').parse(['-b', '33'])
+      Array.isArray(result.b).should.equal(true)
+      result.b[0].should.equal(33)
+    })
+
+    it('should add multiple argument values to the array', function () {
+      var result = yargs().array('b').parse(['-b', '33', '-b', 'hello'])
+      Array.isArray(result.b).should.equal(true)
+      result.b.should.include(33)
+      result.b.should.include('hello')
+    })
+
+    it('should allow array: true, to be set inside an option block', function () {
+      var result = yargs().option('b', {array: true}).parse(['-b', '33'])
+      Array.isArray(result.b).should.equal(true)
+      result.b.should.include(33)
+    })
+
+    // issue #103
+    it('should default camel-case alias to array type', function () {
+      var result = yargs().option('ca-path', {
+        array: true
+      }).parse([ '--ca-path', 'http://www.example.com' ])
+
+      Array.isArray(result['ca-path']).should.equal(true)
+      Array.isArray(result.caPath).should.equal(true)
+    })
+
+    it('should default alias to array type', function () {
+      var result = yargs().option('ca-path', {
+        array: true,
+        alias: 'c'
+      }).parse([ '--ca-path', 'http://www.example.com' ])
+
+      Array.isArray(result['ca-path']).should.equal(true)
+      Array.isArray(result.caPath).should.equal(true)
+      Array.isArray(result.c).should.equal(true)
+    })
+
+    // see: https://github.com/bcoe/yargs/issues/162
+    it('should eat non-hyphenated arguments until hyphenated option is hit', function () {
+      var result = yargs()
+        .option('a', {array: true})
+        .option('b', {array: true})
+        .option('foo', {array: true})
+        .option('bar', {array: true})
+        .parse(['-a=hello', 'world', '-b',
+          '33', '22', '--foo', 'red', 'green',
+          '--bar=cat', 'dog'])
+
+      Array.isArray(result.a).should.equal(true)
+      result.a.should.include('hello')
+      result.a.should.include('world')
+
+      Array.isArray(result.b).should.equal(true)
+      result.b.should.include(33)
+      result.b.should.include(22)
+
+      Array.isArray(result.foo).should.equal(true)
+      result.foo.should.include('red')
+      result.foo.should.include('green')
+
+      Array.isArray(result.bar).should.equal(true)
+      result.bar.should.include('cat')
+      result.bar.should.include('dog')
+    })
+  })
+
+  describe('nargs', function () {
+    it('should allow the number of arguments following a key to be specified', function () {
+      var result = yargs().nargs('foo', 2)
+      .parse([ '--foo', 'apple', 'bar' ])
+
+      Array.isArray(result.foo).should.equal(true)
+      result.foo[0].should.equal('apple')
+      result.foo[1].should.equal('bar')
+    })
+
+    it('should raise an exception if there are not enough arguments following key', function () {
+      expect(function () {
+        yargs().nargs('foo', 2)
+          .parse(['--foo', 'apple'])
+      }).to.throw('Not enough arguments following: foo')
+    })
+
+    it('nargs is applied to aliases', function () {
+      var result = yargs().nargs('foo', 2)
+        .alias('foo', 'bar')
+        .parse([ '--bar', 'apple', 'bar' ])
+
+      Array.isArray(result.foo).should.equal(true)
+      result.foo[0].should.equal('apple')
+      result.foo[1].should.equal('bar')
+    })
+
+    it('should apply nargs to flag arguments', function () {
+      var result = yargs()
+        .option('f', {
+          nargs: 2
+        }).parse([ '-f', 'apple', 'bar', 'blerg' ])
+
+      result.f[0].should.equal('apple')
+      result.f[1].should.equal('bar')
+      result._[0].should.equal('blerg')
+    })
+
+    it('should support nargs for -f= and --bar= format arguments', function () {
+      var result = yargs()
+        .option('f', {
+          nargs: 2
+        })
+        .option('bar', {
+          nargs: 2
+        })
+        .parse(['-f=apple', 'bar', 'blerg', '--bar=monkey', 'washing', 'cat'])
+
+      result.f[0].should.equal('apple')
+      result.f[1].should.equal('bar')
+      result._[0].should.equal('blerg')
+
+      result.bar[0].should.equal('monkey')
+      result.bar[1].should.equal('washing')
+      result._[1].should.equal('cat')
+    })
+
+    it('allows multiple nargs to be set at the same time', function () {
+      var result = yargs().nargs({
+        'foo': 2,
+        'bar': 1
+      })
+      .parse([ '--foo', 'apple', 'bar', '--bar', 'banana', '-f' ])
+
+      Array.isArray(result.foo).should.equal(true)
+      result.foo[0].should.equal('apple')
+      result.foo[1].should.equal('bar')
+      result.bar.should.equal('banana')
+      result.f.should.equal(true)
+    })
+  })
+
+  describe('env vars', function () {
+    it('should apply all env vars if prefix is empty', function () {
+      process.env.ONE_FISH = 'twofish'
+      process.env.RED_FISH = 'bluefish'
+      var result = yargs().env('').parse([])
+
+      result.oneFish.should.equal('twofish')
+      result.redFish.should.equal('bluefish')
+    })
+
+    it('should apply only env vars matching prefix if prefix is valid string', function () {
+      process.env.ONE_FISH = 'twofish'
+      process.env.RED_FISH = 'bluefish'
+      process.env.GREEN_EGGS = 'sam'
+      process.env.GREEN_HAM = 'iam'
+      var result = yargs().env('GREEN').parse([])
+
+      result.eggs.should.equal('sam')
+      result.ham.should.equal('iam')
+      expect(result.oneFish).to.be.undefined
+      expect(result.redFish).to.be.undefined
+    })
+
+    it('should set aliases for options defined by env var', function () {
+      process.env.AIRFORCE_ONE = 'two'
+      var result = yargs()
+        .env('AIRFORCE')
+        .option('1', {
+          alias: 'one'
+        })
+        .alias('1', 'uno')
+        .parse([])
+
+      result['1'].should.equal('two')
+      result.one.should.equal('two')
+      result.uno.should.equal('two')
+    })
+
+    it('should prefer command line value over env var', function () {
+      process.env.FOO_BAR = 'ignore'
+      var result = yargs().env().parse(['--foo-bar', 'baz'])
+
+      result.fooBar.should.equal('baz')
+    })
+
+    it('should respect type for args defined by env var', function () {
+      process.env.MY_TEST_STRING = '1'
+      process.env.MY_TEST_NUMBER = '2'
+      var result = yargs().string('string').env('MY_TEST_').parse([])
+
+      result.string.should.equal('1')
+      result.number.should.equal(2)
+    })
+
+    it('should ignore env vars if enabled and subsequently disabled', function () {
+      process.env.STATE = 'denial'
+      var result = yargs().env(true).env(false).parse([])
+
+      expect(result.state).to.be.undefined
+    })
+
+    it('should set option from aliased env var', function () {
+      process.env.SPACE_X = 'awesome'
+      var result = yargs()
+        .env('SPACE')
+        .alias('xactly', 'x')
+        .parse([])
+
+      result.xactly.should.equal('awesome')
+    })
+
+    it('should prefer env var value over configured default', function () {
+      process.env.FOO_BALL = 'wut'
+      process.env.FOO_BOOL = 'true'
+      var result = yargs()
+        .env('FOO')
+        .option('ball', {
+          type: 'string',
+          default: 'baz'
+        })
+        .option('bool', {
+          type: 'boolean',
+          default: false
+        })
+        .parse([])
+
+      result.ball.should.equal('wut')
+      result.bool.should.equal(true)
+    })
+
+    var jsonPath = path.resolve(__dirname, './fixtures/config.json')
+    it('should prefer config file value over env var', function () {
+      process.env.CFG_HERP = 'zerp'
+      var result = yargs()
+        .env('CFG')
+        .config('cfg')
+        .option('herp', {
+          type: 'string',
+          default: 'nerp'
+        })
+        .parse(['--cfg', jsonPath])
+
+      result.herp.should.equal('derp')
+    })
+
+    it('should support an env var value as config file option', function () {
+      process.env.TUX_CFG = jsonPath
+      var result = yargs()
+        .env('TUX')
+        .config('cfg')
+        .default('z', 44)
+        .parse([])
+
+      result.should.have.property('herp')
+      result.should.have.property('foo')
+      result.should.have.property('version')
+      result.should.have.property('truthy')
+      result.z.should.equal(55)
+    })
+
+    it('should prefer cli config file option over env var config file option', function () {
+      process.env.MUX_CFG = path.resolve(__dirname, '../package.json')
+      var result = yargs()
+        .env('MUX')
+        .config('cfg')
+        .parse(['--cfg', jsonPath])
+
+      result.should.have.property('herp')
+      result.should.have.property('foo')
+      result.should.have.property('version')
+      result.should.have.property('truthy')
+      result.z.should.equal(55)
+    })
+  })
+})
diff --git a/test/tokenize-arg-string.js b/test/tokenize-arg-string.js
new file mode 100644
index 0000000..9b9f9a0
--- /dev/null
+++ b/test/tokenize-arg-string.js
@@ -0,0 +1,40 @@
+/* global describe, it */
+
+var tokenizeArgString = require('../lib/tokenize-arg-string')
+
+require('chai').should()
+
+describe('TokenizeArgString', function () {
+  it('handles unquoted string', function () {
+    var args = tokenizeArgString('--foo 99')
+    args[0].should.equal('--foo')
+    args[1].should.equal('99')
+  })
+
+  it('handles quoted string with no spaces', function () {
+    var args = tokenizeArgString("--foo 'hello'")
+    args[0].should.equal('--foo')
+    args[1].should.equal('hello')
+  })
+
+  it('handles single quoted string with spaces', function () {
+    var args = tokenizeArgString("--foo 'hello world' --bar='foo bar'")
+    args[0].should.equal('--foo')
+    args[1].should.equal('hello world')
+    args[2].should.equal('--bar=foo bar')
+  })
+
+  it('handles double quoted string with spaces', function () {
+    var args = tokenizeArgString('--foo "hello world" --bar="foo bar"')
+    args[0].should.equal('--foo')
+    args[1].should.equal('hello world')
+    args[2].should.equal('--bar=foo bar')
+  })
+
+  it('handles quoted string with embeded quotes', function () {
+    var args = tokenizeArgString('--foo "hello \'world\'" --bar=\'foo "bar"\'')
+    args[0].should.equal('--foo')
+    args[1].should.equal('hello \'world\'')
+    args[2].should.equal('--bar=foo "bar"')
+  })
+})
diff --git a/test/usage.js b/test/usage.js
index bd94795..b01a39b 100644
--- a/test/usage.js
+++ b/test/usage.js
@@ -1,9 +1,9 @@
 /* global describe, it, beforeEach */
 
 var checkUsage = require('./helpers/utils').checkOutput
-var chalk = require('chalk')
 var path = require('path')
 var yargs = require('../')
+var chalk = require('chalk')
 
 require('chai').should()
 
@@ -862,7 +862,7 @@ describe('usage tests', function () {
     it('should display version', function () {
       var r = checkUsage(function () {
         return yargs(['--version'])
-        .version('version', 'Show version number', '1.0.1')
+        .version('1.0.1', 'version', 'Show version number')
         .wrap(null)
         .argv
       })
@@ -874,22 +874,12 @@ describe('usage tests', function () {
       r.logs[0].should.eql('1.0.1')
     })
 
-    it('accepts version option as first argument, and version number as second argument', function () {
-      var r = checkUsage(function () {
-        return yargs(['--version'])
-        .version('version', '1.0.0')
-        .wrap(null)
-        .argv
-      })
-      r.logs[0].should.eql('1.0.0')
-    })
-
     it('should allow a function to be provided, rather than a number', function () {
       var r = checkUsage(function () {
         return yargs(['--version'])
-        .version('version', function () {
+        .version(function () {
           return require('./fixtures/config').version
-        })
+        }, 'version')
         .wrap(null)
         .argv
       })
@@ -912,7 +902,7 @@ describe('usage tests', function () {
       it('should not validate arguments (required argument)', function () {
         var r = checkUsage(function () {
           return yargs(['--version'])
-            .version('version', 'Show version number', '1.0.1')
+            .version('1.0.1', 'version', 'Show version number')
             .demand('some-opt')
             .wrap(null)
             .exitProcess(false)
@@ -933,7 +923,7 @@ describe('usage tests', function () {
         var r = checkUsage(function () {
           return yargs(['--version', '--some-opt'])
             .nargs('some-opt', 3)
-            .version('version', 'Show version number', '1.0.1')
+            .version('1.0.1', 'version', 'Show version number')
             .wrap(null)
             .exitProcess(false)
             .argv
@@ -1007,13 +997,20 @@ describe('usage tests', function () {
     yargs.rebase(['home', 'chevex', 'foo'].join(path.sep), ['home', 'chevex', 'pow', 'zoom.txt'].join(path.sep)).should.equal(['..', 'pow', 'zoom.txt'].join(path.sep))
   })
 
-  // Fixes: https://github.com/chevex/yargs/issues/71
-  it('should not raise an exception if help called on empty arguments', function () {
+  it('should not print usage string if help() is called without arguments', function () {
+    var r = checkUsage(function () {
+      return yargs([]).usage('foo').help().argv
+    })
+
+    r.logs.length.should.equal(0)
+  })
+
+  it('should add --help as an option for printing usage text if help() is called without arguments', function () {
     var r = checkUsage(function () {
-      return yargs([]).usage('foo').help()
+      return yargs(['--help']).usage('foo').help().argv
     })
 
-    r.result.should.match(/foo/)
+    r.logs.length.should.not.equal(0)
   })
 
   describe('wrap', function () {
diff --git a/test/validation.js b/test/validation.js
index bd4800f..c1b6bf1 100644
--- a/test/validation.js
+++ b/test/validation.js
@@ -149,126 +149,4 @@ describe('validation tests', function () {
         .argv
     })
   })
-
-  describe('config', function () {
-    it('should raise an appropriate error if JSON file is not found', function (done) {
-      yargs(['--settings', 'fake.json', '--foo', 'bar'])
-        .alias('z', 'zoom')
-        .config('settings')
-        .fail(function (msg) {
-          msg.should.eql('Invalid JSON config file: fake.json')
-          return done()
-        })
-        .argv
-    })
-
-    // see: https://github.com/bcoe/yargs/issues/172
-    it('should not raise an exception if config file is set as default argument value', function () {
-      var fail = false
-      yargs([])
-        .option('config', {
-          default: 'foo.json'
-        })
-        .config('config')
-        .fail(function () {
-          fail = true
-        })
-        .argv
-
-      fail.should.equal(false)
-    })
-
-    it('should be displayed in the help message', function () {
-      var checkUsage = require('./helpers/utils').checkOutput
-      var r = checkUsage(function () {
-        return yargs(['--help'])
-          .config('settings')
-          .help('help')
-          .wrap(null)
-          .argv
-      })
-      r.should.have.property('logs').with.length(1)
-      r.logs.join('\n').split(/\n+/).should.deep.equal([
-        'Options:',
-        '  --settings  Path to JSON config file',
-        '  --help      Show help  [boolean]',
-        ''
-      ])
-    })
-
-    it('should allow help message to be overridden', function () {
-      var checkUsage = require('./helpers/utils').checkOutput
-      var r = checkUsage(function () {
-        return yargs(['--help'])
-          .config('settings', 'pork chop sandwiches')
-          .help('help')
-          .wrap(null)
-          .argv
-      })
-      r.should.have.property('logs').with.length(1)
-      r.logs.join('\n').split(/\n+/).should.deep.equal([
-        'Options:',
-        '  --settings  pork chop sandwiches',
-        '  --help      Show help  [boolean]',
-        ''
-      ])
-    })
-
-    it('outputs an error returned by the parsing function', function () {
-      var checkUsage = require('./helpers/utils').checkOutput
-      var r = checkUsage(function () {
-        return yargs(['--settings=./package.json'])
-          .config('settings', 'path to config file', function (configPath) {
-            return Error('someone set us up the bomb')
-          })
-          .help('help')
-          .wrap(null)
-          .argv
-      })
-
-      r.errors.join('\n').split(/\n+/).should.deep.equal([
-        'Options:',
-        '  --settings  path to config file',
-        '  --help      Show help  [boolean]',
-        'someone set us up the bomb'
-      ])
-    })
-
-    it('outputs an error if thrown by the parsing function', function () {
-      var checkUsage = require('./helpers/utils').checkOutput
-      var r = checkUsage(function () {
-        return yargs(['--settings=./package.json'])
-          .config('settings', 'path to config file', function (configPath) {
-            throw Error('someone set us up the bomb')
-          })
-          .help('help')
-          .wrap(null)
-          .argv
-      })
-
-      r.errors.join('\n').split(/\n+/).should.deep.equal([
-        'Options:',
-        '  --settings  path to config file',
-        '  --help      Show help  [boolean]',
-        'someone set us up the bomb'
-      ])
-    })
-  })
-
-  describe('defaults', function () {
-    // See https://github.com/chevex/yargs/issues/31
-    it('should not fail when demanded options with defaults are missing', function () {
-      yargs()
-        .fail(function (msg) {
-          throw new Error(msg)
-        })
-        .option('some-option', {
-          describe: 'some option',
-          demand: true,
-          default: 88
-        })
-        .strict()
-        .parse([])
-    })
-  })
 })
diff --git a/test/yargs.js b/test/yargs.js
index 6d6c5db..460ee10 100644
--- a/test/yargs.js
+++ b/test/yargs.js
@@ -1,8 +1,6 @@
 /* global describe, it, beforeEach */
 
 var expect = require('chai').expect
-var fs = require('fs')
-var path = require('path')
 var checkOutput = require('./helpers/utils').checkOutput
 var yargs = require('../')
 
@@ -576,89 +574,4 @@ describe('yargs dsl tests', function () {
       a2.why.should.equal('hello world')
     })
   })
-
-  describe('config', function () {
-    it('allows a parsing function to be provided as a second argument', function () {
-      var argv = yargs('--config ./test/fixtures/config.json')
-        .config('config', function (path) {
-          return JSON.parse(fs.readFileSync(path))
-        })
-        .argv
-
-      argv.foo.should.equal('baz')
-    })
-
-    it('allows key to be specified with option shorthand', function () {
-      var argv = yargs('--config ./test/fixtures/config.json')
-        .option('config', {
-          config: true
-        })
-        .argv
-
-      argv.foo.should.equal('baz')
-    })
-  })
-
-  describe('normalize', function () {
-    it('normalizes paths passed as arguments', function () {
-      var argv = yargs('--path /foo/bar//baz/asdf/quux/..')
-        .normalize(['path'])
-        .argv
-
-      argv.path.should.equal(['', 'foo', 'bar', 'baz', 'asdf'].join(path.sep))
-    })
-
-    it('normalizes path when when it is updated', function () {
-      var argv = yargs('--path /batman')
-        .normalize(['path'])
-        .argv
-
-      argv.path = '/foo/bar//baz/asdf/quux/..'
-      argv.path.should.equal(['', 'foo', 'bar', 'baz', 'asdf'].join(path.sep))
-    })
-
-    it('allows key to be specified with option shorthand', function () {
-      var argv = yargs('--path /batman')
-        .option('path', {
-          normalize: true
-        })
-        .argv
-
-      argv.path = '/foo/bar//baz/asdf/quux/..'
-      argv.path.should.equal(['', 'foo', 'bar', 'baz', 'asdf'].join(path.sep))
-    })
-  })
-
-  describe('narg', function () {
-    it('accepts a key as the first argument and a count as the second', function () {
-      var argv = yargs('--foo a b c')
-        .nargs('foo', 2)
-        .argv
-
-      argv.foo.should.deep.equal(['a', 'b'])
-      argv._.should.deep.equal(['c'])
-    })
-
-    it('accepts a hash of keys and counts', function () {
-      var argv = yargs('--foo a b c')
-        .nargs({
-          foo: 2
-        })
-        .argv
-
-      argv.foo.should.deep.equal(['a', 'b'])
-      argv._.should.deep.equal(['c'])
-    })
-
-    it('allows key to be specified with option shorthand', function () {
-      var argv = yargs('--foo a b c')
-        .option('foo', {
-          nargs: 2
-        })
-        .argv
-
-      argv.foo.should.deep.equal(['a', 'b'])
-      argv._.should.deep.equal(['c'])
-    })
-  })
 })