mantacode/safe-obj

View on GitHub
lib/index.js

Summary

Maintainability
A
3 hrs
Test Coverage
;(function(root) {

  var isObject = function isObject(obj) {
    // Implementation lifted from underscore
    return typeof obj === 'function' || typeof obj === 'object' && !!obj;
  };

  var isArray = function isArray(arr) {
    return Array.isArray ? Array.isArray(arr) : arr instanceof Array || (arr && arr.constructor && arr.constructor.name === 'Array');
  };

  var any = function any(list, func) {
    var i;
    var l = list.length;
    for (i = 0; i < l; i++) {
      if (func(list[i])) {
        return true;
      }
    }
    return false;
  };

  var every = function every(list, func) {
    var i;
    var l = list.length;
    for (i = 0; i < l; i++) {
      if (!func(list[i])) {
        return false;
      }
    }
    return true;
  };

  var isEqual = function isEqual(a, b) {
    if (a === b) {
      return true;
    }

    if (a.constructor !== b.constructor) {
      return false;
    }

    if (typeof a === 'object') {
      if (a instanceof Array) {
        if (a.length !== b.length) {
          return false;
        }
        var i;
        var l = a.length;
        for (i = 0; i < l; i++) {
          if (!isEqual(a[i], b[i])) {
            return false;
          }
        }
        return true;
      } else {
        for (var k in a) {
          if (!(k in b) || !isEqual(a[k], b[k])) {
            return false;
          }
        }
        return true;

      }
    } else {
      return false;
    }
  };

  var anyOrAll = function anyOrAll(method, _args) {
    var paths = [], obj = {};

    // args is of type Arguments
    var args = [].slice.call(_args);

    // Allow array or list of strings
    if (isArray(args[1])) {
      obj = args[0];
      paths = args[1];
    } else {
      paths = args;
      obj = paths.shift();
    }

    if (isObject(obj)) {
      return method(paths, function(path) {
        return _safe.safe(obj, path);
      });
    } else {
      return false;
    }
  };

  var _safe = {
    safe: function (obj, path, otherwise) {
      if (!path) {
        return otherwise;
      }
      obj = isObject(obj) ? obj : {};
      var props = path.split('.');
      if (props.length === 1) {
        if (typeof obj[props[0]] === 'undefined') {
          return otherwise;
        } else if (obj[props[0]] === null) {
          return typeof otherwise === 'undefined' ? null : otherwise;
        } else {
          return obj[props.shift()];
        }
      } else {
        var prop = props.shift();
        return isObject(obj[prop]) ? _safe.safe(obj[prop], props.join('.'), otherwise) : otherwise;
      }
    },

    expand: function (obj, path, thing) {
      if (!path || typeof thing === 'undefined') {
        return;
      }
      obj = isObject(obj) && obj !== null ? obj : {};
      var props = path.split('.');
      if (props.length === 1) {
        obj[props.shift()] = thing;
      } else {
        var prop = props.shift();
        if (!(prop in obj)) {
          obj[prop] = {};
        }
        _safe.expand(obj[prop], props.join('.'), thing);
      }
    },

    ensure: function (obj, path, disallowed, otherwise) {
      if (arguments.length === 3) {
        otherwise = disallowed;
        disallowed = [];
      }
      var current = _safe.safe(obj, path);
      if  (typeof current === 'undefined' || current === null || any(disallowed, function(item) { return isEqual(current, item); })) {
        _safe.expand(obj, path, otherwise);
      }
    },

    allOf: function() {
      return anyOrAll.apply(null, [every].concat(arguments));
    },

    anyOf: function() {
      return anyOrAll.apply(null, [any].concat(arguments));
    },

    noneOf: function() {
      return !_safe.anyOf.apply(null, arguments);
    }
  };

  if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      module.exports = _safe;
    }
    exports._safe = _safe;
  }
  
  root._ = root._ || {};
  root._._safe = _safe;
})(this);