Compare commits

...

45 commits

Author SHA1 Message Date
Sami Samhuri
48ca583338 0.4.2 2011-11-05 19:00:43 -07:00
Sami Samhuri
77ca57ea69 fix required node version 2011-11-05 19:00:35 -07:00
Sami Samhuri
03fc1bdad9 v0.4.1 2011-11-05 16:14:37 -07:00
Sami Samhuri
ad63146736 node v0.6 2011-11-05 16:14:37 -07:00
Sami Samhuri
dc63991bb2 fix initialization of extensions 2011-11-05 16:14:37 -07:00
Sami Samhuri
483210b91f fix syntax error in set.js 2011-11-05 16:14:37 -07:00
Sami Samhuri
8b5841587f accept a context in extendNative() methods 2011-11-05 16:08:00 -07:00
Sami Samhuri
59e6566d51 make flatten work on objects and arrays 2011-11-05 16:02:36 -07:00
Sami Samhuri
92912c0678 make set compatible with non-strings, still very slow 2011-06-05 22:00:43 -07:00
Sami Samhuri
d2b88f5c54 new Set type 2011-06-05 21:54:50 -07:00
Sami Samhuri
f94cf785d0 ext does not need to be a function any more 2011-06-05 21:45:09 -07:00
Sami Samhuri
b9667705d8 drop and go extensions, no need to update index.js 2011-06-05 21:44:29 -07:00
Sami Samhuri
1ffc31ea90 minor style changes 2011-06-05 21:44:05 -07:00
Sami Samhuri
eb3f23a3e5 remove half-baked and experimental fuse stuff 2011-06-05 21:43:23 -07:00
Sami Samhuri
435310ef38 export functions in math ext (whoops) 2011-06-05 21:42:31 -07:00
Sami Samhuri
bad39e3050 DRY up requireEverything, make it lazy 2011-06-05 21:18:02 -07:00
Sami Samhuri
1d6f12c559 fix batteries path in file follower 2011-06-05 21:16:49 -07:00
Sami Samhuri
d73c2ca537 update test.js (probably broken, need real tests) 2011-06-05 18:13:17 -07:00
Sami Samhuri
094edf4176 update repl.js 2011-06-05 18:13:00 -07:00
Sami Samhuri
8902873175 accept a context to extend in requireEverything() 2011-06-05 18:12:49 -07:00
Sami Samhuri
6a12ca076a load extensions lazily thanks to @indexzero, closes #2 2011-06-05 18:12:27 -07:00
Sami Samhuri
b7ba7b5dc0 fix style in package.json 2011-06-05 18:12:01 -07:00
Sami Samhuri
7ec9d3f296 add extendNative() to repr extension 2011-06-05 18:11:34 -07:00
Sami Samhuri
930225c297 fix style of range extension, add extendNative() 2011-06-05 18:11:20 -07:00
Sami Samhuri
2dbe210816 finish up object.extendPrototype refactoring 2011-06-05 18:10:56 -07:00
Sami Samhuri
22a5e5854b minor changes to cmp functions, more consistent now 2011-06-05 18:09:51 -07:00
Sami Samhuri
f956cefaab use strftime module instead of duplicating the code 2011-06-05 18:09:25 -07:00
Sami Samhuri
8e2ca7fec3 move fs extensions into a subdir 2011-06-05 18:06:27 -07:00
Sami Samhuri
2b7db269a3 move prototype extender into object module [broken, refactoring] 2011-06-05 18:06:04 -07:00
Sami Samhuri
0bae38cdb5 export better names 2011-06-04 22:01:46 -07:00
Sami Samhuri
4428cbc09c version 0.3.1 2011-05-30 01:12:59 -07:00
Sami Samhuri
1652d2a782 add FileFollower for tail -f functionality 2011-05-30 01:12:31 -07:00
Sami Samhuri
5295fe8f20 export LineEmitter directly, change to 2 space indenting 2011-05-30 00:53:41 -07:00
Sami Samhuri
6ba8e98eed bump version 2011-05-30 00:06:17 -07:00
Sami Samhuri
144ff713e6 update file-ext to new format 2011-05-30 00:06:17 -07:00
Sami Samhuri
6ca19ad0cd update repr 2011-05-30 00:06:17 -07:00
Sami Samhuri
f03c8084d7 update index to match reality
- update index to match new extension structure
- remove fuse, too experimental
- export Range and repr
2011-05-30 00:06:17 -07:00
Sami Samhuri
d16f7e2b49 update license and add copyright to all files 2011-05-30 00:06:16 -07:00
Sami Samhuri
d177c83f5e extend natives with non-enumerable properties, other clean up 2011-05-30 00:06:16 -07:00
Sami Samhuri
154dead245 bump version 2011-05-14 16:34:36 -07:00
Sami Samhuri
7f0cb97c10 fix array extensions 2011-05-14 16:34:24 -07:00
Sami Samhuri
3ecc25e518 fix a syntax error 2011-05-14 15:28:06 -07:00
Sami Samhuri
34a5d9455e nuke node <0.4 constants hack 2011-05-13 21:29:19 -07:00
Sami Samhuri
0fcb346d24 bump version 2011-05-13 21:27:08 -07:00
Sami Samhuri
2f825bf1fd clean up eachLine 2011-05-13 21:26:18 -07:00
26 changed files with 733 additions and 704 deletions

View file

@ -1,4 +1,4 @@
Copyright 2010 Sami Samhuri. All rights reserved. Copyright 2010 - 2011 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

View file

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

View file

@ -1,126 +0,0 @@
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]
})
}

136
lib/array.js Normal file
View file

@ -0,0 +1,136 @@
// 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]
})
}

View file

@ -1,98 +0,0 @@
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;

17
lib/date.js Normal file
View file

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

View file

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

View file

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

74
lib/fs/file-follower.js Normal file
View file

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

111
lib/fs/index.js Normal file
View file

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

49
lib/fs/line-emitter.js Normal file
View file

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

View file

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

View file

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

View file

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

29
lib/math.js Normal file
View file

@ -0,0 +1,29 @@
// 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;
}

View file

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

49
lib/object.js Normal file
View file

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

71
lib/set.js Normal file
View file

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

View file

@ -1,11 +0,0 @@
var StringExt = exports.StringExt = {
cmp: function(a, b) {
if (a === b) return 0;
if (a < b) return -1;
return 1; // a > b
}
// unpack
};

23
lib/string.js Normal file
View file

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

14
repl.js
View file

@ -1,4 +1,14 @@
#!/usr/bin/env node #!/usr/bin/env node
require('./lib/index').requireEverything().extendNative() var batteries = require('./lib');
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/line-emitter').LineEmitter , LineEmitter = require('./lib/fs/line-emitter')
; ;
require('./lib/index').extendNative(); require('./lib').extendNative();
// wc -l, for reference // wc -l, for reference