Compare commits

..

No commits in common. "master" and "v0.1.0" have entirely different histories.

26 changed files with 704 additions and 733 deletions

View file

@ -1,4 +1,4 @@
Copyright 2010 - 2011 Sami Samhuri. All rights reserved. Copyright 2010 Sami Samhuri. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the deal in the Software without restriction, including without limitation the

121
fuse-test.js Normal file
View file

@ -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);
}

126
lib/array-ext.js Normal file
View file

@ -0,0 +1,126 @@
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 = {
// abbrev
// [1,2,3,4,5].at(-1) => 5
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;
}
, 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;
});
}
, grep: function(a, regex) {
return a.filter(function(v) { return regex.match(v); });
}
, last: function(a) { return a[a.length-1]; }
, max: function(a) {
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 });
}
// pack
// partition
, pluck: function(a /* , key paths, ... */) {
var args = [].slice.call(arguments, 1);
args.unshift(a);
return pluck.apply(null, args);
}
, sortBy: function(arr, keyPath) {
return arr.slice().sort(function(a, b) {
var propA = drillInto(a)
, propB = drillInto(b);
if (propA < propB) return -1
if (propA > propB) return 1
return 0
});
}
, 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;
}
};
// pluck
function getProp(thing, key) {
if (thing === null || thing === undefined) return thing
var prop = thing[key]
return typeof prop === 'function' ? prop.call(thing) : prop
}
function drillInto(thing, keyPath) {
return keyPath.split('.').reduce(function(memo, key) {
return getProp(memo, key)
}, thing)
}
function mapInto(thing /* , key paths, ... */) {
var keyPaths = [].slice.call(arguments, 1)
return keyPaths.map(function(keyPath) {
return drillInto(thing, keyPath)
})
}
function pluck(things /* , key paths, ... */) {
var keyPaths = [].slice.call(arguments, 1)
return things.map(function(thing) {
var results = mapInto.apply(null, [thing].concat(keyPaths))
if (results.length > 1) return results
return results[0]
})
}

View file

@ -1,136 +0,0 @@
// batteries
// Copyright 2010 - 2011 Sami Samhuri <sami@samhuri.net>
var batteries = require('./');
var ArrayExt =
{ at: at
, compact: compact
, first: first
, flatten: flatten
, grep: grep
, last: last
, max: max
, min: min
, pluck: pluck
, sortBy: sortBy
, unique: unique
};
batteries.object.extend(exports, ArrayExt);
exports.extendNative = function() {
batteries.object.extendPrototype(Array, ArrayExt);
};
// TODO
// - abbrev
// - pack
// - partition
// [1,2,3,4,5].at(-1) => 5
function at(a, i) {
if (i >= 0) return a[i];
return a[a.length + i];
}
function compact(a) {
var b = []
, i = a.length
;
while (i--) {
if (a[i] !== null && a[i] !== undefined) b[i] = a[i];
}
return b;
}
function first(a) { return a[0]; }
// Based on underscore.js's flatten
function flatten(a) {
return a.reduce(function(flat, val) {
if (val && typeof val.flatten === 'function') {
flat = flat.concat(val.flatten());
}
else if (Array.isArray(val)) {
flat = flat.concat(flatten(val));
}
else {
flat.push(val);
}
return flat;
});
}
function grep(a, regex) {
return a.filter(function(v) { return regex.test(v); });
}
function last(a) { return a[a.length-1]; }
function max(a) {
return a.reduce(function(max, v) { return v > max ? v : max });
}
function min(a) {
return a.reduce(function(min, v) { return v < min ? v : min });
}
function pluck(a /* , key paths, ... */) {
var args = [].slice.call(arguments, 1);
args.unshift(a);
return pluck.apply(null, args);
}
function sortBy(arr, keyPath) {
return arr.slice().sort(function(a, b) {
var propA = drillInto(a)
, propB = drillInto(b);
if (propA < propB) return -1
if (propA > propB) return 1
return 0
});
}
function unique(a) {
var b = []
, i = 0
, n = a.length
;
for (; i < n; ++i) {
if (b.indexOf(a[i]) === -1) b.push(a[i]);
}
return b;
}
// pluck
function getProp(thing, key) {
if (thing === null || thing === undefined) return thing
var prop = thing[key]
return typeof prop === 'function' ? prop.call(thing) : prop
}
function drillInto(thing, keyPath) {
return keyPath.split('.').reduce(function(memo, key) {
return getProp(memo, key)
}, thing)
}
function mapInto(thing /* , key paths, ... */) {
var keyPaths = [].slice.call(arguments, 1)
return keyPaths.map(function(keyPath) {
return drillInto(thing, keyPath)
})
}
function pluck(things /* , key paths, ... */) {
var keyPaths = [].slice.call(arguments, 1)
return things.map(function(thing) {
var results = mapInto.apply(null, [thing].concat(keyPaths))
if (results.length > 1) return results
return results[0]
})
}

