/* * CPLog.j * Foundation * * Created by Thomas Robinson. * Copyright 2008, 280 North, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ exports.CPLogDisable = false; var CPLogDefaultTitle = "Cappuccino"; var CPLogLevels = ["fatal", "error", "warn", "info", "debug", "trace"]; var CPLogDefaultLevel = CPLogLevels[3]; var _CPLogLevelsInverted = {}; for (var i = 0; i < CPLogLevels.length; i++) _CPLogLevelsInverted[CPLogLevels[i]] = i; var _CPLogRegistrations = {}; // Register Functions: // Register a logger for all levels, or up to an optional max level exports.CPLogRegister = function(aProvider, aMaxLevel) { exports.CPLogRegisterRange(aProvider, CPLogLevels[0], aMaxLevel || CPLogLevels[CPLogLevels.length-1]); } // Register a logger for a range of levels exports.CPLogRegisterRange = function(aProvider, aMinLevel, aMaxLevel) { var min = _CPLogLevelsInverted[aMinLevel]; var max = _CPLogLevelsInverted[aMaxLevel]; if (min !== undefined && max !== undefined) for (var i = 0; i <= max; i++) CPLogRegisterSingle(aProvider, CPLogLevels[i]); } // Register a logger for a single level exports.CPLogRegisterSingle = function(aProvider, aLevel) { if (!_CPLogRegistrations[aLevel]) _CPLogRegistrations[aLevel] = []; // prevent duplicate registrations for (var i = 0; i < _CPLogRegistrations[aLevel].length; i++) if (_CPLogRegistrations[aLevel][i] === aProvider) return; _CPLogRegistrations[aLevel].push(aProvider); } exports.CPLogUnregister = function(aProvider) { for (var aLevel in _CPLogRegistrations) for (var i = 0; i < _CPLogRegistrations[aLevel].length; i++) if (_CPLogRegistrations[aLevel][i] === aProvider) _CPLogRegistrations[aLevel].splice(i--, 1); // decrement since we're removing an element } // Main CPLog, which dispatches to individual loggers function _CPLogDispatch(parameters, aLevel, aTitle) { if (aTitle == undefined) aTitle = CPLogDefaultTitle; if (aLevel == undefined) aLevel = CPLogDefaultLevel; // use sprintf if param 0 is a string and there is more than one param. otherwise just convert param 0 to a string var message = (typeof parameters[0] == "string" && parameters.length > 1) ? sprintf.apply(null, parameters) : String(parameters[0]); if (_CPLogRegistrations[aLevel]) for (var i = 0; i < _CPLogRegistrations[aLevel].length; i++) _CPLogRegistrations[aLevel][i](message, aLevel, aTitle); } // Setup CPLog() and CPLog.xxx() aliases var CPLog = exports.CPLog = function() { _CPLogDispatch(arguments); } for (var i = 0; i < CPLogLevels.length; i++) CPLog[CPLogLevels[i]] = (function(level) { return function() { _CPLogDispatch(arguments, level); }; })(CPLogLevels[i]); // Helpers functions: var _CPFormatLogMessage = function(aString, aLevel, aTitle) { var now = new Date(); aLevel = ( aLevel == null ? '' : ' [' + aLevel + ']' ); if (typeof sprintf == "function") return sprintf("%4d-%02d-%02d %02d:%02d:%02d.%03d %s%s: %s", now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes(), now.getSeconds(), now.getMilliseconds(), aTitle, aLevel, aString); else return now + " " + aTitle + aLevel + ": " + aString; } // Loggers: var ANSI_ESC = String.fromCharCode(0x1B); var ANSI_CSI = ANSI_ESC + '['; var ANSI_TEXT_PROP = 'm'; var ANSI_RESET = '0'; var ANSI_BOLD = '1'; var ANSI_FAINT = '2'; // unsupported? var ANSI_NORMAL = '22'; var ANSI_ITALIC = '3'; // unsupported? var ANSI_UNDER = '4'; var ANSI_UNDER_DBL = '21'; // unsupported? var ANSI_UNDER_OFF = '24'; var ANSI_BLINK = '5'; var ANSI_BLINK_FAST = '6'; // unsupported? var ANSI_BLINK_OFF = '25'; var ANSI_REVERSE = '7'; var ANSI_POSITIVE = '27'; var ANSI_CONCEAL = '8'; var ANSI_REVEAL = '28'; var ANSI_FG = '3'; var ANSI_BG = '4'; var ANSI_FG_INTENSE = '9'; var ANSI_BG_INTENSE = '10'; var ANSI_BLACK = '0'; var ANSI_RED = '1'; var ANSI_GREEN = '2'; var ANSI_YELLOW = '3'; var ANSI_BLUE = '4'; var ANSI_MAGENTA = '5'; var ANSI_CYAN = '6'; var ANSI_WHITE = '7'; var colorCodeMap = { "black" : ANSI_BLACK, "red" : ANSI_RED, "green" : ANSI_GREEN, "yellow" : ANSI_YELLOW, "blue" : ANSI_BLUE, "magenta" : ANSI_MAGENTA, "cyan" : ANSI_CYAN, "white" : ANSI_WHITE } function ANSIControlCode(code, parameters) { if (parameters == undefined) parameters = ""; else if (typeof(parameters) == 'object' && (parameters instanceof Array)) parameters = parameters.join(';'); return ANSI_CSI + String(parameters) + String(code); } // simple text helpers: function ANSITextApplyProperties(string, properties) { return ANSIControlCode(ANSI_TEXT_PROP, properties) + String(string) + ANSIControlCode(ANSI_TEXT_PROP); } exports.ANSITextColorize = function(string, color) { if (colorCodeMap[color] == undefined) return string; return ANSITextApplyProperties(string, ANSI_FG + colorCodeMap[color]); } // CPLogPrint uses the print() functions present in many non-browser command line JavaScript interpreters var levelColorMap = { "fatal": "red", "error": "red", "warn" : "yellow", "info" : "green", "debug": "cyan", "trace": "blue" } // CPLogConsole uses the built in "console" object exports.CPLogConsole = function(aString, aLevel, aTitle) { if (typeof console != "undefined") { var message = _CPFormatLogMessage(aString, aLevel, aTitle); var logger = { "fatal": "error", "error": "error", "warn": "warn", "info": "info", "debug": "debug", "trace": "debug" }[aLevel]; if (logger && console[logger]) console[logger](message); else if (console.log) console.log(message); } } #if COMMONJS exports.CPLogPrint = function(aString, aLevel, aTitle) { if (typeof print != "undefined") { if (aLevel == "fatal" || aLevel == "error" || aLevel == "warn") var message = exports.ANSITextColorize(_CPFormatLogMessage(aString, aLevel, aTitle), levelColorMap[aLevel]); else var message = _CPFormatLogMessage(aString, exports.ANSITextColorize(aLevel, levelColorMap[aLevel]), aTitle); require("system").stderr.print(message); } } #else // CPLogAlert uses basic browser alert() functions exports.CPLogAlert = function(aString, aLevel, aTitle) { if (typeof alert != "undefined" && !exports.CPLogDisable) { var message = _CPFormatLogMessage(aString, aLevel, aTitle); exports.CPLogDisable = !confirm(message + "\n\n(Click cancel to stop log alerts)"); } } // CPLogPopup uses a slick popup window in the browser: var CPLogWindow = null; exports.CPLogPopup = function(aString, aLevel, aTitle) { try { if (exports.CPLogDisable || window.open == undefined) return; if (!CPLogWindow || !CPLogWindow.document) { CPLogWindow = window.open("", "_blank", "width=600,height=400,status=no,resizable=yes,scrollbars=yes"); if (!CPLogWindow) { exports.CPLogDisable = !confirm(aString + "\n\n(Disable pop-up blocking for CPLog window; Click cancel to stop log alerts)"); return; } _CPLogInitPopup(CPLogWindow); } var logDiv = CPLogWindow.document.createElement("div"); logDiv.setAttribute("class", aLevel || "fatal"); var message = _CPFormatLogMessage(aString, null, aTitle); logDiv.appendChild(CPLogWindow.document.createTextNode(message)); CPLogWindow.log.appendChild(logDiv); if (CPLogWindow.focusEnabled.checked) CPLogWindow.focus(); if (CPLogWindow.blockEnabled.checked) CPLogWindow.blockEnabled.checked = CPLogWindow.confirm(message+"\nContinue blocking?"); if (CPLogWindow.scrollEnabled.checked) CPLogWindow.scrollToBottom(); } catch(e) { // TODO: some error handling/reporting } } function _CPLogInitPopup(logWindow) { var doc = logWindow.document; // HACK so that head is available below: doc.writeln(""); doc.title = CPLogDefaultTitle + " Run Log"; var head = doc.getElementsByTagName("head")[0]; var body = doc.getElementsByTagName("body")[0]; var base = window.location.protocol + "//" + window.location.host + window.location.pathname; base = base.substring(0,base.lastIndexOf("/")+1); var link = doc.createElement("link"); link.setAttribute("type", "text/css"); link.setAttribute("rel", "stylesheet"); link.setAttribute("href", base+"Frameworks/Foundation/Resources/log.css"); link.setAttribute("media", "screen"); head.appendChild(link); var div = doc.createElement("div"); div.setAttribute("id", "header"); body.appendChild(div); // Enablers var ul = doc.createElement("ul"); ul.setAttribute("id", "enablers"); div.appendChild(ul); for (var i = 0; i < CPLogLevels.length; i++) { var li = doc.createElement("li"); li.setAttribute("id", "en"+CPLogLevels[i]); li.setAttribute("class", CPLogLevels[i]); li.setAttribute("onclick", "toggle(this);"); li.setAttribute("enabled", "yes"); li.appendChild(doc.createTextNode(CPLogLevels[i])); ul.appendChild(li); } // Options var ul = doc.createElement("ul"); ul.setAttribute("id", "options"); div.appendChild(ul); var options = {"focus":["Focus",false], "block":["Block",false], "wrap":["Wrap",false], "scroll":["Scroll",true], "close":["Close",true]}; for (o in options) { var li = doc.createElement("li"); ul.appendChild(li); logWindow[o+"Enabled"] = doc.createElement("input"); logWindow[o+"Enabled"].setAttribute("id", o); logWindow[o+"Enabled"].setAttribute("type", "checkbox"); if (options[o][1]) logWindow[o+"Enabled"].setAttribute("checked", "checked"); li.appendChild(logWindow[o+"Enabled"]); var label = doc.createElement("label"); label.setAttribute("for", o); label.appendChild(doc.createTextNode(options[o][0])); li.appendChild(label); } // Log logWindow.log = doc.createElement("div"); logWindow.log.setAttribute("class", "enerror endebug enwarn eninfo enfatal entrace"); body.appendChild(logWindow.log); logWindow.toggle = function(elem) { var enabled = (elem.getAttribute("enabled") == "yes") ? "no" : "yes"; elem.setAttribute("enabled", enabled); if (enabled == "yes") logWindow.log.className += " " + elem.id else logWindow.log.className = logWindow.log.className.replace(new RegExp("[\\s]*"+elem.id, "g"), ""); } // Scroll logWindow.scrollToBottom = function() { logWindow.scrollTo(0, body.offsetHeight); } // Wrap logWindow.wrapEnabled.addEventListener("click", function() { logWindow.log.setAttribute("wrap", logWindow.wrapEnabled.checked ? "yes" : "no"); }, false); // Clear logWindow.addEventListener("keydown", function(e) { var e = e || logWindow.event; if (e.keyCode == 75 && (e.ctrlKey || e.metaKey)) { while (logWindow.log.firstChild) { logWindow.log.removeChild(logWindow.log.firstChild); } e.preventDefault(); } }, "false"); // Parent closing window.addEventListener("unload", function() { if (logWindow && logWindow.closeEnabled && logWindow.closeEnabled.checked) { exports.CPLogDisable = true; logWindow.close(); } }, false); // Log popup closing logWindow.addEventListener("unload", function() { if (!exports.CPLogDisable) { exports.CPLogDisable = !confirm("Click cancel to stop logging"); } }, false); } #endif