Refactor Utils (mostly) out of global namespace

This commit reframes the Utils module to be more self-contained, and now only
the Number prototype methods and the every/after/constantly methods are exposed
globally.

I'm unsure about whether these methods can be more scoped such that people can
turn them off if they want, but that's potentially something to look into in the
future.

This commit also updates most of the other source files in Cylon, requiring the
Utils module rather than assuming the methods are in the global namespace.

A similar batch of commits will need to be made for all other Cylon modules to
make sure they don't explode when they can't find #subclass or something like
that.

To make the previously-global methods available, both in submodules and for
people looking for them, all the utility functions are exported in Cylon.Utils.
This commit is contained in:
Andrew Stewart 2014-06-05 12:11:37 -07:00
parent 789347f6f8
commit f91dd7d5c3
24 changed files with 168 additions and 162 deletions

View File

@ -9,7 +9,8 @@
"use strict";
var Basestar = require('./basestar'),
Logger = require('./logger');
Logger = require('./logger'),
Utils = require('./utils');
// The Adaptor class is a base class for Adaptor classes in external Cylon
// modules to use. It offers basic functions for connecting/disconnecting that
@ -34,7 +35,7 @@ module.exports = Adaptor = function Adaptor(opts) {
this.commandList = [];
};
subclass(Adaptor, Basestar);
Utils.subclass(Adaptor, Basestar);
// Public: Exposes all commands the adaptor will respond to/proxy
//

View File

@ -8,10 +8,10 @@
"use strict";
require('./utils');
var EventEmitter = require('events').EventEmitter;
var Utils = require('./utils');
// Basestar is a base class to be used when writing external Cylon adaptors and
// drivers. It provides some useful base methods and functionality
//
@ -23,7 +23,7 @@ module.exports = Basestar = function Basestar(opts) {
this.self = this;
}
subclass(Basestar, EventEmitter);
Utils.subclass(Basestar, EventEmitter);
// Public: Proxies calls from all methods in the object to a target object
//
@ -35,7 +35,7 @@ subclass(Basestar, EventEmitter);
// Returns the klass where the methods have been proxied
Basestar.prototype.proxyMethods = function(methods, target, source, force) {
if (force == null) { force = false; }
return proxyFunctionsToObject(methods, target, source, force);
return Utils.proxyFunctionsToObject(methods, target, source, force);
};
// Public: Defines an event handler that proxies events from a source object

View File

@ -8,11 +8,10 @@
'use strict';
require("./utils");
var EventEmitter = require('events').EventEmitter;
var Logger = require('./logger');
var Logger = require('./logger'),
Utils = require('./utils');
// The Connection class represents the interface to
// a specific group of hardware devices. Examples would be an
@ -38,7 +37,7 @@ module.exports = Connection = function Connection(opts) {
opts.id = Math.floor(Math.random() * 10000);
}
this.connect = bind(this.connect, this);
this.connect = Utils.bind(this.connect, this);
this.self = this;
this.robot = opts.robot;
@ -47,10 +46,10 @@ module.exports = Connection = function Connection(opts) {
this.port = opts.port;
this.adaptor = this.initAdaptor(opts);
proxyFunctionsToObject(this.adaptor.commands(), this.adaptor, this.self);
Utils.proxyFunctionsToObject(this.adaptor.commands(), this.adaptor, this.self);
}
subclass(Connection, EventEmitter);
Utils.subclass(Connection, EventEmitter);
// Public: Exports basic data for the Connection
//

View File