98
lib/date-ext.js Normal file
View file

@ -0,0 +1,98 @@
exports.extendNative = function() {
Object.keys(DateExt).forEach(function(k) {
if (Date.prototype[k]) return; // don't overwrite existing members
Date.prototype[k] = function() {
var fn = DateExt[k]
, args = [].slice.call(arguments)
;
args.shift(this);
fn.apply(DateExt, args);
};
});
};
var Weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday',
'Friday', 'Saturday'];
var WeekdaysShort = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
var Months = ['January', 'February', 'March', 'April', 'May', 'June', 'July',
'August', 'September', 'October', 'November', 'December'];
var MonthsShort = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
'Sep', 'Oct', 'Nov', 'Dec'];
function pad(n, padding) {
padding = padding || '0';
return n < 10 ? (padding + n) : n;
}
var DateExt = {
// FIXME write a c extension that uses strftime to do the heavy lifting
format: function(d, fmt) {
return fmt.replace(/%(.)/, function(_, c) {
switch (c) {
case 'A': return Weekdays[d.getDay()];
case 'a': return WeekdaysShort[d.getDay()];
case 'B': return Months[d.getMonth()];
case 'b': // fall through
case 'h': return MonthsShort[d.getMonth()];
case 'D': return DateExt.format(d, '%m/%d/%y');
case 'd': return pad(d.getDate());
case 'e': return d.getDate();
case 'F': return DateExt.format(d, '%Y-%m-%d');
case 'H': return pad(d.getHours());
case 'I':
var hour = d.getHours();
if (hour == 0) hour = 12;
else if (hour > 12) hour -= 12;
return pad(hour);
case 'k': return pad(d.getHours(), ' ');
case 'l':
var hour = d.getHours();
if (hour == 0) hour = 12;
else if (hour > 12) hour -= 12;
return pad(hour, ' ');
case 'M': return pad(d.getMinutes());
case 'm': return pad(d.getMonth() + 1);
case 'n': return '\n';
case 'p': return d.getHours() < 12 ? 'AM' : 'PM';
case 'R': return DateExt.format(d, '%H:%M');
case 'r': return DateExt.format(d, '%I:%M:%S %p');
case 'S': return pad(d.getSeconds());
case 's': return d.getTime();
case 'T': return DateExt.format(d, '%H:%M:%S');
case 't': return '\t';
case 'u':
var day = d.getDay();
return day == 0 ? 7 : day; // 1 - 7, Monday is first day of the week
case 'v': return DateExt.format(d, '%e-%b-%Y');
case 'w': return d.getDay(); // 0 - 6, Sunday is first day of the week
case 'Y': return d.getFullYear();
case 'y':
var year = d.getYear();
return year < 100 ? year : year - 100;
case 'Z':
var tz = d.toString().match(/\((\w+)\)/);
return tz && tz[1] || '';
case 'z':
var off = d.getTimezoneOffset();
return (off < 0 ? '-' : '+') + pad(off / 60) + pad(off % 60);
default: return c;
}
});
},
month: function(d) {
return Months(d.getMonth());
},
shortMonth: function(d) {
return MonthsShort(d.getMonth());
},
weekday: function(d) {
return Weekdays(d.getDay());
},
shortWeekday: function(d) {
return WeekdaysShort(d.getDay());
}
};
exports.DateExt = DateExt;

