Repository: yargs/yargs (js)

Issue:
when in singleton mode, calling parse() multiple times results in undefined behavior
So my situation is that I want to use a required positional argument with some non-required options. To my surprise the positional argument is not picked up as I expected. Here is what the command look like
```bash
$ > node index.js stress-test restock
index.js stress-test <notification>

Some description

Positionals:
  notification  notification type to stress test
                                        [string] [required] [choices: "restock"]

Options:
  --version             Show version number                            [boolean]
  --help                Show help                                      [boolean]
  --amount, -n          amount of notifications to create  [number] [default: 1]
  --environment, --env  environment to conduct stress test in
                                 [choices: "dev", "stg", "pro"] [default: "stg"]

Missing required argument: notification
```

If I try to give an argument that is not part of the argument choice this is what I get:
```bash
$ > node index.js stress-test test
index.js stress-test <notification>

Some description

Positionals:
  notification  notification type to stress test
                                        [string] [required] [choices: "restock"]

Options:
  --version             Show version number                            [boolean]
  --help                Show help                                      [boolean]
  --amount, -n          amount of notifications to create  [number] [default: 1]
  --environment, --env  environment to conduct stress test in
                                 [choices: "dev", "stg", "pro"] [default: "stg"]

Invalid values:
  Argument: notification, Given: "test", Choices: "restock"
```

So obviously the argument is properly picked up. Not sure what is going on here. This is what the command option looks like:

```javascript
   {
      command: 'stress-test <notification>',
      describe: 'Some description',
      builder: (yargs) => {
        return yargs.positional('notification', {
          describe: 'notification type to stress test',
          choices: ['restock'],
          type: 'string'
        }).options({
          amount: {
            default: 1,
            requiresArg: true,
            describe: 'amount of notifications to create',
            alias: 'n',
            number: true
          },
          environment: {
            default: 'stg',
            requresArg: true,
            choices: ['dev','stg','pro'],
            describe: 'environment to conduct stress test in',
            alias: 'env'
          }
        });
      }
   }
```

