@lumjs/core 1.38.4 → 1.38.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/context.js +14 -12
- package/lib/env.js +937 -0
- package/lib/index.js +19 -25
- package/lib/meta.js +8 -14
- package/lib/node/index.js +14 -0
- package/lib/{modules.js → node/modules.js} +35 -6
- package/lib/node/package.js +107 -0
- package/lib/obj/apply.js +237 -29
- package/lib/obj/copyall.js +14 -20
- package/lib/obj/copyprops.js +6 -386
- package/lib/obj/cp.js +4 -1370
- package/lib/obj/cycle.js +182 -0
- package/lib/obj/cycle2.js +122 -0
- package/lib/obj/index.js +4 -4
- package/lib/obj/ns/formats.js +300 -0
- package/lib/obj/{ns.js → ns/index.js} +160 -37
- package/lib/objectid.js +236 -71
- package/lib/state.js +30 -45
- package/lib/types/index.js +1 -1
- package/package.json +8 -3
- /package/lib/{types → obj}/owncount.js +0 -0
package/lib/obj/cycle.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/*
|
|
2
|
+
cycle.js
|
|
3
|
+
2021-05-31
|
|
4
|
+
|
|
5
|
+
Public Domain.
|
|
6
|
+
|
|
7
|
+
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
|
|
8
|
+
|
|
9
|
+
This code should be minified before deployment.
|
|
10
|
+
See https://www.crockford.com/jsmin.html
|
|
11
|
+
|
|
12
|
+
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
|
|
13
|
+
NOT CONTROL.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// The file uses the WeakMap feature of ES6.
|
|
17
|
+
|
|
18
|
+
/*jslint eval */
|
|
19
|
+
|
|
20
|
+
/*property
|
|
21
|
+
$ref, decycle, forEach, get, indexOf, isArray, keys, length, push,
|
|
22
|
+
retrocycle, set, stringify, test
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
if (typeof JSON.decycle !== "function") {
|
|
26
|
+
JSON.decycle = function decycle(object, replacer) {
|
|
27
|
+
"use strict";
|
|
28
|
+
|
|
29
|
+
// Make a deep copy of an object or array, assuring that there is at most
|
|
30
|
+
// one instance of each object or array in the resulting structure. The
|
|
31
|
+
// duplicate references (which might be forming cycles) are replaced with
|
|
32
|
+
// an object of the form
|
|
33
|
+
|
|
34
|
+
// {"$ref": PATH}
|
|
35
|
+
|
|
36
|
+
// where the PATH is a JSONPath string that locates the first occurance.
|
|
37
|
+
|
|
38
|
+
// So,
|
|
39
|
+
|
|
40
|
+
// var a = [];
|
|
41
|
+
// a[0] = a;
|
|
42
|
+
// return JSON.stringify(JSON.decycle(a));
|
|
43
|
+
|
|
44
|
+
// produces the string '[{"$ref":"$"}]'.
|
|
45
|
+
|
|
46
|
+
// If a replacer function is provided, then it will be called for each value.
|
|
47
|
+
// A replacer function receives a value and returns a replacement value.
|
|
48
|
+
|
|
49
|
+
// JSONPath is used to locate the unique object. $ indicates the top level of
|
|
50
|
+
// the object or array. [NUMBER] or [STRING] indicates a child element or
|
|
51
|
+
// property.
|
|
52
|
+
|
|
53
|
+
var objects = new WeakMap(); // object to path mappings
|
|
54
|
+
|
|
55
|
+
return (function derez(value, path) {
|
|
56
|
+
|
|
57
|
+
// The derez function recurses through the object, producing the deep copy.
|
|
58
|
+
|
|
59
|
+
var old_path; // The path of an earlier occurance of value
|
|
60
|
+
var nu; // The new object or array
|
|
61
|
+
|
|
62
|
+
// If a replacer function was provided, then call it to get a replacement value.
|
|
63
|
+
|
|
64
|
+
if (replacer !== undefined) {
|
|
65
|
+
value = replacer(value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// typeof null === "object", so go on if this value is really an object but not
|
|
69
|
+
// one of the weird builtin objects.
|
|
70
|
+
|
|
71
|
+
if (
|
|
72
|
+
typeof value === "object"
|
|
73
|
+
&& value !== null
|
|
74
|
+
&& !(value instanceof Boolean)
|
|
75
|
+
&& !(value instanceof Date)
|
|
76
|
+
&& !(value instanceof Number)
|
|
77
|
+
&& !(value instanceof RegExp)
|
|
78
|
+
&& !(value instanceof String)
|
|
79
|
+
) {
|
|
80
|
+
|
|
81
|
+
// If the value is an object or array, look to see if we have already
|
|
82
|
+
// encountered it. If so, return a {"$ref":PATH} object. This uses an
|
|
83
|
+
// ES6 WeakMap.
|
|
84
|
+
|
|
85
|
+
old_path = objects.get(value);
|
|
86
|
+
if (old_path !== undefined) {
|
|
87
|
+
return {$ref: old_path};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Otherwise, accumulate the unique value and its path.
|
|
91
|
+
|
|
92
|
+
objects.set(value, path);
|
|
93
|
+
|
|
94
|
+
// If it is an array, replicate the array.
|
|
95
|
+
|
|
96
|
+
if (Array.isArray(value)) {
|
|
97
|
+
nu = [];
|
|
98
|
+
value.forEach(function (element, i) {
|
|
99
|
+
nu[i] = derez(element, path + "[" + i + "]");
|
|
100
|
+
});
|
|
101
|
+
} else {
|
|
102
|
+
|
|
103
|
+
// If it is an object, replicate the object.
|
|
104
|
+
|
|
105
|
+
nu = {};
|
|
106
|
+
Object.keys(value).forEach(function (name) {
|
|
107
|
+
nu[name] = derez(
|
|
108
|
+
value[name],
|
|
109
|
+
path + "[" + JSON.stringify(name) + "]"
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return nu;
|
|
114
|
+
}
|
|
115
|
+
return value;
|
|
116
|
+
}(object, "$"));
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if (typeof JSON.retrocycle !== "function") {
|
|
122
|
+
JSON.retrocycle = function retrocycle($) {
|
|
123
|
+
"use strict";
|
|
124
|
+
|
|
125
|
+
// Restore an object that was reduced by decycle. Members whose values are
|
|
126
|
+
// objects of the form
|
|
127
|
+
// {$ref: PATH}
|
|
128
|
+
// are replaced with references to the value found by the PATH. This will
|
|
129
|
+
// restore cycles. The object will be mutated.
|
|
130
|
+
|
|
131
|
+
// The eval function is used to locate the values described by a PATH. The
|
|
132
|
+
// root object is kept in a $ variable. A regular expression is used to
|
|
133
|
+
// assure that the PATH is extremely well formed. The regexp contains nested
|
|
134
|
+
// * quantifiers. That has been known to have extremely bad performance
|
|
135
|
+
// problems on some browsers for very long strings. A PATH is expected to be
|
|
136
|
+
// reasonably short. A PATH is allowed to belong to a very restricted subset of
|
|
137
|
+
// Goessner's JSONPath.
|
|
138
|
+
|
|
139
|
+
// So,
|
|
140
|
+
// var s = '[{"$ref":"$"}]';
|
|
141
|
+
// return JSON.retrocycle(JSON.parse(s));
|
|
142
|
+
// produces an array containing a single element which is the array itself.
|
|
143
|
+
|
|
144
|
+
var px = /^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\(?:[\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*")\])*$/;
|
|
145
|
+
|
|
146
|
+
(function rez(value) {
|
|
147
|
+
|
|
148
|
+
// The rez function walks recursively through the object looking for $ref
|
|
149
|
+
// properties. When it finds one that has a value that is a path, then it
|
|
150
|
+
// replaces the $ref object with a reference to the value that is found by
|
|
151
|
+
// the path.
|
|
152
|
+
|
|
153
|
+
if (value && typeof value === "object") {
|
|
154
|
+
if (Array.isArray(value)) {
|
|
155
|
+
value.forEach(function (element, i) {
|
|
156
|
+
if (typeof element === "object" && element !== null) {
|
|
157
|
+
var path = element.$ref;
|
|
158
|
+
if (typeof path === "string" && px.test(path)) {
|
|
159
|
+
value[i] = eval(path);
|
|
160
|
+
} else {
|
|
161
|
+
rez(element);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
} else {
|
|
166
|
+
Object.keys(value).forEach(function (name) {
|
|
167
|
+
var item = value[name];
|
|
168
|
+
if (typeof item === "object" && item !== null) {
|
|
169
|
+
var path = item.$ref;
|
|
170
|
+
if (typeof path === "string" && px.test(path)) {
|
|
171
|
+
value[name] = eval(path);
|
|
172
|
+
} else {
|
|
173
|
+
rez(item);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}($));
|
|
180
|
+
return $;
|
|
181
|
+
};
|
|
182
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TODO: replace cycle.js with this once its finished.
|
|
4
|
+
|
|
5
|
+
const { isObj } = require('../types');
|
|
6
|
+
|
|
7
|
+
const isCache = v => isObj(v)
|
|
8
|
+
&& typeof v.get === 'function'
|
|
9
|
+
&& typeof v.set === 'function';
|
|
10
|
+
|
|
11
|
+
const isRegularObj = v => isObj(v)
|
|
12
|
+
&& !(value instanceof Boolean)
|
|
13
|
+
&& !(value instanceof Date)
|
|
14
|
+
&& !(value instanceof Number)
|
|
15
|
+
&& !(value instanceof RegExp)
|
|
16
|
+
&& !(value instanceof String);
|
|
17
|
+
|
|
18
|
+
const DEF_ARGS = ['key','value'];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* An extended version of Douglas Crockford's JSON.decycle() function.
|
|
22
|
+
* @param {object} object - Object to be decycled.
|
|
23
|
+
*/
|
|
24
|
+
function decycle(object, opts={})
|
|
25
|
+
{
|
|
26
|
+
if (typeof opts === 'function')
|
|
27
|
+
{
|
|
28
|
+
opts = {
|
|
29
|
+
cache: new WeakMap(),
|
|
30
|
+
replacer: opts,
|
|
31
|
+
replacerArgs: DEF_ARGS.slice(),
|
|
32
|
+
weak: true
|
|
33
|
+
};
|
|
34
|
+
opts.ctx = {opts}; // speaking of circular references...
|
|
35
|
+
}
|
|
36
|
+
else if (isObj(opts))
|
|
37
|
+
{
|
|
38
|
+
if (!isCache(opts.cache))
|
|
39
|
+
{
|
|
40
|
+
opts.cache = (opts.weak ?? true) ? new WeakMap() : new Map();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!isObj(opts.ctx))
|
|
44
|
+
{
|
|
45
|
+
opts.ctx = {};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
opts.ctx.opts = opts; // there it is again...
|
|
49
|
+
|
|
50
|
+
if (typeof opts.replacerArgs === 'string')
|
|
51
|
+
{
|
|
52
|
+
opts.replacerArgs = [opts.replacerArgs];
|
|
53
|
+
}
|
|
54
|
+
else if (!Array.isArray(opts.replacerArgs))
|
|
55
|
+
{
|
|
56
|
+
opts.replacerArgs = DEF_ARGS.slice();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else
|
|
60
|
+
{
|
|
61
|
+
throw new TypeError("invalid options");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const replacer = (typeof opts.replacer === 'function')
|
|
65
|
+
? opts.replacer
|
|
66
|
+
: null;
|
|
67
|
+
|
|
68
|
+
opts.ctx.target = object;
|
|
69
|
+
|
|
70
|
+
return (function derez(value, path, key)
|
|
71
|
+
{
|
|
72
|
+
if (replacer)
|
|
73
|
+
{
|
|
74
|
+
let args = [], namedArgs = {key,value,opts,path};
|
|
75
|
+
namedArgs.named = namedArgs;
|
|
76
|
+
|
|
77
|
+
for (let arg of opts.replacerArgs)
|
|
78
|
+
{
|
|
79
|
+
if (arg in namedArgs)
|
|
80
|
+
{
|
|
81
|
+
args.push(namedArgs[arg]);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
value = replacer.apply(opts.ctx, args);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (isRegularObj(value))
|
|
89
|
+
{
|
|
90
|
+
let oldPath = opts.cache.get(value);
|
|
91
|
+
if (oldPath !== undefined)
|
|
92
|
+
{
|
|
93
|
+
return {$ref: oldPath};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
opts.cache.set(value, path);
|
|
97
|
+
|
|
98
|
+
let nu;
|
|
99
|
+
if (Array.isArray(value))
|
|
100
|
+
{
|
|
101
|
+
nu = [];
|
|
102
|
+
value.forEach(function(elem, i)
|
|
103
|
+
{
|
|
104
|
+
nu[i] = derez(elem, path+`[${i}]`, i);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
else
|
|
108
|
+
{
|
|
109
|
+
nu = {};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return nu;
|
|
113
|
+
}
|
|
114
|
+
return value;
|
|
115
|
+
|
|
116
|
+
})(object, '$', '');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function retrocycle(object, opts={})
|
|
120
|
+
{
|
|
121
|
+
// TODO: this
|
|
122
|
+
}
|
package/lib/obj/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @module @lumjs/core/obj
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const apply = require('./apply');
|
|
6
|
+
const {apply,ApplyInfo,ApplyOpts,ApplyWith} = require('./apply');
|
|
7
7
|
const assignd = require('./assignd');
|
|
8
8
|
const {copyAll,duplicateOne,duplicateAll} = require('./copyall');
|
|
9
9
|
const copyProps = require('./copyprops');
|
|
@@ -18,6 +18,7 @@ const {mergeNested,syncNested} = require('./merge');
|
|
|
18
18
|
const ns = require('./ns');
|
|
19
19
|
const cp = require('./cp');
|
|
20
20
|
const unlocked = require('./unlocked');
|
|
21
|
+
const ownCount = require('./owncount');
|
|
21
22
|
|
|
22
23
|
const
|
|
23
24
|
{
|
|
@@ -49,7 +50,6 @@ module.exports =
|
|
|
49
50
|
mergeNested, syncNested, copyProps, copyAll, ns, nsFactory,
|
|
50
51
|
getObjectPath, setObjectPath, delObjectPath, getNamespace, setNamespace,
|
|
51
52
|
getProperty, duplicateAll, duplicateOne, getMethods, signatureOf,
|
|
52
|
-
MethodFilter,
|
|
53
|
-
|
|
53
|
+
MethodFilter, flip, flipKeyVal, flipMap, unlocked, getPrototypesOf,
|
|
54
|
+
apply, ApplyInfo, ApplyOpts, ApplyWith, ownCount,
|
|
54
55
|
}
|
|
55
|
-
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A collection of path formats for the obj.ns* functions.
|
|
5
|
+
* @namespace module:@lumjs/core/obj.NS
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const {isObj} = require('../../types');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A format object for the obj.ns* functions.
|
|
12
|
+
* @typedef {object} module:@lumjs/core/obj.NS~Format
|
|
13
|
+
* @prop {function} canParse - Can this parse a path string?
|
|
14
|
+
* @prop {function} canStringify - Can this stringify a path array?
|
|
15
|
+
* @prop {function} parse - Parse a path string into a path array.
|
|
16
|
+
* @prop {function} stringify - Stringify a path array into a path string.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A test to see if a value is a format object.
|
|
21
|
+
* @param {mixed} v - Value to be tested
|
|
22
|
+
* @returns {boolean}
|
|
23
|
+
*/
|
|
24
|
+
const isFormat = v => isObj(v)
|
|
25
|
+
&& typeof v.canParse === 'function'
|
|
26
|
+
&& typeof v.canStringify === 'function'
|
|
27
|
+
&& typeof v.parse === 'function'
|
|
28
|
+
&& typeof v.stringify === 'function';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The simplest base format for the obj.ns* functions.
|
|
32
|
+
*
|
|
33
|
+
* This one has NO escapes, and there is no way to include the
|
|
34
|
+
* delimiter character (which is a single `'.'` in this format).
|
|
35
|
+
*
|
|
36
|
+
* Its methods are designed so that this can be used as a template
|
|
37
|
+
* for other simplistic formats, and indeed the Pointer format
|
|
38
|
+
* is an extension of this one.
|
|
39
|
+
*
|
|
40
|
+
* Using it as a template is simple:
|
|
41
|
+
*
|
|
42
|
+
* ```js
|
|
43
|
+
* const MyFormat = {
|
|
44
|
+
* // Begin by composing Simple properties.
|
|
45
|
+
* ...Simple,
|
|
46
|
+
*
|
|
47
|
+
* // Override any of the syntax properties.
|
|
48
|
+
* hasEscapes: true,
|
|
49
|
+
* join: '/',
|
|
50
|
+
* prefix: '/',
|
|
51
|
+
*
|
|
52
|
+
* // Add some filter methods for parse() and stringify().
|
|
53
|
+
* preParse(pathStr) {
|
|
54
|
+
* // Do something to process path string and return it.
|
|
55
|
+
* },
|
|
56
|
+
* postParse(part, index) {
|
|
57
|
+
* // Do something to process each path segment after parsing.
|
|
58
|
+
* // This is used as an array.map() callback.
|
|
59
|
+
* },
|
|
60
|
+
* preStringify(part, index) {
|
|
61
|
+
* // Process path segment before joining (an array.map() callback).
|
|
62
|
+
* },
|
|
63
|
+
* postStringify(pathStr) {
|
|
64
|
+
* // Process path string after joining.
|
|
65
|
+
* },
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* NOTE: Due to this format having no prefix its canParse() test
|
|
70
|
+
* always returns true, therefore it must be the last in a list
|
|
71
|
+
* of formats to test for.
|
|
72
|
+
*
|
|
73
|
+
* @alias module:@lumjs/core/obj.NS.Simple
|
|
74
|
+
*/
|
|
75
|
+
const Simple =
|
|
76
|
+
{
|
|
77
|
+
hasEscapes: false,
|
|
78
|
+
join: '.',
|
|
79
|
+
prefix: '',
|
|
80
|
+
suffix: '',
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Test if this format can handle a path string.
|
|
84
|
+
*
|
|
85
|
+
* This version tests if the path string starts with `this.prefix`.
|
|
86
|
+
* @param {string} pathStr
|
|
87
|
+
* @returns {boolean}
|
|
88
|
+
*/
|
|
89
|
+
canParse(pathStr)
|
|
90
|
+
{
|
|
91
|
+
return pathStr.startsWith(this.prefix);
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Test if this format can stringify a path array.
|
|
96
|
+
*
|
|
97
|
+
* This version will return true immediately if `this.hasEscapes` is true.
|
|
98
|
+
*
|
|
99
|
+
* Assuming `this.hasEscapes` is false, this will look at every part
|
|
100
|
+
* in the path array and check for the delimiter (`this.join`),
|
|
101
|
+
* and will return false if any part contains the delimiter.
|
|
102
|
+
* It returns true if none of the parts contain the delimiter.
|
|
103
|
+
*/
|
|
104
|
+
canStringify(pathArray)
|
|
105
|
+
{
|
|
106
|
+
if (this.hasEscapes)
|
|
107
|
+
{ // Joiner can be escaped.
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (let part of pathArray)
|
|
112
|
+
{
|
|
113
|
+
if (typeof part === 'string')
|
|
114
|
+
{
|
|
115
|
+
if (part.includes(this.join))
|
|
116
|
+
{
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// No dots found, we're good.
|
|
122
|
+
return true;
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Parse a path string into an array of path segments.
|
|
127
|
+
* @param {string} pathStr
|
|
128
|
+
* @returns {Array}
|
|
129
|
+
*/
|
|
130
|
+
parse(pathStr)
|
|
131
|
+
{
|
|
132
|
+
if (typeof this.preParse === 'function')
|
|
133
|
+
{
|
|
134
|
+
pathStr = this.preParse(pathStr);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let array = pathStr.split(this.join);
|
|
138
|
+
|
|
139
|
+
return ((typeof this.postParse === 'function')
|
|
140
|
+
? array.map((p,i,a) => this.postParse(p,i,a))
|
|
141
|
+
: array);
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Stringify an array of path segments into a path string.
|
|
146
|
+
* @param {Array} pathArray
|
|
147
|
+
* @returns {string}
|
|
148
|
+
*/
|
|
149
|
+
stringify(pathArray)
|
|
150
|
+
{
|
|
151
|
+
if (typeof this.preStringify === 'function')
|
|
152
|
+
{
|
|
153
|
+
pathArray = pathArray.map((p,i,a) => this.preStringify(p,i,a));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let pathStr = this.prefix + pathArray.join(this.join) + this.suffix;
|
|
157
|
+
|
|
158
|
+
return ((typeof this.postStringify === 'function')
|
|
159
|
+
? this.postStringify(pathStr)
|
|
160
|
+
: pathStr);
|
|
161
|
+
},
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* An extension of NS.Simple implementing a subset of JSONPointer.
|
|
166
|
+
*
|
|
167
|
+
* This supports the two basic escapes (`~0` for `'~'` and `~1` for `'/'`),
|
|
168
|
+
* and will be used by nsArray() if a path string starts with a `/` slash.
|
|
169
|
+
*
|
|
170
|
+
* @alias module:@lumjs/core/obj.NS.Pointer
|
|
171
|
+
*/
|
|
172
|
+
const Pointer =
|
|
173
|
+
{
|
|
174
|
+
...Simple,
|
|
175
|
+
|
|
176
|
+
escParse: [['~1','/'],['~0','~']],
|
|
177
|
+
escString: [['~','~0'],['/','~1']],
|
|
178
|
+
hasEscapes: true,
|
|
179
|
+
join: '/',
|
|
180
|
+
prefix: '/',
|
|
181
|
+
|
|
182
|
+
preParse(pathStr)
|
|
183
|
+
{
|
|
184
|
+
let strip = this.prefix || this.join;
|
|
185
|
+
if (pathStr.startsWith(strip))
|
|
186
|
+
{
|
|
187
|
+
pathStr = pathStr.substring(strip.length);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
strip = this.suffix || this.join;
|
|
191
|
+
if (pathStr.endsWith(strip))
|
|
192
|
+
{
|
|
193
|
+
pathStr = pathStr.substring(0, pathStr.length-strip.length);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return pathStr;
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
postParse(part)
|
|
200
|
+
{
|
|
201
|
+
for (let esc of this.escParse)
|
|
202
|
+
{
|
|
203
|
+
part = part.replaceAll(...esc);
|
|
204
|
+
}
|
|
205
|
+
return part;
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
preStringify(part)
|
|
209
|
+
{
|
|
210
|
+
for (let esc of this.escString)
|
|
211
|
+
{
|
|
212
|
+
part = part.replaceAll(...esc);
|
|
213
|
+
}
|
|
214
|
+
return part;
|
|
215
|
+
},
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const JP_PRE = /^\$[.\[]/;
|
|
219
|
+
const JP_BLOCK = /\.?\[(["'])?(?<spath>.*?)\1\]/g;
|
|
220
|
+
const D = '.';
|
|
221
|
+
const ESCD = '\b_\b';
|
|
222
|
+
const IS_INT = /^\d+$/;
|
|
223
|
+
const SAFE_PATH = /^\w+$/;
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* A namespace path format based on a minimal subset of JSONPath.
|
|
227
|
+
*
|
|
228
|
+
* Obviously given the use-cases, this only supports single paths
|
|
229
|
+
* with no extra query features. Basically the following are valid:
|
|
230
|
+
*
|
|
231
|
+
* - `$.hello.darkness.my_old_friend` (plain dotted paths).
|
|
232
|
+
* - `$["test.com"].expired` (a key with reserved characters in it).
|
|
233
|
+
* - `$.users[3]` (an array index).
|
|
234
|
+
*
|
|
235
|
+
* Anything more complicated than that isn't going to work.
|
|
236
|
+
*
|
|
237
|
+
* @alias module:@lumjs/core/obj.NS.JSPath
|
|
238
|
+
*/
|
|
239
|
+
const JSPath =
|
|
240
|
+
{
|
|
241
|
+
canParse: pathStr => pathStr.startsWith('$'),
|
|
242
|
+
|
|
243
|
+
// TODO: look into JSONPath escapes.
|
|
244
|
+
canStringify(pathArray)
|
|
245
|
+
{
|
|
246
|
+
for (let part of pathArray)
|
|
247
|
+
{
|
|
248
|
+
if (typeof part === 'string')
|
|
249
|
+
{
|
|
250
|
+
if (part.includes('"'))
|
|
251
|
+
{
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// No double-quotes found, we're good.
|
|
257
|
+
return true;
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
parse(pathStr)
|
|
261
|
+
{
|
|
262
|
+
pathStr = pathStr.replace(JP_PRE, '')
|
|
263
|
+
.replaceAll(JP_BLOCK, function(...args)
|
|
264
|
+
{
|
|
265
|
+
let {spath} = args[args.length-1] ?? '';
|
|
266
|
+
return D+spath.replaceAll(D, ESCD);
|
|
267
|
+
});
|
|
268
|
+
return pathStr.split(D).map((vpath) => vpath.replaceAll(ESCD, D));
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
stringify(pathArr)
|
|
272
|
+
{
|
|
273
|
+
let pathStr = '$';
|
|
274
|
+
for (let path of pathArr)
|
|
275
|
+
{
|
|
276
|
+
let isInt = IS_INT.test(path);
|
|
277
|
+
if (!isInt && SAFE_PATH.test(path))
|
|
278
|
+
{ // Can use dotted syntax.
|
|
279
|
+
pathStr += '.' + path;
|
|
280
|
+
}
|
|
281
|
+
else
|
|
282
|
+
{
|
|
283
|
+
let blockPath = isInt
|
|
284
|
+
? `[${path}]`
|
|
285
|
+
: `["${path}"]`;
|
|
286
|
+
pathStr += blockPath;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return pathStr;
|
|
290
|
+
},
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
module.exports =
|
|
294
|
+
{
|
|
295
|
+
Defaults: [Pointer, JSPath, Simple],
|
|
296
|
+
isFormat,
|
|
297
|
+
Simple,
|
|
298
|
+
Pointer,
|
|
299
|
+
JSPath,
|
|
300
|
+
}
|