diff --git a/plugins/pilot/lib/cli.js b/plugins/pilot/lib/cli.js
index b47aff0f..928af610 100644
--- a/plugins/pilot/lib/cli.js
+++ b/plugins/pilot/lib/cli.js
@@ -38,31 +38,99 @@
define(function(require, exports, module) {
-var console = require('skywriter/console');
+var console = require('pilot/console');
var util = require('pilot/util');
+var oop = require('pilot/oop').oop;
-var keyboard = require('keyboard/keyboard');
-var Status = require('pilot/canon').Status;
-var Requisition = require('pilot/canon').Requisition;
+//var keyboard = require('keyboard/keyboard');
+var Status = require('pilot/types').Status;
+var canon = require('pilot/canon');
+var types = require('pilot/types');
-var typehint = require('command_line/typehint');
+var commandType;
+exports.startup = function(data, reason) {
+ commandType = types.getType('command');
+};
/**
* The information required to tell the user there is a problem with their
* input.
- * TODO: Consider formalizing alternatives
*/
function Hint(status, message, start, end) {
this.status = status;
this.message = message;
- this.start = start;
- this.end = end;
+
+ if (typeof start === 'number') {
+ this.start = start;
+ this.end = end;
+ }
+ else {
+ var arg = start;
+ this.start = arg.start;
+ this.end = arg.end;
+ }
}
Hint.prototype = {
-
};
exports.Hint = Hint;
+/**
+ * A Hint that arose as a result of a Conversion
+ */
+function ConversionHint(conversion, arg) {
+ this.status = conversion.status;
+ this.message = conversion.message;
+ if (arg) {
+ this.start = arg.start;
+ this.end = arg.end;
+ }
+ else {
+ this.start = 0;
+ this.end = 0;
+ }
+ this.predictions = conversion.predictions;
+};
+oop.inherits(ConversionHint, Hint);
+
+
+/**
+ * We record where in the input string an argument comes so we can report errors
+ * against those string positions.
+ * @constructor
+ */
+function Argument(text, start, end, priorSpace) {
+ this.text = text;
+ this.start = start;
+ this.end = end;
+ this.priorSpace = priorSpace;
+}
+Argument.prototype = {
+ /**
+ * Return the result of merging these arguments
+ */
+ merge: function(following) {
+ return new Argument(
+ this.text + following.priorSpace + following.text,
+ this.start, following.end,
+ this.priorSpace);
+ }
+};
+/**
+ * Merge an array of arguments into a single argument.
+ */
+Argument.mergeAll = function(argArray) {
+ var joined;
+ argArray.forEach(function(arg) {
+ if (!joined) {
+ joined = arg;
+ }
+ else {
+ joined = joined.merge(arg);
+ }
+ });
+ return joined;
+};
+
/**
* An object used during command line parsing to hold the various intermediate
@@ -72,6 +140,15 @@ exports.Hint = Hint;
* single value.
*
The other output value is input.requisition which gives access to an
* args object for use in executing the final command.
+ *
+ * The majority of the functions in this class are called in sequence by the
+ * constructor. Their task is to add to hints fill out the requisition.
+ *
The general sequence is:
+ * - _tokenize(): convert _typed into _parts
+ *
- _split(): convert _parts into _command and _unparsedArgs
+ *
- _assign(): convert _unparsedArgs into requisition
+ *
+ *
* @param typed {string} The instruction as typed by the user so far
* @param options {object} A list of optional named parameters. Can be any of:
* flags: Flags for us to check against the predicates specified with the
@@ -79,169 +156,85 @@ exports.Hint = Hint;
* if not specified.
* @constructor
*/
-function Input(typed, options) {
- if (util.none(typed)) {
- throw new Error('Input requires something \'typed\' to work on');
+function Input(options) {
+ if (options) {
+ if (options.flags) {
+ this.flags = options.flags;
+ }
+ if (options.input) {
+ // TODO: implement
+ this.useAsInput(options.input);
+ }
}
- this.typed = typed;
- this.hints = [];
-
- options = options || {};
-
- // TODO: We were using a default of keyboard.buildFlags({ });
- // I think this allowed us to have commands that only existed in certain
- // contexts - i.e. Javascript specific commands.
- this.flags = options.flags || {};
-
- // Once tokenize() has been called, we have the #typed string cut up into
- // #_parts
- this._parts = [];
-
- // Once split has been called we have #_parts split into #_unparsedArgs and
- // #_command (if there is a matching command).
- this._unparsedArgs = undefined;
-
- // Once we know what the command is, we can fire up a Requisition
- this.requisition = undefined;
-
- // Assign matches #_unparsedArgs to the params declared by the #_command
- // A list of arguments in _command.params order
- this._assignments = undefined;
-
- this._tokenize();
-};
-
-/**
- * Implementation of Input.
- * The majority of the functions in this class are called in sequence by the
- * constructor. Their task is to add to hints fill out the requisition.
- * The general sequence is:
- * - _tokenize(): convert _typed into _parts
- *
- _split(): convert _parts into _command and _unparsedArgs
- *
- _assign(): convert _unparsedArgs into _assignments
- *
- */
+ this.requisition = new Requisition();
+}
Input.prototype = {
/**
- * Split up the input taking into account ' and "
+ * TODO: We were using a default of keyboard.buildFlags({ });
+ * I think this allowed us to have commands that only existed in certain
+ * contexts - i.e. Javascript specific commands.
*/
- _tokenize: function() {
- if (!this.typed || this.typed === '') {
+ flags: {},
+
+ /**
+ *
+ */
+ parse: function(typed) {
+ if (util.none(typed)) {
+ this.requisition.setCommand(null);
+ return;
+ }
+
+ this.typed = typed;
+ this.hints = [];
+
+ var args = _tokenize(this.typed);
+ if (args.length === 0) {
// We would like to put some initial help here, but for anyone but
// a complete novice a 'type help' message is very annoying, so we
// need to find a way to only display this message once, or for
// until the user click a 'close' button or similar
- this.hints.push(new Hint(Status.INCOMPLETE));
+ this._addHint(Status.INCOMPLETE, '', 0, 0);
+ this.requisition.setCommand(null);
return;
}
- // replace(/^\s\s*/, '') = trimLeft()
- var incoming = this.typed.replace(/^\s\s*/, '').split(/\s+/);
+ var command = _split(args);
- var nextToken;
- while (true) {
- nextToken = incoming.shift();
- if (util.none(nextToken)) {
- break;
- }
- if (nextToken[0] == '"' || nextToken[0] == '\'') {
- // It's quoting time
- var eaten = [ nextToken.substring(1, nextToken.length) ];
- var eataway;
- while (true) {
- eataway = incoming.shift();
- if (!eataway) {
- break;
- }
- if (eataway[eataway.length - 1] == '"' ||
- eataway[eataway.length - 1] == '\'') {
- // End quoting time
- eaten.push(eataway.substring(0, eataway.length - 1));
- break;
- } else {
- eaten.push(eataway);
- }
- }
- this._parts.push(eaten.join(' '));
- } else {
- this._parts.push(nextToken);
- }
+ if (!command) {
+ // No command found - bail helpfully.
+ var conversion = commandType.parse(typed);
+ var arg = Argument.mergeAll(args);
+ this._addHint(new ConversionHint(conversion, arg));
+
+ this.requisition.setCommand(null);
}
+ else {
+ // The user hasn't started to type any arguments
+ if (args.length === 0) {
+ var message = documentCommand(command);
+ this._addHint(Status.VALID, message, 0, typed.length);
+ }
- // Split the command from the args
- this._split();
+ this.requisition.setCommand(command);
+ this._assign(args);
+ this.addHints(this.requisition.getHints());
+ }
},
/**
- * Looks in the canon for a command extension that matches what has been
- * typed at the command line.
+ * Some sugar around: 'this.hints.push(new Hint(...));'
*/
- _split: function() {
- this._unparsedArgs = this._parts.slice(); // aka clone()
- var initial = this._unparsedArgs.shift();
- var command;
-
- while (true) {
- command = canon.getCommand(initial);
-
- if (!command) {
- // Not found. break with command == null
- break;
- }
-
- if (!keyboard.flagsMatch(command.predicates, this.flags)) {
- // If the predicates say 'no match' then go LA LA LA
- command = null;
- break;
- }
-
- if (command.exec) {
- // Valid command, break with command valid
- break;
- }
-
- // command, but no exec - this must be a sub-command
- initial += ' ' + this._unparsedArgs.shift();
+ _addHint: function(status, message, start, end) {
+ if (status instanceof Hint) {
+ this.hints.push(status);
}
-
- // Do we know what the command is.
- if (!command) {
- // We don't know what the command is
- // TODO: We should probably cache this
- var commands = [];
- canon.getCommandNames().forEach(function(name) {
- var command = canon.getCommand(name);
- if (keyboard.flagsMatch(command.predicates, this.flags) &&
- command.description) {
- commands.push(command);
- }
- }.bind(this));
-
- // TODO: make this a hint
- var hintSpec = {
- param: {
- type: { name: 'selection', data: commands },
- description: 'Commands'
- },
- value: this.typed
- };
-
- return;
+ else if (Array.isArray(status)) {
+ this.hints.push.apply(this.hints, status);
}
-
- // The user hasn't started to type any params
- if (this._parts.length === 1) {
- if (this.typed == command.name ||
- !command.params ||
- command.params.length === 0) {
- this.hints.push(documentCommand(command, this.typed));
- }
+ else {
+ this.hints.push(new Hint(status, message, start, end));
}
-
- this.requisition = new Requisition(command);
-
- // Assign input to declared parameters
- this._assign();
},
/**
@@ -253,168 +246,293 @@ Input.prototype = {
* index - Zero based index into where the match came from on the input
* value - The matching input
*
- * The resulting #_assignments member created by this function is a list of
- * assignments of arguments in command.params order.
- * TODO: _unparsedArgs should be a list of objects that contain the
- * following values: name, param (when assigned) and maybe hints?
*/
- _assign: function() {
- // TODO: something smarter than just assuming that they are all in order
- this._assignments = [];
- var params = this.requisition.command.params;
- var unparsedArgs = this._unparsedArgs;
- var message;
+ _assign: function(args) {
+ if (args.length === 0) {
+ this.requisition.setDefaultValues();
+ return;
+ }
// Create an error if the command does not take parameters, but we have
// been given them ...
- if (!params || params.length === 0) {
- // No problem if we're passed nothing or an empty something
- var argCount = 0;
- unparsedArgs.forEach(function(unparsedArg) {
- if (unparsedArg.trim() !== '') {
- argCount++;
- }
- });
-
- if (argCount !== 0) {
- message = this.requisition.command.name + ' does not take any parameters';
- this.hints.push(new Hint(Status.INVALID, message));
- }
-
+ if (this.requisition.assignmentCount === 0) {
+ // TODO: previously we were doing some extra work to avoid this if
+ // we determined that we had args that were all whitespace, but
+ // probably given our tighter tokenize() this won't be an issue?
+ this._addHint(Status.INVALID,
+ this.requisition.command.name + ' does not take any parameters',
+ Argument.mergeAll(args));
return;
}
// Special case: if there is only 1 parameter, and that's of type
// text we put all the params into the first param
- if (params.length == 1 && params[0].type == 'text') {
- // Warning: There is some potential problem here if spaces are
- // significant. It might be better to chop the command of the
- // start of this.typed? But that's not easy because there could
- // be multiple spaces in the command if we're doing sub-commands
- this._assignments[0] = {
- value: unparsedArgs.length === 0 ? null : unparsedArgs.join(' '),
- param: params[0]
- };
- } else {
- // The normal case where we have to assign params individually
- var index = 0;
- var used = [];
- params.forEach(function(param) {
- this._assignParam(param, index++, used);
- }.bind(this));
-
- // Check there are no params that don't fit
- var unparsed = false;
- unparsedArgs.forEach(function(unparsedArg) {
- if (used.indexOf(unparsedArg) == -1) {
- message = 'Parameter \'' + unparsedArg + '\' makes no sense.';
- this.hints.push(new Hint(Status.INVALID, message));
- unparsed = true;
- }
- }.bind(this));
-
- if (unparsed) {
+ if (this.requisition.assignmentCount == 1) {
+ var assignment = this.requisition.getAssignment(0);
+ if (assignment.param.type.name === 'text') {
+ assignment.setText(Argument.mergeAll(args).text);
return;
}
}
- // Show a hint for the last parameter
- if (this._parts.length > 1) {
- var assignment = this._getAssignmentForLastArg();
+ var assignments = this.requisition.cloneAssignments();
+ var names = this.requisition.getParameterNames();
- // HACK! deferred types need to have some parameters
- // by which to determine which type they should defer to
- // so we hack in the assignments so the deferrer can work
- assignment.param.type.assignments = this._assignments;
+ // Extract all the named parameters
+ var used = [];
+ assignments.forEach(function(assignment) {
+ var namedArgText = '--' + assignment.name;
- if (assignment) {
- this.hints.push(typehint.getHint(this, assignment));
- }
- }
+ var i = 0;
+ while (true) {
+ var arg = args[i];
+ if (namedArgText !== arg.text) {
+ i++;
+ if (i >= args.length) {
+ break;
+ }
+ continue;
+ }
- // Convert input into declared types
- this._convertTypes();
- },
-
- /**
- * Extract a value from the set of inputs for a given param.
- * @param param The param that we are providing a value for. This is taken
- * from the command meta-data for the command in question.
- * @param index The number of the param - i.e. the index of param
- * into the original params array.
- */
- _assignParam: function(param, index, used) {
- var message;
- // Look for '--param X' style inputs
- for (var i = 0; i < this._unparsedArgs.length; i++) {
- var unparsedArg = this._unparsedArgs[i];
-
- if ('--' + param.name == unparsedArg) {
- used.push(unparsedArg);
- // boolean parameters don't have values, they default to false
- if (param.type.name === 'boolean') {
- this._assignments[index] = {
- value: true,
- param: param
- };
+ // boolean parameters don't have values, default to false
+ if (assignment.param.type.name === 'boolean') {
+ assignment.setValue(true);
} else {
- if (i + 1 < this._unparsedArgs.length) {
- message = 'Missing parameter: ' + param.name;
+ if (i + 1 < args.length) {
// Missing value for this param
- this.hints.push(new Hint(Status.INCOMPLETE, message));
+ this._addHint(Status.INCOMPLETE,
+ 'Missing value for: ' + namedArgText,
+ args[i]);
} else {
- used.push(this._unparsedArgs[i + 1]);
+ args.splice(i + 1, 1);
+ assignment.setText(args[i + 1].text);
}
}
- return;
+
+ util.arrayRemove(names, assignment.name);
+ args.splice(i, 1);
+ // We don't need to i++ if we splice
}
- }
+ }, this);
- var value = null;
- if (this._unparsedArgs.length > index) {
- value = this._unparsedArgs[index];
- used.push(this._unparsedArgs[index]);
- }
-
- // null is a valid default value, and common because it identifies an
- // parameter that is optional. undefined means there is no value from
- // the command line
- if (value !== undefined) {
- this._assignments[index] = { value: value, param: param };
- } else {
- this._assignments[index] = { param: param };
-
- if (param.defaultValue === undefined) {
- // There is no default, and we've not supplied one so far
- message = 'Missing parameter: ' + param.name;
- this.hints.push(new Hint(Status.INCOMPLETE, message));
+ // What's left are positional parameters assign in order
+ var i = 0;
+ names.forEach(function(name) {
+ var assignment = this.requisition.getAssignment(name);
+ if (i >= args.length) {
+ // No more values
+ assignment.setValue(undefined); // i.e. default
}
+ else {
+ var arg = args[i];
+ args.splice(i, 1);
+ assignment.setText(arg.text);
+ }
+
+ i++;
+ }, this);
+
+ if (args.length > 0) {
+ var remaining = Argument.mergeAll(args);
+ this._addHint(Status.INVALID,
+ 'Input \'' + remaining.text + '\' makes no sense.',
+ remaining);
}
}
};
exports.Input = Input;
+/**
+ * Split up the input taking into account ' and "
+ */
+function _tokenize(typed) {
+ var OUTSIDE = 1; // The last character was whitespace
+ var IN_SIMPLE = 2; // The last character was part of a parameter
+ var IN_SINGLE_Q = 3; // We're inside a single quote: '
+ var IN_DOUBLE_Q = 4; // We're inside double quotes: "
+
+ var mode = OUTSIDE;
+
+ // First we un-escape. This list was taken from:
+ // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Core_Language_Features#Unicode
+ // We are generally converting to their real values except for \', \"
+ // and '\ ' which we are converting to unicode private characters so we
+ // can distinguish them from ', " and ' ', which have special meaning.
+ // They need swapping back post-split.
+ typed = typed
+ .replace(/\\\\/g, '\\')
+ .replace(/\\b/g, '\b')
+ .replace(/\\f/g, '\f')
+ .replace(/\\n/g, '\n')
+ .replace(/\\r/g, '\r')
+ .replace(/\\t/g, '\t')
+ .replace(/\\v/g, '\v')
+ .replace(/\\n/g, '\n')
+ .replace(/\\r/g, '\r')
+ .replace(/\\ /g, '\uF000')
+ .replace(/\\'/g, '\uF001')
+ .replace(/\\"/g, '\uF002');
+
+ var i = 0;
+ var start = 0; // Where did this section start?
+ var priorSpace = '';
+ var args = [];
+
+ while (true) {
+ if (i >= typed.length) {
+ // There is no more - tidy up
+ if (mode !== OUTSIDE) {
+ var str = typed.substring(start, i);
+ args.push(new Argument(str, start, i, priorSpace));
+ }
+ break;
+ }
+
+ var c = typed[i];
+ switch (mode) {
+ case OUTSIDE:
+ if (c === '\'') {
+ priorSpace = typed.substring(start, i);
+ mode = IN_SINGLE_Q;
+ start = i + 1;
+ }
+ else if (c === '"') {
+ priorSpace = typed.substring(start, i);
+ mode = IN_DOUBLE_Q;
+ start = i + 1;
+ }
+ else if (/ /.test(c)) {
+ // Still whitespace, do nothing
+ }
+ else {
+ priorSpace = typed.substring(start, i);
+ mode = IN_SIMPLE;
+ start = i;
+ }
+ break;
+
+ case IN_SIMPLE:
+ // There is an edge case of xx'xx which we are assuming to be
+ // a single parameter (and same with ")
+ if (c === ' ') {
+ var str = typed.substring(start, i);
+ args.push(new Argument(str, start, i, priorSpace));
+ mode = OUTSIDE;
+ start = i;
+ priorSpace = '';
+ }
+ break;
+
+ case IN_SINGLE_Q:
+ if (c === '\'') {
+ var str = typed.substring(start, i);
+ args.push(new Argument(str, start, i, priorSpace));
+ mode = OUTSIDE;
+ start = i + 1;
+ priorSpace = '';
+ }
+ break;
+
+ case IN_DOUBLE_Q:
+ if (c === '"') {
+ var str = typed.substring(start, i);
+ args.push(new Argument(str, start, i, priorSpace));
+ mode = OUTSIDE;
+ start = i + 1;
+ priorSpace = '';
+ }
+ break;
+ }
+
+ i++;
+ }
+
+ args.forEach(function(arg) {
+ arg.text = arg.text
+ .replace(/\uF000/g, ' ')
+ .replace(/\uF001/g, '\'')
+ .replace(/\uF002/g, '"');
+ });
+
+ return args;
+}
+exports._tokenize = _tokenize;
+
+/**
+ * Looks in the canon for a command extension that matches what has been
+ * typed at the command line.
+ */
+function _split(args) {
+ if (args.length === 0) {
+ return undefined;
+ }
+
+ var argsUsed = 0;
+ var lookup = '';
+ var command;
+
+ while (true) {
+ argsUsed++;
+ lookup += args.map(function(arg) {
+ return arg.text;
+ }).slice(0, argsUsed).join(' ');
+ command = canon.getCommand(lookup);
+
+ if (!command) {
+ // Not found. break with command == null
+ return undefined;
+ }
+
+ /*
+ // Previously we needed a way to hide commands depending context.
+ // We have not resurrected that feature yet.
+ if (!keyboard.flagsMatch(command.predicates, this.flags)) {
+ // If the predicates say 'no match' then go LA LA LA
+ command = null;
+ break;
+ }
+ */
+
+ if (command.exec) {
+ // Valid command, break with command valid
+ break;
+ }
+
+ // command, but no exec - this must be a sub-command
+ lookup += ' ';
+ }
+
+ // Remove the used args
+ for (var i = 0; i < argsUsed; i++) {
+ args.shift();
+ }
+
+ return command;
+}
+exports._split = _split;
+
+
/**
* Provide some documentation for a command.
* TODO: this should return a hint
*/
-function documentCommand(cmdExt, typed) {
+function documentCommand(command) {
var docs = [];
- docs.push('' + cmdExt.name + '
');
+ docs.push('' + command.name + '
');
docs.push('Summary
');
- docs.push('' + cmdExt.description + '
');
+ docs.push('' + command.description + '
');
- if (cmdExt.manual) {
+ if (command.manual) {
docs.push('Description
');
- docs.push('' + cmdExt.description + '
');
+ docs.push('' + command.description + '
');
}
- if (cmdExt.params && cmdExt.params.length > 0) {
+ if (command.params && command.params.length > 0) {
docs.push('Synopsis
');
docs.push('');
- docs.push(cmdExt.name);
+ docs.push(command.name);
var optionalParamCount = 0;
- cmdExt.params.forEach(function(param) {
+ command.params.forEach(function(param) {
if (param.defaultValue === undefined) {
docs.push(' ');
docs.push(param.name);
@@ -430,7 +548,7 @@ function documentCommand(cmdExt, typed) {
if (optionalParamCount > 3) {
docs.push(' [options]');
} else if (optionalParamCount > 0) {
- cmdExt.params.forEach(function(param) {
+ command.params.forEach(function(param) {
if (param.defaultValue) {
docs.push(' [--');
docs.push(param.name);
@@ -446,7 +564,7 @@ function documentCommand(cmdExt, typed) {
docs.push('');
docs.push('Parameters
');
- cmdExt.params.forEach(function(param) {
+ command.params.forEach(function(param) {
docs.push('' + param.name + '
');
docs.push('' + param.description + '
');
if (param.type.defaultValue) {
@@ -455,12 +573,190 @@ function documentCommand(cmdExt, typed) {
});
}
- return {
- param: { type: 'text', description: docs.join('') },
- value: typed
- };
+ return docs.join('');
};
exports.documentCommand = documentCommand;
+/**
+ * A Requisition collects the information needed to execute a command.
+ * There is no point in a requisition for parameter-less commands because there
+ * is no information to collect. A Requisition is a collection of assignments
+ * of values to parameters, each handled by an instance of Assignment.
+ * @constructor
+ */
+function Requisition() {
+}
+Requisition.prototype = {
+ /**
+ * The command that we are about to execute.
+ * @readonly
+ */
+ command: undefined,
+
+ /**
+ * The count of assignments
+ * @readonly
+ */
+ assignmentCount: undefined,
+
+ /**
+ * Set a new command. We make no attempt to convert the args in the old
+ * command to args in the new command. The assignments need to be
+ * re-entered.
+ */
+ setCommand: function(command) {
+ if (this.command === command) {
+ return;
+ }
+
+ this.command = command;
+ this._assignments = {};
+
+ if (command) {
+ command.params.forEach(function(param) {
+ this._assignments[param.name] = new Assignment(param);
+ }, this);
+ }
+
+ this.assignmentCount = Object.keys(this._assignments);
+ },
+
+ /**
+ * Assignments have an order, so we need to store them in an array.
+ * But we also need named access ...
+ */
+ getAssignment: function(nameOrNumber) {
+ var name = (typeof nameOrNumber === 'string') ?
+ nameOrNumber :
+ Object.keys(this._assignments)[nameOrNumber];
+ return this._assignments[name];
+ },
+
+ /**
+ * Where parameter name == assignment names - they are the same.
+ */
+ getParameterNames: function() {
+ return Object.keys(this._assignments);
+ },
+
+ /**
+ * A *shallow* clone of the assignments.
+ * This is useful for systems that wish to go over all the assignments
+ * finding values one way or another and wish to trim an array as they go.
+ */
+ cloneAssignments: function() {
+ return Object.keys(this._assignments).map(function(name) {
+ return this._assignments[name];
+ }, this);
+ },
+
+ /**
+ * Collect the statuses from the Assignments
+ */
+ getHints: function() {
+
+ },
+
+ /**
+ * Extract the names and values of all the assignments, and return as
+ * an object.
+ */
+ getArgs: function() {
+ var args = {};
+ Object.keys(this._assignments).forEach(function(name) {
+ args[name] = getCommand(name);
+ }, this);
+ return args;
+ },
+
+ /**
+ * Reset all the assignments to their default values
+ */
+ setDefaultValues: function() {
+ Object.keys(this._assignments).forEach(function(name) {
+ this._assignments[name].setValue(undefined);
+ }, this);
+ },
+
+ /**
+ * Helper to call canon.exec
+ */
+ exec: function() {
+ exports.exec(this.command, this.getArgs());
+ }
+};
+exports.Requisition = Requisition;
+
+
+/**
+ * A link between a parameter and the data for that parameter.
+ * The data for the parameter is available as in the preferred type and as
+ * an Argument for the CLI.
+ * We also record validity information where applicable.
+ *
For values, null and undefined have distinct definitions. null means
+ * that a value has been provided, undefined means that it has not.
+ * Thus, null is a valid default value, and common because it identifies an
+ * parameter that is optional. undefined means there is no value from
+ * the command line.
+ * @constructor
+ */
+function Assignment(param) {
+ this.param = param;
+ this.setValue(param.defaultValue);
+};
+Assignment.prototype = {
+ /**
+ * The parameter that we are assigning to
+ * @readonly
+ */
+ param: undefined,
+
+ /**
+ * The current value (i.e. not the string representation)
+ * @readonly - use setValue() to mutate
+ */
+ value: undefined,
+ setValue: function(value) {
+ if (this.value === value) {
+ return;
+ }
+ if (value === undefined) {
+ value = this.param.defaultValue;
+ }
+ this.text = (value === null) ? '' : this.param.type.stringify(value);
+ this.value = value;
+ this.status = Status.VALID;
+ this.message = '';
+ //this._dispatchEvent('change', { assignment: this });
+ },
+
+ /**
+ * The textual representation of the current value
+ * @readonly - use setValue() to mutate
+ */
+ text: undefined,
+ setText: function(text) {
+ if (this.text === text) {
+ return;
+ }
+ var conversion = this.param.type.parse(text);
+ this.text = text;
+ this.value = conversion.value;
+ this.status = conversion.status;
+ this.message = conversion.message;
+ //this._dispatchEvent('change', { assignment: this });
+ },
+
+ /**
+ * Report on the status of the last parse() conversion.
+ * @see types.Conversion
+ */
+ status: undefined,
+ message: undefined
+};
+oop.implement(Assignment, EventEmitter);
+exports.Assignment = Assignment;
+
+
});