/* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ /* * * * * * * * *********************************** WARNING *********************************** * * Do not edit this file without understanding where it comes from, * Your changes are likely to be overwritten without warning. * * For more information on GCLI see: * - https://github.com/mozilla/gcli/blob/master/docs/index.md * - https://wiki.mozilla.org/DevTools/Features/GCLI * * The original source for this file is: * https://github.com/mozilla/gcli/ * * This build of GCLI for Firefox comes from 4 bits of code: * - prefix-gcli.jsm: Initial commentary and EXPORTED_SYMBOLS * - console.js: Support code common to web content that is not part of the * default firefox chrome environment and is easy to shim. * - mini_require: A very basic commonjs AMD (Asynchronous Modules Definition) * 'require' implementation (which is just good enough to load GCLI). For * more, see http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition. * This alleviates the need for requirejs (http://requirejs.org/) which is * used when running in the browser. This code is provided by dryice. * - A build of GCLI itself, packaged using dryice * - suffix-gcli.jsm - code to require the gcli object for EXPORTED_SYMBOLS. * * See Makefile.dryice.js for more details of this build. * For more details on dryice, see the https://github.com/mozilla/dryice * ******************************************************************************* * * * * * * * * * */ /////////////////////////////////////////////////////////////////////////////// var EXPORTED_SYMBOLS = [ "gcli" ]; /** * Expose a Node object. This allows us to use the Node constants without * resorting to hardcoded numbers */ var Node = Components.interfaces.nsIDOMNode; Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); /** * Define setTimeout and clearTimeout to match the browser functions */ var setTimeout; var clearTimeout; (function() { /** * The next value to be returned by setTimeout */ var nextID = 1; /** * The map of outstanding timeouts */ var timers = {}; /** * Object to be passed to Timer.initWithCallback() */ function TimerCallback(callback) { this._callback = callback; var interfaces = [ Components.interfaces.nsITimerCallback ]; this.QueryInterface = XPCOMUtils.generateQI(interfaces); } TimerCallback.prototype.notify = function(timer) { try { for (var timerID in timers) { if (timers[timerID] === timer) { delete timers[timerID]; break; } } this._callback.apply(null, []); } catch (ex) { console.error(ex); } }; /** * Executes a code snippet or a function after specified delay. * This is designed to have the same interface contract as the browser * function. * @param callback is the function you want to execute after the delay. * @param delay is the number of milliseconds that the function call should * be delayed by. Note that the actual delay may be longer, see Notes below. * @return the ID of the timeout, which can be used later with * window.clearTimeout. */ setTimeout = function setTimeout(callback, delay) { var timer = Components.classes["@mozilla.org/timer;1"] .createInstance(Components.interfaces.nsITimer); var timerID = nextID++; timers[timerID] = timer; timer.initWithCallback(new TimerCallback(callback), delay, timer.TYPE_ONE_SHOT); return timerID; }; /** * Clears the delay set by window.setTimeout() and prevents the callback from * being executed (if it hasn't been executed already) * @param timerID the ID of the timeout you wish to clear, as returned by * window.setTimeout(). */ clearTimeout = function clearTimeout(timerID) { var timer = timers[timerID]; if (timer) { timer.cancel(); delete timers[timerID]; } }; })(); /** * This creates a console object that somewhat replicates Firebug's console * object. It currently writes to dump(), but should write to the web * console's chrome error section (when it has one) */ var console = {}; (function() { /** * String utility to ensure that strings are a specified length. Strings * that are too long are truncated to the max length and the last char is * set to "_". Strings that are too short are left padded with spaces. * * @param {string} aStr * The string to format to the correct length * @param {number} aMaxLen * The maximum allowed length of the returned string * @param {number} aMinLen (optional) * The minimum allowed length of the returned string. If undefined, * then aMaxLen will be used * @param {object} aOptions (optional) * An object allowing format customization. The only customization * allowed currently is 'truncate' which can take the value "start" to * truncate strings from the start as opposed to the end. * @return {string} * The original string formatted to fit the specified lengths */ function fmt(aStr, aMaxLen, aMinLen, aOptions) { if (aMinLen == null) { aMinLen = aMaxLen; } if (aStr == null) { aStr = ""; } if (aStr.length > aMaxLen) { if (aOptions && aOptions.truncate == "start") { return "_" + aStr.substring(aStr.length - aMaxLen + 1); } else { return aStr.substring(0, aMaxLen - 1) + "_"; } } if (aStr.length < aMinLen) { return Array(aMinLen - aStr.length + 1).join(" ") + aStr; } return aStr; } /** * Utility to extract the constructor name of an object. * Object.toString gives: "[object ?????]"; we want the "?????". * * @param {object} aObj * The object from which to extract the constructor name * @return {string} * The constructor name */ function getCtorName(aObj) { return Object.prototype.toString.call(aObj).slice(8, -1); } /** * A single line stringification of an object designed for use by humans * * @param {any} aThing * The object to be stringified * @return {string} * A single line representation of aThing, which will generally be at * most 80 chars long */ function stringify(aThing) { if (aThing === undefined) { return "undefined"; } if (aThing === null) { return "null"; } if (typeof aThing == "object") { var type = getCtorName(aThing); if (type == "XULElement") { return debugElement(aThing); } type = (type == "Object" ? "" : type + " "); var json; try { json = JSON.stringify(aThing); } catch (ex) { // Can't use a real ellipsis here, because cmd.exe isn't unicode-enabled json = "{" + Object.keys(aThing).join(":..,") + ":.., " + "}"; } return type + fmt(json, 50, 0); } var str = aThing.toString(); //.replace(/\s+/g, " "); return fmt(str, 80, 0); } /** * Create a simple debug representation of a given element. * * @param {nsIDOMElement} aElement * The element to debug * @return {string} * A simple single line representation of aElement */ function debugElement(aElement) { return "<" + aElement.tagName + (aElement.id ? "#" + aElement.id : "") + (aElement.className ? "." + aElement.className.split(" ").join(" .") : "") + ">"; } /** * A multi line stringification of an object, designed for use by humans * * @param {any} aThing * The object to be stringified * @return {string} * A multi line representation of aThing */ function log(aThing) { if (aThing === null) { return "null\n"; } if (aThing === undefined) { return "undefined\n"; } if (typeof aThing == "object") { var reply = ""; var type = getCtorName(aThing); if (type == "Error") { reply += " " + aThing.message + "\n"; reply += logProperty("stack", aThing.stack); } else if (type == "XULElement") { reply += " " + debugElement(aThing) + " (XUL)\n"; } else { var keys = Object.getOwnPropertyNames(aThing); if (keys.length > 0) { reply += type + "\n"; keys.forEach(function(aProp) { reply += logProperty(aProp, aThing[aProp]); }, this); } else { reply += type + " (enumerated with for-in)\n"; var prop; for (prop in aThing) { reply += logProperty(prop, aThing[prop]); } } } return reply; } return " " + aThing.toString() + "\n"; } /** * Helper for log() which converts a property/value pair into an output * string * * @param {string} aProp * The name of the property to include in the output string * @param {object} aValue * Value assigned to aProp to be converted to a single line string * @return {string} * Multi line output string describing the property/value pair */ function logProperty(aProp, aValue) { var reply = ""; if (aProp == "stack" && typeof value == "string") { var trace = parseStack(aValue); reply += formatTrace(trace); } else { reply += " - " + aProp + " = " + stringify(aValue) + "\n"; } return reply; } /** * Parse a stack trace, returning an array of stack frame objects, where * each has file/line/call members * * @param {string} aStack * The serialized stack trace * @return {object[]} * Array of { file: "...", line: NNN, call: "..." } objects */ function parseStack(aStack) { var trace = []; aStack.split("\n").forEach(function(line) { if (!line) { return; } var at = line.lastIndexOf("@"); var posn = line.substring(at + 1); trace.push({ file: posn.split(":")[0], line: posn.split(":")[1], call: line.substring(0, at) }); }, this); return trace; } /** * parseStack() takes output from an exception from which it creates the an * array of stack frame objects, this has the same output but using data from * Components.stack * * @param {string} aFrame * The stack frame from which to begin the walk * @return {object[]} * Array of { file: "...", line: NNN, call: "..." } objects */ function getStack(aFrame) { if (!aFrame) { aFrame = Components.stack.caller; } var trace = []; while (aFrame) { trace.push({ file: aFrame.filename, line: aFrame.lineNumber, call: aFrame.name }); aFrame = aFrame.caller; } return trace; } /** * Take the output from parseStack() and convert it to nice readable * output * * @param {object[]} aTrace * Array of trace objects as created by parseStack() * @return {string} Multi line report of the stack trace */ function formatTrace(aTrace) { var reply = ""; aTrace.forEach(function(frame) { reply += fmt(frame.file, 20, 20, { truncate: "start" }) + " " + fmt(frame.line, 5, 5) + " " + fmt(frame.call, 75, 75) + "\n"; }); return reply; } /** * Create a function which will output a concise level of output when used * as a logging function * * @param {string} aLevel * A prefix to all output generated from this function detailing the * level at which output occurred * @return {function} * A logging function * @see createMultiLineDumper() */ function createDumper(aLevel) { return function() { var args = Array.prototype.slice.call(arguments, 0); var data = args.map(function(arg) { return stringify(arg); }); dump(aLevel + ": " + data.join(", ") + "\n"); }; } /** * Create a function which will output more detailed level of output when * used as a logging function * * @param {string} aLevel * A prefix to all output generated from this function detailing the * level at which output occurred * @return {function} * A logging function * @see createDumper() */ function createMultiLineDumper(aLevel) { return function() { dump(aLevel + "\n"); var args = Array.prototype.slice.call(arguments, 0); args.forEach(function(arg) { dump(log(arg)); }); }; } /** * Build out the console object */ console.debug = createMultiLineDumper("debug"); console.log = createDumper("log"); console.info = createDumper("info"); console.warn = createDumper("warn"); console.error = createMultiLineDumper("error"); console.trace = function Console_trace() { var trace = getStack(Components.stack.caller); dump(formatTrace(trace) + "\n"); }, console.clear = function Console_clear() {}; console.dir = createMultiLineDumper("dir"); console.dirxml = createMultiLineDumper("dirxml"); console.group = createDumper("group"); console.groupEnd = createDumper("groupEnd"); })(); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ /** * Define a module along with a payload. * @param moduleName Name for the payload * @param deps Ignored. For compatibility with CommonJS AMD Spec * @param payload Function with (require, exports, module) params */ function define(moduleName, deps, payload) { if (typeof moduleName != "string") { console.error(this.depth + " Error: Module name is not a string."); console.trace(); return; } if (arguments.length == 2) { payload = deps; } if (define.debugDependencies) { console.log("define: " + moduleName + " -> " + payload.toString() .slice(0, 40).replace(/\n/, '\\n').replace(/\r/, '\\r') + "..."); } if (moduleName in define.modules) { console.error(this.depth + " Error: Redefining module: " + moduleName); } define.modules[moduleName] = payload; } /** * The global store of un-instantiated modules */ define.modules = {}; /** * Should we console.log on module definition/instantiation/requirement? */ define.debugDependencies = false; /** * Self executing function in which Domain is defined, and attached to define */ (function() { /** * We invoke require() in the context of a Domain so we can have multiple * sets of modules running separate from each other. * This contrasts with JSMs which are singletons, Domains allows us to * optionally load a CommonJS module twice with separate data each time. * Perhaps you want 2 command lines with a different set of commands in each, * for example. */ function Domain() { this.modules = {}; if (define.debugDependencies) { this.depth = ""; } } /** * Lookup module names and resolve them by calling the definition function if * needed. * There are 2 ways to call this, either with an array of dependencies and a * callback to call when the dependencies are found (which can happen * asynchronously in an in-page context) or with a single string an no * callback where the dependency is resolved synchronously and returned. * The API is designed to be compatible with the CommonJS AMD spec and * RequireJS. * @param deps A name, or array of names for the payload * @param callback Function to call when the dependencies are resolved * @return The module required or undefined for array/callback method */ Domain.prototype.require = function(deps, callback) { if (Array.isArray(deps)) { var params = deps.map(function(dep) { return this.lookup(dep); }, this); if (callback) { callback.apply(null, params); } return undefined; } else { return this.lookup(deps); } }; /** * Lookup module names and resolve them by calling the definition function if * needed. * @param moduleName A name for the payload to lookup * @return The module specified by aModuleName or null if not found */ Domain.prototype.lookup = function(moduleName) { if (moduleName in this.modules) { var module = this.modules[moduleName]; if (define.debugDependencies) { console.log(this.depth + " Using module: " + moduleName); } return module; } if (!(moduleName in define.modules)) { console.error(this.depth + " Missing module: " + moduleName); return null; } var module = define.modules[moduleName]; if (define.debugDependencies) { console.log(this.depth + " Compiling module: " + moduleName); } if (typeof module == "function") { if (define.debugDependencies) { this.depth += "."; } var exports = {}; try { module(this.require.bind(this), exports, { id: moduleName, uri: "" }); } catch (ex) { console.error("Error using module: " + moduleName, ex); throw ex; } module = exports; if (define.debugDependencies) { this.depth = this.depth.slice(0, -1); } } // cache the resulting module object for next time this.modules[moduleName] = module; return module; }; /** * Expose the Domain constructor and a global domain (on the define function * to avoid exporting more than we need. This is a common pattern with * require systems) */ define.Domain = Domain; define.globalDomain = new Domain(); })(); /** * Expose a default require function which is the require of the global * sandbox to make it easy to use. */ var require = define.globalDomain.require.bind(define.globalDomain); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ define('gcli/index', ['require', 'exports', 'module' , 'gcli/canon', 'gcli/types/basic', 'gcli/types/javascript', 'gcli/types/node', 'gcli/cli', 'gcli/ui/inputter', 'gcli/ui/arg_fetch', 'gcli/ui/menu', 'gcli/ui/focus'], function(require, exports, module) { // The API for use by command authors exports.addCommand = require('gcli/canon').addCommand; exports.removeCommand = require('gcli/canon').removeCommand; // Internal startup process. Not exported require('gcli/types/basic').startup(); require('gcli/types/javascript').startup(); require('gcli/types/node').startup(); require('gcli/cli').startup(); var Requisition = require('gcli/cli').Requisition; var cli = require('gcli/cli'); var Inputter = require('gcli/ui/inputter').Inputter; var ArgFetcher = require('gcli/ui/arg_fetch').ArgFetcher; var CommandMenu = require('gcli/ui/menu').CommandMenu; var FocusManager = require('gcli/ui/focus').FocusManager; var jstype = require('gcli/types/javascript'); var nodetype = require('gcli/types/node'); /** * API for use by HUDService only. * This code is internal and subject to change without notice. */ exports._internal = { require: require, define: define, console: console, /** * createView() for Firefox requires an options object with the following * members: * - contentDocument: From the window of the attached tab * - chromeDocument: GCLITerm.document * - environment.hudId: GCLITerm.hudId * - jsEnvironment.globalObject: 'window' * - jsEnvironment.evalFunction: 'eval' in a sandbox * - inputElement: GCLITerm.inputNode * - completeElement: GCLITerm.completeNode * - gcliTerm: GCLITerm * - hintElement: GCLITerm.hintNode * - inputBackgroundElement: GCLITerm.inputStack */ createView: function(opts) { opts.autoHide = true; opts.requisition = new Requisition(opts.environment, opts.chromeDocument); opts.completionPrompt = ''; jstype.setGlobalObject(opts.jsEnvironment.globalObject); nodetype.setDocument(opts.contentDocument); cli.setEvalFunction(opts.jsEnvironment.evalFunction); // Create a FocusManager for the various parts to register with if (!opts.focusManager) { opts.debug = true; opts.focusManager = new FocusManager({ document: opts.chromeDocument }); } opts.inputter = new Inputter(opts); opts.inputter.update(); if (opts.gcliTerm) { opts.focusManager.onFocus.add(opts.gcliTerm.show, opts.gcliTerm); opts.focusManager.onBlur.add(opts.gcliTerm.hide, opts.gcliTerm); opts.focusManager.addMonitoredElement(opts.gcliTerm.hintNode, 'gcliTerm'); } if (opts.hintElement) { opts.menu = new CommandMenu(opts.chromeDocument, opts.requisition); opts.hintElement.appendChild(opts.menu.element); opts.argFetcher = new ArgFetcher(opts.chromeDocument, opts.requisition); opts.hintElement.appendChild(opts.argFetcher.element); opts.menu.onCommandChange(); } }, /** * Undo the effects of createView() to prevent memory leaks */ removeView: function(opts) { opts.hintElement.removeChild(opts.menu.element); opts.menu.destroy(); opts.hintElement.removeChild(opts.argFetcher.element); opts.argFetcher.destroy(); opts.inputter.destroy(); opts.focusManager.removeMonitoredElement(opts.gcliTerm.hintNode, 'gcliTerm'); opts.focusManager.onFocus.remove(opts.gcliTerm.show, opts.gcliTerm); opts.focusManager.onBlur.remove(opts.gcliTerm.hide, opts.gcliTerm); opts.focusManager.destroy(); cli.unsetEvalFunction(); nodetype.unsetDocument(); jstype.unsetGlobalObject(); opts.requisition.destroy(); }, commandOutputManager: require('gcli/canon').commandOutputManager }; }); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ define('gcli/canon', ['require', 'exports', 'module' , 'gcli/util', 'gcli/l10n', 'gcli/types', 'gcli/types/basic'], function(require, exports, module) { var canon = exports; var createEvent = require('gcli/util').createEvent; var l10n = require('gcli/l10n'); var types = require('gcli/types'); var Status = require('gcli/types').Status; var BooleanType = require('gcli/types/basic').BooleanType; /** * A lookup hash of our registered commands */ var commands = {}; /** * A sorted list of command names, we regularly want them in order, so pre-sort */ var commandNames = []; /** * Implement the localization algorithm for any documentation objects (i.e. * description and manual) in a command. * @param data The data assigned to a description or manual property * @param onUndefined If data == null, should we return the data untouched or * lookup a 'we don't know' key in it's place. */ function lookup(data, onUndefined) { if (data == null) { if (onUndefined) { return l10n.lookup(onUndefined); } return data; } if (typeof data === 'string') { return data; } if (typeof data === 'object') { if (data.key) { return l10n.lookup(data.key); } var locales = l10n.getPreferredLocales(); var translated; locales.some(function(locale) { translated = data[locale]; return translated != null; }); if (translated != null) { return translated; } console.error('Can\'t find locale in descriptions: ' + 'locales=' + JSON.stringify(locales) + ', ' + 'description=' + JSON.stringify(data)); return '(No description)'; } return l10n.lookup(onUndefined); } /** * The command object is mostly just setup around a commandSpec (as passed to * #addCommand()). */ function Command(commandSpec) { Object.keys(commandSpec).forEach(function(key) { this[key] = commandSpec[key]; }, this); if (!this.name) { throw new Error('All registered commands must have a name'); } if (this.params == null) { this.params = []; } if (!Array.isArray(this.params)) { throw new Error('command.params must be an array in ' + this.name); } this.description = 'description' in this ? this.description : undefined; this.description = lookup(this.description, 'canonDescNone'); this.manual = 'manual' in this ? this.manual : undefined; this.manual = lookup(this.manual); // At this point this.params has nested param groups. We want to flatten it // out and replace the param object literals with Parameter objects var paramSpecs = this.params; this.params = []; // Track if the user is trying to mix default params and param groups. // All the non-grouped parameters must come before all the param groups // because non-grouped parameters can be assigned positionally, so their // index is important. We don't want 'holes' in the order caused by // parameter groups. var usingGroups = false; // In theory this could easily be made recursive, so param groups could // contain nested param groups. Current thinking is that the added // complexity for the UI probably isn't worth it, so this implementation // prevents nesting. paramSpecs.forEach(function(spec) { if (!spec.group) { if (usingGroups) { console.error('Parameters can\'t come after param groups.' + ' Ignoring ' + this.name + '/' + spec.name); } else { var param = new Parameter(spec, this, null); this.params.push(param); } } else { spec.params.forEach(function(ispec) { var param = new Parameter(ispec, this, spec.group); this.params.push(param); }, this); usingGroups = true; } }, this); } canon.Command = Command; /** * A wrapper for a paramSpec so we can sort out shortened versions names for * option switches */ function Parameter(paramSpec, command, groupName) { this.command = command || { name: 'unnamed' }; Object.keys(paramSpec).forEach(function(key) { this[key] = paramSpec[key]; }, this); this.description = 'description' in this ? this.description : undefined; this.description = lookup(this.description, 'canonDescNone'); this.manual = 'manual' in this ? this.manual : undefined; this.manual = lookup(this.manual); this.groupName = groupName; if (!this.name) { throw new Error('In ' + this.command.name + ': all params must have a name'); } var typeSpec = this.type; this.type = types.getType(typeSpec); if (this.type == null) { console.error('Known types: ' + types.getTypeNames().join(', ')); throw new Error('In ' + this.command.name + '/' + this.name + ': can\'t find type for: ' + JSON.stringify(typeSpec)); } // boolean parameters have an implicit defaultValue:false, which should // not be changed. See the docs. if (this.type instanceof BooleanType) { if ('defaultValue' in this) { console.error('In ' + this.command.name + '/' + this.name + ': boolean parameters can not have a defaultValue.' + ' Ignoring'); } this.defaultValue = false; } // Check the defaultValue for validity. // Both undefined and null get a pass on this test. undefined is used when // there is no defaultValue, and null is used when the parameter is // optional, neither are required to parse and stringify. if (this.defaultValue != null) { try { var defaultText = this.type.stringify(this.defaultValue); var defaultConversion = this.type.parseString(defaultText); if (defaultConversion.getStatus() !== Status.VALID) { console.error('In ' + this.command.name + '/' + this.name + ': Error round tripping defaultValue. status = ' + defaultConversion.getStatus()); } } catch (ex) { console.error('In ' + this.command.name + '/' + this.name + ': ' + ex); } } } /** * Does the given name uniquely identify this param (among the other params * in this command) * @param name The name to check */ Parameter.prototype.isKnownAs = function(name) { if (name === '--' + this.name) { return true; } return false; }; /** * Is the user required to enter data for this parameter? (i.e. has * defaultValue been set to something other than undefined) */ Parameter.prototype.isDataRequired = function() { return this.defaultValue === undefined; }; /** * Are we allowed to assign data to this parameter using positional * parameters? */ Parameter.prototype.isPositionalAllowed = function() { return this.groupName == null; }; canon.Parameter = Parameter; /** * Add a command to the canon of known commands. * This function is exposed to the outside world (via gcli/index). It is * documented in docs/index.md for all the world to see. * @param commandSpec The command and its metadata. * @return The new command */ canon.addCommand = function addCommand(commandSpec) { var command = new Command(commandSpec); commands[commandSpec.name] = command; commandNames.push(commandSpec.name); commandNames.sort(); canon.canonChange(); return command; }; /** * Remove an individual command. The opposite of #addCommand(). * @param commandOrName Either a command name or the command itself. */ canon.removeCommand = function removeCommand(commandOrName) { var name = typeof commandOrName === 'string' ? commandOrName : commandOrName.name; delete commands[name]; commandNames = commandNames.filter(function(test) { return test !== name; }); canon.canonChange(); }; /** * Retrieve a command by name * @param name The name of the command to retrieve */ canon.getCommand = function getCommand(name) { return commands[name]; }; /** * Get an array of all the registered commands. */ canon.getCommands = function getCommands() { // return Object.values(commands); return Object.keys(commands).map(function(name) { return commands[name]; }, this); }; /** * Get an array containing the names of the registered commands. */ canon.getCommandNames = function getCommandNames() { return commandNames.slice(0); }; /** * Enable people to be notified of changes to the list of commands */ canon.canonChange = createEvent('canon.canonChange'); /** * CommandOutputManager stores the output objects generated by executed * commands. * * CommandOutputManager is exposed (via canon.commandOutputManager) to the the * outside world and could (but shouldn't) be used before gcli.startup() has * been called. This could should be defensive to that where possible, and we * should certainly document if the use of it or similar will fail if used too * soon. */ function CommandOutputManager() { this._event = createEvent('CommandOutputManager'); } /** * Call this method to notify the manager (and therefore all listeners) of a * new or updated command output. * @param output The command output object that has been created or updated. */ CommandOutputManager.prototype.sendCommandOutput = function(output) { this._event({ output: output }); }; /** * Register a function to be called whenever there is a new command output * object. */ CommandOutputManager.prototype.addListener = function(fn, ctx) { this._event.add(fn, ctx); }; /** * Undo the effects of CommandOutputManager.addListener() */ CommandOutputManager.prototype.removeListener = function(fn, ctx) { this._event.remove(fn, ctx); }; canon.CommandOutputManager = CommandOutputManager; /** * We maintain a global command output manager for the majority case where * there is only one important set of outputs. */ canon.commandOutputManager = new CommandOutputManager(); }); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ define('gcli/util', ['require', 'exports', 'module' ], function(require, exports, module) { /* * This module is a Pilot-Lite. It exports a number of objects that replicate * parts of the Pilot project. It aims to be mostly API compatible, while * removing the submodule complexity and helping us make things work inside * Firefox. * The Pilot compatible exports are: console/dom/event * * In addition it contains a small event library similar to EventEmitter but * which makes it harder to mistake the event in use. */ //------------------------------------------------------------------------------ /** * Create an event. * For use as follows: * function Hat() { * this.putOn = createEvent(); * ... * } * Hat.prototype.adorn = function(person) { * this.putOn({ hat: hat, person: person }); * ... * } * * var hat = new Hat(); * hat.putOn.add(function(ev) { * console.log('The hat ', ev.hat, ' has is worn by ', ev.person); * }, scope); * @param name Optional name that helps us work out what event this * is when debugging. */ exports.createEvent = function(name) { var handlers = []; /** * This is how the event is triggered. * @param ev The event object to be passed to the event listeners */ var event = function(ev) { // Use for rather than forEach because it step debugs better, which is // important for debugging events for (var i = 0; i < handlers.length; i++) { var handler = handlers[i]; handler.func.call(handler.scope, ev); } }; /** * Add a new handler function * @param func The function to call when this event is triggered * @param scope Optional 'this' object for the function call */ event.add = function(func, scope) { handlers.push({ func: func, scope: scope }); }; /** * Remove a handler function added through add(). Both func and scope must * be strict equals (===) the values used in the call to add() * @param func The function to call when this event is triggered * @param scope Optional 'this' object for the function call */ event.remove = function(func, scope) { handlers = handlers.filter(function(test) { return test.func !== func && test.scope !== scope; }); }; /** * Remove all handlers. * Reset the state of this event back to it's post create state */ event.removeAll = function() { handlers = []; }; return event; }; //------------------------------------------------------------------------------ var dom = {}; var NS_XHTML = 'http://www.w3.org/1999/xhtml'; /** * Pass-through to createElement or createElementNS * @param doc The document in which to create the element * @param tag The name of the tag to create * @param ns Custom namespace, HTML/XHTML is assumed if this is missing * @returns The created element */ dom.createElement = function(doc, tag, ns) { // If we've not been given a namespace, but the document is XML, then we // use an XHTML namespace, otherwise we use HTML if (ns == null && doc.xmlVersion != null) { ns = NS_XHTML; } if (ns == null) { return doc.createElement(tag); } else { return doc.createElementNS(ns, tag); } }; /** * Remove all the child nodes from this node * @param el The element that should have it's children removed */ dom.clearElement = function(el) { while (el.hasChildNodes()) { el.removeChild(el.firstChild); } }; if (this.document && !this.document.documentElement.classList) { /** * Is the given element marked with the given CSS class? */ dom.hasCssClass = function(el, name) { var classes = el.className.split(/\s+/g); return classes.indexOf(name) !== -1; }; /** * Add a CSS class to the list of classes on the given node */ dom.addCssClass = function(el, name) { if (!dom.hasCssClass(el, name)) { el.className += ' ' + name; } }; /** * Remove a CSS class from the list of classes on the given node */ dom.removeCssClass = function(el, name) { var classes = el.className.split(/\s+/g); while (true) { var index = classes.indexOf(name); if (index == -1) { break; } classes.splice(index, 1); } el.className = classes.join(' '); }; /** * Add the named CSS class from the element if it is not already present or * remove it if is present. */ dom.toggleCssClass = function(el, name) { var classes = el.className.split(/\s+/g), add = true; while (true) { var index = classes.indexOf(name); if (index == -1) { break; } add = false; classes.splice(index, 1); } if (add) { classes.push(name); } el.className = classes.join(' '); return add; }; } else { /* * classList shim versions of methods above. * See the functions above for documentation */ dom.hasCssClass = function(el, name) { return el.classList.contains(name); }; dom.addCssClass = function(el, name) { el.classList.add(name); }; dom.removeCssClass = function(el, name) { el.classList.remove(name); }; dom.toggleCssClass = function(el, name) { return el.classList.toggle(name); }; } /** * Add or remove a CSS class from the list of classes on the given node * depending on the value of include */ dom.setCssClass = function(node, className, include) { if (include) { dom.addCssClass(node, className); } else { dom.removeCssClass(node, className); } }; /** * Create a style element in the document head, and add the given CSS text to * it. * @param cssText The CSS declarations to append * @param doc The document element to work from */ dom.importCss = function(cssText, doc) { doc = doc || document; var style = dom.createElement(doc, 'style'); style.appendChild(doc.createTextNode(cssText)); var head = doc.getElementsByTagName('head')[0] || doc.documentElement; head.appendChild(style); return style; }; /** * Shim for window.getComputedStyle */ dom.computedStyle = function(element, style) { var win = element.ownerDocument.defaultView; if (win.getComputedStyle) { var styles = win.getComputedStyle(element, '') || {}; return styles[style] || ''; } else { return element.currentStyle[style]; } }; /** * Using setInnerHtml(foo) rather than innerHTML = foo allows us to enable * tweaks in XHTML documents. */ dom.setInnerHtml = function(el, html) { if (!this.document || el.namespaceURI === NS_XHTML) { try { dom.clearElement(el); var range = el.ownerDocument.createRange(); html = '
' + html + '
'; el.appendChild(range.createContextualFragment(html)); } catch (ex) { el.innerHTML = html; } } else { el.innerHTML = html; } }; /** * Shim to textarea.selectionStart */ dom.getSelectionStart = function(textarea) { try { return textarea.selectionStart || 0; } catch (e) { return 0; } }; /** * Shim to textarea.selectionStart */ dom.setSelectionStart = function(textarea, start) { return textarea.selectionStart = start; }; /** * Shim to textarea.selectionEnd */ dom.getSelectionEnd = function(textarea) { try { return textarea.selectionEnd || 0; } catch (e) { return 0; } }; /** * Shim to textarea.selectionEnd */ dom.setSelectionEnd = function(textarea, end) { return textarea.selectionEnd = end; }; exports.dom = dom; //------------------------------------------------------------------------------ /** * Various event utilities */ var event = {}; /** * Shim for lack of addEventListener on old IE. */ event.addListener = function(elem, type, callback) { if (elem.addEventListener) { return elem.addEventListener(type, callback, false); } if (elem.attachEvent) { var wrapper = function() { callback(window.event); }; callback._wrapper = wrapper; elem.attachEvent('on' + type, wrapper); } }; /** * Shim for lack of removeEventListener on old IE. */ event.removeListener = function(elem, type, callback) { if (elem.removeEventListener) { return elem.removeEventListener(type, callback, false); } if (elem.detachEvent) { elem.detachEvent('on' + type, callback._wrapper || callback); } }; /** * Prevents propagation and clobbers the default action of the passed event */ event.stopEvent = function(e) { event.stopPropagation(e); if (e.preventDefault) { e.preventDefault(); } return false; }; /** * Prevents propagation of the event */ event.stopPropagation = function(e) { if (e.stopPropagation) { e.stopPropagation(); } else { e.cancelBubble = true; } }; /** * Keyboard handling is a mess. http://unixpapa.com/js/key.html * It would be good to use DOM L3 Keyboard events, * http://www.w3.org/TR/2010/WD-DOM-Level-3-Events-20100907/#events-keyboardevents * however only Webkit supports them, and there isn't a shim on Monernizr: * https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-browser-Polyfills * and when the code that uses this KeyEvent was written, nothing was clear, * so instead, we're using this unmodern shim: * http://stackoverflow.com/questions/5681146/chrome-10-keyevent-or-something-similar-to-firefoxs-keyevent * See BUG 664991: GCLI's keyboard handling should be updated to use DOM-L3 * https://bugzilla.mozilla.org/show_bug.cgi?id=664991 */ if ('KeyEvent' in this) { event.KeyEvent = this.KeyEvent; } else { event.KeyEvent = { DOM_VK_CANCEL: 3, DOM_VK_HELP: 6, DOM_VK_BACK_SPACE: 8, DOM_VK_TAB: 9, DOM_VK_CLEAR: 12, DOM_VK_RETURN: 13, DOM_VK_ENTER: 14, DOM_VK_SHIFT: 16, DOM_VK_CONTROL: 17, DOM_VK_ALT: 18, DOM_VK_PAUSE: 19, DOM_VK_CAPS_LOCK: 20, DOM_VK_ESCAPE: 27, DOM_VK_SPACE: 32, DOM_VK_PAGE_UP: 33, DOM_VK_PAGE_DOWN: 34, DOM_VK_END: 35, DOM_VK_HOME: 36, DOM_VK_LEFT: 37, DOM_VK_UP: 38, DOM_VK_RIGHT: 39, DOM_VK_DOWN: 40, DOM_VK_PRINTSCREEN: 44, DOM_VK_INSERT: 45, DOM_VK_DELETE: 46, DOM_VK_0: 48, DOM_VK_1: 49, DOM_VK_2: 50, DOM_VK_3: 51, DOM_VK_4: 52, DOM_VK_5: 53, DOM_VK_6: 54, DOM_VK_7: 55, DOM_VK_8: 56, DOM_VK_9: 57, DOM_VK_SEMICOLON: 59, DOM_VK_EQUALS: 61, DOM_VK_A: 65, DOM_VK_B: 66, DOM_VK_C: 67, DOM_VK_D: 68, DOM_VK_E: 69, DOM_VK_F: 70, DOM_VK_G: 71, DOM_VK_H: 72, DOM_VK_I: 73, DOM_VK_J: 74, DOM_VK_K: 75, DOM_VK_L: 76, DOM_VK_M: 77, DOM_VK_N: 78, DOM_VK_O: 79, DOM_VK_P: 80, DOM_VK_Q: 81, DOM_VK_R: 82, DOM_VK_S: 83, DOM_VK_T: 84, DOM_VK_U: 85, DOM_VK_V: 86, DOM_VK_W: 87, DOM_VK_X: 88, DOM_VK_Y: 89, DOM_VK_Z: 90, DOM_VK_CONTEXT_MENU: 93, DOM_VK_NUMPAD0: 96, DOM_VK_NUMPAD1: 97, DOM_VK_NUMPAD2: 98, DOM_VK_NUMPAD3: 99, DOM_VK_NUMPAD4: 100, DOM_VK_NUMPAD5: 101, DOM_VK_NUMPAD6: 102, DOM_VK_NUMPAD7: 103, DOM_VK_NUMPAD8: 104, DOM_VK_NUMPAD9: 105, DOM_VK_MULTIPLY: 106, DOM_VK_ADD: 107, DOM_VK_SEPARATOR: 108, DOM_VK_SUBTRACT: 109, DOM_VK_DECIMAL: 110, DOM_VK_DIVIDE: 111, DOM_VK_F1: 112, DOM_VK_F2: 113, DOM_VK_F3: 114, DOM_VK_F4: 115, DOM_VK_F5: 116, DOM_VK_F6: 117, DOM_VK_F7: 118, DOM_VK_F8: 119, DOM_VK_F9: 120, DOM_VK_F10: 121, DOM_VK_F11: 122, DOM_VK_F12: 123, DOM_VK_F13: 124, DOM_VK_F14: 125, DOM_VK_F15: 126, DOM_VK_F16: 127, DOM_VK_F17: 128, DOM_VK_F18: 129, DOM_VK_F19: 130, DOM_VK_F20: 131, DOM_VK_F21: 132, DOM_VK_F22: 133, DOM_VK_F23: 134, DOM_VK_F24: 135, DOM_VK_NUM_LOCK: 144, DOM_VK_SCROLL_LOCK: 145, DOM_VK_COMMA: 188, DOM_VK_PERIOD: 190, DOM_VK_SLASH: 191, DOM_VK_BACK_QUOTE: 192, DOM_VK_OPEN_BRACKET: 219, DOM_VK_BACK_SLASH: 220, DOM_VK_CLOSE_BRACKET: 221, DOM_VK_QUOTE: 222, DOM_VK_META: 224 }; } exports.event = event; }); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ define('gcli/l10n', ['require', 'exports', 'module' ], function(require, exports, module) { Components.utils.import('resource://gre/modules/XPCOMUtils.jsm'); Components.utils.import('resource://gre/modules/Services.jsm'); XPCOMUtils.defineLazyGetter(this, 'stringBundle', function () { return Services.strings.createBundle('chrome://browser/locale/devtools/gcli.properties'); }); /* * Not supported when embedded - we're doing things the Mozilla way not the * require.js way. */ exports.registerStringsSource = function(modulePath) { throw new Error('registerStringsSource is not available in mozilla'); }; exports.unregisterStringsSource = function(modulePath) { throw new Error('unregisterStringsSource is not available in mozilla'); }; exports.lookupSwap = function(key, swaps) { throw new Error('lookupSwap is not available in mozilla'); }; exports.lookupPlural = function(key, ord, swaps) { throw new Error('lookupPlural is not available in mozilla'); }; exports.getPreferredLocales = function() { return [ 'root' ]; }; /** @see lookup() in lib/gcli/l10n.js */ exports.lookup = function(key) { try { return stringBundle.GetStringFromName(key); } catch (ex) { console.error('Failed to lookup ', key, ex); return key; } }; /** @see lookupFormat in lib/gcli/l10n.js */ exports.lookupFormat = function(key, swaps) { try { return stringBundle.formatStringFromName(key, swaps, swaps.length); } catch (ex) { console.error('Failed to format ', key, ex); return key; } }; }); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ define('gcli/types', ['require', 'exports', 'module' , 'gcli/argument'], function(require, exports, module) { var types = exports; var Argument = require('gcli/argument').Argument; /** * Some types can detect validity, that is to say they can distinguish between * valid and invalid values. * We might want to change these constants to be numbers for better performance */ var Status = { /** * The conversion process worked without any problem, and the value is * valid. There are a number of failure states, so the best way to check * for failure is (x !== Status.VALID) */ VALID: { toString: function() { return 'VALID'; }, valueOf: function() { return 0; } }, /** * A conversion process failed, however it was noted that the string * provided to 'parse()' could be VALID by the addition of more characters, * so the typing may not be actually incorrect yet, just unfinished. * @see Status.ERROR */ INCOMPLETE: { toString: function() { return 'INCOMPLETE'; }, valueOf: function() { return 1; } }, /** * The conversion process did not work, the value should be null and a * reason for failure should have been provided. In addition some * completion values may be available. * @see Status.INCOMPLETE */ ERROR: { toString: function() { return 'ERROR'; }, valueOf: function() { return 2; } }, /** * A combined status is the worser of the provided statuses. The statuses * can be provided either as a set of arguments or a single array */ combine: function() { var combined = Status.VALID; for (var i = 0; i < arguments.length; i++) { var status = arguments[i]; if (Array.isArray(status)) { status = Status.combine.apply(null, status); } if (status > combined) { combined = status; } } return combined; } }; types.Status = Status; /** * The type.parse() method converts an Argument into a value, Conversion is * a wrapper to that value. * Conversion is needed to collect a number of properties related to that * conversion in one place, i.e. to handle errors and provide traceability. * @param value The result of the conversion * @param arg The data from which the conversion was made * @param status See the Status values [VALID|INCOMPLETE|ERROR] defined above. * The default status is Status.VALID. * @param message If status=ERROR, there should be a message to describe the * error. A message is not needed unless for other statuses, but could be * present for any status including VALID (in the case where we want to note a * warning, for example). * See BUG 664676: GCLI conversion error messages should be localized * @param predictions If status=INCOMPLETE, there could be predictions as to * the options available to complete the input. * We generally expect there to be about 7 predictions (to match human list * comprehension ability) however it is valid to provide up to about 20, * or less. It is the job of the predictor to decide a smart cut-off. * For example if there are 4 very good matches and 4 very poor ones, * probably only the 4 very good matches should be presented. * The predictions are presented either as an array of prediction objects or as * a function which returns this array when called with no parameters. * Each prediction object has the following shape: * { * name: '...', // textual completion. i.e. what the cli uses * value: { ... }, // value behind the textual completion * incomplete: true // this completion is only partial (optional) * } * The 'incomplete' property could be used to denote a valid completion which * could have sub-values (e.g. for tree navigation). */ function Conversion(value, arg, status, message, predictions) { // The result of the conversion process. Will be null if status != VALID this.value = value; // Allow us to trace where this Conversion came from this.arg = arg; if (arg == null) { throw new Error('Missing arg'); } this._status = status || Status.VALID; this.message = message; this.predictions = predictions; } types.Conversion = Conversion; /** * Ensure that all arguments that are part of this conversion know what they * are assigned to. * @param assignment The Assignment (param/conversion link) to inform the * argument about. */ Conversion.prototype.assign = function(assignment) { this.arg.assign(assignment); }; /** * Work out if there is information provided in the contained argument. */ Conversion.prototype.isDataProvided = function() { var argProvided = this.arg.text !== ''; return this.value !== undefined || argProvided; }; /** * 2 conversions are equal if and only if their args are equal (argEquals) and * their values are equal (valueEquals). * @param that The conversion object to compare against. */ Conversion.prototype.equals = function(that) { if (this === that) { return true; } if (that == null) { return false; } return this.valueEquals(that) && this.argEquals(that); }; /** * Check that the value in this conversion is strict equal to the value in the * provided conversion. * @param that The conversion to compare values with */ Conversion.prototype.valueEquals = function(that) { return this.value === that.value; }; /** * Check that the argument in this conversion is equal to the value in the * provided conversion as defined by the argument (i.e. arg.equals). * @param that The conversion to compare arguments with */ Conversion.prototype.argEquals = function(that) { return this.arg.equals(that.arg); }; /** * Accessor for the status of this conversion */ Conversion.prototype.getStatus = function(arg) { return this._status; }; /** * Defined by the toString() value provided by the argument */ Conversion.prototype.toString = function() { return this.arg.toString(); }; /** * If status === INCOMPLETE, then we may be able to provide predictions as to * how the argument can be completed. * @return An array of items, where each item is an object with the following * properties: * - name (mandatory): Displayed to the user, and typed in. No whitespace * - description (optional): Short string for display in a tool-tip * - manual (optional): Longer description which details usage * - incomplete (optional): Indicates that the prediction if used should not * be considered necessarily sufficient, which typically will mean that the * UI should not append a space to the completion * - value (optional): If a value property is present, this will be used as the * value of the conversion, otherwise the item itself will be used. */ Conversion.prototype.getPredictions = function() { if (typeof this.predictions === 'function') { return this.predictions(); } return this.predictions || []; }; /** * ArrayConversion is a special Conversion, needed because arrays are converted * member by member rather then as a whole, which means we can track the * conversion if individual array elements. So an ArrayConversion acts like a * normal Conversion (which is needed as Assignment requires a Conversion) but * it can also be devolved into a set of Conversions for each array member. */ function ArrayConversion(conversions, arg) { this.arg = arg; this.conversions = conversions; this.value = conversions.map(function(conversion) { return conversion.value; }, this); this._status = Status.combine(conversions.map(function(conversion) { return conversion.getStatus(); })); // This message is just for reporting errors like "not enough values" // rather that for problems with individual values. this.message = ''; // Predictions are generally provided by individual values this.predictions = []; } ArrayConversion.prototype = Object.create(Conversion.prototype); ArrayConversion.prototype.assign = function(assignment) { this.conversions.forEach(function(conversion) { conversion.assign(assignment); }, this); this.assignment = assignment; }; ArrayConversion.prototype.getStatus = function(arg) { if (arg && arg.conversion) { return arg.conversion.getStatus(); } return this._status; }; ArrayConversion.prototype.isDataProvided = function() { return this.conversions.length > 0; }; ArrayConversion.prototype.valueEquals = function(that) { if (!(that instanceof ArrayConversion)) { throw new Error('Can\'t compare values with non ArrayConversion'); } if (this.value === that.value) { return true; } if (this.value.length !== that.value.length) { return false; } for (var i = 0; i < this.conversions.length; i++) { if (!this.conversions[i].valueEquals(that.conversions[i])) { return false; } } return true; }; ArrayConversion.prototype.toString = function() { return '[ ' + this.conversions.map(function(conversion) { return conversion.toString(); }, this).join(', ') + ' ]'; }; types.ArrayConversion = ArrayConversion; /** * Most of our types are 'static' e.g. there is only one type of 'string', * however some types like 'selection' and 'deferred' are customizable. * The basic Type type isn't useful, but does provide documentation about what * types do. */ function Type() { } /** * Convert the given value to a string representation. * Where possible, there should be round-tripping between values and their * string representations. */ Type.prototype.stringify = function(value) { throw new Error('Not implemented'); }; /** * Convert the given arg to an instance of this type. * Where possible, there should be round-tripping between values and their * string representations. * @param arg An instance of Argument to convert. * @return Conversion */ Type.prototype.parse = function(arg) { throw new Error('Not implemented'); }; /** * A convenience method for times when you don't have an argument to parse * but instead have a string. * @see #parse(arg) */ Type.prototype.parseString = function(str) { return this.parse(new Argument(str)); }, /** * The plug-in system, and other things need to know what this type is * called. The name alone is not enough to fully specify a type. Types like * 'selection' and 'deferred' need extra data, however this function returns * only the name, not the extra data. */ Type.prototype.name = undefined; /** * If there is some concept of a higher value, return it, * otherwise return undefined. */ Type.prototype.increment = function(value) { return undefined; }; /** * If there is some concept of a lower value, return it, * otherwise return undefined. */ Type.prototype.decrement = function(value) { return undefined; }; /** * There is interesting information (like predictions) in a conversion of * nothing, the output of this can sometimes be customized. * @return Conversion */ Type.prototype.getDefault = undefined; types.Type = Type; /** * Private registry of types * Invariant: types[name] = type.name */ var registeredTypes = {}; types.getTypeNames = function() { return Object.keys(registeredTypes); }; /** * Add a new type to the list available to the system. * You can pass 2 things to this function - either an instance of Type, in * which case we return this instance when #getType() is called with a 'name' * that matches type.name. * Also you can pass in a constructor (i.e. function) in which case when * #getType() is called with a 'name' that matches Type.prototype.name we will * pass the typeSpec into this constructor. */ types.registerType = function(type) { if (typeof type === 'object') { if (type instanceof Type) { if (!type.name) { throw new Error('All registered types must have a name'); } registeredTypes[type.name] = type; } else { throw new Error('Can\'t registerType using: ' + type); } } else if (typeof type === 'function') { if (!type.prototype.name) { throw new Error('All registered types must have a name'); } registeredTypes[type.prototype.name] = type; } else { throw new Error('Unknown type: ' + type); } }; types.registerTypes = function registerTypes(newTypes) { Object.keys(newTypes).forEach(function(name) { var type = newTypes[name]; type.name = name; newTypes.registerType(type); }); }; /** * Remove a type from the list available to the system */ types.deregisterType = function(type) { delete registeredTypes[type.name]; }; /** * Find a type, previously registered using #registerType() */ types.getType = function(typeSpec) { var type; if (typeof typeSpec === 'string') { type = registeredTypes[typeSpec]; if (typeof type === 'function') { type = new type(); } return type; } if (typeof typeSpec === 'object') { if (!typeSpec.name) { throw new Error('Missing \'name\' member to typeSpec'); } type = registeredTypes[typeSpec.name]; if (typeof type === 'function') { type = new type(typeSpec); } return type; } throw new Error('Can\'t extract type from ' + typeSpec); }; }); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ define('gcli/argument', ['require', 'exports', 'module' ], function(require, exports, module) { var argument = exports; /** * We record where in the input string an argument comes so we can report * errors against those string positions. * @param text The string (trimmed) that contains the argument * @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(text, prefix, suffix) { if (text === undefined) { this.text = ''; this.prefix = ''; this.suffix = ''; } else { this.text = text; this.prefix = prefix !== undefined ? prefix : ''; this.suffix = suffix !== undefined ? suffix : ''; } } /** * Return the result of merging these arguments. * case and some of the arguments are in quotation marks? */ Argument.prototype.merge = function(following) { // Is it possible that this gets called when we're merging arguments // for the single string? return new Argument( this.text + this.suffix + following.prefix + following.text, this.prefix, following.suffix); }; /** * Returns a new Argument like this one but with the text set to * replText and the end adjusted to fit. * @param replText Text to replace the old text value */ Argument.prototype.beget = function(replText, options) { var prefix = this.prefix; var suffix = this.suffix; var quote = (replText.indexOf(' ') >= 0 || replText.length == 0) ? '\'' : ''; if (options) { prefix = (options.prefixSpace ? ' ' : '') + quote; suffix = quote; } return new Argument(replText, prefix, suffix); }; /** * Is there any visible content to this argument? */ Argument.prototype.isBlank = function() { return this.text === '' && this.prefix.trim() === '' && this.suffix.trim() === ''; }; /** * We need to keep track of which assignment we've been assigned to */ Argument.prototype.assign = function(assignment) { this.assignment = assignment; }; /** * Sub-classes of Argument are collections of arguments, getArgs() gets access * to the members of the collection in order to do things like re-create input * command lines. For the simple Argument case it's just an array containing * only this. */ Argument.prototype.getArgs = function() { return [ this ]; }; /** * We define equals to mean all arg properties are strict equals. * Used by Conversion.argEquals and Conversion.equals and ultimately * Assignment.equals to avoid reporting a change event when a new conversion * is assigned. */ Argument.prototype.equals = function(that) { if (this === that) { return true; } if (that == null || !(that instanceof Argument)) { return false; } return this.text === that.text && this.prefix === that.prefix && this.suffix === that.suffix; }; /** * Helper when we're putting arguments back together */ Argument.prototype.toString = function() { // BUG 664207: 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; }; argument.Argument = Argument; /** * ScriptArgument is a marker that the argument is designed to be Javascript. * It also implements the special rules that spaces after the { or before the * } are part of the pre/suffix rather than the content. */ function ScriptArgument(text, prefix, suffix) { this.text = text; this.prefix = prefix !== undefined ? prefix : ''; this.suffix = suffix !== undefined ? suffix : ''; while (this.text.charAt(0) === ' ') { this.prefix = this.prefix + ' '; this.text = this.text.substring(1); } while (this.text.charAt(this.text.length - 1) === ' ') { this.suffix = ' ' + this.suffix; this.text = this.text.slice(0, -1); } } ScriptArgument.prototype = Object.create(Argument.prototype); /** * Returns a new Argument like this one but with the text set to * replText and the end adjusted to fit. * @param replText Text to replace the old text value */ ScriptArgument.prototype.beget = function(replText, options) { var prefix = this.prefix; var suffix = this.suffix; var quote = (replText.indexOf(' ') >= 0 || replText.length == 0) ? '\'' : ''; if (options && options.normalize) { prefix = '{ '; suffix = ' }'; } return new ScriptArgument(replText, prefix, suffix); }; argument.ScriptArgument = ScriptArgument; /** * Commands like 'echo' with a single string argument, and used with the * special format like: 'echo a b c' effectively have a number of arguments * merged together. */ function MergedArgument(args, start, end) { if (!Array.isArray(args)) { throw new Error('args is not an array of Arguments'); } if (start === undefined) { this.args = args; } else { this.args = args.slice(start, end); } var arg = Argument.merge(this.args); this.text = arg.text; this.prefix = arg.prefix; this.suffix = arg.suffix; } MergedArgument.prototype = Object.create(Argument.prototype); /** * Keep track of which assignment we've been assigned to, and allow the * original args to do the same. */ MergedArgument.prototype.assign = function(assignment) { this.args.forEach(function(arg) { arg.assign(assignment); }, this); this.assignment = assignment; }; MergedArgument.prototype.getArgs = function() { return this.args; }; MergedArgument.prototype.equals = function(that) { if (this === that) { return true; } if (that == null || !(that instanceof MergedArgument)) { return false; } // We might need to add a check that args is the same here return this.text === that.text && this.prefix === that.prefix && this.suffix === that.suffix; }; argument.MergedArgument = MergedArgument; /** * TrueNamedArguments are for when we have an argument like --verbose which * has a boolean value, and thus the opposite of '--verbose' is ''. */ function TrueNamedArgument(name, arg) { this.arg = arg; this.text = arg ? arg.text : '--' + name; this.prefix = arg ? arg.prefix : ' '; this.suffix = arg ? arg.suffix : ''; } TrueNamedArgument.prototype = Object.create(Argument.prototype); TrueNamedArgument.prototype.assign = function(assignment) { if (this.arg) { this.arg.assign(assignment); } this.assignment = assignment; }; TrueNamedArgument.prototype.getArgs = function() { // NASTY! getArgs has a fairly specific use: in removing used arguments // from a command line. Unlike other arguments which are EITHER used // in assignments directly OR grouped in things like MergedArguments, // TrueNamedArgument is used raw from the UI, or composed of another arg // from the CLI, so we return both here so they can both be removed. return this.arg ? [ this, this.arg ] : [ this ]; }; TrueNamedArgument.prototype.equals = function(that) { if (this === that) { return true; } if (that == null || !(that instanceof TrueNamedArgument)) { return false; } return this.text === that.text && this.prefix === that.prefix && this.suffix === that.suffix; }; argument.TrueNamedArgument = TrueNamedArgument; /** * FalseNamedArguments are for when we don't have an argument like --verbose * which has a boolean value, and thus the opposite of '' is '--verbose'. */ function FalseNamedArgument() { this.text = ''; this.prefix = ''; this.suffix = ''; } FalseNamedArgument.prototype = Object.create(Argument.prototype); FalseNamedArgument.prototype.getArgs = function() { return [ ]; }; FalseNamedArgument.prototype.equals = function(that) { if (this === that) { return true; } if (that == null || !(that instanceof FalseNamedArgument)) { return false; } return this.text === that.text && this.prefix === that.prefix && this.suffix === that.suffix; }; argument.FalseNamedArgument = FalseNamedArgument; /** * A named argument is for cases where we have input in one of the following * formats: * * The general format is: * /--?{unique-param-name-prefix}[ :=]{value}/ * We model this as a normal argument but with a long prefix. */ function NamedArgument(nameArg, valueArg) { this.nameArg = nameArg; this.valueArg = valueArg; this.text = valueArg.text; this.prefix = nameArg.toString() + valueArg.prefix; this.suffix = valueArg.suffix; } NamedArgument.prototype = Object.create(Argument.prototype); NamedArgument.prototype.assign = function(assignment) { this.nameArg.assign(assignment); this.valueArg.assign(assignment); this.assignment = assignment; }; NamedArgument.prototype.getArgs = function() { return [ this.nameArg, this.valueArg ]; }; NamedArgument.prototype.equals = function(that) { if (this === that) { return true; } if (that == null) { return false; } if (!(that instanceof NamedArgument)) { return false; } // We might need to add a check that nameArg and valueArg are the same return this.text === that.text && this.prefix === that.prefix && this.suffix === that.suffix; }; argument.NamedArgument = NamedArgument; /** * An argument the groups together a number of plain arguments together so they * can be jointly assigned to a single array parameter */ function ArrayArgument() { this.args = []; } ArrayArgument.prototype = Object.create(Argument.prototype); ArrayArgument.prototype.addArgument = function(arg) { this.args.push(arg); }; ArrayArgument.prototype.addArguments = function(args) { Array.prototype.push.apply(this.args, args); }; ArrayArgument.prototype.getArguments = function() { return this.args; }; ArrayArgument.prototype.assign = function(assignment) { this.args.forEach(function(arg) { arg.assign(assignment); }, this); this.assignment = assignment; }; ArrayArgument.prototype.getArgs = function() { return this.args; }; ArrayArgument.prototype.equals = function(that) { if (this === that) { return true; } if (that == null) { return false; } if (!(that instanceof ArrayArgument)) { return false; } if (this.args.length !== that.args.length) { return false; } for (var i = 0; i < this.args.length; i++) { if (!this.args[i].equals(that.args[i])) { return false; } } return true; }; /** * Helper when we're putting arguments back together */ ArrayArgument.prototype.toString = function() { return '{' + this.args.map(function(arg) { return arg.toString(); }, this).join(',') + '}'; }; argument.ArrayArgument = ArrayArgument; }); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ define('gcli/types/basic', ['require', 'exports', 'module' , 'gcli/l10n', 'gcli/types', 'gcli/argument'], function(require, exports, module) { var l10n = require('gcli/l10n'); var types = require('gcli/types'); var Type = require('gcli/types').Type; var Status = require('gcli/types').Status; var Conversion = require('gcli/types').Conversion; var ArrayConversion = require('gcli/types').ArrayConversion; var Argument = require('gcli/argument').Argument; var TrueNamedArgument = require('gcli/argument').TrueNamedArgument; var FalseNamedArgument = require('gcli/argument').FalseNamedArgument; var ArrayArgument = require('gcli/argument').ArrayArgument; /** * Registration and de-registration. */ exports.startup = function() { types.registerType(StringType); types.registerType(NumberType); types.registerType(BooleanType); types.registerType(BlankType); types.registerType(SelectionType); types.registerType(DeferredType); types.registerType(ArrayType); }; exports.shutdown = function() { types.unregisterType(StringType); types.unregisterType(NumberType); types.unregisterType(BooleanType); types.unregisterType(BlankType); types.unregisterType(SelectionType); types.unregisterType(DeferredType); types.unregisterType(ArrayType); }; /** * 'string' the most basic string type that doesn't need to convert */ function StringType(typeSpec) { if (typeSpec != null) { throw new Error('StringType can not be customized'); } } StringType.prototype = Object.create(Type.prototype); StringType.prototype.stringify = function(value) { if (value == null) { return ''; } return value.toString(); }; StringType.prototype.parse = function(arg) { if (arg.text == null || arg.text === '') { return new Conversion(null, arg, Status.INCOMPLETE, ''); } return new Conversion(arg.text, arg); }; StringType.prototype.name = 'string'; exports.StringType = StringType; /** * We don't currently plan to distinguish between integers and floats */ function NumberType(typeSpec) { if (typeSpec) { this._min = typeSpec.min; this._max = typeSpec.max; this._step = typeSpec.step || 1; } else { this._step = 1; } } NumberType.prototype = Object.create(Type.prototype); NumberType.prototype.stringify = function(value) { if (value == null) { return ''; } return '' + value; }; NumberType.prototype.getMin = function() { if (this._min) { if (typeof this._min === 'function') { return this._min(); } if (typeof this._min === 'number') { return this._min; } } return 0; }; NumberType.prototype.getMax = function() { if (this._max) { if (typeof this._max === 'function') { return this._max(); } if (typeof this._max === 'number') { return this._max; } } return undefined; }; NumberType.prototype.parse = function(arg) { if (arg.text.replace(/\s/g, '').length === 0) { return new Conversion(null, arg, Status.INCOMPLETE, ''); } var value = parseInt(arg.text, 10); if (isNaN(value)) { return new Conversion(null, arg, Status.ERROR, l10n.lookupFormat('typesNumberNan', [ arg.text ])); } if (this.getMax() != null && value > this.getMax()) { return new Conversion(null, arg, Status.ERROR, l10n.lookupFormat('typesNumberMax', [ value, this.getMax() ])); } if (value < this.getMin()) { return new Conversion(null, arg, Status.ERROR, l10n.lookupFormat('typesNumberMin', [ value, this.getMin() ])); } return new Conversion(value, arg); }; NumberType.prototype.decrement = function(value) { if (typeof value !== 'number' || isNaN(value)) { return this.getMax() || 1; } var newValue = value - this._step; // Snap to the nearest incremental of the step newValue = Math.ceil(newValue / this._step) * this._step; return this._boundsCheck(newValue); }; NumberType.prototype.increment = function(value) { if (typeof value !== 'number' || isNaN(value)) { return this.getMin(); } var newValue = value + this._step; // Snap to the nearest incremental of the step newValue = Math.floor(newValue / this._step) * this._step; if (this.getMax() == null) { return newValue; } return this._boundsCheck(newValue); }; /** * Return the input value so long as it is within the max/min bounds. If it is * lower than the minimum, return the minimum. If it is bigger than the maximum * then return the maximum. */ NumberType.prototype._boundsCheck = function(value) { var min = this.getMin(); if (value < min) { return min; } var max = this.getMax(); if (value > max) { return max; } return value; }; NumberType.prototype.name = 'number'; exports.NumberType = NumberType; /** * One of a known set of options */ function SelectionType(typeSpec) { if (typeSpec) { Object.keys(typeSpec).forEach(function(key) { this[key] = typeSpec[key]; }, this); } } SelectionType.prototype = Object.create(Type.prototype); SelectionType.prototype.stringify = function(value) { var name = null; var lookup = this.getLookup(); lookup.some(function(item) { var test = (item.value == null) ? item : item.value; if (test === value) { name = item.name; return true; } return false; }, this); return name; }; /** * There are several ways to get selection data. This unifies them into one * single function. * @return A map of names to values. */ SelectionType.prototype.getLookup = function() { if (this.lookup) { if (typeof this.lookup === 'function') { return this.lookup(); } return this.lookup; } if (Array.isArray(this.data)) { this.lookup = this._dataToLookup(this.data); return this.lookup; } if (typeof(this.data) === 'function') { return this._dataToLookup(this.data()); } throw new Error('SelectionType has no data'); }; /** * Selection can be provided with either a lookup object (in the 'lookup' * property) or an array of strings (in the 'data' property). Internally we * always use lookup, so we need a way to convert a 'data' array to a lookup. */ SelectionType.prototype._dataToLookup = function(data) { return data.map(function(option) { return { name: option, value: option }; }); }; /** * Return a list of possible completions for the given arg. * This code is very similar to CommandType._findPredictions(). If you are * making changes to this code, you should check there too. * @param arg The initial input to match * @return A trimmed lookup table of string:value pairs */ SelectionType.prototype._findPredictions = function(arg) { var predictions = []; this.getLookup().forEach(function(item) { if (item.name.indexOf(arg.text) === 0) { predictions.push(item); } }, this); return predictions; }; SelectionType.prototype.parse = function(arg) { var predictions = this._findPredictions(arg); if (predictions.length === 1 && predictions[0].name === arg.text) { var value = predictions[0].value ? predictions[0].value : predictions[0]; return new Conversion(value, arg); } // This is something of a hack it basically allows us to tell the // setting type to forget its last setting hack. if (this.noMatch) { this.noMatch(); } if (predictions.length > 0) { // Especially at startup, predictions live over the time that things // change so we provide a completion function rather than completion // values. // This was primarily designed for commands, which have since moved // into their own type, so technically we could remove this code, // except that it provides more up-to-date answers, and it's hard to // predict when it will be required. var predictFunc = function() { return this._findPredictions(arg); }.bind(this); return new Conversion(null, arg, Status.INCOMPLETE, '', predictFunc); } return new Conversion(null, arg, Status.ERROR, l10n.lookupFormat('typesSelectionNomatch', [ arg.text ])); }; /** * For selections, up is down and black is white. It's like this, given a list * [ a, b, c, d ], it's natural to think that it starts at the top and that * going up the list, moves towards 'a'. However 'a' has the lowest index, so * for SelectionType, up is down and down is up. * Sorry. */ SelectionType.prototype.decrement = function(value) { var lookup = this.getLookup(); var index = this._findValue(lookup, value); if (index === -1) { index = 0; } index++; if (index >= lookup.length) { index = 0; } return lookup[index].value; }; /** * See note on SelectionType.decrement() */ SelectionType.prototype.increment = function(value) { var lookup = this.getLookup(); var index = this._findValue(lookup, value); if (index === -1) { // For an increment operation when there is nothing to start from, we // want to start from the top, i.e. index 0, so the value before we // 'increment' (see note above) must be 1. index = 1; } index--; if (index < 0) { index = lookup.length - 1; } return lookup[index].value; }; /** * Walk through an array of { name:.., value:... } objects looking for a * matching value (using strict equality), returning the matched index (or -1 * if not found). * @param lookup Array of objects with name/value properties to search through * @param value The value to search for * @return The index at which the match was found, or -1 if no match was found */ SelectionType.prototype._findValue = function(lookup, value) { var index = -1; for (var i = 0; i < lookup.length; i++) { var pair = lookup[i]; if (pair.value === value) { index = i; break; } } return index; }; SelectionType.prototype.name = 'selection'; exports.SelectionType = SelectionType; /** * true/false values */ function BooleanType(typeSpec) { if (typeSpec != null) { throw new Error('BooleanType can not be customized'); } } BooleanType.prototype = Object.create(SelectionType.prototype); BooleanType.prototype.lookup = [ { name: 'true', value: true }, { name: 'false', value: false } ]; BooleanType.prototype.parse = function(arg) { if (arg instanceof TrueNamedArgument) { return new Conversion(true, arg); } if (arg instanceof FalseNamedArgument) { return new Conversion(false, arg); } return SelectionType.prototype.parse.call(this, arg); }; BooleanType.prototype.stringify = function(value) { return '' + value; }; BooleanType.prototype.getDefault = function() { return new Conversion(false, new Argument('')); }; BooleanType.prototype.name = 'boolean'; exports.BooleanType = BooleanType; /** * A type for "we don't know right now, but hope to soon". */ function DeferredType(typeSpec) { if (typeof typeSpec.defer !== 'function') { throw new Error('Instances of DeferredType need typeSpec.defer to be a function that returns a type'); } Object.keys(typeSpec).forEach(function(key) { this[key] = typeSpec[key]; }, this); } DeferredType.prototype = Object.create(Type.prototype); DeferredType.prototype.stringify = function(value) { return this.defer().stringify(value); }; DeferredType.prototype.parse = function(arg) { return this.defer().parse(arg); }; DeferredType.prototype.decrement = function(value) { var deferred = this.defer(); return (deferred.decrement ? deferred.decrement(value) : undefined); }; DeferredType.prototype.increment = function(value) { var deferred = this.defer(); return (deferred.increment ? deferred.increment(value) : undefined); }; DeferredType.prototype.increment = function(value) { var deferred = this.defer(); return (deferred.increment ? deferred.increment(value) : undefined); }; DeferredType.prototype.name = 'deferred'; exports.DeferredType = DeferredType; /** * 'blank' is a type for use with DeferredType when we don't know yet. * It should not be used anywhere else. */ function BlankType(typeSpec) { if (typeSpec != null) { throw new Error('BlankType can not be customized'); } } BlankType.prototype = Object.create(Type.prototype); BlankType.prototype.stringify = function(value) { return ''; }; BlankType.prototype.parse = function(arg) { return new Conversion(null, arg); }; BlankType.prototype.name = 'blank'; exports.BlankType = BlankType; /** * A set of objects of the same type */ function ArrayType(typeSpec) { if (!typeSpec.subtype) { console.error('Array.typeSpec is missing subtype. Assuming string.' + JSON.stringify(typeSpec)); typeSpec.subtype = 'string'; } Object.keys(typeSpec).forEach(function(key) { this[key] = typeSpec[key]; }, this); this.subtype = types.getType(this.subtype); } ArrayType.prototype = Object.create(Type.prototype); ArrayType.prototype.stringify = function(values) { // BUG 664204: Check for strings with spaces and add quotes return values.join(' '); }; ArrayType.prototype.parse = function(arg) { if (arg instanceof ArrayArgument) { var conversions = arg.getArguments().map(function(subArg) { var conversion = this.subtype.parse(subArg); // Hack alert. ArrayConversion needs to be able to answer questions // about the status of individual conversions in addition to the // overall state. This allows us to do that easily. subArg.conversion = conversion; return conversion; }, this); return new ArrayConversion(conversions, arg); } else { console.error('non ArrayArgument to ArrayType.parse', arg); throw new Error('non ArrayArgument to ArrayType.parse'); } }; ArrayType.prototype.getDefault = function() { return new ArrayConversion([], new ArrayArgument()); }; ArrayType.prototype.name = 'array'; exports.ArrayType = ArrayType; }); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ define('gcli/types/javascript', ['require', 'exports', 'module' , 'gcli/l10n', 'gcli/types'], function(require, exports, module) { var l10n = require('gcli/l10n'); var types = require('gcli/types'); var Conversion = types.Conversion; var Type = types.Type; var Status = types.Status; /** * Registration and de-registration. */ exports.startup = function() { types.registerType(JavascriptType); }; exports.shutdown = function() { types.unregisterType(JavascriptType); }; /** * The object against which we complete, which is usually 'window' if it exists * but could be something else in non-web-content environments. */ var globalObject; if (typeof window !== 'undefined') { globalObject = window; } /** * Setter for the object against which JavaScript completions happen */ exports.setGlobalObject = function(obj) { globalObject = obj; }; /** * Remove registration of object against which JavaScript completions happen */ exports.unsetGlobalObject = function() { globalObject = undefined; }; /** * 'javascript' handles scripted input */ function JavascriptType(typeSpec) { if (typeSpec != null) { throw new Error('JavascriptType can not be customized'); } } JavascriptType.prototype = Object.create(Type.prototype); JavascriptType.prototype.stringify = function(value) { if (value == null) { return ''; } return value; }; JavascriptType.prototype.parse = function(arg) { var typed = arg.text; var scope = globalObject; // In FX-land we need to unwrap. TODO: Enable in the browser. // scope = unwrap(scope); // Analyze the input text and find the beginning of the last part that // should be completed. var beginning = this._findCompletionBeginning(typed); // There was an error analyzing the string. if (beginning.err) { return new Conversion(typed, arg, Status.ERROR, beginning.err); } // If the current state is not ParseState.NORMAL, then we are inside of a // string which means that no completion is possible. if (beginning.state !== ParseState.NORMAL) { return new Conversion(typed, arg, Status.INCOMPLETE, ''); } var completionPart = typed.substring(beginning.startPos); var properties = completionPart.split('.'); var matchProp; var prop; if (properties.length > 1) { matchProp = properties.pop().trimLeft(); for (var i = 0; i < properties.length; i++) { prop = properties[i].trim(); // We can't complete on null.foo, so bail out if (scope == null) { return new Conversion(typed, arg, Status.ERROR, l10n.lookup('jstypeParseScope')); } // TODO: Re-enable this test // Check if prop is a getter function on obj. Functions can change other // stuff so we can't execute them to get the next object. Stop here. // if (isNonNativeGetter(scope, prop)) { // return new Conversion(typed, arg); // } try { scope = scope[prop]; } catch (ex) { return new Conversion(typed, arg, Status.ERROR, '' + ex); } } } else { matchProp = properties[0].trimLeft(); } // If the reason we just stopped adjusting the scope was a non-simple string, // then we're not sure if the input is valid or invalid, so accept it if (prop && !prop.match(/^[0-9A-Za-z]*$/)) { return new Conversion(typed, arg); } // However if the prop was a simple string, it is an error if (scope == null) { return new Conversion(typed, arg, Status.ERROR, l10n.lookupFormat('jstypeParseMissing', [ prop ])); } // If the thing we're looking for isn't a simple string, then we're not going // to find it, but we're not sure if it's valid or invalid, so accept it if (!matchProp.match(/^[0-9A-Za-z]*$/)) { return new Conversion(typed, arg); } // Skip Iterators and Generators. if (this._isIteratorOrGenerator(scope)) { return null; } var matchLen = matchProp.length; var prefix = matchLen === 0 ? typed : typed.slice(0, -matchLen); var status = Status.INCOMPLETE; var message; var matches = []; for (var prop in scope) { if (prop.indexOf(matchProp) === 0) { var value; try { value = scope[prop]; } catch (ex) { break; } var description; var incomplete = true; if (typeof value === 'function') { description = '(function)'; } if (typeof value === 'boolean' || typeof value === 'number') { description = '= ' + value; incomplete = false; } else if (typeof value === 'string') { if (value.length > 40) { value = value.substring(0, 37) + '...'; } description = '= \'' + value + '\''; incomplete = false; } else { description = '(' + typeof value + ')'; } matches.push({ name: prefix + prop, value: { name: prefix + prop, description: description }, incomplete: incomplete }); } if (prop === matchProp) { status = Status.VALID; message = ''; } } // Error message if this isn't valid if (status !== Status.VALID) { message = l10n.lookupFormat('jstypeParseMissing', [ matchProp ]); } // If the match is the only one possible, and its VALID, predict nothing if (matches.length === 1 && status === Status.VALID) { matches = undefined; } else { // Can we think of a better sort order than alpha? There are certainly some // properties that are far more commonly used ... matches.sort(function(p1, p2) { return p1.name.localeCompare(p2.name); }); } // More than 10 matches are generally not helpful. We should really do a // better job of finding matches (bug 682694), but in the mean time there is // a performance problem associated with creating a large number of DOM nodes // that few people will ever read, so trim the list of matches if (matches && matches.length > 10) { matches = matches.slice(0, 9); } return new Conversion(typed, arg, status, message, matches); }; var ParseState = { NORMAL: 0, QUOTE: 2, DQUOTE: 3 }; var OPEN_BODY = '{[('.split(''); var CLOSE_BODY = '}])'.split(''); var OPEN_CLOSE_BODY = { '{': '}', '[': ']', '(': ')' }; /** * Analyzes a given string to find the last statement that is interesting for * later completion. * @param text A string to analyze * @return If there was an error in the string detected, then a object like * { err: 'ErrorMesssage' } * is returned, otherwise a object like * { * state: ParseState.NORMAL|ParseState.QUOTE|ParseState.DQUOTE, * startPos: index of where the last statement begins * } */ JavascriptType.prototype._findCompletionBeginning = function(text) { var bodyStack = []; var state = ParseState.NORMAL; var start = 0; var c; for (var i = 0; i < text.length; i++) { c = text[i]; switch (state) { // Normal JS state. case ParseState.NORMAL: if (c === '"') { state = ParseState.DQUOTE; } else if (c === '\'') { state = ParseState.QUOTE; } else if (c === ';') { start = i + 1; } else if (c === ' ') { start = i + 1; } else if (OPEN_BODY.indexOf(c) != -1) { bodyStack.push({ token: c, start: start }); start = i + 1; } else if (CLOSE_BODY.indexOf(c) != -1) { var last = bodyStack.pop(); if (!last || OPEN_CLOSE_BODY[last.token] != c) { return { err: l10n.lookup('jstypeBeginSyntax') }; } if (c === '}') { start = i + 1; } else { start = last.start; } } break; // Double quote state > " < case ParseState.DQUOTE: if (c === '\\') { i ++; } else if (c === '\n') { return { err: l10n.lookup('jstypeBeginUnterm') }; } else if (c === '"') { state = ParseState.NORMAL; } break; // Single quote state > ' < case ParseState.QUOTE: if (c === '\\') { i ++; } else if (c === '\n') { return { err: l10n.lookup('jstypeBeginUnterm') }; } else if (c === '\'') { state = ParseState.NORMAL; } break; } } return { state: state, startPos: start }; }; /** * Return true if the passed object is either an iterator or a generator, and * false otherwise. * @param obj The object to check */ JavascriptType.prototype._isIteratorOrGenerator = function(obj) { if (obj === null) { return false; } if (typeof aObject === 'object') { if (typeof obj.__iterator__ === 'function' || obj.constructor && obj.constructor.name === 'Iterator') { return true; } try { var str = obj.toString(); if (typeof obj.next === 'function' && str.indexOf('[object Generator') === 0) { return true; } } catch (ex) { // window.history.next throws in the typeof check above. return false; } } return false; }; JavascriptType.prototype.name = 'javascript'; exports.JavascriptType = JavascriptType; }); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ define('gcli/types/node', ['require', 'exports', 'module' , 'gcli/l10n', 'gcli/types'], function(require, exports, module) { var l10n = require('gcli/l10n'); var types = require('gcli/types'); var Type = require('gcli/types').Type; var Status = require('gcli/types').Status; var Conversion = require('gcli/types').Conversion; /** * Registration and de-registration. */ exports.startup = function() { types.registerType(NodeType); }; exports.shutdown = function() { types.unregisterType(NodeType); }; /** * The object against which we complete, which is usually 'window' if it exists * but could be something else in non-web-content environments. */ var doc; if (typeof document !== 'undefined') { doc = document; } /** * Setter for the document that contains the nodes we're matching */ exports.setDocument = function(document) { doc = document; }; /** * Undo the effects of setDocument() */ exports.unsetDocument = function() { doc = undefined; }; /** * A CSS expression that refers to a single node */ function NodeType(typeSpec) { if (typeSpec != null) { throw new Error('NodeType can not be customized'); } } NodeType.prototype = Object.create(Type.prototype); NodeType.prototype.stringify = function(value) { return value.__gcliQuery || 'Error'; }; NodeType.prototype.parse = function(arg) { if (arg.text === '') { return new Conversion(null, arg, Status.INCOMPLETE, l10n.lookup('nodeParseNone')); } var nodes; try { nodes = doc.querySelectorAll(arg.text); } catch (ex) { console.error(ex); return new Conversion(null, arg, Status.ERROR, l10n.lookup('nodeParseSyntax')); } if (nodes.length === 0) { return new Conversion(null, arg, Status.INCOMPLETE, l10n.lookup('nodeParseNone')); } if (nodes.length === 1) { var node = nodes.item(0); node.__gcliQuery = arg.text; flashNode(node, 'green'); return new Conversion(node, arg, Status.VALID, ''); } Array.prototype.forEach.call(nodes, function(n) { flashNode(n, 'red'); }); return new Conversion(null, arg, Status.ERROR, l10n.lookupFormat('nodeParseMultiple', [ nodes.length ])); }; NodeType.prototype.name = 'node'; /** * Helper to turn a node background it's complementary color for 1 second. * There is likely a better way to do this, but this will do for now. */ function flashNode(node, color) { if (!node.__gcliHighlighting) { node.__gcliHighlighting = true; var original = node.style.background; node.style.background = color; setTimeout(function() { node.style.background = original; delete node.__gcliHighlighting; }, 1000); } } }); /* * Copyright 2009-2011 Mozilla Foundation and contributors * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ define('gcli/cli', ['require', 'exports', 'module' , 'gcli/util', 'gcli/canon', 'gcli/promise', 'gcli/types', 'gcli/types/basic', 'gcli/argument'], function(require, exports, module) { var createEvent = require('gcli/util').createEvent; var canon = require('gcli/canon'); var Promise = require('gcli/promise').Promise; var types = require('gcli/types'); var Status = require('gcli/types').Status; var Conversion = require('gcli/types').Conversion; var Type = require('gcli/types').Type; var ArrayType = require('gcli/types/basic').ArrayType; var StringType = require('gcli/types/basic').StringType; var BooleanType = require('gcli/types/basic').BooleanType; var SelectionType = require('gcli/types/basic').SelectionType; var Argument = require('gcli/argument').Argument; var ArrayArgument = require('gcli/argument').ArrayArgument; var NamedArgument = require('gcli/argument').NamedArgument; var TrueNamedArgument = require('gcli/argument').TrueNamedArgument; var MergedArgument = require('gcli/argument').MergedArgument; var ScriptArgument = require('gcli/argument').ScriptArgument; var evalCommand; /** * Registration and de-registration. */ exports.startup = function() { types.registerType(CommandType); evalCommand = canon.addCommand(evalCommandSpec); }; exports.shutdown = function() { types.unregisterType(CommandType); canon.removeCommand(evalCommandSpec.name); evalCommand = undefined; }; /** * Assignment is 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. * *

Events

* Assignment publishes the following event: