diff --git a/lib/console.js b/lib/console.js index 64d21a4266cdc6..d37114b2ec416c 100644 --- a/lib/console.js +++ b/lib/console.js @@ -21,57 +21,88 @@ var util = require('util'); -exports.log = function() { - process.stdout.write(util.format.apply(this, arguments) + '\n'); +function Console(stdout, stderr) { + if (!(this instanceof Console)) { + return new Console(stdout, stderr); + } + if (!stdout || typeof stdout.write !== 'function') { + throw new TypeError('Console expects a writable stream instance'); + } + if (!stderr) { + stderr = stdout; + } + var prop = { + writable: true, + enumerable: false, + configurable: true + }; + prop.value = stdout; + Object.defineProperty(this, '_stdout', prop); + prop.value = stderr; + Object.defineProperty(this, '_stderr', prop); + prop.value = {}; + Object.defineProperty(this, '_times', prop); + + // bind the prototype functions to this Console instance + Object.keys(Console.prototype).forEach(function(k) { + this[k] = this[k].bind(this); + }, this); +} + +Console.prototype.log = function() { + this._stdout.write(util.format.apply(this, arguments) + '\n'); }; -exports.info = exports.log; +Console.prototype.info = Console.prototype.log; -exports.warn = function() { - process.stderr.write(util.format.apply(this, arguments) + '\n'); +Console.prototype.warn = function() { + this._stderr.write(util.format.apply(this, arguments) + '\n'); }; -exports.error = exports.warn; +Console.prototype.error = Console.prototype.warn; -exports.dir = function(object) { - process.stdout.write(util.inspect(object) + '\n'); +Console.prototype.dir = function(object) { + this._stdout.write(util.inspect(object) + '\n'); }; -var times = {}; -exports.time = function(label) { - times[label] = Date.now(); +Console.prototype.time = function(label) { + this._times[label] = Date.now(); }; -exports.timeEnd = function(label) { - var time = times[label]; +Console.prototype.timeEnd = function(label) { + var time = this._times[label]; if (!time) { throw new Error('No such label: ' + label); } var duration = Date.now() - time; - exports.log('%s: %dms', label, duration); + this.log('%s: %dms', label, duration); }; -exports.trace = function(label) { +Console.prototype.trace = function(label) { // TODO probably can to do this better with V8's debug object once that is // exposed. var err = new Error; err.name = 'Trace'; err.message = label || ''; Error.captureStackTrace(err, arguments.callee); - console.error(err.stack); + this.error(err.stack); }; -exports.assert = function(expression) { +Console.prototype.assert = function(expression) { if (!expression) { var arr = Array.prototype.slice.call(arguments, 1); require('assert').ok(false, util.format.apply(this, arr)); } }; + + +module.exports = new Console(process.stdout, process.stderr); +module.exports.Console = Console; diff --git a/test/simple/test-console-instance.js b/test/simple/test-console-instance.js new file mode 100644 index 00000000000000..e0166ebd3148b4 --- /dev/null +++ b/test/simple/test-console-instance.js @@ -0,0 +1,80 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + + +var common = require('../common'); +var assert = require('assert'); +var Stream = require('stream'); +var Console = require('console').Console; +var called = false; + +// ensure the Console instance doesn't write to the +// process' "stdout" or "stderr" streams +process.stdout.write = process.stderr.write = function() { + throw new Error('write() should not be called!'); +}; + +// make sure that the "Console" function exists +assert.equal('function', typeof Console); + +// make sure that the Console constructor throws +// when not given a writable stream instance +assert.throws(function () { + new Console(); +}, /Console expects a writable stream/); + +var out = new Stream(); +var err = new Stream(); +out.writable = err.writable = true; +out.write = err.write = function(d) {}; + +var c = new Console(out, err); + +out.write = err.write = function(d) { + assert.equal(d, 'test\n'); + called = true; +}; + +assert(!called); +c.log('test'); +assert(called); + +called = false; +c.error('test'); +assert(called); + +out.write = function(d) { + assert.equal('{ foo: 1 }\n', d); + called = true; +}; + +called = false; +c.dir({ foo: 1 }); +assert(called); + +// ensure that the console functions are bound to the console instance +called = 0; +out.write = function(d) { + called++; + assert.equal(d, called + ' ' + (called - 1) + ' [ 1, 2, 3 ]\n'); +}; +[1, 2, 3].forEach(c.log); +assert.equal(3, called);