| 'use strict'; |
| |
| var map = require('es5-ext/object/map') |
| , isCallable = require('es5-ext/object/is-callable') |
| , validValue = require('es5-ext/object/valid-value') |
| , contains = require('es5-ext/string/#/contains') |
| |
| , call = Function.prototype.call |
| , defineProperty = Object.defineProperty |
| , getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor |
| , getPrototypeOf = Object.getPrototypeOf |
| , hasOwnProperty = Object.prototype.hasOwnProperty |
| , cacheDesc = { configurable: false, enumerable: false, writable: false, |
| value: null } |
| , define; |
| |
| define = function (name, options) { |
| var value, dgs, cacheName, desc, writable = false, resolvable |
| , flat; |
| options = Object(validValue(options)); |
| cacheName = options.cacheName; |
| flat = options.flat; |
| if (cacheName == null) cacheName = name; |
| delete options.cacheName; |
| value = options.value; |
| resolvable = isCallable(value); |
| delete options.value; |
| dgs = { configurable: Boolean(options.configurable), |
| enumerable: Boolean(options.enumerable) }; |
| if (name !== cacheName) { |
| dgs.get = function () { |
| if (hasOwnProperty.call(this, cacheName)) return this[cacheName]; |
| cacheDesc.value = resolvable ? call.call(value, this, options) : value; |
| cacheDesc.writable = writable; |
| defineProperty(this, cacheName, cacheDesc); |
| cacheDesc.value = null; |
| if (desc) defineProperty(this, name, desc); |
| return this[cacheName]; |
| }; |
| } else if (!flat) { |
| dgs.get = function self() { |
| var ownDesc; |
| if (hasOwnProperty.call(this, name)) { |
| ownDesc = getOwnPropertyDescriptor(this, name); |
| // It happens in Safari, that getter is still called after property |
| // was defined with a value, following workarounds that |
| if (ownDesc.hasOwnProperty('value')) return ownDesc.value; |
| if ((typeof ownDesc.get === 'function') && (ownDesc.get !== self)) { |
| return ownDesc.get.call(this); |
| } |
| return value; |
| } |
| desc.value = resolvable ? call.call(value, this, options) : value; |
| defineProperty(this, name, desc); |
| desc.value = null; |
| return this[name]; |
| }; |
| } else { |
| dgs.get = function self() { |
| var base = this, ownDesc; |
| if (hasOwnProperty.call(this, name)) { |
| // It happens in Safari, that getter is still called after property |
| // was defined with a value, following workarounds that |
| ownDesc = getOwnPropertyDescriptor(this, name); |
| if (ownDesc.hasOwnProperty('value')) return ownDesc.value; |
| if ((typeof ownDesc.get === 'function') && (ownDesc.get !== self)) { |
| return ownDesc.get.call(this); |
| } |
| } |
| while (!hasOwnProperty.call(base, name)) base = getPrototypeOf(base); |
| desc.value = resolvable ? call.call(value, base, options) : value; |
| defineProperty(base, name, desc); |
| desc.value = null; |
| return base[name]; |
| }; |
| } |
| dgs.set = function (value) { |
| dgs.get.call(this); |
| this[cacheName] = value; |
| }; |
| if (options.desc) { |
| desc = { |
| configurable: contains.call(options.desc, 'c'), |
| enumerable: contains.call(options.desc, 'e') |
| }; |
| if (cacheName === name) { |
| desc.writable = contains.call(options.desc, 'w'); |
| desc.value = null; |
| } else { |
| writable = contains.call(options.desc, 'w'); |
| desc.get = dgs.get; |
| desc.set = dgs.set; |
| } |
| delete options.desc; |
| } else if (cacheName === name) { |
| desc = { |
| configurable: Boolean(options.configurable), |
| enumerable: Boolean(options.enumerable), |
| writable: Boolean(options.writable), |
| value: null |
| }; |
| } |
| delete options.configurable; |
| delete options.enumerable; |
| delete options.writable; |
| return dgs; |
| }; |
| |
| module.exports = function (props) { |
| return map(props, function (desc, name) { return define(name, desc); }); |
| }; |