| 'use strict'; |
| |
| /* ! |
| * Chai - pathval utility |
| * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com> |
| * @see https://github.com/logicalparadox/filtr |
| * MIT Licensed |
| */ |
| |
| /** |
| * ### .hasProperty(object, name) |
| * |
| * This allows checking whether an object has own |
| * or inherited from prototype chain named property. |
| * |
| * Basically does the same thing as the `in` |
| * operator but works properly with null/undefined values |
| * and other primitives. |
| * |
| * var obj = { |
| * arr: ['a', 'b', 'c'] |
| * , str: 'Hello' |
| * } |
| * |
| * The following would be the results. |
| * |
| * hasProperty(obj, 'str'); // true |
| * hasProperty(obj, 'constructor'); // true |
| * hasProperty(obj, 'bar'); // false |
| * |
| * hasProperty(obj.str, 'length'); // true |
| * hasProperty(obj.str, 1); // true |
| * hasProperty(obj.str, 5); // false |
| * |
| * hasProperty(obj.arr, 'length'); // true |
| * hasProperty(obj.arr, 2); // true |
| * hasProperty(obj.arr, 3); // false |
| * |
| * @param {Object} object |
| * @param {String|Symbol} name |
| * @returns {Boolean} whether it exists |
| * @namespace Utils |
| * @name hasProperty |
| * @api public |
| */ |
| |
| function hasProperty(obj, name) { |
| if (typeof obj === 'undefined' || obj === null) { |
| return false; |
| } |
| |
| // The `in` operator does not work with primitives. |
| return name in Object(obj); |
| } |
| |
| /* ! |
| * ## parsePath(path) |
| * |
| * Helper function used to parse string object |
| * paths. Use in conjunction with `internalGetPathValue`. |
| * |
| * var parsed = parsePath('myobject.property.subprop'); |
| * |
| * ### Paths: |
| * |
| * * Can be infinitely deep and nested. |
| * * Arrays are also valid using the formal `myobject.document[3].property`. |
| * * Literal dots and brackets (not delimiter) must be backslash-escaped. |
| * |
| * @param {String} path |
| * @returns {Object} parsed |
| * @api private |
| */ |
| |
| function parsePath(path) { |
| var str = path.replace(/([^\\])\[/g, '$1.['); |
| var parts = str.match(/(\\\.|[^.]+?)+/g); |
| return parts.map(function mapMatches(value) { |
| var regexp = /^\[(\d+)\]$/; |
| var mArr = regexp.exec(value); |
| var parsed = null; |
| if (mArr) { |
| parsed = { i: parseFloat(mArr[1]) }; |
| } else { |
| parsed = { p: value.replace(/\\([.\[\]])/g, '$1') }; |
| } |
| |
| return parsed; |
| }); |
| } |
| |
| /* ! |
| * ## internalGetPathValue(obj, parsed[, pathDepth]) |
| * |
| * Helper companion function for `.parsePath` that returns |
| * the value located at the parsed address. |
| * |
| * var value = getPathValue(obj, parsed); |
| * |
| * @param {Object} object to search against |
| * @param {Object} parsed definition from `parsePath`. |
| * @param {Number} depth (nesting level) of the property we want to retrieve |
| * @returns {Object|Undefined} value |
| * @api private |
| */ |
| |
| function internalGetPathValue(obj, parsed, pathDepth) { |
| var temporaryValue = obj; |
| var res = null; |
| pathDepth = (typeof pathDepth === 'undefined' ? parsed.length : pathDepth); |
| |
| for (var i = 0; i < pathDepth; i++) { |
| var part = parsed[i]; |
| if (temporaryValue) { |
| if (typeof part.p === 'undefined') { |
| temporaryValue = temporaryValue[part.i]; |
| } else { |
| temporaryValue = temporaryValue[part.p]; |
| } |
| |
| if (i === (pathDepth - 1)) { |
| res = temporaryValue; |
| } |
| } |
| } |
| |
| return res; |
| } |
| |
| /* ! |
| * ## internalSetPathValue(obj, value, parsed) |
| * |
| * Companion function for `parsePath` that sets |
| * the value located at a parsed address. |
| * |
| * internalSetPathValue(obj, 'value', parsed); |
| * |
| * @param {Object} object to search and define on |
| * @param {*} value to use upon set |
| * @param {Object} parsed definition from `parsePath` |
| * @api private |
| */ |
| |
| function internalSetPathValue(obj, val, parsed) { |
| var tempObj = obj; |
| var pathDepth = parsed.length; |
| var part = null; |
| // Here we iterate through every part of the path |
| for (var i = 0; i < pathDepth; i++) { |
| var propName = null; |
| var propVal = null; |
| part = parsed[i]; |
| |
| // If it's the last part of the path, we set the 'propName' value with the property name |
| if (i === (pathDepth - 1)) { |
| propName = typeof part.p === 'undefined' ? part.i : part.p; |
| // Now we set the property with the name held by 'propName' on object with the desired val |
| tempObj[propName] = val; |
| } else if (typeof part.p !== 'undefined' && tempObj[part.p]) { |
| tempObj = tempObj[part.p]; |
| } else if (typeof part.i !== 'undefined' && tempObj[part.i]) { |
| tempObj = tempObj[part.i]; |
| } else { |
| // If the obj doesn't have the property we create one with that name to define it |
| var next = parsed[i + 1]; |
| // Here we set the name of the property which will be defined |
| propName = typeof part.p === 'undefined' ? part.i : part.p; |
| // Here we decide if this property will be an array or a new object |
| propVal = typeof next.p === 'undefined' ? [] : {}; |
| tempObj[propName] = propVal; |
| tempObj = tempObj[propName]; |
| } |
| } |
| } |
| |
| /** |
| * ### .getPathInfo(object, path) |
| * |
| * This allows the retrieval of property info in an |
| * object given a string path. |
| * |
| * The path info consists of an object with the |
| * following properties: |
| * |
| * * parent - The parent object of the property referenced by `path` |
| * * name - The name of the final property, a number if it was an array indexer |
| * * value - The value of the property, if it exists, otherwise `undefined` |
| * * exists - Whether the property exists or not |
| * |
| * @param {Object} object |
| * @param {String} path |
| * @returns {Object} info |
| * @namespace Utils |
| * @name getPathInfo |
| * @api public |
| */ |
| |
| function getPathInfo(obj, path) { |
| var parsed = parsePath(path); |
| var last = parsed[parsed.length - 1]; |
| var info = { |
| parent: parsed.length > 1 ? internalGetPathValue(obj, parsed, parsed.length - 1) : obj, |
| name: last.p || last.i, |
| value: internalGetPathValue(obj, parsed), |
| }; |
| info.exists = hasProperty(info.parent, info.name); |
| |
| return info; |
| } |
| |
| /** |
| * ### .getPathValue(object, path) |
| * |
| * This allows the retrieval of values in an |
| * object given a string path. |
| * |
| * var obj = { |
| * prop1: { |
| * arr: ['a', 'b', 'c'] |
| * , str: 'Hello' |
| * } |
| * , prop2: { |
| * arr: [ { nested: 'Universe' } ] |
| * , str: 'Hello again!' |
| * } |
| * } |
| * |
| * The following would be the results. |
| * |
| * getPathValue(obj, 'prop1.str'); // Hello |
| * getPathValue(obj, 'prop1.att[2]'); // b |
| * getPathValue(obj, 'prop2.arr[0].nested'); // Universe |
| * |
| * @param {Object} object |
| * @param {String} path |
| * @returns {Object} value or `undefined` |
| * @namespace Utils |
| * @name getPathValue |
| * @api public |
| */ |
| |
| function getPathValue(obj, path) { |
| var info = getPathInfo(obj, path); |
| return info.value; |
| } |
| |
| /** |
| * ### .setPathValue(object, path, value) |
| * |
| * Define the value in an object at a given string path. |
| * |
| * ```js |
| * var obj = { |
| * prop1: { |
| * arr: ['a', 'b', 'c'] |
| * , str: 'Hello' |
| * } |
| * , prop2: { |
| * arr: [ { nested: 'Universe' } ] |
| * , str: 'Hello again!' |
| * } |
| * }; |
| * ``` |
| * |
| * The following would be acceptable. |
| * |
| * ```js |
| * var properties = require('tea-properties'); |
| * properties.set(obj, 'prop1.str', 'Hello Universe!'); |
| * properties.set(obj, 'prop1.arr[2]', 'B'); |
| * properties.set(obj, 'prop2.arr[0].nested.value', { hello: 'universe' }); |
| * ``` |
| * |
| * @param {Object} object |
| * @param {String} path |
| * @param {Mixed} value |
| * @api private |
| */ |
| |
| function setPathValue(obj, path, val) { |
| var parsed = parsePath(path); |
| internalSetPathValue(obj, val, parsed); |
| return obj; |
| } |
| |
| module.exports = { |
| hasProperty: hasProperty, |
| getPathInfo: getPathInfo, |
| getPathValue: getPathValue, |
| setPathValue: setPathValue, |
| }; |