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
of this software and associated documentation files (the "Software"), to
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')
, DateExt = require('./date-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')
// batteries
// Copyright 2010 - 2011 Sami Samhuri <sami@samhuri.net>
var fs = require('fs')
, batteries = module.exports
, exts = []
;
module.exports = { ArrayExt: ArrayExt.ArrayExt
, DateExt: DateExt.DateExt
, FileExt: FileExt.FileExt
, MathExt: MathExt.MathExt
, ObjectExt: ObjectExt.ObjectExt
, StringExt: StringExt.StringExt
, extendNative: function() {
// Extend native types
ArrayExt.extendNative();
DateExt.extendNative();
MathExt.extendNative();
fuse.extendArray();
fs.readdirSync(__dirname).forEach(function (file) {
file = file.replace('.js', '');
if (file !== 'index') {
exts.push(file);
defineLazyProperty(batteries, file, function() { return require('./' + file); });
}
});
// 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');
util = require('util');
url = require('url');
return module.exports;
}
};
function defineLazyProperty(obj, name, getter) {
Object.defineProperty(obj, name, {
configurable: true
, enumerable: true
// Call the getter and overwrite this property with one that returns
// that value directly.
, get: function() {
var val = getter();
Object.defineProperty(batteries, name, { value: val });
return val;
}
});
}
// ES5 strict mode compatible
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;
};

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.extendNative = function(context) {
context.Range = Range;
};
function Range(start, length) {
this.start = start;
this.length = 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;
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;
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;
};

View file

@ -1,30 +1,50 @@
// batteries
// Copyright 2010 - 2011 Sami Samhuri <sami@samhuri.net>
// readable string representations of values
exports.repr = function(x) {
if (x !== null && x !== undefined && typeof x.repr === 'function') return x.repr();
exports.repr = 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);
exports.extendNative = function(context) {
context.repr = repr;
};
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"
, "version" : "0.1.0"
, "homepage" : "http://samhuri.net/proj/batteries"
, "author" : "Sami Samhuri <sami@samhuri.net>"
, "repository" :
{ "type" : "git"
, "url" : "http://github.com/samsonjs/batteries.git"
}
, "bugs" :
{ "mail" : "sami.samhuri+batteries@gmail.com"
, "web" : "http://github.com/samsonjs/batteries/issues"
}
, "directories" : { "lib" : "./lib" }
, "bin" : { "node-batteries" : "./repl.js" }
, "main" : "./lib/index"
, "engines" : { "node" : ">=0.4.0" }
, "licenses" :
[ { "type" : "MIT"
, "url" : "http://github.com/samsonjs/batteries/raw/master/LICENSE"
{
"name": "batteries",
"description": "A general purpose library for Node",
"version": "0.4.2",
"homepage": "http://samhuri.net/proj/batteries",
"author": "Sami Samhuri <sami@samhuri.net>",
"repository": {
"type": "git",
"url": "git://github.com/samsonjs/batteries.git"
},
"bugs": {
"email": "sami@samhuri.net",
"url": "https://github.com/samsonjs/batteries/issues"
},
"bin": {
"node-batteries": "./repl.js"
},
"main": "./lib/index",
"engines": {
"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
require('./lib/index').requireEverything().extendNative()
require('repl').start()
var batteries = require('./lib');
batteries
.requireEverything()
.extendNative();
try {
require('repl-edit').startRepl();
}
catch (e) {
require('repl').start();
}

View file

@ -1,10 +1,10 @@
var fs = require('fs')
, 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