From 2793046b7fca0527d5e10fd2e3789ad16a20b55f Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Thu, 14 Oct 2010 08:46:00 -0700 Subject: [PATCH] style change (include semis), added fuse, more native extensions, repl --- fuse-test.js | 121 ++++++++++++++++++++++++++++++++++++++++++++ lib/array-ext.js | 75 ++++++++++++++------------- lib/ext.js | 31 +++++------- lib/file-ext.js | 56 ++++++++++++++++---- lib/fuse.js | 48 ++++++++++++++++++ lib/index.js | 52 ++++++++++++++++--- lib/line-emitter.js | 40 +++++++++------ lib/math-ext.js | 20 ++++++++ lib/object-ext.js | 10 ++++ lib/range.js | 22 ++++++++ lib/repr.js | 30 +++++++++++ lib/string-ext.js | 11 ++++ package.json | 2 +- repl.js | 4 ++ test.js | 60 ++++++++++++++++++++++ 15 files changed, 496 insertions(+), 86 deletions(-) create mode 100644 fuse-test.js create mode 100644 lib/fuse.js create mode 100644 lib/math-ext.js create mode 100644 lib/object-ext.js create mode 100644 lib/range.js create mode 100644 lib/repr.js create mode 100644 lib/string-ext.js create mode 100755 repl.js create mode 100644 test.js diff --git a/fuse-test.js b/fuse-test.js new file mode 100644 index 0000000..28cacd6 --- /dev/null +++ b/fuse-test.js @@ -0,0 +1,121 @@ +var fuse = require('./lib/fuse') + , a = [] + , n = Number(process.argv[2]) || 10000000 // 10,000,000 + , iters = a.length + , parts = [] + , s + , start + ; + +iters = n; +while (iters >= 1) { + s = (iters % 1000).toString(); + if (iters / 1000 >= 1) while (s.length < 3) s = '0' + s; + parts.push(s); + iters = iters / 1000; +} +console.log(parts.reverse().join(',') + ' iterations'); +while (n--) a.push(n); + +function time(title, fn, cb) { + console.log('---- ' + title + ' ----'); + var i = 0 + , n = 5 + , start + , avg = 0 + , min + , max = 0 + , next = function() { + start = +new Date(); + fn(function() { + var time = +new Date() - start; + if (time > max) max = time; + if (!min || time < min) min = time; + avg += time; + if (++i < n) next(); + else done(); + }); + } + , done = function() { + avg /= n; + console.log('avg: ' + avg + 'ms'); + console.log('min: ' + min + 'ms'); + console.log('max: ' + max + 'ms'); + console.log(); + } + next(); +} + +function timeSync(title, fn, cb) { + console.log('---- ' + title + ' ----'); + var i = 0 + , n = 5 + , start + , avg = 0 + , min + , max = 0 + ; + for (; i < n; ++i) { + start = +new Date(); + fn(); + var time = +new Date() - start; + if (time > max) max = time; + if (!min || time < min) min = time; + avg += time; + } + avg /= n; + console.log('avg: ' + avg + 'ms'); + console.log('min: ' + min + 'ms'); + console.log('max: ' + max + 'ms'); + console.log(); +} + +// Plain old while loop +timeSync('while loop', function() { + var b = [] + , i = a.length + ; + while (i--) { + b[i] = (a[i] + 1) * 2; + for (var j = 0; j < 100; ++j) j; + } +}); + +// Composed map +timeSync('composed map', function() { + a.map(function(x) { + for (var j = 0; j < 100; ++j) j; + return (x + 1) * 2; + }); +}); + +// Chained map (modular) +timeSync('chained map', function() { + a.map(add1).map(wasteTime).map(times2); +}); + +// Synchronous fused map +timeSync('fused map (sync)', function() { + fuse.fusedMapSync(add1, wasteTime, times2)(a); +}); + +// Asynchronous fused map (test not actually async, but meh) +time('fused map (async)', function(cb) { + fuse.fusedMap(add1Async, wasteTimeAsync, times2Async)(a, function(b) { + cb(); + }); +}); + +function add1(v) { return v + 1; } +function times2(v) { return v * 2; } +function wasteTime(v) { + for (var i = 0; i < 100; ++i) i; + return v; +} + +function add1Async(v, cb) { cb(v + 1); } +function times2Async(v, cb) { cb(v * 2); } +function wasteTimeAsync(v, cb) { + for (var i = 0; i < 100; ++i) i; + cb(v); +} diff --git a/lib/array-ext.js b/lib/array-ext.js index 878d81a..0798e9b 100644 --- a/lib/array-ext.js +++ b/lib/array-ext.js @@ -1,72 +1,79 @@ -exports.extend = function(obj) { - ArrayExt.extend(obj) -} +exports.extendNative = function() { + Object.keys(ArrayExt).forEach(function(k) { + if (Array.prototype[k]) return; // don't overwrite existing members + Array.prototype[k] = function() { + var fn = ArrayExt[k] + , args = [].slice.call(arguments) + ; + args.shift(this); + fn.apply(ArrayExt, args); + }; + }); +}; + +var ArrayToString = [].toString(); var ArrayExt = exports.ArrayExt = { - extend: function(obj) { - Object.keys(this).forEach(function(k) { - if (k === 'extend' || obj[k]) return - var fn = this[k] - obj[k] = function() { - fn.apply(this, [this].concat([].slice.call(arguments))) - } - }) - } - // abbrev -// assoc // [1,2,3,4,5].at(-1) => 5 - , at: function(a, i) { - if (i >= 0) return a[i] - return a[a.length + i] + at: function(a, i) { + if (i >= 0) return a[i]; + return a[a.length + i]; } // TODO make this work for non-array objects , compact: function(a) { var b = [] , i = a.length - while (i--) - if (a[i] !== null && a[i] !== undefined) b[i] = a[i] - return b + ; + while (i--) { + if (a[i] !== null && a[i] !== undefined) b[i] = a[i]; + } + return b; } - , first: function(a) { return a[0] } + , first: function(a) { return a[0]; } // Based on underscore.js's flatten , flatten: function(a) { return a.reduce(function(initial, elem) { - if (elem && elem.flatten) initial = initial.concat(elem.flatten()) - else initial.push(elem) - return initial - }) + if (elem && elem.flatten) initial = initial.concat(elem.flatten()); + else initial.push(elem); + return initial; + }); } , grep: function(a, regex) { - return a.filter(function(v) { return regex.match(v) }) + return a.filter(function(v) { return regex.match(v); }); } - , last: function(a) { return a[a.length-1] } + , last: function(a) { return a[a.length-1]; } , max: function(a) { - return a.reduce(function(max, v) { return v > max ? v : max }) + return a.reduce(function(max, v) { return v > max ? v : max }); } , min: function(a) { - return a.reduce(function(min, v) { return v < min ? v : min }) + return a.reduce(function(min, v) { return v < min ? v : min }); } // pack // partition - // rassoc + + , toString: function(a) { + return '[' + ArrayToString.call(a) + ']'; + } , unique: function(a) { var b = [] , i = 0 , n = a.length - for (; i < n; ++i) - if (b.indexOf(a[i]) === -1) b.push(a[i]) - return b + ; + for (; i < n; ++i) { + if (b.indexOf(a[i]) === -1) b.push(a[i]); + } + return b; } -} +}; diff --git a/lib/ext.js b/lib/ext.js index 8bdf469..17445d1 100644 --- a/lib/ext.js +++ b/lib/ext.js @@ -1,18 +1,13 @@ -exports.createExt = function() { - return new Ext() -} - -var Ext = exports.Ext = function(){} -Ext.prototype = { - extend: function(obj) { - Object.keys(this).forEach(function(k) { - if (obj[k]) return // don't overwrite existing members - var fn = this[k] - obj[k] = function() { - // Like JavaScript itself functions effectively take `this` as the first param - var args = [].slice.call(arguments).shift(this) - fn.apply(null, args) - } - }) - } -} +exports.extend = function(obj, ext) { + // FIXME why doesn't this work when the caller supplies + // a native type's proto for obj? e.g. Array.prototype + Object.keys(ext).forEach(function(k) { + if (obj[k]) return; // don't overwrite existing members + obj[k] = function() { + var fn = ext[k] + , args = [].slice.call(arguments) + ; + fn.apply(null, args); + }; + }); +}; diff --git a/lib/file-ext.js b/lib/file-ext.js index 0a602a4..679f04e 100644 --- a/lib/file-ext.js +++ b/lib/file-ext.js @@ -1,23 +1,57 @@ var fs = require('fs') + , ArrayExt = require('./array-ext').ArrayExt , LineEmitter = require('./line-emitter').LineEmitter + , ext = require('./ext') + ; exports.extend = function(obj) { - FileExt.extend(obj) + ext.extend(obj || fs, FileExt); } var FileExt = exports.FileExt = { - eachLine: function(f, fn, cb) { - var le = new LineEmitter(fs.createReadStream(f)) - le.on('line', fn) - le.on('end', cb) + eachLine: function(f, options) { + if (typeof options === 'function') options = {line: options, end: arguments[2]}; + var lineType = typeof options.line + , endType = typeof options.end + ; + if (lineType !== 'function' && endType !== 'function') + throw new Error('bad arguments'); + var le = new LineEmitter(f); + if (typeof options.line === 'function') + le.on('line', function(line) { options.line(line); }); + if (typeof options.end === 'function') + le.on('end', function() { options.end(); }); + } + + , grep: function(regex, f, callback) { + if (!callback) throw new Error('grep requires a callback'); + var results = []; + FileExt.eachLine(f, + { line: function(line) { if (line.match(regex)) results.push(line); } + , end: callback(results) + }); + } + + , home: function(user, callback) { + if (user && callback && user !== process.env['USER']) { + FileExt.grep(new RegExp('^' + user + ':'), '/etc/passwd', function(line) { + callback(line && line.split(':')[4]); + }); + } + else if (user) + throw new Error('home requires a callback with user'); + else if (callback) + callback(process.env['HOME']); + else + return process.env['HOME']; } , readLines: function(f, cb) { - var lines = [] - , addLine = function(line) { - lines.push(line) - } - FileExt.eachLine(f, addLine, function() { cb(lines) }) + var lines = []; + FileExt.eachLine(f, { line: function(line) { lines.push(line); } + , end: function() { cb(lines); } + }); } -} + +}; diff --git a/lib/fuse.js b/lib/fuse.js new file mode 100644 index 0000000..612d0f5 --- /dev/null +++ b/lib/fuse.js @@ -0,0 +1,48 @@ +exports.extendArray = function() { + Array.prototype.fusedMap = function() { + var args = [].slice.call(arguments); + return function(cb) { + fusedMap.apply(null, args)(this, cb); + } + }; + Array.prototype.fusedMapSync = function() { + return fusedMapSync.apply(null, arguments)(this); + }; +}; + +exports.fusedMap = fusedMap; +function fusedMap() { + var fns = Array.isArray(arguments[0]) ? arguments[0] : [].slice.call(arguments); + return function(a, cb) { + var b = [] + , n = a.length + ; + a.forEach(function(v, i) { + var nFns = fns.length + , next = function(j) { + if (j < nFns) + fns[j](v, function(v) { next(j + 1); }) + else + done(); + } + , done = function() { + b[i] = v; + if (--n === 0) cb(b); + } + next(0); + }) + } +} + +exports.fusedMapSync = fusedMapSync; +function fusedMapSync() { + var fns = Array.isArray(arguments[0]) ? arguments[0] : [].slice.call(arguments) + , n = fns.length + ; + return function(a) { + return a.map(function(v) { + for (var i = 0; i < n; ++i) v = fns[i](v); + return v; + }); + }; +} diff --git a/lib/index.js b/lib/index.js index 4b7e729..450149e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,10 +1,48 @@ -var AE = require('./array-ext') - , FE = require('./file-ext') +var ArrayExt = require('./array-ext') + , FileExt = require('./file-ext') + , MathExt = require('./math-ext') + , ObjectExt = require('./object-ext') + , StringExt = require('./string-ext') + , Range = require('./range') + , repr = require('./repr').repr + , fuse = require('./fuse') + ; -module.exports = { ArrayExt: AE.ArrayExt - , FileExt: FE.FileExt +module.exports = { ArrayExt: ArrayExt.ArrayExt + , FileExt: FileExt.FileExt + , MathExt: MathExt.MathExt + , ObjectExt: ObjectExt.ObjectExt + , StringExt: StringExt.StringExt , extendNative: function() { - AE.extend(Object.getPrototypeOf(Array)) - FE.extend(require('fs')) + // Extend native types + ArrayExt.extendNative(); + MathExt.extendNative(); + fuse.extendArray(); + + // Extend Node + FileExt.extend(require('fs')); + + global['Range'] = Range; + global['repr'] = repr; + + return module.exports; } - } + , requireEverything: function() { + assert = require('assert'); + child_process = require('child_process'); + crypto = require('crypto'); + dgram = require('dgram'); + dns = require('dns'); + events = require('events'); + fs = require('fs'); + http = require('http'); + net = require('net'); + path = require('path'); + querystring = require('querystring'); + repl = require('repl'); + sys = require('sys'); + url = require('url'); + + return module.exports; + } + }; diff --git a/lib/line-emitter.js b/lib/line-emitter.js index 12fb1fd..8a5a9b3 100644 --- a/lib/line-emitter.js +++ b/lib/line-emitter.js @@ -1,26 +1,36 @@ -var sys = require('sys') +var fs = require('fs') + , sys = require('sys') , EventEmitter = require('events').EventEmitter + ; -exports.LineEmitter = LineEmitter -function LineEmitter(stream) { +exports.LineEmitter = LineEmitter; +function LineEmitter(fileOrStream) { var self = this - this.buffer = '' + , stream = typeof fileOrStream === 'string' ? fs.createReadStream(fileOrStream) : fileOrStream + ; + this.buffer = ''; + this.ended = false; stream.on('data', function(chunk) { - self.buffer += chunk - process.nextTick(function() { self.checkForLine() }) + self.buffer += chunk; + self.checkForLine(); }) - stream.on('end', function() { this.emit('end') }) - stream.on('error', function(err) { this.emit('error', err) }) + stream.on('end', function() { self.ended = true; }); + stream.on('error', function(err) { self.emit('error', err); }); } -sys.inherits(LineEmitter, EventEmitter) +sys.inherits(LineEmitter, EventEmitter); LineEmitter.prototype.checkForLine = function() { var i = this.buffer.indexOf('\n') , self = this - if (i === -1) return - this.emit('line', this.buffer.slice(0, i)) - this.buffer = this.buffer.slice(i + 1) - if (this.buffer.indexOf('\n') !== -1) { - process.nextTick(function() { self.checkForLine() }) + ; + if (i === -1) { + if (this.ended) { + if (this.buffer.length > 0) self.emit('line', this.buffer); + self.emit('end'); + } + return; } -} + this.emit('line', this.buffer.slice(0, i)); + this.buffer = this.buffer.slice(i + 1); + process.nextTick(function() { self.checkForLine(); }); +}; diff --git a/lib/math-ext.js b/lib/math-ext.js new file mode 100644 index 0000000..80618d6 --- /dev/null +++ b/lib/math-ext.js @@ -0,0 +1,20 @@ +exports.extendNative = function() { + Math.sum = sum; + Math.avg = avg; +}; + +exports.sum = sum; +function sum() { + var nums = Array.isArray(arguments[0]) ? arguments[0] : arguments + , i = nums.length + , sum = 0 + ; + while (i--) sum += arguments[i]; + return sum; +} + +exports.avg = avg; +function avg() { + var nums = Array.isArray(arguments[0]) ? arguments[0] : arguments; + return sum.call(this, nums) / nums.length; +} diff --git a/lib/object-ext.js b/lib/object-ext.js new file mode 100644 index 0000000..fe885b1 --- /dev/null +++ b/lib/object-ext.js @@ -0,0 +1,10 @@ +var ext = exports.ObjectExt = { + + cmp: function(a, b) { + if (a === b) return 0; + if (a < b) return -1; + if (a > b) return 1; + throw new Error('cannot effectively compare values'); + } + +}; diff --git a/lib/range.js b/lib/range.js new file mode 100644 index 0000000..692d365 --- /dev/null +++ b/lib/range.js @@ -0,0 +1,22 @@ +exports.Range = Range; +function Range(start, length) { + this.start = start; + this.length = length; +}; + +Range.prototype.inRange = function(val) { + if (this.test) return this.test(val); + return val >= this.start && val <= this.start + this.length; +}; + +Range.prototype.toArray = function(nth) { + var a = [] + , i = this.length + ; + nth = nth || this.nth; + if (nth) + while (i--) a[i] = nth(i); + else + while (i--) a[i] = this.start + i; + return a; +}; diff --git a/lib/repr.js b/lib/repr.js new file mode 100644 index 0000000..f43df2b --- /dev/null +++ b/lib/repr.js @@ -0,0 +1,30 @@ +// readable string representations of values +exports.repr = function(x) { + if (x !== null && x !== undefined && typeof x.repr === 'function') return x.repr(); + + if (x === null || x === undefined || + x instanceof Number || typeof x === 'number' || + x instanceof Boolean || typeof x === 'boolean' || + x instanceof RegExp || x.constructor === RegExp) + { + return String(x); + } + + if (x instanceof String || typeof x === 'string') + return '"' + x.replace(/"/g, '\\"') + '"'; + + if (x instanceof Date || x.toUTCString) + return 'new Date(' + (+x) + ')'; // lame + + if (Array.isArray(x)) + return '[' + x.map(repr).join(',') + ']'; + + if (x instanceof Function || typeof x === 'function') + return x.toString(); + + // TODO determine how far to go with this. should we include non-enumerable props too? + if (x instanceof Object || typeof x === 'object') + return '{' + Object.keys(x).map(function(k) { return repr(k) + ':' + repr(x[k]); }).join(',') + '}'; + + throw new Error("don't know how to represent " + x); +}; diff --git a/lib/string-ext.js b/lib/string-ext.js new file mode 100644 index 0000000..f4873ee --- /dev/null +++ b/lib/string-ext.js @@ -0,0 +1,11 @@ +var StringExt = exports.StringExt = { + + cmp: function(a, b) { + if (a === b) return 0; + if (a < b) return -1; + return 1; // a > b + } + + // unpack + +}; diff --git a/package.json b/package.json index 15af7f6..791884b 100644 --- a/package.json +++ b/package.json @@ -20,4 +20,4 @@ , "url" : "http://github.com/samsonjs/batteries/raw/master/LICENSE" } ] -} \ No newline at end of file +} diff --git a/repl.js b/repl.js new file mode 100755 index 0000000..a5bb1ef --- /dev/null +++ b/repl.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node + +require('./lib/index').requireEverything().extendNative() +require('repl').start() diff --git a/test.js b/test.js new file mode 100644 index 0000000..1e527fb --- /dev/null +++ b/test.js @@ -0,0 +1,60 @@ +var fs = require('fs') + , spawn = require('child_process').spawn + , LineEmitter = require('./lib/line-emitter').LineEmitter + ; + + +require('./lib/index').extendNative(); + + +// wc -l, for reference + +(function(n){ + var wc = spawn('wc', ['-l', __filename]); + wc.stdout.on('data', function(chunk) { + var m = chunk.toString().trim().split(/\s+/); + n += m[0] || ''; + }) + wc.on('exit', function(code) { + console.log(n + ' lines in this file [wc -l]'); + }); +}('')); + + +// LineEmitter + +var le = new LineEmitter(__filename) + , lines = [] + ; +le.on('line', function(line) { lines.push(line); }); +le.on('end', function() { + console.log(lines.length + ' lines in this file [LineEmitter]'); +}); + + +// fs.eachLine (named params) + +(function(n){ + fs.eachLine(__filename, { + line: function(line) { ++n; }, + end: function() { + console.log(n + ' lines in this file [eachLine (named params)]'); + } + }); +})(0); + + +// fs.eachLine (unnamed params) + +(function(n){ + fs.eachLine(__filename, function(line) { ++n; }, function() { + console.log(n + ' lines in this file [eachLine (unnamed params)]'); + }); +}(0)); + + +// fs.readLines +fs.readLines(__filename, function(lines) { + console.log(lines.length + ' lines in this file [readLines]'); +}); +