--- /dev/null
+'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); });
+};