/* ***** 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 GCLI. * * The Initial Developer of the Original Code is * The Mozilla Foundation * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Joe Walker (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 ***** */ /* * * * * * * * *********************************** WARNING *********************************** * * Do not edit this file without understanding where it comes from, * Your changes are likely to be overwritten without warning. * * The original source for this file is: * https://github.com/mozilla/gcli/ * ******************************************************************************* * * * * * * * * * */ /////////////////////////////////////////////////////////////////////////////// /* * This build of GCLI for Firefox is really 4 bits of code: * - Browser support code - Currently just an implementation of the console * object that uses dump. We may need to add other browser shims to this. * - 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 section of code is a copy of mini_require.js without the header and * footers. Changes to one should be reflected in the other. * - A build of GCLI itself, packaged using dryice (for more details see the * project https://github.com/mozilla/dryice and the build file in this * project at Makefile.dryice.js) * - Lastly, code to require the gcli object as needed by EXPORTED_SYMBOLS. */ var EXPORTED_SYMBOLS = [ "gcli" ]; /////////////////////////////////////////////////////////////////////////////// /* * 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) */ /** * 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 == undefined) { 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 60 chars long */ function stringify(aThing) { if (aThing === undefined) { return "undefined"; } if (aThing === null) { return "null"; } if (typeof aThing == "object") { try { return getCtorName(aThing) + " " + fmt(JSON.stringify(aThing), 50, 0); } catch (ex) { return "[stringify error]"; } } var str = aThing.toString().replace(/\s+/g, " "); return fmt(str, 60, 0); } /** * 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"; } if (aThing == undefined) { return "undefined"; } if (typeof aThing == "object") { var reply = ""; var type = getCtorName(aThing); if (type == "Error") { reply += " " + aThing.message + "\n"; reply += logProperty("stack", aThing.stack); } 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)); }); }; } /** * The console object to expose */ var console = { debug: createMultiLineDumper("debug"), log: createDumper("log"), info: createDumper("info"), warn: createDumper("warn"), error: createMultiLineDumper("error"), trace: function Console_trace() { var trace = getStack(Components.stack.caller); dump(formatTrace(trace) + "\n"); }, clear: function Console_clear() {}, dir: createMultiLineDumper("dir"), dirxml: createMultiLineDumper("dirxml"), group: createDumper("group"), groupEnd: createDumper("groupEnd") }; /////////////////////////////////////////////////////////////////////////////// // There are 2 virtually identical copies of this code: // - $GCLI_HOME/build/prefix-gcli.jsm // - $GCLI_HOME/build/mini_require.js // They should both be kept in sync var debugDependencies = false; /** * Define a module along with a payload. * @param {string} moduleName Name for the payload * @param {ignored} deps Ignored. For compatibility with CommonJS AMD Spec * @param {function} 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 (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 = {}; /** * 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 (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 {string[]|string} deps A name, or names for the payload * @param {function|undefined} callback Function to call when the dependencies * are resolved * @return {undefined|object} 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 {string} moduleName A name for the payload to lookup * @return {object} 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 (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 (debugDependencies) { console.log(this.depth + " Compiling module: " + moduleName); } if (typeof module == "function") { if (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 (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); /////////////////////////////////////////////////////////////////////////////// /* * The API of interest to people wanting to create GCLI commands is as * follows. The implementation of this API is left to bug 659061 and other * bugs. */ define('gcli/index', [ ], function(require, exports, module) { exports.addCommand = function() { /* implementation goes here */ }; exports.removeCommand = function() { /* implementation goes here */ }; exports.startup = function() { /* implementation goes here */ }; exports.shutdown = function() { /* implementation goes here */ }; }); /////////////////////////////////////////////////////////////////////////////// /* * require GCLI so it can be exported as declared in EXPORTED_SYMBOLS * The dependencies specified here should be the same as in Makefile.dryice.js */ var gcli = require("gcli/index"); gcli.createView = require("gcli/ui/start/firefox"); gcli._internal = { require: require, define: define, console: console };