View file

@ -1,17 +0,0 @@
// batteries
// Copyright 2010 - 2011 Sami Samhuri <sami@samhuri.net>
var strftime = require('strftime').strftime
, batteries = require('./')
, DateExt = { format: format }
;
exports.extendNative = function() {
batteries.object.extendPrototype(Date, DateExt);
};
batteries.object.extend(exports, DateExt);
function format(d, fmt, locale) {
return strftime.call(null, fmt, d, locale);
}

13
lib/ext.js Normal file
View file

@ -0,0 +1,13 @@
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);
};
});
};

97
lib/file-ext.js Normal file
View file

@ -0,0 +1,97 @@
var fs = require('fs')
, ArrayExt = require('./array-ext').ArrayExt
, LineEmitter = require('./line-emitter').LineEmitter
, ext = require('./ext')
, constants
, ENOENT
;
try {
constants = require('constants')
} catch (e) {
constants = process
}
ENOENT = constants.ENOENT
exports.extend = function(obj) {
ext.extend(obj || fs, FileExt);
}
var FileExt = exports.FileExt = {
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(); });
}
, exists: function(f) {
try {
fs.statSync(f)
return true
} catch (e) {
if (e.errno === ENOENT) return false
throw e
}
}
, 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) {
// user is optional so the first param may be a callback
if (typeof user === 'function') {
callback = user;
user = null;
}
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 = [];
FileExt.eachLine(f, { line: function(line) { lines.push(line); }
, end: function() { cb(lines); }
});
}
};
// isDirectory, isFile, isSymbolicLink, etc.
var s = fs.statSync(__dirname);
Object.keys(Object.getPrototypeOf(s)).forEach(function(k) {
if (k.match(/^is/) && typeof s[k] === 'function') {
FileExt[k] = function(f, cb) {
if (cb) {
fs.stat(f, function(err, stat) {
cb(err, err ? null : stat[k]());
});
} else {
return fs.statSync(f)[k]();
}
}
}
});

View file

@ -1,74 +0,0 @@
// batteries
// Copyright 2010 - 2011 Sami Samhuri <sami@samhuri.net>
var fs = require('fs')
, util = require('util')
, EventEmitter = require('events').EventEmitter
, batteries = require('../')
;
module.exports = FileFollower;
// TODO: option to act like tail and only show the last N lines, N >= 0
function FileFollower(file, options) {
options = options || {};
var self = this;
this.file = file;
this.currSize = fs.statSync(file).size;
this.prevSize = this.currSize;
this.interval = options.interval || 1000;
batteries.fs.eachLine(file,
{ line: function(line) {
self.emit('line', line);
}
, end: function() {
self.startFollowing();
}
});
}
util.inherits(FileFollower, EventEmitter);
FileFollower.prototype.startFollowing = function() {
if (this._interval) {
console.warn('already following');
return;
}
this.buffer = '';
this.fd = fs.openSync(this.file, 'r');
this._interval = setInterval(this.checkForLine.bind(this), this.interval);
};
FileFollower.prototype.stopFollowing = function() {
if (!this._interval) {
console.warn('not following');
return;
}
delete this.buffer;
clearInterval(this._interval);
delete this._interval;
fs.closeSync(this.fd);
delete this.fd;
};
FileFollower.prototype.checkForLine = function() {
this.currSize = fs.statSync(this.file).size;
if (this.currSize > this.prevSize) {
var n = this.currSize - this.prevSize
, buf = new Buffer(n + 1)
, self = this
;
fs.read(this.fd, buf, 0, n, this.prevSize, function(err, bytesRead, buffer) {
if (err) {
self.emit('error', err);
return;
}
self.buffer += buf.slice(0, bytesRead);
self.prevSize += bytesRead;
var i;
while ((i = self.buffer.indexOf('\n')) !== -1) {
self.emit('line', self.buffer.slice(0, i));
self.buffer = self.buffer.slice(i + 1);
}
});
}
};

View file

