Repository: yargs/yargs (js)

Issue:
camel-case commands
For options, e.g., `--foo-bar`, we populate the camel-case `fooBar` variable on the argv object. For commands we're not that clever:

`.command('foo <api-token>')` will result in only `api-token` being populated on the argv object; it would be nice if we were clever enough to populate both `api-token` and `apiToken`.

Pull request:
feat(command): add camelcase commands to argv
Populate argv object with camelcase variants of hyphenated arguments.

Fixes #653 

Patch:
diff --git a/lib/command.js b/lib/command.js
index 35b3077..7df2b43 100644
--- a/lib/command.js
+++ b/lib/command.js
@@ -1,5 +1,6 @@
 const path = require('path')
 const inspect = require('util').inspect
+const camelCase = require('camelcase')
 
 // handles parsing positional arguments,
 // and populating argv with said positional
@@ -185,6 +186,7 @@ module.exports = function (yargs, usage, validation) {
       if (demand.variadic) argv[demand.cmd] = argv._.splice(0)
       else argv[demand.cmd] = argv._.shift()
       postProcessPositional(yargs, argv, demand.cmd)
+      addCamelCaseExpansions(argv, demand.cmd)
     }
 
     while (optional.length) {
@@ -194,6 +196,7 @@ module.exports = function (yargs, usage, validation) {
       if (maybe.variadic) argv[maybe.cmd] = argv._.splice(0)
       else argv[maybe.cmd] = argv._.shift()
       postProcessPositional(yargs, argv, maybe.cmd)
+      addCamelCaseExpansions(argv, maybe.cmd)
     }
 
     argv._ = context.commands.concat(argv._)
@@ -211,6 +214,14 @@ module.exports = function (yargs, usage, validation) {
     }
   }
 
+  function addCamelCaseExpansions (argv, option) {
+    if (/-/.test(option)) {
+      const cc = camelCase(option)
+      if (typeof argv[option] === 'object') argv[cc] = argv[option].slice(0)
+      else argv[cc] = argv[option]
+    }
+  }
+
   self.reset = function () {
     handlers = {}
     aliasMap = {}
diff --git a/locales/es.json b/locales/es.json
index f6bdb69..d7c8af9 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -2,12 +2,12 @@
   "Commands:": "Comandos:",
   "Options:": "Opciones:",
   "Examples:": "Ejemplos:",
-  "boolean": "boolean",
+  "boolean": "booleano",
   "count": "cuenta",
   "string": "cadena de caracteres",
   "number": "número",
   "array": "tabla",
-  "required": "requisito",
+  "required": "requerido",
   "default:": "defecto:",
   "choices:": "selección:",
   "aliases:": "alias:",
@@ -27,9 +27,9 @@
     "other": "Argumentos desconocidos: %s"
   },
   "Invalid values:": "Valores inválidos:",
-  "Argument: %s, Given: %s, Choices: %s": "Argumento: %s, Recibido: %s, Selección: %s",
-  "Argument check failed: %s": "Verificación de argumento ha fracasado: %s",
-  "Implications failed:": "Implicaciones fracasadas:",
+  "Argument: %s, Given: %s, Choices: %s": "Argumento: %s, Recibido: %s, Seleccionados: %s",
+  "Argument check failed: %s": "Verificación de argumento ha fallado: %s",
+  "Implications failed:": "Implicaciones fallidas:",
   "Not enough arguments following: %s": "No hay suficientes argumentos después de: %s",
   "Invalid JSON config file: %s": "Archivo de configuración JSON inválido: %s",
   "Path to JSON config file": "Ruta al archivo de configuración JSON",