@ -19,6 +19,7 @@ var Cylon = module.exports = {
Logger: Logger,
Driver: require('./driver'),
Adaptor: require('./adaptor'),
Utils: Utils,
IO: {
DigitalPin: require('./io/digital-pin')

View File

@ -8,11 +8,10 @@
'use strict';
require('./utils');
var EventEmitter = require('events').EventEmitter;
var Logger = require('./logger');
var Logger = require('./logger'),
Utils = require('./utils');
// The Artoo::Device class represents the interface to
// a specific individual hardware devices. Examples would be a digital
@ -34,8 +33,8 @@ module.exports = Device = function Device(opts) {
opts = {};
}
this.halt = bind(this.halt, this);
this.start = bind(this.start, this);
this.halt = Utils.bind(this.halt, this);
this.start = Utils.bind(this.start, this);
this.self = this;
this.robot = opts.robot;
@ -44,10 +43,10 @@ module.exports = Device = function Device(opts) {
this.connection = this.determineConnection(opts.connection) || this.defaultConnection();
this.driver = this.initDriver(opts);
proxyFunctionsToObject(this.driver.commands(), this.driver, this.self);
Utils.proxyFunctionsToObject(this.driver.commands(), this.driver, this.self);
};
subclass(Device, EventEmitter);
Utils.subclass(Device, EventEmitter);
// Public: Starts the device driver
//

View File

@ -9,7 +9,8 @@
'use strict';
var Basestar = require('./basestar'),
Logger = require('./logger');
Logger = require('./logger'),
Utils = require('./utils');
// The Driver class is a base class for Driver classes in external Cylon
// modules to use. It offers basic functions for starting/halting that
@ -35,7 +36,7 @@ module.exports = Driver = function Driver(opts) {
this.commandList = [];
};
subclass(Driver, Basestar);
Utils.subclass(Driver, Basestar);
// Public: Exposes all commands the driver will respond to/proxy
//

View File

@ -11,6 +11,8 @@
var FS = require('fs'),
EventEmitter = require('events').EventEmitter;
var Utils = require('../utils');
var GPIO_PATH = "/sys/class/gpio";
var GPIO_READ = "in";
@ -28,7 +30,7 @@ var DigitalPin = module.exports = function DigitalPin(opts) {
this.mode = opts.mode;
}
subclass(DigitalPin, EventEmitter);
Utils.subclass(DigitalPin, EventEmitter);
DigitalPin.prototype.connect = function(mode) {
var _this = this;
@ -81,7 +83,7 @@ DigitalPin.prototype.digitalRead = function(interval) {
if (this.mode !== 'r') { this._setMode('r'); }
every(interval, function() {
Utils.every(interval, function() {
FS.readFile(_this._valuePath(), function(err, data) {
if (err) {
var error = "Error occurred while reading from pin " + _this.pinNum;

View File

@ -8,11 +8,10 @@
'use strict';
require('./utils');
var Connection = require("./connection"),
Device = require("./device"),
Logger = require('./logger'),
Utils = require('./utils'),
config = require('./config');
var Async = require("async"),
@ -43,7 +42,7 @@ var Robot;
// name: 'sphero', driver: 'sphero'
//
// work: (me) ->
// every 1.second(), ->
// Utils.every 1.second(), ->
// me.sphero.roll 60, Math.floor(Math.random() * 360//
module.exports = Robot = function Robot(opts) {
if (opts == null) {
@ -66,7 +65,7 @@ module.exports = Robot = function Robot(opts) {
for (var i = 0; i < methods.length ; i++) {
var method = methods[i];
this[method] = bind(this[method], this);
this[method] = Utils.bind(this[method], this);
}
this.robot = this;
@ -101,7 +100,7 @@ module.exports = Robot = function Robot(opts) {
}
};
subclass(Robot, EventEmitter);
Utils.subclass(Robot, EventEmitter);
// Public: Generates a random name for a Robot.
//
@ -276,7 +275,7 @@ Robot.prototype.initAdaptor = function(adaptorName, connection, opts) {
connection: connection,
extraParams: opts
});
return proxyTestStubs(adaptor.commands(), testAdaptor);
return Utils.proxyTestStubs(adaptor.commands(), testAdaptor);
} else {
return adaptor;
}
@ -339,7 +338,7 @@ Robot.prototype.initDriver = function(driverName, device, opts) {
extraParams: opts
});
return proxyTestStubs(driver.commands(), testDriver);
return Utils.proxyTestStubs(driver.commands(), testDriver);
} else {
return driver;
}

View File

@ -8,7 +8,8 @@
"use strict";
var Adaptor = require('../adaptor');
var Adaptor = require('../adaptor'),
Utils = require('../utils');
var Loopback;
@ -16,7 +17,7 @@ module.exports = Loopback = function Loopback() {
Loopback.__super__.constructor.apply(this, arguments);
};
subclass(Loopback, Adaptor);
Utils.subclass(Loopback, Adaptor);
Loopback.prototype.commands = function() {
return ['ping'];

View File

@ -8,7 +8,8 @@
'use strict';
var Driver = require('../driver');
var Driver = require('../driver'),
Utils = require('../utils');
var Ping;
@ -16,7 +17,7 @@ module.exports = Ping = function Ping() {
Ping.__super__.constructor.apply(this, arguments);
};
subclass(Ping, Driver);
Utils.subclass(Ping, Driver);
Ping.prototype.commands = function() {
return ['ping'];

View File

@ -8,7 +8,8 @@
"use strict";
var Adaptor = require('../adaptor')
var Adaptor = require('../adaptor'),
Utils = require('../utils');
var TestAdaptor;
@ -16,6 +17,6 @@ module.exports = TestAdaptor = function TestAdaptor() {
TestAdaptor.__super__.constructor.apply(this, arguments);
};
subclass(TestAdaptor, Adaptor);
Utils.subclass(TestAdaptor, Adaptor);
TestAdaptor.adaptor = function(opts) { return new TestAdaptor(opts); };

View File

@ -8,7 +8,8 @@
'use strict';
var Driver = require('../driver');
var Driver = require('../driver'),
Utils = require('../utils');
var TestDriver;
@ -16,6 +17,6 @@ module.exports = TestDriver = function TestDriver() {
TestDriver.__super__.constructor.apply(this, arguments);
};
subclass(TestDriver, Driver);
Utils.subclass(TestDriver, Driver);
TestDriver.driver = function(opts) { return new TestDriver(opts); };

View File

@ -1,84 +1,12 @@
/*
* utils
* Cylon - Utils
* cylonjs.com
*
* Copyright (c) 2013 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
// Public: Monkey-patches Number to have Rails-like //seconds() function.
// Warning, due to the way the Javascript parser works, applying functions on
// numbers is kind of weird. See examples for details.
//
// Examples
//
// 2.seconds()
// //=> SyntaxError: Unexpected token ILLEGAL
//
// 10..seconds()
// //=> 10000
//
// (5).seconds()
// //=> 5000
// // This is the preferred way to represent numbers when calling these
// // methods on them
//
// Returns an integer representing time in milliseconds
Number.prototype.seconds = function() {
return this * 1000;
};
// Public: Alias for Number::seconds, see comments for that method
//
// Examples
//
// 1.second()
// //=> 1000
//
// Returns an integer representing time in milliseconds
Number.prototype.second = function() {
return this.seconds(this);
};
// Public: Convert value from old scale (start, end) to (0..1) scale
//
// start - low point of scale to convert value from
// end - high point of scale to convert value from
//
// Examples
//
// (5).fromScale(0, 10)
// //=> 0.5
//
// Returns an integer representing the scaled value
Number.prototype.fromScale = function(start, end) {
return (this - Math.min(start, end)) / (Math.max(start, end) - Math.min(start, end));
};
// Public: Convert value from (0..1) scale to new (start, end) scale
//
// start - low point of scale to convert value to
// end - high point of scale to convert value to
//
// Examples
//
// (0.5).toScale(0, 10)
// //=> 5
//
// Returns an integer representing the scaled value
Number.prototype.toScale = function(start, end) {
var i = this * (Math.max(start, end) - Math.min(start, end)) + Math.min(start, end);
if (i < Math.min(start, end)) {
return Math.min(start, end);
} else if (i > Math.max(start,end)){
return Math.max(start, end);
} else {
return i;
}
};
global.Utils = {
var Utils = module.exports = {
// Public: Alias to setInterval, combined with Number monkeypatches below to
// create an artoo-like syntax.
//
@ -142,10 +70,6 @@ global.Utils = {
}
},
// Copies
slice: [].slice,
hasProp: {}.hasOwnProperty,
// Public: Function to use for class inheritance. Copy of a CoffeeScript helper
// function.
//
@ -163,7 +87,9 @@ global.Utils = {
var ctor = function() { this.constructor = child; };
for (var key in parent) {
if (hasProp.call(parent, key)) { child[key] = parent[key]; }
if (Object.hasOwnProperty.call(parent, key)) {
child[key] = parent[key];
}
}
ctor.prototype = parent.prototype;
@ -242,25 +168,103 @@ global.Utils = {
return function() { return fn.apply(me, arguments); };
},
// Public: Adds all methods from Cylon.Utils directly to the global
// namespace.
// Public: Adds necessary utils to global namespace, along with base class
// extensions.
//
// Examples
//
// Number.prototype.seconds // undefined
// after // undefined
//
// Utils.bootstrap();
// (after === Utils.after) // true
//
// Number.prototype.seconds // [function]
// (after === Utils.after) // true
//
// Returns Cylon.Utils
bootstrap: function bootstrap() {
for (util in this) {
// we're not going to attach the 'bootstrap' method
if (!(util === "bootstrap")) {
global[util] = this[util];
}
}
global.every = this.every;
global.after = this.after;
global.constantly = this.constantly;
addCoreExtensions();
return this;
}
};
module.exports = Utils.bootstrap();
var addCoreExtensions = function addCoreExtensions() {
// Public: Monkey-patches Number to have Rails-like //seconds() function.
// Warning, due to the way the Javascript parser works, applying functions on
// numbers is kind of weird. See examples for details.
//
// Examples
//
// 2.seconds()
// //=> SyntaxError: Unexpected token ILLEGAL
//
// 10..seconds()
// //=> 10000
//
// (5).seconds()
// //=> 5000
// // This is the preferred way to represent numbers when calling these
// // methods on them
//
// Returns an integer representing time in milliseconds
Number.prototype.seconds = function() {
return this * 1000;
};
// Public: Alias for Number::seconds, see comments for that method
//
// Examples
//
// 1.second()
// //=> 1000
//
// Returns an integer representing time in milliseconds
Number.prototype.second = function() {
return this.seconds(this);
};
// Public: Convert value from old scale (start, end) to (0..1) scale
//
// start - low point of scale to convert value from
// end - high point of scale to convert value from
//
// Examples
//
// (5).fromScale(0, 10)
// //=> 0.5
//
// Returns an integer representing the scaled value
Number.prototype.fromScale = function(start, end) {
return (this - Math.min(start, end)) / (Math.max(start, end) - Math.min(start, end));
};
// Public: Convert value from (0..1) scale to new (start, end) scale
//
// start - low point of scale to convert value to
// end - high point of scale to convert value to
//
// Examples
//
// (0.5).toScale(0, 10)
// //=> 5
//
// Returns an integer representing the scaled value
Number.prototype.toScale = function(start, end) {
var i = this * (Math.max(start, end) - Math.min(start, end)) + Math.min(start, end);
if (i < Math.min(start, end)) {
return Math.min(start, end);
} else if (i > Math.max(start,end)){
return Math.max(start, end);
} else {
return i;
}
};
};
Utils.bootstrap();

View File

@ -3,7 +3,8 @@
var EventEmitter = require('events').EventEmitter;
var Adaptor = source("adaptor"),
Logger = source('logger');
Logger = source('logger'),
Utils = source('utils');
describe("Adaptor", function() {
var connection = new EventEmitter;

View File

@ -4,7 +4,8 @@ var express = require('express'),
https = require('https'),
fs = require('fs');
var API = source('api');
var API = source('api'),
Utils = source('utils');
describe("API", function() {
var api, opts;

View File

@ -1,6 +1,7 @@
"use strict";
var Basestar = source('basestar');
var Basestar = source('basestar'),
Utils = source('utils');
var EventEmitter = require('events').EventEmitter;
@ -34,7 +35,7 @@ describe('Basestar', function() {
this.proxyMethods(methods, this.testInstance, this, true);
}
subclass(TestClass, Basestar);
Utils.subclass(TestClass, Basestar);
it('can alias methods', function() {
var testclass = new TestClass;
@ -70,8 +71,8 @@ describe('Basestar', function() {
});
}
subclass(ProxyClass, Basestar);
subclass(EmitterClass, Basestar);
Utils.subclass(ProxyClass, Basestar);
Utils.subclass(EmitterClass, Basestar);
it("proxies events from one class to another", function() {
var eventSpy = spy(),

View File

@ -1,7 +1,8 @@
"use strict";
var Robot = source("robot"),
Logger = source('logger');
Logger = source('logger'),
Utils = source('utils');
describe("Cylon.Connection", function() {
var robot = new Robot({

View File

@ -1,6 +1,7 @@
"use strict";
var Cylon = source("cylon");
var Cylon = source("cylon"),
Utils = source('utils');
var API = source('api'),
Logger = source('logger'),

View File

@ -3,7 +3,8 @@
var Ping = source('test/ping'),
Device = source("device"),
Robot = source("robot"),
Logger = source('logger');
Logger = source('logger'),
Utils = source('utils');
describe("Cylon.Device", function() {
var robot = new Robot({

View File

@ -2,7 +2,8 @@
var fs = require('fs');
var DigitalPin = source('io/digital-pin');
var DigitalPin = source('io/digital-pin'),
Utils = source('utils');
describe("Cylon.IO.DigitalPin", function() {
var pin = new DigitalPin({ pin: '4', mode: 'w' })
@ -195,12 +196,12 @@ describe("Cylon.IO.DigitalPin", function() {
context("if the mode isn't 'r'", function() {
before(function() {
stub(global, 'every');
stub(Utils, 'every');
stub(pin, '_setMode');
});
after(function() {
global.every.restore();
Utils.every.restore();
pin._setMode.restore();
});

View File

@ -3,7 +3,8 @@
var EventEmitter = require('events').EventEmitter;
var Driver = source("driver"),
Logger = source('logger');
Logger = source('logger'),
Utils = source('utils');
describe("Driver", function() {
var device = {

View File

@ -1,6 +1,7 @@
'use strict';
var Logger = source('logger');
var Logger = source('logger'),
Utils = source('utils');
describe('Logger', function() {
after(function() {

View File

@ -2,7 +2,8 @@
var Device = source('device'),
Connection = source('connection'),
Robot = source("robot");
Robot = source("robot"),
Utils = source('utils');
describe("Robot", function() {
var work = spy();

View File

@ -95,20 +95,6 @@ describe("Utils", function() {
});
});
describe("#slice", function() {
it("performs array slices", function() {
var arr = [1, 2, 3, 4, 5];
expect(slice.call(arr, 1)).to.be.eql([2, 3, 4, 5]);
});
});
describe("hasProp", function() {
it("checks objects have properties", function() {
var obj = { test: 'test' };
expect(hasProp.call(obj, 'test')).to.be.true;
});
});
describe("#subclass", function() {
var BaseClass = (function() {
function BaseClass(opts) {
@ -123,7 +109,7 @@ describe("Utils", function() {
})();
var SubClass = (function(klass) {
subclass(SubClass, klass);
utils.subclass(SubClass, klass);
function SubClass(opts) {
SubClass.__super__.constructor.apply(this, arguments);
@ -164,7 +150,7 @@ describe("Utils", function() {
function TestClass() {
this.self = this;
this.testInstance = new ProxyClass;
proxyFunctionsToObject(methods, this.testInstance, this.self, true);
utils.proxyFunctionsToObject(methods, this.testInstance, this.self, true);
}
return TestClass;
@ -190,7 +176,7 @@ describe("Utils", function() {
var methods = ["hello", "goodbye"],
base = { commandList: [] };
proxyTestStubs(methods, base);
utils.proxyTestStubs(methods, base);
expect(base.commandList).to.be.eql(methods);
});
@ -198,7 +184,7 @@ describe("Utils", function() {
var methods = ["hello", "goodbye"],
base = { commandList: [] };
expect(proxyTestStubs(methods, base)).to.be.eql(base);
expect(utils.proxyTestStubs(methods, base)).to.be.eql(base);
});
});
@ -208,14 +194,14 @@ describe("Utils", function() {
it("binds the 'this' scope for the method", function() {
proxy.boundMethod = function() { return this.hello; };
proxy.boundMethod = bind(proxy.boundMethod, me);
proxy.boundMethod = utils.bind(proxy.boundMethod, me);
expect(proxy.boundMethod()).to.eql("Hello World");
});
it("passes arguments along to bound functions", function() {
proxy.boundMethod = function(hello, world) { return [hello, world]; };
proxy.boundMethod = bind(proxy.boundMethod, me);
proxy.boundMethod = utils.bind(proxy.boundMethod, me);
expect(proxy.boundMethod("Hello", "World")).to.eql(["Hello", "World"]);
})