@ -1,111 +0,0 @@
// batteries
// Copyright 2010 - 2011 Sami Samhuri <sami@samhuri.net>
var fs = require('fs')
, batteries = require('../')
, FileFollower = require('./file-follower')
, LineEmitter = require('./line-emitter')
, constants = require('constants')
, FileExt
;
FileExt =
{ eachLine: eachLine
, exists: exists
, follow: follow
, grep: grep
, home: home
, readLines: readLines
}
// isDirectory, isFile, isSymbolicLink, etc.
var s = fs.statSync(__dirname);
Object.keys(Object.getPrototypeOf(s)).forEach(function(k) {
if (k.match(/^is/) && typeof s[k] === 'function') {
FileExt[k] = function(f, cb) {
if (cb) {
fs.stat(f, function(err, stat) {
cb(err, err ? null : stat[k]());
});
} else {
return fs.statSync(f)[k]();
}
}
}
});
exports.extendNative = function() {
batteries.object.extend(fs, FileExt);
};
batteries.object.extend(exports, FileExt);
function eachLine(f, optionsOrLineFn, endFn) {
var lineFn, hasLineFn, hasEndFn;
if (typeof optionsOrLineFn === 'object') {
lineFn = optionsOrLineFn.line;
endFn = optionsOrLineFn.end;
}
else if (typeof optionsOrLineFn === 'function') {
lineFn = optionsOrLineFn;
}
hasLineFn = typeof lineFn == 'function';
hasEndFn = typeof endFn == 'function';
if (!hasLineFn && !hasEndFn) throw new Error('bad arguments');
var le = new LineEmitter(f);
if (hasLineFn) le.on('line', lineFn);
if (hasEndFn) le.on('end', endFn);
}
function exists(f) {
try {
fs.statSync(f);
return true;
} catch (e) {
if (e.errno === constants.ENOENT) return false;
throw e;
}
}
function follow(f, lineFn) {
var ff = new FileFollower(f);
ff.on('line', lineFn);
return {
stop: ff.stopFollowing.bind(ff)
};
}
function grep(regex, f, callback) {
if (!callback) throw new Error('grep requires a callback');
var results = [];
eachLine(f,
{ line: function(line) { if (line.match(regex)) results.push(line); }
, end: function() { callback(results); }
});
}
function home(user, callback) {
// user is optional so the first param may be a callback
if (typeof user === 'function') {
callback = user;
user = null;
}
if (user && callback && user !== process.env['USER']) {
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'];
}
function readLines(f, cb) {
var lines = [];
eachLine(f, { line: function(line) { lines.push(line); }
, end: function() { cb(lines); }
});
}

View file

@ -1,49 +0,0 @@
// batteries
// Copyright 2010 - 2011 Sami Samhuri <sami@samhuri.net>
var fs = require('fs')
, util = require('util')
, EventEmitter = require('events').EventEmitter
;
module.exports = LineEmitter;
function LineEmitter(fileOrStream) {
var self = this
, stream = typeof fileOrStream === 'string' ? fs.createReadStream(fileOrStream) : fileOrStream
;
this.buffer = '';
this.ended = false;
this.endEmitted = false;
stream.on('data', function(chunk) {
self.buffer += chunk;
self.checkForLine();
});
stream.on('end', function() {
self.ended = true;
self.checkForLine();
});
stream.on('error', function(err) {
self.ended = true;
self.emit('error', err);
});
}
util.inherits(LineEmitter, EventEmitter);
LineEmitter.prototype.checkForLine = function() {
var i = this.buffer.indexOf('\n')
, self = this
;
if (i === -1) {
if (this.ended && !this.endEmitted) {
if (this.buffer.length > 0) self.emit('line', this.buffer);
this.buffer = '';
this.endEmitted = true;
self.emit('end');
}
return;
}
this.emit('line', this.buffer.slice(0, i));
this.buffer = this.buffer.slice(i + 1);
process.nextTick(function() { self.checkForLine(); });
};

48
lib/fuse.js Normal file
View file

@ -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;
});
};
}

View file