diff --git a/locales/hi.json b/locales/hi.json
new file mode 100644
index 0000000..2a9826d
--- /dev/null
+++ b/locales/hi.json
@@ -0,0 +1,39 @@
+{
+  "Commands:": "आदेश:",
+  "Options:": "विकल्प:",
+  "Examples:": "उदाहरण:",
+  "boolean": "सत्यता",
+  "count": "संख्या",
+  "string": "वर्णों का तार ",
+  "number": "अंक",
+  "array": "सरणी",
+  "required": "आवश्यक",
+  "default:": "डिफॉल्ट:",
+  "choices:": "विकल्प:",
+  "aliases:": "उपनाम:",
+  "generated-value": "उत्पन्न-मूल्य",
+  "Not enough non-option arguments: got %s, need at least %s": "पर्याप्त गैर-विकल्प तर्क प्राप्त नहीं: %s प्राप्त, कम से कम %s की आवश्यकता है",
+  "Too many non-option arguments: got %s, maximum of %s": "बहुत सारे गैर-विकल्प तर्क: %s प्राप्त, अधिकतम %s मान्य",
+  "Missing argument value: %s": {
+    "one": "कुछ तर्को के मूल्य गुम हैं: %s",
+    "other": "कुछ तर्को के मूल्य गुम हैं: %s"
+  },
+  "Missing required argument: %s": {
+    "one": "आवश्यक तर्क गुम हैं: %s",
+    "other": "आवश्यक तर्क गुम हैं: %s"
+  },
+  "Unknown argument: %s": {
+    "one": "अज्ञात तर्क प्राप्त: %s",
+    "other": "अज्ञात तर्क प्राप्त: %s"
+  },
+  "Invalid values:": "अमान्य मूल्य:",
+  "Argument: %s, Given: %s, Choices: %s": "तर्क: %s, प्राप्त: %s, विकल्प: %s",
+  "Argument check failed: %s": "तर्क जांच विफल: %s",
+  "Implications failed:": "दिए गए तर्क के लिए अतिरिक्त तर्क की अपेक्षा है:",
+  "Not enough arguments following: %s": "निम्नलिखित के बाद पर्याप्त तर्क नहीं प्राप्त: %s",
+  "Invalid JSON config file: %s": "अमान्य JSON config फाइल: %s",
+  "Path to JSON config file": "JSON config फाइल का पथ",
+  "Show help": "सहायता दिखाएँ",
+  "Show version number": "Version संख्या दिखाएँ",
+  "Did you mean %s?": "क्या आपका मतलब है %s?"
+}
diff --git a/locales/hu.json b/locales/hu.json
new file mode 100644
index 0000000..7b7d166
--- /dev/null
+++ b/locales/hu.json
@@ -0,0 +1,39 @@
+{
+  "Commands:": "Parancsok:",
+  "Options:": "Opciók:",
+  "Examples:": "Példák:",
+  "boolean": "boolean",
+  "count": "számláló",
+  "string": "szöveg",
+  "number": "szám",
+  "array": "tömb",
+  "required": "kötelező",
+  "default:": "alapértelmezett:",
+  "choices:": "lehetőségek:",
+  "aliases:": "aliaszok:",
+  "generated-value": "generált-érték",
+  "Not enough non-option arguments: got %s, need at least %s": "Nincs elég nem opcionális argumentum: %s van, legalább %s kell",
+  "Too many non-option arguments: got %s, maximum of %s": "Túl sok nem opciánlis argumentum van: %s van, maximum %s lehet",
+  "Missing argument value: %s": {
+    "one": "Hiányzó argumentum érték: %s",
+    "other": "Hiányzó argumentum értékek: %s"
+  },
+  "Missing required argument: %s": {
+    "one": "Hiányzó kötelező argumentum: %s",
+    "other": "Hiányzó kötelező argumentumok: %s"
+  },
+  "Unknown argument: %s": {
+    "one": "Ismeretlen argumentum: %s",
+    "other": "Ismeretlen argumentumok: %s"
+  },
+  "Invalid values:": "Érvénytelen érték:",
+  "Argument: %s, Given: %s, Choices: %s": "Argumentum: %s, Megadott: %s, Lehetőségek: %s",
+  "Argument check failed: %s": "Argumentum ellenőrzés sikertelen: %s",
+  "Implications failed:": "Implikációk sikertelenek:",
+  "Not enough arguments following: %s": "Nem elég argumentum követi: %s",
+  "Invalid JSON config file: %s": "Érvénytelen JSON konfigurációs file: %s",
+  "Path to JSON config file": "JSON konfigurációs file helye",
+  "Show help": "Súgo megjelenítése",
+  "Show version number": "Verziószám megjelenítése",
+  "Did you mean %s?": "Erre gondoltál %s?"
+}
diff --git a/locales/id.json b/locales/id.json
index abe317f..a06e860 100644
--- a/locales/id.json
+++ b/locales/id.json
@@ -35,6 +35,6 @@
   "Invalid JSON config file: %s": "Berkas konfigurasi JSON tidak valid: %s",
   "Path to JSON config file": "Alamat berkas konfigurasi JSON",
   "Show help": "Lihat bantuan",
-  "Did you mean %s?": "Maksud Anda: %s?",
-  "Show version number": "Lihat nomor versi"
+  "Show version number": "Lihat nomor versi",
+  "Did you mean %s?": "Maksud Anda: %s?"
 }
