From 7610fe87ccd2d0199c3db90c4722a58f2145d5e8 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Thu, 20 Nov 2014 11:50:58 -0400 Subject: [PATCH] Implement Yargs Command plugin --- lib/yargs-command/yargs-command.coffee | 48 +++++++++ lib/yargs-command/yargs-command.spec.coffee | 110 ++++++++++++++++++++ package.json | 8 +- 3 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 lib/yargs-command/yargs-command.coffee create mode 100644 lib/yargs-command/yargs-command.spec.coffee diff --git a/lib/yargs-command/yargs-command.coffee b/lib/yargs-command/yargs-command.coffee new file mode 100644 index 00000000..1ef6e42e --- /dev/null +++ b/lib/yargs-command/yargs-command.coffee @@ -0,0 +1,48 @@ +yargs = require('yargs') +_ = require('lodash') + +commandIndexBySignature = (command) -> + query = { signature: command.signature } + return _.findIndex(@command._commands, query) + +isArgVariable = (word) -> + return /^(<.*>|\[.*\])$/.test(word) + +splitSignature = (signature) -> + return signature.split(' ') + +commandApplies = (command, args) -> + args._ ?= {} + splittedCommandSignature = splitSignature(command.signature) + if splittedCommandSignature.length isnt args._.length + return false + + for word in splittedCommandSignature + index = splittedCommandSignature.indexOf(word) + + if not isArgVariable(word) and word isnt args._[index] + return false + + return true + +run = -> + signature = splitSignature(@command._matchedCommand.signature) + parameters = _.difference(@argv._, signature) + @command._matchedCommand.action.apply(this, parameters) + +module.exports = (signature, action) -> + command = { signature, action } + + @command._commands ?= [] + @command.run ?= _.bind(run, this) + + commandIndex = commandIndexBySignature.call(this, command) + if commandIndex is -1 + @command._commands.push(command) + else + @command._commands.splice(commandIndex, 1, command) + + if commandApplies(command, @argv) + @command._matchedCommand = command + + return this diff --git a/lib/yargs-command/yargs-command.spec.coffee b/lib/yargs-command/yargs-command.spec.coffee new file mode 100644 index 00000000..cd1d14c0 --- /dev/null +++ b/lib/yargs-command/yargs-command.spec.coffee @@ -0,0 +1,110 @@ +chai = require('chai') +sinon = require('sinon') +chai.use(require('chai-things')) +chai.use(require('sinon-chai')) +expect = chai.expect + +_ = require('lodash') +yargs = require('yargs') +yargs.command = require('./yargs-command') + +COMMANDS = + appList: + signature: 'app list ' + action: (id) -> "App List: #{id}" + action2: (id) -> "App List: #{id}!" + deviceList: + signature: 'device list ' + action: (id) -> "Device: #{id}" + +ARGS = + appList: + _: [ 'app', 'list', '7' ] + deviceList: + _: [ 'device', 'list', '7' ] + noMatch: + _: [ 'foo', 'bar', 'baz' ] + +# Hacky way to check that both functions are equal based +# on what they returns, as mocha crashes when trying equal +# or deep equal directly on the functions for some reason +areFunctionsEqual = (fn1, fn2) -> + return fn1.toString() is fn2.toString() + +describe 'Yargs Command:', -> + + cleanUpYargs = -> + yargs.command._commands = [] if yargs.command._commands + yargs.argv = {} + delete yargs.command._matchedCommand + + beforeEach -> + cleanUpYargs() + + afterEach -> + cleanUpYargs() + + it 'should expose a public command function', -> + expect(yargs.command).to.be.an.instanceof(Function) + + it 'should return yargs to enable chaining', -> + result = yargs.command(COMMANDS.appList.signature, COMMANDS.appList.action) + expect(result).to.equal(yargs) + + it 'should contain an empty _commands array', -> + expect(yargs.command._commands).to.deep.equal([]) + + it 'should push a command to _commands', -> + yargs.command(COMMANDS.appList.signature, COMMANDS.appList.action) + expect(yargs.command._commands).to.contain.something.that.deep.equals + signature: COMMANDS.appList.signature + action: COMMANDS.appList.action + + describe 'if adding the same command signature twice', -> + + beforeEach -> + yargs.command(COMMANDS.appList.signature, COMMANDS.appList.action) + yargs.command(COMMANDS.appList.signature, COMMANDS.appList.action2) + + it 'should contain only one command', -> + expect(yargs.command._commands).to.have.length(1) + + it 'should make the last action override the first one', -> + firstCommandAction = yargs.command._commands[0].action + expect(areFunctionsEqual(firstCommandAction, COMMANDS.appList.action2)).to.be.true + + describe 'given various registered commands', -> + + registerCommands = -> + yargs.command(COMMANDS.appList.signature, COMMANDS.appList.action) + yargs.command(COMMANDS.deviceList.signature, COMMANDS.deviceList.action) + + testCommand = (name) -> + yargs.argv = ARGS[name] + registerCommands() + matchedCommand = yargs.command._matchedCommand + expect(matchedCommand.signature).to.equal(COMMANDS[name].signature) + + it 'should choose the right command', -> + for command in [ + 'appList' + 'deviceList' + ] + testCommand(command) + + it 'should be undefined if no match', -> + yargs.argv = ARGS.noMatch + registerCommands() + expect(yargs.command._matchedCommand).to.not.exist + + it 'should have a run public function', -> + expect(yargs.command.run).to.be.an.instanceof(Function) + + describe '#run()', -> + + it 'should call the action with the correct parameters', -> + yargs.argv = ARGS.appList + callback = sinon.spy() + yargs.command(COMMANDS.appList.signature, callback) + yargs.command.run() + expect(callback).to.have.been.calledWith(_.last(ARGS.appList._)) diff --git a/package.json b/package.json index 28ed9745..5f2c08d3 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,10 @@ "gulp-coffeelint": "~0.4.0", "nock": "~0.48.2", "mock-fs": "~2.3.2", - "chai-as-promised": "~4.1.1" + "chai-as-promised": "~4.1.1", + "chai-things": "~0.2.0", + "sinon": "~1.12.1", + "sinon-chai": "~2.6.0" }, "dependencies": { "request": "~2.47.0", @@ -37,6 +40,7 @@ "open": "0.0.5", "inquirer": "~0.8.0", "cliff": "~0.1.9", - "underscore.string": "~2.4.0" + "underscore.string": "~2.4.0", + "yargs": "~1.3.3" } }