Add helpers to replace some lodash functionality

Not a 1:1, and probably much slower than Lodash's ultra-optimised
implementations, but we do enough I/O that it's not really relevant,
is it?
This commit is contained in:
Andrew Stewart 2015-02-20 18:49:31 -08:00
parent 618aa8212d
commit 6e3d7c6667
11 changed files with 457 additions and 233 deletions

View File

@ -9,7 +9,8 @@
"use strict";
var Basestar = require("./basestar"),
Utils = require("./utils");
Utils = require("./utils"),
_ = require("./utils/helpers");
// Public: Creates a new Adaptor
//
@ -33,14 +34,11 @@ var Adaptor = module.exports = function Adaptor(opts) {
// misc. details provided in args hash
this.details = {};
for (var name in opts) {
var opt = opts[name],
banned = ["robot", "name", "adaptor", "events"];
if (!~banned.indexOf(name)) {
_.each(opts, function(opt, name) {
if (!~["robot", "name", "adaptor", "events"].indexOf(name)) {
this.details[name] = opt;
}
}
}, this);
};
Utils.subclass(Adaptor, Basestar);

View File

@ -10,7 +10,8 @@
var EventEmitter = require("events").EventEmitter;
var Utils = require("./utils");
var Utils = require("./utils"),
_ = require("./utils/helpers");
// Basestar is a base class to be used when writing external Cylon adaptors and
// drivers. It provides some useful base methods and functionality
@ -83,7 +84,7 @@ Basestar.prototype.defineDriverEvent = function(opts) {
};
Basestar.prototype._proxyEvents = function(opts, source, target) {
opts = (typeof opts === "string") ? { eventName: opts } : opts;
opts = _.isString(opts) ? { eventName: opts } : opts;
opts.source = source;
opts.target = target;

View File

@ -13,7 +13,8 @@ var Async = require("async");
var Logger = require("./logger"),
Robot = require("./robot"),
Config = require("./config"),
Utils = require("./utils");
Utils = require("./utils"),
_ = require("./utils/helpers");
var EventEmitter = require("events").EventEmitter;
@ -73,19 +74,15 @@ Cylon.robot = function robot(opts) {
//
// Returns nothing
Cylon.api = function api(Server, opts) {
var isObject = function(arg) { return typeof arg === "object"; },
isFunction = function(arg) { return typeof arg === "function"; },
isString = function(arg) { return typeof arg === "string"; };
// if only passed options (or nothing), assume HTTP server
if (Server == null || isObject(Server) && !isFunction(Server)) {
if (Server == null || _.isObject(Server) && !_.isFunction(Server)) {
opts = Server;
Server = "http";
}
opts = opts || {};
if (isString(Server)) {
if (_.isString(Server)) {
var req = "cylon-api-" + Server;
try {
@ -106,9 +103,7 @@ Cylon.api = function api(Server, opts) {
];
}
messages.forEach(function(str) {
Logger.error(str);
});
_.each(messages, _.arity(Logger.error, 1));
return;
} else {
throw e;
@ -126,22 +121,13 @@ Cylon.api = function api(Server, opts) {
//
// Returns nothing
Cylon.start = function start() {
var starters = [],
name;
for (name in this.robots) {
var bot = this.robots[name];
starters.push(bot.start.bind(bot));
}
var starters = _.pluck(this.robots, "start");
Async.parallel(starters, function() {
var mode = Utils.fetch(Config, "workMode", "async");
if (mode === "sync") {
for (name in this.robots) {
var bot = this.robots[name];
bot.startWork.call(bot);
}
_.invoke(this.robots, "startWork");
}
}.bind(this));
};
@ -153,11 +139,11 @@ Cylon.start = function start() {
// Returns the current config
Cylon.config = function(opts) {
var loggingChanged = (
opts.logging && Config.logging !== Utils.merge(Config.logging, opts.logging)
opts.logging && Config.logging !== _.extend(Config.logging, opts.logging)
);
if (typeof opts === "object" && !Array.isArray(opts)) {
Config = Utils.merge(Config, opts);
if (_.isObject(opts)) {
Config = _.extend(Config, opts);
}
if (loggingChanged) {
@ -175,12 +161,7 @@ Cylon.config = function(opts) {
Cylon.halt = function halt(callback) {
callback = callback || function() {};
var fns = [];
for (var name in this.robots) {
var bot = this.robots[name];
fns.push(bot.halt.bind(bot));
}
var fns = _.pluck(this.robots, "halt");
// if robots can"t shut down quickly enough, forcefully self-terminate
var timeout = Config.haltTimeout || 3000;
@ -190,15 +171,8 @@ Cylon.halt = function halt(callback) {
};
Cylon.toJSON = function() {
var robots = [];
for (var name in this.robots) {
var bot = this.robots[name];
robots.push(bot.toJSON.call(bot));
}
return {
robots: robots,
robots: _.invoke(this.robots, "toJSON"),
commands: Object.keys(this.commands),
events: this.events
};

View File

@ -9,7 +9,8 @@
"use strict";
var Basestar = require("./basestar"),
Utils = require("./utils");
Utils = require("./utils"),
_ = require("./utils/helpers");
// Public: Creates a new Driver
//
@ -35,14 +36,13 @@ var Driver = module.exports = function Driver(opts) {
this.details = {};
for (var name in opts) {
var opt = opts[name],
banned = ["robot", "name", "connection", "driver", "events"];
_.each(opts, function(opt, name) {
var banned = ["robot", "name", "connection", "driver", "events"];
if (!~banned.indexOf(name)) {
this.details[name] = opt;
}
}
}, this);
};
Utils.subclass(Driver, Basestar);

View File

@ -9,14 +9,15 @@
"use strict";
var Registry = require("./registry"),
Config = require("./config");
Config = require("./config"),
_ = require("./utils/helpers");
function testMode() {
return process.env.NODE_ENV === "test" && Config.testMode;
}
module.exports = function Initializer(type, opts) {
var mod, name, prop;
var mod;
mod = Registry.findBy(type, opts[type]);
@ -32,28 +33,24 @@ module.exports = function Initializer(type, opts) {
var obj = mod[type](opts);
for (name in obj) {
prop = obj[name];
_.each(obj, function(prop, name) {
if (name === "constructor") {
continue;
return;
}
if (typeof prop === "function") {
if (_.isFunction(prop)) {
obj[name] = prop.bind(obj);
}
}
});
if (testMode()) {
var test = Registry.findBy(type, "test")[type](opts);
for (name in obj) {
prop = obj[name];
if (typeof prop === "function" && !test[name]) {
_.each(obj, function(prop, name) {
if (_.isFunction(prop) && !test[name]) {
test[name] = function() { return true; };
}
}
});
return test;
}

View File

@ -13,12 +13,12 @@ var levels = ["debug", "info", "warn", "error", "fatal"];
var BasicLogger = require("./logger/basic_logger"),
NullLogger = require("./logger/null_logger"),
Config = require("./config"),
Utils = require("./utils");
_ = require("./utils/helpers");
var Logger = module.exports = {
setup: function(opts) {
if (typeof opts === "object") {
Config.logging = Utils.merge(Config.logging, opts);
if (_.isObject(opts)) {
Config.logging = _.extend(Config.logging, opts);
}
var logger = Config.logging.logger,

View File

@ -11,7 +11,8 @@
var initializer = require("./initializer"),
Logger = require("./logger"),
Utils = require("./utils"),
Config = require("./config");
Config = require("./config"),
_ = require("./utils/helpers");
var Async = require("async"),
EventEmitter = require("events").EventEmitter;
@ -48,30 +49,28 @@ var Robot = module.exports = function Robot(opts) {
this.initConnections(opts);
this.initDevices(opts);
for (var name in opts) {
var opt = opts[name];
_.each(opts, function(opt, name) {
if (this[name] !== undefined) {
continue;
return;
}
this[name] = opt;
if (opts.commands == null && typeof opt === "function") {
if (opts.commands == null && _.isFunction(opt)) {
this.commands[name] = opt;
}
}
}, this);
if (opts.commands) {
var cmds;
if (typeof opts.commands === "function") {
if (_.isFunction(opts.commands)) {
cmds = opts.commands.call(this);
} else {
cmds = opts.commands;
}
if (typeof cmds === "object" && !Array.isArray(cmds)) {
if (_.isObject(cmds)) {
this.commands = cmds;
} else {
var err = "#commands must be an object ";
@ -101,26 +100,12 @@ Robot.randomName = function() {
//
// Returns an Object containing Robot data
Robot.prototype.toJSON = function() {
var connections = [],
devices = [],
n;
for (n in this.connections) {
var conn = this.connections[n];
connections.push(conn.toJSON.call(conn));
}
for (n in this.devices) {
var device = this.devices[n];
devices.push(device.toJSON.call(device));
}
return {
name: this.name,
connections: connections,
devices: devices,
connections: _.invoke(this.connections, "toJSON"),
devices: _.invoke(this.devices, "toJSON"),
commands: Object.keys(this.commands),
events: Array.isArray(this.events) ? this.events : []
events: _.isArray(this.events) ? this.events : []
};
};
@ -182,30 +167,28 @@ Robot.prototype.initConnections = function(opts) {
return this.connections;
}
if (typeof opts.connections === "object") {
if (Array.isArray(opts.connections)) {
if (_.isObjectLoose(opts.connections)) {
if (_.isArray(opts.connections)) {
this.performArraySetup(opts.connections, "connection", "connections");
return this.connections;
}
for (var key in opts.connections) {
var conn = opts.connections[key],
name = typeof key === "string" ? key : conn.name;
_.each(opts.connections, function(conn, key) {
var name = _.isString(key) ? key : conn.name;
if (conn.devices) {
opts.devices = opts.devices || {};
for (var d in conn.devices) {
var device = conn.devices[d];
_.each(conn.devices, function(device, d) {
device.connection = name;
opts.devices[d] = device;
}
});
delete conn.devices;
}
this.connection(name, conn);
}
}, this);
}
return this.connections;
@ -226,7 +209,7 @@ Robot.prototype.device = function(name, device) {
this.log("warn", str);
}
if (typeof device.connection === "string") {
if (_.isString(device.connection)) {
if (this.connections[device.connection] == null) {
str = "No connection found with the name " + device.connection + ".\n";
this.log("fatal", str);
@ -269,18 +252,16 @@ Robot.prototype.initDevices = function(opts) {
return this.devices;
}
if (typeof opts.devices === "object") {
if (Array.isArray(opts.devices)) {
if (_.isObjectLoose(opts.devices)) {
if (_.isArray(opts.devices)) {
this.performArraySetup(opts.devices, "device", "devices");
return this.devices;
}
for (var key in opts.devices) {
var device = opts.devices[key],
name = typeof key === "string" ? key : device.name;
_.each(opts.devices, function(device, key) {
var name = _.isString(key) ? key : device.name;
this.device(name, device);
}
}, this);
}
return this.devices;
@ -314,7 +295,7 @@ Robot.prototype.start = function(callback) {
this.log("fatal", err);
this.halt(function() {
if (typeof(this.error) === "function") {
if (_.isFunction(this.error)) {
this.error.call(this, err);
}
@ -324,7 +305,7 @@ Robot.prototype.start = function(callback) {
}.bind(this));
}
if (typeof callback === "function") {
if (_.isFunction(callback)) {
callback(err, results);
}
}.bind(this));
@ -353,11 +334,7 @@ Robot.prototype.startWork = function() {
Robot.prototype.startConnections = function(callback) {
this.log("info", "Starting connections.");
var starters = [];
var createStarter = function(name) {
var conn = this.connections[name];
var starters = _.map(this.connections, function(conn, name) {
this[name] = conn;
return function(cb) {
@ -372,11 +349,7 @@ Robot.prototype.startConnections = function(callback) {
this.log("debug", str + ".");
return conn.connect.call(conn, cb);
}.bind(this);
}.bind(this);
for (var name in this.connections) {
starters.push(createStarter(name));
}
}, this);
return Async.parallel(starters, callback);
};
@ -391,11 +364,7 @@ Robot.prototype.startDevices = function(callback) {
log("info", "Starting devices.");
var starters = [];
var createStarter = function(name) {
var device = this.devices[name];
var starters = _.map(this.devices, function(device, name) {
this[name] = device;
return function(cb) {
@ -408,12 +377,7 @@ Robot.prototype.startDevices = function(callback) {
log("debug", str + ".");
return device.start.call(device, cb);
};
}.bind(this);
for (var name in this.devices) {
starters.push(createStarter(name));
}
}, this);
return Async.parallel(starters, callback);
};
@ -432,19 +396,8 @@ Robot.prototype.halt = function(callback) {
return callback();
}
var devices = [],
connections = [],
n;
for (n in this.devices) {
var device = this.devices[n];
devices.push(device.halt.bind(device));
}
for (n in this.connections) {
var conn = this.connections[n];
connections.push(conn.disconnect.bind(conn));
}
var devices = _.pluck(this.devices, "halt"),
connections = _.pluck(this.connections, "disconnect");
try {
Async.parallel(devices, function() {
@ -481,7 +434,7 @@ Robot.prototype.performArraySetup = function(things, typeOfThing, arrayName) {
this.log("warn", str);
things.forEach(function(t, key) {
var name = typeof key === "string" ? key : t.name;
var name = _.isString(key) === "string" ? key : t.name;
this[typeOfThing](name, t);
}, this);
};

View File

@ -8,6 +8,8 @@
"use strict";
var _ = require("./utils/helpers");
var monkeyPatches = require("./utils/monkey-patches");
var Utils = module.exports = {
@ -106,13 +108,11 @@ var Utils = module.exports = {
},
proxyFunctions: function proxyFunctions(source, target) {
for (var key in source) {
var prop = source[key];
if (typeof prop === "function" && !target[key]) {
_.each(source, function(prop, key) {
if (_.isFunction(prop) && !target[key]) {
target[key] = prop.bind(source);
}
}
});
},
// Public: Proxies a list of methods from one object to another. It will not
@ -133,7 +133,7 @@ var Utils = module.exports = {
force = force || false;
methods.forEach(function(method) {
if (typeof base[method] === "function" && !force) {
if (_.isFunction(base[method]) && !force) {
return;
}
@ -187,7 +187,7 @@ var Utils = module.exports = {
throw new Error("key not found: \"" + property + "\"");
}
if (typeof(fallback) === "function") {
if (_.isFunction(fallback)) {
var value = fallback(property);
if (value === void 0) {
@ -200,51 +200,6 @@ var Utils = module.exports = {
return fallback;
},
// Public: Merges two arrays/objects together, recursively.
//
// base - what should be merged onto
// source - what should be merged into it
//
// Returns a merged object/array
merge: function(base, source) {
var isArray = Array.isArray(source);
if (base == null) {
base = isArray ? [] : {};
}
// merge objects
if (isArray) {
source.forEach(function(e, i) {
if (typeof base[i] === "undefined") {
base[i] = e;
} else if (typeof e === "object") {
base[i] = Utils.merge(base[i], e);
} else {
if (!~base.indexOf(e)) {
base.push(e);
}
}
});
} else {
var key;
for (key in source) {
if (typeof source[key] !== "object" || !source[key]) {
base[key] = source[key];
} else {
if (base[key]) {
Utils.merge(base[key], source[key]);
} else {
base[key] = source[key];
}
}
}
}
return base;
},
// Public: Given a name, and an array of existing names, returns a unique
// name.
//

156
lib/utils/helpers.js Normal file
View File

@ -0,0 +1,156 @@
// A collection of useful helper functions, used internally but not exported
// with the rest of Cylon.
"use strict";
var __slice = Array.prototype.slice;
var H = module.exports = {};
function identity(value) {
return value;
}
function extend(base, source) {
var isArray = Array.isArray(source);
if (base == null) {
base = isArray ? [] : {};
}
if (isArray) {
source.forEach(function(e, i) {
if (typeof base[i] === "undefined") {
base[i] = e;
} else if (typeof e === "object") {
base[i] = extend(base[i], e);
} else {
if (!~base.indexOf(e)) {
base.push(e);
}
}
});
} else {
var key;
for (key in source) {
if (typeof source[key] !== "object" || !source[key]) {
base[key] = source[key];
} else {
if (base[key]) {
extend(base[key], source[key]);
} else {
base[key] = source[key];
}
}
}
}
return base;
}
extend(H, {
identity: identity,
extend: extend
});
function kind(thing) {
return Object.prototype.toString.call(thing).slice(8, -1);
}
function isA(type) {
return function(thing) {
return kind(thing) === type;
};
}
extend(H, {
isObject: isA("Object"),
isObjectLoose: function(thing) { return typeof thing === "object"; },
isFunction: isA("Function"),
isArray: isA("Array"),
isString: isA("String"),
isNumber: isA("Number"),
isArguments: isA("Arguments"),
isUndefined: isA("Undefined")
});
function iterate(thing, fn, thisVal) {
if (H.isArray(thing)) {
thing.forEach(fn, thisVal);
return;
}
if (H.isObject(thing)) {
Object.keys(thing).forEach(function(key) {
var value = thing[key];
fn.call(thisVal, value, key);
}, thisVal);
}
return [];
}
function pluck(collection, key) {
var keys = [];
iterate(collection, function(object) {
if (H.isObject(object)) {
if (H.isFunction(object[key])) {
keys.push(object[key].bind(object));
} else {
keys.push(object[key]);
}
}
});
return keys;
}
function map(collection, fn, thisVal) {
var vals = [];
if (fn == null) {
fn = identity;
}
iterate(collection, function(object, index) {
vals.push(fn.call(thisVal, object, index));
});
return vals;
}
function invoke(collection, fn) {
var args = __slice.call(arguments, 2),
vals = [];
iterate(collection, function(object) {
if (H.isFunction(fn)) {
vals.push(fn.apply(object, args));
return;
}
vals.push(object[fn].apply(object, arguments));
});
return vals;
}
extend(H, {
pluck: pluck,
each: iterate,
map: map,
invoke: invoke
});
function arity(fn, n) {
return function() {
var args = __slice.call(arguments, 0, n);
return fn.apply(null, args);
};
}
extend(H, {
arity: arity
});

View File

@ -252,37 +252,4 @@ describe("Utils", function() {
});
});
});
describe("#merge", function() {
var merge = utils.merge;
var base = {
fruits: ["apple"],
vegetables: ["beet"],
thing: null,
otherThing: "hello!",
data: [{ "user": "barney" }, { "user": "fred" }]
};
var source = {
fruits: ["banana"],
vegetables: ["carrot"],
thing: "hello!",
otherThing: null,
data: [{ "age": 36 }, { "age": 40 }]
};
var expected = {
data: [ { age: 36, user: "barney" }, { age: 40, user: "fred" } ],
fruits: [ "apple", "banana" ],
vegetables: [ "beet", "carrot" ],
thing: "hello!",
otherThing: null
};
it("merges two objects", function() {
var merged = merge(base, source);
expect(merged).to.be.eql(expected);
});
});
});

View File

@ -0,0 +1,223 @@
"use strict";
var _ = source("utils/helpers");
describe("Helpers", function() {
describe("extend", function() {
var extend = _.extend;
var base = {
fruits: ["apple"],
vegetables: ["beet"],
thing: null,
otherThing: "hello!",
data: [{ "user": "barney" }, { "user": "fred" }]
};
var source = {
fruits: ["banana"],
vegetables: ["carrot"],
thing: "hello!",
otherThing: null,
data: [{ "age": 36 }, { "age": 40 }]
};
var expected = {
data: [ { age: 36, user: "barney" }, { age: 40, user: "fred" } ],
fruits: [ "apple", "banana" ],
vegetables: [ "beet", "carrot" ],
thing: "hello!",
otherThing: null
};
it("extends two objects", function() {
var extended = extend(base, source);
expect(extended).to.be.eql(expected);
});
});
describe("isObject", function() {
var fn =_.isObject;
it("checks if a value is an Object", function() {
var Klass = function() {},
instance = new Klass();
expect(fn({})).to.be.eql(true);
expect(fn(instance)).to.be.eql(true);
expect(fn([])).to.be.eql(false);
expect(fn(function() {})).to.be.eql(false);
expect(fn(10)).to.be.eql(false);
expect(fn("")).to.be.eql(false);
});
});
describe("isFunction", function() {
var fn =_.isFunction;
it("checks if a value is a Function", function() {
expect(fn(function() {})).to.be.eql(true);
expect(fn({})).to.be.eql(false);
expect(fn([])).to.be.eql(false);
expect(fn(10)).to.be.eql(false);
expect(fn("")).to.be.eql(false);
});
});
describe("isArray", function() {
var fn = _.isArray;
it("checks if a value is an Array", function() {
expect(fn([])).to.be.eql(true);
expect(fn(function() {})).to.be.eql(false);
expect(fn({})).to.be.eql(false);
expect(fn(10)).to.be.eql(false);
expect(fn("")).to.be.eql(false);
});
});
describe("isNumber", function() {
var fn = _.isNumber;
it("checks if a value is a Number", function() {
expect(fn(10)).to.be.eql(true);
expect(fn(function() {})).to.be.eql(false);
expect(fn({})).to.be.eql(false);
expect(fn([])).to.be.eql(false);
expect(fn("")).to.be.eql(false);
});
});
describe("isString", function() {
var fn = _.isString;
it("checks if a value is a String", function() {
expect(fn("")).to.be.eql(true);
expect(fn(10)).to.be.eql(false);
expect(fn(function() {})).to.be.eql(false);
expect(fn({})).to.be.eql(false);
expect(fn([])).to.be.eql(false);
});
});
describe("#pluck", function() {
var object = { a: { item: "hello" }, b: { item: "world" } },
array = [ { item: "hello" }, { item: "world" } ];
it("plucks values from a collection", function() {
expect(_.pluck(object, "item")).to.be.eql(["hello", "world"]);
expect(_.pluck(array, "item")).to.be.eql(["hello", "world"]);
});
});
describe("#map", function() {
var object = { a: { item: "hello" }, b: { item: "world" } },
array = [ { item: "hello" }, { item: "world" } ];
var fn = function(value, key) {
return [value, key];
};
it("runs a function over items in a collection", function() {
expect(_.map(object, fn)).to.be.eql([
[{ item: "hello" }, "a"],
[{ item: "world" }, "b"]
]);
expect(_.map(array, fn)).to.be.eql([
[{ item: "hello" }, 0],
[{ item: "world" }, 1]
]);
});
it("defaults to the identity function", function() {
expect(_.map(array)).to.be.eql(array);
expect(_.map(object)).to.be.eql(array);
});
});
describe("#invoke", function() {
var array = [
{
name: "bob",
toString: function() {
return "Hi from " + this.name;
}
},
{
name: "dave",
toString: function() {
return "hello from " + this.name;
}
}
];
var object = {
bob: {
name: "bob",
toString: function() {
return "Hi from " + this.name;
}
},
dave: {
name: "dave",
toString: function() {
return "hello from " + this.name;
}
}
};
it("runs a instance function over items in a collection", function() {
expect(_.invoke(object, "toString")).to.be.eql([
"Hi from bob",
"hello from dave"
]);
expect(_.invoke(array, "toString")).to.be.eql([
"Hi from bob",
"hello from dave"
]);
expect(_.invoke([1, 2, 3, 4, 5], Number.prototype.toString)).to.be.eql([
"1", "2", "3", "4", "5"
]);
});
});
describe("#each", function() {
var object = { a: { item: "hello" }, b: { item: "world" } },
array = [ { item: "hello" }, { item: "world" } ];
var fn = function(value, key) {
return [value, key];
};
it("runs a function over items in a collection", function() {
fn = spy();
_.map(object, fn);
expect(fn).to.be.calledWith(object.a, "a");
expect(fn).to.be.calledWith(object.b, "b");
fn = spy();
_.map(array, fn);
expect(fn).to.be.calledWith(array[0], 0);
expect(fn).to.be.calledWith(array[1], 1);
});
});
describe("#arity", function() {
it("creates a function that only takes a certain # of args", function() {
var fn = spy();
var one = _.arity(fn, 1);
one("one", "two", "three");
expect(fn).to.be.calledWith("one");
});
});
});