Skip to content
Browse files

Add support for ArgumentProvider. canon.exec checks now if the reques…

…t is valid/complete/incomplete and calls a ArgumentProvider if the request is incomplete
  • Loading branch information...
1 parent f300562 commit af8cc2457c4aa91757eb8767dc1ce1ee3de6bec9 @jviereck committed Feb 20, 2011
Showing with 182 additions and 9 deletions.
  1. +182 −9 lib/pilot/canon.js
View
191 lib/pilot/canon.js
@@ -165,11 +165,55 @@ function getCommandNames() {
};
/**
- * Entry point for keyboard accelerators or anything else that knows
- * everything it needs to about the command params
- * @param command Either a command, or the name of one
+ * Default ArgumentProvider that is used if no ArgumentProvider is provided
+ * by the command's sender.
*/
-function exec(command, env, args, typed) {
+function defaultArgsProvider(request, callback) {
+ var args = request.args,
+ params = request.command.params;
+
+ for (var i = 0; i < params.length; i++) {
+ var param = params[i];
+
+ // If the parameter is already valid, then don't ask for it anymore.
+ if (request.getParamStatus(param) != Status.VALID ||
+ // Ask for optional parameters as well.
+ param.defaultValue === null)
+ {
+ var paramPrompt = param.description;
+ if (param.defaultValue === null) {
+ paramPrompt += " (optional)";
+ }
+ var value = prompt(paramPrompt, param.defaultValue || "");
+ // No value but required -> nope.
+ if (!value) {
+ callback();
+ return;
+ } else {
+ args[param.name] = value;
+ }
+ }
+ }
+ callback();
+}
+
+/**
+ * Entry point for keyboard accelerators or anything else that wants to execute
+ * a command. A new request object is created and a check performed, if the
+ * passed in arguments are VALID/INVALID or INCOMPLETE. If they are INCOMPLETE
+ * the ArgumentProvider on the sender is called or otherwise the default
+ * ArgumentProvider to get the still required arguments.
+ * If they are valid (or valid after the ArgumentProvider is done), the command
+ * is executed.
+ *
+ * @param command Either a command, or the name of one
+ * @param env Current environment to execute the command in
+ * @param sender String that should be the same as the senderObject stored on
+ * the environment in env[sender]
+ * @param args Arguments for the command
+ * @param typed (Optional)
+ */
+function exec(command, env, sender, args, typed) {
if (typeof command === 'string') {
command = commands[command];
}
@@ -179,12 +223,54 @@ function exec(command, env, args, typed) {
}
var request = new Request({
+ sender: sender,
command: command,
- args: args,
+ args: args || {},
typed: typed
});
- command.exec(env, args || {}, request);
- return true;
+
+ /**
+ * Executes the command and ensures request.done is called on the request in
+ * case it's not marked to be done already or async.
+ */
+ function execute() {
+ command.exec(env, request.args, request);
+
+ // If the request isn't asnync and isn't done, then make it done.
+ if (!request.isAsync && !request.isDone) {
+ request.done();
+ }
+ }
+
+
+ if (request.getStatus() == Status.INVALID) {
+ console.error("Canon.exec: Invalid parameter(s) passed to " +
+ command.name);
+ return false;
+ }
+ // If the request isn't complete yet, try to complete it.
+ else if (request.getStatus() == Status.INCOMPLETE) {
+ // Check if the sender has a ArgsProvider, otherwise use the default
+ // build in one.
+ var argsProvider;
+ var senderObj = env[sender];
+ if (!senderObj || !senderObj.getArgsProvider ||
+ !(argsProvider = senderObj.getArgsProvider()))
+ {
+ argsProvider = defaultArgsProvider;
+ }
+
+ // Ask the paramProvider to complete the request.
+ argsProvider(request, function() {
+ if (request.getStatus() == Status.VALID) {
+ execute();
+ }
+ });
+ return true;
+ } else {
+ execute();
+ return true;
+ }
};
exports.removeCommand = removeCommand;
@@ -262,6 +348,87 @@ function Request(options) {
oop.implement(Request.prototype, EventEmitter);
/**
+ * Return the status of a parameter on the request object.
+ */
+Request.prototype.getParamStatus = function(param) {
+ var args = this.args || {};
+
+ // Check if there is already a value for this parameter.
+ if (param.name in args) {
+ // If there is no value set and then the value is VALID if it's not
+ // required or INCOMPLETE if not set yet.
+ if (args[param.name] == null) {
+ if (param.defaultValue === null) {
+ return Status.VALID;
+ } else {
+ return Status.INCOMPLETE;
+ }
+ }
+
+ // Check if the parameter value is valid.
+ var reply,
+ // The passed in value when parsing a type is a string.
+ argsValue = args[param.name].toString();
+
+ // Type.parse can throw errors.
+ try {
+ reply = param.type.parse(argsValue);
+ } catch (e) {
+ return Status.INVALID;
+ }
+
+ if (reply.status != Status.VALID) {
+ return reply.status;
+ }
+ }
+ // Check if the param is marked as required.
+ else if (param.defaultValue === undefined) {
+ // The parameter is not set on the args object but it's required,
+ // which means, things are invalid.
+ return Status.INCOMPLETE;
+ }
+
+ return Status.VALID;
+}
+
+/**
+ * Return the status of a parameter name on the request object.
+ */
+Request.prototype.getParamNameStatus = function(paramName) {
+ var params = this.command.params || [];
+
+ for (var i = 0; i < params.length; i++) {
+ if (params[i].name == paramName) {
+ return this.getParamStatus(params[i]);
+ }
+ }
+
+ throw "Parameter '" + paramName +
+ "' not defined on command '" + this.command.name + "'";
+}
+
+/**
+ * Checks if all required arguments are set on the request such that it can
+ * get executed.
+ */
+Request.prototype.getStatus = function() {
+ var args = this.args || {},
+ params = this.command.params;
+
+ // If there are not parameters, then it's valid.
+ if (!params || params.length == 0) {
+ return Status.VALID;
+ }
+
+ var status = [];
+ for (var i = 0; i < params.length; i++) {
+ status.push(this.getParamStatus(params[i]));
+ }
+
+ return Status.combine(status);
+}
+
+/**
* Lazy init to register with the history should only be done on output.
* init() is expensive, and won't be used in the majority of cases
*/
@@ -293,6 +460,7 @@ Request.prototype.doneWithError = function(content) {
* the command exits
*/
Request.prototype.async = function() {
+ this.isAsync = true;
if (!this._begunOutput) {
this._beginOutput();
}
@@ -313,6 +481,7 @@ Request.prototype.output = function(content) {
}
this.outputs.push(content);
+ this.isDone = true;
this._dispatchEvent('output', {});
return this;
@@ -330,8 +499,12 @@ Request.prototype.done = function(content) {
if (content) {
this.output(content);
}
-
- this._dispatchEvent('output', {});
+
+ // Ensure to finish the request only once.
+ if (!this.isDone) {
+ this.isDone = true;
+ this._dispatchEvent('output', {});
+ }
};
exports.Request = Request;

0 comments on commit af8cc24

Please sign in to comment.
Something went wrong with that request. Please try again.