UNPKG

node-oops

Version:

A small library that makes object-oriented programming in javascript a little simpler.

244 lines (225 loc) 7.67 kB
;(function(undefined) { "use strict"; var $scope , inferFromName = function (f) { return f.name; } , conflict, conflictResolution = [], obstructions = []; if (typeof global == 'object' && global) { $scope = global; conflict = global.oops; } else if (typeof window !== 'undefined'){ $scope = window; conflict = window.oops; if (typeof (function x(){}).name == 'undefined') { // the environment doens't report an anonymous function's name (IE?) // parse it out of the toString. inferFromName = function(f) { var n = f.toString().match(/^function\s*(?:\s+([\w\$]*))?\s*\(/); return (n) ? n[1] : ""; } } } else { $scope = {}; } if (conflict) { conflictResolution.push( function() { if ($scope.oops === Define) { $scope.oops = conflict; conflict = undefined; } }); } function dbc(requirements, description) { requirements = (Array.isArray(requirements)) ? requirements : [requirements]; var i, disposition; for(i = 0; i < requirements.length; i++) { var req = requirements[i]; disposition = (typeof req === 'function') ? req() : (req); if(!disposition) { description = description || 'Failed contract requirement:'.concat(req); throw new ContractError((typeof description === 'function') ? description() : description); } } } function val(target, name, value, descriptor) { dbc([typeof name === 'string'], "Invalid property name [String]."); descriptor = descriptor || {}; descriptor.value = value; Object.defineProperty(target, name, descriptor); } function meth(target, name, method, descriptor) { dbc([!method || typeof method === 'function'], "Invalid method [Function]."); descriptor = descriptor || {}; val(target, name, method, descriptor); } function prop(target,name,get,set,descriptor) { dbc([typeof name === 'string'], "Invalid property name [String]."); dbc([!get || typeof get === 'function'], "Invalid getter [Function]."); dbc([!set || typeof set === 'function'], "Invalid setter [Function]."); descriptor = descriptor || {}; delete descriptor.writable; // not valid for properties. if (get) { descriptor.get = get; } if (set) { descriptor.set = set; } Object.defineProperty(target, name, descriptor); } function CEW(c,e,w){ // set from actual booleans, not from truthy args. var d = {}; if(c === true) { d.configurable = true; } if(e === true) { d.enumerable = true; } if(w === true) { d.writable = true; } return d; } function cloneDesc(d) { d = d || {}; return CEW(d.configurable, d.enumerable, d.writable); } function Define(it, abs){ /** Ensure declarations are applied to either an instance or its prototype, but not the function (Ctor) itself (unless instructed so by 'abs')... */ var target = it; if(!abs && typeof it.prototype === 'object' && typeof it.prototype.constructor === 'function') { target = it.prototype; } val(this, '_it', target); val(this, '_d', {}); } Object.defineProperties(Define.prototype, { enumerable: { get: function() { this._d.enumerable = true; return this; }, enumerable: true }, configurable: { get: function() { this._d.configurable = true; return this; }, enumerable: true }, writable: { get: function() { this._d.writable = true; return this; }, enumerable: true }, value: { value: function(name, value) { val(this._it, name, value, cloneDesc(this._d)); return this; }, enumerable: true }, method: { value: function(name, method) { var typeofName = typeof name; dbc([typeofName === 'string' || typeofName === 'function'], "Either the method or its name must be provided as the first argument."); if (typeofName === 'function') { dbc([typeof method === 'undefined'], "When the method is given as first argument, additional arguments indicate an error on the part of the caller."); meth(this._it, inferFromName(name), name, cloneDesc(this._d)); } else { dbc([typeof method !== 'undefined'], "When the method's name is provided as the first argument the method must appear as the second."); meth(this._it, name, method, cloneDesc(this._d)); } return this; }, enumerable: true }, property: { value: function(name, get, set) { var typeofName = typeof name; dbc([typeofName === 'string' || typeofName === 'function'], "Either the property's name or it's getter [Function] must be provided as the first argument."); var d = this._d if (typeofName === 'function') { /** When first arg is [Function] treat it as a getter. The property's name is taken from the getter. (args shift 1 left) */ prop(this._it, inferFromName(name), name, get, cloneDesc(this._d)); } else { prop(this._it, name, get, set, cloneDesc(this._d)); } return this; }, enumerable: true } }); // nodejs compatible on server side and in the browser. function inherits(ctor, superCtor) { ctor.super_ = superCtor; ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor, enumerable: false, writable: true, configurable: true } }); } /** * Factory for Define. */ function create(it, abs) { return new Define(it || this, abs); } if (Object.getOwnPropertyDescriptor(Object.prototype, 'defines')) { obstructions.push('defines'); } else { Object.defineProperties(Object.prototype, { defines: { get: create, configurable: true } }); conflictResolution.push(function() { delete Object.prototype.defines; obstructions.push('defines'); }); } if (Object.getOwnPropertyDescriptor(Function.prototype, 'inherits')) { obstructions.push('inherits'); } else { Object.defineProperties(Function.prototype, { inherits: { value: function (superCtor) { inherits(this, superCtor); // Prevent multiple inheritance (no intentional support)... // user should duck type if they must behave like a catdog. this.inherits = undefined; }, configurable: true } }); conflictResolution.push(function() { delete Function.prototype.inherits; obstructions.push('inherits'); }); } function obstructed(which) { return which && obstructions.indexOf(which) >= 0; } function noConflict() { if (conflictResolution) { conflictResolution.forEach(function (it) { it(); }); conflictResolution = null; } return Define; } function ContractError(message) { ContractError.super_.call(this, message); (new Define(this)).value('message', message || ''); } inherits(ContractError, Error); (new Define(ContractError)).configurable.enumerable.method(function toString() { return 'ContractError: '.concat(this.message); }); (new Define(Define, true)).enumerable .method(dbc) .method(Define) .method(create) .method(ContractError) .method(inherits) .method(noConflict) .method(obstructed) ; if (typeof module != 'undefined' && module && typeof exports == 'object' && exports && module.exports === exports) { // Yay! probably nodejs module.exports = Define; } else { // hrm. browser? $scope.oops = Define; } }());