Comparing version 0.1.1 to 0.1.2
var assert = require('assert'); | ||
var helpCommand = require('./commands/help'); | ||
var async = require('async'); | ||
@@ -79,2 +80,15 @@ var defaultCommands = [ | ||
var hasCallback = function(fn){ | ||
// check if the following are true: | ||
// 1. we have a named argument | ||
// 2. we call, apply, or invoke a variable by that name in the method body | ||
// This gives us a good estimate if the function is async or not. | ||
var fnString = fn.toString(); | ||
var argMatches = fnString.match(/^function \((?:.*[,\s]+)?([\w]+)\){/); | ||
if(!argMatches) return false; | ||
var lastArg = argMatches[1]; | ||
var cbPattern = new RegExp(';?[\\s\\S]*' + lastArg + '\\.?(call|apply)?\\([^\\)]*\\)[\\s\\S]*;?'); | ||
return cbPattern.test(fnString); | ||
} | ||
cuisinart.program = function(name){ | ||
@@ -128,6 +142,5 @@ assert(name,'programs must have names.'); | ||
self.parse = function(args){ | ||
self.parse = function(args,callback){ | ||
var matchedCommands = 0; | ||
for(var i in self._commands){ | ||
var command = self._commands[i]; | ||
async.forEach(self._commands,function(command,done){ | ||
var matched = matchCommand(args,command); | ||
@@ -143,12 +156,25 @@ if(matched){ | ||
},{}); | ||
command.run.apply(self,[optionMap].concat(self._baseArgs)); | ||
if(command.stop) return; | ||
var calledBack; | ||
var complete = function(err){ | ||
if(calledBack) return; | ||
calledBack = true; | ||
if(err) return done(err); | ||
if(command.stop) return done(new Error('complete')); | ||
done(err); | ||
}; | ||
command.run.apply(self,[optionMap].concat(self._baseArgs,complete)); | ||
// if we don't detect a callback, assume that this is synchronous. | ||
if(!hasCallback(command.run)) complete(); | ||
} else { | ||
done(); | ||
} | ||
} | ||
if(!matchedCommands){ | ||
// print the help if nothing matches | ||
helpCommand.run.apply(self); | ||
} | ||
},function(err){ | ||
if(!matchedCommands){ | ||
// print the help if nothing matches | ||
helpCommand.run.apply(self); | ||
} | ||
if(callback) callback(err); | ||
}); | ||
}; | ||
return self; | ||
}; |
{ | ||
"name": "cuisinart", | ||
"version": "0.1.1", | ||
"version": "0.1.2", | ||
"description": "A node js command line parser", | ||
@@ -5,0 +5,0 @@ "main": "cuisinart.js", |
@@ -89,2 +89,12 @@ cuisinart | ||
All of the default commands are synchronous, but if you have async commands, you can pass a callback to the `.parse` call: | ||
```javascript | ||
program | ||
// setup program... | ||
.parse(process.argv,function(err){ | ||
// this will be executed after all commands have run, and will return any errors they call back with | ||
}); | ||
``` | ||
Commands | ||
@@ -120,4 +130,40 @@ --- | ||
There are 5 possible keys for a command, 2 of which are mandatory: | ||
Commands can be synchronous or async. Cuisinart expects that if a command is async, the callback will be called, invoked, or applied by the same name that it is passed to the `run` method of the command. | ||
Examples | ||
```javascript | ||
var command = { | ||
name : 'async', | ||
run : function(options,baseArg,callback){ | ||
setTimeout(function(){ | ||
callback(); | ||
// note that this can be called with any arguments, and can also be: | ||
// callback.call() | ||
// or | ||
// callback.apply() | ||
},1000); | ||
} | ||
} | ||
``` | ||
However, cuisinart is unable to detect that a command is async if you rename the callback or access it directly from the arguments list. So this example would be run *synchronously*, even though it is supposed to be async: | ||
```javascript | ||
var command = { | ||
name : 'bad-async', | ||
run : function(options,baseArg,callback){ | ||
var args = Array.prototype.slice.call(arguments); | ||
var done = args.pop(); | ||
setTimeout(function(){ | ||
done(); | ||
// because we're calling our callback by a different name, cuisinart is not able to detect that it should wait for this command. | ||
// as a result, this would accidentally call the next command before this one completes. | ||
},1000); | ||
} | ||
} | ||
``` | ||
There are 6 possible keys for a command, 2 of which are mandatory: | ||
- name (mandatory) : this is the name of the command. Use this name to invoke the command. | ||
@@ -130,2 +176,4 @@ | ||
- run (mandatory) : this is a method that will be called when a command is run. The options hash will be passed to this function. | ||
- options (optional) : you can specify options if you have them, these will be parsed and set either as true/false or with their values and passed to the run function | ||
@@ -135,9 +183,11 @@ | ||
```shell | ||
app complex -f value --bar | ||
app complex -f value --bar | ||
``` | ||
- description (optional) : this is printed with the command when `--help` is called. | ||
- root (optional) : this is used to indicate that this command does not need to be called explicitly - `--help` and `--version` are examples of root commands. They are essentially option-only commands. In normal cases, you would not specify this at all, as it defaults to false. | ||
- run (mandatory) : this is a method that will be called when a command is run. The options hash will be passed to this function. | ||
- stop (optional) : if this is set to true, it will prevent other commands from running after this command. | ||
Notes | ||
@@ -144,0 +194,0 @@ --- |
36613
20
983
196