diff --git a/plugins/cockpit/lib/cli.js b/plugins/cockpit/lib/cli.js index 68af68e7..a9d5acc6 100644 --- a/plugins/cockpit/lib/cli.js +++ b/plugins/cockpit/lib/cli.js @@ -49,10 +49,19 @@ var Status = require('pilot/types').Status; var Conversion = require('pilot/types').Conversion; var canon = require('pilot/canon'); +/** + * Normally type upgrade is done when the owning command is registered, but + * out commandParam isn't part of a command, so it misses out. + */ +exports.startup = function(data, reason) { + canon.upgradeType(commandParam); +}; /** * The information required to tell the user there is a problem with their * input. + * TODO: There a several places where {start,end} crop up. Perhaps we should + * have a Cursor object. */ function Hint(status, message, start, end, predictions) { this.status = status; @@ -137,9 +146,20 @@ oop.inherits(ConversionHint, Hint); /** * We record where in the input string an argument comes so we can report errors * against those string positions. + * We publish a 'change' event when-ever the text changes + * @param emitter Arguments use something else to pass on change events. + * Currently this will be the creating Requisition. This prevents dependency + * loops and prevents us from needing to merge listener lists. + * @param text The string (trimmed) that contains the argument + * @param start The position of the text in the original input string + * @param end See start + * @param priorSpace Knowledge of the whitespace used prior to this argument in + * the input string allows us to re-generate the original input from the + * arguments. * @constructor */ -function Argument(text, start, end, priorSpace) { +function Argument(emitter, text, start, end, priorSpace) { + this.emitter = emitter; this.setText(text); this.start = start; this.end = end; @@ -150,7 +170,11 @@ Argument.prototype = { * Return the result of merging these arguments */ merge: function(following) { + if (following.emitter != this.emitter) { + throw new Error('Can\'t merge Arguments from different EventEmitters'); + } return new Argument( + this.emitter, this.text + following.priorSpace + following.text, this.start, following.end, this.priorSpace); @@ -164,11 +188,14 @@ Argument.prototype = { if (text == null) { throw new Error('Illegal text for Argument: ' + text); } + var ev = { argument: this, oldText: this.text, text: text }; this.text = text; + this.emitter._dispatchEvent('argumentChange', ev); } }; /** * Merge an array of arguments into a single argument. + * All Arguments in the array are expected to have the same emitter */ Argument.merge = function(argArray, start, end) { start = (start === undefined) ? 0 : start; @@ -187,7 +214,7 @@ Argument.merge = function(argArray, start, end) { return joined; }; /** - * We sometimes need a way to say 'this error occurs whereever the cursor is' + * We sometimes need a way to say 'this error occurs where ever the cursor is' */ Argument.AT_CURSOR = -1; @@ -202,17 +229,11 @@ Argument.AT_CURSOR = -1; * 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. - * - *

TODO: We might need events in the future - * The current hope is that a GUI and CLI that share an Assignment can be in - * direct connection due to assignment.getValue calling arg.setText, however - * we might need to use events if not. - * oop.implement(Assignment, EventEmitter); - * * @constructor */ -function Assignment(param) { +function Assignment(param, requisition) { this.param = param; + this.requisition = requisition; this.setValue(param.defaultValue); }; Assignment.prototype = { @@ -260,6 +281,7 @@ Assignment.prototype = { } this.conversion = undefined; + this.requisition._assignmentChanged(this); }, /** @@ -275,6 +297,7 @@ Assignment.prototype = { this.conversion = this.param.type.parse(arg.text); this.conversion.arg = arg; // TODO: make this automatic? this.value = this.conversion.value; + this.requisition._assignmentChanged(this); }, /** @@ -285,9 +308,7 @@ Assignment.prototype = { * from this assignment all but the most severe will ever be used. It might * make sense with more experience to alter this to function to be getHint() */ - getHints: function() { - var hints = []; - + getHint: function() { // If there is no argument, use the cursor position var message = '' + this.param.name + ': '; if (this.param.description) { @@ -324,56 +345,155 @@ Assignment.prototype = { message += 'Required<\strong>'; } - return [ new Hint(status, message, start, end, predictions) ]; + // Allow the parameter to provide documentation + // TODO: consider when we should do this + if (status === Status.VALID && message === '' && this.param.documentValid) { + message = this.param.documentValid(value); + } + + return new Hint(status, message, start, end, predictions); + }, + + /** + * Basically setValue(conversion.predictions[0]) done in a safe + * way. + */ + complete: function() { + if (this.conversion && this.conversion.predictions && + this.conversion.predictions.length > 0) { + this.setValue(this.conversion.predictions[0]); + } } }; exports.Assignment = Assignment; +/** + * This is a special parameter to reflect the command itself. + */ +var commandParam = { + name: 'command', + type: 'command', + description: 'The command to execute', + + /** + * Provide some documentation for a command. + */ + documentValid: function(command) { + var docs = []; + docs.push(' > '); + docs.push(command.name); + if (command.params && command.params.length > 0) { + command.params.forEach(function(param) { + if (param.defaultValue === undefined) { + docs.push(' [' + param.name + ']'); + } + else { + docs.push(' [' + param.name + ']'); + } + }, this); + } + docs.push('
'); + + docs.push(command.description ? command.description : '(No description)'); + docs.push('
'); + + if (command.params && command.params.length > 0) { + docs.push('

'); + } + + return docs.join(''); + } +}; + /** * 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. * CliRequisition adds functions for parsing input from a command line to this - * class + * class. + *

Events

+ * We publish the following events: