/* ***** 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(function(require, exports, module) { var console = require('pilot/console'); var util = require('pilot/util'); var oop = require('pilot/oop').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'); /** * The information required to tell the user there is a problem with their * input. */ function Hint(status, message, start, end) { this.status = status; this.message = message; 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); }, setText: function(text) { this.text = text; } }; /** * 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 * data steps. *
The 'output' of the parse is held in 2 objects: input.hints which is an * array of hints to display to the user. In the future this will become a * 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:
This takes #_command.params and #_unparsedArgs and creates a map of * param names to 'assignment' objects, which have the following properties: *
' + command.description + '
'); if (command.manual) { docs.push('' + command.description + '
'); } if (command.params && command.params.length > 0) { docs.push('');
docs.push(command.name);
var optionalParamCount = 0;
command.params.forEach(function(param) {
if (param.defaultValue === undefined) {
docs.push(' ');
docs.push(param.name);
docs.push('');
} else if (param.defaultValue === null) {
docs.push(' [');
docs.push(param.name);
docs.push(']');
} else {
optionalParamCount++;
}
});
if (optionalParamCount > 3) {
docs.push(' [options]');
} else if (optionalParamCount > 0) {
command.params.forEach(function(param) {
if (param.defaultValue) {
docs.push(' [--');
docs.push(param.name);
if (param.type.name === 'boolean') {
docs.push('');
} else {
docs.push(' ' + param.type.name);
}
docs.push(']');
}
});
}
docs.push('');
docs.push('' + param.description + '
'); if (param.type.defaultValue) { docs.push('Default: ' + param.type.defaultValue + '
'); } }); } 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() { var hints = []; Object.keys(this._assignments).map(function(name) { var hint = this._assignments[name].getHint(); if (hint) { hints.push(hint); } }, this); return hints; }, /** * 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; } var text = (value === null) ? '' : this.param.type.stringify(value); if (this.arg) { this.arg.setText(text); } this.value = value; this.conversion = new Conversion(value, Status.VALID, '', []); //this._dispatchEvent('change', { assignment: this }); }, /** * The textual representation of the current value * @readonly - 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.value = this.conversion.value; //this._dispatchEvent('change', { assignment: this }); }, getHint: function() { if (this.conversion.status === Status.VALID && this.conversion.message === '') { return undefined; } return new ConversionHint(this.conversion, this.arg); }, /** * Report on the status of the last parse() conversion. * @see types.Conversion */ conversion: undefined }; oop.implement(Assignment, EventEmitter); exports.Assignment = Assignment; });