UNPKG

3.52 kBJavaScriptView Raw
1const debug = require('debug')('upward-js:ContextPath');
2// Tests for dot-separated strings of "word characters" or allowed characters / [ ] -
3// Slashes so that MIME type constants, like "text/html", can be legal values.
4// "Word characters" are [a-zA-Z0-9_].
5const illegalPathChars = /(^[\.\[\]])|[^-\.\w\$\[\]\/]/;
6const contextPathCache = new Map();
7class ContextPath {
8 static from(lookup) {
9 if (lookup instanceof ContextPath) {
10 return lookup;
11 }
12 if (typeof lookup !== 'string') {
13 throw new Error(
14 `Internal error: Cannot build ContextPath from non-string ${lookup}`
15 );
16 }
17 if (illegalPathChars.test(lookup)) {
18 throw new Error(
19 `Illegal context property name found: ${lookup}\nContext properties must be dot-separated strings, cannot begin with a dot and must contain word characters [a-zA-Z0-9_] or / [ ] -.`
20 );
21 }
22 if (contextPathCache.has(lookup)) {
23 return contextPathCache.get(lookup);
24 }
25 const segments = lookup.split('.');
26 const path = segments.reduce(
27 (parent, newSegment) => parent.extend(newSegment),
28 ContextPath.root
29 );
30 contextPathCache.set(lookup, path);
31 return path;
32 }
33 extend(newSegment) {
34 const fullPath = this._segments.concat(newSegment);
35 const fullPathString = fullPath.join('.');
36 let path = contextPathCache.get(fullPathString);
37 if (!path) {
38 path = new ContextPath(fullPath);
39 contextPathCache.set(fullPathString, path);
40 }
41 return path;
42 }
43 constructor(segments) {
44 this._segments = segments;
45 }
46 base() {
47 return this._segments[0];
48 }
49 getFrom(obj) {
50 let current = obj;
51 for (const segment of this._segments) {
52 if (Array.isArray(current)) {
53 const index = Number(segment);
54 if (!isNaN(index)) {
55 debug('array index %d yields %o', segment, current[index]);
56 if (current.length < index - 1) {
57 return '';
58 }
59 current = current[index];
60 } else {
61 throw new Error(
62 `Attempted non-integer lookup on a list: ${current} [${segment}]`
63 );
64 }
65 } else if (current === undefined || current === null) {
66 current = '';
67 break;
68 } else if (typeof current === 'object') {
69 current = current[segment];
70 } else {
71 break;
72 }
73 }
74 debug(
75 'traverse %j yielded %j from %j',
76 this._segments,
77 current,
78 obj[this.base()]
79 );
80 return current;
81 }
82 // contains(otherPath) {
83 // return this._segments.every((segment, i) => otherPath.keyAt(i) === i);
84 // }
85 // containsSegment(segment) {
86 // return this._segments.some(mySegment => mySegment === segment);
87 // }
88 // depth() {
89 // return this._segments.length;
90 // }
91 // keyAt(index) {
92 // return this._segments[index];
93 // }
94 // relative(ancestor) {
95 // return this._segments.slice(ancestor.depth());
96 // }
97 toString() {
98 return this._segments.join('.');
99 }
100}
101
102ContextPath.root = new ContextPath([]);
103
104module.exports = ContextPath;