diff src/implab/safe.js @ 0:fc2517695ee1

Initial commit, draft import of existing work
author cin
date Thu, 01 Jun 2017 13:20:03 +0300
parents
children 00779cb63b12
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/implab/safe.js	Thu Jun 01 13:20:03 2017 +0300
@@ -0,0 +1,356 @@
+define([],
+
+    function () {
+        var id = 0,
+            OID_FIELD = "__core_safe_oid_field",
+            _create, _defineProperty, _keys;
+
+        if (!Object.keys) {
+            _defineProperty = function (obj, prop, d) {
+                if (!d)
+                    throw new Error("Invalid descriptor");
+                obj[prop] = d.value;
+            };
+        } else {
+            _defineProperty = Object.defineProperty;
+        }
+
+        if (!Object.create) {
+            var tctor = function () {};
+            _create = function (proto) {
+                if (arguments.length > 1)
+                    throw new Error("The two arguments for isn't supported");
+
+                tctor.prototype = proto;
+                return new tctor();
+            };
+        } else {
+            _create = Object.create;
+        }
+
+        if (!Object.keys) {
+            // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys#Polyfill
+            _keys = (function () {
+                'use strict';
+                var hasOwnProperty = Object.prototype.hasOwnProperty,
+                    hasDontEnumBug = !({
+                        toString: null
+                    }).propertyIsEnumerable('toString'),
+                    dontEnums = [
+                        'toString',
+                        'toLocaleString',
+                        'valueOf',
+                        'hasOwnProperty',
+                        'isPrototypeOf',
+                        'propertyIsEnumerable',
+                        'constructor'
+                    ],
+                    dontEnumsLength = dontEnums.length;
+
+                return function (obj) {
+                    if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
+                        throw new TypeError('Object.keys called on non-object');
+                    }
+
+                    var result = [],
+                        prop, i;
+
+                    for (prop in obj) {
+                        if (prop != OID_FIELD && hasOwnProperty.call(obj, prop)) {
+                            result.push(prop);
+                        }
+                    }
+
+                    if (hasDontEnumBug) {
+                        for (i = 0; i < dontEnumsLength; i++) {
+                            if (hasOwnProperty.call(obj, dontEnums[i])) {
+                                result.push(dontEnums[i]);
+                            }
+                        }
+                    }
+                    return result;
+                };
+            }());
+        } else {
+            _keys = Object.keys;
+        }
+
+        var safe = null;
+        safe = {
+            OID_FIELD: OID_FIELD,
+
+            argumentNotNull: function (arg, name) {
+                if (arg === null || arg === undefined)
+                    throw new Error("The argument " + name + " can't be null or undefined");
+            },
+
+            argumentNotEmptyString: function (arg, name) {
+                if (typeof (arg) !== "string" || !arg.length)
+                    throw new Error("The argument '" + name + "' must be a not empty string");
+            },
+
+            argumentNotEmptyArray: function (arg, name) {
+                if (!(arg instanceof Array) || !arg.length)
+                    throw new Error("The argument '" + name + "' must be a not empty array");
+            },
+
+            argumentOfType: function (arg, type, name) {
+                if (!(arg instanceof type))
+                    throw new Error("The argument '" + name + "' type doesn't match");
+            },
+
+            isNull: function (arg) {
+                return (arg === null || arg === undefined);
+            },
+
+            isPrimitive: function (arg) {
+                return (arg === null || arg === undefined || typeof (arg) === "string" ||
+                    typeof (arg) === "number" || typeof (arg) === "boolean");
+            },
+
+            isInteger: function (arg) {
+                return parseInt(arg) === arg;
+            },
+
+            isNumber: function (arg) {
+                return parseFloat(arg) === arg;
+            },
+
+            isString: function (val) {
+                return typeof (val) == "string" || val instanceof String;
+            },
+
+            isNullOrEmptyString: function (str) {
+                if (str === null || str === undefined ||
+                    ((typeof (str) == "string" || str instanceof String) && str.length === 0))
+                    return true;
+            },
+
+            isNotEmptyArray: function (arg) {
+                return (arg instanceof Array && arg.length > 0);
+            },
+
+            /**
+             * Выполняет метод для каждого элемента массива, останавливается, когда
+             * либо достигнут конец массива, либо функция <c>cb</c> вернула
+             * значение.
+             * 
+             * @param{Array | Object} obj массив элементов для просмотра
+             * @param{Function} cb функция, вызываемая для каждого элемента
+             * @param{Object} thisArg значение, которое будет передано в качестве
+             *                <c>this</c> в <c>cb</c>.
+             * @returns Результат вызова функции <c>cb</c>, либо <c>undefined</c>
+             *          если достигнут конец массива.
+             */
+            each: function (obj, cb, thisArg) {
+                safe.argumentNotNull(cb, "cb");
+                var i, x;
+                if (obj instanceof Array) {
+                    for (i = 0; i < obj.length; i++) {
+                        x = cb.call(thisArg, obj[i], i);
+                        if (x !== undefined)
+                            return x;
+                    }
+                } else {
+                    var keys = _keys(obj);
+                    for (i = 0; i < keys.length; i++) {
+                        var k = keys[i];
+                        x = cb.call(thisArg, obj[k], k);
+                        if (x !== undefined)
+                            return x;
+                    }
+                }
+            },
+
+            oid: function (obj) {
+                return this.isPrimitive(obj) ? undefined : obj[OID_FIELD] ||
+                    (_defineProperty(obj, OID_FIELD, {
+                        value: ++id,
+                        enumerable: false
+                    }) && id);
+            },
+
+            /**
+             * Копирует свойства одного объекта в другой.
+             * 
+             * @param{Any} dest объект в который нужно скопировать значения
+             * @param{Any} src источник из которого будут копироваться значения
+             * @tmpl{Object|Array} tmpl шаблон по которому будет происходить
+             *                     копирование. Если шаблон является массивом
+             *                     (список свойств), тогда значения этого массива
+             *                     являются именами свойсвт которые будут
+             *                     скопированы. Если шаблон является объектом (карта
+             *                     преобразования имен свойств src->dst), тогда
+             *                     копирование будет осуществляться только
+             *                     собственных свойств источника, присутсвующих в
+             *                     шаблоне, при этом значение свойства шаблона
+             *                     является именем свойства в которое будет
+             *                     произведено коприрование
+             */
+            mixin: function (dest, src, tmpl) {
+                safe.argumentNotNull(dest, "dest");
+                if (!src)
+                    return dest;
+
+                var keys, i, p;
+                if (arguments.length < 3) {
+                    keys = _keys(src);
+                    for (i = 0; i < keys.length; i++) {
+                        p = keys[i];
+                        dest[p] = src[p];
+                    }
+                } else {
+                    if (tmpl instanceof Array) {
+                        for (i = 0; i < tmpl.length; i++) {
+                            p = tmpl[i];
+                            if (p in src)
+                                dest[p] = src[p];
+                        }
+
+                    } else {
+                        keys = _keys(src);
+                        for (i = 0; i < keys.length; i++) {
+                            p = keys[i];
+                            if (p in tmpl)
+                                dest[tmpl[p]] = src[p];
+                        }
+                    }
+                }
+                return dest;
+            },
+
+            create: _create,
+
+            delegate: function (target, method) {
+                if (!(method instanceof Function)) {
+                    this.argumentNotNull(target, "target");
+                    method = target[method];
+                }
+
+                if (!(method instanceof Function))
+                    throw new Error("'method' argument must be a Function or a method name");
+
+                return function () {
+                    return method.apply(target, arguments);
+                };
+            },
+
+            /**
+             * Для каждого элемента массива вызывает указанную функцию и сохраняет
+             * возвращенное значение в массиве результатов.
+             * 
+             * @remarks cb может выполняться асинхронно, при этом одновременно будет
+             *          только одна операция.
+             * 
+             * @async
+             */
+            pmap: function (items, cb) {
+                safe.argumentNotNull(cb, "cb");
+
+                if (items && items.then instanceof Function)
+                    return items.then(function (data) {
+                        return safe.pmap(data, cb);
+                    });
+
+                if (safe.isNull(items) || !items.length)
+                    return items;
+
+                var i = 0,
+                    result = [];
+
+                function next() {
+                    var r, ri;
+
+                    function chain(x) {
+                        result[ri] = x;
+                        return next();
+                    }
+
+                    while (i < items.length) {
+                        r = cb(items[i], i);
+                        ri = i;
+                        i++;
+                        if (r && r.then) {
+                            return r.then(chain);
+                        } else {
+                            result[ri] = r;
+                        }
+                    }
+                    return result;
+                }
+
+                return next();
+            },
+
+            /**
+             * Для каждого элемента массива вызывает указанную функцию, результаты
+             * не сохраняются
+             * 
+             * @remarks cb может выполняться асинхронно, при этом одновременно будет
+             *          только одна операция.
+             * @async
+             */
+            pfor: function (items, cb) {
+                safe.argumentNotNull(cb, "cb");
+
+                if (items && items.then instanceof Function)
+                    return items.then(function (data) {
+                        return safe.pmap(data, cb);
+                    });
+
+                if (safe.isNull(items) || !items.length)
+                    return items;
+
+                var i = 0;
+
+                function next() {
+                    while (i < items.length) {
+                        var r = cb(items[i], i);
+                        i++;
+                        if (r && r.then)
+                            return r.then(next);
+                    }
+                }
+
+                return next();
+            },
+
+            /**
+             * Выбирает первый элемент из последовательности, или обещания, если в
+             * качестве параметра используется обещание, оно должно вернуть массив.
+             * 
+             * @param{Function} cb обработчик результата, ему будет передан первый
+             *                  элемент последовательности в случае успеха
+             * @param{Fucntion} err обработчик исключения, если массив пустой, либо
+             *                  не массив
+             * 
+             * @remarks Если не указаны ни cb ни err, тогда функция вернет либо
+             *          обещание, либо первый элемент.
+             * @async
+             */
+            first: function (sequence, cb, err) {
+                if (sequence) {
+                    if (sequence.then instanceof Function) {
+                        return sequence.then(function (res) {
+                            return safe.first(res, cb, err);
+                        }, err);
+                    } else if (sequence && "length" in sequence) {
+                        if (sequence.length === 0) {
+                            if (err)
+                                return err(new Error("The sequence is empty"));
+                            else
+                                throw new Error("The sequence is empty");
+                        }
+                        return cb ? cb(sequence[0]) : sequence[0];
+                    }
+                }
+                
+                if (err)
+                    return err(new Error("The sequence is required"));
+                else
+                    throw new Error("The sequence is required");
+            }
+        };
+
+        return safe;
+    });
\ No newline at end of file