/* ***** 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(' The 'output' of the update 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:
*
*
*
');
docs.push(command.description ? command.description : '(No description)');
docs.push('
');
if (command.params && command.params.length > 0) {
docs.push('');
command.params.forEach(function(param) {
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:
*
*
*
* @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
* commands. Defaulted to keyboard.buildFlags({ });
* if not specified.
* @constructor
*/
function CliRequisition(env, options) {
Requisition.call(this, env);
if (options && options.flags) {
/**
* TODO: We were using a default of keyboard.buildFlags({ });
* This allowed us to have commands that only existed in certain contexts
* - i.e. Javascript specific commands.
*/
this.flags = options.flags;
}
}
oop.inherits(CliRequisition, Requisition);
(function() {
/**
* Called by the UI when ever the user interacts with a command line input
* @param input A structure that details the state of the input field.
* It should look something like: { typed:a, cursor: { start:b, end:c } }
* Where a is the contents of the input field, and b and c are the start
* and end of the cursor/selection respectively.
*/
CliRequisition.prototype.update = function(input) {
this.input = input;
this._hints = [];
var args = this._tokenize(input.typed);
this._split(args);
if (this.commandAssignment.value) {
this._assign(args);
}
this._updateHints();
};
/**
* Return an array of Status scores so we can create a marked up
* version of the command line input.
*/
CliRequisition.prototype.getInputStatusMarkup = function() {
// 'scores' is an array which tells us what chars are errors
// Initialize with everything VALID
var scores = this.toString().split('').map(function(ch) {
return Status.VALID;
});
// For all chars in all hints, check and upgrade the score
this._hints.forEach(function(hint) {
for (var i = hint.start; i <= hint.end; i++) {
if (hint.status > scores[i]) {
scores[i] = hint.status;
}
}
}, this);
return scores;
};
/**
* Reconstitute the input from the args
*/
CliRequisition.prototype.toString = function() {
return this.getAssignments(true).map(function(assignment) {
return assignment.toString();
}, this).join('');
};
var superUpdateHints = CliRequisition.prototype._updateHints;
/**
* Marks up hints in a number of ways:
* - Makes INCOMPLETE hints that are not near the cursor INVALID since
* they can't be completed by typing
* - Finds the most severe hint, and annotates the array with it
* - Finds the hint to display, and also annotates the array with it
* TODO: I'm wondering if array annotation is evil and we should replace
* this with an object. Need to find out more.
*/
CliRequisition.prototype._updateHints = function() {
superUpdateHints.call(this);
// Not knowing about cursor positioning, the requisition and assignments
// can't know this, but anything they mark as INCOMPLETE is actually
// INVALID unless the cursor is actually inside that argument.
var c = this.input.cursor;
this._hints.forEach(function(hint) {
var startInHint = c.start >= hint.start && c.start <= hint.end;
var endInHint = c.end >= hint.start && c.end <= hint.end;
var inHint = startInHint || endInHint;
if (!inHint && hint.status === Status.INCOMPLETE) {
hint.status = Status.INVALID;
}
}, this);
Hint.sort(this._hints);
};
/**
* Accessor for the hints array.
* While we could just use the hints property, using getHints() is
* preferred for symmetry with Requisition where it needs a function due to
* lack of an atomic update system.
*/
CliRequisition.prototype.getHints = function() {
return this._hints;
};
/**
* Look through the arguments attached to our assignments for the assignment
* at the given position.
*/
CliRequisition.prototype.getAssignmentAt = function(position) {
var assignments = this.getAssignments(true);
for (var i = 0; i < assignments.length; i++) {
var assignment = assignments[i];
if (!assignment.arg) {
// There is no argument in this assignment, we've fallen off
// the end of the obvious answers - it must be this one.
return assignment;
}
if (assignment.isPositionCaptured(position)) {
return assignment;
}
}
return assignment;
};
/**
* Split up the input taking into account ' and "
*/
CliRequisition.prototype._tokenize = function(typed) {
// For blank input, place a dummy empty argument into the list
if (typed == null || typed.length === 0) {
return [ new Argument(this, '', 0, 0, '', '') ];
}
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 - see unescape2()
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');
function unescape2(str) {
return str
.replace(/\uF000/g, ' ')
.replace(/\uF001/g, '\'')
.replace(/\uF002/g, '"');
}
var i = 0;
var start = 0; // Where did this section start?
var prefix = '';
var args = [];
while (true) {
if (i >= typed.length) {
// There is nothing else to read - tidy up
if (mode !== OUTSIDE) {
var str = unescape2(typed.substring(start, i));
args.push(new Argument(this, str, start, i, prefix, ''));
}
else {
if (i !== start) {
// There's a bunch of whitespace at the end of the
// command add it to the last argument's suffix,
// creating an empty argument if needed.
var extra = typed.substring(start, i);
var lastArg = args[args.length - 1];
if (!lastArg) {
lastArg = new Argument(this, '', i, i, extra, '');
args.push(lastArg);
}
else {
lastArg.suffix += extra;
}
}
}
break;
}
var c = typed[i];
switch (mode) {
case OUTSIDE:
if (c === '\'') {
prefix = typed.substring(start, i + 1);
mode = IN_SINGLE_Q;
start = i + 1;
}
else if (c === '"') {
prefix = typed.substring(start, i + 1);
mode = IN_DOUBLE_Q;
start = i + 1;
}
else if (/ /.test(c)) {
// Still whitespace, do nothing
}
else {
prefix = 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 = unescape2(typed.substring(start, i));
args.push(new Argument(this, str,
start, i, prefix, ''));
mode = OUTSIDE;
start = i;
prefix = '';
}
break;
case IN_SINGLE_Q:
if (c === '\'') {
var str = unescape2(typed.substring(start, i));
args.push(new Argument(this, str,
start - 1, i + 1, prefix, c));
mode = OUTSIDE;
start = i + 1;
prefix = '';
}
break;
case IN_DOUBLE_Q:
if (c === '"') {
var str = unescape2(typed.substring(start, i));
args.push(new Argument(this, str,
start - 1, i + 1, prefix, c));
mode = OUTSIDE;
start = i + 1;
prefix = '';
}
break;
}
i++;
}
return args;
};
/**
* Looks in the canon for a command extension that matches what has been
* typed at the command line.
*/
CliRequisition.prototype._split = function(args) {
var argsUsed = 1;
var arg;
while (argsUsed <= args.length) {
var arg = Argument.merge(args, 0, argsUsed);
this.commandAssignment.setArgument(arg);
if (!this.commandAssignment.value) {
// Not found. break with value == null
break;
}
/*
// 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 (this.commandAssignment.value.exec) {
// Valid command, break with command valid
for (var i = 0; i < argsUsed; i++) {
args.shift();
}
break;
}
argsUsed++;
}
};
/**
* Work out which arguments are applicable to which parameters.
*
*
*/
CliRequisition.prototype._assign = function(args) {
if (args.length === 0) {
this.setDefaultValues();
return;
}
// Create an error if the command does not take parameters, but we have
// been given them ...
if (this.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._hints.push(new Hint(Status.INVALID,
this.commandAssignment.value.name +
' does not take any parameters',
Argument.merge(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 (this.assignmentCount === 1) {
var assignment = this.getAssignment(0);
if (assignment.param.type.name === 'text') {
assignment.setArgument(Argument.merge(args));
return;
}
}
var assignments = this.cloneAssignments();
var names = this.getParameterNames();
// Extract all the named parameters
var used = [];
assignments.forEach(function(assignment) {
var namedArgText = '--' + assignment.name;
var i = 0;
while (true) {
var arg = args[i];
if (namedArgText !== arg.text) {
i++;
if (i >= args.length) {
break;
}
continue;
}
// boolean parameters don't have values, default to false
if (assignment.param.type.name === 'boolean') {
assignment.setValue(true);
}
else {
if (i + 1 < args.length) {
// Missing value portion of this named param
this._hints.push(new Hint(Status.INCOMPLETE,
'Missing value for: ' + namedArgText,
args[i]));
}
else {
args.splice(i + 1, 1);
assignment.setArgument(args[i + 1]);
}
}
lang.arrayRemove(names, assignment.name);
args.splice(i, 1);
// We don't need to i++ if we splice
}
}, this);
// What's left are positional parameters assign in order
names.forEach(function(name) {
var assignment = this.getAssignment(name);
if (args.length === 0) {
// No more values
assignment.setValue(undefined); // i.e. default
}
else {
var arg = args[0];
args.splice(0, 1);
assignment.setArgument(arg);
}
}, this);
if (args.length > 0) {
var remaining = Argument.merge(args);
this._hints.push(new Hint(Status.INVALID,
'Input \'' + remaining.text + '\' makes no sense.',
remaining));
}
};
})();
exports.CliRequisition = CliRequisition;
});
/* ***** 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):
* 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/ui/settings', ['require', 'exports', 'module' , 'pilot/types', 'pilot/types/basic'], function(require, exports, module) {
var types = require("pilot/types");
var SelectionType = require('pilot/types/basic').SelectionType;
var direction = new SelectionType({
name: 'direction',
data: [ 'above', 'below' ]
});
var hintDirectionSetting = {
name: "hintDirection",
description: "Are hints shown above or below the command line?",
type: "direction",
defaultValue: "above"
};
var outputDirectionSetting = {
name: "outputDirection",
description: "Is the output window shown above or below the command line?",
type: "direction",
defaultValue: "above"
};
var outputHeightSetting = {
name: "outputHeight",
description: "What height should the output panel be?",
type: "number",
defaultValue: 300
};
exports.startup = function(data, reason) {
types.registerType(direction);
data.env.settings.addSetting(hintDirectionSetting);
data.env.settings.addSetting(outputDirectionSetting);
data.env.settings.addSetting(outputHeightSetting);
};
exports.shutdown = function(data, reason) {
types.unregisterType(direction);
data.env.settings.removeSetting(hintDirectionSetting);
data.env.settings.removeSetting(outputDirectionSetting);
data.env.settings.removeSetting(outputHeightSetting);
};
});
/* ***** 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/ui/cli_view', ['require', 'exports', 'module' , 'text!cockpit/ui/cli_view.css', 'pilot/event', 'pilot/dom', 'pilot/keys', 'pilot/canon', 'pilot/types', 'cockpit/cli', 'cockpit/ui/request_view'], function(require, exports, module) {
var editorCss = require("text!cockpit/ui/cli_view.css");
var event = require("pilot/event");
var dom = require("pilot/dom");
dom.importCssString(editorCss);
var event = require("pilot/event");
var keys = require("pilot/keys");
var canon = require("pilot/canon");
var Status = require('pilot/types').Status;
var CliRequisition = require('cockpit/cli').CliRequisition;
var Hint = require('cockpit/cli').Hint;
var RequestView = require('cockpit/ui/request_view').RequestView;
var NO_HINT = new Hint(Status.VALID, '', 0, 0);
/**
* On startup we need to:
* 1. Add 3 sets of elements to the DOM for:
* - command line output
* - input hints
* - completion
* 2. Attach a set of events so the command line works
*/
exports.startup = function(data, reason) {
var cli = new CliRequisition(data.env);
var cliView = new CliView(cli, data.env);
data.env.cli = cli;
};
/**
* A class to handle the simplest UI implementation
*/
function CliView(cli, env) {
cli.cliView = this;
this.cli = cli;
this.doc = document;
this.win = dom.getParentWindow(this.doc);
this.env = env;
// TODO: we should have a better way to specify command lines???
this.element = this.doc.getElementById('cockpitInput');
if (!this.element) {
// console.log('No element with an id of cockpit. Bailing on cli');
return;
}
this.settings = env.settings;
this.hintDirection = this.settings.getSetting('hintDirection');
this.outputDirection = this.settings.getSetting('outputDirection');
this.outputHeight = this.settings.getSetting('outputHeight');
// If the requisition tells us something has changed, we use this to know
// if we should ignore it
this.isUpdating = false;
this.createElements();
this.update();
}
CliView.prototype = {
/**
* Create divs for completion, hints and output
*/
createElements: function() {
var input = this.element;
this.element.spellcheck = false;
this.output = this.doc.getElementById('cockpitOutput');
this.popupOutput = (this.output == null);
if (!this.output) {
this.output = this.doc.createElement('div');
this.output.id = 'cockpitOutput';
this.output.className = 'cptOutput';
input.parentNode.insertBefore(this.output, input.nextSibling);
var setMaxOutputHeight = function() {
this.output.style.maxHeight = this.outputHeight.get() + 'px';
}.bind(this);
this.outputHeight.addEventListener('change', setMaxOutputHeight);
setMaxOutputHeight();
}
this.completer = this.doc.createElement('div');
this.completer.className = 'cptCompletion VALID';
this.completer.style.color = dom.computedStyle(input, "color");
this.completer.style.fontSize = dom.computedStyle(input, "fontSize");
this.completer.style.fontFamily = dom.computedStyle(input, "fontFamily");
this.completer.style.fontWeight = dom.computedStyle(input, "fontWeight");
this.completer.style.fontStyle = dom.computedStyle(input, "fontStyle");
input.parentNode.insertBefore(this.completer, input.nextSibling);
// Transfer background styling to the completer.
this.completer.style.backgroundColor = input.style.backgroundColor;
input.style.backgroundColor = 'transparent';
this.hinter = this.doc.createElement('div');
this.hinter.className = 'cptHints';
input.parentNode.insertBefore(this.hinter, input.nextSibling);
var resizer = this.resizer.bind(this);
event.addListener(this.win, 'resize', resizer);
this.hintDirection.addEventListener('change', resizer);
this.outputDirection.addEventListener('change', resizer);
resizer();
canon.addEventListener('output', function(ev) {
new RequestView(ev.request, this);
}.bind(this));
event.addCommandKeyListener(input, this.onCommandKey.bind(this));
event.addListener(input, 'keyup', this.onKeyUp.bind(this));
// cursor position affects hint severity. TODO: shortcuts for speed
event.addListener(input, 'mouseup', function(ev) {
this.isUpdating = true;
this.update();
this.isUpdating = false;
}.bind(this));
this.cli.addEventListener('argumentChange', this.onArgChange.bind(this));
event.addListener(input, "focus", function() {
dom.addCssClass(this.output, "cptFocusPopup");
dom.addCssClass(this.hinter, "cptFocusPopup");
}.bind(this));
function hideOutput() {
dom.removeCssClass(this.output, "cptFocusPopup");
dom.removeCssClass(this.hinter, "cptFocusPopup");
};
event.addListener(input, "blur", hideOutput.bind(this));
hideOutput.call(this);
},
/**
* We need to see the output of the latest command entered
*/
scrollOutputToBottom: function() {
// Certain browsers have a bug such that scrollHeight is too small
// when content does not fill the client area of the element
var scrollHeight = Math.max(this.output.scrollHeight, this.output.clientHeight);
this.output.scrollTop = scrollHeight - this.output.clientHeight;
},
/**
* To be called on window resize or any time we want to align the elements
* with the input box.
*/
resizer: function() {
var rect = this.element.getClientRects()[0];
this.completer.style.top = rect.top + 'px';
var height = rect.bottom - rect.top;
this.completer.style.height = height + 'px';
this.completer.style.lineHeight = height + 'px';
this.completer.style.left = rect.left + 'px';
var width = rect.right - rect.left;
this.completer.style.width = width + 'px';
if (this.hintDirection.get() === 'below') {
this.hinter.style.top = rect.bottom + 'px';
this.hinter.style.bottom = 'auto';
}
else {
this.hinter.style.top = 'auto';
this.hinter.style.bottom = (this.doc.documentElement.clientHeight - rect.top) + 'px';
}
this.hinter.style.left = (rect.left + 30) + 'px';
this.hinter.style.maxWidth = (width - 110) + 'px';
if (this.popupOutput) {
if (this.outputDirection.get() === 'below') {
this.output.style.top = rect.bottom + 'px';
this.output.style.bottom = 'auto';
}
else {
this.output.style.top = 'auto';
this.output.style.bottom = (this.doc.documentElement.clientHeight - rect.top) + 'px';
}
this.output.style.left = rect.left + 'px';
this.output.style.width = (width - 80) + 'px';
}
},
/**
* Ensure that TAB isn't handled by the browser
*/
onCommandKey: function(ev, hashId, keyCode) {
var stopEvent;
if (keyCode === keys.TAB ||
keyCode === keys.UP ||
keyCode === keys.DOWN) {
stopEvent = true;
} else if (hashId != 0 || keyCode != 0) {
stopEvent = canon.execKeyCommand(this.env, 'cli', hashId, keyCode);
}
stopEvent && event.stopEvent(ev);
},
/**
* The main keyboard processing loop
*/
onKeyUp: function(ev) {
var handled;
/*
var handled = keyboardManager.processKeyEvent(ev, this, {
isCommandLine: true, isKeyUp: true
});
*/
// RETURN does a special exec/highlight thing
if (ev.keyCode === keys.RETURN) {
var worst = this.cli.getWorstHint();
// Deny RETURN unless the command might work
if (worst.status === Status.VALID) {
this.cli.exec();
this.element.value = '';
}
else {
// If we've denied RETURN because the command was not VALID,
// select the part of the command line that is causing problems
// TODO: if there are 2 errors are we picking the right one?
dom.setSelectionStart(this.element, worst.start);
dom.setSelectionEnd(this.element, worst.end);
}
}
this.update();
// Special actions which delegate to the assignment
var current = this.cli.getAssignmentAt(dom.getSelectionStart(this.element));
if (current) {
// TAB does a special complete thing
if (ev.keyCode === keys.TAB) {
current.complete();
this.update();
}
// UP/DOWN look for some history
if (ev.keyCode === keys.UP) {
current.increment();
this.update();
}
if (ev.keyCode === keys.DOWN) {
current.decrement();
this.update();
}
}
return handled;
},
/**
* Actually parse the input and make sure we're all up to date
*/
update: function() {
this.isUpdating = true;
var input = {
typed: this.element.value,
cursor: {
start: dom.getSelectionStart(this.element),
end: dom.getSelectionEnd(this.element.selectionEnd)
}
};
this.cli.update(input);
var display = this.cli.getAssignmentAt(input.cursor.start).getHint();
// 1. Update the completer with prompt/error marker/TAB info
dom.removeCssClass(this.completer, Status.VALID.toString());
dom.removeCssClass(this.completer, Status.INCOMPLETE.toString());
dom.removeCssClass(this.completer, Status.INVALID.toString());
var completion = '> ';
if (this.element.value.length > 0) {
var scores = this.cli.getInputStatusMarkup();
completion += this.markupStatusScore(scores);
}
// Display the "-> prediction" at the end of the completer
if (this.element.value.length > 0 &&
display.predictions && display.predictions.length > 0) {
var tab = display.predictions[0];
completion += ' ⇥ ' + (tab.name ? tab.name : tab);
}
this.completer.innerHTML = completion;
dom.addCssClass(this.completer, this.cli.getWorstHint().status.toString());
// 2. Update the hint element
var hint = '';
if (this.element.value.length !== 0) {
hint += display.message;
if (display.predictions && display.predictions.length > 0) {
hint += ': [ ';
display.predictions.forEach(function(prediction) {
hint += (prediction.name ? prediction.name : prediction);
hint += ' | ';
}, this);
hint = hint.replace(/\| $/, ']');
}
}
this.hinter.innerHTML = hint;
if (hint.length === 0) {
dom.addCssClass(this.hinter, 'cptNoPopup');
}
else {
dom.removeCssClass(this.hinter, 'cptNoPopup');
}
this.isUpdating = false;
},
/**
* Markup an array of Status values with spans
*/
markupStatusScore: function(scores) {
var completion = '';
// Create mark-up
var i = 0;
var lastStatus = -1;
while (true) {
if (lastStatus !== scores[i]) {
completion += '';
lastStatus = scores[i];
}
completion += this.element.value[i];
i++;
if (i === this.element.value.length) {
completion += '';
break;
}
if (lastStatus !== scores[i]) {
completion += '';
}
}
return completion;
},
/**
* Update the input element to reflect the changed argument
*/
onArgChange: function(ev) {
if (this.isUpdating) {
return;
}
var prefix = this.element.value.substring(0, ev.argument.start);
var suffix = this.element.value.substring(ev.argument.end);
var insert = typeof ev.text === 'string' ? ev.text : ev.text.name;
this.element.value = prefix + insert + suffix;
// Fix the cursor.
var insertEnd = (prefix + insert).length;
this.element.selectionStart = insertEnd;
this.element.selectionEnd = insertEnd;
}
};
exports.CliView = CliView;
});
/* ***** 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/ui/request_view', ['require', 'exports', 'module' , 'pilot/dom', 'pilot/event', 'text!cockpit/ui/request_view.html', 'pilot/domtemplate', 'text!cockpit/ui/request_view.css'], function(require, exports, module) {
var dom = require("pilot/dom");
var event = require("pilot/event");
var requestViewHtml = require("text!cockpit/ui/request_view.html");
var Templater = require("pilot/domtemplate").Templater;
var requestViewCss = require("text!cockpit/ui/request_view.css");
dom.importCssString(requestViewCss);
/**
* Pull the HTML into the DOM, but don't add it to the document
*/
var templates = document.createElement('div');
templates.innerHTML = requestViewHtml;
var row = templates.querySelector('.cptRow');
/**
* Work out the path for images.
* TODO: This should probably live in some utility area somewhere
*/
function imageUrl(path) {
var dataUrl;
try {
dataUrl = require('text!cockpit/ui/' + path);
} catch (e) { }
if (dataUrl) {
return dataUrl;
}
var filename = module.id.split('/').pop() + '.js';
var imagePath;
if (module.uri.substr(-filename.length) !== filename) {
console.error('Can\'t work out path from module.uri/module.id');
return path;
}
if (module.uri) {
var end = module.uri.length - filename.length - 1;
return module.uri.substr(0, end) + "/" + path;
}
return filename + path;
}
/**
* Adds a row to the CLI output display
*/
function RequestView(request, cliView) {
this.request = request;
this.cliView = cliView;
this.imageUrl = imageUrl;
// Elements attached to this by the templater. For info only
this.rowin = null;
this.rowout = null;
this.output = null;
this.hide = null;
this.show = null;
this.duration = null;
this.throb = null;
new Templater().processNode(row.cloneNode(true), this);
this.cliView.output.appendChild(this.rowin);
this.cliView.output.appendChild(this.rowout);
this.request.addEventListener('output', this.onRequestChange.bind(this));
};
RequestView.prototype = {
/**
* A single click on an invocation line in the console copies the command to
* the command line
*/
copyToInput: function() {
this.cliView.element.value = this.request.typed;
},
/**
* A double click on an invocation line in the console executes the command
*/
executeRequest: function(ev) {
this.cliView.cli.update({
typed: this.request.typed,
cursor: { start:0, end:0 }
});
this.cliView.cli.exec();
},
hideOutput: function(ev) {
this.output.style.display = 'none';
dom.addCssClass(this.hide, 'cmd_hidden');
dom.removeCssClass(this.show, 'cmd_hidden');
event.stopPropagation(ev);
},
showOutput: function(ev) {
this.output.style.display = 'block';
dom.removeCssClass(this.hide, 'cmd_hidden');
dom.addCssClass(this.show, 'cmd_hidden');
event.stopPropagation(ev);
},
remove: function(ev) {
this.cliView.output.removeChild(this.rowin);
this.cliView.output.removeChild(this.rowout);
event.stopPropagation(ev);
},
onRequestChange: function(ev) {
this.duration.innerHTML = this.request.duration ?
'completed in ' + (this.request.duration / 1000) + ' sec ' :
'';
this.output.innerHTML = '';
this.request.outputs.forEach(function(output) {
var node;
if (typeof output == 'string') {
node = document.createElement('p');
node.innerHTML = output;
} else {
node = output;
}
this.output.appendChild(node);
}, this);
this.cliView.scrollOutputToBottom();
dom.setCssClass(this.output, 'cmd_error', this.request.error);
this.throb.style.display = this.request.completed ? 'none' : 'block';
}
};
exports.RequestView = RequestView;
});
/* ***** 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 DomTemplate.
*
* 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) (original author)
*
* 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('pilot/domtemplate', ['require', 'exports', 'module' ], function(require, exports, module) {
// WARNING: do not 'use_strict' without reading the notes in envEval;
/**
* A templater that allows one to quickly template DOM nodes.
*/
function Templater() {
this.scope = [];
};
/**
* Recursive function to walk the tree processing the attributes as it goes.
* @param node the node to process. If you pass a string in instead of a DOM
* element, it is assumed to be an id for use with document.getElementById()
* @param data the data to use for node processing.
*/
Templater.prototype.processNode = function(node, data) {
if (typeof node === 'string') {
node = document.getElementById(node);
}
if (data === null || data === undefined) {
data = {};
}
this.scope.push(node.nodeName + (node.id ? '#' + node.id : ''));
try {
// Process attributes
if (node.attributes && node.attributes.length) {
// We need to handle 'foreach' and 'if' first because they might stop
// some types of processing from happening, and foreach must come first
// because it defines new data on which 'if' might depend.
if (node.hasAttribute('foreach')) {
this.processForEach(node, data);
return;
}
if (node.hasAttribute('if')) {
if (!this.processIf(node, data)) {
return;
}
}
// Only make the node available once we know it's not going away
data.__element = node;
// It's good to clean up the attributes when we've processed them,
// but if we do it straight away, we mess up the array index
var attrs = Array.prototype.slice.call(node.attributes);
for (var i = 0; i < attrs.length; i++) {
var value = attrs[i].value;
var name = attrs[i].name;
this.scope.push(name);
try {
if (name === 'save') {
// Save attributes are a setter using the node
value = this.stripBraces(value);
this.property(value, data, node);
node.removeAttribute('save');
} else if (name.substring(0, 2) === 'on') {
// Event registration relies on property doing a bind
value = this.stripBraces(value);
var func = this.property(value, data);
if (typeof func !== 'function') {
this.handleError('Expected ' + value +
' to resolve to a function, but got ' + typeof func);
}
node.removeAttribute(name);
var capture = node.hasAttribute('capture' + name.substring(2));
node.addEventListener(name.substring(2), func, capture);
if (capture) {
node.removeAttribute('capture' + name.substring(2));
}
} else {
// Replace references in all other attributes
var self = this;
var newValue = value.replace(/\$\{[^}]*\}/g, function(path) {
return self.envEval(path.slice(2, -1), data, value);
});
// Remove '_' prefix of attribute names so the DOM won't try
// to use them before we've processed the template
if (name.charAt(0) === '_') {
node.removeAttribute(name);
node.setAttribute(name.substring(1), newValue);
} else if (value !== newValue) {
attrs[i].value = newValue;
}
}
} finally {
this.scope.pop();
}
}
}
// Loop through our children calling processNode. First clone them, so the
// set of nodes that we visit will be unaffected by additions or removals.
var childNodes = Array.prototype.slice.call(node.childNodes);
for (var j = 0; j < childNodes.length; j++) {
this.processNode(childNodes[j], data);
}
if (node.nodeType === Node.TEXT_NODE) {
this.processTextNode(node, data);
}
} finally {
this.scope.pop();
}
};
/**
* Handle
*
* @param path An array of strings indicating the path through the data, or
* a string to be cut into an array using split('.')
* @param data An object to look in for the path argument
* @param newValue (optional) If defined, this value will replace the
* original value for the data at the path specified.
* @return The value pointed to by path before any
* newValue is applied.
*/
Templater.prototype.property = function(path, data, newValue) {
this.scope.push(path);
try {
if (typeof path === 'string') {
path = path.split('.');
}
var value = data[path[0]];
if (path.length === 1) {
if (newValue !== undefined) {
data[path[0]] = newValue;
}
if (typeof value === 'function') {
return function() {
return value.apply(data, arguments);
};
}
return value;
}
if (!value) {
this.handleError('Can\'t find path=' + path);
return null;
}
return this.property(path.slice(1), value, newValue);
} finally {
this.scope.pop();
}
};
/**
* Like eval, but that creates a context of the variables in env in
* which the script is evaluated.
* WARNING: This script uses 'with' which is generally regarded to be evil.
* The alternative is to create a Function at runtime that takes X parameters
* according to the X keys in the env object, and then call that function using
* the values in the env object. This is likely to be slow, but workable.
* @param script The string to be evaluated.
* @param env The environment in which to eval the script.
* @param context Optional debugging string in case of failure
* @return The return value of the script, or the error message if the script
* execution failed.
*/
Templater.prototype.envEval = function(script, env, context) {
with (env) {
try {
this.scope.push(context);
return eval(script);
} catch (ex) {
this.handleError('Template error evaluating \'' + script + '\'', ex);
return script;
} finally {
this.scope.pop();
}
}
};
/**
* A generic way of reporting errors, for easy overloading in different
* environments.
* @param message the error message to report.
* @param ex optional associated exception.
*/
Templater.prototype.handleError = function(message, ex) {
this.logError(message);
this.logError('In: ' + this.scope.join(' > '));
if (ex) {
this.logError(ex);
}
};
/**
* A generic way of reporting errors, for easy overloading in different
* environments.
* @param message the error message to report.
*/
Templater.prototype.logError = function(message) {
window.console && window.console.log && console.log(message);
};
exports.Templater = Templater;
});
/* ***** 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):
* Skywriter Team (skywriter@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/commands/basic', ['require', 'exports', 'module' , 'pilot/canon'], function(require, exports, module) {
var canon = require('pilot/canon');
/**
* '!' command
*/
var bangCommandSpec = {
name: 'sh',
description: 'Execute a system command (requires server support)',
params: [
{
name: 'command',
type: 'text',
description: 'The string to send to the os shell.'
}
],
exec: function(env, args, request) {
var req = new XMLHttpRequest();
req.open('GET', '/exec?args=' + args.command, true);
req.onreadystatechange = function(ev) {
if (req.readyState == 4) {
if (req.status == 200) {
request.done('' + req.responseText + '
');
}
}
};
req.send(null);
}
};
var canon = require('pilot/canon');
exports.startup = function(data, reason) {
canon.addCommand(bangCommandSpec);
};
exports.shutdown = function(data, reason) {
canon.removeCommand(bangCommandSpec);
};
});
define("text!cockpit/ui/cli_view.css", [], "" +
"#cockpitInput { padding-left: 16px; }" +
"" +
".cptOutput { overflow: auto; position: absolute; z-index: 999; display: none; }" +
"" +
".cptCompletion { padding: 0; position: absolute; z-index: -1000; }" +
".cptCompletion.VALID { background: #FFF; }" +
".cptCompletion.INCOMPLETE { background: #DDD; }" +
".cptCompletion.INVALID { background: #DDD; }" +
".cptCompletion span { color: #FFF; }" +
".cptCompletion span.INCOMPLETE { color: #DDD; border-bottom: 2px dotted #F80; }" +
".cptCompletion span.INVALID { color: #DDD; border-bottom: 2px dotted #F00; }" +
"span.cptPrompt { color: #66F; font-weight: bold; }" +
"" +
"" +
".cptHints {" +
" color: #000;" +
" position: absolute;" +
" border: 1px solid rgba(230, 230, 230, 0.8);" +
" background: rgba(250, 250, 250, 0.8);" +
" -moz-border-radius-topleft: 10px;" +
" -moz-border-radius-topright: 10px;" +
" border-top-left-radius: 10px; border-top-right-radius: 10px;" +
" z-index: 1000;" +
" padding: 8px;" +
" display: none;" +
"}" +
"" +
".cptFocusPopup { display: block; }" +
".cptFocusPopup.cptNoPopup { display: none; }" +
"" +
".cptHints ul { margin: 0; padding: 0 15px; }" +
"" +
".cptGt { font-weight: bold; font-size: 120%; }" +
"");
define("text!cockpit/ui/request_view.css", [], "" +
".cptRowIn {" +
" display: box; display: -moz-box; display: -webkit-box;" +
" box-orient: horizontal; -moz-box-orient: horizontal; -webkit-box-orient: horizontal;" +
" box-align: center; -moz-box-align: center; -webkit-box-align: center;" +
" color: #333;" +
" background-color: #EEE;" +
" width: 100%;" +
" font-family: consolas, courier, monospace;" +
"}" +
".cptRowIn > * { padding-left: 2px; padding-right: 2px; }" +
".cptRowIn > img { cursor: pointer; }" +
".cptHover { display: none; }" +
".cptRowIn:hover > .cptHover { display: block; }" +
".cptRowIn:hover > .cptHover.cptHidden { display: none; }" +
".cptOutTyped {" +
" box-flex: 1; -moz-box-flex: 1; -webkit-box-flex: 1;" +
" font-weight: bold; color: #000; font-size: 120%;" +
"}" +
".cptRowOutput { padding-left: 10px; line-height: 1.2em; }" +
".cptRowOutput strong," +
".cptRowOutput b," +
".cptRowOutput th," +
".cptRowOutput h1," +
".cptRowOutput h2," +
".cptRowOutput h3 { color: #000; }" +
".cptRowOutput a { font-weight: bold; color: #666; text-decoration: none; }" +
".cptRowOutput a: hover { text-decoration: underline; cursor: pointer; }" +
".cptRowOutput input[type=password]," +
".cptRowOutput input[type=text]," +
".cptRowOutput textarea {" +
" color: #000; font-size: 120%;" +
" background: transparent; padding: 3px;" +
" border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px;" +
"}" +
".cptRowOutput table," +
".cptRowOutput td," +
".cptRowOutput th { border: 0; padding: 0 2px; }" +
".cptRowOutput .right { text-align: right; }" +
"");
define("text!cockpit/ui/request_view.html", [], "" +
"