@ -1,63 +1,51 @@
// batteries var ArrayExt = require('./array-ext')
// Copyright 2010 - 2011 Sami Samhuri <sami@samhuri.net> , DateExt = require('./date-ext')
, FileExt = require('./file-ext')
var fs = require('fs') , MathExt = require('./math-ext')
, batteries = module.exports , ObjectExt = require('./object-ext')
, exts = [] , StringExt = require('./string-ext')
, Range = require('./range')
, repr = require('./repr').repr
, fuse = require('./fuse')
; ;
fs.readdirSync(__dirname).forEach(function (file) { module.exports = { ArrayExt: ArrayExt.ArrayExt
file = file.replace('.js', ''); , DateExt: DateExt.DateExt
if (file !== 'index') { , FileExt: FileExt.FileExt
exts.push(file); , MathExt: MathExt.MathExt
defineLazyProperty(batteries, file, function() { return require('./' + file); }); , ObjectExt: ObjectExt.ObjectExt
} , StringExt: StringExt.StringExt
}); , extendNative: function() {
// Extend native types
ArrayExt.extendNative();
DateExt.extendNative();
MathExt.extendNative();
fuse.extendArray();
function defineLazyProperty(obj, name, getter) { // Extend Node
Object.defineProperty(obj, name, { FileExt.extend(require('fs'));
configurable: true
, enumerable: true
// Call the getter and overwrite this property with one that returns global['Range'] = Range;
// that value directly. global['repr'] = repr;
, get: function() {
var val = getter();
Object.defineProperty(batteries, name, { value: val });
return val;
}
}); 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');
util = require('util');
url = require('url');
// ES5 strict mode compatible return module.exports;
function ensureContext(context) { }
return context || this || (1, eval)('this'); };
}
// TODO figure out how to extend native types in the REPL
batteries.extendNative = function(context) {
context = ensureContext(context)
exts.forEach(function(name) {
var ext = batteries[name];
if (typeof ext.extendNative === 'function') {
ext.extendNative(context);
}
});
return batteries;
};
var NodeModules = ( 'assert buffer child_process crypto dgram dns events freelist'
+ ' fs http https net os path querystring readline repl'
+ ' string_decoder util url'
).split(' ');
batteries.requireEverything = function(context) {
context = ensureContext(context);
NodeModules.forEach(function(name) {
defineLazyProperty(context, name, function() { return require(name); });
});
return batteries;
};

45
lib/line-emitter.js Normal file
View file

@ -0,0 +1,45 @@
var fs = require('fs')
, util = require('util')
, EventEmitter = require('events').EventEmitter
;
exports.LineEmitter = LineEmitter;
function LineEmitter(fileOrStream) {
var self = this
, stream = typeof fileOrStream === 'string' ? fs.createReadStream(fileOrStream) : fileOrStream
;
this.buffer = '';
this.ended = false;
this.endEmitted = false;
stream.on('data', function(chunk) {
self.buffer += chunk;
self.checkForLine();
});
stream.on('end', function() {
self.ended = true;
self.checkForLine();
});
stream.on('error', function(err) {
self.ended = true;
self.emit('error', err);
});
}
util.inherits(LineEmitter, EventEmitter);
LineEmitter.prototype.checkForLine = function() {
var i = this.buffer.indexOf('\n');
, self = this
;
if (i === -1) {
if (this.ended && !this.endEmitted) {
if (this.buffer.length > 0) self.emit('line', this.buffer);
this.buffer = '';
this.endEmitted = true;
self.emit('end');
}
return;
}
this.emit('line', this.buffer.slice(0, i));
this.buffer = this.buffer.slice(i + 1);
process.nextTick(function() { self.checkForLine(); });
};

20
lib/math-ext.js Normal file
View file

@ -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;
}

View file

@ -1,29 +0,0 @@
// batteries
// Copyright 2010 - 2011 Sami Samhuri <sami@samhuri.net>
var MathExt =
{ avg: avg
, sum: sum
};
var batteries = require('./');
batteries.object.extend(module.exports, MathExt);
exports.extendNative = function() {
batteries.object.extend(Math, MathExt);
};
function sum() {
var nums = Array.isArray(arguments[0]) ? arguments[0] : arguments
, i = nums.length
, sum = 0
;
while (i--) sum += arguments[i];
return sum;
}
function avg() {
var nums = Array.isArray(arguments[0]) ? arguments[0] : arguments;
return sum.call(this, nums) / nums.length;
}

