1 | const debug = require('debug')('upward-js:ContextPath');
|
2 |
|
3 |
|
4 |
|
5 | const illegalPathChars = /(^[\.\[\]])|[^-\.\w\$\[\]\/]/;
|
6 | const contextPathCache = new Map();
|
7 | class 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 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 | toString() {
|
98 | return this._segments.join('.');
|
99 | }
|
100 | }
|
101 |
|
102 | ContextPath.root = new ContextPath([]);
|
103 |
|
104 | module.exports = ContextPath;
|