@voxgig/sdkgen 4.5.3 → 4.6.0
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/bin/voxgig-sdkgen +1 -1
- package/dist/action/check.js +15 -0
- package/dist/action/check.js.map +1 -1
- package/dist/cmp/AgentGuide.js +1 -1
- package/dist/cmp/AgentGuide.js.map +1 -1
- package/dist/cmp/AgentGuideContent.d.ts +1 -1
- package/dist/cmp/AgentGuideContent.js +8 -2
- package/dist/cmp/AgentGuideContent.js.map +1 -1
- package/dist/cmp/Feature.js +17 -0
- package/dist/cmp/Feature.js.map +1 -1
- package/dist/cmp/FeatureDocs.d.ts +1 -1
- package/dist/cmp/FeatureDocs.js +6 -1
- package/dist/cmp/FeatureDocs.js.map +1 -1
- package/dist/cmp/ReadmeExplanation.js +3 -1
- package/dist/cmp/ReadmeExplanation.js.map +1 -1
- package/dist/cmp/ReadmeFeatures.js +1 -1
- package/dist/cmp/ReadmeFeatures.js.map +1 -1
- package/dist/cmp/ReadmeRefFeatures.js +1 -1
- package/dist/cmp/ReadmeRefFeatures.js.map +1 -1
- package/dist/helpers/applicability.d.ts +6 -0
- package/dist/helpers/applicability.js +103 -0
- package/dist/helpers/applicability.js.map +1 -0
- package/dist/helpers/collectDeps.js +4 -2
- package/dist/helpers/collectDeps.js.map +1 -1
- package/dist/helpers/serverVars.d.ts +2 -1
- package/dist/helpers/serverVars.js +17 -0
- package/dist/helpers/serverVars.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -2
- package/dist/sdkgen.js +9 -2
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.js +7 -1
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aon +39 -0
- package/package.json +1 -1
- package/project/.sdk/model/feature/feature-index.aon +1 -0
- package/project/.sdk/model/feature/secrets.aon +118 -0
- package/project/.sdk/model/target/ts.aon +8 -1
- package/project/.sdk/src/cmp/c/Config_c.ts +5 -1
- package/project/.sdk/src/cmp/cpp/Config_cpp.ts +5 -1
- package/project/.sdk/src/cmp/csharp/Config_csharp.ts +5 -1
- package/project/.sdk/src/cmp/dart/Config_dart.ts +5 -1
- package/project/.sdk/src/cmp/elixir/Config_elixir.ts +5 -1
- package/project/.sdk/src/cmp/elixir/Main_elixir.ts +5 -1
- package/project/.sdk/src/cmp/go/Config_go.ts +5 -1
- package/project/.sdk/src/cmp/go/Main_go.ts +6 -2
- package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +16 -3
- package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +16 -3
- package/project/.sdk/src/cmp/go/TestDirect_go.ts +28 -4
- package/project/.sdk/src/cmp/go/TestEntity_go.ts +43 -10
- package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +5 -5
- package/project/.sdk/src/cmp/java/Config_java.ts +22 -1
- package/project/.sdk/src/cmp/java/Main_java.ts +5 -1
- package/project/.sdk/src/cmp/java/ReadmeQuick_java.ts +17 -3
- package/project/.sdk/src/cmp/java/ReadmeTopQuick_java.ts +17 -3
- package/project/.sdk/src/cmp/java/TestDirect_java.ts +23 -4
- package/project/.sdk/src/cmp/java/TestEntity_java.ts +29 -4
- package/project/.sdk/src/cmp/js/Config_js.ts +5 -1
- package/project/.sdk/src/cmp/js/Main_js.ts +5 -1
- package/project/.sdk/src/cmp/js/Package_js.ts +4 -1
- package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +5 -1
- package/project/.sdk/src/cmp/lean/Config_lean.ts +5 -1
- package/project/.sdk/src/cmp/lua/Config_lua.ts +5 -1
- package/project/.sdk/src/cmp/lua/Main_lua.ts +5 -1
- package/project/.sdk/src/cmp/lua/Package_lua.ts +4 -1
- package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +5 -1
- package/project/.sdk/src/cmp/perl/Main_perl.ts +5 -1
- package/project/.sdk/src/cmp/php/Config_php.ts +5 -1
- package/project/.sdk/src/cmp/php/Main_php.ts +5 -1
- package/project/.sdk/src/cmp/py/Config_py.ts +5 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +5 -1
- package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +16 -3
- package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +16 -3
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +24 -5
- package/project/.sdk/src/cmp/py/TestEntity_py.ts +23 -3
- package/project/.sdk/src/cmp/rb/Config_rb.ts +5 -1
- package/project/.sdk/src/cmp/rb/Main_rb.ts +5 -1
- package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -1
- package/project/.sdk/src/cmp/rust/Main_rust.ts +5 -1
- package/project/.sdk/src/cmp/scala/Config_scala.ts +5 -1
- package/project/.sdk/src/cmp/scala/Main_scala.ts +5 -1
- package/project/.sdk/src/cmp/swift/Config_swift.ts +5 -1
- package/project/.sdk/src/cmp/ts/Config_ts.ts +5 -1
- package/project/.sdk/src/cmp/ts/Main_ts.ts +57 -1
- package/project/.sdk/src/cmp/ts/Package_ts.ts +4 -1
- package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +15 -3
- package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +18 -5
- package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +19 -5
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +67 -7
- package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +27 -4
- package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +9 -2
- package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +9 -2
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +7 -0
- package/project/.sdk/src/cmp/zig/Config_zig.ts +5 -1
- package/project/.sdk/tm/c/utility/make_options.c +22 -0
- package/project/.sdk/tm/clojure/src/sdk/core.clj +18 -1
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +23 -0
- package/project/.sdk/tm/csharp/test/CustomUtilityTest.cs +15 -0
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +23 -0
- package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +22 -0
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +19 -0
- package/project/.sdk/tm/go/core/context.go +1 -1
- package/project/.sdk/tm/go/feature/cost_feature.go +1 -1
- package/project/.sdk/tm/go/feature/paging_feature.go +5 -5
- package/project/.sdk/tm/go/feature/test_feature.go +12 -5
- package/project/.sdk/tm/go/test/custom_utility_test.go +15 -0
- package/project/.sdk/tm/go/test/feature_corpus_test.go +7 -0
- package/project/.sdk/tm/go/test/runner_test.go +46 -0
- package/project/.sdk/tm/go/test/struct_runner_test.go +7 -7
- package/project/.sdk/tm/go/test/struct_utility_test.go +63 -27
- package/project/.sdk/tm/go/utility/fetcher.go +2 -2
- package/project/.sdk/tm/go/utility/make_options.go +24 -5
- package/project/.sdk/tm/go/utility/make_point.go +1 -1
- package/project/.sdk/tm/go/utility/make_spec.go +17 -1
- package/project/.sdk/tm/go/utility/prepare_auth.go +1 -1
- package/project/.sdk/tm/go/utility/struct/voxgigstruct.go +370 -307
- package/project/.sdk/tm/go/utility/transform_request.go +29 -1
- package/project/.sdk/tm/go/utility/transform_response.go +20 -1
- package/project/.sdk/tm/java/test/CustomUtilityTest.java +10 -0
- package/project/.sdk/tm/java/test/FeatureCorpusTest.java +7 -0
- package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +6 -0
- package/project/.sdk/tm/java/test/RunnerSupport.java +43 -0
- package/project/.sdk/tm/java/utility/MakeOptions.java +84 -1
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +24 -0
- package/project/.sdk/tm/js/src/utility/StructUtility.js +2076 -1837
- package/project/.sdk/tm/js/test/feature/Corpus.test.js +9 -0
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +21 -0
- package/project/.sdk/tm/lean/src/SdkUtility.lean +12 -1
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +11 -0
- package/project/.sdk/tm/perl/utility/make_options.pm +18 -0
- package/project/.sdk/tm/php/test/FeatureCorpusTest.php +10 -1
- package/project/.sdk/tm/php/utility/MakeOptions.php +24 -0
- package/project/.sdk/tm/py/pkg/utility/make_options.py +20 -0
- package/project/.sdk/tm/py/test/runner.py +32 -0
- package/project/.sdk/tm/py/test/test_feature_corpus.py +10 -1
- package/project/.sdk/tm/rb/test/feature_corpus_test.rb +9 -1
- package/project/.sdk/tm/rb/utility/make_options.rb +17 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +25 -0
- package/project/.sdk/tm/scala/utility/Make.scala +17 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +21 -0
- package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +419 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +1221 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +381 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +151 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +42 -0
- package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +18 -0
- package/project/.sdk/tm/ts/src/utility/StructUtility.ts +1321 -710
- package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +9 -0
- package/project/.sdk/tm/ts/test/feature/secrets/Secrets.test.ts +764 -0
- package/project/.sdk/tm/ts/test/omni.test.ts +84 -0
- package/project/.sdk/tm/ts/test/omni.ts +86 -0
- package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +6 -2
- package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +2 -2
- package/project/.sdk/tm/ts/test/utility.ts +122 -0
- package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +692 -0
- package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +238 -0
- package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +188 -0
- package/project/.sdk/tm/ts/test/vendor/omni/index.ts +38 -0
- package/project/.sdk/tm/zig/core/utility.zig +23 -0
- package/project/sdkgen-package.json +2 -1
- package/src/action/check.ts +18 -0
- package/src/cmp/AgentGuide.ts +1 -1
- package/src/cmp/AgentGuideContent.ts +9 -2
- package/src/cmp/Feature.ts +19 -0
- package/src/cmp/FeatureDocs.ts +7 -1
- package/src/cmp/ReadmeExplanation.ts +4 -1
- package/src/cmp/ReadmeFeatures.ts +1 -1
- package/src/cmp/ReadmeRefFeatures.ts +1 -1
- package/src/helpers/applicability.ts +123 -0
- package/src/helpers/collectDeps.ts +5 -1
- package/src/helpers/serverVars.ts +19 -0
- package/src/sdkgen.ts +9 -1
- package/src/utility.ts +10 -1
- package/project/.sdk/tm/ts/test/runner.ts +0 -425
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
// VENDORED: @voxgig/struct 0.3.2 (javascript/src/struct.js)
|
|
2
|
+
// Source: https://github.com/voxgig/struct @ 94409354d0a60fda8098c83e290583b7dee76c84
|
|
3
|
+
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
1
4
|
/* Copyright (c) 2025-2026 Voxgig Ltd. MIT LICENSE. */
|
|
2
|
-
// VERSION: @voxgig/struct 0.
|
|
5
|
+
// VERSION: @voxgig/struct 0.3.2
|
|
3
6
|
/* Voxgig Struct
|
|
4
7
|
* =============
|
|
5
8
|
*
|
|
@@ -99,7 +102,7 @@ const S_VIZ = ': '
|
|
|
99
102
|
// Types
|
|
100
103
|
let t = 31
|
|
101
104
|
const T_any = (1 << t--) - 1
|
|
102
|
-
const T_noval = 1 << t
|
|
105
|
+
const T_noval = 1 << t-- // Means property absent, undefined. Also NOT a scalar!
|
|
103
106
|
const T_boolean = 1 << t--
|
|
104
107
|
const T_decimal = 1 << t--
|
|
105
108
|
const T_integer = 1 << t--
|
|
@@ -107,7 +110,7 @@ const T_number = 1 << t--
|
|
|
107
110
|
const T_string = 1 << t--
|
|
108
111
|
const T_function = 1 << t--
|
|
109
112
|
const T_symbol = 1 << t--
|
|
110
|
-
const T_null = 1 << t
|
|
113
|
+
const T_null = 1 << t-- // The actual JSON null value.
|
|
111
114
|
t -= 7
|
|
112
115
|
const T_list = 1 << t--
|
|
113
116
|
const T_map = 1 << t--
|
|
@@ -116,24 +119,32 @@ t -= 4
|
|
|
116
119
|
const T_scalar = 1 << t--
|
|
117
120
|
const T_node = 1 << t--
|
|
118
121
|
const TYPENAME = [
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
122
|
+
S_any,
|
|
123
|
+
S_nil,
|
|
124
|
+
S_boolean,
|
|
125
|
+
S_decimal,
|
|
126
|
+
S_integer,
|
|
127
|
+
S_number,
|
|
128
|
+
S_string,
|
|
129
|
+
S_function,
|
|
130
|
+
S_symbol,
|
|
131
|
+
S_null,
|
|
132
|
+
'',
|
|
133
|
+
'',
|
|
134
|
+
'',
|
|
135
|
+
'',
|
|
136
|
+
'',
|
|
137
|
+
'',
|
|
138
|
+
'',
|
|
139
|
+
S_list,
|
|
140
|
+
S_map,
|
|
141
|
+
S_instance,
|
|
142
|
+
'',
|
|
143
|
+
'',
|
|
144
|
+
'',
|
|
145
|
+
'',
|
|
146
|
+
S_scalar,
|
|
147
|
+
S_node,
|
|
137
148
|
]
|
|
138
149
|
// The standard undefined value for this language.
|
|
139
150
|
const NONE = undefined
|
|
@@ -141,85 +152,81 @@ const NONE = undefined
|
|
|
141
152
|
const SKIP = { '`$SKIP`': true }
|
|
142
153
|
const DELETE = { '`$DELETE`': true }
|
|
143
154
|
// Regular expression constants
|
|
144
|
-
const R_INTEGER_KEY = /^[-0-9]
|
|
145
|
-
const R_ESCAPE_REGEXP = /[.*+?^${}()|[\]\\]/g
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
const R_BT_ESCAPE = /\$BT/g; // Backtick escape sequence.
|
|
157
|
-
const R_DS_ESCAPE = /\$DS/g; // Dollar sign escape sequence.
|
|
158
|
-
const R_INJECTION_PARTIAL = /`([^`]+)`/g; // Partial string injection pattern.
|
|
155
|
+
const R_INTEGER_KEY = /^[-0-9]+$/ // Match integer keys (including <0).
|
|
156
|
+
const R_ESCAPE_REGEXP = /[.*+?^${}()|[\]\\]/g // Chars that need escaping in regexp.
|
|
157
|
+
const R_QUOTES = /"/g // Double quotes for removal.
|
|
158
|
+
const R_DOT = /\./g // Dots in path strings.
|
|
159
|
+
const R_CLONE_REF = /^`\$REF:([0-9]+)`$/ // Copy reference in cloning.
|
|
160
|
+
const R_META_PATH = /^([^$]+)\$([=~])(.+)$/ // Meta path syntax.
|
|
161
|
+
const R_DOUBLE_DOLLAR = /\$\$/g // Double dollar escape sequence.
|
|
162
|
+
const R_TRANSFORM_NAME = /`\$([A-Z]+)`/g // Transform command names.
|
|
163
|
+
const R_INJECTION_FULL = /^`(\$[A-Z]+|[^`]*)[0-9]*`$/ // Full string injection pattern.
|
|
164
|
+
const R_BT_ESCAPE = /\$BT/g // Backtick escape sequence.
|
|
165
|
+
const R_DS_ESCAPE = /\$DS/g // Dollar sign escape sequence.
|
|
166
|
+
const R_INJECTION_PARTIAL = /`([^`]+)`/g // Partial string injection pattern.
|
|
159
167
|
// Default max depth (for walk etc).
|
|
160
168
|
const MAXDEPTH = 32
|
|
161
169
|
// Return type string for narrowest type.
|
|
162
170
|
function typename(t) {
|
|
163
|
-
|
|
171
|
+
return getelem(TYPENAME, Math.clz32(t), TYPENAME[0])
|
|
164
172
|
}
|
|
165
173
|
// Get a defined value. Returns alt if val is undefined.
|
|
166
174
|
function getdef(val, alt) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
175
|
+
if (NONE === val) {
|
|
176
|
+
return alt
|
|
177
|
+
}
|
|
178
|
+
return val
|
|
171
179
|
}
|
|
172
180
|
// Value is a node - defined, and a map (hash) or list (array).
|
|
173
181
|
// NOTE: typescript
|
|
174
182
|
// things
|
|
175
183
|
function isnode(val) {
|
|
176
|
-
|
|
184
|
+
return null != val && S_object == typeof val
|
|
177
185
|
}
|
|
178
186
|
// Value is a defined map (hash) with string keys.
|
|
179
187
|
function ismap(val) {
|
|
180
|
-
|
|
188
|
+
return null != val && S_object == typeof val && !Array.isArray(val)
|
|
181
189
|
}
|
|
182
190
|
// Value is a defined list (array) with integer keys (indexes).
|
|
183
191
|
function islist(val) {
|
|
184
|
-
|
|
192
|
+
return Array.isArray(val)
|
|
185
193
|
}
|
|
186
194
|
// Value is a defined string (non-empty) or integer key.
|
|
187
195
|
function iskey(key) {
|
|
188
|
-
|
|
189
|
-
|
|
196
|
+
const keytype = typeof key
|
|
197
|
+
return (S_string === keytype && S_MT !== key) || S_number === keytype
|
|
190
198
|
}
|
|
191
199
|
// Check for an "empty" value - undefined, empty string, array, object.
|
|
192
200
|
function isempty(val) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
201
|
+
return (
|
|
202
|
+
null == val ||
|
|
203
|
+
S_MT === val ||
|
|
204
|
+
(Array.isArray(val) && 0 === val.length) ||
|
|
205
|
+
(S_object === typeof val && 0 === Object.keys(val).length)
|
|
206
|
+
)
|
|
196
207
|
}
|
|
197
208
|
// Value is a function.
|
|
198
209
|
function isfunc(val) {
|
|
199
|
-
|
|
210
|
+
return S_function === typeof val
|
|
200
211
|
}
|
|
201
212
|
// The integer size of the value. For arrays and strings, the length,
|
|
202
213
|
// for numbers, the integer part, for boolean, true is 1 and falso 0, for all other values, 0.
|
|
203
214
|
function size(val) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
return 0
|
|
222
|
-
}
|
|
215
|
+
if (islist(val)) {
|
|
216
|
+
return val.length
|
|
217
|
+
} else if (ismap(val)) {
|
|
218
|
+
return Object.keys(val).length
|
|
219
|
+
}
|
|
220
|
+
const valtype = typeof val
|
|
221
|
+
if (S_string == valtype) {
|
|
222
|
+
return val.length
|
|
223
|
+
} else if (S_number == typeof val) {
|
|
224
|
+
return Math.floor(val)
|
|
225
|
+
} else if (S_boolean == typeof val) {
|
|
226
|
+
return true === val ? 1 : 0
|
|
227
|
+
} else {
|
|
228
|
+
return 0
|
|
229
|
+
}
|
|
223
230
|
}
|
|
224
231
|
// Extract part of an array or string into a new value, from the start
|
|
225
232
|
// point to the end point. If no end is specified, extract to the
|
|
@@ -229,157 +236,151 @@ function size(val) {
|
|
|
229
236
|
// NOTE: input lists are not mutated by default. Use the mutate
|
|
230
237
|
// argument to mutate lists in place.
|
|
231
238
|
function slice(val, start, end, mutate) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
else if (S_string === typeof val) {
|
|
287
|
-
val = S_MT
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return val
|
|
239
|
+
if (S_number === typeof val) {
|
|
240
|
+
start = null == start || S_number !== typeof start ? Number.MIN_SAFE_INTEGER : start
|
|
241
|
+
end = (null == end || S_number !== typeof end ? Number.MAX_SAFE_INTEGER : end) - 1
|
|
242
|
+
return Math.min(Math.max(val, start), end)
|
|
243
|
+
}
|
|
244
|
+
const vlen = size(val)
|
|
245
|
+
if (null != end && null == start) {
|
|
246
|
+
start = 0
|
|
247
|
+
}
|
|
248
|
+
if (null != start) {
|
|
249
|
+
if (start < 0) {
|
|
250
|
+
end = vlen + start
|
|
251
|
+
if (end < 0) {
|
|
252
|
+
end = 0
|
|
253
|
+
}
|
|
254
|
+
start = 0
|
|
255
|
+
} else if (null != end) {
|
|
256
|
+
if (end < 0) {
|
|
257
|
+
end = vlen + end
|
|
258
|
+
if (end < 0) {
|
|
259
|
+
end = 0
|
|
260
|
+
}
|
|
261
|
+
} else if (vlen < end) {
|
|
262
|
+
end = vlen
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
end = vlen
|
|
266
|
+
}
|
|
267
|
+
if (vlen < start) {
|
|
268
|
+
start = vlen
|
|
269
|
+
}
|
|
270
|
+
if (-1 < start && start <= end && end <= vlen) {
|
|
271
|
+
if (islist(val)) {
|
|
272
|
+
if (mutate) {
|
|
273
|
+
for (let i = 0, j = start; j < end; i++, j++) {
|
|
274
|
+
val[i] = val[j]
|
|
275
|
+
}
|
|
276
|
+
val.length = end - start
|
|
277
|
+
} else {
|
|
278
|
+
val = val.slice(start, end)
|
|
279
|
+
}
|
|
280
|
+
} else if (S_string === typeof val) {
|
|
281
|
+
val = val.substring(start, end)
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
if (islist(val)) {
|
|
285
|
+
val = []
|
|
286
|
+
} else if (S_string === typeof val) {
|
|
287
|
+
val = S_MT
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return val
|
|
292
292
|
}
|
|
293
293
|
// String padding.
|
|
294
294
|
function pad(str, padding, padchar) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
295
|
+
str = S_string === typeof str ? str : stringify(str)
|
|
296
|
+
padding = null == padding ? 44 : padding
|
|
297
|
+
padchar = null == padchar ? S_SP : (padchar + S_SP)[0]
|
|
298
|
+
return -1 < padding ? str.padEnd(padding, padchar) : str.padStart(0 - padding, padchar)
|
|
299
299
|
}
|
|
300
300
|
// Determine the type of a value as a bit code.
|
|
301
301
|
function typify(value) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if ('Object' !== cname && 'Array' !== cname) {
|
|
340
|
-
return T_node | T_instance
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
return T_node | T_map
|
|
344
|
-
}
|
|
345
|
-
// Anything else (e.g. bigint) is considered T_any
|
|
346
|
-
return T_any
|
|
302
|
+
if (undefined === value) {
|
|
303
|
+
return T_noval
|
|
304
|
+
}
|
|
305
|
+
const typestr = typeof value
|
|
306
|
+
if (null === value) {
|
|
307
|
+
return T_scalar | T_null
|
|
308
|
+
} else if (S_number === typestr) {
|
|
309
|
+
if (Number.isInteger(value)) {
|
|
310
|
+
return T_scalar | T_number | T_integer
|
|
311
|
+
} else if (isNaN(value)) {
|
|
312
|
+
return T_noval
|
|
313
|
+
} else {
|
|
314
|
+
return T_scalar | T_number | T_decimal
|
|
315
|
+
}
|
|
316
|
+
} else if (S_string === typestr) {
|
|
317
|
+
return T_scalar | T_string
|
|
318
|
+
} else if (S_boolean === typestr) {
|
|
319
|
+
return T_scalar | T_boolean
|
|
320
|
+
} else if (S_function === typestr) {
|
|
321
|
+
return T_scalar | T_function
|
|
322
|
+
}
|
|
323
|
+
// For languages that have symbolic atoms.
|
|
324
|
+
else if (S_symbol === typestr) {
|
|
325
|
+
return T_scalar | T_symbol
|
|
326
|
+
} else if (Array.isArray(value)) {
|
|
327
|
+
return T_node | T_list
|
|
328
|
+
} else if (S_object === typestr) {
|
|
329
|
+
if (value.constructor instanceof Function) {
|
|
330
|
+
let cname = value.constructor.name
|
|
331
|
+
if ('Object' !== cname && 'Array' !== cname) {
|
|
332
|
+
return T_node | T_instance
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return T_node | T_map
|
|
336
|
+
}
|
|
337
|
+
// Anything else (e.g. bigint) is considered T_any
|
|
338
|
+
return T_any
|
|
347
339
|
}
|
|
348
340
|
// Get a list element. The key should be an integer, or a string
|
|
349
341
|
// that can parse to an integer only. Negative integers count from the end of the list.
|
|
350
342
|
function getelem(val, key, alt) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
//
|
|
343
|
+
let out = NONE
|
|
344
|
+
if (NONE === val || NONE === key) {
|
|
345
|
+
return alt
|
|
346
|
+
}
|
|
347
|
+
if (islist(val)) {
|
|
348
|
+
let nkey = parseInt(key)
|
|
349
|
+
if (Number.isInteger(nkey) && ('' + key).match(R_INTEGER_KEY)) {
|
|
350
|
+
if (nkey < 0) {
|
|
351
|
+
key = val.length + nkey
|
|
352
|
+
}
|
|
353
|
+
out = val[key]
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// Group A: a stored JSON null is treated as "no value".
|
|
357
|
+
if (null == out) {
|
|
358
|
+
return 0 < (T_function & typify(alt)) ? alt() : alt
|
|
359
|
+
}
|
|
360
|
+
return out
|
|
361
|
+
}
|
|
362
|
+
// Safely get a property of a node. Group A semantics (UNDEF_SPEC.md):
|
|
363
|
+
// a stored null is treated as "no value" and returns alt.
|
|
371
364
|
function getprop(val, key, alt) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
365
|
+
let out = alt
|
|
366
|
+
if (NONE === val || NONE === key) {
|
|
367
|
+
return alt
|
|
368
|
+
}
|
|
369
|
+
if (isnode(val)) {
|
|
370
|
+
out = val[key]
|
|
371
|
+
}
|
|
372
|
+
if (null == out) {
|
|
373
|
+
return alt
|
|
374
|
+
}
|
|
375
|
+
return out
|
|
376
|
+
}
|
|
377
|
+
// Internal raw read: returns the literally-stored value (including null),
|
|
378
|
+
// or NONE when absent. Group B callers use this to distinguish null from
|
|
379
|
+
// absent. Group A's getprop conflates them.
|
|
380
|
+
function _lookup(val, key) {
|
|
381
|
+
if (NONE === val || NONE === key) return NONE
|
|
382
|
+
if (isnode(val)) return val[key]
|
|
383
|
+
return NONE
|
|
383
384
|
}
|
|
384
385
|
// Convert different types of keys to string representation.
|
|
385
386
|
// String keys are returned as is.
|
|
@@ -387,38 +388,35 @@ function getprop(val, key, alt) {
|
|
|
387
388
|
// Floats are truncated to integers.
|
|
388
389
|
// Booleans, objects, arrays, null, undefined all return empty string.
|
|
389
390
|
function strkey(key = NONE) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
else if (0 < (T_boolean & t)) {
|
|
398
|
-
return S_MT
|
|
399
|
-
}
|
|
400
|
-
else if (0 < (T_number & t)) {
|
|
401
|
-
return key % 1 === 0 ? String(key) : String(Math.floor(key))
|
|
402
|
-
}
|
|
391
|
+
if (NONE === key) {
|
|
392
|
+
return S_MT
|
|
393
|
+
}
|
|
394
|
+
const t = typify(key)
|
|
395
|
+
if (0 < (T_string & t)) {
|
|
396
|
+
return key
|
|
397
|
+
} else if (0 < (T_boolean & t)) {
|
|
403
398
|
return S_MT
|
|
399
|
+
} else if (0 < (T_number & t)) {
|
|
400
|
+
return key % 1 === 0 ? String(key) : String(Math.floor(key))
|
|
401
|
+
}
|
|
402
|
+
return S_MT
|
|
404
403
|
}
|
|
405
404
|
// Sorted keys of a map, or indexes (as strings) of a list.
|
|
406
405
|
// Root utility - only uses language facilities.
|
|
407
406
|
function keysof(val) {
|
|
408
|
-
|
|
409
|
-
ismap(val) ? Object.keys(val).sort() : val.map((_n, i) => S_MT + i)
|
|
407
|
+
return !isnode(val) ? [] : ismap(val) ? Object.keys(val).sort() : val.map((_n, i) => S_MT + i)
|
|
410
408
|
}
|
|
411
409
|
// Value of property with name key in node val is defined.
|
|
412
410
|
// Root utility - only uses language facilities.
|
|
413
411
|
function haskey(val, key) {
|
|
414
|
-
|
|
412
|
+
return null != getprop(val, key)
|
|
415
413
|
}
|
|
416
414
|
function items(val, apply) {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
415
|
+
let out = keysof(val).map((k) => [k, val[k]])
|
|
416
|
+
if (null != apply) {
|
|
417
|
+
out = out.map(apply)
|
|
418
|
+
}
|
|
419
|
+
return out
|
|
422
420
|
}
|
|
423
421
|
// To replicate the array spread operator:
|
|
424
422
|
// a=1, b=[2,3], c=[4,5]
|
|
@@ -426,248 +424,337 @@ function items(val, apply) {
|
|
|
426
424
|
// flatten([a,b,[c]]) -> [1,2,3,[4,5]]
|
|
427
425
|
// NOTE: [c] ensures c is not expanded
|
|
428
426
|
function flatten(list, depth) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
427
|
+
if (!islist(list)) {
|
|
428
|
+
return list
|
|
429
|
+
}
|
|
430
|
+
return list.flat(getdef(depth, 1))
|
|
433
431
|
}
|
|
434
432
|
// Filter item values using check function.
|
|
435
433
|
function filter(val, check) {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}
|
|
434
|
+
let all = items(val)
|
|
435
|
+
let numall = size(all)
|
|
436
|
+
let out = []
|
|
437
|
+
for (let i = 0; i < numall; i++) {
|
|
438
|
+
if (check(all[i])) {
|
|
439
|
+
out.push(all[i][1])
|
|
443
440
|
}
|
|
444
|
-
|
|
441
|
+
}
|
|
442
|
+
return out
|
|
445
443
|
}
|
|
446
444
|
// Escape regular expression.
|
|
447
445
|
function escre(s) {
|
|
448
|
-
|
|
449
|
-
return replace(s, R_ESCAPE_REGEXP, '\\$&')
|
|
446
|
+
return re_replace(R_ESCAPE_REGEXP, s, '\\$&')
|
|
450
447
|
}
|
|
448
|
+
|
|
449
|
+
// Regex utility — uniform re_* API. See /REGEX_API.md.
|
|
450
|
+
// The JS port wraps the built-in RegExp; the dialect is the RE2 subset.
|
|
451
|
+
function re_compile(pattern, flags) {
|
|
452
|
+
if (pattern instanceof RegExp) return pattern
|
|
453
|
+
return new RegExp(pattern, flags)
|
|
454
|
+
}
|
|
455
|
+
function re_find(pattern, input) {
|
|
456
|
+
const re = pattern instanceof RegExp ? pattern : new RegExp(pattern)
|
|
457
|
+
return input.match(re)
|
|
458
|
+
}
|
|
459
|
+
function re_find_all(pattern, input) {
|
|
460
|
+
let re
|
|
461
|
+
if (pattern instanceof RegExp) {
|
|
462
|
+
re = pattern.global ? pattern : new RegExp(pattern.source, pattern.flags + 'g')
|
|
463
|
+
} else {
|
|
464
|
+
re = new RegExp(pattern, 'g')
|
|
465
|
+
}
|
|
466
|
+
const out = []
|
|
467
|
+
let m
|
|
468
|
+
while ((m = re.exec(input)) !== null) {
|
|
469
|
+
out.push(m)
|
|
470
|
+
if (m[0] === '') re.lastIndex++
|
|
471
|
+
}
|
|
472
|
+
return out
|
|
473
|
+
}
|
|
474
|
+
function re_replace(pattern, input, replacement) {
|
|
475
|
+
let re
|
|
476
|
+
if (pattern instanceof RegExp) {
|
|
477
|
+
re = pattern.global ? pattern : new RegExp(pattern.source, pattern.flags + 'g')
|
|
478
|
+
} else {
|
|
479
|
+
re = new RegExp(pattern, 'g')
|
|
480
|
+
}
|
|
481
|
+
if (typeof replacement === 'function') {
|
|
482
|
+
return input.replace(re, (...args) => replacement(args.slice(0, -2)))
|
|
483
|
+
}
|
|
484
|
+
return input.replace(re, replacement)
|
|
485
|
+
}
|
|
486
|
+
function re_test(pattern, input) {
|
|
487
|
+
const re = pattern instanceof RegExp ? pattern : new RegExp(pattern)
|
|
488
|
+
return re.test(input)
|
|
489
|
+
}
|
|
490
|
+
function re_escape(s) {
|
|
491
|
+
return escre(s)
|
|
492
|
+
}
|
|
493
|
+
|
|
451
494
|
// Escape URLs.
|
|
452
495
|
function escurl(s) {
|
|
453
|
-
|
|
454
|
-
|
|
496
|
+
s = null == s ? S_MT : s
|
|
497
|
+
return encodeURIComponent(s)
|
|
455
498
|
}
|
|
456
499
|
// Replace a search string (all), or a regexp, in a source string.
|
|
457
500
|
function replace(s, from, to) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
return rs.replace(from, to)
|
|
501
|
+
let rs = s
|
|
502
|
+
let ts = typify(s)
|
|
503
|
+
if (0 === (T_string & ts)) {
|
|
504
|
+
rs = stringify(s)
|
|
505
|
+
} else if (0 < ((T_noval | T_null) & ts)) {
|
|
506
|
+
rs = S_MT
|
|
507
|
+
} else {
|
|
508
|
+
rs = stringify(s)
|
|
509
|
+
}
|
|
510
|
+
return rs.replace(from, to)
|
|
470
511
|
}
|
|
471
512
|
// Concatenate url part strings, merging sep char as needed.
|
|
472
513
|
function join(arr, sep, url) {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
514
|
+
const sarr = size(arr)
|
|
515
|
+
const sepdef = getdef(sep, S_CM)
|
|
516
|
+
const sepre = 1 === size(sepdef) ? escre(sepdef) : NONE
|
|
517
|
+
const out = filter(
|
|
518
|
+
items(
|
|
519
|
+
// filter(arr, (n) => null != n[1] && S_MT !== n[1]),
|
|
520
|
+
filter(arr, (n) => 0 < (T_string & typify(n[1])) && S_MT !== n[1]),
|
|
521
|
+
(n) => {
|
|
479
522
|
let i = +n[0]
|
|
480
523
|
let s = n[1]
|
|
481
524
|
if (NONE !== sepre && S_MT !== sepre) {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
525
|
+
if (url && 0 === i) {
|
|
526
|
+
s = replace(s, RegExp(sepre + '+$'), S_MT)
|
|
527
|
+
return s
|
|
528
|
+
}
|
|
529
|
+
if (0 < i) {
|
|
530
|
+
s = replace(s, RegExp('^' + sepre + '+'), S_MT)
|
|
531
|
+
}
|
|
532
|
+
if (i < sarr - 1 || !url) {
|
|
533
|
+
s = replace(s, RegExp(sepre + '+$'), S_MT)
|
|
534
|
+
}
|
|
535
|
+
s = replace(
|
|
536
|
+
s,
|
|
537
|
+
RegExp('([^' + sepre + '])' + sepre + '+([^' + sepre + '])'),
|
|
538
|
+
'$1' + sepdef + '$2',
|
|
539
|
+
)
|
|
493
540
|
}
|
|
494
541
|
return s
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
542
|
+
},
|
|
543
|
+
),
|
|
544
|
+
(n) => S_MT !== n[1],
|
|
545
|
+
).join(sepdef)
|
|
546
|
+
return out
|
|
498
547
|
}
|
|
499
548
|
// Output JSON in a "standard" format, with 2 space indents, each property on a new line,
|
|
500
549
|
// and spaces after {[: and before ]}. Any "wierd" values (NaN, etc) are output as null.
|
|
501
550
|
// In general, the behaivor of of JavaScript's JSON.stringify(val,null,2) is followed.
|
|
502
551
|
function jsonify(val, flags) {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}
|
|
523
|
-
|
|
552
|
+
let str = S_null
|
|
553
|
+
if (null != val) {
|
|
554
|
+
try {
|
|
555
|
+
const indent = getprop(flags, 'indent', 2)
|
|
556
|
+
str = JSON.stringify(val, null, indent)
|
|
557
|
+
if (NONE === str) {
|
|
558
|
+
str = S_null
|
|
559
|
+
}
|
|
560
|
+
const offset = getprop(flags, 'offset', 0)
|
|
561
|
+
if (0 < offset) {
|
|
562
|
+
// Left offset entire indented JSON so that it aligns with surrounding code
|
|
563
|
+
// indented by offset. Assume first brace is on line with asignment, so not offset.
|
|
564
|
+
str =
|
|
565
|
+
'{\n' +
|
|
566
|
+
join(
|
|
567
|
+
items(slice(str.split('\n'), 1), (n) => pad(n[1], 0 - offset - size(n[1]))),
|
|
568
|
+
'\n',
|
|
569
|
+
)
|
|
570
|
+
}
|
|
571
|
+
} catch {
|
|
572
|
+
str = '__JSONIFY_FAILED__'
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return str
|
|
524
576
|
}
|
|
525
577
|
// Safely stringify a value for humans (NOT JSON!).
|
|
526
578
|
function stringify(val, maxlen, pretty) {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
return val
|
|
548
|
-
})
|
|
549
|
-
valstr = valstr.replace(R_QUOTES, S_MT)
|
|
550
|
-
}
|
|
551
|
-
catch (err) {
|
|
552
|
-
valstr = '__STRINGIFY_FAILED__'
|
|
579
|
+
let valstr = S_MT
|
|
580
|
+
pretty = !!pretty
|
|
581
|
+
if (NONE === val) {
|
|
582
|
+
return pretty ? '<>' : valstr
|
|
583
|
+
}
|
|
584
|
+
if (S_string === typeof val) {
|
|
585
|
+
valstr = val
|
|
586
|
+
} else {
|
|
587
|
+
try {
|
|
588
|
+
valstr = JSON.stringify(val, function (_key, val) {
|
|
589
|
+
if (val !== null && typeof val === 'object' && !Array.isArray(val)) {
|
|
590
|
+
// Emit map keys in sorted order without the intermediate key/tuple
|
|
591
|
+
// arrays items() would allocate for every node.
|
|
592
|
+
const keys = Object.keys(val).sort()
|
|
593
|
+
const sortedObj = {}
|
|
594
|
+
for (let i = 0; i < keys.length; i++) {
|
|
595
|
+
sortedObj[keys[i]] = val[keys[i]]
|
|
596
|
+
}
|
|
597
|
+
return sortedObj
|
|
553
598
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
599
|
+
return val
|
|
600
|
+
})
|
|
601
|
+
valstr = valstr.replace(R_QUOTES, S_MT)
|
|
602
|
+
} catch {
|
|
603
|
+
valstr = '__STRINGIFY_FAILED__'
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (null != maxlen && -1 < maxlen) {
|
|
607
|
+
let js = valstr.substring(0, maxlen)
|
|
608
|
+
valstr = maxlen < valstr.length ? js.substring(0, maxlen - 3) + '...' : valstr
|
|
609
|
+
}
|
|
610
|
+
if (pretty) {
|
|
611
|
+
// Indicate deeper JSON levels with different terminal colors (simplistic wrt strings).
|
|
612
|
+
let c = items(
|
|
613
|
+
[81, 118, 213, 39, 208, 201, 45, 190, 129, 51, 160, 121, 226, 33, 207, 69],
|
|
614
|
+
(n) => '\x1b[38;5;' + n[1] + 'm',
|
|
615
|
+
),
|
|
616
|
+
r = '\x1b[0m',
|
|
617
|
+
d = 0,
|
|
618
|
+
o = c[0],
|
|
619
|
+
t = o
|
|
620
|
+
for (const ch of valstr) {
|
|
621
|
+
if (ch === '{' || ch === '[') {
|
|
622
|
+
d++
|
|
623
|
+
o = c[d % c.length]
|
|
624
|
+
t += o + ch
|
|
625
|
+
} else if (ch === '}' || ch === ']') {
|
|
626
|
+
t += o + ch
|
|
627
|
+
d--
|
|
628
|
+
o = c[d % c.length]
|
|
629
|
+
} else {
|
|
630
|
+
t += o + ch
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return t + r
|
|
634
|
+
}
|
|
635
|
+
return valstr
|
|
580
636
|
}
|
|
581
637
|
// Build a human friendly path string.
|
|
582
638
|
function pathify(val, startin, endin) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
639
|
+
let pathstr = NONE
|
|
640
|
+
let path = islist(val)
|
|
641
|
+
? val
|
|
642
|
+
: S_string == typeof val
|
|
643
|
+
? [val]
|
|
644
|
+
: S_number == typeof val
|
|
645
|
+
? [val]
|
|
646
|
+
: NONE
|
|
647
|
+
const start = null == startin ? 0 : -1 < startin ? startin : 0
|
|
648
|
+
const end = null == endin ? 0 : -1 < endin ? endin : 0
|
|
649
|
+
if (NONE != path && 0 <= start) {
|
|
650
|
+
path = slice(path, start, path.length - end)
|
|
651
|
+
if (0 === path.length) {
|
|
652
|
+
pathstr = '<root>'
|
|
653
|
+
} else {
|
|
654
|
+
pathstr = join(
|
|
655
|
+
items(
|
|
656
|
+
filter(path, (n) => iskey(n[1])),
|
|
657
|
+
(n) => {
|
|
658
|
+
let p = n[1]
|
|
659
|
+
return S_number === typeof p ? S_MT + Math.floor(p) : p.replace(R_DOT, S_MT)
|
|
660
|
+
},
|
|
661
|
+
),
|
|
662
|
+
S_DT,
|
|
663
|
+
)
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (NONE === pathstr) {
|
|
667
|
+
pathstr = '<unknown-path' + (NONE === val ? S_MT : S_CN + stringify(val, 47)) + '>'
|
|
668
|
+
}
|
|
669
|
+
return pathstr
|
|
607
670
|
}
|
|
608
671
|
// Clone a JSON-like data structure.
|
|
609
672
|
// NOTE: function and instance values are copied, *not* cloned.
|
|
610
673
|
function clone(val) {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
674
|
+
const refs = []
|
|
675
|
+
// Copy (don't clone) functions and non-plain-object instances by reference.
|
|
676
|
+
// Inlines `0 < ((T_function | T_instance) & typify(v))` (functions, class
|
|
677
|
+
// instances, bigints) without a typify() call per value.
|
|
678
|
+
const replacer = (_k, v) => {
|
|
679
|
+
const t = typeof v
|
|
680
|
+
if (
|
|
681
|
+
S_function === t ||
|
|
682
|
+
'bigint' === t ||
|
|
683
|
+
(S_object === t &&
|
|
684
|
+
null !== v &&
|
|
685
|
+
!Array.isArray(v) &&
|
|
686
|
+
v.constructor instanceof Function &&
|
|
687
|
+
'Object' !== v.constructor.name &&
|
|
688
|
+
'Array' !== v.constructor.name)
|
|
689
|
+
) {
|
|
690
|
+
return (refs.push(v), '`$REF:' + (refs.length - 1) + '`')
|
|
691
|
+
}
|
|
692
|
+
return v
|
|
693
|
+
}
|
|
694
|
+
const reviver = (_k, v, m) =>
|
|
695
|
+
S_string === typeof v ? ((m = v.match(R_CLONE_REF)), m ? refs[m[1]] : v) : v
|
|
696
|
+
if (NONE === val) {
|
|
697
|
+
return NONE
|
|
698
|
+
}
|
|
699
|
+
const json = JSON.stringify(val, replacer)
|
|
700
|
+
// A reviver forces V8 off its native JSON.parse fast path (a large cost),
|
|
701
|
+
// and is only needed to swap `$REF:n` placeholders back for the captured
|
|
702
|
+
// functions/instances — so skip it when none were captured.
|
|
703
|
+
return 0 === refs.length ? JSON.parse(json) : JSON.parse(json, reviver)
|
|
619
704
|
}
|
|
620
705
|
// Define a JSON Object using function arguments.
|
|
621
706
|
function jm(...kv) {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
707
|
+
const kvsize = size(kv)
|
|
708
|
+
const o = {}
|
|
709
|
+
for (let i = 0; i < kvsize; i += 2) {
|
|
710
|
+
// Builders are Group B (value processing): preserve literal stored
|
|
711
|
+
// values, including null. Direct array index distinguishes "slot exists
|
|
712
|
+
// with value null" from "slot beyond end of kv".
|
|
713
|
+
let k = i < kv.length ? kv[i] : '$KEY' + i
|
|
714
|
+
k = 'string' === typeof k ? k : stringify(k)
|
|
715
|
+
o[k] = i + 1 < kv.length ? kv[i + 1] : null
|
|
716
|
+
}
|
|
717
|
+
return o
|
|
630
718
|
}
|
|
631
719
|
// Define a JSON Array using function arguments.
|
|
632
720
|
function jt(...v) {
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
640
|
-
// Safely delete a property from an object or array element.
|
|
721
|
+
const vsize = size(v)
|
|
722
|
+
const a = new Array(vsize)
|
|
723
|
+
for (let i = 0; i < vsize; i++) {
|
|
724
|
+
a[i] = getprop(v, i, null)
|
|
725
|
+
}
|
|
726
|
+
return a
|
|
727
|
+
}
|
|
728
|
+
// Safely delete a property from an object or array element.
|
|
641
729
|
// Undefined arguments and invalid keys are ignored.
|
|
642
730
|
// Returns the (possibly modified) parent.
|
|
643
731
|
// For objects, the property is deleted using the delete operator.
|
|
644
732
|
// For arrays, the element at the index is removed and remaining elements are shifted down.
|
|
645
733
|
// NOTE: parent list may be new list, thus update references.
|
|
646
734
|
function delprop(parent, key) {
|
|
647
|
-
|
|
648
|
-
return parent
|
|
649
|
-
}
|
|
650
|
-
if (ismap(parent)) {
|
|
651
|
-
key = strkey(key)
|
|
652
|
-
delete parent[key]
|
|
653
|
-
}
|
|
654
|
-
else if (islist(parent)) {
|
|
655
|
-
// Ensure key is an integer.
|
|
656
|
-
let keyI = +key
|
|
657
|
-
if (isNaN(keyI)) {
|
|
658
|
-
return parent
|
|
659
|
-
}
|
|
660
|
-
keyI = Math.floor(keyI)
|
|
661
|
-
// Delete list element at position keyI, shifting later elements down.
|
|
662
|
-
const psize = size(parent)
|
|
663
|
-
if (0 <= keyI && keyI < psize) {
|
|
664
|
-
for (let pI = keyI; pI < psize - 1; pI++) {
|
|
665
|
-
parent[pI] = parent[pI + 1]
|
|
666
|
-
}
|
|
667
|
-
parent.length = parent.length - 1
|
|
668
|
-
}
|
|
669
|
-
}
|
|
735
|
+
if (!iskey(key)) {
|
|
670
736
|
return parent
|
|
737
|
+
}
|
|
738
|
+
if (ismap(parent)) {
|
|
739
|
+
key = strkey(key)
|
|
740
|
+
delete parent[key]
|
|
741
|
+
} else if (islist(parent)) {
|
|
742
|
+
// Ensure key is an integer.
|
|
743
|
+
let keyI = +key
|
|
744
|
+
if (isNaN(keyI)) {
|
|
745
|
+
return parent
|
|
746
|
+
}
|
|
747
|
+
keyI = Math.floor(keyI)
|
|
748
|
+
// Delete list element at position keyI, shifting later elements down.
|
|
749
|
+
const psize = size(parent)
|
|
750
|
+
if (0 <= keyI && keyI < psize) {
|
|
751
|
+
for (let pI = keyI; pI < psize - 1; pI++) {
|
|
752
|
+
parent[pI] = parent[pI + 1]
|
|
753
|
+
}
|
|
754
|
+
parent.length = parent.length - 1
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return parent
|
|
671
758
|
}
|
|
672
759
|
// Safely set a property. Undefined arguments and invalid keys are ignored.
|
|
673
760
|
// Returns the (possibly modified) parent.
|
|
@@ -675,881 +762,936 @@ function delprop(parent, key) {
|
|
|
675
762
|
// NOTE: If the key is above the list size, append the value; below, prepend.
|
|
676
763
|
// NOTE: parent list may be new list, thus update references.
|
|
677
764
|
function setprop(parent, key, val) {
|
|
678
|
-
|
|
679
|
-
return parent
|
|
680
|
-
}
|
|
681
|
-
if (ismap(parent)) {
|
|
682
|
-
key = S_MT + key
|
|
683
|
-
const pany = parent
|
|
684
|
-
pany[key] = val
|
|
685
|
-
}
|
|
686
|
-
else if (islist(parent)) {
|
|
687
|
-
// Ensure key is an integer.
|
|
688
|
-
let keyI = +key
|
|
689
|
-
if (isNaN(keyI)) {
|
|
690
|
-
return parent
|
|
691
|
-
}
|
|
692
|
-
keyI = Math.floor(keyI)
|
|
693
|
-
// TODO: DELETE list element
|
|
694
|
-
// Set or append value at position keyI, or append if keyI out of bounds.
|
|
695
|
-
if (0 <= keyI) {
|
|
696
|
-
parent[slice(keyI, 0, size(parent) + 1)] = val
|
|
697
|
-
}
|
|
698
|
-
// Prepend value if keyI is negative
|
|
699
|
-
else {
|
|
700
|
-
parent.unshift(val)
|
|
701
|
-
}
|
|
702
|
-
}
|
|
765
|
+
if (!iskey(key)) {
|
|
703
766
|
return parent
|
|
767
|
+
}
|
|
768
|
+
if (ismap(parent)) {
|
|
769
|
+
key = S_MT + key
|
|
770
|
+
const pany = parent
|
|
771
|
+
pany[key] = val
|
|
772
|
+
} else if (islist(parent)) {
|
|
773
|
+
// Ensure key is an integer.
|
|
774
|
+
let keyI = +key
|
|
775
|
+
if (isNaN(keyI)) {
|
|
776
|
+
return parent
|
|
777
|
+
}
|
|
778
|
+
keyI = Math.floor(keyI)
|
|
779
|
+
// TODO: DELETE list element
|
|
780
|
+
// Set or append value at position keyI, or append if keyI out of bounds.
|
|
781
|
+
if (0 <= keyI) {
|
|
782
|
+
parent[slice(keyI, 0, size(parent) + 1)] = val
|
|
783
|
+
}
|
|
784
|
+
// Prepend value if keyI is negative
|
|
785
|
+
else {
|
|
786
|
+
parent.unshift(val)
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return parent
|
|
704
790
|
}
|
|
705
791
|
// Walk a data structure depth first, applying a function to each value.
|
|
792
|
+
// The `path` argument passed to the before/after callbacks is a single
|
|
793
|
+
// mutable array per depth, shared across all callback invocations for the
|
|
794
|
+
// lifetime of this top-level walk call. Callbacks that need to store the
|
|
795
|
+
// path MUST clone it (e.g. `path.slice()`); the contents will otherwise
|
|
796
|
+
// be overwritten by subsequent visits.
|
|
706
797
|
function walk(
|
|
707
|
-
// These arguments are the public interface.
|
|
708
|
-
val,
|
|
709
|
-
// Before descending into a node.
|
|
710
|
-
before,
|
|
711
|
-
// After descending into a node.
|
|
712
|
-
after,
|
|
713
|
-
// Maximum recursive depth, default: 32. Use null for infinite depth.
|
|
714
|
-
maxdepth,
|
|
715
|
-
// These areguments are used for recursive state.
|
|
716
|
-
key,
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
798
|
+
// These arguments are the public interface.
|
|
799
|
+
val,
|
|
800
|
+
// Before descending into a node.
|
|
801
|
+
before,
|
|
802
|
+
// After descending into a node.
|
|
803
|
+
after,
|
|
804
|
+
// Maximum recursive depth, default: 32. Use null for infinite depth.
|
|
805
|
+
maxdepth,
|
|
806
|
+
// These areguments are used for recursive state.
|
|
807
|
+
key,
|
|
808
|
+
parent,
|
|
809
|
+
path,
|
|
810
|
+
pool,
|
|
811
|
+
) {
|
|
812
|
+
if (NONE === pool) {
|
|
813
|
+
pool = [[]]
|
|
814
|
+
}
|
|
815
|
+
if (NONE === path) {
|
|
816
|
+
path = pool[0]
|
|
817
|
+
}
|
|
818
|
+
const depth = path.length
|
|
819
|
+
let out = null == before ? val : before(key, val, parent, path)
|
|
820
|
+
maxdepth = null != maxdepth && 0 <= maxdepth ? maxdepth : MAXDEPTH
|
|
821
|
+
if (0 === maxdepth || (0 < maxdepth && maxdepth <= depth)) {
|
|
731
822
|
return out
|
|
823
|
+
}
|
|
824
|
+
if (isnode(out)) {
|
|
825
|
+
const childDepth = depth + 1
|
|
826
|
+
let childPath = pool[childDepth]
|
|
827
|
+
if (NONE === childPath) {
|
|
828
|
+
childPath = new Array(childDepth)
|
|
829
|
+
pool[childDepth] = childPath
|
|
830
|
+
}
|
|
831
|
+
// Sync prefix [0..depth-1] from the current path. Only needed once per
|
|
832
|
+
// parent: siblings share the same prefix and will each overwrite slot
|
|
833
|
+
// [depth] below.
|
|
834
|
+
for (let i = 0; i < depth; i++) {
|
|
835
|
+
childPath[i] = path[i]
|
|
836
|
+
}
|
|
837
|
+
// Iterate the sorted keys directly (as items() would order them) without
|
|
838
|
+
// building the intermediate [key, value] tuple array per node.
|
|
839
|
+
const ckeys = keysof(out)
|
|
840
|
+
for (let cI = 0; cI < ckeys.length; cI++) {
|
|
841
|
+
const ckey = ckeys[cI]
|
|
842
|
+
childPath[depth] = S_MT + ckey
|
|
843
|
+
setprop(out, ckey, walk(out[ckey], before, after, maxdepth, ckey, out, childPath, pool))
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
out = null == after ? out : after(key, out, parent, path)
|
|
847
|
+
return out
|
|
732
848
|
}
|
|
733
849
|
// Merge a list of values into each other. Later values have
|
|
734
850
|
// precedence. Nodes override scalars. Node kinds (list or map)
|
|
735
851
|
// override each other, and do *not* merge. The first element is
|
|
736
852
|
// modified.
|
|
737
853
|
function merge(val, maxdepth) {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
854
|
+
// const md: number = null == maxdepth ? MAXDEPTH : maxdepth < 0 ? 0 : maxdepth
|
|
855
|
+
const md = slice(maxdepth ?? MAXDEPTH, 0)
|
|
856
|
+
let out = NONE
|
|
857
|
+
// Handle edge cases.
|
|
858
|
+
if (!islist(val)) {
|
|
859
|
+
return val
|
|
860
|
+
}
|
|
861
|
+
const list = val
|
|
862
|
+
const lenlist = list.length
|
|
863
|
+
if (0 === lenlist) {
|
|
864
|
+
return NONE
|
|
865
|
+
} else if (1 === lenlist) {
|
|
866
|
+
return list[0]
|
|
867
|
+
}
|
|
868
|
+
// Merge a list of values.
|
|
869
|
+
out = getprop(list, 0, {})
|
|
870
|
+
for (let oI = 1; oI < lenlist; oI++) {
|
|
871
|
+
let obj = list[oI]
|
|
872
|
+
if (!isnode(obj)) {
|
|
873
|
+
// Nodes win.
|
|
874
|
+
out = obj
|
|
875
|
+
} else {
|
|
876
|
+
// Current value at path end in overriding node.
|
|
877
|
+
let cur = [out]
|
|
878
|
+
// Current value at path end in destination node.
|
|
879
|
+
let dst = [out]
|
|
880
|
+
function before(key, val, _parent, path) {
|
|
881
|
+
const pI = size(path)
|
|
882
|
+
if (md <= pI) {
|
|
883
|
+
setprop(cur[pI - 1], key, val)
|
|
884
|
+
}
|
|
885
|
+
// Scalars just override directly.
|
|
886
|
+
else if (!isnode(val)) {
|
|
887
|
+
cur[pI] = val
|
|
888
|
+
}
|
|
889
|
+
// Descend into override node - Set up correct target in `after` function.
|
|
761
890
|
else {
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
// stringify(key, -1, 1), stringify(value, -1, 1), 'T=', stringify(target, -1, 1))
|
|
806
|
-
setprop(target, key, value)
|
|
807
|
-
return value
|
|
808
|
-
}
|
|
809
|
-
// Walk overriding node, creating paths in output as needed.
|
|
810
|
-
out = walk(obj, before, after, maxdepth)
|
|
811
|
-
// console.log('WALK-DONE', out, obj)
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
if (0 === md) {
|
|
815
|
-
out = getelem(list, -1)
|
|
816
|
-
out = islist(out) ? [] : ismap(out) ? {} : out
|
|
817
|
-
}
|
|
818
|
-
return out
|
|
891
|
+
// Descend into destination node using same key.
|
|
892
|
+
dst[pI] = 0 < pI ? getprop(dst[pI - 1], key) : dst[pI]
|
|
893
|
+
const tval = dst[pI]
|
|
894
|
+
const vtype = typify(val)
|
|
895
|
+
// Destination empty, so create node (unless override is class instance).
|
|
896
|
+
if (NONE === tval && 0 === (T_instance & vtype)) {
|
|
897
|
+
cur[pI] = islist(val) ? [] : {}
|
|
898
|
+
}
|
|
899
|
+
// Matching override and destination so continue with their values.
|
|
900
|
+
else if (vtype === typify(tval)) {
|
|
901
|
+
cur[pI] = tval
|
|
902
|
+
}
|
|
903
|
+
// Override wins.
|
|
904
|
+
else {
|
|
905
|
+
cur[pI] = val
|
|
906
|
+
// No need to descend when override wins (destination is discarded).
|
|
907
|
+
val = NONE
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
// console.log('BEFORE-END', pathify(path), '@', pI, key,
|
|
911
|
+
// stringify(val, -1, 1), stringify(parent, -1, 1),
|
|
912
|
+
// 'CUR=', stringify(cur, -1, 1), 'DST=', stringify(dst, -1, 1))
|
|
913
|
+
return val
|
|
914
|
+
}
|
|
915
|
+
function after(key, _val, _parent, path) {
|
|
916
|
+
const cI = size(path)
|
|
917
|
+
const target = cur[cI - 1]
|
|
918
|
+
const value = cur[cI]
|
|
919
|
+
// console.log('AFTER-PREP', pathify(path), '@', cI, cur, '|',
|
|
920
|
+
// stringify(key, -1, 1), stringify(value, -1, 1), 'T=', stringify(target, -1, 1))
|
|
921
|
+
setprop(target, key, value)
|
|
922
|
+
return value
|
|
923
|
+
}
|
|
924
|
+
// Walk overriding node, creating paths in output as needed.
|
|
925
|
+
out = walk(obj, before, after, maxdepth)
|
|
926
|
+
// console.log('WALK-DONE', out, obj)
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
if (0 === md) {
|
|
930
|
+
out = getelem(list, -1)
|
|
931
|
+
out = islist(out) ? [] : ismap(out) ? {} : out
|
|
932
|
+
}
|
|
933
|
+
return out
|
|
819
934
|
}
|
|
820
935
|
// Set a value using a path. Missing path parts are created.
|
|
821
936
|
// String paths create only maps. Use a string list to create list parts.
|
|
822
937
|
function setpath(store, path, val, injdef) {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
938
|
+
const pathType = typify(path)
|
|
939
|
+
const parts =
|
|
940
|
+
0 < (T_list & pathType)
|
|
941
|
+
? path
|
|
942
|
+
: 0 < (T_string & pathType)
|
|
943
|
+
? path.split(S_DT)
|
|
944
|
+
: 0 < (T_number & pathType)
|
|
945
|
+
? [path]
|
|
946
|
+
: NONE
|
|
947
|
+
if (NONE === parts) {
|
|
948
|
+
return NONE
|
|
949
|
+
}
|
|
950
|
+
const base = getprop(injdef, S_base)
|
|
951
|
+
const numparts = size(parts)
|
|
952
|
+
let parent = getprop(store, base, store)
|
|
953
|
+
for (let pI = 0; pI < numparts - 1; pI++) {
|
|
954
|
+
const partKey = getelem(parts, pI)
|
|
955
|
+
let nextParent = getprop(parent, partKey)
|
|
956
|
+
if (!isnode(nextParent)) {
|
|
957
|
+
nextParent = 0 < (T_number & typify(getelem(parts, pI + 1))) ? [] : {}
|
|
958
|
+
setprop(parent, partKey, nextParent)
|
|
959
|
+
}
|
|
960
|
+
parent = nextParent
|
|
961
|
+
}
|
|
962
|
+
if (DELETE === val) {
|
|
963
|
+
delprop(parent, getelem(parts, -1))
|
|
964
|
+
} else {
|
|
965
|
+
setprop(parent, getelem(parts, -1), val)
|
|
966
|
+
}
|
|
967
|
+
return parent
|
|
849
968
|
}
|
|
850
969
|
function getpath(store, path, injdef) {
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
970
|
+
// Operate on a string array.
|
|
971
|
+
const parts = islist(path)
|
|
972
|
+
? path
|
|
973
|
+
: 'string' === typeof path
|
|
974
|
+
? path.split(S_DT)
|
|
975
|
+
: 'number' === typeof path
|
|
976
|
+
? [strkey(path)]
|
|
977
|
+
: NONE
|
|
978
|
+
if (NONE === parts) {
|
|
979
|
+
return NONE
|
|
980
|
+
}
|
|
981
|
+
// let root = store
|
|
982
|
+
let val = store
|
|
983
|
+
const base = getprop(injdef, S_base)
|
|
984
|
+
const src = getprop(store, base, store)
|
|
985
|
+
const numparts = size(parts)
|
|
986
|
+
const dparent = getprop(injdef, 'dparent')
|
|
987
|
+
// An empty path (incl empty string) just finds the store.
|
|
988
|
+
if (null == path || null == store || (1 === numparts && S_MT === parts[0])) {
|
|
989
|
+
val = src
|
|
990
|
+
} else if (0 < numparts) {
|
|
991
|
+
// Check for $ACTIONs
|
|
992
|
+
if (1 === numparts) {
|
|
993
|
+
val = getprop(store, parts[0])
|
|
994
|
+
}
|
|
995
|
+
if (!isfunc(val)) {
|
|
996
|
+
val = src
|
|
997
|
+
const m = parts[0].match(R_META_PATH)
|
|
998
|
+
if (m && injdef && injdef.meta) {
|
|
999
|
+
val = getprop(injdef.meta, m[1])
|
|
1000
|
+
parts[0] = m[3]
|
|
1001
|
+
}
|
|
1002
|
+
const dpath = getprop(injdef, 'dpath')
|
|
1003
|
+
for (let pI = 0; NONE !== val && pI < numparts; pI++) {
|
|
1004
|
+
let part = parts[pI]
|
|
1005
|
+
if (injdef && S_DKEY === part) {
|
|
1006
|
+
part = getprop(injdef, S_key)
|
|
1007
|
+
} else if (injdef && part.startsWith('$GET:')) {
|
|
1008
|
+
// $GET:path$ -> get store value, use as path part (string)
|
|
1009
|
+
part = stringify(getpath(src, slice(part, 5, -1)))
|
|
1010
|
+
} else if (injdef && part.startsWith('$REF:')) {
|
|
1011
|
+
// $REF:refpath$ -> get spec value, use as path part (string)
|
|
1012
|
+
part = stringify(getpath(getprop(store, S_DSPEC), slice(part, 5, -1)))
|
|
1013
|
+
} else if (injdef && part.startsWith('$META:')) {
|
|
1014
|
+
// $META:metapath$ -> get meta value, use as path part (string)
|
|
1015
|
+
part = stringify(getpath(getprop(injdef, 'meta'), slice(part, 6, -1)))
|
|
1016
|
+
}
|
|
1017
|
+
// $$ escapes $ (skip the regex for the common no-`$$` segment).
|
|
1018
|
+
if (-1 !== part.indexOf('$$')) {
|
|
1019
|
+
part = part.replace(R_DOUBLE_DOLLAR, '$')
|
|
1020
|
+
}
|
|
1021
|
+
if (S_MT === part) {
|
|
1022
|
+
let ascends = 0
|
|
1023
|
+
while (S_MT === parts[1 + pI]) {
|
|
1024
|
+
ascends++
|
|
1025
|
+
pI++
|
|
1026
|
+
}
|
|
1027
|
+
if (injdef && 0 < ascends) {
|
|
1028
|
+
if (pI === parts.length - 1) {
|
|
1029
|
+
ascends--
|
|
879
1030
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
// $REF:refpath$ -> get spec value, use as path part (string)
|
|
892
|
-
part = stringify(getpath(getprop(store, S_DSPEC), slice(part, 5, -1)))
|
|
893
|
-
}
|
|
894
|
-
else if (injdef && part.startsWith('$META:')) {
|
|
895
|
-
// $META:metapath$ -> get meta value, use as path part (string)
|
|
896
|
-
part = stringify(getpath(getprop(injdef, 'meta'), slice(part, 6, -1)))
|
|
897
|
-
}
|
|
898
|
-
// $$ escapes $
|
|
899
|
-
part = part.replace(R_DOUBLE_DOLLAR, '$')
|
|
900
|
-
if (S_MT === part) {
|
|
901
|
-
let ascends = 0
|
|
902
|
-
while (S_MT === parts[1 + pI]) {
|
|
903
|
-
ascends++
|
|
904
|
-
pI++
|
|
905
|
-
}
|
|
906
|
-
if (injdef && 0 < ascends) {
|
|
907
|
-
if (pI === parts.length - 1) {
|
|
908
|
-
ascends--
|
|
909
|
-
}
|
|
910
|
-
if (0 === ascends) {
|
|
911
|
-
val = dparent
|
|
912
|
-
}
|
|
913
|
-
else {
|
|
914
|
-
// const fullpath = slice(dpath, 0 - ascends).concat(parts.slice(pI + 1))
|
|
915
|
-
const fullpath = flatten([slice(dpath, 0 - ascends), parts.slice(pI + 1)])
|
|
916
|
-
if (ascends <= size(dpath)) {
|
|
917
|
-
val = getpath(store, fullpath)
|
|
918
|
-
}
|
|
919
|
-
else {
|
|
920
|
-
val = NONE
|
|
921
|
-
}
|
|
922
|
-
break
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
else {
|
|
926
|
-
val = dparent
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
else {
|
|
930
|
-
val = getprop(val, part)
|
|
931
|
-
}
|
|
1031
|
+
if (0 === ascends) {
|
|
1032
|
+
val = dparent
|
|
1033
|
+
} else {
|
|
1034
|
+
// const fullpath = slice(dpath, 0 - ascends).concat(parts.slice(pI + 1))
|
|
1035
|
+
const fullpath = flatten([slice(dpath, 0 - ascends), parts.slice(pI + 1)])
|
|
1036
|
+
if (ascends <= size(dpath)) {
|
|
1037
|
+
val = getpath(store, fullpath)
|
|
1038
|
+
} else {
|
|
1039
|
+
val = NONE
|
|
1040
|
+
}
|
|
1041
|
+
break
|
|
932
1042
|
}
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
|
|
1043
|
+
} else {
|
|
1044
|
+
val = dparent
|
|
1045
|
+
}
|
|
1046
|
+
} else {
|
|
1047
|
+
val = getprop(val, part)
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
// Inj may provide a custom handler to modify found value.
|
|
1053
|
+
const handler = getprop(injdef, 'handler')
|
|
1054
|
+
if (null != injdef && isfunc(handler)) {
|
|
1055
|
+
const ref = pathify(path)
|
|
1056
|
+
val = handler(injdef, val, ref, store)
|
|
1057
|
+
}
|
|
1058
|
+
// console.log('GETPATH', path, val)
|
|
1059
|
+
return val
|
|
943
1060
|
}
|
|
944
1061
|
// Inject values from a data store into a node recursively, resolving
|
|
945
1062
|
// paths against the store, or current if they are local. The modify
|
|
946
1063
|
// argument allows custom modification of the result. The inj
|
|
947
1064
|
// (Injection) argument is used to maintain recursive state.
|
|
948
1065
|
function inject(val, store, injdef) {
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
return getprop(inj.parent, S_DTOP)
|
|
1066
|
+
const valtype = typeof val
|
|
1067
|
+
let inj = injdef
|
|
1068
|
+
// Create state if at root of injection. The input value is placed
|
|
1069
|
+
// inside a virtual parent holder to simplify edge cases.
|
|
1070
|
+
if (NONE === injdef || null == injdef.mode) {
|
|
1071
|
+
// Set up state assuming we are starting in the virtual parent.
|
|
1072
|
+
inj = new Injection(val, { [S_DTOP]: val })
|
|
1073
|
+
inj.dparent = store
|
|
1074
|
+
inj.errs = getprop(store, S_DERRS, [])
|
|
1075
|
+
inj.meta.__d = 0
|
|
1076
|
+
if (NONE !== injdef) {
|
|
1077
|
+
inj.modify = null == injdef.modify ? inj.modify : injdef.modify
|
|
1078
|
+
inj.extra = null == injdef.extra ? inj.extra : injdef.extra
|
|
1079
|
+
inj.meta = null == injdef.meta ? inj.meta : injdef.meta
|
|
1080
|
+
inj.handler = null == injdef.handler ? inj.handler : injdef.handler
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
inj.descend()
|
|
1084
|
+
// console.log('INJ-START', val, inj.mode, inj.key, inj.val,
|
|
1085
|
+
// 't=', inj.path, 'P=', inj.parent, 'dp=', inj.dparent, 'ST=', store.$TOP)
|
|
1086
|
+
// Descend into node.
|
|
1087
|
+
if (isnode(val)) {
|
|
1088
|
+
// Keys are sorted alphanumerically to ensure determinism.
|
|
1089
|
+
// Injection transforms ($FOO) are processed *after* other keys.
|
|
1090
|
+
// NOTE: the optional digits suffix of the transform can thus be
|
|
1091
|
+
// used to order the transforms.
|
|
1092
|
+
let nodekeys
|
|
1093
|
+
nodekeys = keysof(val)
|
|
1094
|
+
if (ismap(val)) {
|
|
1095
|
+
nodekeys = flatten([
|
|
1096
|
+
filter(nodekeys, (n) => !n[1].includes(S_DS)),
|
|
1097
|
+
filter(nodekeys, (n) => n[1].includes(S_DS)),
|
|
1098
|
+
])
|
|
1099
|
+
} else {
|
|
1100
|
+
nodekeys = keysof(val)
|
|
1101
|
+
}
|
|
1102
|
+
// Each child key-value pair is processed in three injection phases:
|
|
1103
|
+
// 1. inj.mode=M_KEYPRE - Key string is injected, returning a possibly altered key.
|
|
1104
|
+
// 2. inj.mode=M_VAL - The child value is injected.
|
|
1105
|
+
// 3. inj.mode=M_KEYPOST - Key string is injected again, allowing child mutation.
|
|
1106
|
+
for (let nkI = 0; nkI < nodekeys.length; nkI++) {
|
|
1107
|
+
const childinj = inj.child(nkI, nodekeys)
|
|
1108
|
+
const nodekey = childinj.key
|
|
1109
|
+
childinj.mode = M_KEYPRE
|
|
1110
|
+
// Peform the key:pre mode injection on the child key.
|
|
1111
|
+
const prekey = _injectstr(nodekey, store, childinj)
|
|
1112
|
+
// The injection may modify child processing.
|
|
1113
|
+
nkI = childinj.keyI
|
|
1114
|
+
nodekeys = childinj.keys
|
|
1115
|
+
// Prevent further processing by returning an undefined prekey
|
|
1116
|
+
if (NONE !== prekey) {
|
|
1117
|
+
childinj.val = getprop(val, prekey)
|
|
1118
|
+
childinj.mode = M_VAL
|
|
1119
|
+
// Perform the val mode injection on the child value.
|
|
1120
|
+
// NOTE: return value is not used.
|
|
1121
|
+
inject(childinj.val, store, childinj)
|
|
1122
|
+
// The injection may modify child processing.
|
|
1123
|
+
nkI = childinj.keyI
|
|
1124
|
+
nodekeys = childinj.keys
|
|
1125
|
+
// Peform the key:post mode injection on the child key.
|
|
1126
|
+
childinj.mode = M_KEYPOST
|
|
1127
|
+
_injectstr(nodekey, store, childinj)
|
|
1128
|
+
// The injection may modify child processing.
|
|
1129
|
+
nkI = childinj.keyI
|
|
1130
|
+
nodekeys = childinj.keys
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
// Inject paths into string scalars.
|
|
1135
|
+
else if (S_string === valtype) {
|
|
1136
|
+
inj.mode = M_VAL
|
|
1137
|
+
val = _injectstr(val, store, inj)
|
|
1138
|
+
if (SKIP !== val) {
|
|
1139
|
+
inj.setval(val)
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
// Custom modification.
|
|
1143
|
+
if (inj.modify && SKIP !== val) {
|
|
1144
|
+
let mkey = inj.key
|
|
1145
|
+
let mparent = inj.parent
|
|
1146
|
+
let mval = getprop(mparent, mkey)
|
|
1147
|
+
inj.modify(mval, mkey, mparent, inj, store)
|
|
1148
|
+
}
|
|
1149
|
+
// console.log('INJ-VAL', val)
|
|
1150
|
+
inj.val = val
|
|
1151
|
+
// Original val reference may no longer be correct.
|
|
1152
|
+
// This return value is only used as the top level result.
|
|
1153
|
+
return _lookup(inj.parent, S_DTOP)
|
|
1038
1154
|
}
|
|
1039
1155
|
// The transform_* functions are special command inject handlers (see Injector).
|
|
1040
1156
|
// Delete a key from a map or list.
|
|
1041
1157
|
const transform_DELETE = (inj) => {
|
|
1042
|
-
|
|
1043
|
-
|
|
1158
|
+
inj.setval(NONE)
|
|
1159
|
+
return NONE
|
|
1044
1160
|
}
|
|
1045
1161
|
// Copy value from source data.
|
|
1046
1162
|
const transform_COPY = (inj, _val) => {
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1163
|
+
const ijname = 'COPY'
|
|
1164
|
+
if (!checkPlacement(M_VAL, ijname, T_any, inj)) {
|
|
1165
|
+
return NONE
|
|
1166
|
+
}
|
|
1167
|
+
let out = getprop(inj.dparent, inj.key)
|
|
1168
|
+
inj.setval(out)
|
|
1169
|
+
return out
|
|
1054
1170
|
}
|
|
1055
1171
|
// As a value, inject the key of the parent node.
|
|
1056
1172
|
// As a key, defined the name of the key property in the source object.
|
|
1057
1173
|
const transform_KEY = (inj) => {
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
return getprop(getprop(parent, S_BANNO), S_KEY, getelem(path, -2))
|
|
1174
|
+
const { mode, path, parent } = inj
|
|
1175
|
+
// Do nothing in val mode - not an error.
|
|
1176
|
+
if (M_VAL !== mode) {
|
|
1177
|
+
return NONE
|
|
1178
|
+
}
|
|
1179
|
+
// Key is defined by $KEY meta property.
|
|
1180
|
+
const keyspec = _lookup(parent, S_BKEY)
|
|
1181
|
+
if (NONE !== keyspec) {
|
|
1182
|
+
delprop(parent, S_BKEY)
|
|
1183
|
+
return _lookup(inj.dparent, keyspec)
|
|
1184
|
+
}
|
|
1185
|
+
// Key is defined within general purpose $META object.
|
|
1186
|
+
return _lookup(_lookup(parent, S_BANNO), S_KEY) ?? getelem(path, -2)
|
|
1072
1187
|
}
|
|
1073
1188
|
// Annotate node. Does nothing itself, just used by
|
|
1074
1189
|
// other injectors, and is removed when called.
|
|
1075
1190
|
const transform_ANNO = (inj) => {
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1191
|
+
const { parent } = inj
|
|
1192
|
+
delprop(parent, S_BANNO)
|
|
1193
|
+
return NONE
|
|
1079
1194
|
}
|
|
1080
|
-
// Merge a list of objects into the current object.
|
|
1195
|
+
// Merge a list of objects into the current object.
|
|
1081
1196
|
// Must be a key in an object. The value is merged over the current object.
|
|
1082
|
-
// If the value is an array, the elements are first merged using `merge`.
|
|
1197
|
+
// If the value is an array, the elements are first merged using `merge`.
|
|
1083
1198
|
// If the value is the empty string, merge the top level store.
|
|
1084
1199
|
// Format: { '`$MERGE`': '`source-path`' | ['`source-paths`', ...] }
|
|
1085
1200
|
const transform_MERGE = (inj) => {
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1201
|
+
const { mode, key, parent } = inj
|
|
1202
|
+
// Ensures $MERGE is removed from parent list (val mode).
|
|
1203
|
+
let out = NONE
|
|
1204
|
+
if (M_KEYPRE === mode) {
|
|
1205
|
+
out = key
|
|
1206
|
+
}
|
|
1207
|
+
// Operate after child values have been transformed.
|
|
1208
|
+
else if (M_KEYPOST === mode) {
|
|
1209
|
+
out = key
|
|
1210
|
+
let args = getprop(parent, key)
|
|
1211
|
+
args = Array.isArray(args) ? args : [args]
|
|
1212
|
+
// Remove the $MERGE command from a parent map.
|
|
1213
|
+
inj.setval(NONE)
|
|
1214
|
+
// Literals in the parent have precedence, but we still merge onto
|
|
1215
|
+
// the parent object, so that node tree references are not changed.
|
|
1216
|
+
const mergelist = flatten([[parent], args, [clone(parent)]])
|
|
1217
|
+
merge(mergelist)
|
|
1218
|
+
}
|
|
1219
|
+
return out
|
|
1105
1220
|
}
|
|
1106
1221
|
// Convert a node to a list.
|
|
1107
1222
|
// Format: ['`$EACH`', '`source-path-of-node`', child-template]
|
|
1108
1223
|
const transform_EACH = (inj, _val, _ref, store) => {
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1224
|
+
const ijname = 'EACH'
|
|
1225
|
+
if (!checkPlacement(M_VAL, ijname, T_list, inj)) {
|
|
1226
|
+
return NONE
|
|
1227
|
+
}
|
|
1228
|
+
// Remove remaining keys to avoid spurious processing.
|
|
1229
|
+
slice(inj.keys, 0, 1, true)
|
|
1230
|
+
// const [err, srcpath, child] = injectorArgs([T_string, T_any], inj)
|
|
1231
|
+
const [err, srcpath, child] = injectorArgs([T_string, T_any], slice(inj.parent, 1))
|
|
1232
|
+
if (NONE !== err) {
|
|
1233
|
+
inj.errs.push('$' + ijname + ': ' + err)
|
|
1234
|
+
return NONE
|
|
1235
|
+
}
|
|
1236
|
+
// Source data.
|
|
1237
|
+
const srcstore = getprop(store, inj.base, store)
|
|
1238
|
+
const src = getpath(srcstore, srcpath, inj)
|
|
1239
|
+
const srctype = typify(src)
|
|
1240
|
+
// Create parallel data structures:
|
|
1241
|
+
// source entries :: child templates
|
|
1242
|
+
let tcur = []
|
|
1243
|
+
let tval = []
|
|
1244
|
+
const tkey = getelem(inj.path, -2)
|
|
1245
|
+
const target = getelem(inj.nodes, -2, () => getelem(inj.nodes, -1))
|
|
1246
|
+
// Create clones of the child template for each value of the current soruce.
|
|
1247
|
+
if (0 < (T_list & srctype)) {
|
|
1248
|
+
tval = items(src, () => clone(child))
|
|
1249
|
+
} else if (0 < (T_map & srctype)) {
|
|
1250
|
+
tval = items(src, (n) =>
|
|
1251
|
+
merge(
|
|
1252
|
+
[
|
|
1253
|
+
clone(child),
|
|
1254
|
+
// Make a note of the key for $KEY transforms.
|
|
1255
|
+
{ [S_BANNO]: { KEY: n[0] } },
|
|
1256
|
+
],
|
|
1257
|
+
1,
|
|
1258
|
+
),
|
|
1259
|
+
)
|
|
1260
|
+
}
|
|
1261
|
+
let rval = []
|
|
1262
|
+
if (0 < size(tval)) {
|
|
1263
|
+
tcur = null == src ? NONE : Object.values(src)
|
|
1264
|
+
const ckey = getelem(inj.path, -2)
|
|
1265
|
+
const tpath = slice(inj.path, -1)
|
|
1266
|
+
const dpath = flatten([S_DTOP, srcpath.split(S_DT), '$:' + ckey])
|
|
1267
|
+
// Parent structure.
|
|
1268
|
+
tcur = { [ckey]: tcur }
|
|
1269
|
+
if (1 < size(tpath)) {
|
|
1270
|
+
const pkey = getelem(inj.path, -3, S_DTOP)
|
|
1271
|
+
tcur = { [pkey]: tcur }
|
|
1272
|
+
dpath.push('$:' + pkey)
|
|
1273
|
+
}
|
|
1274
|
+
const tinj = inj.child(0, [ckey])
|
|
1275
|
+
tinj.path = tpath
|
|
1276
|
+
tinj.nodes = slice(inj.nodes, -1)
|
|
1277
|
+
tinj.parent = getelem(tinj.nodes, -1)
|
|
1278
|
+
setprop(tinj.parent, ckey, tval)
|
|
1279
|
+
tinj.val = tval
|
|
1280
|
+
tinj.dpath = dpath
|
|
1281
|
+
tinj.dparent = tcur
|
|
1282
|
+
inject(tval, store, tinj)
|
|
1283
|
+
rval = tinj.val
|
|
1284
|
+
}
|
|
1285
|
+
// _updateAncestors(inj, target, tkey, rval)
|
|
1286
|
+
setprop(target, tkey, rval)
|
|
1287
|
+
// Prevent callee from damaging first list entry (since we are in `val` mode).
|
|
1288
|
+
return rval[0]
|
|
1170
1289
|
}
|
|
1171
1290
|
// Convert a node to a map.
|
|
1172
1291
|
// Format: { '`$PACK`':['source-path', child-template]}
|
|
1173
1292
|
const transform_PACK = (inj, _val, _ref, store) => {
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
return NONE
|
|
1185
|
-
}
|
|
1186
|
-
// Find key and target node.
|
|
1187
|
-
const tkey = getelem(path, -2)
|
|
1188
|
-
const pathsize = size(path)
|
|
1189
|
-
const target = getelem(nodes, pathsize - 2, () => getelem(nodes, pathsize - 1))
|
|
1190
|
-
// Source data
|
|
1191
|
-
const srcstore = getprop(store, inj.base, store)
|
|
1192
|
-
let src = getpath(srcstore, srcpath, inj)
|
|
1193
|
-
// Prepare source as a list.
|
|
1194
|
-
if (!islist(src)) {
|
|
1195
|
-
if (ismap(src)) {
|
|
1196
|
-
src = items(src, (item) => {
|
|
1197
|
-
setprop(item[1], S_BANNO, { KEY: item[0] })
|
|
1198
|
-
return item[1]
|
|
1199
|
-
})
|
|
1200
|
-
}
|
|
1201
|
-
else {
|
|
1202
|
-
src = NONE
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
if (null == src) {
|
|
1206
|
-
return NONE
|
|
1207
|
-
}
|
|
1208
|
-
// Get keypath.
|
|
1209
|
-
const keypath = getprop(origchildspec, S_BKEY)
|
|
1210
|
-
const childspec = delprop(origchildspec, S_BKEY)
|
|
1211
|
-
const child = getprop(childspec, S_BVAL, childspec)
|
|
1212
|
-
// Build parallel target object.
|
|
1213
|
-
let tval = {}
|
|
1214
|
-
items(src, (item) => {
|
|
1215
|
-
const srckey = item[0]
|
|
1216
|
-
const srcnode = item[1]
|
|
1217
|
-
let key = srckey
|
|
1218
|
-
if (NONE !== keypath) {
|
|
1219
|
-
if (keypath.startsWith('`')) {
|
|
1220
|
-
key = inject(keypath, merge([{}, store, { $TOP: srcnode }], 1))
|
|
1221
|
-
}
|
|
1222
|
-
else {
|
|
1223
|
-
key = getpath(srcnode, keypath, inj)
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
const tchild = clone(child)
|
|
1227
|
-
setprop(tval, key, tchild)
|
|
1228
|
-
const anno = getprop(srcnode, S_BANNO)
|
|
1229
|
-
if (NONE === anno) {
|
|
1230
|
-
delprop(tchild, S_BANNO)
|
|
1231
|
-
}
|
|
1232
|
-
else {
|
|
1233
|
-
setprop(tchild, S_BANNO, anno)
|
|
1234
|
-
}
|
|
1235
|
-
})
|
|
1236
|
-
let rval = {}
|
|
1237
|
-
if (!isempty(tval)) {
|
|
1238
|
-
// Build parallel source object.
|
|
1239
|
-
let tsrc = {}
|
|
1240
|
-
src.reduce((a, n, i) => {
|
|
1241
|
-
let kn = null == keypath ? i :
|
|
1242
|
-
keypath.startsWith('`') ?
|
|
1243
|
-
inject(keypath, merge([{}, store, { $TOP: n }], 1)) :
|
|
1244
|
-
getpath(n, keypath, inj)
|
|
1245
|
-
setprop(a, kn, n)
|
|
1246
|
-
return a
|
|
1247
|
-
}, tsrc)
|
|
1248
|
-
const tpath = slice(inj.path, -1)
|
|
1249
|
-
const ckey = getelem(inj.path, -2)
|
|
1250
|
-
const dpath = flatten([S_DTOP, srcpath.split(S_DT), '$:' + ckey])
|
|
1251
|
-
let tcur = { [ckey]: tsrc }
|
|
1252
|
-
if (1 < size(tpath)) {
|
|
1253
|
-
const pkey = getelem(inj.path, -3, S_DTOP)
|
|
1254
|
-
tcur = { [pkey]: tcur }
|
|
1255
|
-
dpath.push('$:' + pkey)
|
|
1256
|
-
}
|
|
1257
|
-
const tinj = inj.child(0, [ckey])
|
|
1258
|
-
tinj.path = tpath
|
|
1259
|
-
tinj.nodes = slice(inj.nodes, -1)
|
|
1260
|
-
tinj.parent = getelem(tinj.nodes, -1)
|
|
1261
|
-
tinj.val = tval
|
|
1262
|
-
tinj.dpath = dpath
|
|
1263
|
-
tinj.dparent = tcur
|
|
1264
|
-
inject(tval, store, tinj)
|
|
1265
|
-
rval = tinj.val
|
|
1266
|
-
}
|
|
1267
|
-
// _updateAncestors(inj, target, tkey, rval)
|
|
1268
|
-
setprop(target, tkey, rval)
|
|
1269
|
-
// Drop transform key.
|
|
1293
|
+
const { key, path, parent, nodes } = inj
|
|
1294
|
+
const ijname = 'EACH'
|
|
1295
|
+
if (!checkPlacement(M_KEYPRE, ijname, T_map, inj)) {
|
|
1296
|
+
return NONE
|
|
1297
|
+
}
|
|
1298
|
+
// Get arguments.
|
|
1299
|
+
const args = getprop(parent, key)
|
|
1300
|
+
const [err, srcpath, origchildspec] = injectorArgs([T_string, T_any], args)
|
|
1301
|
+
if (NONE !== err) {
|
|
1302
|
+
inj.errs.push('$' + ijname + ': ' + err)
|
|
1270
1303
|
return NONE
|
|
1304
|
+
}
|
|
1305
|
+
// Find key and target node.
|
|
1306
|
+
const tkey = getelem(path, -2)
|
|
1307
|
+
const pathsize = size(path)
|
|
1308
|
+
const target = getelem(nodes, pathsize - 2, () => getelem(nodes, pathsize - 1))
|
|
1309
|
+
// Source data
|
|
1310
|
+
const srcstore = getprop(store, inj.base, store)
|
|
1311
|
+
let src = getpath(srcstore, srcpath, inj)
|
|
1312
|
+
// Prepare source as a list.
|
|
1313
|
+
if (!islist(src)) {
|
|
1314
|
+
if (ismap(src)) {
|
|
1315
|
+
src = items(src, (item) => {
|
|
1316
|
+
setprop(item[1], S_BANNO, { KEY: item[0] })
|
|
1317
|
+
return item[1]
|
|
1318
|
+
})
|
|
1319
|
+
} else {
|
|
1320
|
+
src = NONE
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
if (null == src) {
|
|
1324
|
+
return NONE
|
|
1325
|
+
}
|
|
1326
|
+
// Get keypath.
|
|
1327
|
+
const keypath = getprop(origchildspec, S_BKEY)
|
|
1328
|
+
const childspec = delprop(origchildspec, S_BKEY)
|
|
1329
|
+
const child = getprop(childspec, S_BVAL, childspec)
|
|
1330
|
+
// Build parallel target object.
|
|
1331
|
+
let tval = {}
|
|
1332
|
+
items(src, (item) => {
|
|
1333
|
+
const srckey = item[0]
|
|
1334
|
+
const srcnode = item[1]
|
|
1335
|
+
let key = srckey
|
|
1336
|
+
if (NONE !== keypath) {
|
|
1337
|
+
if (keypath.startsWith('`')) {
|
|
1338
|
+
key = inject(keypath, merge([{}, store, { $TOP: srcnode }], 1))
|
|
1339
|
+
} else {
|
|
1340
|
+
key = getpath(srcnode, keypath, inj)
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
const tchild = clone(child)
|
|
1344
|
+
setprop(tval, key, tchild)
|
|
1345
|
+
const anno = getprop(srcnode, S_BANNO)
|
|
1346
|
+
if (NONE === anno) {
|
|
1347
|
+
delprop(tchild, S_BANNO)
|
|
1348
|
+
} else {
|
|
1349
|
+
setprop(tchild, S_BANNO, anno)
|
|
1350
|
+
}
|
|
1351
|
+
})
|
|
1352
|
+
let rval = {}
|
|
1353
|
+
if (!isempty(tval)) {
|
|
1354
|
+
// Build parallel source object.
|
|
1355
|
+
let tsrc = {}
|
|
1356
|
+
src.reduce((a, n, i) => {
|
|
1357
|
+
let kn =
|
|
1358
|
+
null == keypath
|
|
1359
|
+
? i
|
|
1360
|
+
: keypath.startsWith('`')
|
|
1361
|
+
? inject(keypath, merge([{}, store, { $TOP: n }], 1))
|
|
1362
|
+
: getpath(n, keypath, inj)
|
|
1363
|
+
setprop(a, kn, n)
|
|
1364
|
+
return a
|
|
1365
|
+
}, tsrc)
|
|
1366
|
+
const tpath = slice(inj.path, -1)
|
|
1367
|
+
const ckey = getelem(inj.path, -2)
|
|
1368
|
+
const dpath = flatten([S_DTOP, srcpath.split(S_DT), '$:' + ckey])
|
|
1369
|
+
let tcur = { [ckey]: tsrc }
|
|
1370
|
+
if (1 < size(tpath)) {
|
|
1371
|
+
const pkey = getelem(inj.path, -3, S_DTOP)
|
|
1372
|
+
tcur = { [pkey]: tcur }
|
|
1373
|
+
dpath.push('$:' + pkey)
|
|
1374
|
+
}
|
|
1375
|
+
const tinj = inj.child(0, [ckey])
|
|
1376
|
+
tinj.path = tpath
|
|
1377
|
+
tinj.nodes = slice(inj.nodes, -1)
|
|
1378
|
+
tinj.parent = getelem(tinj.nodes, -1)
|
|
1379
|
+
tinj.val = tval
|
|
1380
|
+
tinj.dpath = dpath
|
|
1381
|
+
tinj.dparent = tcur
|
|
1382
|
+
inject(tval, store, tinj)
|
|
1383
|
+
rval = tinj.val
|
|
1384
|
+
}
|
|
1385
|
+
// _updateAncestors(inj, target, tkey, rval)
|
|
1386
|
+
setprop(target, tkey, rval)
|
|
1387
|
+
// Drop transform key.
|
|
1388
|
+
return NONE
|
|
1271
1389
|
}
|
|
1272
1390
|
// TODO: not found ref should removed key (setprop NONE)
|
|
1273
1391
|
// Reference original spec (enables recursice transformations)
|
|
1274
1392
|
// Format: ['`$REF`', '`spec-path`']
|
|
1275
1393
|
const transform_REF = (inj, val, _ref, store) => {
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1394
|
+
const { nodes } = inj
|
|
1395
|
+
if (M_VAL !== inj.mode) {
|
|
1396
|
+
return NONE
|
|
1397
|
+
}
|
|
1398
|
+
// Get arguments: ['`$REF`', 'ref-path'].
|
|
1399
|
+
const refpath = _lookup(inj.parent, 1)
|
|
1400
|
+
inj.keyI = size(inj.keys)
|
|
1401
|
+
// Spec reference.
|
|
1402
|
+
const spec = getprop(store, S_DSPEC)()
|
|
1403
|
+
const dpath = slice(inj.path, 1)
|
|
1404
|
+
const ref = getpath(spec, refpath, {
|
|
1405
|
+
// TODO: test relative refs
|
|
1406
|
+
// dpath: inj.path.slice(1),
|
|
1407
|
+
dpath,
|
|
1408
|
+
// dparent: getpath(spec, inj.path.slice(1))
|
|
1409
|
+
dparent: getpath(spec, dpath),
|
|
1410
|
+
})
|
|
1411
|
+
let hasSubRef = false
|
|
1412
|
+
if (isnode(ref)) {
|
|
1413
|
+
walk(ref, (_k, v) => {
|
|
1414
|
+
if ('`$REF`' === v) {
|
|
1415
|
+
hasSubRef = true
|
|
1416
|
+
}
|
|
1417
|
+
return v
|
|
1292
1418
|
})
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
}
|
|
1319
|
-
else {
|
|
1320
|
-
rval = NONE
|
|
1321
|
-
}
|
|
1322
|
-
const grandparent = inj.setval(rval, 2)
|
|
1323
|
-
if (islist(grandparent) && inj.prior) {
|
|
1324
|
-
inj.prior.keyI--
|
|
1325
|
-
}
|
|
1326
|
-
return val
|
|
1419
|
+
}
|
|
1420
|
+
let tref = clone(ref)
|
|
1421
|
+
const cpath = slice(inj.path, -3)
|
|
1422
|
+
const tpath = slice(inj.path, -1)
|
|
1423
|
+
let tcur = getpath(store, cpath)
|
|
1424
|
+
let tval = getpath(store, tpath)
|
|
1425
|
+
let rval = NONE
|
|
1426
|
+
if (!hasSubRef || NONE !== tval) {
|
|
1427
|
+
const tinj = inj.child(0, [getelem(tpath, -1)])
|
|
1428
|
+
tinj.path = tpath
|
|
1429
|
+
tinj.nodes = slice(inj.nodes, -1)
|
|
1430
|
+
tinj.parent = getelem(nodes, -2)
|
|
1431
|
+
tinj.val = tref
|
|
1432
|
+
tinj.dpath = flatten([cpath])
|
|
1433
|
+
tinj.dparent = tcur
|
|
1434
|
+
inject(tref, store, tinj)
|
|
1435
|
+
rval = tinj.val
|
|
1436
|
+
} else {
|
|
1437
|
+
rval = NONE
|
|
1438
|
+
}
|
|
1439
|
+
const grandparent = inj.setval(rval, 2)
|
|
1440
|
+
if (islist(grandparent) && inj.prior) {
|
|
1441
|
+
inj.prior.keyI--
|
|
1442
|
+
}
|
|
1443
|
+
return val
|
|
1327
1444
|
}
|
|
1328
1445
|
const transform_FORMAT = (inj, _val, _ref, store) => {
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1446
|
+
// console.log('FORMAT-START', inj, _val)
|
|
1447
|
+
// Remove remaining keys to avoid spurious processing.
|
|
1448
|
+
slice(inj.keys, 0, 1, true)
|
|
1449
|
+
if (M_VAL !== inj.mode) {
|
|
1450
|
+
return NONE
|
|
1451
|
+
}
|
|
1452
|
+
// Get arguments: ['`$FORMAT`', 'name', child].
|
|
1453
|
+
// TODO: EACH and PACK should accept customm functions too
|
|
1454
|
+
const name = _lookup(inj.parent, 1)
|
|
1455
|
+
const child = _lookup(inj.parent, 2)
|
|
1456
|
+
// Source data.
|
|
1457
|
+
const tkey = getelem(inj.path, -2)
|
|
1458
|
+
const target = getelem(inj.nodes, -2, () => getelem(inj.nodes, -1))
|
|
1459
|
+
const cinj = injectChild(child, store, inj)
|
|
1460
|
+
const resolved = cinj.val
|
|
1461
|
+
let formatter = 0 < (T_function & typify(name)) ? name : getprop(FORMATTER, name)
|
|
1462
|
+
if (NONE === formatter) {
|
|
1463
|
+
inj.errs.push('$FORMAT: unknown format: ' + name + '.')
|
|
1464
|
+
return NONE
|
|
1465
|
+
}
|
|
1466
|
+
let out = walk(resolved, formatter)
|
|
1467
|
+
setprop(target, tkey, out)
|
|
1468
|
+
// _updateAncestors(inj, target, tkey, out)
|
|
1469
|
+
return out
|
|
1353
1470
|
}
|
|
1354
1471
|
const FORMATTER = {
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1472
|
+
identity: (_k, v) => v,
|
|
1473
|
+
upper: (_k, v) => (isnode(v) ? v : ('' + v).toUpperCase()),
|
|
1474
|
+
lower: (_k, v) => (isnode(v) ? v : ('' + v).toLowerCase()),
|
|
1475
|
+
string: (_k, v) => (isnode(v) ? v : '' + v),
|
|
1476
|
+
number: (_k, v) => {
|
|
1477
|
+
if (isnode(v)) {
|
|
1478
|
+
return v
|
|
1479
|
+
} else {
|
|
1480
|
+
let n = Number(v)
|
|
1481
|
+
if (isNaN(n)) {
|
|
1482
|
+
n = 0
|
|
1483
|
+
}
|
|
1484
|
+
return n
|
|
1485
|
+
}
|
|
1486
|
+
},
|
|
1487
|
+
integer: (_k, v) => {
|
|
1488
|
+
if (isnode(v)) {
|
|
1489
|
+
return v
|
|
1490
|
+
} else {
|
|
1491
|
+
let n = Number(v)
|
|
1492
|
+
if (isNaN(n)) {
|
|
1493
|
+
n = 0
|
|
1494
|
+
}
|
|
1495
|
+
return n | 0
|
|
1496
|
+
}
|
|
1497
|
+
},
|
|
1498
|
+
concat: (k, v) =>
|
|
1499
|
+
null == k && islist(v)
|
|
1500
|
+
? join(
|
|
1501
|
+
items(v, (n) => (isnode(n[1]) ? S_MT : S_MT + n[1])),
|
|
1502
|
+
S_MT,
|
|
1503
|
+
)
|
|
1504
|
+
: v,
|
|
1384
1505
|
}
|
|
1385
1506
|
const transform_APPLY = (inj, _val, _ref, store) => {
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1507
|
+
const ijname = 'APPLY'
|
|
1508
|
+
if (!checkPlacement(M_VAL, ijname, T_list, inj)) {
|
|
1509
|
+
return NONE
|
|
1510
|
+
}
|
|
1511
|
+
// const [err, apply, child] = injectorArgs([T_function, T_any], inj)
|
|
1512
|
+
const [err, apply, child] = injectorArgs([T_function, T_any], slice(inj.parent, 1))
|
|
1513
|
+
if (NONE !== err) {
|
|
1514
|
+
inj.errs.push('$' + ijname + ': ' + err)
|
|
1515
|
+
return NONE
|
|
1516
|
+
}
|
|
1517
|
+
const tkey = getelem(inj.path, -2)
|
|
1518
|
+
const target = getelem(inj.nodes, -2, () => getelem(inj.nodes, -1))
|
|
1519
|
+
const cinj = injectChild(child, store, inj)
|
|
1520
|
+
const resolved = cinj.val
|
|
1521
|
+
const out = apply(resolved, store, cinj)
|
|
1522
|
+
setprop(target, tkey, out)
|
|
1523
|
+
return out
|
|
1403
1524
|
}
|
|
1404
1525
|
// Transform data using spec.
|
|
1405
1526
|
// Only operates on static JSON-like data.
|
|
1406
1527
|
// Arrays are treated as if they are objects with indices as keys.
|
|
1407
|
-
function transform(
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1528
|
+
function transform(
|
|
1529
|
+
data, // Source data to transform into new data (original not mutated)
|
|
1530
|
+
spec, // Transform specification; output follows this shape
|
|
1531
|
+
injdef,
|
|
1532
|
+
) {
|
|
1533
|
+
// Clone the spec so that the clone can be modified in place as the transform result.
|
|
1534
|
+
const origspec = spec
|
|
1535
|
+
spec = clone(origspec)
|
|
1536
|
+
const extra = injdef?.extra
|
|
1537
|
+
const collect = null != injdef?.errs
|
|
1538
|
+
const errs = injdef?.errs || []
|
|
1539
|
+
const extraTransforms = {}
|
|
1540
|
+
const extraData =
|
|
1541
|
+
null == extra
|
|
1542
|
+
? NONE
|
|
1543
|
+
: items(extra).reduce(
|
|
1544
|
+
(a, n) => (n[0].startsWith(S_DS) ? (extraTransforms[n[0]] = n[1]) : (a[n[0]] = n[1]), a),
|
|
1545
|
+
{},
|
|
1546
|
+
)
|
|
1547
|
+
const dataClone = merge([isempty(extraData) ? NONE : clone(extraData), clone(data)])
|
|
1548
|
+
// Define a top level store that provides transform operations.
|
|
1549
|
+
const store = merge(
|
|
1550
|
+
[
|
|
1551
|
+
{
|
|
1552
|
+
// The inject function recognises this special location for the root of the source data.
|
|
1553
|
+
// NOTE: to escape data that contains "`$FOO`" keys at the top level,
|
|
1554
|
+
// place that data inside a holding map: { myholder: mydata }.
|
|
1555
|
+
$TOP: dataClone,
|
|
1556
|
+
$SPEC: () => origspec,
|
|
1557
|
+
// Escape backtick (this also works inside backticks).
|
|
1558
|
+
$BT: () => S_BT,
|
|
1559
|
+
// Escape dollar sign (this also works inside backticks).
|
|
1560
|
+
$DS: () => S_DS,
|
|
1561
|
+
// Insert current date and time as an ISO string.
|
|
1562
|
+
$WHEN: () => new Date().toISOString(),
|
|
1563
|
+
$DELETE: transform_DELETE,
|
|
1564
|
+
$COPY: transform_COPY,
|
|
1565
|
+
$KEY: transform_KEY,
|
|
1566
|
+
$ANNO: transform_ANNO,
|
|
1567
|
+
$MERGE: transform_MERGE,
|
|
1568
|
+
$EACH: transform_EACH,
|
|
1569
|
+
$PACK: transform_PACK,
|
|
1570
|
+
$REF: transform_REF,
|
|
1571
|
+
$FORMAT: transform_FORMAT,
|
|
1572
|
+
$APPLY: transform_APPLY,
|
|
1573
|
+
},
|
|
1574
|
+
// Custom extra transforms, if any.
|
|
1575
|
+
extraTransforms,
|
|
1576
|
+
{
|
|
1577
|
+
$ERRS: errs,
|
|
1578
|
+
},
|
|
1579
|
+
],
|
|
1580
|
+
1,
|
|
1581
|
+
)
|
|
1582
|
+
const out = inject(spec, store, injdef)
|
|
1583
|
+
const generr = 0 < size(errs) && !collect
|
|
1584
|
+
if (generr) {
|
|
1585
|
+
throw new Error(join(errs, ' | '))
|
|
1586
|
+
}
|
|
1587
|
+
return out
|
|
1460
1588
|
}
|
|
1461
1589
|
// A required string value. NOTE: Rejects empty strings.
|
|
1462
1590
|
const validate_STRING = (inj) => {
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1591
|
+
let out = _lookup(inj.dparent, inj.key)
|
|
1592
|
+
const t = typify(out)
|
|
1593
|
+
if (0 === (T_string & t)) {
|
|
1594
|
+
let msg = _invalidTypeMsg(inj.path, S_string, t, out, 'V1010')
|
|
1595
|
+
inj.errs.push(msg)
|
|
1596
|
+
return NONE
|
|
1597
|
+
}
|
|
1598
|
+
if (S_MT === out) {
|
|
1599
|
+
let msg = 'Empty string at ' + pathify(inj.path, 1)
|
|
1600
|
+
inj.errs.push(msg)
|
|
1601
|
+
return NONE
|
|
1602
|
+
}
|
|
1603
|
+
return out
|
|
1476
1604
|
}
|
|
1477
1605
|
const validate_TYPE = (inj, _val, ref) => {
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1606
|
+
const tname = slice(ref, 1).toLowerCase()
|
|
1607
|
+
const typev = 1 << (31 - TYPENAME.indexOf(tname))
|
|
1608
|
+
let out = _lookup(inj.dparent, inj.key)
|
|
1609
|
+
const t = typify(out)
|
|
1610
|
+
// console.log('TYPE', tname, typev, tn(typev), 'O=', t, tn(t), out, 'C=', t & typev)
|
|
1611
|
+
if (0 === (t & typev)) {
|
|
1612
|
+
inj.errs.push(_invalidTypeMsg(inj.path, tname, t, out, 'V1001'))
|
|
1613
|
+
return NONE
|
|
1614
|
+
}
|
|
1615
|
+
return out
|
|
1488
1616
|
}
|
|
1489
1617
|
// Allow any value.
|
|
1490
1618
|
const validate_ANY = (inj) => {
|
|
1491
|
-
|
|
1492
|
-
|
|
1619
|
+
let out = _lookup(inj.dparent, inj.key)
|
|
1620
|
+
return out
|
|
1493
1621
|
}
|
|
1494
1622
|
// Specify child values for map or list.
|
|
1495
1623
|
// Map syntax: {'`$CHILD`': child-template }
|
|
1496
1624
|
// List syntax: ['`$CHILD`', child-template ]
|
|
1497
1625
|
const validate_CHILD = (inj) => {
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
inj.setval(NONE)
|
|
1521
|
-
return NONE
|
|
1522
|
-
}
|
|
1523
|
-
// List syntax.
|
|
1524
|
-
if (M_VAL === mode) {
|
|
1525
|
-
if (!islist(parent)) {
|
|
1526
|
-
// $CHILD was not inside a list.
|
|
1527
|
-
inj.errs.push('Invalid $CHILD as value')
|
|
1528
|
-
return NONE
|
|
1529
|
-
}
|
|
1530
|
-
const childtm = getprop(parent, 1)
|
|
1531
|
-
if (NONE === inj.dparent) {
|
|
1532
|
-
// Empty list as default.
|
|
1533
|
-
// parent.length = 0
|
|
1534
|
-
slice(parent, 0, 0, true)
|
|
1535
|
-
return NONE
|
|
1536
|
-
}
|
|
1537
|
-
if (!islist(inj.dparent)) {
|
|
1538
|
-
const msg = _invalidTypeMsg(slice(inj.path, -1), S_list, typify(inj.dparent), inj.dparent, 'V0230')
|
|
1539
|
-
inj.errs.push(msg)
|
|
1540
|
-
inj.keyI = size(parent)
|
|
1541
|
-
return inj.dparent
|
|
1542
|
-
}
|
|
1543
|
-
// Clone children abd reset inj key index.
|
|
1544
|
-
// The inject child loop will now iterate over the cloned children,
|
|
1545
|
-
// validating them againt the current list values.
|
|
1546
|
-
items(inj.dparent, (n) => setprop(parent, n[0], clone(childtm)))
|
|
1547
|
-
slice(parent, 0, inj.dparent.length, true)
|
|
1548
|
-
inj.keyI = 0
|
|
1549
|
-
const out = getprop(inj.dparent, 0)
|
|
1550
|
-
return out
|
|
1551
|
-
}
|
|
1626
|
+
const { mode, key, parent, keys, path } = inj
|
|
1627
|
+
// Setup data structures for validation by cloning child template.
|
|
1628
|
+
// Map syntax.
|
|
1629
|
+
if (M_KEYPRE === mode) {
|
|
1630
|
+
const childtm = _lookup(parent, key)
|
|
1631
|
+
// Get corresponding current object.
|
|
1632
|
+
const pkey = getelem(path, -2)
|
|
1633
|
+
let tval = getprop(inj.dparent, pkey)
|
|
1634
|
+
if (NONE == tval) {
|
|
1635
|
+
tval = {}
|
|
1636
|
+
} else if (!ismap(tval)) {
|
|
1637
|
+
inj.errs.push(_invalidTypeMsg(slice(inj.path, -1), S_object, typify(tval), tval), 'V0220')
|
|
1638
|
+
return NONE
|
|
1639
|
+
}
|
|
1640
|
+
const ckeys = keysof(tval)
|
|
1641
|
+
for (let ckey of ckeys) {
|
|
1642
|
+
setprop(parent, ckey, clone(childtm))
|
|
1643
|
+
// NOTE: modifying inj! This extends the child value loop in inject.
|
|
1644
|
+
keys.push(ckey)
|
|
1645
|
+
}
|
|
1646
|
+
// Remove $CHILD to cleanup ouput.
|
|
1647
|
+
inj.setval(NONE)
|
|
1552
1648
|
return NONE
|
|
1649
|
+
}
|
|
1650
|
+
// List syntax.
|
|
1651
|
+
if (M_VAL === mode) {
|
|
1652
|
+
if (!islist(parent)) {
|
|
1653
|
+
// $CHILD was not inside a list.
|
|
1654
|
+
inj.errs.push('Invalid $CHILD as value')
|
|
1655
|
+
return NONE
|
|
1656
|
+
}
|
|
1657
|
+
const childtm = getprop(parent, 1)
|
|
1658
|
+
if (NONE === inj.dparent) {
|
|
1659
|
+
// Empty list as default.
|
|
1660
|
+
// parent.length = 0
|
|
1661
|
+
slice(parent, 0, 0, true)
|
|
1662
|
+
return NONE
|
|
1663
|
+
}
|
|
1664
|
+
if (!islist(inj.dparent)) {
|
|
1665
|
+
const msg = _invalidTypeMsg(
|
|
1666
|
+
slice(inj.path, -1),
|
|
1667
|
+
S_list,
|
|
1668
|
+
typify(inj.dparent),
|
|
1669
|
+
inj.dparent,
|
|
1670
|
+
'V0230',
|
|
1671
|
+
)
|
|
1672
|
+
inj.errs.push(msg)
|
|
1673
|
+
inj.keyI = size(parent)
|
|
1674
|
+
return inj.dparent
|
|
1675
|
+
}
|
|
1676
|
+
// Clone children and reset inj key index.
|
|
1677
|
+
// The inject child loop will now iterate over the cloned children,
|
|
1678
|
+
// validating them against the current list values.
|
|
1679
|
+
items(inj.dparent, (n) => setprop(parent, n[0], clone(childtm)))
|
|
1680
|
+
slice(parent, 0, inj.dparent.length, true)
|
|
1681
|
+
// NOTE: modifying inj! This extends the child value loop in inject
|
|
1682
|
+
// to cover every cloned child.
|
|
1683
|
+
for (let ckeyI = size(keys); ckeyI < size(parent); ckeyI++) {
|
|
1684
|
+
keys.push(strkey(ckeyI))
|
|
1685
|
+
}
|
|
1686
|
+
// Restart the child value loop at the first element (the loop
|
|
1687
|
+
// increments keyI on resume) so that the first element is also
|
|
1688
|
+
// validated against the child template.
|
|
1689
|
+
inj.keyI = -1
|
|
1690
|
+
// SKIP leaves the cloned child template in place at the first
|
|
1691
|
+
// element so the resumed loop can validate it.
|
|
1692
|
+
return SKIP
|
|
1693
|
+
}
|
|
1694
|
+
return NONE
|
|
1553
1695
|
}
|
|
1554
1696
|
// TODO: implement SOME, ALL
|
|
1555
1697
|
// FIX: ONE should mean exactly one, not at least one (=SOME)
|
|
@@ -1557,167 +1699,201 @@ const validate_CHILD = (inj) => {
|
|
|
1557
1699
|
// Match at least one of the specified shapes.
|
|
1558
1700
|
// Syntax: ['`$ONE`', alt0, alt1, ...]
|
|
1559
1701
|
const validate_ONE = (inj, _val, _ref, store) => {
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
// Clean up structure, replacing [$ONE, ...] with current
|
|
1571
|
-
inj.setval(inj.dparent, 2)
|
|
1572
|
-
inj.path = slice(inj.path, -1)
|
|
1573
|
-
inj.key = getelem(inj.path, -1)
|
|
1574
|
-
let tvals = slice(parent, 1)
|
|
1575
|
-
if (0 === size(tvals)) {
|
|
1576
|
-
inj.errs.push('The $ONE validator at field ' +
|
|
1577
|
-
pathify(inj.path, 1, 1) +
|
|
1578
|
-
' must have at least one argument.')
|
|
1579
|
-
return
|
|
1580
|
-
}
|
|
1581
|
-
// See if we can find a match.
|
|
1582
|
-
for (let tval of tvals) {
|
|
1583
|
-
// If match, then errs.length = 0
|
|
1584
|
-
let terrs = []
|
|
1585
|
-
const vstore = merge([{}, store], 1)
|
|
1586
|
-
vstore.$TOP = inj.dparent
|
|
1587
|
-
const vcurrent = validate(inj.dparent, tval, {
|
|
1588
|
-
extra: vstore,
|
|
1589
|
-
errs: terrs,
|
|
1590
|
-
meta: inj.meta,
|
|
1591
|
-
})
|
|
1592
|
-
inj.setval(vcurrent, -2)
|
|
1593
|
-
// Accept current value if there was a match
|
|
1594
|
-
if (0 === size(terrs)) {
|
|
1595
|
-
return
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
// There was no match.
|
|
1599
|
-
const valdesc = replace(join(items(tvals, (n) => stringify(n[1])), ', '), R_TRANSFORM_NAME, (_m, p1) => p1.toLowerCase())
|
|
1600
|
-
inj.errs.push(_invalidTypeMsg(inj.path, (1 < size(tvals) ? 'one of ' : '') + valdesc, typify(inj.dparent), inj.dparent, 'V0210'))
|
|
1702
|
+
const { mode, parent, keyI } = inj
|
|
1703
|
+
// Only operate in val mode, since parent is a list.
|
|
1704
|
+
if (M_VAL === mode) {
|
|
1705
|
+
if (!islist(parent) || 0 !== keyI) {
|
|
1706
|
+
inj.errs.push(
|
|
1707
|
+
'The $ONE validator at field ' +
|
|
1708
|
+
pathify(inj.path, 1, 1) +
|
|
1709
|
+
' must be the first element of an array.',
|
|
1710
|
+
)
|
|
1711
|
+
return
|
|
1601
1712
|
}
|
|
1713
|
+
inj.keyI = size(inj.keys)
|
|
1714
|
+
// Clean up structure, replacing [$ONE, ...] with current
|
|
1715
|
+
inj.setval(inj.dparent, 2)
|
|
1716
|
+
inj.path = slice(inj.path, -1)
|
|
1717
|
+
inj.key = getelem(inj.path, -1)
|
|
1718
|
+
let tvals = slice(parent, 1)
|
|
1719
|
+
if (0 === size(tvals)) {
|
|
1720
|
+
inj.errs.push(
|
|
1721
|
+
'The $ONE validator at field ' +
|
|
1722
|
+
pathify(inj.path, 1, 1) +
|
|
1723
|
+
' must have at least one argument.',
|
|
1724
|
+
)
|
|
1725
|
+
return
|
|
1726
|
+
}
|
|
1727
|
+
// See if we can find a match.
|
|
1728
|
+
for (let tval of tvals) {
|
|
1729
|
+
// If match, then errs.length = 0
|
|
1730
|
+
let terrs = []
|
|
1731
|
+
const vstore = merge([{}, store], 1)
|
|
1732
|
+
vstore.$TOP = inj.dparent
|
|
1733
|
+
const vcurrent = validate(inj.dparent, tval, {
|
|
1734
|
+
extra: vstore,
|
|
1735
|
+
errs: terrs,
|
|
1736
|
+
meta: inj.meta,
|
|
1737
|
+
})
|
|
1738
|
+
inj.setval(vcurrent, -2)
|
|
1739
|
+
// Accept current value if there was a match
|
|
1740
|
+
if (0 === size(terrs)) {
|
|
1741
|
+
return
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
// There was no match.
|
|
1745
|
+
const valdesc = replace(
|
|
1746
|
+
join(
|
|
1747
|
+
items(tvals, (n) => stringify(n[1])),
|
|
1748
|
+
', ',
|
|
1749
|
+
),
|
|
1750
|
+
R_TRANSFORM_NAME,
|
|
1751
|
+
(_m, p1) => p1.toLowerCase(),
|
|
1752
|
+
)
|
|
1753
|
+
inj.errs.push(
|
|
1754
|
+
_invalidTypeMsg(
|
|
1755
|
+
inj.path,
|
|
1756
|
+
(1 < size(tvals) ? 'one of ' : '') + valdesc,
|
|
1757
|
+
typify(inj.dparent),
|
|
1758
|
+
inj.dparent,
|
|
1759
|
+
'V0210',
|
|
1760
|
+
),
|
|
1761
|
+
)
|
|
1762
|
+
}
|
|
1602
1763
|
}
|
|
1603
1764
|
const validate_EXACT = (inj) => {
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
// Clean up structure, replacing [$EXACT, ...] with current data parent
|
|
1615
|
-
inj.setval(inj.dparent, 2)
|
|
1616
|
-
// inj.path = slice(inj.path, 0, size(inj.path) - 1)
|
|
1617
|
-
inj.path = slice(inj.path, 0, -1)
|
|
1618
|
-
inj.key = getelem(inj.path, -1)
|
|
1619
|
-
let tvals = slice(parent, 1)
|
|
1620
|
-
if (0 === size(tvals)) {
|
|
1621
|
-
inj.errs.push('The $EXACT validator at field ' +
|
|
1622
|
-
pathify(inj.path, 1, 1) +
|
|
1623
|
-
' must have at least one argument.')
|
|
1624
|
-
return
|
|
1625
|
-
}
|
|
1626
|
-
// See if we can find an exact value match.
|
|
1627
|
-
let currentstr = undefined
|
|
1628
|
-
for (let tval of tvals) {
|
|
1629
|
-
let exactmatch = tval === inj.dparent
|
|
1630
|
-
if (!exactmatch && isnode(tval)) {
|
|
1631
|
-
currentstr = undefined === currentstr ? stringify(inj.dparent) : currentstr
|
|
1632
|
-
const tvalstr = stringify(tval)
|
|
1633
|
-
exactmatch = tvalstr === currentstr
|
|
1634
|
-
}
|
|
1635
|
-
if (exactmatch) {
|
|
1636
|
-
return
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
// There was no match.
|
|
1640
|
-
const valdesc = replace(join(items(tvals, (n) => stringify(n[1])), ', '), R_TRANSFORM_NAME, (_m, p1) => p1.toLowerCase())
|
|
1641
|
-
inj.errs.push(_invalidTypeMsg(inj.path, (1 < size(inj.path) ? '' : 'value ') +
|
|
1642
|
-
'exactly equal to ' + (1 === size(tvals) ? '' : 'one of ') + valdesc, typify(inj.dparent), inj.dparent, 'V0110'))
|
|
1643
|
-
}
|
|
1644
|
-
else {
|
|
1645
|
-
delprop(parent, key)
|
|
1765
|
+
const { mode, parent, key, keyI } = inj
|
|
1766
|
+
// Only operate in val mode, since parent is a list.
|
|
1767
|
+
if (M_VAL === mode) {
|
|
1768
|
+
if (!islist(parent) || 0 !== keyI) {
|
|
1769
|
+
inj.errs.push(
|
|
1770
|
+
'The $EXACT validator at field ' +
|
|
1771
|
+
pathify(inj.path, 1, 1) +
|
|
1772
|
+
' must be the first element of an array.',
|
|
1773
|
+
)
|
|
1774
|
+
return
|
|
1646
1775
|
}
|
|
1776
|
+
inj.keyI = size(inj.keys)
|
|
1777
|
+
// Clean up structure, replacing [$EXACT, ...] with current data parent
|
|
1778
|
+
inj.setval(inj.dparent, 2)
|
|
1779
|
+
// inj.path = slice(inj.path, 0, size(inj.path) - 1)
|
|
1780
|
+
inj.path = slice(inj.path, 0, -1)
|
|
1781
|
+
inj.key = getelem(inj.path, -1)
|
|
1782
|
+
let tvals = slice(parent, 1)
|
|
1783
|
+
if (0 === size(tvals)) {
|
|
1784
|
+
inj.errs.push(
|
|
1785
|
+
'The $EXACT validator at field ' +
|
|
1786
|
+
pathify(inj.path, 1, 1) +
|
|
1787
|
+
' must have at least one argument.',
|
|
1788
|
+
)
|
|
1789
|
+
return
|
|
1790
|
+
}
|
|
1791
|
+
// See if we can find an exact value match.
|
|
1792
|
+
let currentstr = undefined
|
|
1793
|
+
for (let tval of tvals) {
|
|
1794
|
+
let exactmatch = tval === inj.dparent
|
|
1795
|
+
if (!exactmatch && isnode(tval)) {
|
|
1796
|
+
currentstr = undefined === currentstr ? stringify(inj.dparent) : currentstr
|
|
1797
|
+
const tvalstr = stringify(tval)
|
|
1798
|
+
exactmatch = tvalstr === currentstr
|
|
1799
|
+
}
|
|
1800
|
+
if (exactmatch) {
|
|
1801
|
+
return
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
// There was no match.
|
|
1805
|
+
const valdesc = replace(
|
|
1806
|
+
join(
|
|
1807
|
+
items(tvals, (n) => stringify(n[1])),
|
|
1808
|
+
', ',
|
|
1809
|
+
),
|
|
1810
|
+
R_TRANSFORM_NAME,
|
|
1811
|
+
(_m, p1) => p1.toLowerCase(),
|
|
1812
|
+
)
|
|
1813
|
+
inj.errs.push(
|
|
1814
|
+
_invalidTypeMsg(
|
|
1815
|
+
inj.path,
|
|
1816
|
+
(1 < size(inj.path) ? '' : 'value ') +
|
|
1817
|
+
'exactly equal to ' +
|
|
1818
|
+
(1 === size(tvals) ? '' : 'one of ') +
|
|
1819
|
+
valdesc,
|
|
1820
|
+
typify(inj.dparent),
|
|
1821
|
+
inj.dparent,
|
|
1822
|
+
'V0110',
|
|
1823
|
+
),
|
|
1824
|
+
)
|
|
1825
|
+
} else {
|
|
1826
|
+
delprop(parent, key)
|
|
1827
|
+
}
|
|
1647
1828
|
}
|
|
1648
1829
|
// This is the "modify" argument to inject. Use this to perform
|
|
1649
1830
|
// generic validation. Runs *after* any special commands.
|
|
1650
1831
|
const _validation = (pval, key, parent, inj) => {
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
return
|
|
1656
|
-
}
|
|
1657
|
-
// select needs exact matches
|
|
1658
|
-
const exact = getprop(inj.meta, S_BEXACT, false)
|
|
1659
|
-
// Current val to verify.
|
|
1660
|
-
const cval = getprop(inj.dparent, key)
|
|
1661
|
-
if (NONE === inj || (!exact && NONE === cval)) {
|
|
1662
|
-
return
|
|
1663
|
-
}
|
|
1664
|
-
const ptype = typify(pval)
|
|
1665
|
-
// Delete any special commands remaining.
|
|
1666
|
-
if (0 < (T_string & ptype) && pval.includes(S_DS)) {
|
|
1667
|
-
return
|
|
1668
|
-
}
|
|
1669
|
-
const ctype = typify(cval)
|
|
1670
|
-
// Type mismatch.
|
|
1671
|
-
if (ptype !== ctype && NONE !== pval) {
|
|
1672
|
-
inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0010'))
|
|
1673
|
-
return
|
|
1674
|
-
}
|
|
1675
|
-
if (ismap(cval)) {
|
|
1676
|
-
if (!ismap(pval)) {
|
|
1677
|
-
inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0020'))
|
|
1678
|
-
return
|
|
1679
|
-
}
|
|
1680
|
-
const ckeys = keysof(cval)
|
|
1681
|
-
const pkeys = keysof(pval)
|
|
1682
|
-
// Empty spec object {} means object can be open (any keys).
|
|
1683
|
-
if (0 < size(pkeys) && true !== getprop(pval, '`$OPEN`')) {
|
|
1684
|
-
const badkeys = []
|
|
1685
|
-
for (let ckey of ckeys) {
|
|
1686
|
-
if (!haskey(pval, ckey)) {
|
|
1687
|
-
badkeys.push(ckey)
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
// Closed object, so reject extra keys not in shape.
|
|
1691
|
-
if (0 < size(badkeys)) {
|
|
1692
|
-
const msg = 'Unexpected keys at field ' + pathify(inj.path, 1) + S_VIZ + join(badkeys, ', ')
|
|
1693
|
-
inj.errs.push(msg)
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
else {
|
|
1697
|
-
// Object is open, so merge in extra keys.
|
|
1698
|
-
merge([pval, cval])
|
|
1699
|
-
if (isnode(pval)) {
|
|
1700
|
-
delprop(pval, '`$OPEN`')
|
|
1701
|
-
}
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
else if (islist(cval)) {
|
|
1705
|
-
if (!islist(pval)) {
|
|
1706
|
-
inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0030'))
|
|
1707
|
-
}
|
|
1708
|
-
}
|
|
1709
|
-
else if (exact) {
|
|
1710
|
-
if (cval !== pval) {
|
|
1711
|
-
const pathmsg = 1 < size(inj.path) ? 'at field ' + pathify(inj.path, 1) + S_VIZ : S_MT
|
|
1712
|
-
inj.errs.push('Value ' + pathmsg + cval +
|
|
1713
|
-
' should equal ' + pval + S_DT)
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
|
-
else {
|
|
1717
|
-
// Spec value was a default, copy over data
|
|
1718
|
-
setprop(parent, key, cval)
|
|
1719
|
-
}
|
|
1832
|
+
if (NONE === inj) {
|
|
1833
|
+
return
|
|
1834
|
+
}
|
|
1835
|
+
if (SKIP === pval) {
|
|
1720
1836
|
return
|
|
1837
|
+
}
|
|
1838
|
+
// select needs exact matches
|
|
1839
|
+
const exact = getprop(inj.meta, S_BEXACT, false)
|
|
1840
|
+
// Current val to verify.
|
|
1841
|
+
const cval = getprop(inj.dparent, key)
|
|
1842
|
+
if (NONE === inj || (!exact && NONE === cval)) {
|
|
1843
|
+
return
|
|
1844
|
+
}
|
|
1845
|
+
const ptype = typify(pval)
|
|
1846
|
+
// Delete any special commands remaining.
|
|
1847
|
+
if (0 < (T_string & ptype) && pval.includes(S_DS)) {
|
|
1848
|
+
return
|
|
1849
|
+
}
|
|
1850
|
+
const ctype = typify(cval)
|
|
1851
|
+
// Type mismatch.
|
|
1852
|
+
if (ptype !== ctype && NONE !== pval) {
|
|
1853
|
+
inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0010'))
|
|
1854
|
+
return
|
|
1855
|
+
}
|
|
1856
|
+
if (ismap(cval)) {
|
|
1857
|
+
if (!ismap(pval)) {
|
|
1858
|
+
inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0020'))
|
|
1859
|
+
return
|
|
1860
|
+
}
|
|
1861
|
+
const ckeys = keysof(cval)
|
|
1862
|
+
const pkeys = keysof(pval)
|
|
1863
|
+
// Empty spec object {} means object can be open (any keys).
|
|
1864
|
+
if (0 < size(pkeys) && true !== getprop(pval, '`$OPEN`')) {
|
|
1865
|
+
const badkeys = []
|
|
1866
|
+
for (let ckey of ckeys) {
|
|
1867
|
+
if (NONE === _lookup(pval, ckey)) {
|
|
1868
|
+
badkeys.push(ckey)
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
// Closed object, so reject extra keys not in shape.
|
|
1872
|
+
if (0 < size(badkeys)) {
|
|
1873
|
+
const msg = 'Unexpected keys at field ' + pathify(inj.path, 1) + S_VIZ + join(badkeys, ', ')
|
|
1874
|
+
inj.errs.push(msg)
|
|
1875
|
+
}
|
|
1876
|
+
} else {
|
|
1877
|
+
// Object is open, so merge in extra keys.
|
|
1878
|
+
merge([pval, cval])
|
|
1879
|
+
if (isnode(pval)) {
|
|
1880
|
+
delprop(pval, '`$OPEN`')
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
} else if (islist(cval)) {
|
|
1884
|
+
if (!islist(pval)) {
|
|
1885
|
+
inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0030'))
|
|
1886
|
+
}
|
|
1887
|
+
} else if (exact) {
|
|
1888
|
+
if (cval !== pval) {
|
|
1889
|
+
const pathmsg = 1 < size(inj.path) ? 'at field ' + pathify(inj.path, 1) + S_VIZ : S_MT
|
|
1890
|
+
inj.errs.push('Value ' + pathmsg + cval + ' should equal ' + pval + S_DT)
|
|
1891
|
+
}
|
|
1892
|
+
} else {
|
|
1893
|
+
// Spec value was a default, copy over data
|
|
1894
|
+
setprop(parent, key, cval)
|
|
1895
|
+
}
|
|
1896
|
+
return
|
|
1721
1897
|
}
|
|
1722
1898
|
// Validate a data structure against a shape specification. The shape
|
|
1723
1899
|
// specification follows the "by example" principle. Plain data in
|
|
@@ -1729,305 +1905,329 @@ const _validation = (pval, key, parent, inj) => {
|
|
|
1729
1905
|
// provided to specify required values. Thus shape {a:'`$STRING`'}
|
|
1730
1906
|
// validates {a:'A'} but not {a:1}. Empty map or list means the node
|
|
1731
1907
|
// is open, and if missing an empty default is inserted.
|
|
1732
|
-
function validate(
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1908
|
+
function validate(
|
|
1909
|
+
data, // Source data to transform into new data (original not mutated)
|
|
1910
|
+
spec, // Transform specification; output follows this shape
|
|
1911
|
+
injdef,
|
|
1912
|
+
) {
|
|
1913
|
+
const extra = injdef?.extra
|
|
1914
|
+
const collect = null != injdef?.errs
|
|
1915
|
+
const errs = injdef?.errs || []
|
|
1916
|
+
const store = merge(
|
|
1917
|
+
[
|
|
1918
|
+
{
|
|
1919
|
+
// Remove the transform commands.
|
|
1920
|
+
$DELETE: null,
|
|
1921
|
+
$COPY: null,
|
|
1922
|
+
$KEY: null,
|
|
1923
|
+
$META: null,
|
|
1924
|
+
$MERGE: null,
|
|
1925
|
+
$EACH: null,
|
|
1926
|
+
$PACK: null,
|
|
1927
|
+
$STRING: validate_STRING,
|
|
1928
|
+
$NUMBER: validate_TYPE,
|
|
1929
|
+
$INTEGER: validate_TYPE,
|
|
1930
|
+
$DECIMAL: validate_TYPE,
|
|
1931
|
+
$BOOLEAN: validate_TYPE,
|
|
1932
|
+
$NULL: validate_TYPE,
|
|
1933
|
+
$NIL: validate_TYPE,
|
|
1934
|
+
$MAP: validate_TYPE,
|
|
1935
|
+
$LIST: validate_TYPE,
|
|
1936
|
+
$FUNCTION: validate_TYPE,
|
|
1937
|
+
$INSTANCE: validate_TYPE,
|
|
1938
|
+
$ANY: validate_ANY,
|
|
1939
|
+
$CHILD: validate_CHILD,
|
|
1940
|
+
$ONE: validate_ONE,
|
|
1941
|
+
$EXACT: validate_EXACT,
|
|
1942
|
+
},
|
|
1943
|
+
getdef(extra, {}),
|
|
1944
|
+
// A special top level value to collect errors.
|
|
1945
|
+
// NOTE: collecterrs parameter always wins.
|
|
1946
|
+
{
|
|
1947
|
+
$ERRS: errs,
|
|
1948
|
+
},
|
|
1949
|
+
],
|
|
1950
|
+
1,
|
|
1951
|
+
)
|
|
1952
|
+
let meta = getprop(injdef, 'meta', {})
|
|
1953
|
+
setprop(meta, S_BEXACT, getprop(meta, S_BEXACT, false))
|
|
1954
|
+
const out = transform(data, spec, {
|
|
1955
|
+
meta,
|
|
1956
|
+
extra: store,
|
|
1957
|
+
modify: _validation,
|
|
1958
|
+
handler: _validatehandler,
|
|
1959
|
+
errs,
|
|
1960
|
+
})
|
|
1961
|
+
const generr = 0 < size(errs) && !collect
|
|
1962
|
+
if (generr) {
|
|
1963
|
+
throw new Error(join(errs, ' | '))
|
|
1964
|
+
}
|
|
1965
|
+
return out
|
|
1785
1966
|
}
|
|
1786
1967
|
const select_AND = (inj, _val, _ref, store) => {
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1968
|
+
if (M_KEYPRE === inj.mode) {
|
|
1969
|
+
const terms = _lookup(inj.parent, inj.key)
|
|
1970
|
+
const ppath = slice(inj.path, -1)
|
|
1971
|
+
const point = getpath(store, ppath)
|
|
1972
|
+
const vstore = merge([{}, store], 1)
|
|
1973
|
+
vstore.$TOP = point
|
|
1974
|
+
for (let term of terms) {
|
|
1975
|
+
let terrs = []
|
|
1976
|
+
validate(point, term, {
|
|
1977
|
+
extra: vstore,
|
|
1978
|
+
errs: terrs,
|
|
1979
|
+
meta: inj.meta,
|
|
1980
|
+
})
|
|
1981
|
+
if (0 != size(terrs)) {
|
|
1982
|
+
inj.errs.push(
|
|
1983
|
+
'AND:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms),
|
|
1984
|
+
)
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
const gkey = getelem(inj.path, -2)
|
|
1988
|
+
const gp = getelem(inj.nodes, -2)
|
|
1989
|
+
setprop(gp, gkey, point)
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
const select_OR = (inj, _val, _ref, store) => {
|
|
1993
|
+
if (M_KEYPRE === inj.mode) {
|
|
1994
|
+
const terms = _lookup(inj.parent, inj.key)
|
|
1995
|
+
const ppath = slice(inj.path, -1)
|
|
1996
|
+
const point = getpath(store, ppath)
|
|
1997
|
+
const vstore = merge([{}, store], 1)
|
|
1998
|
+
vstore.$TOP = point
|
|
1999
|
+
for (let term of terms) {
|
|
2000
|
+
let terrs = []
|
|
2001
|
+
validate(point, term, {
|
|
2002
|
+
extra: vstore,
|
|
2003
|
+
errs: terrs,
|
|
2004
|
+
meta: inj.meta,
|
|
2005
|
+
})
|
|
2006
|
+
if (0 === size(terrs)) {
|
|
1804
2007
|
const gkey = getelem(inj.path, -2)
|
|
1805
2008
|
const gp = getelem(inj.nodes, -2)
|
|
1806
2009
|
setprop(gp, gkey, point)
|
|
2010
|
+
return
|
|
2011
|
+
}
|
|
1807
2012
|
}
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
if (M_KEYPRE === inj.mode) {
|
|
1811
|
-
const terms = getprop(inj.parent, inj.key)
|
|
1812
|
-
const ppath = slice(inj.path, -1)
|
|
1813
|
-
const point = getpath(store, ppath)
|
|
1814
|
-
const vstore = merge([{}, store], 1)
|
|
1815
|
-
vstore.$TOP = point
|
|
1816
|
-
for (let term of terms) {
|
|
1817
|
-
let terrs = []
|
|
1818
|
-
validate(point, term, {
|
|
1819
|
-
extra: vstore,
|
|
1820
|
-
errs: terrs,
|
|
1821
|
-
meta: inj.meta,
|
|
1822
|
-
})
|
|
1823
|
-
if (0 === size(terrs)) {
|
|
1824
|
-
const gkey = getelem(inj.path, -2)
|
|
1825
|
-
const gp = getelem(inj.nodes, -2)
|
|
1826
|
-
setprop(gp, gkey, point)
|
|
1827
|
-
return
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
inj.errs.push('OR:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms))
|
|
1831
|
-
}
|
|
2013
|
+
inj.errs.push('OR:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms))
|
|
2014
|
+
}
|
|
1832
2015
|
}
|
|
1833
2016
|
const select_NOT = (inj, _val, _ref, store) => {
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
}
|
|
1849
|
-
const gkey = getelem(inj.path, -2)
|
|
1850
|
-
const gp = getelem(inj.nodes, -2)
|
|
1851
|
-
setprop(gp, gkey, point)
|
|
2017
|
+
if (M_KEYPRE === inj.mode) {
|
|
2018
|
+
const term = _lookup(inj.parent, inj.key)
|
|
2019
|
+
const ppath = slice(inj.path, -1)
|
|
2020
|
+
const point = getpath(store, ppath)
|
|
2021
|
+
const vstore = merge([{}, store], 1)
|
|
2022
|
+
vstore.$TOP = point
|
|
2023
|
+
let terrs = []
|
|
2024
|
+
validate(point, term, {
|
|
2025
|
+
extra: vstore,
|
|
2026
|
+
errs: terrs,
|
|
2027
|
+
meta: inj.meta,
|
|
2028
|
+
})
|
|
2029
|
+
if (0 == size(terrs)) {
|
|
2030
|
+
inj.errs.push('NOT:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(term))
|
|
1852
2031
|
}
|
|
2032
|
+
const gkey = getelem(inj.path, -2)
|
|
2033
|
+
const gp = getelem(inj.nodes, -2)
|
|
2034
|
+
setprop(gp, gkey, point)
|
|
2035
|
+
}
|
|
1853
2036
|
}
|
|
1854
2037
|
const select_CMP = (inj, _val, ref, store) => {
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
2038
|
+
if (M_KEYPRE === inj.mode) {
|
|
2039
|
+
const term = _lookup(inj.parent, inj.key)
|
|
2040
|
+
// const src = getprop(store, inj.base, store)
|
|
2041
|
+
const gkey = getelem(inj.path, -2)
|
|
2042
|
+
// const tval = getprop(src, gkey)
|
|
2043
|
+
const ppath = slice(inj.path, -1)
|
|
2044
|
+
const point = getpath(store, ppath)
|
|
2045
|
+
let pass = false
|
|
2046
|
+
if ('$GT' === ref && point > term) {
|
|
2047
|
+
pass = true
|
|
2048
|
+
} else if ('$LT' === ref && point < term) {
|
|
2049
|
+
pass = true
|
|
2050
|
+
} else if ('$GTE' === ref && point >= term) {
|
|
2051
|
+
pass = true
|
|
2052
|
+
} else if ('$LTE' === ref && point <= term) {
|
|
2053
|
+
pass = true
|
|
2054
|
+
} else if ('$LIKE' === ref && stringify(point).match(RegExp(term))) {
|
|
2055
|
+
pass = true
|
|
2056
|
+
}
|
|
2057
|
+
if (pass) {
|
|
2058
|
+
// Update spec to match found value so that _validate does not complain.
|
|
2059
|
+
const gp = getelem(inj.nodes, -2)
|
|
2060
|
+
setprop(gp, gkey, point)
|
|
2061
|
+
} else {
|
|
2062
|
+
inj.errs.push(
|
|
2063
|
+
'CMP: ' +
|
|
2064
|
+
pathify(ppath) +
|
|
2065
|
+
S_VIZ +
|
|
2066
|
+
stringify(point) +
|
|
2067
|
+
' fail:' +
|
|
2068
|
+
ref +
|
|
2069
|
+
' ' +
|
|
2070
|
+
stringify(term),
|
|
2071
|
+
)
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
return NONE
|
|
1889
2075
|
}
|
|
1890
2076
|
// Select children from a top-level object that match a MongoDB-style query.
|
|
1891
2077
|
// Supports $and, $or, and equality comparisons.
|
|
1892
2078
|
// For arrays, children are elements; for objects, children are values.
|
|
1893
2079
|
// TODO: swap arg order for consistency
|
|
1894
2080
|
function select(children, query) {
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
})
|
|
1903
|
-
}
|
|
1904
|
-
else {
|
|
1905
|
-
children = items(children, (n) => (setprop(n[1], S_DKEY, +n[0]), n[1]))
|
|
1906
|
-
}
|
|
1907
|
-
const results = []
|
|
1908
|
-
const injdef = {
|
|
1909
|
-
errs: [],
|
|
1910
|
-
meta: { [S_BEXACT]: true },
|
|
1911
|
-
extra: {
|
|
1912
|
-
$AND: select_AND,
|
|
1913
|
-
$OR: select_OR,
|
|
1914
|
-
$NOT: select_NOT,
|
|
1915
|
-
$GT: select_CMP,
|
|
1916
|
-
$LT: select_CMP,
|
|
1917
|
-
$GTE: select_CMP,
|
|
1918
|
-
$LTE: select_CMP,
|
|
1919
|
-
$LIKE: select_CMP,
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
const q = clone(query)
|
|
1923
|
-
walk(q, (_k, v) => {
|
|
1924
|
-
if (ismap(v)) {
|
|
1925
|
-
setprop(v, '`$OPEN`', getprop(v, '`$OPEN`', true))
|
|
1926
|
-
}
|
|
1927
|
-
return v
|
|
2081
|
+
if (!isnode(children)) {
|
|
2082
|
+
return []
|
|
2083
|
+
}
|
|
2084
|
+
if (ismap(children)) {
|
|
2085
|
+
children = items(children, (n) => {
|
|
2086
|
+
setprop(n[1], S_DKEY, n[0])
|
|
2087
|
+
return n[1]
|
|
1928
2088
|
})
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
}
|
|
1936
|
-
|
|
2089
|
+
} else {
|
|
2090
|
+
children = items(children, (n) => (setprop(n[1], S_DKEY, +n[0]), n[1]))
|
|
2091
|
+
}
|
|
2092
|
+
const results = []
|
|
2093
|
+
const injdef = {
|
|
2094
|
+
errs: [],
|
|
2095
|
+
meta: { [S_BEXACT]: true },
|
|
2096
|
+
extra: {
|
|
2097
|
+
$AND: select_AND,
|
|
2098
|
+
$OR: select_OR,
|
|
2099
|
+
$NOT: select_NOT,
|
|
2100
|
+
$GT: select_CMP,
|
|
2101
|
+
$LT: select_CMP,
|
|
2102
|
+
$GTE: select_CMP,
|
|
2103
|
+
$LTE: select_CMP,
|
|
2104
|
+
$LIKE: select_CMP,
|
|
2105
|
+
},
|
|
2106
|
+
}
|
|
2107
|
+
const q = clone(query)
|
|
2108
|
+
walk(q, (_k, v) => {
|
|
2109
|
+
if (ismap(v)) {
|
|
2110
|
+
setprop(v, '`$OPEN`', getprop(v, '`$OPEN`', true))
|
|
2111
|
+
}
|
|
2112
|
+
return v
|
|
2113
|
+
})
|
|
2114
|
+
for (const child of children) {
|
|
2115
|
+
injdef.errs = []
|
|
2116
|
+
validate(child, clone(q), injdef)
|
|
2117
|
+
if (0 === size(injdef.errs)) {
|
|
2118
|
+
results.push(child)
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
return results
|
|
1937
2122
|
}
|
|
1938
2123
|
// Injection state used for recursive injection into JSON - like data structures.
|
|
1939
2124
|
class Injection {
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2125
|
+
constructor(val, parent) {
|
|
2126
|
+
this.val = val
|
|
2127
|
+
this.parent = parent
|
|
2128
|
+
this.errs = []
|
|
2129
|
+
this.dparent = NONE
|
|
2130
|
+
this.dpath = [S_DTOP]
|
|
2131
|
+
this.mode = M_VAL
|
|
2132
|
+
this.full = false
|
|
2133
|
+
this.keyI = 0
|
|
2134
|
+
this.keys = [S_DTOP]
|
|
2135
|
+
this.key = S_DTOP
|
|
2136
|
+
this.path = [S_DTOP]
|
|
2137
|
+
this.nodes = [parent]
|
|
2138
|
+
this.handler = _injecthandler
|
|
2139
|
+
this.base = S_DTOP
|
|
2140
|
+
this.meta = {}
|
|
2141
|
+
}
|
|
2142
|
+
toString(prefix) {
|
|
2143
|
+
return (
|
|
2144
|
+
'INJ' +
|
|
2145
|
+
(null == prefix ? '' : S_FS + prefix) +
|
|
2146
|
+
S_CN +
|
|
2147
|
+
pad(pathify(this.path, 1)) +
|
|
2148
|
+
MODENAME[this.mode] +
|
|
2149
|
+
(this.full ? '/full' : '') +
|
|
2150
|
+
S_CN +
|
|
2151
|
+
'key=' +
|
|
2152
|
+
this.keyI +
|
|
2153
|
+
S_FS +
|
|
2154
|
+
this.key +
|
|
2155
|
+
S_FS +
|
|
2156
|
+
S_OS +
|
|
2157
|
+
this.keys +
|
|
2158
|
+
S_CS +
|
|
2159
|
+
' p=' +
|
|
2160
|
+
stringify(this.parent, -1, 1) +
|
|
2161
|
+
' m=' +
|
|
2162
|
+
stringify(this.meta, -1, 1) +
|
|
2163
|
+
' d/' +
|
|
2164
|
+
pathify(this.dpath, 1) +
|
|
2165
|
+
'=' +
|
|
2166
|
+
stringify(this.dparent, -1, 1) +
|
|
2167
|
+
' r=' +
|
|
2168
|
+
stringify(this.nodes[0]?.[S_DTOP], -1, 1)
|
|
2169
|
+
)
|
|
2170
|
+
}
|
|
2171
|
+
descend() {
|
|
2172
|
+
this.meta.__d++
|
|
2173
|
+
const parentkey = getelem(this.path, -2)
|
|
2174
|
+
// Resolve current node in store for local paths.
|
|
2175
|
+
if (NONE === this.dparent) {
|
|
2176
|
+
// Even if there's no data, dpath should continue to match path, so that
|
|
2177
|
+
// relative paths work properly.
|
|
2178
|
+
if (1 < size(this.dpath)) {
|
|
2179
|
+
this.dpath = flatten([this.dpath, parentkey])
|
|
2180
|
+
}
|
|
2181
|
+
} else {
|
|
2182
|
+
// this.dparent is the containing node of the current store value.
|
|
2183
|
+
if (null != parentkey) {
|
|
2184
|
+
this.dparent = getprop(this.dparent, parentkey)
|
|
2185
|
+
let lastpart = getelem(this.dpath, -1)
|
|
2186
|
+
if (lastpart === '$:' + parentkey) {
|
|
2187
|
+
this.dpath = slice(this.dpath, -1)
|
|
2188
|
+
} else {
|
|
2189
|
+
this.dpath = flatten([this.dpath, parentkey])
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
// TODO: is this needed?
|
|
2194
|
+
return this.dparent
|
|
2195
|
+
}
|
|
2196
|
+
child(keyI, keys) {
|
|
2197
|
+
const key = strkey(keys[keyI])
|
|
2198
|
+
const val = this.val
|
|
2199
|
+
const cinj = new Injection(getprop(val, key), val)
|
|
2200
|
+
cinj.keyI = keyI
|
|
2201
|
+
cinj.keys = keys
|
|
2202
|
+
cinj.key = key
|
|
2203
|
+
cinj.path = flatten([getdef(this.path, []), key])
|
|
2204
|
+
cinj.nodes = flatten([getdef(this.nodes, []), [val]])
|
|
2205
|
+
cinj.mode = this.mode
|
|
2206
|
+
cinj.handler = this.handler
|
|
2207
|
+
cinj.modify = this.modify
|
|
2208
|
+
cinj.base = this.base
|
|
2209
|
+
cinj.meta = this.meta
|
|
2210
|
+
cinj.errs = this.errs
|
|
2211
|
+
cinj.prior = this
|
|
2212
|
+
cinj.dpath = flatten([this.dpath])
|
|
2213
|
+
cinj.dparent = this.dparent
|
|
2214
|
+
return cinj
|
|
2215
|
+
}
|
|
2216
|
+
setval(val, ancestor) {
|
|
2217
|
+
let parent = NONE
|
|
2218
|
+
if (null == ancestor || ancestor < 2) {
|
|
2219
|
+
parent =
|
|
2220
|
+
NONE === val
|
|
2221
|
+
? (this.parent = delprop(this.parent, this.key))
|
|
2222
|
+
: setprop(this.parent, this.key, val)
|
|
2223
|
+
} else {
|
|
2224
|
+
const aval = getelem(this.nodes, 0 - ancestor)
|
|
2225
|
+
const akey = getelem(this.path, 0 - ancestor)
|
|
2226
|
+
parent = NONE === val ? delprop(aval, akey) : setprop(aval, akey, val)
|
|
2227
|
+
}
|
|
2228
|
+
// console.log('SETVAL', val, this.key, this.parent)
|
|
2229
|
+
return parent
|
|
2230
|
+
}
|
|
2031
2231
|
}
|
|
2032
2232
|
// Internal utilities
|
|
2033
2233
|
// ==================
|
|
@@ -2038,49 +2238,51 @@ class Injection {
|
|
|
2038
2238
|
// }
|
|
2039
2239
|
// Build a type validation error message.
|
|
2040
2240
|
function _invalidTypeMsg(path, needtype, vt, v, _whence) {
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2241
|
+
let vs = null == v ? 'no value' : stringify(v)
|
|
2242
|
+
return (
|
|
2243
|
+
'Expected ' +
|
|
2244
|
+
(1 < size(path) ? 'field ' + pathify(path, 1) + ' to be ' : '') +
|
|
2245
|
+
needtype +
|
|
2246
|
+
', but found ' +
|
|
2247
|
+
(null != v ? typename(vt) + S_VIZ : '') +
|
|
2248
|
+
vs +
|
|
2249
|
+
// Uncomment to help debug validation errors.
|
|
2250
|
+
// ' [' + _whence + ']' +
|
|
2251
|
+
'.'
|
|
2252
|
+
)
|
|
2049
2253
|
}
|
|
2050
2254
|
// Default inject handler for transforms. If the path resolves to a function,
|
|
2051
2255
|
// call the function passing the injection inj. This is how transforms operate.
|
|
2052
2256
|
const _injecthandler = (inj, val, ref, store) => {
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2257
|
+
let out = val
|
|
2258
|
+
const iscmd = isfunc(val) && (NONE === ref || ref.startsWith(S_DS))
|
|
2259
|
+
// Only call val function if it is a special command ($NAME format).
|
|
2260
|
+
// TODO: OR if meta.'$CALL'
|
|
2261
|
+
if (iscmd) {
|
|
2262
|
+
out = val(inj, val, ref, store)
|
|
2263
|
+
}
|
|
2264
|
+
// Update parent with value. Ensures references remain in node tree.
|
|
2265
|
+
else if (M_VAL === inj.mode && inj.full) {
|
|
2266
|
+
inj.setval(val)
|
|
2267
|
+
}
|
|
2268
|
+
return out
|
|
2065
2269
|
}
|
|
2066
2270
|
const _validatehandler = (inj, val, ref, store) => {
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
}
|
|
2083
|
-
return out
|
|
2271
|
+
let out = val
|
|
2272
|
+
const m = ref.match(R_META_PATH)
|
|
2273
|
+
const ismetapath = null != m
|
|
2274
|
+
if (ismetapath) {
|
|
2275
|
+
if ('=' === m[2]) {
|
|
2276
|
+
inj.setval([S_BEXACT, val])
|
|
2277
|
+
} else {
|
|
2278
|
+
inj.setval(val)
|
|
2279
|
+
}
|
|
2280
|
+
inj.keyI = -1
|
|
2281
|
+
out = SKIP
|
|
2282
|
+
} else {
|
|
2283
|
+
out = _injecthandler(inj, val, ref, store)
|
|
2284
|
+
}
|
|
2285
|
+
return out
|
|
2084
2286
|
}
|
|
2085
2287
|
// Inject values from a data store into a string. Not a public utility - used by
|
|
2086
2288
|
// `inject`. Inject are marked with `path` where path is resolved
|
|
@@ -2092,186 +2294,223 @@ const _validatehandler = (inj, val, ref, store) => {
|
|
|
2092
2294
|
// discarded. This syntax specifies the name of a transform, and
|
|
2093
2295
|
// optionally allows transforms to be ordered by alphanumeric sorting.
|
|
2094
2296
|
function _injectstr(val, store, inj) {
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
return out
|
|
2297
|
+
// Can't inject into non-strings
|
|
2298
|
+
if (S_string !== typeof val || S_MT === val) {
|
|
2299
|
+
return S_MT
|
|
2300
|
+
}
|
|
2301
|
+
let out = val
|
|
2302
|
+
// Pattern examples: "`a.b.c`", "`$NAME`", "`$NAME1`"
|
|
2303
|
+
const m = val.match(R_INJECTION_FULL)
|
|
2304
|
+
// Full string of the val is an injection.
|
|
2305
|
+
if (m) {
|
|
2306
|
+
if (null != inj) {
|
|
2307
|
+
inj.full = true
|
|
2308
|
+
}
|
|
2309
|
+
let pathref = m[1]
|
|
2310
|
+
// Special escapes inside injection.
|
|
2311
|
+
if (3 < size(pathref)) {
|
|
2312
|
+
pathref = pathref.replace(R_BT_ESCAPE, S_BT).replace(R_DS_ESCAPE, S_DS)
|
|
2313
|
+
}
|
|
2314
|
+
// Get the extracted path reference.
|
|
2315
|
+
out = getpath(store, pathref, inj)
|
|
2316
|
+
} else {
|
|
2317
|
+
// Check for injections within the string.
|
|
2318
|
+
const partial = (_m, ref) => {
|
|
2319
|
+
// Special escapes inside injection.
|
|
2320
|
+
if (3 < size(ref)) {
|
|
2321
|
+
ref = ref.replace(R_BT_ESCAPE, S_BT).replace(R_DS_ESCAPE, S_DS)
|
|
2322
|
+
}
|
|
2323
|
+
if (inj) {
|
|
2324
|
+
inj.full = false
|
|
2325
|
+
}
|
|
2326
|
+
const found = getpath(store, ref, inj)
|
|
2327
|
+
// Ensure inject value is a string.
|
|
2328
|
+
return NONE === found ? S_MT : S_string === typeof found ? found : JSON.stringify(found)
|
|
2329
|
+
}
|
|
2330
|
+
out = val.replace(R_INJECTION_PARTIAL, partial)
|
|
2331
|
+
// Also call the inj handler on the entire string, providing the
|
|
2332
|
+
// option for custom injection.
|
|
2333
|
+
if (null != inj && isfunc(inj.handler)) {
|
|
2334
|
+
inj.full = true
|
|
2335
|
+
out = inj.handler(inj, out, val, store)
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
return out
|
|
2138
2339
|
}
|
|
2139
2340
|
// Handler Utilities
|
|
2140
2341
|
// =================
|
|
2141
2342
|
const MODENAME = {
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2343
|
+
[M_VAL]: 'val',
|
|
2344
|
+
[M_KEYPRE]: 'key:pre',
|
|
2345
|
+
[M_KEYPOST]: 'key:post',
|
|
2145
2346
|
}
|
|
2146
2347
|
const PLACEMENT = {
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2348
|
+
[M_VAL]: 'value',
|
|
2349
|
+
[M_KEYPRE]: S_key,
|
|
2350
|
+
[M_KEYPOST]: S_key,
|
|
2150
2351
|
}
|
|
2151
2352
|
function checkPlacement(modes, ijname, parentTypes, inj) {
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2353
|
+
if (0 === (modes & inj.mode)) {
|
|
2354
|
+
inj.errs.push(
|
|
2355
|
+
'$' +
|
|
2356
|
+
ijname +
|
|
2357
|
+
': invalid placement as ' +
|
|
2358
|
+
PLACEMENT[inj.mode] +
|
|
2359
|
+
', expected: ' +
|
|
2360
|
+
join(
|
|
2361
|
+
items(
|
|
2362
|
+
[M_KEYPRE, M_KEYPOST, M_VAL].filter((m) => modes & m),
|
|
2363
|
+
(n) => PLACEMENT[n[1]],
|
|
2364
|
+
),
|
|
2365
|
+
',',
|
|
2366
|
+
) +
|
|
2367
|
+
'.',
|
|
2368
|
+
)
|
|
2369
|
+
return false
|
|
2370
|
+
}
|
|
2371
|
+
if (!isempty(parentTypes)) {
|
|
2372
|
+
const ptype = typify(inj.parent)
|
|
2373
|
+
if (0 === (parentTypes & ptype)) {
|
|
2374
|
+
inj.errs.push(
|
|
2375
|
+
'$' +
|
|
2376
|
+
ijname +
|
|
2377
|
+
': invalid placement in parent ' +
|
|
2378
|
+
typename(ptype) +
|
|
2379
|
+
', expected: ' +
|
|
2380
|
+
typename(parentTypes) +
|
|
2381
|
+
'.',
|
|
2382
|
+
)
|
|
2383
|
+
return false
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
return true
|
|
2166
2387
|
}
|
|
2167
2388
|
// function injectorArgs(argTypes: number[], inj: Injection): any {
|
|
2168
2389
|
function injectorArgs(argTypes, args) {
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2390
|
+
const numargs = size(argTypes)
|
|
2391
|
+
const found = new Array(1 + numargs)
|
|
2392
|
+
found[0] = NONE
|
|
2393
|
+
for (let argI = 0; argI < numargs; argI++) {
|
|
2394
|
+
// const arg = inj.parent[1 + argI]
|
|
2395
|
+
const arg = args[argI]
|
|
2396
|
+
const argType = typify(arg)
|
|
2397
|
+
if (0 === (argTypes[argI] & argType)) {
|
|
2398
|
+
found[0] =
|
|
2399
|
+
'invalid argument: ' +
|
|
2400
|
+
stringify(arg, 22) +
|
|
2401
|
+
' (' +
|
|
2402
|
+
typename(argType) +
|
|
2403
|
+
' at position ' +
|
|
2404
|
+
(1 + argI) +
|
|
2405
|
+
') is not of type: ' +
|
|
2406
|
+
typename(argTypes[argI]) +
|
|
2407
|
+
'.'
|
|
2408
|
+
break
|
|
2183
2409
|
}
|
|
2184
|
-
|
|
2410
|
+
found[1 + argI] = arg
|
|
2411
|
+
}
|
|
2412
|
+
return found
|
|
2185
2413
|
}
|
|
2186
2414
|
function injectChild(child, store, inj) {
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
return cinj
|
|
2415
|
+
let cinj = inj
|
|
2416
|
+
// Replace ['`$FORMAT`',...] with child
|
|
2417
|
+
if (null != inj.prior) {
|
|
2418
|
+
if (null != inj.prior.prior) {
|
|
2419
|
+
cinj = inj.prior.prior.child(inj.prior.keyI, inj.prior.keys)
|
|
2420
|
+
cinj.val = child
|
|
2421
|
+
setprop(cinj.parent, inj.prior.key, child)
|
|
2422
|
+
} else {
|
|
2423
|
+
cinj = inj.prior.child(inj.keyI, inj.keys)
|
|
2424
|
+
cinj.val = child
|
|
2425
|
+
setprop(cinj.parent, inj.key, child)
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
// console.log('FORMAT-INJECT-CHILD', child)
|
|
2429
|
+
inject(child, store, cinj)
|
|
2430
|
+
return cinj
|
|
2204
2431
|
}
|
|
2205
2432
|
class StructUtility {
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2433
|
+
constructor() {
|
|
2434
|
+
this.clone = clone
|
|
2435
|
+
this.delprop = delprop
|
|
2436
|
+
this.escre = escre
|
|
2437
|
+
this.escurl = escurl
|
|
2438
|
+
this.filter = filter
|
|
2439
|
+
this.flatten = flatten
|
|
2440
|
+
this.getdef = getdef
|
|
2441
|
+
this.getelem = getelem
|
|
2442
|
+
this.getpath = getpath
|
|
2443
|
+
this.getprop = getprop
|
|
2444
|
+
this.haskey = haskey
|
|
2445
|
+
this.inject = inject
|
|
2446
|
+
this.isempty = isempty
|
|
2447
|
+
this.isfunc = isfunc
|
|
2448
|
+
this.iskey = iskey
|
|
2449
|
+
this.islist = islist
|
|
2450
|
+
this.ismap = ismap
|
|
2451
|
+
this.isnode = isnode
|
|
2452
|
+
this.items = items
|
|
2453
|
+
this.join = join
|
|
2454
|
+
this.jsonify = jsonify
|
|
2455
|
+
this.keysof = keysof
|
|
2456
|
+
this.merge = merge
|
|
2457
|
+
this.pad = pad
|
|
2458
|
+
this.pathify = pathify
|
|
2459
|
+
this.select = select
|
|
2460
|
+
this.setpath = setpath
|
|
2461
|
+
this.setprop = setprop
|
|
2462
|
+
this.size = size
|
|
2463
|
+
this.slice = slice
|
|
2464
|
+
this.strkey = strkey
|
|
2465
|
+
this.stringify = stringify
|
|
2466
|
+
this.transform = transform
|
|
2467
|
+
this.typify = typify
|
|
2468
|
+
this.typename = typename
|
|
2469
|
+
this.validate = validate
|
|
2470
|
+
this.walk = walk
|
|
2471
|
+
this.re_compile = re_compile
|
|
2472
|
+
this.re_find = re_find
|
|
2473
|
+
this.re_find_all = re_find_all
|
|
2474
|
+
this.re_replace = re_replace
|
|
2475
|
+
this.re_test = re_test
|
|
2476
|
+
this.re_escape = re_escape
|
|
2477
|
+
this.SKIP = SKIP
|
|
2478
|
+
this.DELETE = DELETE
|
|
2479
|
+
this.jm = jm
|
|
2480
|
+
this.jt = jt
|
|
2481
|
+
this.tn = typename
|
|
2482
|
+
this.T_any = T_any
|
|
2483
|
+
this.T_noval = T_noval
|
|
2484
|
+
this.T_boolean = T_boolean
|
|
2485
|
+
this.T_decimal = T_decimal
|
|
2486
|
+
this.T_integer = T_integer
|
|
2487
|
+
this.T_number = T_number
|
|
2488
|
+
this.T_string = T_string
|
|
2489
|
+
this.T_function = T_function
|
|
2490
|
+
this.T_symbol = T_symbol
|
|
2491
|
+
this.T_null = T_null
|
|
2492
|
+
this.T_list = T_list
|
|
2493
|
+
this.T_map = T_map
|
|
2494
|
+
this.T_instance = T_instance
|
|
2495
|
+
this.T_scalar = T_scalar
|
|
2496
|
+
this.T_node = T_node
|
|
2497
|
+
this.checkPlacement = checkPlacement
|
|
2498
|
+
this.injectorArgs = injectorArgs
|
|
2499
|
+
this.injectChild = injectChild
|
|
2500
|
+
}
|
|
2268
2501
|
}
|
|
2269
2502
|
|
|
2270
|
-
|
|
2271
2503
|
module.exports = {
|
|
2272
2504
|
StructUtility,
|
|
2273
2505
|
Injection,
|
|
2274
2506
|
|
|
2507
|
+
re_compile,
|
|
2508
|
+
re_find,
|
|
2509
|
+
re_find_all,
|
|
2510
|
+
re_replace,
|
|
2511
|
+
re_test,
|
|
2512
|
+
re_escape,
|
|
2513
|
+
|
|
2275
2514
|
clone,
|
|
2276
2515
|
delprop,
|
|
2277
2516
|
escre,
|