10
lib/object-ext.js Normal file
View file

@ -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');
}
};

View file

@ -1,49 +0,0 @@
// batteries
// Copyright 2010 - 2011 Sami Samhuri <sami@samhuri.net>
var ObjectExt =
{ cmp: cmp
, extend: extend
, extendPrototype: extendPrototype
}
exports.extendNative = function() {
extendPrototype(Object, ObjectExt);
};
extend(module.exports, ObjectExt);
function cmp(a, b) {
if (a > b) return 1;
if (a < b) return -1;
if (a === b) return 0;
throw new Error('cannot effectively compare values');
}
function extend(a, b) {
Object.getOwnPropertyNames(b).forEach(function(k) {
a[k] = b[k];
});
}
function extendPrototype(obj, ext) {
Object.keys(ext).forEach(function(k) {
// TODO remove this and just warn? ... no good solution for conflicts, needs a human
if (k in obj.prototype) return; // don't overwrite existing members
var val = ext[k];
if (typeof val === 'function') {
val = methodWrapper(val);
}
Object.defineProperty(obj.prototype, k, { value: val });
});
}
// Make a wrapper than passes `this` as the first argument, Python style. All
// extension functions that can extend native types must follow this convention.
function methodWrapper(fn) {
return function() {
var args = [].slice.call(arguments);
args.unshift(this);
return fn.apply(this, args);
};
}

View file

@ -1,30 +1,22 @@
// batteries
// Copyright 2010 - 2011 Sami Samhuri <sami@samhuri.net>
exports.Range = Range; exports.Range = Range;
exports.extendNative = function(context) {
context.Range = Range;
};
function Range(start, length) { function Range(start, length) {
this.start = start; this.start = start;
this.length = length; this.length = length;
}; };
Range.prototype.inRange = function(val) { Range.prototype.inRange = function(val) {
if (this.test) return this.test(val); if (this.test) return this.test(val);
return val >= this.start && val <= this.start + this.length; return val >= this.start && val <= this.start + this.length;
}; };
Range.prototype.toArray = function(nth) { Range.prototype.toArray = function(nth) {
var a = [] var a = []
, i = this.length , i = this.length
; ;
nth = nth || this.nth; nth = nth || this.nth;
if (nth) if (nth)
while (i--) a[i] = nth(i); while (i--) a[i] = nth(i);
else else
while (i--) a[i] = this.start + i; while (i--) a[i] = this.start + i;
return a; return a;
}; };

View file

@ -1,50 +1,30 @@
// batteries
// Copyright 2010 - 2011 Sami Samhuri <sami@samhuri.net>
// readable string representations of values // readable string representations of values
exports.repr = repr; exports.repr = function(x) {
if (x !== null && x !== undefined && typeof x.repr === 'function') return x.repr();
exports.extendNative = function(context) { if (x === null || x === undefined ||
context.repr = repr; 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);
}; };
function repr(x) {
if (x !== null && x !== undefined && typeof x.repr === 'function') return x.repr();
var nativeToStringIsReadable =
x === null
|| x === undefined
|| x instanceof Number
|| typeof x === 'number'
|| x instanceof Boolean
|| typeof x === 'boolean'
|| x instanceof RegExp
|| x.constructor === RegExp;
if (nativeToStringIsReadable) {
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);
}

View file

