/* * Cylon - Utils * cylonjs.com * * Copyright (c) 2013 The Hybrid Group * Licensed under the Apache 2.0 license. */ "use strict"; var _ = require("./lodash"); var addCoreExtensions = function addCoreExtensions() { var max = Math.max, min = Math.min; // 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) { var val = (this - min(start, end)) / (max(start, end) - min(start, end)); if (val > 1) { return 1; } if (val < 0){ return 0; } return val; }; // 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 * (max(start, end) - min(start, end)) + min(start, end); if (i < start) { return start; } if (i > end) { return end; } return i; }; }; var Utils = module.exports = { // Public: Alias to setInterval, combined with Number monkeypatches below to // create an artoo-like syntax. // // interval - interval to run action on // action - action to perform at interval // // Examples // // every((5).seconds(), function() { // console.log("Hello world (and again in 5 seconds)!"); // }); // // Returns an interval every: function every(interval, action) { return setInterval(action, interval); }, // Public: Alias to setTimeout, combined with Number monkeypatches below to // create an artoo-like syntax. // // interval - interval to run action on // action - action to perform at interval // // Examples // // after((10).seconds(), function() { // console.log("Hello world from ten seconds ago!"); // }); // // Returns an interval after: function after(delay, action) { return setTimeout(action, delay); }, // Public: Alias to the `every` function, but passing 0 // Examples // // constantly(function() { // console.log("hello world (and again and again)!"); // }); // // Returns an interval constantly: function constantly(action) { return every(0, action); }, // Public: Sleep - do nothing for some duration of time. // // ms - number of ms to sleep for // // Examples // // sleep((1).second()); // // Returns a function sleep: function sleep(ms) { var start = Date.now(), i; while(Date.now() < start + ms) { i = 0; } }, // Public: Function to use for class inheritance. // Based on CoffeeScript's implementation. // // Example // // var Sphero = function Sphero() {}; // // subclass(Sphero, ParentClass); // // // Sphero is now a subclass of Parent, and can access parent methods // // through Sphero.__super__ // // Returns subclass subclass: function subclass(child, parent) { var Ctor = function() { this.constructor = child; }; _.forOwn(parent, function(prop, key) { child[key] = prop; }); Ctor.prototype = parent.prototype; child.prototype = new Ctor(); child.__super__ = parent.prototype; return child; }, proxyFunctions: function proxyFunctions(source, target) { _.forEach(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 // overwrite existing methods unless told to. // // methods - array of functions to proxy // target - object to proxy the functions to // base - (optional) object that proxied functions will be declared on. // Defaults to 'this'. // force - (optional) boolean - whether or not to force method assignment // // Returns base proxyFunctionsToObject: function(methods, target, base, force) { if (base == null) { base = this; } force = force || false; _.forEach(methods, function(method) { if (_.isFunction(base[method]) && !force) { return; } base[method] = function() { return target[method].apply(target, arguments); }; }); return base; }, // Public: Analogue to Ruby"s Hash#fetch method for looking up object // properties. // // obj - object to do property lookup on // property - string property name to attempt to look up // fallback - either: // - a fallback value to return if `property` can"t be found // - a function to be executed if `property` can"t be found. The function // will be passed `property` as an argument. // // Examples // // var object = { property: "hello world" }; // fetch(object, "property"); // //=> "hello world" // // fetch(object, "notaproperty", "default value"); // //=> "default value" // // var notFound = function(prop) { return prop + " not found!" }; // fetch(object, "notaproperty", notFound) // // "notaproperty not found!" // // var badFallback = function(prop) { prop + " not found!" }; // fetch(object, "notaproperty", badFallback) // // Error: no return value from provided callback function // // fetch(object, "notaproperty"); // // Error: key not found: "notaproperty" // // Returns the value of obj[property], a fallback value, or the results of // running "fallback". If the property isn"t found, and "fallback" hasn"t been // provided, will throw an error. fetch: function(obj, property, fallback) { if (obj.hasOwnProperty(property)) { return obj[property]; } if (fallback === void 0) { throw new Error("key not found: \"" + property + "\""); } if (typeof(fallback) === "function") { var value = fallback(property); if (value === void 0) { throw new Error("no return value from provided fallback function"); } return value; } return fallback; }, // Public: Given a name, and an array of existing names, returns a unique // name. // // name - name that"s colliding with existing names // arr - array of existing names // // Returns the new name as a string makeUnique: function(name, arr) { var newName; if (!~arr.indexOf(name)) { return name; } for (var n = 1; ; n++) { newName = name + "-" + n; if (!~arr.indexOf(newName)) { return newName; } } }, // Public: Adds necessary utils to global namespace, along with base class // extensions. // // Examples // // Number.prototype.seconds // undefined // after // undefined // // Utils.bootstrap(); // // Number.prototype.seconds // [function] // (after === Utils.after) // true // // Returns Cylon.Utils bootstrap: function bootstrap() { global.every = this.every; global.after = this.after; global.constantly = this.constantly; addCoreExtensions(); return this; } }; Utils.bootstrap();