diff --git a/locales/ru.json b/locales/ru.json
new file mode 100644
index 0000000..304e648
--- /dev/null
+++ b/locales/ru.json
@@ -0,0 +1,39 @@
+{
+  "Commands:": "Комманды:",
+  "Options:": "Опции:",
+  "Examples:": "Примеры:",
+  "boolean": "булевый тип",
+  "count": "подсчет",
+  "string": "строковой тип",
+  "number": "число",
+  "array": "массив",
+  "required": "необходимо",
+  "default:": "по умолчанию:",
+  "choices:": "возможности:",
+  "aliases:": "алиасы:",
+  "generated-value": "генерированное значение",
+  "Not enough non-option arguments: got %s, need at least %s": "Не достаточно неопционных аргументов: есть %s, нужно как минимум %s",
+  "Too many non-option arguments: got %s, maximum of %s": "Слишком много неопционных аргументов: есть %s, максимум допустимо %s",
+  "Missing argument value: %s": {
+    "one": "Не хватает значения аргумента: %s",
+    "other": "Не хватает значений аргументов: %s"
+  },
+  "Missing required argument: %s": {
+    "one": "Не хватает необходимого аргумента: %s",
+    "other": "Не хватает необходимых аргументов: %s"
+  },
+  "Unknown argument: %s": {
+    "one": "Неизвестный аргумент: %s",
+    "other": "Неизвестные аргументы: %s"
+  },
+  "Invalid values:": "Недействительные значения:",
+  "Argument: %s, Given: %s, Choices: %s": "Аргумент: %s, Данное значение: %s, Возможности: %s",
+  "Argument check failed: %s": "Проверка аргументов не удалась: %s",
+  "Implications failed:": "Данный аргумент требует следующий дополнительный аргумент:",
+  "Not enough arguments following: %s": "Not enough arguments following: %s",
+  "Invalid JSON config file: %s": "Недействительный файл конфигурации JSON: %s",
+  "Path to JSON config file": "Путь к файлу конфигурации JSON",
+  "Show help": "Показать помощь",
+  "Show version number": "Показать номер версии",
+  "Did you mean %s?": "Вы имели в виду %s?"
+}
diff --git a/locales/tr.json b/locales/tr.json
index 5b5c91b..000060b 100644
--- a/locales/tr.json
+++ b/locales/tr.json
@@ -10,6 +10,7 @@
   "required": "zorunlu",
   "default:": "varsayılan:",
   "choices:": "seçimler:",
+  "aliases:": "takma adlar:",
   "generated-value": "oluşturulan-değer",
   "Not enough non-option arguments: got %s, need at least %s": "Seçenek dışı argümanlar yetersiz: %s bulundu, %s gerekli",
   "Too many non-option arguments: got %s, maximum of %s": "Seçenek dışı argümanlar gereğinden fazla: %s bulundu, azami %s",
@@ -33,5 +34,6 @@
   "Invalid JSON config file: %s": "Geçersiz JSON yapılandırma dosyası: %s",
   "Path to JSON config file": "JSON yapılandırma dosya konumu",
   "Show help": "Yardım detaylarını göster",
-  "Show version number": "Versiyon detaylarını göster"
+  "Show version number": "Versiyon detaylarını göster",
+  "Did you mean %s?": "Bunu mu demek istediniz: %s?"
 }
diff --git a/package.json b/package.json
index c06b0bd..66f2c72 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
     "LICENSE"
   ],
   "dependencies": {
+    "camelcase": "^3.0.0",
     "cliui": "^3.2.0",
     "decamelize": "^1.1.1",
     "get-caller-file": "^1.0.1",


Test patch:
diff --git a/test/command.js b/test/command.js
index b344e50..1dea29c 100644
--- a/test/command.js
+++ b/test/command.js
@@ -49,6 +49,30 @@ describe('Command', function () {
       argv.awesome.should.equal('world')
     })
 
+    it('populates argv with camel-case variants of arguments when possible', function () {
+      var argv = yargs('foo hello world')
+        .command('foo <foo-bar> [baz-qux]')
+        .argv
+
+      argv._.should.include('foo')
+      argv['foo-bar'].should.equal('hello')
+      argv.fooBar.should.equal('hello')
+      argv.bazQux.should.equal('world')
+      argv['baz-qux'].should.equal('world')
+    })
+
+    it('populates argv with camel-case variants of variadic args when possible', function () {
+      var argv = yargs('foo hello world !')
+        .command('foo <foo-bar> [baz-qux..]')
+        .argv
+
+      argv._.should.include('foo')
+      argv['foo-bar'].should.equal('hello')
+      argv.fooBar.should.equal('hello')
+      argv.bazQux.should.deep.equal(['world', '!'])
+      argv['baz-qux'].should.deep.equal(['world', '!'])
+    })
+
     it('populates subcommand\'s inner argv with positional arguments', function () {
       yargs('foo bar hello world')
         .command('foo', 'my awesome command', function (yargs) {
diff --git a/test/yargs.js b/test/yargs.js
index 75d129d..6887f2e 100644
--- a/test/yargs.js
+++ b/test/yargs.js
@@ -1435,6 +1435,34 @@ describe('yargs dsl tests', function () {
       dates[1].toString().should.equal(new Date('2016-07-18').toString())
     })
 
+    it('returns camelcase args for a command', function () {
+      var age1
+      var age2
+      var dates
+      yargs('add 30days 2016-06-13 2016-07-18')
+        .command('add <age-in-days> [dates..]', 'Testing', function (yargs) {
+          return yargs
+            .coerce('age-in-days', function (arg) {
+              return parseInt(arg, 10) * 86400000
+            })
+            .coerce('dates', function (arg) {
+              return arg.map(function (str) {
+                return new Date(str)
+              })
+            })
+        }, function (argv) {
+          age1 = argv.ageInDays
+          age2 = argv['age-in-days']
+          dates = argv.dates
+        })
+        .argv
+      expect(age1).to.equal(2592000000)
+      expect(age2).to.equal(2592000000)
+      expect(dates).to.have.lengthOf(2)
+      dates[0].toString().should.equal(new Date('2016-06-13').toString())
+      dates[1].toString().should.equal(new Date('2016-07-18').toString())
+    })
+
     it('allows an error from positional arg to be handled by fail() handler', function () {
       var msg
       var err