/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Kevin Dangoor (kdangoor@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('cockpit/index', ['require', 'exports', 'module' , 'pilot/index', 'cockpit/cli', 'cockpit/ui/settings', 'cockpit/ui/cli_view', 'cockpit/commands/basic'], function(require, exports, module) { exports.startup = function(data, reason) { require('pilot/index'); require('cockpit/cli').startup(data, reason); // window.testCli = require('cockpit/test/testCli'); require('cockpit/ui/settings').startup(data, reason); require('cockpit/ui/cli_view').startup(data, reason); require('cockpit/commands/basic').startup(data, reason); }; /* exports.shutdown(data, reason) { }; */ }); /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Skywriter. * * The Initial Developer of the Original Code is * Mozilla. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Joe Walker (jwalker@mozilla.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('cockpit/cli', ['require', 'exports', 'module' , 'pilot/console', 'pilot/lang', 'pilot/oop', 'pilot/event_emitter', 'pilot/types', 'pilot/canon'], function(require, exports, module) { var console = require('pilot/console'); var lang = require('pilot/lang'); var oop = require('pilot/oop'); var EventEmitter = require('pilot/event_emitter').EventEmitter; //var keyboard = require('keyboard/keyboard'); var types = require('pilot/types'); 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('command', 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; this.message = message; if (typeof start === 'number') { this.start = start; this.end = end; this.predictions = predictions; } else { var arg = start; this.start = arg.start; this.end = arg.end; this.predictions = arg.predictions; } } Hint.prototype = { }; /** * Loop over the array of hints finding the one we should display. * @param hints array of hints */ Hint.sort = function(hints, cursor) { // Calculate 'distance from cursor' if (cursor !== undefined) { hints.forEach(function(hint) { if (hint.start === Argument.AT_CURSOR) { hint.distance = 0; } else if (cursor < hint.start) { hint.distance = hint.start - cursor; } else if (cursor > hint.end) { hint.distance = cursor - hint.end; } else { hint.distance = 0; } }, this); } // Sort hints.sort(function(hint1, hint2) { // Compare first based on distance from cursor if (cursor !== undefined) { var diff = hint1.distance - hint2.distance; if (diff != 0) { return diff; } } // otherwise go with hint severity return hint2.status - hint1.status; }); // tidy-up if (cursor !== undefined) { hints.forEach(function(hint) { delete hint.distance; }, this); } return hints; }; 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. * 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 prefix Knowledge of quotation marks and whitespace used prior to the * text in the input string allows us to re-generate the original input from * the arguments. * @param suffix Any quotation marks and whitespace used after the text. * Whitespace is normally placed in the prefix to the succeeding argument, but * can be used here when this is the last argument. * @constructor */ function Argument(emitter, text, start, end, prefix, suffix) { this.emitter = emitter; this.setText(text); this.start = start; this.end = end; this.prefix = prefix; this.suffix = suffix; } Argument.prototype = { /** * Return the result of merging these arguments. * TODO: What happens when we're merging arguments for the single string * case and some of the arguments are in quotation marks? */ 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 + this.suffix + following.prefix + following.text, this.start, following.end, this.prefix, following.suffix); }, /** * See notes on events in Assignment. We might need to hook changes here * into a CliRequisition so they appear of the command line. */ setText: function(text) { 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); }, /** * Helper when we're putting arguments back together */ toString: function() { // TODO: There is a bug here - we should re-escape escaped characters // But can we do that reliably? return this.prefix + this.text + this.suffix; } }; /** * 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; end = (end === undefined) ? argArray.length : end; var joined; for (var i = start; i < end; i++) { var arg = argArray[i]; if (!joined) { joined = arg; } else { joined = joined.merge(arg); } } return joined; }; /** * We sometimes need a way to say 'this error occurs where ever the cursor is' */ Argument.AT_CURSOR = -1; /** * 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, requisition) { this.param = param; this.requisition = requisition; this.setValue(param.defaultValue); }; Assignment.prototype = { /** * The parameter that we are assigning to * @readonly */ param: undefined, /** * Report on the status of the last parse() conversion. * @see types.Conversion */ conversion: undefined, /** * The current value in a type as specified by param.type */ value: undefined, /** * The string version of the current value */ arg: undefined, /** * The current value (i.e. not the string representation) * Use setValue() to mutate */ value: undefined, setValue: function(value) { if (this.value === value) { return; } if (value === undefined) { this.value = this.param.defaultValue; this.conversion = this.param.getDefault ? this.param.getDefault() : this.param.type.getDefault(); this.arg = undefined; } else { this.value = value; this.conversion = undefined; var text = (value == null) ? '' : this.param.type.stringify(value); if (this.arg) { this.arg.setText(text); } } this.requisition._assignmentChanged(this); }, /** * The textual representation of the current value * Use setValue() to mutate */ arg: undefined, setArgument: function(arg) { if (this.arg === arg) { return; } this.arg = arg; 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); }, /** * Create a list of the hints associated with this parameter assignment. * Generally there will be only one hint generated because we're currently * only displaying one hint at a time, ordering by distance from cursor * and severity. Since distance from cursor will be the same for all hints * 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() */ getHint: function() { // Allow the parameter to provide documentation if (this.param.getCustomHint && this.value && this.arg) { var hint = this.param.getCustomHint(this.value, this.arg); if (hint) { return hint; } } // If there is no argument, use the cursor position var message = '' + this.param.name + ': '; if (this.param.description) { // TODO: This should be a short description - do we need to trim? message += this.param.description.trim(); // Ensure the help text ends with '. ' if (message.charAt(message.length - 1) !== '.') { message += '.'; } if (message.charAt(message.length - 1) !== ' ') { message += ' '; } } var status = Status.VALID; var start = this.arg ? this.arg.start : Argument.AT_CURSOR; var end = this.arg ? this.arg.end : Argument.AT_CURSOR; var predictions; // Non-valid conversions will have useful information to pass on if (this.conversion) { status = this.conversion.status; if (this.conversion.message) { message += this.conversion.message; } predictions = this.conversion.predictions; } // Hint if the param is required, but not provided var argProvided = this.arg && this.arg.text !== ''; var dataProvided = this.value !== undefined || argProvided; if (this.param.defaultValue === undefined && !dataProvided) { status = Status.INVALID; message += 'Required<\strong>'; } 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]); } }, /** * If the cursor is at 'position', do we have sufficient data to start * displaying the next hint. This is both complex and important. * For example, if the user has just typed:

*

Note that the input for 2 and 4 is identical, only the configuration * has changed, so hint display is environmental. * *

This function works out if the cursor is before the end of this * assignment (assuming that we've asked the same thing of the previous * assignment) and then attempts to work out if we should use the hint from * the next assignment even though technically the cursor is still inside * this one due to the rules above. */ isPositionCaptured: function(position) { if (!this.arg) { return false; } // Note we don't check if position >= this.arg.start because that's // implied by the fact that we're asking the assignments in turn, and // we want to avoid thing falling between the cracks, but we do need // to check that the argument does have a position if (this.arg.start === -1) { return false; } // We're clearly done if the position is past the end of the text if (position > this.arg.end) { return false; } // If we're AT the end, the position is captured if either the status // is not valid or if there are other valid options including current if (position === this.arg.end) { return this.conversion.status !== Status.VALID || this.conversion.predictions.length !== 0; } // Otherwise we're clearly inside return true; }, /** * Replace the current value with the lower value if such a concept * exists. */ decrement: function() { var replacement = this.param.type.decrement(this.value); if (replacement != null) { this.setValue(replacement); } }, /** * Replace the current value with the higher value if such a concept * exists. */ increment: function() { var replacement = this.param.type.increment(this.value); if (replacement != null) { this.setValue(replacement); } }, /** * Helper when we're rebuilding command lines. */ toString: function() { return this.arg ? this.arg.toString() : ''; } }; 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. */ getCustomHint: function(command, arg) { 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 new Hint(Status.VALID, docs.join(''), arg); } }; /** * 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. *

Events

* We publish the following events: