UNPKG

ingenta-lens

Version:
571 lines (481 loc) 16.3 kB
"use strict"; // Imports // ==== var _ = require('underscore'); // Module // ==== var util = {}; // UUID Generator // ----------------- /*! Math.uuid.js (v1.4) http://www.broofa.com mailto:robert@broofa.com Copyright (c) 2010 Robert Kieffer Dual licensed under the MIT and GPL licenses. */ util.uuid = function (prefix, len) { var chars = '0123456789abcdefghijklmnopqrstuvwxyz'.split(''), uuid = [], radix = 16, idx; len = len || 32; if (len) { // Compact form for (idx = 0; idx < len; idx++) uuid[idx] = chars[0 | Math.random()*radix]; } else { // rfc4122, version 4 form var r; // rfc4122 requires these characters uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; uuid[14] = '4'; // Fill in random data. At i==19 set the high bits of clock sequence as // per rfc4122, sec. 4.1.5 for (idx = 0; idx < 36; idx++) { if (!uuid[idx]) { r = 0 | Math.random()*16; uuid[idx] = chars[(idx == 19) ? (r & 0x3) | 0x8 : r]; } } } return (prefix ? prefix : "") + uuid.join(''); }; // creates a uuid function that generates counting uuids util.uuidGen = function(defaultPrefix) { var id = 1; defaultPrefix = (defaultPrefix !== undefined) ? defaultPrefix : "uuid_"; return function(prefix) { prefix = prefix || defaultPrefix; return prefix+(id++); }; }; // Events // --------------- // Taken from Backbone.js // // A module that can be mixed in to *any object* in order to provide it with // custom events. You may bind with `on` or remove with `off` callback // functions to an event; `trigger`-ing an event fires all callbacks in // succession. // // var object = {}; // _.extend(object, util.Events); // object.on('expand', function(){ alert('expanded'); }); // object.trigger('expand'); // // A difficult-to-believe, but optimized internal dispatch function for // triggering events. Tries to keep the usual cases speedy (most internal // Backbone events have 3 arguments). var triggerEvents = function(events, args) { var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; switch (args.length) { case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); } }; // Regular expression used to split event strings. var eventSplitter = /\s+/; // Implement fancy features of the Events API such as multiple event // names `"change blur"` and jQuery-style event maps `{change: action}` // in terms of the existing API. var eventsApi = function(obj, action, name, rest) { if (!name) return true; // Handle event maps. if (typeof name === 'object') { for (var key in name) { obj[action].apply(obj, [key, name[key]].concat(rest)); } return false; } // Handle space separated event names. if (eventSplitter.test(name)) { var names = name.split(eventSplitter); for (var i = 0, l = names.length; i < l; i++) { obj[action].apply(obj, [names[i]].concat(rest)); } return false; } return true; }; util.Events = { // Bind an event to a `callback` function. Passing `"all"` will bind // the callback to all events fired. on: function(name, callback, context) { if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this; this._events = this._events || {}; var events = this._events[name] || (this._events[name] = []); events.push({callback: callback, context: context, ctx: context || this}); return this; }, // Bind an event to only be triggered a single time. After the first time // the callback is invoked, it will be removed. once: function(name, callback, context) { if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this; var self = this; var once = _.once(function() { self.off(name, once); callback.apply(this, arguments); }); once._callback = callback; return this.on(name, once, context); }, // Remove one or many callbacks. If `context` is null, removes all // callbacks with that function. If `callback` is null, removes all // callbacks for the event. If `name` is null, removes all bound // callbacks for all events. off: function(name, callback, context) { var retain, ev, events, names, i, l, j, k; if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; if (!name && !callback && !context) { this._events = {}; return this; } names = name ? [name] : _.keys(this._events); for (i = 0, l = names.length; i < l; i++) { name = names[i]; events = this._events[name]; if (events) { this._events[name] = retain = []; if (callback || context) { for (j = 0, k = events.length; j < k; j++) { ev = events[j]; if ((callback && callback !== ev.callback && callback !== ev.callback._callback) || (context && context !== ev.context)) { retain.push(ev); } } } if (!retain.length) delete this._events[name]; } } return this; }, // Trigger one or many events, firing all bound callbacks. Callbacks are // passed the same arguments as `trigger` is, apart from the event name // (unless you're listening on `"all"`, which will cause your callback to // receive the true name of the event as the first argument). trigger: function(name) { if (!this._events) return this; var args = Array.prototype.slice.call(arguments, 1); if (!eventsApi(this, 'trigger', name, args)) return this; var events = this._events[name]; var allEvents = this._events.all; if (events) triggerEvents(events, args); if (allEvents) triggerEvents(allEvents, arguments); return this; }, triggerLater: function() { var self = this; var _arguments = arguments; window.setTimeout(function() { self.trigger.apply(self, _arguments); }, 0); }, // Tell this object to stop listening to either specific events ... or // to every object it's currently listening to. stopListening: function(obj, name, callback) { var listeners = this._listeners; if (!listeners) return this; var deleteListener = !name && !callback; if (typeof name === 'object') callback = this; if (obj) (listeners = {})[obj._listenerId] = obj; for (var id in listeners) { listeners[id].off(name, callback, this); if (deleteListener) delete this._listeners[id]; } return this; } }; var listenMethods = {listenTo: 'on', listenToOnce: 'once'}; // Inversion-of-control versions of `on` and `once`. Tell *this* object to // listen to an event in another object ... keeping track of what it's // listening to. _.each(listenMethods, function(implementation, method) { util.Events[method] = function(obj, name, callback) { var listeners = this._listeners || (this._listeners = {}); var id = obj._listenerId || (obj._listenerId = _.uniqueId('l')); listeners[id] = obj; if (typeof name === 'object') callback = this; obj[implementation](name, callback, this); return this; }; }); // Aliases for backwards compatibility. util.Events.bind = util.Events.on; util.Events.unbind = util.Events.off; util.Events.Listener = { listenTo: function(obj, name, callback) { if (!_.isFunction(callback)) { throw new Error("Illegal argument: expecting function as callback, was: " + callback); } // initialize container for keeping handlers to unbind later this._handlers = this._handlers || []; obj.on(name, callback, this); this._handlers.push({ unbind: function() { obj.off(name, callback); } }); return this; }, stopListening: function() { if (this._handlers) { for (var i = 0; i < this._handlers.length; i++) { this._handlers[i].unbind(); } } } }; util.propagate = function(data, cb) { if(!_.isFunction(cb)) { throw "Illegal argument: provided callback is not a function"; } return function(err) { if (err) return cb(err); cb(null, data); }; }; // shamelessly stolen from backbone.js: // Helper function to correctly set up the prototype chain, for subclasses. // Similar to `goog.inherits`, but uses a hash of prototype properties and // class properties to be extended. var ctor = function(){}; util.inherits = function(parent, protoProps, staticProps) { var child; // The constructor function for the new subclass is either defined by you // (the "constructor" property in your `extend` definition), or defaulted // by us to simply call the parent's constructor. if (protoProps && protoProps.hasOwnProperty('constructor')) { child = protoProps.constructor; } else { child = function(){ parent.apply(this, arguments); }; } // Inherit class (static) properties from parent. _.extend(child, parent); // Set the prototype chain to inherit from `parent`, without calling // `parent`'s constructor function. ctor.prototype = parent.prototype; child.prototype = new ctor(); // Add prototype properties (instance properties) to the subclass, // if supplied. if (protoProps) _.extend(child.prototype, protoProps); // Add static properties to the constructor function, if supplied. if (staticProps) _.extend(child, staticProps); // Correctly set child's `prototype.constructor`. child.prototype.constructor = child; // Set a convenience property in case the parent's prototype is needed later. child.__super__ = parent.prototype; return child; }; // Util to read seed data from file system // ---------- util.getJSON = function(resource, cb) { if (typeof window === 'undefined' || typeof nwglobal !== 'undefined') { var fs = require('fs'); var obj = JSON.parse(fs.readFileSync(resource, 'utf8')); cb(null, obj); } else { //console.log("util.getJSON", resource); var $ = window.$; $.getJSON(resource) .done(function(obj) { cb(null, obj); }) .error(function(err) { cb(err, null); }); } }; util.prototype = function(that) { /*jshint proto: true*/ // supressing a warning about using deprecated __proto__. return Object.getPrototypeOf ? Object.getPrototypeOf(that) : that.__proto__; }; util.inherit = function(Super, Self) { var super_proto = _.isFunction(Super) ? new Super() : Super; var proto; if (_.isFunction(Self)) { Self.prototype = super_proto; proto = new Self(); } else { var TmpClass = function(){}; TmpClass.prototype = super_proto; proto = _.extend(new TmpClass(), Self); } return proto; }; util.pimpl = function(pimpl) { var Pimpl = function(self) { this.self = self; }; Pimpl.prototype = pimpl; return function(self) { self = self || this; return new Pimpl(self); }; }; util.parseStackTrace = function(err) { var SAFARI_STACK_ELEM = /([^@]*)@(.*):(\d+)/; var CHROME_STACK_ELEM = /\s*at ([^(]*)[(](.*):(\d+):(\d+)[)]/; var idx; var stackTrace = err.stack.split('\n'); // parse the stack trace: each line is a tuple (function, file, lineNumber) // Note: unfortunately this is interpreter specific // safari: "<function>@<file>:<lineNumber>" // chrome: "at <function>(<file>:<line>:<col>" var stack = []; for (idx = 0; idx < stackTrace.length; idx++) { var match = SAFARI_STACK_ELEM.exec(stackTrace[idx]); if (!match) match = CHROME_STACK_ELEM.exec(stackTrace[idx]); var entry; if (match) { entry = { func: match[1], file: match[2], line: match[3], col: match[4] || 0 }; if (entry.func === "") entry.func = "<anonymous>"; } else { entry = { func: "", file: stackTrace[idx], line: "", col: "" }; } stack.push(entry); } return stack; }; util.callstack = function(k) { var err; try { throw new Error(); } catch (_err) { err = _err; } var stack = util.parseStackTrace(err); k = k || 0; return stack.splice(k+1); }; util.stacktrace = function (err) { var stack = (arguments.length === 0) ? util.callstack().splice(1) : util.parseStackTrace(err); var str = []; _.each(stack, function(s) { str.push(s.file+":"+s.line+":"+s.col+" ("+s.func+")"); }); return str.join("\n"); }; util.printStackTrace = function(err, N) { if (!err.stack) return; var stack; // Substance errors have a nice stack already if (err.__stack !== undefined) { stack = err.__stack; } // built-in errors have the stack trace as one string else if (_.isString(err.stack)) { stack = util.parseStackTrace(err); } else return; N = N || stack.length; N = Math.min(N, stack.length); for (var idx = 0; idx < N; idx++) { var s = stack[idx]; console.log(s.file+":"+s.line+":"+s.col, "("+s.func+")"); } }; // computes the difference of obj1 to obj2 util.diff = function(obj1, obj2) { var diff; if (_.isArray(obj1) && _.isArray(obj2)) { diff = _.difference(obj2, obj1); // return null in case of equality if (diff.length === 0) return null; else return diff; } if (_.isObject(obj1) && _.isObject(obj2)) { diff = {}; _.each(Object.keys(obj2), function(key) { var d = util.diff(obj1[key], obj2[key]); if (d) diff[key] = d; }); // return null in case of equality if (_.isEmpty(diff)) return null; else return diff; } if(obj1 !== obj2) return obj2; }; // Deep-Clone a given object // -------- // Note: this is currently done via JSON.parse(JSON.stringify(obj)) // which is in fact not optimal, as it depends on `toJSON` implementation. util.deepclone = function(obj) { if (obj === undefined) return undefined; if (obj === null) return null; return JSON.parse(JSON.stringify(obj)); }; // Clones a given object // -------- // Calls obj's `clone` function if available, // otherwise clones the obj using `util.deepclone()`. util.clone = function(obj) { if (obj === null || obj === undefined) { return obj; } if (_.isFunction(obj.clone)) { return obj.clone(); } return util.deepclone(obj); }; util.freeze = function(obj) { var idx; if (_.isObject(obj)) { if (Object.isFrozen(obj)) return obj; var keys = Object.keys(obj); for (idx = 0; idx < keys.length; idx++) { var key = keys[idx]; obj[key] = util.freeze(obj[key]); } return Object.freeze(obj); } else if (_.isArray(obj)) { var arr = obj; for (idx = 0; idx < arr.length; idx++) { arr[idx] = util.freeze(arr[idx]); } return Object.freeze(arr); } else { return obj; // Object.freeze(obj); } }; util.later = function(f, context) { return function() { var _args = arguments; window.setTimeout(function() { f.apply(context, _args); }, 0); }; }; // Returns true if a string doesn't contain any real content util.isEmpty = function(str) { return !str.match(/\w/); }; // Create a human readable, but URL-compatible slug from a string util.slug = function(str) { str = str.replace(/^\s+|\s+$/g, ''); // trim str = str.toLowerCase(); // remove accents, swap ñ for n, etc var from = "àáäâèéëêìíïîòóöôùúüûñç·/_,:;"; var to = "aaaaeeeeiiiioooouuuunc------"; for (var i=0, l=from.length ; i<l ; i++) { str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i)); } str = str.replace(/[^a-z0-9 -]/g, '') // remove invalid chars .replace(/\s+/g, '-') // collapse whitespace and replace by - .replace(/-+/g, '-'); // collapse dashes return str; }; util.getReadableFileSizeString = function(fileSizeInBytes) { var i = -1; var byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB']; do { fileSizeInBytes = fileSizeInBytes / 1024; i++; } while (fileSizeInBytes > 1024); return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i]; }; // Export // ==== module.exports = util;