Pull request description:
fix: calling parse multiple times now appropriately maintains state (#1137)
Extends the freezing/unfreezing mechanism already in place for `parse(args, cb)` to `parse()` (without arguments) to be able to call it several times with the same behavior.

Also corrects the builder function return value handler for it no longer to depend on `yargs.parsed` being set after a call to `parse()` (which is no longer the case after unfreezing).

Closes #1137 

Patch:
diff --git a/README.md b/README.md
index 6b7d096..2d1ab3b 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,7 @@
 <p align="center">
   <b >Yargs be a node.js library fer hearties tryin' ter parse optstrings</b>
 </p>
+
 <br>
 
 [![Build Status][travis-image]][travis-url]
@@ -102,6 +103,7 @@ Having problems? want to contribute? join our [community slack](http://devtoolsc
   * [Numbers](/docs/tricks.md#numbers)
   * [Arrays](/docs/tricks.md#arrays)
   * [Objects](/docs/tricks.md#objects)
+  * [Quotes](/docs/tricks.md#quotes)
 * [Advanced Topics](/docs/advanced.md)
   * [Composing Your App Using Commands](/docs/advanced.md#commands)
   * [Building Configurable CLI Apps](/docs/advanced.md#configuration)
diff --git a/docs/tricks.md b/docs/tricks.md
index 735cb55..d43cb62 100644
--- a/docs/tricks.md
+++ b/docs/tricks.md
@@ -55,3 +55,27 @@ This lets you organize arguments into nested objects.
     { _: [],
       foo: { bar: { baz: 33 }, quux: 5 },
       '$0': 'examples/reflect.js' }
+
+<a name="quotes"></a>
+
+Quotes
+------------
+
+When you use string arguments that include dashes (`-`), those will be seen as a separate option by the shell instead of part of the string. The problem is that shells like bash tend to strip quotes. The solution for this is to wrap the string in two sets of quotes.
+
+Use double quotes inside single quotes.
+
+```
+$ node examples/reflect.js --foo '"--hello -x=yes -v"'
+{ _: [], foo: '--hello -x=yes -v',
+  '$0': 'examples/reflect.js' }
+```
+
+Use escaped double quotes inside double quotes.
+
+```
+$ node examples/reflect.js --foo "\"--hello -x=yes -v\""
+{ _: [], foo: '--hello -x=yes -v',
+  '$0': 'examples/reflect.js' }
+```
+
diff --git a/example/usage-options.js b/example/usage-options.js
index 65a9c13..5bf9616 100644
--- a/example/usage-options.js
+++ b/example/usage-options.js
@@ -1,17 +1,18 @@
 var yargs = require('yargs');
 
-var argv = yargs.usage('This is my awesome program', {
-  'about': {
-    description: 'Provide some details about the author of this program',
-    required: true,
-    alias: 'a',
-  },
-  'info': {
-    description: 'Provide some information about the node.js agains!!!!!!',
-    boolean: true,
-    alias: 'i'
-  }
-}).argv;
+var argv = yargs.usage('This is my awesome program')
+  .options({
+    'about': {
+      description: 'Provide some details about the author of this program',
+      required: true,
+      alias: 'a',
+    },
+    'info': {
+      description: 'Provide some information about the node.js agains!!!!!!',
+      boolean: true,
+      alias: 'i'
+    }
+  }).argv;
 
 yargs.showHelp();
 
diff --git a/index.js b/index.js
index dfed54b..25bde57 100644
--- a/index.js
+++ b/index.js
@@ -25,8 +25,10 @@ function singletonify (inst) {
   Object.keys(inst).forEach((key) => {
     if (key === 'argv') {
       Argv.__defineGetter__(key, inst.__lookupGetter__(key))
+    } else if (typeof inst[key] === 'function') {
+      Argv[key] = inst[key].bind(inst)
     } else {
-      Argv[key] = typeof inst[key] === 'function' ? inst[key].bind(inst) : inst[key]
+      Argv.__defineGetter__(key, () => inst[key])
     }
   })
 }
diff --git a/lib/apply-extends.js b/lib/apply-extends.js
index 1436b65..864567a 100644
--- a/lib/apply-extends.js
+++ b/lib/apply-extends.js
@@ -19,7 +19,7 @@ function getPathToDefaultConfig (cwd, pathToExtend) {
 function applyExtends (config, cwd) {
   let defaultConfig = {}
 
-  if (config.hasOwnProperty('extends')) {
+  if (Object.prototype.hasOwnProperty.call(config, 'extends')) {
     if (typeof config.extends !== 'string') return defaultConfig
     const isPath = /\.json|\..*rc$/.test(config.extends)
     let pathToDefault = null
diff --git a/lib/command.js b/lib/command.js
index 895423a..7024310 100644
--- a/lib/command.js
+++ b/lib/command.js
@@ -186,24 +186,17 @@ module.exports = function command (yargs, usage, validation, globalMiddleware) {
       // a function can be provided, which builds
       // up a yargs chain and possibly returns it.
       innerYargs = commandHandler.builder(yargs.reset(parsed.aliases))
-      // if the builder function did not yet parse argv with reset yargs
-      // and did not explicitly set a usage() string, then apply the
-      // original command string as usage() for consistent behavior with
-      // options object below.
-      if (yargs.parsed === false) {
-        if (shouldUpdateUsage(yargs)) {
-          yargs.getUsageInstance().usage(
-            usageFromParentCommandsCommandHandler(parentCommands, commandHandler),
-            commandHandler.description
-          )
-        }
-        innerArgv = innerYargs ? innerYargs._parseArgs(null, null, true, commandIndex) : yargs._parseArgs(null, null, true, commandIndex)
-      } else {
-        innerArgv = yargs.parsed.argv
+      if (!innerYargs || (typeof innerYargs._parseArgs !== 'function')) {
+        innerYargs = yargs
       }
-
-      if (innerYargs && yargs.parsed === false) aliases = innerYargs.parsed.aliases
-      else aliases = yargs.parsed.aliases
+      if (shouldUpdateUsage(innerYargs)) {
+        innerYargs.getUsageInstance().usage(
+          usageFromParentCommandsCommandHandler(parentCommands, commandHandler),
+          commandHandler.description
+        )
+      }
+      innerArgv = innerYargs._parseArgs(null, null, true, commandIndex)
+      aliases = innerYargs.parsed.aliases
     } else if (typeof commandHandler.builder === 'object') {
       // as a short hand, an object can instead be provided, specifying
       // the options that a command takes.
@@ -419,18 +412,19 @@ module.exports = function command (yargs, usage, validation, globalMiddleware) {
   // the state of commands such that
   // we can apply .parse() multiple times
   // with the same yargs instance.
-  let frozen
+  let frozens = []
   self.freeze = () => {
-    frozen = {}
+    let frozen = {}
+    frozens.push(frozen)
     frozen.handlers = handlers
     frozen.aliasMap = aliasMap
     frozen.defaultCommand = defaultCommand
   }
   self.unfreeze = () => {
+    let frozen = frozens.pop()
     handlers = frozen.handlers
     aliasMap = frozen.aliasMap
     defaultCommand = frozen.defaultCommand
-    frozen = undefined
   }
 
   return self
diff --git a/lib/decamelize.js b/lib/decamelize.js
deleted file mode 100644
index c3a4151..0000000
--- a/lib/decamelize.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
-MIT License
-
-Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-*/
-'use strict'
-
-module.exports = (text, separator) => {
-  separator = typeof separator === 'undefined' ? '_' : separator
-
-  return text
-    .replace(/([a-z\d])([A-Z])/g, `$1${separator}$2`)
-    .replace(/([A-Z]+)([A-Z][a-z\d]+)/g, `$1${separator}$2`)
-    .toLowerCase()
-}
diff --git a/lib/usage.js b/lib/usage.js
index 8c3b068..ded4fc4 100644
--- a/lib/usage.js
+++ b/lib/usage.js
@@ -1,7 +1,7 @@
 'use strict'
 // this file handles outputting usage instructions,
 // failures, etc. keeps logging in one place.
-const decamelize = require('./decamelize')
+const decamelize = require('decamelize')
 const stringWidth = require('string-width')
 const objFilter = require('./obj-filter')
 const path = require('path')
@@ -512,9 +512,10 @@ module.exports = function usage (yargs, y18n) {
     return self
   }
 
-  let frozen
+  let frozens = []
   self.freeze = function freeze () {
-    frozen = {}
+    let frozen = {}
+    frozens.push(frozen)
     frozen.failMessage = failMessage
     frozen.failureOutput = failureOutput
     frozen.usages = usages
@@ -525,6 +526,7 @@ module.exports = function usage (yargs, y18n) {
     frozen.descriptions = descriptions
   }
   self.unfreeze = function unfreeze () {
+    let frozen = frozens.pop()
     failMessage = frozen.failMessage
     failureOutput = frozen.failureOutput
     usages = frozen.usages
@@ -533,7 +535,6 @@ module.exports = function usage (yargs, y18n) {
     examples = frozen.examples
     commands = frozen.commands
     descriptions = frozen.descriptions
-    frozen = undefined
   }
 
   return self
diff --git a/lib/validation.js b/lib/validation.js
index 5cbe964..527a0d5 100644
--- a/lib/validation.js
+++ b/lib/validation.js
@@ -323,18 +323,19 @@ module.exports = function validation (yargs, usage, y18n) {
     return self
   }
 
-  let frozen
+  let frozens = []
   self.freeze = function freeze () {
-    frozen = {}
+    let frozen = {}
+    frozens.push(frozen)
     frozen.implied = implied
     frozen.checks = checks
     frozen.conflicting = conflicting
   }
   self.unfreeze = function unfreeze () {
+    let frozen = frozens.pop()
     implied = frozen.implied
     checks = frozen.checks
     conflicting = frozen.conflicting
-    frozen = undefined
   }
 
   return self
diff --git a/package.json b/package.json
index 436c04b..f8f06cf 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
   ],
   "dependencies": {
     "cliui": "^5.0.0",
+    "decamelize": "^1.2.0",
     "find-up": "^3.0.0",
     "get-caller-file": "^2.0.1",
     "require-directory": "^2.1.1",
diff --git a/yargs.js b/yargs.js
index f1bf8c6..ed4a8e4 100644
--- a/yargs.js
+++ b/yargs.js
@@ -144,9 +144,10 @@ function Yargs (processArgs, cwd, parentRequire) {
   self.resetOptions()
 
   // temporary hack: allow "freezing" of reset-able state for parse(msg, cb)
-  let frozen
+  let frozens = []
   function freeze () {
-    frozen = {}
+    let frozen = {}
+    frozens.push(frozen)
     frozen.options = options
     frozen.configObjects = options.configObjects.slice(0)
     frozen.exitProcess = exitProcess
@@ -160,8 +161,11 @@ function Yargs (processArgs, cwd, parentRequire) {
     frozen.exitError = exitError
     frozen.hasOutput = hasOutput
     frozen.parsed = self.parsed
+    frozen.parseFn = parseFn
+    frozen.parseContext = parseContext
   }
   function unfreeze () {
+    let frozen = frozens.pop()
     options = frozen.options
     options.configObjects = frozen.configObjects
     exitProcess = frozen.exitProcess
@@ -175,9 +179,8 @@ function Yargs (processArgs, cwd, parentRequire) {
     command.unfreeze()
     strict = frozen.strict
     completionCommand = frozen.completionCommand
-    parseFn = null
-    parseContext = null
-    frozen = undefined
+    parseFn = frozen.parseFn
+    parseContext = frozen.parseContext
   }
 
   self.boolean = function (keys) {
@@ -538,8 +541,11 @@ function Yargs (processArgs, cwd, parentRequire) {
   let parseContext = null
   self.parse = function parse (args, shortCircuit, _parseFn) {
     argsert('[string|array] [function|boolean|object] [function]', [args, shortCircuit, _parseFn], arguments.length)
+    freeze()
     if (typeof args === 'undefined') {
-      return self._parseArgs(processArgs)
+      const parsed = self._parseArgs(processArgs)
+      unfreeze()
+      return parsed
     }
 
     // a context object can optionally be provided, this allows
@@ -560,7 +566,6 @@ function Yargs (processArgs, cwd, parentRequire) {
     // skipping validation, etc.
     if (!shortCircuit) processArgs = args
 
-    freeze()
     if (parseFn) exitProcess = false
 
     const parsed = self._parseArgs(args, shortCircuit)


Test patch:
diff --git a/test/yargs.js b/test/yargs.js
index 20f1058..5b58034 100644
--- a/test/yargs.js
+++ b/test/yargs.js
@@ -835,6 +835,40 @@ describe('yargs dsl tests', () => {
 
       r.logs[0].should.match(/Commands:[\s\S]*blerg command/)
     })
+
+    it('can be called multiple times with the same behavior', () => {
+      let counter = { foobar: 0 }
+      yargs(['test', 'foobar'])
+        .command(
+          'test <name>',
+          'increases counter',
+          yargs => yargs.positional('name', {
+            aliases: 'n',
+            describe: 'a name',
+            choices: ['foobar'],
+            type: 'string'
+          }),
+          argv => { counter[argv.name]++ }
+        )
+        .fail((msg) => {
+          expect.fail(undefined, undefined, msg)
+        })
+      yargs.parse()
+      yargs.parse()
+      yargs.parse()
+      expect(counter.foobar).to.equal(3)
+    })
+  })
+
+  describe('parsed', () => {
+    it('should be false before parsing', () => {
+      yargs.parsed.should.equal(false)
+    })
+
+    it('should not be false after parsing', () => {
+      yargs.parse()
+      yargs.parsed.should.not.equal(false)
+    })
   })
 
   // yargs.parse(['foo', '--bar'], function (err, argv, output) {}
@@ -1281,6 +1315,20 @@ describe('yargs dsl tests', () => {
         argv.z.should.equal(15)
       })
 
+      it('tolerates null prototype config objects', () => {
+        const argv = yargs
+          .config({
+            __proto__: null,
+            a: 2,
+            extends: './test/fixtures/extends/config_1.json'
+          })
+          .parse()
+
+        argv.a.should.equal(2)
+        argv.b.should.equal(22)
+        argv.z.should.equal(15)
+      })
+
       // see: https://www.npmjs.com/package/yargs-test-extends
       it('allows a module to be extended, rather than a JSON file', () => {
         const argv = yargs()
@@ -2035,17 +2083,23 @@ describe('yargs dsl tests', () => {
     })
 
     it('allows a defaultDescription to be set', () => {
-      yargs('cmd')
+      const r = checkOutput(() => yargs('cmd --help').wrap(null)
         .command('cmd [heroes...]', 'a command', (yargs) => {
           yargs.positional('heroes', {
             default: ['batman', 'Iron Man'],
             defaultDescription: 'batman and Iron Man'
           })
         }).parse()
-
-      yargs.getOptions().defaultDescription.should.deep.equal({
-        heroes: 'batman and Iron Man'
-      })
+      )
+      r.logs.join('\n').split(/\n+/).should.deep.equal([
+        'usage cmd [heroes...]',
+        'a command',
+        'Positionals:',
+        '  heroes  [array] [default: batman and Iron Man]',
+        'Options:',
+        '  --help     Show help  [boolean]',
+        '  --version  Show version number  [boolean]'
+      ])
     })
 
     it('allows an implied argument to be specified', (done) => {