@ -1,71 +0,0 @@
// batteries
// Copyright 2010 - 2011 Sami Samhuri <sami@samhuri.net>
exports.Set = Set;
exports.extendNative = function(context) {
context.Set = Set;
};
var ownProps = Object.getOwnPropertyNames;
function Set(items) {
if (!Array.isArray(items)) items = [].slice.call(arguments);
var n = 0;
this.members = items.reduce(function(set, x) {
if (!(x in set)) {
n += 1;
set[x] = x;
}
return set;
}, Object.create(null));
this.size = n;
}
Set.prototype.add = function(item) {
if (!(item in this.members)) {
this.members[item] = item;
this.size += 1;
}
};
Set.prototype.clear = function() {
this.members = Object.create(null);
this.size = 0;
};
Set.prototype.contains = function(item) {
return item in this.members;
}
Set.prototype.diff = function(other) {
var d = []
, x
;
for (x in this.members) if (!(x in other.members)) d.push(this.members[x]);
return new Set(d);
};
Set.prototype.isEmpty = function() {
return this.size === 0;
};
Set.prototype.remove = function(item) {
if (item in this.members) {
delete this.members[item];
this.size -= 1;
}
};
Set.prototype.toArray = function() {
var ms = this.members;
return ownProps(this.members).map(function(k) { return ms[k]; });
};
Set.prototype.union = function(other) {
var ms = this.members
, u = ownProps(this.members).map(function(k) { return ms[k]; });
ms = other.members;
u = u.concat(ownProps(ms).map(function(k) { return ms[k]; }));
return new Set(u);
};

11
lib/string-ext.js Normal file
View file

@ -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
};

View file

@ -1,23 +0,0 @@
// batteries
// Copyright 2010 - 2011 Sami Samhuri <sami@samhuri.net>
// TODO
// - reverse
// - unpack
// - sha1
var batteries = require('./')
, StringExt = { cmp: cmp }
;
exports.extendNative = function() {
batteries.object.extendPrototype(String, StringExt);
};
batteries.object.extend(exports, StringExt);
function cmp(a, b) {
if (a > b) return 1;
if (a < b) return -1;
return 0;
}

View file

@ -1,32 +1,23 @@
{ { "name" : "batteries"
"name": "batteries", , "description" : "A general purpose library for Node"
"description": "A general purpose library for Node", , "version" : "0.1.0"
"version": "0.4.2", , "homepage" : "http://samhuri.net/proj/batteries"
"homepage": "http://samhuri.net/proj/batteries", , "author" : "Sami Samhuri <sami@samhuri.net>"
"author": "Sami Samhuri <sami@samhuri.net>", , "repository" :
"repository": { { "type" : "git"
"type": "git", , "url" : "http://github.com/samsonjs/batteries.git"
"url": "git://github.com/samsonjs/batteries.git" }
}, , "bugs" :
"bugs": { { "mail" : "sami.samhuri+batteries@gmail.com"
"email": "sami@samhuri.net", , "web" : "http://github.com/samsonjs/batteries/issues"
"url": "https://github.com/samsonjs/batteries/issues" }
}, , "directories" : { "lib" : "./lib" }
"bin": { , "bin" : { "node-batteries" : "./repl.js" }
"node-batteries": "./repl.js" , "main" : "./lib/index"
}, , "engines" : { "node" : ">=0.4.0" }
"main": "./lib/index", , "licenses" :
"engines": { [ { "type" : "MIT"
"node": ">=0.4.x" , "url" : "http://github.com/samsonjs/batteries/raw/master/LICENSE"
},
"dependencies": {
"strftime": "0.4.x"
},
"licenses": [
{
"type": "MIT",
"url": "https://github.com/samsonjs/batteries/raw/master/LICENSE"
} }
], ]
"devDependencies": {}
} }

14
repl.js
View file

@ -1,14 +1,4 @@
#!/usr/bin/env node #!/usr/bin/env node
var batteries = require('./lib'); require('./lib/index').requireEverything().extendNative()
require('repl').start()
batteries
.requireEverything()
.extendNative();
try {
require('repl-edit').startRepl();
}
catch (e) {
require('repl').start();
}

View file

@ -1,10 +1,10 @@
var fs = require('fs') var fs = require('fs')
, spawn = require('child_process').spawn , spawn = require('child_process').spawn
, LineEmitter = require('./lib/fs/line-emitter') , LineEmitter = require('./lib/line-emitter').LineEmitter
; ;
require('./lib').extendNative(); require('./lib/index').extendNative();
// wc -l, for reference // wc -l, for reference