Skip to content

Commit

Permalink
feat(command): derive missing command string from module filename (#527)
Browse files Browse the repository at this point in the history
  • Loading branch information
nexdrew authored and bcoe committed Jun 27, 2016
1 parent 365fb9a commit 20d4b8a
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 4 deletions.
24 changes: 20 additions & 4 deletions lib/command.js
@@ -1,5 +1,7 @@
const path = require('path')
const inspect = require('util').inspect
const requireDirectory = require('require-directory')
const whichModule = require('which-module')

// handles parsing positional arguments,
// and populating argv with said positional
Expand All @@ -10,11 +12,12 @@ module.exports = function (yargs, usage, validation) {
var handlers = {}
self.addHandler = function (cmd, description, builder, handler) {
// allow modules that define (a) all properties or (b) only command and description
if (typeof cmd === 'object' && typeof cmd.command === 'string') {
if (typeof cmd === 'object') {
const commandString = typeof cmd.command === 'string' ? cmd.command : moduleName(cmd)
if (cmd.builder && typeof cmd.handler === 'function') {
self.addHandler(cmd.command, extractDesc(cmd), cmd.builder, cmd.handler)
self.addHandler(commandString, extractDesc(cmd), cmd.builder, cmd.handler)
} else {
self.addHandler(cmd.command, extractDesc(cmd))
self.addHandler(commandString, extractDesc(cmd))
}
return
}
Expand Down Expand Up @@ -64,14 +67,27 @@ module.exports = function (yargs, usage, validation) {
context.files.push(joined)
// map "command path" to the directory path it came from
// so that dir can be relative in the API
context.dirs[context.commands.concat(parseCommand(visited.command).cmd).join('|')] = dir
context.dirs[context.commands.concat(parseCommand(visited.command || commandFromFilename(filename)).cmd).join('|')] = dir
self.addHandler(visited)
}
return visited
}
requireDirectory({ require: req, filename: mainFilename }, dir, opts)
}

// lookup module object from require()d command and derive name
// if module was not require()d and no name given, throw error
function moduleName (obj) {
const mod = whichModule(obj)
if (!mod) throw new Error('No command name given for module: ' + inspect(obj))
return commandFromFilename(mod.filename)
}

// derive command name from filename
function commandFromFilename (filename) {
return path.basename(filename, path.extname(filename))
}

function extractDesc (obj) {
for (var keys = ['describe', 'description', 'desc'], i = 0, l = keys.length, test; i < l; i++) {
test = obj[keys[i]]
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -22,6 +22,7 @@
"require-main-filename": "^1.0.1",
"set-blocking": "^2.0.0",
"string-width": "^1.0.1",
"which-module": "^1.0.0",
"window-size": "^0.2.0",
"y18n": "^3.2.1",
"yargs-parser": "^2.4.0"
Expand Down
19 changes: 19 additions & 0 deletions test/command.js
Expand Up @@ -435,5 +435,24 @@ describe('Command', function () {
''
])
})

it('derives \'command\' string from filename when not exported', function () {
var r = checkOutput(function () {
return yargs('--help').help().wrap(null)
// assumes cwd is node_modules/mocha/bin
.commandDir('../../../test/fixtures/cmddir_noname')
.argv
})
r.should.have.property('exit').and.be.true
r.should.have.property('errors').with.length(0)
r.should.have.property('logs')
r.logs.join('\n').split(/\n+/).should.deep.equal([
'Commands:',
' nameless Command name derived from module filename',
'Options:',
' --help Show help [boolean]',
''
])
})
})
})
14 changes: 14 additions & 0 deletions test/fixtures/cmddir_noname/nameless.js
@@ -0,0 +1,14 @@
exports.description = 'Command name derived from module filename'

exports.builder = {
banana: {
default: 'cool'
},
batman: {
default: 'sad'
}
}

exports.handler = function (argv) {
global.commandHandlerCalledWith = argv
}
25 changes: 25 additions & 0 deletions test/yargs.js
Expand Up @@ -377,6 +377,31 @@ describe('yargs dsl tests', function () {
global.commandHandlerCalledWith.foo.should.equal('bar')
delete global.commandHandlerCalledWith
})

it('derives \'command\' string from filename when missing', function () {
var argv = yargs('nameless --foo bar')
.command(require('./fixtures/cmddir_noname/nameless'))
.argv

argv.banana.should.equal('cool')
argv.batman.should.equal('sad')
argv.foo.should.equal('bar')

global.commandHandlerCalledWith.banana.should.equal('cool')
global.commandHandlerCalledWith.batman.should.equal('sad')
global.commandHandlerCalledWith.foo.should.equal('bar')
delete global.commandHandlerCalledWith
})

it('throws error for non-module command object missing \'command\' string', function () {
expect(function () {
yargs.command({
desc: 'A command with no name',
builder: function (yargs) { return yargs },
handler: function (argv) {}
})
}).to.throw(/No command name given for module: { desc: 'A command with no name',\n {2}builder: \[Function\],\n {2}handler: \[Function\] }/)
})
})

describe('terminalWidth', function () {
Expand Down

0 comments on commit 20d4b8a

Please sign in to comment.