Files
polymer/lib/utils/path.js
Peter Burns 658d1cf742 Fix a grab bag of closure compiler warnings.
This fixes all warnings in about half of the remaining files with warnings.
2018-08-16 14:23:55 -07:00

256 lines
6.5 KiB
JavaScript

/**
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
import './boot.js';
/**
* Module with utilities for manipulating structured data path strings.
*
* @summary Module with utilities for manipulating structured data path strings.
*/
/**
* Returns true if the given string is a structured data path (has dots).
*
* Example:
*
* ```
* isPath('foo.bar.baz') // true
* isPath('foo') // false
* ```
*
* @param {string} path Path string
* @return {boolean} True if the string contained one or more dots
*/
export function isPath(path) {
return path.indexOf('.') >= 0;
}
/**
* Returns the root property name for the given path.
*
* Example:
*
* ```
* root('foo.bar.baz') // 'foo'
* root('foo') // 'foo'
* ```
*
* @param {string} path Path string
* @return {string} Root property name
*/
export function root(path) {
let dotIndex = path.indexOf('.');
if (dotIndex === -1) {
return path;
}
return path.slice(0, dotIndex);
}
/**
* Given `base` is `foo.bar`, `foo` is an ancestor, `foo.bar` is not
* Returns true if the given path is an ancestor of the base path.
*
* Example:
*
* ```
* isAncestor('foo.bar', 'foo') // true
* isAncestor('foo.bar', 'foo.bar') // false
* isAncestor('foo.bar', 'foo.bar.baz') // false
* ```
*
* @param {string} base Path string to test against.
* @param {string} path Path string to test.
* @return {boolean} True if `path` is an ancestor of `base`.
*/
export function isAncestor(base, path) {
// base.startsWith(path + '.');
return base.indexOf(path + '.') === 0;
}
/**
* Given `base` is `foo.bar`, `foo.bar.baz` is an descendant
*
* Example:
*
* ```
* isDescendant('foo.bar', 'foo.bar.baz') // true
* isDescendant('foo.bar', 'foo.bar') // false
* isDescendant('foo.bar', 'foo') // false
* ```
*
* @param {string} base Path string to test against.
* @param {string} path Path string to test.
* @return {boolean} True if `path` is a descendant of `base`.
*/
export function isDescendant(base, path) {
// path.startsWith(base + '.');
return path.indexOf(base + '.') === 0;
}
/**
* Replaces a previous base path with a new base path, preserving the
* remainder of the path.
*
* User must ensure `path` has a prefix of `base`.
*
* Example:
*
* ```
* translate('foo.bar', 'zot', 'foo.bar.baz') // 'zot.baz'
* ```
*
* @param {string} base Current base string to remove
* @param {string} newBase New base string to replace with
* @param {string} path Path to translate
* @return {string} Translated string
*/
export function translate(base, newBase, path) {
return newBase + path.slice(base.length);
}
/**
* @param {string} base Path string to test against
* @param {string} path Path string to test
* @return {boolean} True if `path` is equal to `base`
*/
export function matches(base, path) {
return (base === path) ||
isAncestor(base, path) ||
isDescendant(base, path);
}
/**
* Converts array-based paths to flattened path. String-based paths
* are returned as-is.
*
* Example:
*
* ```
* normalize(['foo.bar', 0, 'baz']) // 'foo.bar.0.baz'
* normalize('foo.bar.0.baz') // 'foo.bar.0.baz'
* ```
*
* @param {string | !Array<string|number>} path Input path
* @return {string} Flattened path
*/
export function normalize(path) {
if (Array.isArray(path)) {
let parts = [];
for (let i=0; i<path.length; i++) {
let args = path[i].toString().split('.');
for (let j=0; j<args.length; j++) {
parts.push(args[j]);
}
}
return parts.join('.');
} else {
return path;
}
}
/**
* Splits a path into an array of property names. Accepts either arrays
* of path parts or strings.
*
* Example:
*
* ```
* split(['foo.bar', 0, 'baz']) // ['foo', 'bar', '0', 'baz']
* split('foo.bar.0.baz') // ['foo', 'bar', '0', 'baz']
* ```
*
* @param {string | !Array<string|number>} path Input path
* @return {!Array<string>} Array of path parts
* @suppress {checkTypes}
*/
export function split(path) {
if (Array.isArray(path)) {
return normalize(path).split('.');
}
return path.toString().split('.');
}
/**
* Reads a value from a path. If any sub-property in the path is `undefined`,
* this method returns `undefined` (will never throw.
*
* @param {Object} root Object from which to dereference path from
* @param {string | !Array<string|number>} path Path to read
* @param {Object=} info If an object is provided to `info`, the normalized
* (flattened) path will be set to `info.path`.
* @return {*} Value at path, or `undefined` if the path could not be
* fully dereferenced.
*/
export function get(root, path, info) {
let prop = root;
let parts = split(path);
// Loop over path parts[0..n-1] and dereference
for (let i=0; i<parts.length; i++) {
if (!prop) {
return;
}
let part = parts[i];
prop = prop[part];
}
if (info) {
info.path = parts.join('.');
}
return prop;
}
/**
* Sets a value to a path. If any sub-property in the path is `undefined`,
* this method will no-op.
*
* @param {Object} root Object from which to dereference path from
* @param {string | !Array<string|number>} path Path to set
* @param {*} value Value to set to path
* @return {string | undefined} The normalized version of the input path
*/
export function set(root, path, value) {
let prop = root;
let parts = split(path);
let last = parts[parts.length-1];
if (parts.length > 1) {
// Loop over path parts[0..n-2] and dereference
for (let i=0; i<parts.length-1; i++) {
let part = parts[i];
prop = prop[part];
if (!prop) {
return;
}
}
// Set value to object at end of path
prop[last] = value;
} else {
// Simple property set
prop[path] = value;
}
return parts.join('.');
}
/**
* Returns true if the given string is a structured data path (has dots).
*
* This function is deprecated. Use `isPath` instead.
*
* Example:
*
* ```
* isDeep('foo.bar.baz') // true
* isDeep('foo') // false
* ```
*
* @deprecated
* @param {string} path Path string
* @return {boolean} True if the string contained one or more dots
*/
export const isDeep = isPath;