@voxgig/sdkgen 0.25.0 → 0.28.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 +6 -6
- package/dist/action/action.js +1 -2
- package/dist/action/action.js.map +1 -1
- package/dist/action/feature.js +4 -2
- package/dist/action/feature.js.map +1 -1
- package/dist/action/target.js +4 -2
- package/dist/action/target.js.map +1 -1
- package/dist/cmp/Entity.js +2 -1
- package/dist/cmp/Entity.js.map +1 -1
- package/dist/cmp/Feature.js +11 -9
- package/dist/cmp/Feature.js.map +1 -1
- package/dist/cmp/FeatureHook.js +2 -1
- package/dist/cmp/FeatureHook.js.map +1 -1
- package/dist/cmp/Main.js +6 -2
- package/dist/cmp/Main.js.map +1 -1
- package/dist/cmp/ReadmeEntity.js +2 -1
- package/dist/cmp/ReadmeEntity.js.map +1 -1
- package/dist/cmp/Test.d.ts +2 -0
- package/dist/cmp/Test.js +17 -0
- package/dist/cmp/Test.js.map +1 -0
- package/dist/sdkgen.d.ts +2 -1
- package/dist/sdkgen.js +17 -28
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +3 -1
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -1
- package/model/sdkgen.jsonic +9 -9
- package/package.json +8 -6
- package/project/.sdk/model/feature/log.jsonic +3 -3
- package/project/.sdk/model/feature/test.jsonic +8 -3
- package/project/.sdk/model/target/go.jsonic +19 -4
- package/project/.sdk/model/target/js.jsonic +2 -2
- package/project/.sdk/model/target/ts.jsonic +5 -5
- package/project/.sdk/src/cmp/go/Config_go.ts +119 -0
- package/project/.sdk/src/cmp/go/EntityOperation_go.ts +48 -0
- package/project/.sdk/src/cmp/go/Entity_go.ts +67 -0
- package/project/.sdk/src/cmp/go/MainEntity_go.ts +22 -0
- package/project/.sdk/src/cmp/go/Main_go.ts +209 -0
- package/project/.sdk/src/cmp/go/Package_go.ts +89 -0
- package/project/.sdk/src/cmp/go/TestDirect_go.ts +373 -0
- package/project/.sdk/src/cmp/go/TestEntity_go.ts +341 -0
- package/project/.sdk/src/cmp/go/Test_go.ts +37 -0
- package/project/.sdk/src/cmp/go/fragment/Entity.fragment.go +146 -0
- package/project/.sdk/src/cmp/go/fragment/EntityCreateOp.fragment.go +35 -0
- package/project/.sdk/src/cmp/go/fragment/EntityListOp.fragment.go +26 -0
- package/project/.sdk/src/cmp/go/fragment/EntityLoadOp.fragment.go +38 -0
- package/project/.sdk/src/cmp/go/fragment/EntityRemoveOp.fragment.go +38 -0
- package/project/.sdk/src/cmp/go/fragment/EntityUpdateOp.fragment.go +38 -0
- package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +250 -0
- package/project/.sdk/src/cmp/go/fragment/SdkError.fragment.go +22 -0
- package/project/.sdk/src/cmp/go/tsconfig.json +15 -0
- package/project/.sdk/src/cmp/go/utility_go.ts +88 -0
- package/project/.sdk/src/cmp/js/Main_js.ts +3 -3
- package/project/.sdk/src/cmp/js/Quick_js.ts +1 -1
- package/project/.sdk/src/cmp/js/fragment/EntityCreateOp.fragment.js +6 -6
- package/project/.sdk/src/cmp/js/fragment/EntityListOp.fragment.js +6 -6
- package/project/.sdk/src/cmp/js/fragment/EntityLoadOp.fragment.js +6 -6
- package/project/.sdk/src/cmp/js/fragment/EntityRemoveOp.fragment.js +6 -6
- package/project/.sdk/src/cmp/js/fragment/EntityUpdateOp.fragment.js +6 -6
- package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +85 -1
- package/project/.sdk/src/cmp/ts/Config_ts.ts +53 -6
- package/project/.sdk/src/cmp/ts/EntityOperation_ts.ts +3 -21
- package/project/.sdk/src/cmp/ts/Entity_ts.ts +3 -5
- package/project/.sdk/src/cmp/ts/Main_ts.ts +23 -13
- package/project/.sdk/src/cmp/ts/Package_ts.ts +30 -11
- package/project/.sdk/src/cmp/ts/SdkError_ts.ts +42 -0
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +288 -0
- package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +349 -2
- package/project/.sdk/src/cmp/ts/TestMain_ts.ts +0 -3
- package/project/.sdk/src/cmp/ts/Test_ts.ts +23 -3
- package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +38 -5
- package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +30 -0
- package/project/.sdk/src/cmp/ts/fragment/Entity.fragment.ts +50 -38
- package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +9 -7
- package/project/.sdk/src/cmp/ts/fragment/EntityCreateOp.fragment.ts +47 -31
- package/project/.sdk/src/cmp/ts/fragment/EntityListOp.fragment.ts +49 -31
- package/project/.sdk/src/cmp/ts/fragment/EntityLoadOp.fragment.ts +50 -33
- package/project/.sdk/src/cmp/ts/fragment/EntityRemoveOp.fragment.ts +51 -32
- package/project/.sdk/src/cmp/ts/fragment/EntityUpdateOp.fragment.ts +51 -31
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +166 -34
- package/project/.sdk/src/cmp/ts/fragment/SdkError.fragment.ts +25 -0
- package/project/.sdk/src/cmp/ts/tsconfig.json +15 -0
- package/project/.sdk/src/cmp/ts/utility_ts.ts +55 -1
- package/project/.sdk/tm/go/Makefile +10 -0
- package/project/.sdk/tm/go/core/context.go +267 -0
- package/project/.sdk/tm/go/core/control.go +7 -0
- package/project/.sdk/tm/go/core/error.go +25 -0
- package/project/.sdk/tm/go/core/helpers.go +33 -0
- package/project/.sdk/tm/go/core/operation.go +61 -0
- package/project/.sdk/tm/go/core/response.go +55 -0
- package/project/.sdk/tm/go/core/result.go +73 -0
- package/project/.sdk/tm/go/core/spec.go +97 -0
- package/project/.sdk/tm/go/core/target.go +102 -0
- package/project/.sdk/tm/go/core/types.go +44 -0
- package/project/.sdk/tm/go/core/utility_type.go +54 -0
- package/project/.sdk/tm/go/feature/base_feature.go +40 -0
- package/project/.sdk/tm/go/feature/test_feature.go +196 -0
- package/project/.sdk/tm/go/src/feature/README.md +1 -0
- package/project/.sdk/tm/go/src/feature/base/.gitkeep +0 -0
- package/project/.sdk/tm/go/src/feature/test/.gitkeep +0 -0
- package/project/.sdk/tm/go/test/custom_utility_test.go +80 -0
- package/project/.sdk/tm/go/test/exists_test.go +16 -0
- package/project/.sdk/tm/go/test/primary_utility_test.go +899 -0
- package/project/.sdk/tm/go/test/runner_test.go +428 -0
- package/project/.sdk/tm/go/test/struct_runner_test.go +1094 -0
- package/project/.sdk/tm/go/test/struct_utility_test.go +1423 -0
- package/project/.sdk/tm/go/utility/clean.go +7 -0
- package/project/.sdk/tm/go/utility/done.go +20 -0
- package/project/.sdk/tm/go/utility/feature_add.go +10 -0
- package/project/.sdk/tm/go/utility/feature_hook.go +30 -0
- package/project/.sdk/tm/go/utility/feature_init.go +30 -0
- package/project/.sdk/tm/go/utility/fetcher.go +102 -0
- package/project/.sdk/tm/go/utility/make_context.go +7 -0
- package/project/.sdk/tm/go/utility/make_error.go +69 -0
- package/project/.sdk/tm/go/utility/make_fetch_def.go +44 -0
- package/project/.sdk/tm/go/utility/make_options.go +130 -0
- package/project/.sdk/tm/go/utility/make_request.go +59 -0
- package/project/.sdk/tm/go/utility/make_response.go +46 -0
- package/project/.sdk/tm/go/utility/make_result.go +55 -0
- package/project/.sdk/tm/go/utility/make_spec.go +68 -0
- package/project/.sdk/tm/go/utility/make_target.go +95 -0
- package/project/.sdk/tm/go/utility/make_url.go +41 -0
- package/project/.sdk/tm/go/utility/param.go +66 -0
- package/project/.sdk/tm/go/utility/prepare_auth.go +40 -0
- package/project/.sdk/tm/go/utility/prepare_body.go +14 -0
- package/project/.sdk/tm/go/utility/prepare_headers.go +22 -0
- package/project/.sdk/tm/go/utility/prepare_method.go +21 -0
- package/project/.sdk/tm/go/utility/prepare_params.go +41 -0
- package/project/.sdk/tm/go/utility/prepare_path.go +21 -0
- package/project/.sdk/tm/go/utility/prepare_query.go +47 -0
- package/project/.sdk/tm/go/utility/register.go +39 -0
- package/project/.sdk/tm/go/utility/result_basic.go +31 -0
- package/project/.sdk/tm/go/utility/result_body.go +17 -0
- package/project/.sdk/tm/go/utility/result_headers.go +22 -0
- package/project/.sdk/tm/go/utility/struct/go.mod +3 -0
- package/project/.sdk/tm/go/utility/struct/voxgigstruct.go +4891 -0
- package/project/.sdk/tm/go/utility/transform_request.go +32 -0
- package/project/.sdk/tm/go/utility/transform_response.go +45 -0
- package/project/.sdk/tm/js/src/feature/log/LogFeature.js +2 -2
- package/project/.sdk/tm/ts/src/Context.ts +144 -0
- package/project/.sdk/tm/ts/src/Control.ts +20 -0
- package/project/.sdk/tm/ts/src/Operation.ts +24 -0
- package/project/.sdk/tm/ts/src/Response.ts +26 -0
- package/project/.sdk/tm/ts/src/Result.ts +30 -0
- package/project/.sdk/tm/ts/src/Spec.ts +40 -0
- package/project/.sdk/tm/ts/src/Target.ts +36 -0
- package/project/.sdk/tm/ts/src/feature/base/BaseFeature.ts +1 -1
- package/project/.sdk/tm/ts/src/feature/log/LogFeature.ts +2 -2
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +158 -104
- package/project/.sdk/tm/ts/src/types.ts +18 -78
- package/project/.sdk/tm/ts/src/utility/CleanUtility.ts +17 -31
- package/project/.sdk/tm/ts/src/utility/DoneUtility.ts +3 -4
- package/project/.sdk/tm/ts/src/utility/{AddfeatureUtility.ts → FeatureAddUtility.ts} +2 -2
- package/project/.sdk/tm/ts/src/utility/{FeaturehookUtility.ts → FeatureHookUtility.ts} +8 -5
- package/project/.sdk/tm/ts/src/utility/FeatureInitUtility.ts +15 -0
- package/project/.sdk/tm/ts/src/utility/FetcherUtility.ts +20 -2
- package/project/.sdk/tm/ts/src/utility/MakeContextUtility.ts +27 -0
- package/project/.sdk/tm/ts/src/utility/{ErrorUtility.ts → MakeErrorUtility.ts} +10 -6
- package/project/.sdk/tm/ts/src/utility/MakeFetchDefUtility.ts +46 -0
- package/project/.sdk/tm/ts/src/utility/{OptionsUtility.ts → MakeOptionsUtility.ts} +32 -7
- package/project/.sdk/tm/ts/src/utility/MakeRequestUtility.ts +66 -0
- package/project/.sdk/tm/ts/src/utility/MakeResponseUtility.ts +61 -0
- package/project/.sdk/tm/ts/src/utility/MakeResultUtility.ts +56 -0
- package/project/.sdk/tm/ts/src/utility/MakeSpecUtility.ts +61 -0
- package/project/.sdk/tm/ts/src/utility/MakeTargetUtility.ts +76 -0
- package/project/.sdk/tm/ts/src/utility/MakeUrlUtility.ts +61 -0
- package/project/.sdk/tm/ts/src/utility/{FindparamUtility.ts → ParamUtility.ts} +28 -8
- package/project/.sdk/tm/ts/src/utility/{AuthUtility.ts → PrepareAuthUtility.ts} +10 -4
- package/project/.sdk/tm/ts/src/utility/PrepareBodyUtility.ts +32 -0
- package/project/.sdk/tm/ts/src/utility/{HeadersUtility.ts → PrepareHeadersUtility.ts} +5 -7
- package/project/.sdk/tm/ts/src/utility/{MethodUtility.ts → PrepareMethodUtility.ts} +6 -6
- package/project/.sdk/tm/ts/src/utility/PrepareParamsUtility.ts +37 -0
- package/project/.sdk/tm/ts/src/utility/PreparePathUtility.ts +17 -0
- package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +30 -0
- package/project/.sdk/tm/ts/src/utility/{ResbasicUtility.ts → ResultBasicUtility.ts} +12 -7
- package/project/.sdk/tm/ts/src/utility/ResultBodyUtility.ts +22 -0
- package/project/.sdk/tm/ts/src/utility/ResultHeadersUtility.ts +26 -0
- package/project/.sdk/tm/ts/src/utility/StructUtility.ts +1113 -525
- package/project/.sdk/tm/ts/src/utility/{ReqformUtility.ts → TransformRequestUtility.ts} +9 -7
- package/project/.sdk/tm/ts/src/utility/{ResformUtility.ts → TransformResponseUtility.ts} +11 -8
- package/project/.sdk/tm/ts/src/utility/Utility.ts +52 -65
- package/project/.sdk/tm/ts/test/exists.test.ts +0 -1
- package/project/.sdk/tm/ts/test/runner.ts +36 -13
- package/project/.sdk/tm/ts/test/utility/Custom.test.ts +30 -29
- package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +385 -168
- package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +433 -189
- package/src/action/action.ts +1 -2
- package/src/action/feature.ts +7 -2
- package/src/action/target.ts +7 -7
- package/src/cmp/Entity.ts +7 -1
- package/src/cmp/Feature.ts +11 -9
- package/src/cmp/FeatureHook.ts +6 -1
- package/src/cmp/Main.ts +12 -2
- package/src/cmp/ReadmeEntity.ts +6 -1
- package/src/cmp/Test.ts +31 -0
- package/src/sdkgen.ts +19 -34
- package/src/types.ts +10 -1
- package/project/.sdk/src/cmp/ts/EntityTest_ts.ts +0 -180
- package/project/.sdk/tm/ts/src/utility/BodyUtility.ts +0 -29
- package/project/.sdk/tm/ts/src/utility/ContextUtility.ts +0 -67
- package/project/.sdk/tm/ts/src/utility/FullurlUtility.ts +0 -46
- package/project/.sdk/tm/ts/src/utility/InitfeatureUtility.ts +0 -13
- package/project/.sdk/tm/ts/src/utility/JoinurlUtility.ts +0 -15
- package/project/.sdk/tm/ts/src/utility/OperatorUtility.ts +0 -90
- package/project/.sdk/tm/ts/src/utility/ParamsUtility.ts +0 -37
- package/project/.sdk/tm/ts/src/utility/QueryUtility.ts +0 -27
- package/project/.sdk/tm/ts/src/utility/RequestUtility.ts +0 -66
- package/project/.sdk/tm/ts/src/utility/ResbodyUtility.ts +0 -19
- package/project/.sdk/tm/ts/src/utility/ResheadersUtility.ts +0 -23
- package/project/.sdk/tm/ts/src/utility/ResponseUtility.ts +0 -30
- package/project/.sdk/tm/ts/src/utility/ResultUtility.ts +0 -36
- package/project/.sdk/tm/ts/src/utility/SpecUtility.ts +0 -61
|
@@ -0,0 +1,4891 @@
|
|
|
1
|
+
/* Copyright (c) 2025 Voxgig Ltd. MIT LICENSE. */
|
|
2
|
+
|
|
3
|
+
/* Voxgig Struct
|
|
4
|
+
* =============
|
|
5
|
+
*
|
|
6
|
+
* Utility functions to manipulate in-memory JSON-like data
|
|
7
|
+
* structures. These structures assumed to be composed of nested
|
|
8
|
+
* "nodes", where a node is a list or map, and has named or indexed
|
|
9
|
+
* fields. The general design principle is "by-example". Transform
|
|
10
|
+
* specifications mirror the desired output. This implementation is
|
|
11
|
+
* designed for porting to multiple language, and to be tolerant of
|
|
12
|
+
* undefined values.
|
|
13
|
+
*
|
|
14
|
+
* Main utilities
|
|
15
|
+
* - getpath: get the value at a key path deep inside an object.
|
|
16
|
+
* - merge: merge multiple nodes, overriding values in earlier nodes.
|
|
17
|
+
* - walk: walk a node tree, applying a function at each node and leaf.
|
|
18
|
+
* - inject: inject values from a data store into a new data structure.
|
|
19
|
+
* - transform: transform a data structure to an example structure.
|
|
20
|
+
* - validate: valiate a data structure against a shape specification.
|
|
21
|
+
*
|
|
22
|
+
* Minor utilities
|
|
23
|
+
* - isnode, islist, ismap, iskey, isfunc: identify value kinds.
|
|
24
|
+
* - isempty: undefined values, or empty nodes.
|
|
25
|
+
* - keysof: sorted list of node keys (ascending).
|
|
26
|
+
* - haskey: true if key value is defined.
|
|
27
|
+
* - clone: create a copy of a JSON-like data structure.
|
|
28
|
+
* - items: list entries of a map or list as [key, value] pairs.
|
|
29
|
+
* - getprop: safely get a property value by key.
|
|
30
|
+
* - setprop: safely set a property value by key.
|
|
31
|
+
* - stringify: human-friendly string version of a value.
|
|
32
|
+
* - escre: escape a regular expresion string.
|
|
33
|
+
* - escurl: escape a url.
|
|
34
|
+
* - joinurl: join parts of a url, merging forward slashes.
|
|
35
|
+
*
|
|
36
|
+
* This set of functions and supporting utilities is designed to work
|
|
37
|
+
* uniformly across many languages, meaning that some code that may be
|
|
38
|
+
* functionally redundant in specific languages is still retained to
|
|
39
|
+
* keep the code human comparable.
|
|
40
|
+
*
|
|
41
|
+
* NOTE: In this code JSON nulls are in general *not* considered the
|
|
42
|
+
* same as the undefined value in the given language. However most
|
|
43
|
+
* JSON parsers do use the undefined value to represent JSON
|
|
44
|
+
* null. This is ambiguous as JSON null is a separate value, not an
|
|
45
|
+
* undefined value. You should convert such values to a special value
|
|
46
|
+
* to represent JSON null, if this ambiguity creates issues
|
|
47
|
+
* (thankfully in most APIs, JSON nulls are not used). For example,
|
|
48
|
+
* the unit tests use the string "__NULL__" where necessary.
|
|
49
|
+
*
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
package voxgigstruct
|
|
53
|
+
|
|
54
|
+
import (
|
|
55
|
+
"encoding/json"
|
|
56
|
+
"fmt"
|
|
57
|
+
"math"
|
|
58
|
+
"math/bits"
|
|
59
|
+
"net/url"
|
|
60
|
+
"reflect"
|
|
61
|
+
"regexp"
|
|
62
|
+
"sort"
|
|
63
|
+
"strconv"
|
|
64
|
+
"strings"
|
|
65
|
+
"time"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
const Version = "0.1.0"
|
|
69
|
+
|
|
70
|
+
// String constants are explicitly defined.
|
|
71
|
+
|
|
72
|
+
const (
|
|
73
|
+
// Mode value for inject step (bitfield).
|
|
74
|
+
M_KEYPRE = 1
|
|
75
|
+
M_KEYPOST = 2
|
|
76
|
+
M_VAL = 4
|
|
77
|
+
|
|
78
|
+
// Special keys.
|
|
79
|
+
S_DKEY = "$KEY"
|
|
80
|
+
S_DMETA = "`$META`"
|
|
81
|
+
S_DTOP = "$TOP"
|
|
82
|
+
S_DERRS = "$ERRS"
|
|
83
|
+
|
|
84
|
+
// General strings.
|
|
85
|
+
S_any = "any"
|
|
86
|
+
S_noval = "noval"
|
|
87
|
+
S_array = "array"
|
|
88
|
+
S_list = "list"
|
|
89
|
+
S_map = "map"
|
|
90
|
+
S_boolean = "boolean"
|
|
91
|
+
S_decimal = "decimal"
|
|
92
|
+
S_integer = "integer"
|
|
93
|
+
S_function = "function"
|
|
94
|
+
S_symbol = "symbol"
|
|
95
|
+
S_instance = "instance"
|
|
96
|
+
S_number = "number"
|
|
97
|
+
S_object = "object"
|
|
98
|
+
S_string = "string"
|
|
99
|
+
S_scalar = "scalar"
|
|
100
|
+
S_node = "node"
|
|
101
|
+
S_null = "null"
|
|
102
|
+
S_key = "key"
|
|
103
|
+
S_parent = "parent"
|
|
104
|
+
S_MT = ""
|
|
105
|
+
S_SP = " "
|
|
106
|
+
S_BT = "`"
|
|
107
|
+
S_DS = "$"
|
|
108
|
+
S_DT = "."
|
|
109
|
+
S_CN = ":"
|
|
110
|
+
S_KEY = "KEY"
|
|
111
|
+
S_base = "base"
|
|
112
|
+
S_BEXACT = "`$EXACT`"
|
|
113
|
+
S_BOPEN = "`$OPEN`"
|
|
114
|
+
S_BKEY = "`$KEY`"
|
|
115
|
+
S_BANNO = "`$ANNO`"
|
|
116
|
+
S_BVAL = "`$VAL`"
|
|
117
|
+
S_DSPEC = "$SPEC"
|
|
118
|
+
S_VIZ = ": "
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
// Type bits - using bit positions from 31 downward, matching the TS implementation.
|
|
122
|
+
const (
|
|
123
|
+
T_any = (1 << 31) - 1 // All bits set.
|
|
124
|
+
T_noval = 1 << 30 // Absent value (no value at all). NOT a scalar.
|
|
125
|
+
T_boolean = 1 << 29
|
|
126
|
+
T_decimal = 1 << 28
|
|
127
|
+
T_integer = 1 << 27
|
|
128
|
+
T_number = 1 << 26
|
|
129
|
+
T_string = 1 << 25
|
|
130
|
+
T_function = 1 << 24
|
|
131
|
+
T_symbol = 1 << 23
|
|
132
|
+
T_null = 1 << 22
|
|
133
|
+
// 7 bits reserved
|
|
134
|
+
T_list = 1 << 14
|
|
135
|
+
T_map = 1 << 13
|
|
136
|
+
T_instance = 1 << 12
|
|
137
|
+
// 4 bits reserved
|
|
138
|
+
T_scalar = 1 << 7
|
|
139
|
+
T_node = 1 << 6
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
// TYPENAME maps bit position (via leading zeros count) to type name string.
|
|
143
|
+
var TYPENAME = [...]string{
|
|
144
|
+
S_any,
|
|
145
|
+
S_noval,
|
|
146
|
+
S_boolean,
|
|
147
|
+
S_decimal,
|
|
148
|
+
S_integer,
|
|
149
|
+
S_number,
|
|
150
|
+
S_string,
|
|
151
|
+
S_function,
|
|
152
|
+
S_symbol,
|
|
153
|
+
S_null,
|
|
154
|
+
"", "", "",
|
|
155
|
+
"", "", "", "",
|
|
156
|
+
S_list,
|
|
157
|
+
S_map,
|
|
158
|
+
S_instance,
|
|
159
|
+
"", "", "", "",
|
|
160
|
+
S_scalar,
|
|
161
|
+
S_node,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Sentinel values for control flow in inject/transform.
|
|
165
|
+
type _sentinel struct{ name string }
|
|
166
|
+
|
|
167
|
+
var SKIP = &_sentinel{"SKIP"}
|
|
168
|
+
var DELETE = &_sentinel{"DELETE"}
|
|
169
|
+
|
|
170
|
+
// Regex matching integer keys (including negative).
|
|
171
|
+
var reIntegerKey = regexp.MustCompile(`^[-0-9]+$`)
|
|
172
|
+
|
|
173
|
+
// Meta path syntax regex: matches patterns like "q0$=x1" or "q0$~x1"
|
|
174
|
+
var reMetaPath = regexp.MustCompile(`^([^$]+)\$([=~])(.+)$`)
|
|
175
|
+
|
|
176
|
+
// The standard undefined value for this language.
|
|
177
|
+
// NOTE: `nil` must be used directly.
|
|
178
|
+
|
|
179
|
+
// Keys are strings for maps, or integers for lists.
|
|
180
|
+
type PropKey any
|
|
181
|
+
|
|
182
|
+
// Handle value injections using backtick escape sequences:
|
|
183
|
+
// - `a.b.c`: insert value at {a:{b:{c:1}}}
|
|
184
|
+
// - `$FOO`: apply transform FOO
|
|
185
|
+
type Injector func(
|
|
186
|
+
inj *Injection, // Injection inj.
|
|
187
|
+
val any, // Injection value specification.
|
|
188
|
+
ref *string, // Original injection reference string.
|
|
189
|
+
store any, // Current source root value.
|
|
190
|
+
) any
|
|
191
|
+
|
|
192
|
+
// Injection inj used for recursive injection into JSON-like data structures.
|
|
193
|
+
type Injection struct {
|
|
194
|
+
Mode int // Injection mode: M_KEYPRE, M_VAL, M_KEYPOST (bitfield).
|
|
195
|
+
Full bool // Transform escape was full key name.
|
|
196
|
+
KeyI int // Index of parent key in list of parent keys.
|
|
197
|
+
Keys *ListRef[string] // List of parent keys.
|
|
198
|
+
Key string // Current parent key.
|
|
199
|
+
Val any // Current child value.
|
|
200
|
+
Parent any // Current parent (in transform specification).
|
|
201
|
+
Path *ListRef[string] // Path to current node.
|
|
202
|
+
Nodes *ListRef[any] // Stack of ancestor nodes.
|
|
203
|
+
Handler Injector // Custom handler for injections.
|
|
204
|
+
Errs *ListRef[any] // Error collector.
|
|
205
|
+
Meta map[string]any // Custom meta data.
|
|
206
|
+
Dparent any // Current data parent node (contains current data value).
|
|
207
|
+
Dpath []string // Current data value path.
|
|
208
|
+
Base string // Base key for data in store, if any.
|
|
209
|
+
Modify Modify // Modify injection output.
|
|
210
|
+
Prior *Injection // Parent (aka prior) injection.
|
|
211
|
+
Extra any // Extra data.
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Apply a custom modification to injections.
|
|
215
|
+
type Modify func(
|
|
216
|
+
val any, // Value.
|
|
217
|
+
key any, // Value key, if any,
|
|
218
|
+
parent any, // Parent node, if any.
|
|
219
|
+
inj *Injection, // Injection inj, if any.
|
|
220
|
+
store any, // Store, if any
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
// Create a child injection inj sharing errs/meta/modify/handler with parent.
|
|
224
|
+
func (inj *Injection) child(keyI int, keys []string) *Injection {
|
|
225
|
+
key := StrKey(keys[keyI])
|
|
226
|
+
val := inj.Val
|
|
227
|
+
|
|
228
|
+
childPath := make([]string, len(inj.Path.List))
|
|
229
|
+
copy(childPath, inj.Path.List)
|
|
230
|
+
childPath = append(childPath, key)
|
|
231
|
+
|
|
232
|
+
childNodes := make([]any, len(inj.Nodes.List))
|
|
233
|
+
copy(childNodes, inj.Nodes.List)
|
|
234
|
+
childNodes = append(childNodes, val)
|
|
235
|
+
|
|
236
|
+
childDpath := make([]string, len(inj.Dpath))
|
|
237
|
+
copy(childDpath, inj.Dpath)
|
|
238
|
+
|
|
239
|
+
cinj := &Injection{
|
|
240
|
+
Mode: inj.Mode,
|
|
241
|
+
Full: false,
|
|
242
|
+
KeyI: keyI,
|
|
243
|
+
Keys: &ListRef[string]{List: keys},
|
|
244
|
+
Key: key,
|
|
245
|
+
Val: GetProp(val, key),
|
|
246
|
+
Parent: val,
|
|
247
|
+
Path: &ListRef[string]{List: childPath},
|
|
248
|
+
Nodes: &ListRef[any]{List: childNodes},
|
|
249
|
+
Handler: inj.Handler,
|
|
250
|
+
Modify: inj.Modify,
|
|
251
|
+
Base: inj.Base,
|
|
252
|
+
Meta: inj.Meta,
|
|
253
|
+
Errs: inj.Errs,
|
|
254
|
+
Prior: inj,
|
|
255
|
+
Dpath: childDpath,
|
|
256
|
+
Dparent: inj.Dparent,
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return cinj
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Set value in parent or ancestor node.
|
|
263
|
+
func (inj *Injection) setval(val any, ancestor ...int) any {
|
|
264
|
+
anc := 0
|
|
265
|
+
if len(ancestor) > 0 {
|
|
266
|
+
anc = ancestor[0]
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if anc < 2 {
|
|
270
|
+
if val == nil {
|
|
271
|
+
inj.Parent = DelProp(inj.Parent, inj.Key)
|
|
272
|
+
} else {
|
|
273
|
+
SetProp(inj.Parent, inj.Key, val)
|
|
274
|
+
}
|
|
275
|
+
return inj.Parent
|
|
276
|
+
} else {
|
|
277
|
+
aval := GetElem(inj.Nodes.List, 0-anc)
|
|
278
|
+
akey := GetElem(inj.Path.List, 0-anc)
|
|
279
|
+
if val == nil {
|
|
280
|
+
DelProp(aval, akey)
|
|
281
|
+
} else {
|
|
282
|
+
SetProp(aval, akey, val)
|
|
283
|
+
}
|
|
284
|
+
return aval
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Resolve current node in store for local paths.
|
|
289
|
+
func (inj *Injection) descend() any {
|
|
290
|
+
if inj.Meta == nil {
|
|
291
|
+
inj.Meta = map[string]any{}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Increment depth counter
|
|
295
|
+
d, _ := inj.Meta["__d"].(int)
|
|
296
|
+
inj.Meta["__d"] = d + 1
|
|
297
|
+
|
|
298
|
+
parentkey := ""
|
|
299
|
+
if len(inj.Path.List) >= 2 {
|
|
300
|
+
parentkey = inj.Path.List[len(inj.Path.List)-2]
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if inj.Dparent == nil {
|
|
304
|
+
// Even if there's no data, dpath should continue to match path
|
|
305
|
+
if len(inj.Dpath) > 1 {
|
|
306
|
+
inj.Dpath = append(inj.Dpath, parentkey)
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
if parentkey != "" {
|
|
310
|
+
inj.Dparent = GetProp(inj.Dparent, parentkey)
|
|
311
|
+
|
|
312
|
+
lastpart := ""
|
|
313
|
+
if len(inj.Dpath) > 0 {
|
|
314
|
+
lastpart = inj.Dpath[len(inj.Dpath)-1]
|
|
315
|
+
}
|
|
316
|
+
if lastpart == "$:"+parentkey {
|
|
317
|
+
inj.Dpath = inj.Dpath[:len(inj.Dpath)-1]
|
|
318
|
+
} else {
|
|
319
|
+
inj.Dpath = append(inj.Dpath, parentkey)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return inj.Dparent
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// String returns a human-readable representation of the Injection inj.
|
|
328
|
+
func (inj *Injection) String(prefix ...string) string {
|
|
329
|
+
pfx := ""
|
|
330
|
+
if len(prefix) > 0 && prefix[0] != "" {
|
|
331
|
+
pfx = "/" + prefix[0]
|
|
332
|
+
}
|
|
333
|
+
fullStr := ""
|
|
334
|
+
if inj.Full {
|
|
335
|
+
fullStr = "/full"
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
pathStr := ""
|
|
339
|
+
if inj.Path != nil {
|
|
340
|
+
pathStr = Pathify(inj.Path.List, 1)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
keysStr := ""
|
|
344
|
+
if inj.Keys != nil {
|
|
345
|
+
keysStr = fmt.Sprintf("%v", inj.Keys.List)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
rootVal := ""
|
|
349
|
+
if inj.Nodes != nil && len(inj.Nodes.List) > 0 {
|
|
350
|
+
top := inj.Nodes.List[0]
|
|
351
|
+
if topMap, ok := top.(map[string]any); ok {
|
|
352
|
+
rootVal = Stringify(topMap[S_DTOP], -1, 1)
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return "INJ" + pfx + ":" +
|
|
357
|
+
Pad(pathStr) +
|
|
358
|
+
MODENAME[inj.Mode] + fullStr + ":" +
|
|
359
|
+
"key=" + fmt.Sprintf("%d", inj.KeyI) + "/" + inj.Key + "/" + keysStr +
|
|
360
|
+
" p=" + Stringify(inj.Parent, -1, 1) +
|
|
361
|
+
" m=" + Stringify(inj.Meta, -1, 1) +
|
|
362
|
+
" d/" + Pathify(inj.Dpath, 1) + "=" + Stringify(inj.Dparent, -1, 1) +
|
|
363
|
+
" r=" + rootVal
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Function applied to each node and leaf when walking a node structure depth first.
|
|
367
|
+
type WalkApply func(
|
|
368
|
+
// Map keys are strings, list keys are numbers, top key is nil
|
|
369
|
+
key *string,
|
|
370
|
+
val any,
|
|
371
|
+
parent any,
|
|
372
|
+
path []string,
|
|
373
|
+
) any
|
|
374
|
+
|
|
375
|
+
// Value is a node - defined, and a map (hash) or list (array).
|
|
376
|
+
func IsNode(val any) bool {
|
|
377
|
+
if val == nil {
|
|
378
|
+
return false
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return IsMap(val) || IsList(val)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Value is a defined map (hash) with string keys.
|
|
385
|
+
func IsMap(val any) bool {
|
|
386
|
+
if val == nil {
|
|
387
|
+
return false
|
|
388
|
+
}
|
|
389
|
+
_, ok := val.(map[string]any)
|
|
390
|
+
return ok
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Value is a defined list (array) with integer keys (indexes).
|
|
394
|
+
func IsList(val any) bool {
|
|
395
|
+
if val == nil {
|
|
396
|
+
return false
|
|
397
|
+
}
|
|
398
|
+
if _, ok := val.(*ListRef[any]); ok {
|
|
399
|
+
return true
|
|
400
|
+
}
|
|
401
|
+
rv := reflect.ValueOf(val)
|
|
402
|
+
kind := rv.Kind()
|
|
403
|
+
return kind == reflect.Slice || kind == reflect.Array
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Value is a defined string (non-empty) or integer key.
|
|
407
|
+
func IsKey(val any) bool {
|
|
408
|
+
switch k := val.(type) {
|
|
409
|
+
case string:
|
|
410
|
+
return k != S_MT
|
|
411
|
+
case int, float64, int8, int16, int32, int64:
|
|
412
|
+
return true
|
|
413
|
+
case uint8, uint16, uint32, uint64, uint, float32:
|
|
414
|
+
return true
|
|
415
|
+
default:
|
|
416
|
+
return false
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Check for an "empty" value - nil, empty string, array, object.
|
|
421
|
+
func IsEmpty(val any) bool {
|
|
422
|
+
if val == nil {
|
|
423
|
+
return true
|
|
424
|
+
}
|
|
425
|
+
switch vv := val.(type) {
|
|
426
|
+
case string:
|
|
427
|
+
return vv == S_MT
|
|
428
|
+
case *ListRef[any]:
|
|
429
|
+
return len(vv.List) == 0
|
|
430
|
+
case []any:
|
|
431
|
+
return len(vv) == 0
|
|
432
|
+
case map[string]any:
|
|
433
|
+
return len(vv) == 0
|
|
434
|
+
}
|
|
435
|
+
return false
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Value is a function.
|
|
439
|
+
func IsFunc(val any) bool {
|
|
440
|
+
return reflect.ValueOf(val).Kind() == reflect.Func
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Get a defined value. Returns alt if val is nil.
|
|
444
|
+
func GetDef(val any, alt any) any {
|
|
445
|
+
if nil == val {
|
|
446
|
+
return alt
|
|
447
|
+
}
|
|
448
|
+
return val
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Determine the type of a value as a bitset.
|
|
452
|
+
// Use bitwise AND to test: 0 < (T_string & Typify(val))
|
|
453
|
+
// Use Typename to get the string name.
|
|
454
|
+
func Typify(value any) int {
|
|
455
|
+
if value == nil {
|
|
456
|
+
return T_scalar | T_null
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if _, ok := value.(*ListRef[any]); ok {
|
|
460
|
+
return T_node | T_list
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
val := reflect.ValueOf(value)
|
|
464
|
+
if !val.IsValid() {
|
|
465
|
+
return T_scalar | T_null
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
switch val.Type().Kind() {
|
|
469
|
+
case reflect.Bool:
|
|
470
|
+
return T_scalar | T_boolean
|
|
471
|
+
|
|
472
|
+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
473
|
+
return T_scalar | T_number | T_integer
|
|
474
|
+
|
|
475
|
+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
476
|
+
return T_scalar | T_number | T_integer
|
|
477
|
+
|
|
478
|
+
case reflect.Float32, reflect.Float64:
|
|
479
|
+
f, err := _toFloat64(value)
|
|
480
|
+
if err == nil && f == math.Trunc(f) && !math.IsNaN(f) && !math.IsInf(f, 0) {
|
|
481
|
+
return T_scalar | T_number | T_integer
|
|
482
|
+
}
|
|
483
|
+
if err == nil && math.IsNaN(f) {
|
|
484
|
+
return T_noval
|
|
485
|
+
}
|
|
486
|
+
return T_scalar | T_number | T_decimal
|
|
487
|
+
|
|
488
|
+
case reflect.String:
|
|
489
|
+
return T_scalar | T_string
|
|
490
|
+
|
|
491
|
+
case reflect.Func:
|
|
492
|
+
return T_scalar | T_function
|
|
493
|
+
|
|
494
|
+
case reflect.Slice, reflect.Array:
|
|
495
|
+
return T_node | T_list
|
|
496
|
+
|
|
497
|
+
case reflect.Map:
|
|
498
|
+
return T_node | T_map
|
|
499
|
+
|
|
500
|
+
default:
|
|
501
|
+
return T_node | T_map
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Convert a type bitset to its string name using leading zeros count.
|
|
506
|
+
func Typename(t int) string {
|
|
507
|
+
if t <= 0 {
|
|
508
|
+
return S_any
|
|
509
|
+
}
|
|
510
|
+
idx := bits.LeadingZeros32(uint32(t))
|
|
511
|
+
if idx < len(TYPENAME) && TYPENAME[idx] != "" {
|
|
512
|
+
return TYPENAME[idx]
|
|
513
|
+
}
|
|
514
|
+
return S_any
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// The integer size of the value. For lists and maps, the number of entries.
|
|
518
|
+
// For strings, the length. For numbers, the integer part.
|
|
519
|
+
// For booleans, true is 1 and false is 0. For all other values, 0.
|
|
520
|
+
func Size(val any) int {
|
|
521
|
+
if IsList(val) {
|
|
522
|
+
list, ok := _asList(val)
|
|
523
|
+
if ok {
|
|
524
|
+
return len(list)
|
|
525
|
+
}
|
|
526
|
+
return len(_listify(val))
|
|
527
|
+
} else if IsMap(val) {
|
|
528
|
+
return len(val.(map[string]any))
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
switch v := val.(type) {
|
|
532
|
+
case string:
|
|
533
|
+
return len(v)
|
|
534
|
+
case bool:
|
|
535
|
+
if v {
|
|
536
|
+
return 1
|
|
537
|
+
}
|
|
538
|
+
return 0
|
|
539
|
+
default:
|
|
540
|
+
f, err := _toFloat64(val)
|
|
541
|
+
if err == nil {
|
|
542
|
+
return int(math.Floor(f))
|
|
543
|
+
}
|
|
544
|
+
return 0
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Extract part of a list or string into a new value, from the start
|
|
549
|
+
// point to the end point. If no end is specified, extract to the
|
|
550
|
+
// full length. Negative arguments count from the end. For numbers,
|
|
551
|
+
// perform min and max bounding (start inclusive, end exclusive).
|
|
552
|
+
func Slice(val any, args ...any) any {
|
|
553
|
+
var startP, endP *int
|
|
554
|
+
var mutate bool
|
|
555
|
+
|
|
556
|
+
if len(args) > 0 && args[0] != nil {
|
|
557
|
+
if f, err := _toFloat64(args[0]); err == nil {
|
|
558
|
+
i := int(f)
|
|
559
|
+
startP = &i
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if len(args) > 1 && args[1] != nil {
|
|
563
|
+
if f, err := _toFloat64(args[1]); err == nil {
|
|
564
|
+
i := int(f)
|
|
565
|
+
endP = &i
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if len(args) > 2 {
|
|
569
|
+
if b, ok := args[2].(bool); ok {
|
|
570
|
+
mutate = b
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Number case: clamp between start (inclusive) and end-1 (exclusive->inclusive).
|
|
575
|
+
if _, ok := val.(string); !ok && !IsNode(val) {
|
|
576
|
+
if f, err := _toFloat64(val); err == nil {
|
|
577
|
+
start := math.MinInt64
|
|
578
|
+
if startP != nil {
|
|
579
|
+
start = *startP
|
|
580
|
+
}
|
|
581
|
+
end := math.MaxInt64
|
|
582
|
+
if endP != nil {
|
|
583
|
+
end = *endP - 1
|
|
584
|
+
}
|
|
585
|
+
result := int(math.Min(math.Max(f, float64(start)), float64(end)))
|
|
586
|
+
return result
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
vlen := Size(val)
|
|
591
|
+
|
|
592
|
+
if endP != nil && startP == nil {
|
|
593
|
+
zero := 0
|
|
594
|
+
startP = &zero
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if startP == nil {
|
|
598
|
+
return val
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
start := *startP
|
|
602
|
+
end := vlen
|
|
603
|
+
|
|
604
|
+
if start < 0 {
|
|
605
|
+
end = vlen + start
|
|
606
|
+
if end < 0 {
|
|
607
|
+
end = 0
|
|
608
|
+
}
|
|
609
|
+
start = 0
|
|
610
|
+
} else if endP != nil {
|
|
611
|
+
end = *endP
|
|
612
|
+
if end < 0 {
|
|
613
|
+
end = vlen + end
|
|
614
|
+
if end < 0 {
|
|
615
|
+
end = 0
|
|
616
|
+
}
|
|
617
|
+
} else if vlen < end {
|
|
618
|
+
end = vlen
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if vlen < start {
|
|
623
|
+
start = vlen
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if start >= 0 && start <= end && end <= vlen {
|
|
627
|
+
if IsList(val) {
|
|
628
|
+
list, _ := _asList(val)
|
|
629
|
+
if list == nil {
|
|
630
|
+
list = _listify(val)
|
|
631
|
+
}
|
|
632
|
+
if mutate {
|
|
633
|
+
for i, j := 0, start; j < end; i, j = i+1, j+1 {
|
|
634
|
+
list[i] = list[j]
|
|
635
|
+
}
|
|
636
|
+
list = list[:end-start]
|
|
637
|
+
if lr, ok := val.(*ListRef[any]); ok {
|
|
638
|
+
lr.List = list
|
|
639
|
+
return lr
|
|
640
|
+
}
|
|
641
|
+
return list
|
|
642
|
+
}
|
|
643
|
+
return append([]any{}, list[start:end]...)
|
|
644
|
+
} else if s, ok := val.(string); ok {
|
|
645
|
+
return s[start:end]
|
|
646
|
+
}
|
|
647
|
+
} else {
|
|
648
|
+
if IsList(val) {
|
|
649
|
+
return []any{}
|
|
650
|
+
} else if _, ok := val.(string); ok {
|
|
651
|
+
return S_MT
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return val
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// String padding. Positive padding right-pads, negative left-pads.
|
|
659
|
+
// Default padding is 44, default pad character is space.
|
|
660
|
+
func Pad(str any, args ...any) string {
|
|
661
|
+
var s string
|
|
662
|
+
if ss, ok := str.(string); ok {
|
|
663
|
+
s = ss
|
|
664
|
+
} else {
|
|
665
|
+
s = Stringify(str)
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
padding := 44
|
|
669
|
+
if len(args) > 0 && args[0] != nil {
|
|
670
|
+
if f, err := _toFloat64(args[0]); err == nil {
|
|
671
|
+
padding = int(f)
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
padchar := S_SP
|
|
676
|
+
if len(args) > 1 && args[1] != nil {
|
|
677
|
+
if pc, ok := args[1].(string); ok && len(pc) > 0 {
|
|
678
|
+
padchar = string(pc[0])
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if padding >= 0 {
|
|
683
|
+
for len(s) < padding {
|
|
684
|
+
s += padchar
|
|
685
|
+
}
|
|
686
|
+
} else {
|
|
687
|
+
target := -padding
|
|
688
|
+
for len(s) < target {
|
|
689
|
+
s = padchar + s
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return s
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Get a list element. The key should be an integer, or a string
|
|
697
|
+
// that parses to an integer. Negative integers count from the end.
|
|
698
|
+
func GetElem(val any, key any, alts ...any) any {
|
|
699
|
+
var alt any
|
|
700
|
+
if len(alts) > 0 {
|
|
701
|
+
alt = alts[0]
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if nil == val || nil == key {
|
|
705
|
+
return alt
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
var out any
|
|
709
|
+
|
|
710
|
+
if IsList(val) {
|
|
711
|
+
ks := StrKey(key)
|
|
712
|
+
if reIntegerKey.MatchString(ks) {
|
|
713
|
+
nkey, err := strconv.Atoi(ks)
|
|
714
|
+
if err == nil {
|
|
715
|
+
list, ok := _asList(val)
|
|
716
|
+
if !ok {
|
|
717
|
+
list = _listify(val)
|
|
718
|
+
}
|
|
719
|
+
if nkey < 0 {
|
|
720
|
+
nkey = len(list) + nkey
|
|
721
|
+
}
|
|
722
|
+
if nkey >= 0 && nkey < len(list) {
|
|
723
|
+
out = list[nkey]
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if nil == out {
|
|
730
|
+
if 0 < (T_function & Typify(alt)) {
|
|
731
|
+
fn := reflect.ValueOf(alt)
|
|
732
|
+
results := fn.Call(nil)
|
|
733
|
+
if len(results) > 0 {
|
|
734
|
+
return results[0].Interface()
|
|
735
|
+
}
|
|
736
|
+
return nil
|
|
737
|
+
}
|
|
738
|
+
return alt
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return out
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Safely get a property of a node. Nil arguments return nil.
|
|
745
|
+
// If the key is not found, return the alternative value, if any.
|
|
746
|
+
func GetProp(val any, key any, alts ...any) any {
|
|
747
|
+
var out any
|
|
748
|
+
var alt any
|
|
749
|
+
|
|
750
|
+
if len(alts) > 0 {
|
|
751
|
+
alt = alts[0]
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if nil == val || nil == key {
|
|
755
|
+
return alt
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
if IsMap(val) {
|
|
759
|
+
ks, ok := key.(string)
|
|
760
|
+
if !ok {
|
|
761
|
+
ks = StrKey(key)
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
v := val.(map[string]any)
|
|
765
|
+
res, has := v[ks]
|
|
766
|
+
if has {
|
|
767
|
+
out = res
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
} else if IsList(val) {
|
|
771
|
+
ki, ok := key.(int)
|
|
772
|
+
if !ok {
|
|
773
|
+
switch kf := key.(type) {
|
|
774
|
+
case float64:
|
|
775
|
+
ki = int(kf)
|
|
776
|
+
|
|
777
|
+
case string:
|
|
778
|
+
ki = -1
|
|
779
|
+
ski, err := strconv.Atoi(key.(string))
|
|
780
|
+
if nil == err {
|
|
781
|
+
ki = ski
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if lr, isLR := val.(*ListRef[any]); isLR {
|
|
787
|
+
if 0 <= ki && ki < len(lr.List) {
|
|
788
|
+
out = lr.List[ki]
|
|
789
|
+
}
|
|
790
|
+
} else {
|
|
791
|
+
v, ok := val.([]any)
|
|
792
|
+
|
|
793
|
+
if !ok {
|
|
794
|
+
rv := reflect.ValueOf(val)
|
|
795
|
+
if rv.Kind() == reflect.Slice && 0 <= ki && ki < rv.Len() {
|
|
796
|
+
out = rv.Index(ki).Interface()
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
} else {
|
|
800
|
+
if 0 <= ki && ki < len(v) {
|
|
801
|
+
out = v[ki]
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
} else {
|
|
807
|
+
valRef := reflect.ValueOf(val)
|
|
808
|
+
if valRef.Kind() == reflect.Ptr {
|
|
809
|
+
valRef = valRef.Elem()
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if valRef.Kind() == reflect.Struct {
|
|
813
|
+
ks, ok := key.(string)
|
|
814
|
+
if !ok {
|
|
815
|
+
ks = StrKey(key)
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
field := valRef.FieldByName(ks)
|
|
819
|
+
if field.IsValid() {
|
|
820
|
+
out = field.Interface()
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if nil == out {
|
|
826
|
+
return alt
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
return out
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Sorted keys of a map, or indexes of a list.
|
|
833
|
+
func KeysOf(val any) []string {
|
|
834
|
+
if IsMap(val) {
|
|
835
|
+
m := val.(map[string]any)
|
|
836
|
+
|
|
837
|
+
keys := make([]string, 0, len(m))
|
|
838
|
+
for k := range m {
|
|
839
|
+
keys = append(keys, k)
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
sort.Strings(keys)
|
|
843
|
+
|
|
844
|
+
return keys
|
|
845
|
+
|
|
846
|
+
} else if IsList(val) {
|
|
847
|
+
list, _ := _asList(val)
|
|
848
|
+
if list == nil {
|
|
849
|
+
list = _listify(val)
|
|
850
|
+
}
|
|
851
|
+
keys := make([]string, len(list))
|
|
852
|
+
for i := range list {
|
|
853
|
+
keys[i] = StrKey(i)
|
|
854
|
+
}
|
|
855
|
+
return keys
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
return make([]string, 0)
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
// Value of property with name key in node val is defined.
|
|
863
|
+
func HasKey(val any, key any) bool {
|
|
864
|
+
return nil != GetProp(val, key)
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
// List the sorted keys of a map or list as an array of tuples of the form [key, value].
|
|
869
|
+
func Items(val any) [][2]any {
|
|
870
|
+
if IsMap(val) {
|
|
871
|
+
m := val.(map[string]any)
|
|
872
|
+
out := make([][2]any, 0, len(m))
|
|
873
|
+
|
|
874
|
+
keys := KeysOf(val)
|
|
875
|
+
// keys := make([]string, 0, len(m))
|
|
876
|
+
// for k := range m {
|
|
877
|
+
// keys = append(keys, k)
|
|
878
|
+
// }
|
|
879
|
+
// sort.Strings(keys)
|
|
880
|
+
|
|
881
|
+
for _, k := range keys {
|
|
882
|
+
out = append(out, [2]any{k, m[k]})
|
|
883
|
+
}
|
|
884
|
+
return out
|
|
885
|
+
|
|
886
|
+
} else if IsList(val) {
|
|
887
|
+
list, _ := _asList(val)
|
|
888
|
+
if list == nil {
|
|
889
|
+
list = _listify(val)
|
|
890
|
+
}
|
|
891
|
+
out := make([][2]any, 0, len(list))
|
|
892
|
+
for i, v := range list {
|
|
893
|
+
out = append(out, [2]any{strconv.Itoa(i), v})
|
|
894
|
+
}
|
|
895
|
+
return out
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return make([][2]any, 0, 0)
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// List items with an optional apply callback that maps over each [key, value] tuple.
|
|
902
|
+
func ItemsApply(val any, apply func([2]any) any) []any {
|
|
903
|
+
items := Items(val)
|
|
904
|
+
out := make([]any, len(items))
|
|
905
|
+
for i, item := range items {
|
|
906
|
+
out[i] = apply(item)
|
|
907
|
+
}
|
|
908
|
+
return out
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// Flatten a nested list to a given depth (default 1).
|
|
912
|
+
// Non-list inputs are returned as-is.
|
|
913
|
+
func Flatten(list any, depths ...int) any {
|
|
914
|
+
if !IsList(list) {
|
|
915
|
+
return list
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
depth := 1
|
|
919
|
+
if len(depths) > 0 {
|
|
920
|
+
depth = depths[0]
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
arr, ok := _asList(list)
|
|
924
|
+
if !ok {
|
|
925
|
+
arr = _listify(list)
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
return _flattenDepth(arr, depth)
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
func _flattenDepth(arr []any, depth int) []any {
|
|
932
|
+
result := make([]any, 0)
|
|
933
|
+
for _, item := range arr {
|
|
934
|
+
if depth > 0 {
|
|
935
|
+
if sub, ok := _asList(item); ok {
|
|
936
|
+
result = append(result, _flattenDepth(sub, depth-1)...)
|
|
937
|
+
continue
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
result = append(result, item)
|
|
941
|
+
}
|
|
942
|
+
return result
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Filter item values using check function.
|
|
946
|
+
// Returns values where the check function returns true.
|
|
947
|
+
func Filter(val any, check func([2]any) bool) []any {
|
|
948
|
+
all := Items(val)
|
|
949
|
+
out := make([]any, 0)
|
|
950
|
+
for _, item := range all {
|
|
951
|
+
if check(item) {
|
|
952
|
+
out = append(out, item[1])
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
return out
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
// Escape regular expression.
|
|
960
|
+
func EscRe(s string) string {
|
|
961
|
+
if s == "" {
|
|
962
|
+
return ""
|
|
963
|
+
}
|
|
964
|
+
re := regexp.MustCompile(`[.*+?^${}()|\[\]\\]`)
|
|
965
|
+
return re.ReplaceAllString(s, `\${0}`)
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Escape URLs.
|
|
969
|
+
func EscUrl(s string) string {
|
|
970
|
+
return url.QueryEscape(s)
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
var (
|
|
974
|
+
reNonSlashSlash = regexp.MustCompile(`([^/])/+`)
|
|
975
|
+
reTrailingSlash = regexp.MustCompile(`/+$`)
|
|
976
|
+
reLeadingSlash = regexp.MustCompile(`^/+`)
|
|
977
|
+
)
|
|
978
|
+
|
|
979
|
+
// Concatenate url part strings, merging forward slashes as needed.
|
|
980
|
+
func JoinUrl(parts []any) string {
|
|
981
|
+
var filtered []string
|
|
982
|
+
for _, p := range parts {
|
|
983
|
+
if "" != p && nil != p {
|
|
984
|
+
ps, ok := p.(string)
|
|
985
|
+
if !ok {
|
|
986
|
+
ps = Stringify(p)
|
|
987
|
+
}
|
|
988
|
+
filtered = append(filtered, ps)
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
for i, s := range filtered {
|
|
993
|
+
if i == 0 {
|
|
994
|
+
// For the first part, only remove trailing slashes
|
|
995
|
+
s = reTrailingSlash.ReplaceAllString(s, "")
|
|
996
|
+
} else {
|
|
997
|
+
// For remaining parts, handle both leading and trailing slashes
|
|
998
|
+
s = reNonSlashSlash.ReplaceAllString(s, `$1/`)
|
|
999
|
+
s = reLeadingSlash.ReplaceAllString(s, "")
|
|
1000
|
+
s = reTrailingSlash.ReplaceAllString(s, "")
|
|
1001
|
+
}
|
|
1002
|
+
filtered[i] = s
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
finalParts := filtered[:0]
|
|
1006
|
+
for _, s := range filtered {
|
|
1007
|
+
if s != "" {
|
|
1008
|
+
finalParts = append(finalParts, s)
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
return strings.Join(finalParts, "/")
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
// Concatenate string array elements, merging separator chars as needed.
|
|
1017
|
+
// Optional args: sep (string, default ","), url (bool, default false).
|
|
1018
|
+
func Join(arr []any, args ...any) string {
|
|
1019
|
+
sarr := Size(arr)
|
|
1020
|
+
|
|
1021
|
+
sep := ","
|
|
1022
|
+
urlMode := false
|
|
1023
|
+
|
|
1024
|
+
if len(args) > 0 && args[0] != nil {
|
|
1025
|
+
if s, ok := args[0].(string); ok {
|
|
1026
|
+
sep = s
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
if len(args) > 1 && args[1] != nil {
|
|
1030
|
+
if b, ok := args[1].(bool); ok {
|
|
1031
|
+
urlMode = b
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
var sepre string
|
|
1036
|
+
if 1 == len(sep) {
|
|
1037
|
+
sepre = EscRe(sep)
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Filter to only non-empty strings
|
|
1041
|
+
filtered := Filter(arr, func(n [2]any) bool {
|
|
1042
|
+
t := Typify(n[1])
|
|
1043
|
+
return (0 < (T_string & t)) && S_MT != n[1]
|
|
1044
|
+
})
|
|
1045
|
+
|
|
1046
|
+
// Process each element for separator handling
|
|
1047
|
+
processed := Items(filtered)
|
|
1048
|
+
|
|
1049
|
+
var parts []string
|
|
1050
|
+
for _, kv := range processed {
|
|
1051
|
+
idx := 0
|
|
1052
|
+
if kstr, ok := kv[0].(string); ok {
|
|
1053
|
+
n, err := strconv.Atoi(kstr)
|
|
1054
|
+
if err == nil {
|
|
1055
|
+
idx = n
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
s, ok := kv[1].(string)
|
|
1059
|
+
if !ok {
|
|
1060
|
+
continue
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if sepre != "" && sepre != S_MT {
|
|
1064
|
+
reTrailing := regexp.MustCompile(sepre + `+$`)
|
|
1065
|
+
reLeading := regexp.MustCompile(`^` + sepre + `+`)
|
|
1066
|
+
reInternal := regexp.MustCompile(`([^` + sepre + `])` + sepre + `+([^` + sepre + `])`)
|
|
1067
|
+
|
|
1068
|
+
if urlMode && 0 == idx {
|
|
1069
|
+
s = reTrailing.ReplaceAllString(s, S_MT)
|
|
1070
|
+
} else {
|
|
1071
|
+
if 0 < idx {
|
|
1072
|
+
s = reLeading.ReplaceAllString(s, S_MT)
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
if idx < sarr-1 || !urlMode {
|
|
1076
|
+
s = reTrailing.ReplaceAllString(s, S_MT)
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
s = reInternal.ReplaceAllString(s, "${1}"+sep+"${2}")
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
if s != S_MT {
|
|
1084
|
+
parts = append(parts, s)
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
return strings.Join(parts, sep)
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
|
|
1092
|
+
// Output JSON in a "standard" format, with 2 space indents, each property on a new line,
|
|
1093
|
+
// and spaces after {[: and before ]}. Any "weird" values (NaN, etc) are output as null.
|
|
1094
|
+
// In general, the behavior of JavaScript's JSON.stringify(val,null,2) is followed.
|
|
1095
|
+
func Jsonify(val any, flags ...map[string]any) string {
|
|
1096
|
+
str := S_null
|
|
1097
|
+
|
|
1098
|
+
indent := 2
|
|
1099
|
+
offset := 0
|
|
1100
|
+
|
|
1101
|
+
if len(flags) > 0 && flags[0] != nil {
|
|
1102
|
+
if v, ok := flags[0]["indent"]; ok {
|
|
1103
|
+
if n, ok := v.(int); ok {
|
|
1104
|
+
indent = n
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
if v, ok := flags[0]["offset"]; ok {
|
|
1108
|
+
if n, ok := v.(int); ok {
|
|
1109
|
+
offset = n
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if nil != val {
|
|
1115
|
+
indentStr := strings.Repeat(" ", indent)
|
|
1116
|
+
offsetStr := strings.Repeat(" ", offset)
|
|
1117
|
+
b, err := json.MarshalIndent(val, offsetStr, indentStr)
|
|
1118
|
+
if err != nil {
|
|
1119
|
+
str = S_null
|
|
1120
|
+
} else {
|
|
1121
|
+
str = string(b)
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
return str
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// Safely stringify a value for humans (NOT JSON!).
|
|
1129
|
+
func Stringify(val any, maxlen ...int) string {
|
|
1130
|
+
if nil == val {
|
|
1131
|
+
return S_MT
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
if lr, ok := val.(*ListRef[any]); ok {
|
|
1135
|
+
return Stringify(lr.List, maxlen...)
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// Strings are returned directly without JSON serialization.
|
|
1139
|
+
if s, ok := val.(string); ok {
|
|
1140
|
+
jsonStr := s
|
|
1141
|
+
if len(maxlen) > 0 && maxlen[0] > 0 {
|
|
1142
|
+
ml := maxlen[0]
|
|
1143
|
+
if len(jsonStr) > ml {
|
|
1144
|
+
if ml >= 3 {
|
|
1145
|
+
jsonStr = jsonStr[:ml-3] + "..."
|
|
1146
|
+
} else {
|
|
1147
|
+
jsonStr = jsonStr[:ml]
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return jsonStr
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// Unwrap any nested ListRefs before marshaling to JSON.
|
|
1155
|
+
val = _unwrapListRefs(val)
|
|
1156
|
+
|
|
1157
|
+
b, err := json.Marshal(val)
|
|
1158
|
+
if err != nil {
|
|
1159
|
+
return "__STRINGIFY_FAILED__"
|
|
1160
|
+
}
|
|
1161
|
+
jsonStr := string(b)
|
|
1162
|
+
|
|
1163
|
+
jsonStr = strings.ReplaceAll(jsonStr, `"`, "")
|
|
1164
|
+
|
|
1165
|
+
if len(maxlen) > 0 && maxlen[0] > 0 {
|
|
1166
|
+
ml := maxlen[0]
|
|
1167
|
+
if len(jsonStr) > ml {
|
|
1168
|
+
if ml >= 3 {
|
|
1169
|
+
jsonStr = jsonStr[:ml-3] + "..."
|
|
1170
|
+
} else {
|
|
1171
|
+
jsonStr = jsonStr[:ml]
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
return jsonStr
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// Build a human friendly path string.
|
|
1180
|
+
func Pathify(val any, from ...int) string {
|
|
1181
|
+
var pathstr *string
|
|
1182
|
+
|
|
1183
|
+
var path []any = nil
|
|
1184
|
+
|
|
1185
|
+
if IsList(val) {
|
|
1186
|
+
path = _listify(val)
|
|
1187
|
+
} else {
|
|
1188
|
+
str, ok := val.(string)
|
|
1189
|
+
if ok {
|
|
1190
|
+
path = append(path, str)
|
|
1191
|
+
} else {
|
|
1192
|
+
num, err := _toFloat64(val)
|
|
1193
|
+
if nil == err {
|
|
1194
|
+
path = append(path, strconv.FormatInt(int64(math.Floor(num)), 10))
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
var start int
|
|
1200
|
+
if 0 == len(from) {
|
|
1201
|
+
start = 0
|
|
1202
|
+
|
|
1203
|
+
} else {
|
|
1204
|
+
start = from[0]
|
|
1205
|
+
if start < 0 {
|
|
1206
|
+
start = 0
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
end := 0
|
|
1211
|
+
if len(from) > 1 {
|
|
1212
|
+
end = from[1]
|
|
1213
|
+
if end < 0 {
|
|
1214
|
+
end = 0
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
if nil != path && 0 <= start {
|
|
1219
|
+
if len(path) < start {
|
|
1220
|
+
start = len(path)
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
endIdx := len(path) - end
|
|
1224
|
+
if endIdx < start {
|
|
1225
|
+
endIdx = start
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
sliced := path[start:endIdx]
|
|
1229
|
+
if len(sliced) == 0 {
|
|
1230
|
+
root := "<root>"
|
|
1231
|
+
pathstr = &root
|
|
1232
|
+
|
|
1233
|
+
} else {
|
|
1234
|
+
var filtered []any
|
|
1235
|
+
for _, p := range sliced {
|
|
1236
|
+
switch x := p.(type) {
|
|
1237
|
+
case string:
|
|
1238
|
+
filtered = append(filtered, x)
|
|
1239
|
+
case int, int8, int16, int32, int64,
|
|
1240
|
+
float32, float64, uint, uint8, uint16, uint32, uint64:
|
|
1241
|
+
filtered = append(filtered, x)
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
var mapped []string
|
|
1246
|
+
for _, p := range filtered {
|
|
1247
|
+
switch x := p.(type) {
|
|
1248
|
+
case string:
|
|
1249
|
+
replaced := strings.ReplaceAll(x, S_DT, S_MT)
|
|
1250
|
+
mapped = append(mapped, replaced)
|
|
1251
|
+
default:
|
|
1252
|
+
numVal, err := _toFloat64(x)
|
|
1253
|
+
if err == nil {
|
|
1254
|
+
mapped = append(mapped, S_MT+strconv.FormatInt(int64(math.Floor(numVal)), 10))
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
joined := strings.Join(mapped, S_DT)
|
|
1260
|
+
pathstr = &joined
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
if nil == pathstr {
|
|
1265
|
+
var sb strings.Builder
|
|
1266
|
+
sb.WriteString("<unknown-path")
|
|
1267
|
+
if val == nil {
|
|
1268
|
+
sb.WriteString(S_MT)
|
|
1269
|
+
} else {
|
|
1270
|
+
sb.WriteString(S_CN)
|
|
1271
|
+
sb.WriteString(Stringify(val, 33))
|
|
1272
|
+
}
|
|
1273
|
+
sb.WriteString(">")
|
|
1274
|
+
updesc := sb.String()
|
|
1275
|
+
pathstr = &updesc
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
return *pathstr
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// Clone a JSON-like data structure.
|
|
1282
|
+
// NOTE: function value references are copied, *not* cloned.
|
|
1283
|
+
func Clone(val any) any {
|
|
1284
|
+
return CloneFlags(val, nil)
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
func CloneFlags(val any, flags map[string]bool) any {
|
|
1288
|
+
if val == nil {
|
|
1289
|
+
return nil
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
if nil == flags {
|
|
1293
|
+
flags = map[string]bool{}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
if _, ok := flags["func"]; !ok {
|
|
1297
|
+
flags["func"] = true
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
typ := reflect.TypeOf(val)
|
|
1301
|
+
if typ.Kind() == reflect.Func {
|
|
1302
|
+
if flags["func"] {
|
|
1303
|
+
return val
|
|
1304
|
+
}
|
|
1305
|
+
return nil
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
switch v := val.(type) {
|
|
1309
|
+
case map[string]any:
|
|
1310
|
+
newMap := make(map[string]any, len(v))
|
|
1311
|
+
for key, value := range v {
|
|
1312
|
+
newMap[key] = CloneFlags(value, flags)
|
|
1313
|
+
}
|
|
1314
|
+
return newMap
|
|
1315
|
+
case *ListRef[any]:
|
|
1316
|
+
newSlice := make([]any, len(v.List))
|
|
1317
|
+
for i, value := range v.List {
|
|
1318
|
+
newSlice[i] = CloneFlags(value, flags)
|
|
1319
|
+
}
|
|
1320
|
+
if flags["unwrap"] {
|
|
1321
|
+
return newSlice
|
|
1322
|
+
}
|
|
1323
|
+
return &ListRef[any]{List: newSlice}
|
|
1324
|
+
case []any:
|
|
1325
|
+
newSlice := make([]any, len(v))
|
|
1326
|
+
for i, value := range v {
|
|
1327
|
+
newSlice[i] = CloneFlags(value, flags)
|
|
1328
|
+
}
|
|
1329
|
+
if flags["wrap"] {
|
|
1330
|
+
return &ListRef[any]{List: newSlice}
|
|
1331
|
+
}
|
|
1332
|
+
return newSlice
|
|
1333
|
+
default:
|
|
1334
|
+
return v
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// Define a JSON Object from alternating key-value arguments.
|
|
1339
|
+
// jo("a", 1, "b", 2) => {"a": 1, "b": 2}
|
|
1340
|
+
func Jo(kv ...any) map[string]any {
|
|
1341
|
+
o := make(map[string]any)
|
|
1342
|
+
kvsize := len(kv)
|
|
1343
|
+
for i := 0; i < kvsize; i += 2 {
|
|
1344
|
+
k := GetProp(kv, i, S_DS+S_KEY+strconv.Itoa(i))
|
|
1345
|
+
ks, ok := k.(string)
|
|
1346
|
+
if !ok {
|
|
1347
|
+
ks = Stringify(k)
|
|
1348
|
+
}
|
|
1349
|
+
o[ks] = GetProp(kv, i+1)
|
|
1350
|
+
}
|
|
1351
|
+
return o
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// Define a JSON Array from arguments.
|
|
1355
|
+
// ja(1, "x", true) => [1, "x", true]
|
|
1356
|
+
func Ja(v ...any) []any {
|
|
1357
|
+
a := make([]any, len(v))
|
|
1358
|
+
for i := 0; i < len(v); i++ {
|
|
1359
|
+
a[i] = GetProp(v, i)
|
|
1360
|
+
}
|
|
1361
|
+
return a
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// Safely delete a property from a map or list element.
|
|
1365
|
+
// For maps, the property is deleted. For lists, the element at the
|
|
1366
|
+
// index is removed and remaining elements are shifted down.
|
|
1367
|
+
// Returns the (possibly modified) parent.
|
|
1368
|
+
func DelProp(parent any, key any) any {
|
|
1369
|
+
if !IsKey(key) {
|
|
1370
|
+
return parent
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
if IsMap(parent) {
|
|
1374
|
+
ks := StrKey(key)
|
|
1375
|
+
delete(parent.(map[string]any), ks)
|
|
1376
|
+
} else if IsList(parent) {
|
|
1377
|
+
ks := StrKey(key)
|
|
1378
|
+
ki, err := _parseInt(ks)
|
|
1379
|
+
if err != nil {
|
|
1380
|
+
return parent
|
|
1381
|
+
}
|
|
1382
|
+
ki = int(math.Floor(float64(ki)))
|
|
1383
|
+
|
|
1384
|
+
if lr, isLR := parent.(*ListRef[any]); isLR {
|
|
1385
|
+
psize := len(lr.List)
|
|
1386
|
+
if 0 <= ki && ki < psize {
|
|
1387
|
+
copy(lr.List[ki:], lr.List[ki+1:])
|
|
1388
|
+
lr.List = lr.List[:psize-1]
|
|
1389
|
+
}
|
|
1390
|
+
return parent
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
arr, genarr := parent.([]any)
|
|
1394
|
+
if !genarr {
|
|
1395
|
+
rv := reflect.ValueOf(parent)
|
|
1396
|
+
arr = make([]any, rv.Len())
|
|
1397
|
+
for i := 0; i < rv.Len(); i++ {
|
|
1398
|
+
arr[i] = rv.Index(i).Interface()
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
psize := len(arr)
|
|
1403
|
+
if 0 <= ki && ki < psize {
|
|
1404
|
+
copy(arr[ki:], arr[ki+1:])
|
|
1405
|
+
arr = arr[:psize-1]
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
if !genarr {
|
|
1409
|
+
return _makeArrayType(arr, parent)
|
|
1410
|
+
}
|
|
1411
|
+
return arr
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
return parent
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
// Safely set a property. Undefined arguments and invalid keys are ignored.
|
|
1418
|
+
// Returns the (possibly modified) parent.
|
|
1419
|
+
// If the value is undefined the key will be deleted from the parent.
|
|
1420
|
+
// If the parent is a list, and the key is negative, prepend the value.
|
|
1421
|
+
// NOTE: If the key is above the list size, append the value; below, prepend.
|
|
1422
|
+
// If the value is undefined, remove the list element at index key, and shift the
|
|
1423
|
+
// remaining elements down. These rules avoid "holes" in the list.
|
|
1424
|
+
func SetProp(parent any, key any, newval any) any {
|
|
1425
|
+
if !IsKey(key) {
|
|
1426
|
+
return parent
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
if IsMap(parent) {
|
|
1430
|
+
m := parent.(map[string]any)
|
|
1431
|
+
|
|
1432
|
+
// Convert key to string
|
|
1433
|
+
ks := ""
|
|
1434
|
+
ks = StrKey(key)
|
|
1435
|
+
|
|
1436
|
+
// Preserve nil values (like JS null). Use DelProp for explicit key removal.
|
|
1437
|
+
m[ks] = newval
|
|
1438
|
+
|
|
1439
|
+
} else if IsList(parent) {
|
|
1440
|
+
|
|
1441
|
+
// Convert key to integer
|
|
1442
|
+
var ki int
|
|
1443
|
+
switch k := key.(type) {
|
|
1444
|
+
case int:
|
|
1445
|
+
ki = k
|
|
1446
|
+
case float64:
|
|
1447
|
+
ki = int(k)
|
|
1448
|
+
case string:
|
|
1449
|
+
kiParsed, e := _parseInt(k)
|
|
1450
|
+
if e == nil {
|
|
1451
|
+
ki = kiParsed
|
|
1452
|
+
} else {
|
|
1453
|
+
// no-op, can't set
|
|
1454
|
+
return parent
|
|
1455
|
+
}
|
|
1456
|
+
default:
|
|
1457
|
+
return parent
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// ListRef: modify .List in place, return same pointer for reference stability.
|
|
1461
|
+
if lr, isLR := parent.(*ListRef[any]); isLR {
|
|
1462
|
+
if newval == nil {
|
|
1463
|
+
if ki >= 0 && ki < len(lr.List) {
|
|
1464
|
+
copy(lr.List[ki:], lr.List[ki+1:])
|
|
1465
|
+
lr.List = lr.List[:len(lr.List)-1]
|
|
1466
|
+
}
|
|
1467
|
+
return parent
|
|
1468
|
+
}
|
|
1469
|
+
if ki >= 0 {
|
|
1470
|
+
if ki >= len(lr.List) {
|
|
1471
|
+
lr.List = append(lr.List, newval)
|
|
1472
|
+
} else {
|
|
1473
|
+
lr.List[ki] = newval
|
|
1474
|
+
}
|
|
1475
|
+
return parent
|
|
1476
|
+
}
|
|
1477
|
+
if ki < 0 {
|
|
1478
|
+
lr.List = append([]any{newval}, lr.List...)
|
|
1479
|
+
return parent
|
|
1480
|
+
}
|
|
1481
|
+
return parent
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
arr, genarr := parent.([]any)
|
|
1485
|
+
|
|
1486
|
+
// If newval == nil, remove element [shift down].
|
|
1487
|
+
|
|
1488
|
+
if !genarr {
|
|
1489
|
+
rv := reflect.ValueOf(parent)
|
|
1490
|
+
arr = make([]any, rv.Len())
|
|
1491
|
+
for i := 0; i < rv.Len(); i++ {
|
|
1492
|
+
arr[i] = rv.Index(i).Interface()
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
if newval == nil {
|
|
1497
|
+
if ki >= 0 && ki < len(arr) {
|
|
1498
|
+
copy(arr[ki:], arr[ki+1:])
|
|
1499
|
+
arr = arr[:len(arr)-1]
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
if !genarr {
|
|
1503
|
+
return _makeArrayType(arr, parent)
|
|
1504
|
+
} else {
|
|
1505
|
+
|
|
1506
|
+
return arr
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
// If ki >= 0, set or append
|
|
1511
|
+
if ki >= 0 {
|
|
1512
|
+
if ki >= len(arr) {
|
|
1513
|
+
arr = append(arr, newval)
|
|
1514
|
+
} else {
|
|
1515
|
+
arr[ki] = newval
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
if !genarr {
|
|
1519
|
+
return _makeArrayType(arr, parent)
|
|
1520
|
+
} else {
|
|
1521
|
+
return arr
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// If ki < 0, prepend
|
|
1526
|
+
if ki < 0 {
|
|
1527
|
+
// prepend
|
|
1528
|
+
newarr := make([]any, 0, len(arr)+1)
|
|
1529
|
+
newarr = append(newarr, newval)
|
|
1530
|
+
newarr = append(newarr, arr...)
|
|
1531
|
+
if !genarr {
|
|
1532
|
+
return _makeArrayType(newarr, parent)
|
|
1533
|
+
} else {
|
|
1534
|
+
return newarr
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
return parent
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// Walk a data structure depth first, applying functions to each value.
|
|
1543
|
+
// Walk(val, before) - before callback only (pre-order).
|
|
1544
|
+
// Walk(val, before, after) - both before and after callbacks.
|
|
1545
|
+
// Walk(val, before, after, maxdepth) - with maximum recursion depth.
|
|
1546
|
+
// Pass nil for before or after to skip that callback.
|
|
1547
|
+
// For backward compatibility, Walk(val, apply) applies the callback after children (post-order).
|
|
1548
|
+
func Walk(
|
|
1549
|
+
val any,
|
|
1550
|
+
apply WalkApply,
|
|
1551
|
+
opts ...any,
|
|
1552
|
+
) any {
|
|
1553
|
+
var after WalkApply
|
|
1554
|
+
var maxdepth int = 32
|
|
1555
|
+
|
|
1556
|
+
if len(opts) > 0 {
|
|
1557
|
+
if opts[0] != nil {
|
|
1558
|
+
if fn, ok := opts[0].(WalkApply); ok {
|
|
1559
|
+
after = fn
|
|
1560
|
+
} else if fn, ok := opts[0].(func(*string, any, any, []string) any); ok {
|
|
1561
|
+
after = fn
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
if len(opts) > 1 {
|
|
1566
|
+
if opts[1] != nil {
|
|
1567
|
+
switch md := opts[1].(type) {
|
|
1568
|
+
case int:
|
|
1569
|
+
maxdepth = md
|
|
1570
|
+
case float64:
|
|
1571
|
+
maxdepth = int(md)
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
if after != nil {
|
|
1577
|
+
// Two-callback mode: apply is before, after is after.
|
|
1578
|
+
return _walkDescend(val, apply, after, maxdepth, nil, nil, nil)
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// Single-callback mode: apply is called before children (pre-order),
|
|
1582
|
+
// matching the TS implementation where walk(val, before) is pre-order.
|
|
1583
|
+
return _walkDescend(val, apply, nil, maxdepth, nil, nil, nil)
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
|
|
1587
|
+
func WalkDescend(
|
|
1588
|
+
val any,
|
|
1589
|
+
apply WalkApply,
|
|
1590
|
+
key *string,
|
|
1591
|
+
parent any,
|
|
1592
|
+
path []string,
|
|
1593
|
+
) any {
|
|
1594
|
+
return _walkDescend(val, nil, apply, 32, key, parent, path)
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
|
|
1598
|
+
func _walkDescend(
|
|
1599
|
+
val any,
|
|
1600
|
+
before WalkApply,
|
|
1601
|
+
after WalkApply,
|
|
1602
|
+
maxdepth int,
|
|
1603
|
+
key *string,
|
|
1604
|
+
parent any,
|
|
1605
|
+
path []string,
|
|
1606
|
+
) any {
|
|
1607
|
+
|
|
1608
|
+
out := val
|
|
1609
|
+
|
|
1610
|
+
// Apply before callback.
|
|
1611
|
+
if nil != before {
|
|
1612
|
+
out = before(key, out, parent, path)
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
// Check depth limit.
|
|
1616
|
+
if 0 == maxdepth || (nil != path && 0 < maxdepth && maxdepth <= len(path)) {
|
|
1617
|
+
return out
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
if IsNode(out) {
|
|
1621
|
+
for _, kv := range Items(out) {
|
|
1622
|
+
ckey := kv[0]
|
|
1623
|
+
child := kv[1]
|
|
1624
|
+
ckeyStr := StrKey(ckey)
|
|
1625
|
+
newPath := make([]string, len(path)+1)
|
|
1626
|
+
copy(newPath, path)
|
|
1627
|
+
newPath[len(path)] = ckeyStr
|
|
1628
|
+
newChild := _walkDescend(child, before, after, maxdepth, &ckeyStr, out, newPath)
|
|
1629
|
+
out = SetProp(out, ckey, newChild)
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
if nil != parent && nil != key {
|
|
1633
|
+
SetProp(parent, *key, out)
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
// Apply after callback.
|
|
1638
|
+
if nil != after {
|
|
1639
|
+
out = after(key, out, parent, path)
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
return out
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
// Merge a list of values into each other. Later values have
|
|
1646
|
+
// precedence. Nodes override scalars. Node kinds (list or map)
|
|
1647
|
+
// override each other, and do *not* merge. The first element is
|
|
1648
|
+
// modified.
|
|
1649
|
+
// Optional maxdepth parameter limits recursion depth.
|
|
1650
|
+
func Merge(val any, maxdepths ...int) any {
|
|
1651
|
+
md := 32
|
|
1652
|
+
if len(maxdepths) > 0 {
|
|
1653
|
+
if maxdepths[0] < 0 {
|
|
1654
|
+
md = 0
|
|
1655
|
+
} else {
|
|
1656
|
+
md = maxdepths[0]
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
var out any = nil
|
|
1661
|
+
|
|
1662
|
+
if !IsList(val) {
|
|
1663
|
+
return val
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
list := _listify(val)
|
|
1667
|
+
lenlist := len(list)
|
|
1668
|
+
|
|
1669
|
+
if 0 == lenlist {
|
|
1670
|
+
return nil
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
if 1 == lenlist {
|
|
1674
|
+
return list[0]
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// Merge a list of values.
|
|
1678
|
+
out = GetProp(list, 0, make(map[string]any))
|
|
1679
|
+
|
|
1680
|
+
for i := 1; i < lenlist; i++ {
|
|
1681
|
+
obj := list[i]
|
|
1682
|
+
|
|
1683
|
+
if !IsNode(obj) {
|
|
1684
|
+
// Nodes win.
|
|
1685
|
+
out = obj
|
|
1686
|
+
} else {
|
|
1687
|
+
// Current value at path end in overriding node.
|
|
1688
|
+
cur := make([]any, 33)
|
|
1689
|
+
cur[0] = out
|
|
1690
|
+
|
|
1691
|
+
// Current value at path end in destination node.
|
|
1692
|
+
dst := make([]any, 33)
|
|
1693
|
+
dst[0] = out
|
|
1694
|
+
|
|
1695
|
+
before := func(
|
|
1696
|
+
key *string,
|
|
1697
|
+
val any,
|
|
1698
|
+
_parent any,
|
|
1699
|
+
path []string,
|
|
1700
|
+
) any {
|
|
1701
|
+
pI := len(path)
|
|
1702
|
+
|
|
1703
|
+
if md <= pI {
|
|
1704
|
+
if key != nil {
|
|
1705
|
+
SetProp(cur[pI-1], *key, val)
|
|
1706
|
+
}
|
|
1707
|
+
} else if !IsNode(val) {
|
|
1708
|
+
// Scalars just override directly.
|
|
1709
|
+
cur[pI] = val
|
|
1710
|
+
} else {
|
|
1711
|
+
// Descend into override node.
|
|
1712
|
+
if 0 < pI && key != nil {
|
|
1713
|
+
dst[pI] = GetProp(dst[pI-1], *key)
|
|
1714
|
+
}
|
|
1715
|
+
tval := dst[pI]
|
|
1716
|
+
|
|
1717
|
+
// Destination empty, create node (unless override is class instance).
|
|
1718
|
+
if nil == tval && 0 == (T_instance&Typify(val)) {
|
|
1719
|
+
if IsList(val) {
|
|
1720
|
+
cur[pI] = make([]any, 0)
|
|
1721
|
+
} else {
|
|
1722
|
+
cur[pI] = make(map[string]any)
|
|
1723
|
+
}
|
|
1724
|
+
} else if Typify(val) == Typify(tval) {
|
|
1725
|
+
// Matching override and destination, continue with their values.
|
|
1726
|
+
cur[pI] = tval
|
|
1727
|
+
} else {
|
|
1728
|
+
// Override wins.
|
|
1729
|
+
cur[pI] = val
|
|
1730
|
+
// No need to descend (destination is discarded).
|
|
1731
|
+
val = nil
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
return val
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
after := func(
|
|
1739
|
+
key *string,
|
|
1740
|
+
_val any,
|
|
1741
|
+
_parent any,
|
|
1742
|
+
path []string,
|
|
1743
|
+
) any {
|
|
1744
|
+
cI := len(path)
|
|
1745
|
+
|
|
1746
|
+
// Root node: nothing to set on parent.
|
|
1747
|
+
if nil == key || cI <= 0 {
|
|
1748
|
+
return cur[0]
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
value := cur[cI]
|
|
1752
|
+
|
|
1753
|
+
cur[cI-1] = SetProp(cur[cI-1], *key, value)
|
|
1754
|
+
return value
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
// Walk overriding node, creating paths in output as needed.
|
|
1758
|
+
Walk(obj, before, after, md)
|
|
1759
|
+
|
|
1760
|
+
out = cur[0]
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
if 0 == md {
|
|
1765
|
+
out = GetElem(list, -1)
|
|
1766
|
+
if IsList(out) {
|
|
1767
|
+
out = make([]any, 0)
|
|
1768
|
+
} else if IsMap(out) {
|
|
1769
|
+
out = make(map[string]any)
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
return out
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
// Get a value deep inside a node using a key path. For example the
|
|
1777
|
+
// path `a.b` gets the value 1 from {a:{b:1}}. The path can specified
|
|
1778
|
+
// as a dotted string, or a string array. If the path starts with a
|
|
1779
|
+
// dot (or the first element is "), the path is considered local, and
|
|
1780
|
+
// resolved against the `current` argument, if defined. Integer path
|
|
1781
|
+
// parts are used as array indexes. The inj argument allows for
|
|
1782
|
+
// custom handling when called from `inject` or `transform`.
|
|
1783
|
+
func GetPath(path any, store any, injdefs ...*Injection) any {
|
|
1784
|
+
var inj *Injection
|
|
1785
|
+
if len(injdefs) > 0 {
|
|
1786
|
+
inj = injdefs[0]
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
var parts []string
|
|
1790
|
+
|
|
1791
|
+
// Operate on a string array.
|
|
1792
|
+
switch pp := path.(type) {
|
|
1793
|
+
case []string:
|
|
1794
|
+
parts = make([]string, len(pp))
|
|
1795
|
+
copy(parts, pp)
|
|
1796
|
+
|
|
1797
|
+
case string:
|
|
1798
|
+
if pp == "" {
|
|
1799
|
+
parts = []string{S_MT}
|
|
1800
|
+
} else {
|
|
1801
|
+
parts = strings.Split(pp, S_DT)
|
|
1802
|
+
}
|
|
1803
|
+
default:
|
|
1804
|
+
if IsList(path) {
|
|
1805
|
+
parts = _resolveStrings(_listify(path))
|
|
1806
|
+
} else {
|
|
1807
|
+
return nil
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
val := store
|
|
1812
|
+
var base any = nil
|
|
1813
|
+
if nil != inj {
|
|
1814
|
+
base = inj.Base
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
src := GetProp(store, base, store)
|
|
1818
|
+
var dparent any
|
|
1819
|
+
if inj != nil {
|
|
1820
|
+
dparent = inj.Dparent
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
numparts := len(parts)
|
|
1824
|
+
|
|
1825
|
+
// An empty path (incl empty string) just finds the store.
|
|
1826
|
+
if nil == path || nil == store || (1 == numparts && S_MT == parts[0]) {
|
|
1827
|
+
val = src
|
|
1828
|
+
|
|
1829
|
+
} else if 0 < numparts {
|
|
1830
|
+
|
|
1831
|
+
// Check for $ACTIONs
|
|
1832
|
+
if 1 == numparts {
|
|
1833
|
+
val = GetProp(store, parts[0])
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
if !IsFunc(val) {
|
|
1837
|
+
val = src
|
|
1838
|
+
|
|
1839
|
+
// Meta path syntax: "q0$=x1" or "q0$~x1"
|
|
1840
|
+
m := reMetaPath.FindStringSubmatch(parts[0])
|
|
1841
|
+
if m != nil && inj != nil && inj.Meta != nil {
|
|
1842
|
+
val = GetProp(inj.Meta, m[1])
|
|
1843
|
+
parts[0] = m[3]
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
var dpath []string
|
|
1847
|
+
if inj != nil {
|
|
1848
|
+
dpath = inj.Dpath
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
for pI := 0; val != nil && pI < numparts; pI++ {
|
|
1852
|
+
part := parts[pI]
|
|
1853
|
+
|
|
1854
|
+
if inj != nil && part == "$KEY" {
|
|
1855
|
+
part = inj.Key
|
|
1856
|
+
} else if inj != nil && strings.HasPrefix(part, "$GET:") {
|
|
1857
|
+
// $GET:path$ -> get store value, use as path part
|
|
1858
|
+
subpath := part[5 : len(part)-1]
|
|
1859
|
+
result := GetPath(subpath, src)
|
|
1860
|
+
part = Stringify(result)
|
|
1861
|
+
} else if inj != nil && strings.HasPrefix(part, "$REF:") {
|
|
1862
|
+
// $REF:refpath$ -> get spec value, use as path part
|
|
1863
|
+
subpath := part[5 : len(part)-1]
|
|
1864
|
+
specVal := GetProp(store, S_DSPEC)
|
|
1865
|
+
if specVal != nil {
|
|
1866
|
+
result := GetPath(subpath, specVal)
|
|
1867
|
+
part = Stringify(result)
|
|
1868
|
+
}
|
|
1869
|
+
} else if inj != nil && strings.HasPrefix(part, "$META:") {
|
|
1870
|
+
// $META:metapath$ -> get meta value, use as path part
|
|
1871
|
+
subpath := part[6 : len(part)-1]
|
|
1872
|
+
result := GetPath(subpath, inj.Meta)
|
|
1873
|
+
part = Stringify(result)
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
// $$ escapes $
|
|
1877
|
+
part = strings.ReplaceAll(part, "$$", "$")
|
|
1878
|
+
|
|
1879
|
+
if S_MT == part {
|
|
1880
|
+
ascends := 0
|
|
1881
|
+
for 1+pI < numparts && S_MT == parts[1+pI] {
|
|
1882
|
+
ascends++
|
|
1883
|
+
pI++
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
if inj != nil && 0 < ascends {
|
|
1887
|
+
if pI == numparts-1 {
|
|
1888
|
+
ascends--
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
if 0 == ascends {
|
|
1892
|
+
val = dparent
|
|
1893
|
+
} else {
|
|
1894
|
+
// Build fullpath from dpath + remaining parts
|
|
1895
|
+
cutLen := len(dpath) - ascends
|
|
1896
|
+
if cutLen < 0 {
|
|
1897
|
+
cutLen = 0
|
|
1898
|
+
}
|
|
1899
|
+
fullpath := make([]string, 0)
|
|
1900
|
+
fullpath = append(fullpath, dpath[:cutLen]...)
|
|
1901
|
+
if pI+1 < numparts {
|
|
1902
|
+
fullpath = append(fullpath, parts[pI+1:]...)
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
if ascends <= len(dpath) {
|
|
1906
|
+
val = GetPath(fullpath, store)
|
|
1907
|
+
} else {
|
|
1908
|
+
val = nil
|
|
1909
|
+
}
|
|
1910
|
+
break
|
|
1911
|
+
}
|
|
1912
|
+
} else {
|
|
1913
|
+
val = dparent
|
|
1914
|
+
}
|
|
1915
|
+
} else {
|
|
1916
|
+
val = GetProp(val, part)
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
if nil != inj && inj.Handler != nil {
|
|
1923
|
+
ref := Pathify(path)
|
|
1924
|
+
val = inj.Handler(inj, val, &ref, store)
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
return val
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
// Set a value at a path inside a store. Missing intermediate path
|
|
1931
|
+
// parts are created (maps for string keys, lists for numeric keys).
|
|
1932
|
+
// String paths are split on ".". If val is the DELETE sentinel,
|
|
1933
|
+
// the final key is deleted instead of set.
|
|
1934
|
+
func SetPath(store any, path any, val any, injdefs ...map[string]any) any {
|
|
1935
|
+
pathType := Typify(path)
|
|
1936
|
+
|
|
1937
|
+
var parts []any
|
|
1938
|
+
if 0 < (T_list & pathType) {
|
|
1939
|
+
parts = _listify(path)
|
|
1940
|
+
} else if 0 < (T_string & pathType) {
|
|
1941
|
+
splitParts := strings.Split(path.(string), S_DT)
|
|
1942
|
+
parts = make([]any, len(splitParts))
|
|
1943
|
+
for i, s := range splitParts {
|
|
1944
|
+
parts[i] = s
|
|
1945
|
+
}
|
|
1946
|
+
} else if 0 < (T_number & pathType) {
|
|
1947
|
+
parts = []any{path}
|
|
1948
|
+
} else {
|
|
1949
|
+
return nil
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
var base any
|
|
1953
|
+
if len(injdefs) > 0 && injdefs[0] != nil {
|
|
1954
|
+
base = GetProp(injdefs[0], S_base)
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
numparts := len(parts)
|
|
1958
|
+
parent := GetProp(store, base, store)
|
|
1959
|
+
|
|
1960
|
+
var grandparent any
|
|
1961
|
+
var grandKey any
|
|
1962
|
+
|
|
1963
|
+
for pI := 0; pI < numparts-1; pI++ {
|
|
1964
|
+
partKey := GetElem(parts, pI)
|
|
1965
|
+
nextParent := GetProp(parent, partKey)
|
|
1966
|
+
if !IsNode(nextParent) {
|
|
1967
|
+
nextPartKey := GetElem(parts, pI+1)
|
|
1968
|
+
if 0 < (T_number & Typify(nextPartKey)) {
|
|
1969
|
+
nextParent = []any{}
|
|
1970
|
+
} else {
|
|
1971
|
+
nextParent = map[string]any{}
|
|
1972
|
+
}
|
|
1973
|
+
SetProp(parent, partKey, nextParent)
|
|
1974
|
+
}
|
|
1975
|
+
grandparent = parent
|
|
1976
|
+
grandKey = partKey
|
|
1977
|
+
parent = nextParent
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
lastKey := GetElem(parts, -1)
|
|
1981
|
+
if val == DELETE {
|
|
1982
|
+
newParent := DelProp(parent, lastKey)
|
|
1983
|
+
if grandparent != nil && IsList(parent) {
|
|
1984
|
+
SetProp(grandparent, grandKey, newParent)
|
|
1985
|
+
}
|
|
1986
|
+
return newParent
|
|
1987
|
+
} else {
|
|
1988
|
+
newParent := SetProp(parent, lastKey, val)
|
|
1989
|
+
if grandparent != nil && IsList(parent) {
|
|
1990
|
+
SetProp(grandparent, grandKey, newParent)
|
|
1991
|
+
}
|
|
1992
|
+
return newParent
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
// Inject store values into a string. Not a public utility - used by
|
|
1997
|
+
// `inject`. Inject are marked with `path` where path is resolved
|
|
1998
|
+
// with getpath against the store or current (if defined)
|
|
1999
|
+
// arguments. See `getpath`. Custom injection handling can be
|
|
2000
|
+
// provided by inj.handler (this is used for transform functions).
|
|
2001
|
+
// The path can also have the special syntax $NAME999 where NAME is
|
|
2002
|
+
// upper case letters only, and 999 is any digits, which are
|
|
2003
|
+
// discarded. This syntax specifies the name of a transform, and
|
|
2004
|
+
// optionally allows transforms to be ordered by alphanumeric sorting.
|
|
2005
|
+
func _injectStr(
|
|
2006
|
+
val string,
|
|
2007
|
+
store any,
|
|
2008
|
+
inj *Injection,
|
|
2009
|
+
) any {
|
|
2010
|
+
if val == S_MT {
|
|
2011
|
+
return S_MT
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
// Pattern examples: "`a.b.c`", "`$NAME`", "`$NAME1`"
|
|
2015
|
+
// fullRe := regexp.MustCompile("^`([^`]+)[0-9]*`$")
|
|
2016
|
+
fullRe := regexp.MustCompile("^`(\\$[A-Z]+|[^`]*)[0-9]*`$")
|
|
2017
|
+
matches := fullRe.FindStringSubmatch(val)
|
|
2018
|
+
|
|
2019
|
+
// Full string of the val is an injection.
|
|
2020
|
+
if matches != nil {
|
|
2021
|
+
if nil != inj {
|
|
2022
|
+
inj.Full = true
|
|
2023
|
+
}
|
|
2024
|
+
pathref := matches[1]
|
|
2025
|
+
|
|
2026
|
+
// Special escapes inside injection.
|
|
2027
|
+
if len(pathref) > 3 {
|
|
2028
|
+
pathref = strings.ReplaceAll(pathref, "$BT", S_BT)
|
|
2029
|
+
pathref = strings.ReplaceAll(pathref, "$DS", S_DS)
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
// Get the extracted path reference.
|
|
2033
|
+
out := GetPath(pathref, store, inj)
|
|
2034
|
+
|
|
2035
|
+
return out
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
// Check for injections within the string.
|
|
2039
|
+
partialRe := regexp.MustCompile("`([^`]+)`")
|
|
2040
|
+
out := partialRe.ReplaceAllStringFunc(val, func(m string) string {
|
|
2041
|
+
ref := strings.Trim(m, "`")
|
|
2042
|
+
|
|
2043
|
+
// Special escapes inside injection.
|
|
2044
|
+
if 3 < len(ref) {
|
|
2045
|
+
ref = strings.ReplaceAll(ref, "$BT", S_BT)
|
|
2046
|
+
ref = strings.ReplaceAll(ref, "$DS", S_DS)
|
|
2047
|
+
}
|
|
2048
|
+
if nil != inj {
|
|
2049
|
+
inj.Full = false
|
|
2050
|
+
}
|
|
2051
|
+
found := GetPath(ref, store, inj)
|
|
2052
|
+
|
|
2053
|
+
if nil == found {
|
|
2054
|
+
return S_MT
|
|
2055
|
+
}
|
|
2056
|
+
switch fv := found.(type) {
|
|
2057
|
+
case map[string]any, []any:
|
|
2058
|
+
b, _ := json.Marshal(fv)
|
|
2059
|
+
return string(b)
|
|
2060
|
+
case *ListRef[any]:
|
|
2061
|
+
b, _ := json.Marshal(fv.List)
|
|
2062
|
+
return string(b)
|
|
2063
|
+
default:
|
|
2064
|
+
return _stringifyValue(found)
|
|
2065
|
+
}
|
|
2066
|
+
})
|
|
2067
|
+
|
|
2068
|
+
// Also call the inj handler on the entire string, providing the
|
|
2069
|
+
// option for custom injection.
|
|
2070
|
+
if nil != inj && IsFunc(inj.Handler) {
|
|
2071
|
+
inj.Full = true
|
|
2072
|
+
result := inj.Handler(inj, out, &val, store)
|
|
2073
|
+
if s, ok := result.(string); ok {
|
|
2074
|
+
out = s
|
|
2075
|
+
} else {
|
|
2076
|
+
out = fmt.Sprint(result)
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
return out
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
// Inject values from a data store into a node recursively, resolving
|
|
2084
|
+
// paths against the store, or current if they are local. The modify
|
|
2085
|
+
// argument allows custom modification of the result. The inj
|
|
2086
|
+
// (Injection) argument is used to maintain recursive inj.
|
|
2087
|
+
func Inject(
|
|
2088
|
+
val any,
|
|
2089
|
+
store any,
|
|
2090
|
+
injdefs ...*Injection,
|
|
2091
|
+
) any {
|
|
2092
|
+
var inj *Injection
|
|
2093
|
+
if len(injdefs) > 0 {
|
|
2094
|
+
inj = injdefs[0]
|
|
2095
|
+
}
|
|
2096
|
+
valType := Typify(val)
|
|
2097
|
+
|
|
2098
|
+
// Create inj if at root of injection. The input value is placed
|
|
2099
|
+
// inside a virtual parent holder to simplify edge cases.
|
|
2100
|
+
if inj == nil || inj.Mode == 0 {
|
|
2101
|
+
parent := map[string]any{
|
|
2102
|
+
S_DTOP: val,
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
newInj := &Injection{
|
|
2106
|
+
Mode: M_VAL,
|
|
2107
|
+
Full: false,
|
|
2108
|
+
KeyI: 0,
|
|
2109
|
+
Keys: &ListRef[string]{List: []string{S_DTOP}},
|
|
2110
|
+
Key: S_DTOP,
|
|
2111
|
+
Val: val,
|
|
2112
|
+
Parent: parent,
|
|
2113
|
+
Path: &ListRef[string]{List: []string{S_DTOP}},
|
|
2114
|
+
Nodes: &ListRef[any]{List: []any{parent}},
|
|
2115
|
+
Handler: injectHandler,
|
|
2116
|
+
Base: S_DTOP,
|
|
2117
|
+
Modify: nil,
|
|
2118
|
+
Errs: GetProp(store, S_DERRS, ListRefCreate[any]()).(*ListRef[any]),
|
|
2119
|
+
Meta: make(map[string]any),
|
|
2120
|
+
Dparent: store,
|
|
2121
|
+
Dpath: []string{S_DTOP},
|
|
2122
|
+
}
|
|
2123
|
+
newInj.Meta["__d"] = 0
|
|
2124
|
+
|
|
2125
|
+
if inj != nil {
|
|
2126
|
+
// Partial init provided (like TS injdef)
|
|
2127
|
+
if inj.Modify != nil {
|
|
2128
|
+
newInj.Modify = inj.Modify
|
|
2129
|
+
}
|
|
2130
|
+
if inj.Extra != nil {
|
|
2131
|
+
newInj.Extra = inj.Extra
|
|
2132
|
+
}
|
|
2133
|
+
if inj.Meta != nil {
|
|
2134
|
+
newInj.Meta = inj.Meta
|
|
2135
|
+
}
|
|
2136
|
+
if inj.Handler != nil {
|
|
2137
|
+
newInj.Handler = inj.Handler
|
|
2138
|
+
}
|
|
2139
|
+
if inj.Errs != nil {
|
|
2140
|
+
newInj.Errs = inj.Errs
|
|
2141
|
+
}
|
|
2142
|
+
if inj.Dparent != nil {
|
|
2143
|
+
newInj.Dparent = inj.Dparent
|
|
2144
|
+
}
|
|
2145
|
+
if inj.Dpath != nil {
|
|
2146
|
+
newInj.Dpath = inj.Dpath
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
inj = newInj
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
inj.descend()
|
|
2154
|
+
|
|
2155
|
+
// Descend into node
|
|
2156
|
+
if IsNode(val) {
|
|
2157
|
+
childkeys := KeysOf(val)
|
|
2158
|
+
|
|
2159
|
+
// Keys are sorted alphanumerically to ensure determinism.
|
|
2160
|
+
// Injection transforms ($FOO) are processed *after* other keys.
|
|
2161
|
+
var normalKeys []string
|
|
2162
|
+
var transformKeys []string
|
|
2163
|
+
for _, k := range childkeys {
|
|
2164
|
+
if strings.Contains(k, S_DS) {
|
|
2165
|
+
transformKeys = append(transformKeys, k)
|
|
2166
|
+
} else {
|
|
2167
|
+
normalKeys = append(normalKeys, k)
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
sort.Strings(normalKeys)
|
|
2172
|
+
sort.Strings(transformKeys)
|
|
2173
|
+
nodekeys := append(normalKeys, transformKeys...)
|
|
2174
|
+
|
|
2175
|
+
nkI := 0
|
|
2176
|
+
for nkI < len(nodekeys) {
|
|
2177
|
+
nodekey := nodekeys[nkI]
|
|
2178
|
+
|
|
2179
|
+
childinj := inj.child(nkI, nodekeys)
|
|
2180
|
+
childinj.Mode = M_KEYPRE
|
|
2181
|
+
|
|
2182
|
+
// Perform the key:pre mode injection on the child key.
|
|
2183
|
+
preKey := _injectStr(nodekey, store, childinj)
|
|
2184
|
+
|
|
2185
|
+
// The injection may modify child processing.
|
|
2186
|
+
nkI = childinj.KeyI
|
|
2187
|
+
nodekeys = childinj.Keys.List
|
|
2188
|
+
val = childinj.Parent
|
|
2189
|
+
|
|
2190
|
+
if preKey != nil {
|
|
2191
|
+
childval := GetProp(val, preKey)
|
|
2192
|
+
childinj.Val = childval
|
|
2193
|
+
childinj.Mode = M_VAL
|
|
2194
|
+
|
|
2195
|
+
// Perform the val mode injection on the child value.
|
|
2196
|
+
Inject(childval, store, childinj)
|
|
2197
|
+
|
|
2198
|
+
// The injection may modify child processing.
|
|
2199
|
+
nkI = childinj.KeyI
|
|
2200
|
+
nodekeys = childinj.Keys.List
|
|
2201
|
+
val = childinj.Parent
|
|
2202
|
+
|
|
2203
|
+
// Perform the key:post mode injection on the child key.
|
|
2204
|
+
childinj.Mode = M_KEYPOST
|
|
2205
|
+
_injectStr(nodekey, store, childinj)
|
|
2206
|
+
|
|
2207
|
+
// The injection may modify child processing.
|
|
2208
|
+
nkI = childinj.KeyI
|
|
2209
|
+
nodekeys = childinj.Keys.List
|
|
2210
|
+
val = childinj.Parent
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
nkI = nkI + 1
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
} else if 0 < (T_string & valType) {
|
|
2217
|
+
|
|
2218
|
+
// Inject paths into string scalars.
|
|
2219
|
+
inj.Mode = M_VAL
|
|
2220
|
+
strVal, ok := val.(string)
|
|
2221
|
+
if ok {
|
|
2222
|
+
val = _injectStr(strVal, store, inj)
|
|
2223
|
+
if val != SKIP {
|
|
2224
|
+
inj.setval(val)
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
// Custom modification
|
|
2230
|
+
if nil != inj.Modify && val != SKIP {
|
|
2231
|
+
mkey := inj.Key
|
|
2232
|
+
mparent := inj.Parent
|
|
2233
|
+
mval := GetProp(mparent, mkey)
|
|
2234
|
+
inj.Modify(
|
|
2235
|
+
mval,
|
|
2236
|
+
mkey,
|
|
2237
|
+
mparent,
|
|
2238
|
+
inj,
|
|
2239
|
+
store,
|
|
2240
|
+
)
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
inj.Val = val
|
|
2244
|
+
|
|
2245
|
+
// Original val reference may no longer be correct.
|
|
2246
|
+
// This return value is only used as the top level result.
|
|
2247
|
+
rval := GetProp(inj.Parent, S_DTOP)
|
|
2248
|
+
|
|
2249
|
+
return rval
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
// Default inject handler for transforms. If the path resolves to a function,
|
|
2253
|
+
// call the function passing the injection inj. This is how transforms operate.
|
|
2254
|
+
var injectHandler Injector = func(
|
|
2255
|
+
inj *Injection,
|
|
2256
|
+
val any,
|
|
2257
|
+
ref *string,
|
|
2258
|
+
store any,
|
|
2259
|
+
) any {
|
|
2260
|
+
out := val
|
|
2261
|
+
iscmd := IsFunc(val) && (nil == ref || strings.HasPrefix(*ref, S_DS))
|
|
2262
|
+
|
|
2263
|
+
if iscmd {
|
|
2264
|
+
fnih, ok := val.(Injector)
|
|
2265
|
+
|
|
2266
|
+
if ok {
|
|
2267
|
+
out = fnih(inj, val, ref, store)
|
|
2268
|
+
} else {
|
|
2269
|
+
// In Go, as a convenience, allow injection functions that have no arguments.
|
|
2270
|
+
fn0, ok := val.(func() any)
|
|
2271
|
+
if ok {
|
|
2272
|
+
out = fn0()
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
} else if M_VAL == inj.Mode && inj.Full {
|
|
2276
|
+
// Update parent with value. Ensures references remain in node tree.
|
|
2277
|
+
inj.setval(val)
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
return out
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
// The transform_* functions are special command inject handlers (see Injector).
|
|
2284
|
+
|
|
2285
|
+
// Delete a key from a map or list.
|
|
2286
|
+
var Transform_DELETE Injector = func(
|
|
2287
|
+
inj *Injection,
|
|
2288
|
+
val any,
|
|
2289
|
+
ref *string,
|
|
2290
|
+
store any,
|
|
2291
|
+
) any {
|
|
2292
|
+
inj.setval(nil)
|
|
2293
|
+
return nil
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
// Copy value from source data.
|
|
2297
|
+
var Transform_COPY Injector = func(
|
|
2298
|
+
inj *Injection,
|
|
2299
|
+
val any,
|
|
2300
|
+
ref *string,
|
|
2301
|
+
store any,
|
|
2302
|
+
) any {
|
|
2303
|
+
if !CheckPlacement(M_VAL, "COPY", T_any, inj) {
|
|
2304
|
+
return nil
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
out := GetProp(inj.Dparent, inj.Key)
|
|
2308
|
+
inj.setval(out)
|
|
2309
|
+
|
|
2310
|
+
return out
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
// As a value, inject the key of the parent node.
|
|
2314
|
+
// As a key, defined the name of the key property in the source object.
|
|
2315
|
+
var Transform_KEY Injector = func(
|
|
2316
|
+
inj *Injection,
|
|
2317
|
+
val any,
|
|
2318
|
+
ref *string,
|
|
2319
|
+
store any,
|
|
2320
|
+
) any {
|
|
2321
|
+
if inj.Mode != M_VAL {
|
|
2322
|
+
return nil
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
// Key is defined by $KEY meta property.
|
|
2326
|
+
keyspec := GetProp(inj.Parent, S_BKEY)
|
|
2327
|
+
if keyspec != nil {
|
|
2328
|
+
DelProp(inj.Parent, S_BKEY)
|
|
2329
|
+
return GetProp(inj.Dparent, keyspec)
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
// Key is defined within general purpose $ANNO object.
|
|
2333
|
+
anno := GetProp(inj.Parent, S_BANNO)
|
|
2334
|
+
pkey := GetProp(anno, S_KEY)
|
|
2335
|
+
if pkey != nil {
|
|
2336
|
+
return pkey
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
// fallback to the second-last path element
|
|
2340
|
+
if len(inj.Path.List) >= 2 {
|
|
2341
|
+
return inj.Path.List[len(inj.Path.List)-2]
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
return nil
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
// Store meta data about a node. Does nothing itself, just used by
|
|
2348
|
+
// other injectors, and is removed when called.
|
|
2349
|
+
var Transform_META Injector = func(
|
|
2350
|
+
inj *Injection,
|
|
2351
|
+
val any,
|
|
2352
|
+
ref *string,
|
|
2353
|
+
store any,
|
|
2354
|
+
) any {
|
|
2355
|
+
DelProp(inj.Parent, S_DMETA)
|
|
2356
|
+
return nil
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
// Annotate node. Does nothing itself, just used by other injectors, and is removed when called.
|
|
2360
|
+
var Transform_ANNO Injector = func(
|
|
2361
|
+
inj *Injection,
|
|
2362
|
+
val any,
|
|
2363
|
+
ref *string,
|
|
2364
|
+
store any,
|
|
2365
|
+
) any {
|
|
2366
|
+
DelProp(inj.Parent, S_BANNO)
|
|
2367
|
+
return nil
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
// Merge a list of objects into the current object.
|
|
2371
|
+
// Must be a key in an object. The value is merged over the current object.
|
|
2372
|
+
// If the value is an array, the elements are first merged using `merge`.
|
|
2373
|
+
// If the value is the empty string, merge the top level store.
|
|
2374
|
+
// Format: { '`$MERGE`': '`source-path`' | ['`source-paths`', ...] }
|
|
2375
|
+
var Transform_MERGE Injector = func(
|
|
2376
|
+
inj *Injection,
|
|
2377
|
+
val any,
|
|
2378
|
+
ref *string,
|
|
2379
|
+
store any,
|
|
2380
|
+
) any {
|
|
2381
|
+
if M_KEYPRE == inj.Mode {
|
|
2382
|
+
return inj.Key
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
if M_KEYPOST == inj.Mode {
|
|
2386
|
+
args := GetProp(inj.Parent, inj.Key)
|
|
2387
|
+
if S_MT == args {
|
|
2388
|
+
args = []any{GetProp(store, S_DTOP)}
|
|
2389
|
+
} else if IsList(args) {
|
|
2390
|
+
// do nothing
|
|
2391
|
+
} else {
|
|
2392
|
+
// wrap in array
|
|
2393
|
+
args = []any{args}
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
// Remove the $MERGE command from a parent map.
|
|
2397
|
+
DelProp(inj.Parent, inj.Key)
|
|
2398
|
+
|
|
2399
|
+
list, ok := _asList(args)
|
|
2400
|
+
if !ok {
|
|
2401
|
+
return inj.Key
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
// Literals in the parent have precedence, but we still merge onto
|
|
2405
|
+
// the parent object, so that node tree references are not changed.
|
|
2406
|
+
mergeList := []any{inj.Parent}
|
|
2407
|
+
mergeList = append(mergeList, list...)
|
|
2408
|
+
mergeList = append(mergeList, Clone(inj.Parent))
|
|
2409
|
+
|
|
2410
|
+
Merge(mergeList)
|
|
2411
|
+
|
|
2412
|
+
return inj.Key
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
// Ensures $MERGE is removed from parent list.
|
|
2416
|
+
return nil
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
|
|
2420
|
+
// Convert a node to a list.
|
|
2421
|
+
// Format: ['`$EACH`', '`source-path-of-node`', child-template]
|
|
2422
|
+
var Transform_EACH Injector = func(
|
|
2423
|
+
inj *Injection,
|
|
2424
|
+
val any,
|
|
2425
|
+
ref *string,
|
|
2426
|
+
store any,
|
|
2427
|
+
) any {
|
|
2428
|
+
ijname := "EACH"
|
|
2429
|
+
|
|
2430
|
+
if !CheckPlacement(M_VAL, ijname, T_list, inj) {
|
|
2431
|
+
return nil
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
// Remove remaining keys to avoid spurious processing.
|
|
2435
|
+
if inj.Keys != nil && len(inj.Keys.List) > 0 {
|
|
2436
|
+
inj.Keys.List = inj.Keys.List[:1]
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
// Get arguments: ['`$EACH`', 'source-path', child-template]
|
|
2440
|
+
parentList := _listify(inj.Parent)
|
|
2441
|
+
var sliced []any
|
|
2442
|
+
if len(parentList) > 1 {
|
|
2443
|
+
sliced = parentList[1:]
|
|
2444
|
+
}
|
|
2445
|
+
args := InjectorArgs([]int{T_string, T_any}, sliced)
|
|
2446
|
+
if args[0] != nil {
|
|
2447
|
+
inj.Errs.Append("$" + ijname + ": " + args[0].(string))
|
|
2448
|
+
return nil
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
srcpath := args[1].(string)
|
|
2452
|
+
child := args[2]
|
|
2453
|
+
|
|
2454
|
+
// Source data.
|
|
2455
|
+
srcstore := GetProp(store, inj.Base, store)
|
|
2456
|
+
src := GetPath(srcpath, srcstore, inj)
|
|
2457
|
+
srctype := Typify(src)
|
|
2458
|
+
|
|
2459
|
+
// Create parallel data structures
|
|
2460
|
+
var tcur any
|
|
2461
|
+
var tval any
|
|
2462
|
+
|
|
2463
|
+
tkey := ""
|
|
2464
|
+
if len(inj.Path.List) >= 2 {
|
|
2465
|
+
tkey = inj.Path.List[len(inj.Path.List)-2]
|
|
2466
|
+
}
|
|
2467
|
+
var target any
|
|
2468
|
+
if len(inj.Nodes.List) >= 2 {
|
|
2469
|
+
target = inj.Nodes.List[len(inj.Nodes.List)-2]
|
|
2470
|
+
}
|
|
2471
|
+
if target == nil && len(inj.Nodes.List) > 0 {
|
|
2472
|
+
target = inj.Nodes.List[len(inj.Nodes.List)-1]
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
// Create clones of the child template for each value of the current source.
|
|
2476
|
+
if 0 < (T_list & srctype) {
|
|
2477
|
+
srcList := _listify(src)
|
|
2478
|
+
newlist := make([]any, len(srcList))
|
|
2479
|
+
for i := range srcList {
|
|
2480
|
+
newlist[i] = Clone(child)
|
|
2481
|
+
}
|
|
2482
|
+
tval = newlist
|
|
2483
|
+
} else if 0 < (T_map & srctype) {
|
|
2484
|
+
srcItems := Items(src)
|
|
2485
|
+
newlist := make([]any, len(srcItems))
|
|
2486
|
+
for i, item := range srcItems {
|
|
2487
|
+
cclone := Clone(child)
|
|
2488
|
+
cclone = Merge([]any{
|
|
2489
|
+
cclone,
|
|
2490
|
+
map[string]any{S_BANNO: map[string]any{S_KEY: item[0]}},
|
|
2491
|
+
})
|
|
2492
|
+
newlist[i] = cclone
|
|
2493
|
+
}
|
|
2494
|
+
tval = newlist
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
rval := []any{}
|
|
2498
|
+
|
|
2499
|
+
if tval != nil && len(_listify(tval)) > 0 {
|
|
2500
|
+
if src != nil {
|
|
2501
|
+
srcVals := make([]any, 0)
|
|
2502
|
+
if IsMap(src) {
|
|
2503
|
+
for _, item := range Items(src) {
|
|
2504
|
+
srcVals = append(srcVals, item[1])
|
|
2505
|
+
}
|
|
2506
|
+
} else {
|
|
2507
|
+
srcVals = _listify(src)
|
|
2508
|
+
}
|
|
2509
|
+
tcur = srcVals
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
ckey := ""
|
|
2513
|
+
if len(inj.Path.List) >= 2 {
|
|
2514
|
+
ckey = inj.Path.List[len(inj.Path.List)-2]
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
tpath := make([]string, len(inj.Path.List)-1)
|
|
2518
|
+
copy(tpath, inj.Path.List[:len(inj.Path.List)-1])
|
|
2519
|
+
|
|
2520
|
+
dpath := []string{S_DTOP}
|
|
2521
|
+
for _, p := range strings.Split(srcpath, S_DT) {
|
|
2522
|
+
dpath = append(dpath, p)
|
|
2523
|
+
}
|
|
2524
|
+
dpath = append(dpath, "$:"+ckey)
|
|
2525
|
+
|
|
2526
|
+
// Parent structure.
|
|
2527
|
+
tcur = map[string]any{ckey: tcur}
|
|
2528
|
+
|
|
2529
|
+
if len(tpath) > 1 {
|
|
2530
|
+
pkey := S_DTOP
|
|
2531
|
+
if len(inj.Path.List) >= 3 {
|
|
2532
|
+
pkey = inj.Path.List[len(inj.Path.List)-3]
|
|
2533
|
+
}
|
|
2534
|
+
tcur = map[string]any{pkey: tcur}
|
|
2535
|
+
dpath = append(dpath, "$:"+pkey)
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
tinj := inj.child(0, []string{ckey})
|
|
2539
|
+
tinj.Path = &ListRef[string]{List: tpath}
|
|
2540
|
+
|
|
2541
|
+
tnodeslist := make([]any, 1)
|
|
2542
|
+
copy(tnodeslist, inj.Nodes.List[len(inj.Nodes.List)-1:])
|
|
2543
|
+
tinj.Nodes = &ListRef[any]{List: tnodeslist}
|
|
2544
|
+
|
|
2545
|
+
tinj.Parent = tinj.Nodes.List[len(tinj.Nodes.List)-1]
|
|
2546
|
+
SetProp(tinj.Parent, ckey, tval)
|
|
2547
|
+
|
|
2548
|
+
tinj.Val = tval
|
|
2549
|
+
tinj.Dpath = dpath
|
|
2550
|
+
tinj.Dparent = tcur
|
|
2551
|
+
|
|
2552
|
+
Inject(tval, store, tinj)
|
|
2553
|
+
rval = _listify(tinj.Val)
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
SetProp(target, tkey, rval)
|
|
2557
|
+
|
|
2558
|
+
// Prevent callee from damaging first list entry (since we are in val mode).
|
|
2559
|
+
if len(rval) > 0 {
|
|
2560
|
+
return rval[0]
|
|
2561
|
+
}
|
|
2562
|
+
return nil
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
|
|
2566
|
+
// transform_PACK => `$PACK`
|
|
2567
|
+
var Transform_PACK Injector = func(
|
|
2568
|
+
inj *Injection,
|
|
2569
|
+
val any,
|
|
2570
|
+
ref *string,
|
|
2571
|
+
store any,
|
|
2572
|
+
) any {
|
|
2573
|
+
ijname := "EACH" // TS uses EACH for checkPlacement name
|
|
2574
|
+
|
|
2575
|
+
if !CheckPlacement(M_KEYPRE, ijname, T_map, inj) {
|
|
2576
|
+
return nil
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
// Get arguments.
|
|
2580
|
+
args := GetProp(inj.Parent, inj.Key)
|
|
2581
|
+
argsList := _listify(args)
|
|
2582
|
+
injArgs := InjectorArgs([]int{T_string, T_any}, argsList)
|
|
2583
|
+
if injArgs[0] != nil {
|
|
2584
|
+
inj.Errs.Append("$" + ijname + ": " + injArgs[0].(string))
|
|
2585
|
+
return nil
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
srcpath := injArgs[1].(string)
|
|
2589
|
+
origchildspec := injArgs[2]
|
|
2590
|
+
|
|
2591
|
+
// Find key and target node.
|
|
2592
|
+
tkey := ""
|
|
2593
|
+
if len(inj.Path.List) >= 2 {
|
|
2594
|
+
tkey = inj.Path.List[len(inj.Path.List)-2]
|
|
2595
|
+
}
|
|
2596
|
+
pathsize := len(inj.Path.List)
|
|
2597
|
+
var target any
|
|
2598
|
+
if pathsize >= 2 {
|
|
2599
|
+
target = inj.Nodes.List[pathsize-2]
|
|
2600
|
+
}
|
|
2601
|
+
if target == nil {
|
|
2602
|
+
target = inj.Nodes.List[pathsize-1]
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
// Source data
|
|
2606
|
+
srcstore := GetProp(store, inj.Base, store)
|
|
2607
|
+
src := GetPath(srcpath, srcstore, inj)
|
|
2608
|
+
|
|
2609
|
+
// Prepare source as a list.
|
|
2610
|
+
if !IsList(src) {
|
|
2611
|
+
if IsMap(src) {
|
|
2612
|
+
srcItems := Items(src)
|
|
2613
|
+
srcList := make([]any, len(srcItems))
|
|
2614
|
+
for i, item := range srcItems {
|
|
2615
|
+
node := item[1]
|
|
2616
|
+
if IsMap(node) {
|
|
2617
|
+
SetProp(node, S_BANNO, map[string]any{S_KEY: item[0]})
|
|
2618
|
+
}
|
|
2619
|
+
srcList[i] = node
|
|
2620
|
+
}
|
|
2621
|
+
src = srcList
|
|
2622
|
+
} else {
|
|
2623
|
+
return nil
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
if src == nil {
|
|
2628
|
+
return nil
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
// Get keypath.
|
|
2632
|
+
keypath := GetProp(origchildspec, S_BKEY)
|
|
2633
|
+
childspec := DelProp(origchildspec, S_BKEY)
|
|
2634
|
+
|
|
2635
|
+
child := GetProp(childspec, S_BVAL, childspec)
|
|
2636
|
+
|
|
2637
|
+
// Build parallel target object.
|
|
2638
|
+
tval := map[string]any{}
|
|
2639
|
+
srclist := _listify(src)
|
|
2640
|
+
|
|
2641
|
+
// Helper to resolve key for a src item at given index
|
|
2642
|
+
resolveKey := func(srcItem any, idx int) string {
|
|
2643
|
+
if keypath == nil {
|
|
2644
|
+
return strconv.Itoa(idx)
|
|
2645
|
+
}
|
|
2646
|
+
keypathStr, isStr := keypath.(string)
|
|
2647
|
+
if !isStr {
|
|
2648
|
+
return ""
|
|
2649
|
+
}
|
|
2650
|
+
if strings.HasPrefix(keypathStr, "`") {
|
|
2651
|
+
keyStore := Merge([]any{map[string]any{}, store, map[string]any{S_DTOP: srcItem}})
|
|
2652
|
+
keyResult := Inject(keypathStr, keyStore)
|
|
2653
|
+
if ks, ok := keyResult.(string); ok {
|
|
2654
|
+
return ks
|
|
2655
|
+
}
|
|
2656
|
+
} else {
|
|
2657
|
+
kval := GetPath(keypathStr, srcItem, inj)
|
|
2658
|
+
if ks, ok := kval.(string); ok {
|
|
2659
|
+
return ks
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
return ""
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
for i, srcItem := range srclist {
|
|
2666
|
+
srcnode := srcItem
|
|
2667
|
+
key := resolveKey(srcnode, i)
|
|
2668
|
+
|
|
2669
|
+
if key == "" {
|
|
2670
|
+
continue
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
tchild := Clone(child)
|
|
2674
|
+
tval[key] = tchild
|
|
2675
|
+
|
|
2676
|
+
anno := GetProp(srcnode, S_BANNO)
|
|
2677
|
+
if anno == nil {
|
|
2678
|
+
if tchildMap, ok := tchild.(map[string]any); ok {
|
|
2679
|
+
delete(tchildMap, S_BANNO)
|
|
2680
|
+
}
|
|
2681
|
+
} else {
|
|
2682
|
+
if IsMap(tchild) {
|
|
2683
|
+
SetProp(tchild, S_BANNO, anno)
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
rval := map[string]any{}
|
|
2689
|
+
|
|
2690
|
+
if !IsEmpty(tval) {
|
|
2691
|
+
// Build parallel source object
|
|
2692
|
+
tsrc := map[string]any{}
|
|
2693
|
+
for i, srcItem := range srclist {
|
|
2694
|
+
kn := resolveKey(srcItem, i)
|
|
2695
|
+
if kn != "" {
|
|
2696
|
+
tsrc[kn] = srcItem
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
tpath := make([]string, len(inj.Path.List)-1)
|
|
2701
|
+
copy(tpath, inj.Path.List[:len(inj.Path.List)-1])
|
|
2702
|
+
|
|
2703
|
+
ckey := ""
|
|
2704
|
+
if len(inj.Path.List) >= 2 {
|
|
2705
|
+
ckey = inj.Path.List[len(inj.Path.List)-2]
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
dpath := []string{S_DTOP}
|
|
2709
|
+
for _, p := range strings.Split(srcpath, S_DT) {
|
|
2710
|
+
dpath = append(dpath, p)
|
|
2711
|
+
}
|
|
2712
|
+
dpath = append(dpath, "$:"+ckey)
|
|
2713
|
+
|
|
2714
|
+
tcur := map[string]any{ckey: tsrc}
|
|
2715
|
+
|
|
2716
|
+
if len(tpath) > 1 {
|
|
2717
|
+
pkey := S_DTOP
|
|
2718
|
+
if len(inj.Path.List) >= 3 {
|
|
2719
|
+
pkey = inj.Path.List[len(inj.Path.List)-3]
|
|
2720
|
+
}
|
|
2721
|
+
tcur = map[string]any{pkey: tcur}
|
|
2722
|
+
dpath = append(dpath, "$:"+pkey)
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
tinj := inj.child(0, []string{ckey})
|
|
2726
|
+
tinj.Path = &ListRef[string]{List: tpath}
|
|
2727
|
+
|
|
2728
|
+
tnodeslist := make([]any, 1)
|
|
2729
|
+
copy(tnodeslist, inj.Nodes.List[len(inj.Nodes.List)-1:])
|
|
2730
|
+
tinj.Nodes = &ListRef[any]{List: tnodeslist}
|
|
2731
|
+
|
|
2732
|
+
tinj.Parent = tinj.Nodes.List[len(tinj.Nodes.List)-1]
|
|
2733
|
+
tinj.Val = tval
|
|
2734
|
+
|
|
2735
|
+
tinj.Dpath = dpath
|
|
2736
|
+
tinj.Dparent = tcur
|
|
2737
|
+
|
|
2738
|
+
Inject(tval, store, tinj)
|
|
2739
|
+
if r, ok := tinj.Val.(map[string]any); ok {
|
|
2740
|
+
rval = r
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
SetProp(target, tkey, rval)
|
|
2745
|
+
|
|
2746
|
+
// Drop transform key.
|
|
2747
|
+
return nil
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
// transform_APPLY => `$APPLY`
|
|
2751
|
+
// Reference original spec (enables recursive transformations).
|
|
2752
|
+
// Format: ['`$REF`', '`spec-path`']
|
|
2753
|
+
var Transform_REF Injector = func(
|
|
2754
|
+
inj *Injection,
|
|
2755
|
+
val any,
|
|
2756
|
+
ref *string,
|
|
2757
|
+
store any,
|
|
2758
|
+
) any {
|
|
2759
|
+
if inj.Mode != M_VAL {
|
|
2760
|
+
return nil
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
// Get arguments: ['`$REF`', 'ref-path'].
|
|
2764
|
+
refpath := GetProp(inj.Parent, 1)
|
|
2765
|
+
inj.KeyI = len(inj.Keys.List)
|
|
2766
|
+
|
|
2767
|
+
// Spec reference.
|
|
2768
|
+
specFn := GetProp(store, S_DSPEC)
|
|
2769
|
+
if specFn == nil {
|
|
2770
|
+
return nil
|
|
2771
|
+
}
|
|
2772
|
+
var spec any
|
|
2773
|
+
if fn, ok := specFn.(func() any); ok {
|
|
2774
|
+
spec = fn()
|
|
2775
|
+
} else {
|
|
2776
|
+
return nil
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
dpath := make([]string, 0)
|
|
2780
|
+
if len(inj.Path.List) > 1 {
|
|
2781
|
+
dpath = append(dpath, inj.Path.List[1:]...)
|
|
2782
|
+
}
|
|
2783
|
+
refResult := GetPath(refpath, spec, &Injection{
|
|
2784
|
+
Dpath: dpath,
|
|
2785
|
+
Dparent: GetPath(dpath, spec),
|
|
2786
|
+
})
|
|
2787
|
+
|
|
2788
|
+
hasSubRef := false
|
|
2789
|
+
if IsNode(refResult) {
|
|
2790
|
+
Walk(refResult, func(k *string, v any, parent any, path []string) any {
|
|
2791
|
+
if s, ok := v.(string); ok && s == "`$REF`" {
|
|
2792
|
+
hasSubRef = true
|
|
2793
|
+
}
|
|
2794
|
+
return v
|
|
2795
|
+
})
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
tref := Clone(refResult)
|
|
2799
|
+
|
|
2800
|
+
cpath := make([]string, 0)
|
|
2801
|
+
if len(inj.Path.List) > 3 {
|
|
2802
|
+
cpath = append(cpath, inj.Path.List[:len(inj.Path.List)-3]...)
|
|
2803
|
+
}
|
|
2804
|
+
tpath := make([]string, 0)
|
|
2805
|
+
if len(inj.Path.List) >= 1 {
|
|
2806
|
+
tpath = append(tpath, inj.Path.List[:len(inj.Path.List)]...)
|
|
2807
|
+
}
|
|
2808
|
+
tpath = tpath[:len(tpath)-1]
|
|
2809
|
+
|
|
2810
|
+
tcur := GetPath(cpath, store)
|
|
2811
|
+
tval := GetPath(tpath, store)
|
|
2812
|
+
|
|
2813
|
+
var rval any
|
|
2814
|
+
|
|
2815
|
+
if !hasSubRef || tval != nil {
|
|
2816
|
+
lastPath := S_DTOP
|
|
2817
|
+
if len(tpath) > 0 {
|
|
2818
|
+
lastPath = tpath[len(tpath)-1]
|
|
2819
|
+
}
|
|
2820
|
+
tinj := inj.child(0, []string{lastPath})
|
|
2821
|
+
tinj.Path = &ListRef[string]{List: tpath}
|
|
2822
|
+
|
|
2823
|
+
// TS: tinj.nodes = slice(inj.nodes, -1) → nodes[0:len-1] (all except last)
|
|
2824
|
+
nodesLen := len(inj.Nodes.List)
|
|
2825
|
+
if nodesLen > 1 {
|
|
2826
|
+
tnodeslist := make([]any, nodesLen-1)
|
|
2827
|
+
copy(tnodeslist, inj.Nodes.List[:nodesLen-1])
|
|
2828
|
+
tinj.Nodes = &ListRef[any]{List: tnodeslist}
|
|
2829
|
+
} else {
|
|
2830
|
+
tinj.Nodes = &ListRef[any]{List: []any{}}
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
// TS: tinj.parent = getelem(nodes, -2)
|
|
2834
|
+
if nodesLen >= 2 {
|
|
2835
|
+
tinj.Parent = inj.Nodes.List[nodesLen-2]
|
|
2836
|
+
}
|
|
2837
|
+
tinj.Val = tref
|
|
2838
|
+
|
|
2839
|
+
tinj.Dpath = cpath
|
|
2840
|
+
tinj.Dparent = tcur
|
|
2841
|
+
|
|
2842
|
+
Inject(tref, store, tinj)
|
|
2843
|
+
rval = tinj.Val
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
grandparent := inj.setval(rval, 2)
|
|
2847
|
+
|
|
2848
|
+
if IsList(grandparent) && inj.Prior != nil {
|
|
2849
|
+
inj.Prior.KeyI--
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
return val
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
var Transform_APPLY Injector = func(
|
|
2856
|
+
inj *Injection,
|
|
2857
|
+
val any,
|
|
2858
|
+
ref *string,
|
|
2859
|
+
store any,
|
|
2860
|
+
) any {
|
|
2861
|
+
ijname := "APPLY"
|
|
2862
|
+
|
|
2863
|
+
// Skip remaining keys
|
|
2864
|
+
if inj.Keys != nil && len(inj.Keys.List) > 0 {
|
|
2865
|
+
inj.Keys.List = inj.Keys.List[:1]
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
if !CheckPlacement(M_VAL, ijname, T_list, inj) {
|
|
2869
|
+
return nil
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
parentList := _listify(inj.Parent)
|
|
2873
|
+
var sliced []any
|
|
2874
|
+
if len(parentList) > 1 {
|
|
2875
|
+
sliced = parentList[1:]
|
|
2876
|
+
}
|
|
2877
|
+
args := InjectorArgs([]int{T_function, T_any}, sliced)
|
|
2878
|
+
|
|
2879
|
+
tkey := ""
|
|
2880
|
+
if len(inj.Path.List) >= 2 {
|
|
2881
|
+
tkey = inj.Path.List[len(inj.Path.List)-2]
|
|
2882
|
+
}
|
|
2883
|
+
var target any
|
|
2884
|
+
if len(inj.Nodes.List) >= 2 {
|
|
2885
|
+
target = inj.Nodes.List[len(inj.Nodes.List)-2]
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
if args[0] != nil {
|
|
2889
|
+
inj.Errs.Append("$" + ijname + ": " + args[0].(string))
|
|
2890
|
+
if target != nil {
|
|
2891
|
+
DelProp(target, tkey)
|
|
2892
|
+
}
|
|
2893
|
+
return nil
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
applyFn := args[1]
|
|
2897
|
+
child := args[2]
|
|
2898
|
+
|
|
2899
|
+
// Resolve child via injection
|
|
2900
|
+
cinj := InjectChild(child, store, inj)
|
|
2901
|
+
resolved := cinj.Val
|
|
2902
|
+
|
|
2903
|
+
// Call the apply function
|
|
2904
|
+
fn := reflect.ValueOf(applyFn)
|
|
2905
|
+
fnType := fn.Type()
|
|
2906
|
+
|
|
2907
|
+
var out any
|
|
2908
|
+
switch fnType.NumIn() {
|
|
2909
|
+
case 1:
|
|
2910
|
+
results := fn.Call([]reflect.Value{reflect.ValueOf(resolved)})
|
|
2911
|
+
if len(results) > 0 {
|
|
2912
|
+
out = results[0].Interface()
|
|
2913
|
+
}
|
|
2914
|
+
case 3:
|
|
2915
|
+
results := fn.Call([]reflect.Value{
|
|
2916
|
+
reflect.ValueOf(resolved),
|
|
2917
|
+
reflect.ValueOf(store),
|
|
2918
|
+
reflect.ValueOf(cinj),
|
|
2919
|
+
})
|
|
2920
|
+
if len(results) > 0 {
|
|
2921
|
+
out = results[0].Interface()
|
|
2922
|
+
}
|
|
2923
|
+
default:
|
|
2924
|
+
results := fn.Call([]reflect.Value{reflect.ValueOf(resolved)})
|
|
2925
|
+
if len(results) > 0 {
|
|
2926
|
+
out = results[0].Interface()
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
// Set on parent output
|
|
2931
|
+
if target != nil {
|
|
2932
|
+
SetProp(target, tkey, out)
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
return out
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
|
|
2939
|
+
// transform_FORMAT => `$FORMAT`
|
|
2940
|
+
// injectChild resolves a child value via injection, going up the injection chain
|
|
2941
|
+
// to get the correct data context.
|
|
2942
|
+
func InjectChild(child any, store any, inj *Injection) *Injection {
|
|
2943
|
+
cinj := inj
|
|
2944
|
+
|
|
2945
|
+
if inj.Prior != nil {
|
|
2946
|
+
if inj.Prior.Prior != nil {
|
|
2947
|
+
cinj = inj.Prior.Prior.child(inj.Prior.KeyI, inj.Prior.Keys.List)
|
|
2948
|
+
cinj.Val = child
|
|
2949
|
+
SetProp(cinj.Parent, inj.Prior.Key, child)
|
|
2950
|
+
} else {
|
|
2951
|
+
cinj = inj.Prior.child(inj.KeyI, inj.Keys.List)
|
|
2952
|
+
cinj.Val = child
|
|
2953
|
+
SetProp(cinj.Parent, inj.Key, child)
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
Inject(child, store, cinj)
|
|
2958
|
+
|
|
2959
|
+
return cinj
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
var Transform_FORMAT Injector = func(
|
|
2963
|
+
inj *Injection,
|
|
2964
|
+
val any,
|
|
2965
|
+
ref *string,
|
|
2966
|
+
store any,
|
|
2967
|
+
) any {
|
|
2968
|
+
// Remove remaining keys to avoid spurious processing.
|
|
2969
|
+
if inj.Keys != nil && len(inj.Keys.List) > 0 {
|
|
2970
|
+
inj.Keys.List = inj.Keys.List[:1]
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
if inj.Mode != M_VAL {
|
|
2974
|
+
return nil
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
// Get arguments: ['`$FORMAT`', 'name', child].
|
|
2978
|
+
name := GetProp(inj.Parent, 1)
|
|
2979
|
+
child := GetProp(inj.Parent, 2)
|
|
2980
|
+
|
|
2981
|
+
// Resolve child via injection using injectChild
|
|
2982
|
+
cinj := InjectChild(child, store, inj)
|
|
2983
|
+
resolved := cinj.Val
|
|
2984
|
+
|
|
2985
|
+
tkey := ""
|
|
2986
|
+
if len(inj.Path.List) >= 2 {
|
|
2987
|
+
tkey = inj.Path.List[len(inj.Path.List)-2]
|
|
2988
|
+
}
|
|
2989
|
+
var target any
|
|
2990
|
+
if len(inj.Nodes.List) >= 2 {
|
|
2991
|
+
target = inj.Nodes.List[len(inj.Nodes.List)-2]
|
|
2992
|
+
}
|
|
2993
|
+
if target == nil && len(inj.Nodes.List) > 0 {
|
|
2994
|
+
target = inj.Nodes.List[len(inj.Nodes.List)-1]
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
// Convert nil to "null" string for formatting purposes
|
|
2998
|
+
_fmtStr := func(v any) string {
|
|
2999
|
+
if v == nil {
|
|
3000
|
+
return "null"
|
|
3001
|
+
}
|
|
3002
|
+
return fmt.Sprint(v)
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
// Get formatter
|
|
3006
|
+
var formatter func(key *string, val any, parent any, path []string) any
|
|
3007
|
+
|
|
3008
|
+
if IsFunc(name) {
|
|
3009
|
+
fn := reflect.ValueOf(name)
|
|
3010
|
+
formatter = func(key *string, val any, parent any, path []string) any {
|
|
3011
|
+
results := fn.Call([]reflect.Value{
|
|
3012
|
+
reflect.ValueOf(key),
|
|
3013
|
+
reflect.ValueOf(val),
|
|
3014
|
+
reflect.ValueOf(parent),
|
|
3015
|
+
reflect.ValueOf(path),
|
|
3016
|
+
})
|
|
3017
|
+
if len(results) > 0 {
|
|
3018
|
+
return results[0].Interface()
|
|
3019
|
+
}
|
|
3020
|
+
return val
|
|
3021
|
+
}
|
|
3022
|
+
} else if nameStr, ok := name.(string); ok {
|
|
3023
|
+
switch nameStr {
|
|
3024
|
+
case "upper":
|
|
3025
|
+
formatter = func(_ *string, val any, _ any, _ []string) any {
|
|
3026
|
+
if IsNode(val) {
|
|
3027
|
+
return val
|
|
3028
|
+
}
|
|
3029
|
+
return strings.ToUpper(_fmtStr(val))
|
|
3030
|
+
}
|
|
3031
|
+
case "lower":
|
|
3032
|
+
formatter = func(_ *string, val any, _ any, _ []string) any {
|
|
3033
|
+
if IsNode(val) {
|
|
3034
|
+
return val
|
|
3035
|
+
}
|
|
3036
|
+
return strings.ToLower(_fmtStr(val))
|
|
3037
|
+
}
|
|
3038
|
+
case "string":
|
|
3039
|
+
formatter = func(_ *string, val any, _ any, _ []string) any {
|
|
3040
|
+
if IsNode(val) {
|
|
3041
|
+
return val
|
|
3042
|
+
}
|
|
3043
|
+
return _fmtStr(val)
|
|
3044
|
+
}
|
|
3045
|
+
case "number":
|
|
3046
|
+
formatter = func(_ *string, val any, _ any, _ []string) any {
|
|
3047
|
+
if IsNode(val) {
|
|
3048
|
+
return val
|
|
3049
|
+
}
|
|
3050
|
+
switch v := val.(type) {
|
|
3051
|
+
case int:
|
|
3052
|
+
return v
|
|
3053
|
+
case float64:
|
|
3054
|
+
return v
|
|
3055
|
+
case string:
|
|
3056
|
+
n, err := strconv.ParseFloat(v, 64)
|
|
3057
|
+
if err != nil {
|
|
3058
|
+
return 0
|
|
3059
|
+
}
|
|
3060
|
+
if n == float64(int(n)) {
|
|
3061
|
+
return int(n)
|
|
3062
|
+
}
|
|
3063
|
+
return n
|
|
3064
|
+
default:
|
|
3065
|
+
return 0
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
case "integer":
|
|
3069
|
+
formatter = func(_ *string, val any, _ any, _ []string) any {
|
|
3070
|
+
if IsNode(val) {
|
|
3071
|
+
return val
|
|
3072
|
+
}
|
|
3073
|
+
switch v := val.(type) {
|
|
3074
|
+
case int:
|
|
3075
|
+
return v
|
|
3076
|
+
case float64:
|
|
3077
|
+
return int(v)
|
|
3078
|
+
case string:
|
|
3079
|
+
n, err := strconv.ParseFloat(v, 64)
|
|
3080
|
+
if err != nil {
|
|
3081
|
+
return 0
|
|
3082
|
+
}
|
|
3083
|
+
return int(n)
|
|
3084
|
+
default:
|
|
3085
|
+
return 0
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
case "concat":
|
|
3089
|
+
formatter = func(key *string, val any, _ any, _ []string) any {
|
|
3090
|
+
if key == nil && IsList(val) {
|
|
3091
|
+
parts := []string{}
|
|
3092
|
+
list := _listify(val)
|
|
3093
|
+
for _, v := range list {
|
|
3094
|
+
if IsNode(v) {
|
|
3095
|
+
parts = append(parts, "")
|
|
3096
|
+
} else {
|
|
3097
|
+
parts = append(parts, _fmtStr(v))
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
return strings.Join(parts, "")
|
|
3101
|
+
}
|
|
3102
|
+
return val
|
|
3103
|
+
}
|
|
3104
|
+
case "identity":
|
|
3105
|
+
formatter = func(_ *string, val any, _ any, _ []string) any {
|
|
3106
|
+
return val
|
|
3107
|
+
}
|
|
3108
|
+
default:
|
|
3109
|
+
inj.Errs.Append("$FORMAT: unknown format: " + nameStr + ".")
|
|
3110
|
+
if target != nil {
|
|
3111
|
+
DelProp(target, tkey)
|
|
3112
|
+
}
|
|
3113
|
+
return nil
|
|
3114
|
+
}
|
|
3115
|
+
} else {
|
|
3116
|
+
inj.Errs.Append("$FORMAT: unknown format: " + Stringify(name) + ".")
|
|
3117
|
+
if target != nil {
|
|
3118
|
+
DelProp(target, tkey)
|
|
3119
|
+
}
|
|
3120
|
+
return nil
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
var out any
|
|
3124
|
+
out = Walk(resolved, formatter)
|
|
3125
|
+
|
|
3126
|
+
// Set on parent output
|
|
3127
|
+
if target != nil {
|
|
3128
|
+
SetProp(target, tkey, out)
|
|
3129
|
+
}
|
|
3130
|
+
|
|
3131
|
+
return out
|
|
3132
|
+
}
|
|
3133
|
+
|
|
3134
|
+
|
|
3135
|
+
// ---------------------------------------------------------------------
|
|
3136
|
+
// Transform function: top-level
|
|
3137
|
+
|
|
3138
|
+
func Transform(
|
|
3139
|
+
data any, // source data
|
|
3140
|
+
spec any, // transform specification
|
|
3141
|
+
injdefs ...*Injection,
|
|
3142
|
+
) any {
|
|
3143
|
+
if len(injdefs) > 0 && injdefs[0] != nil {
|
|
3144
|
+
injdef := injdefs[0]
|
|
3145
|
+
return TransformModifyHandler(
|
|
3146
|
+
data,
|
|
3147
|
+
spec,
|
|
3148
|
+
injdef.Extra,
|
|
3149
|
+
injdef.Modify,
|
|
3150
|
+
injdef.Handler,
|
|
3151
|
+
injdef.Errs,
|
|
3152
|
+
injdef.Meta,
|
|
3153
|
+
)
|
|
3154
|
+
}
|
|
3155
|
+
return TransformModify(data, spec, nil, nil)
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
// TransformModifyHandler is like TransformModify but allows a custom handler and injection inj.
|
|
3159
|
+
func TransformModifyHandler(
|
|
3160
|
+
data any,
|
|
3161
|
+
spec any,
|
|
3162
|
+
extra any,
|
|
3163
|
+
modify Modify,
|
|
3164
|
+
handler Injector,
|
|
3165
|
+
errs *ListRef[any],
|
|
3166
|
+
meta map[string]any,
|
|
3167
|
+
) any {
|
|
3168
|
+
// Clone and wrap
|
|
3169
|
+
wrapFlags := map[string]bool{"wrap": true}
|
|
3170
|
+
origspec := spec
|
|
3171
|
+
spec = CloneFlags(spec, wrapFlags)
|
|
3172
|
+
|
|
3173
|
+
// Split extra transforms from extra data
|
|
3174
|
+
extraTransforms := map[string]any{}
|
|
3175
|
+
extraData := map[string]any{}
|
|
3176
|
+
|
|
3177
|
+
if extra != nil {
|
|
3178
|
+
pairs := Items(extra)
|
|
3179
|
+
for _, kv := range pairs {
|
|
3180
|
+
k, _ := kv[0].(string)
|
|
3181
|
+
v := kv[1]
|
|
3182
|
+
if strings.HasPrefix(k, S_DS) {
|
|
3183
|
+
extraTransforms[k] = v
|
|
3184
|
+
} else {
|
|
3185
|
+
extraData[k] = v
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
if extraData == nil {
|
|
3191
|
+
extraData = map[string]any{}
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
var dataClone any
|
|
3195
|
+
if data == nil {
|
|
3196
|
+
dataClone = nil
|
|
3197
|
+
} else {
|
|
3198
|
+
dataClone = Merge([]any{
|
|
3199
|
+
CloneFlags(extraData, wrapFlags),
|
|
3200
|
+
CloneFlags(data, wrapFlags),
|
|
3201
|
+
})
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
// Save original spec for $REF
|
|
3205
|
+
_ = origspec
|
|
3206
|
+
|
|
3207
|
+
store := map[string]any{
|
|
3208
|
+
S_DTOP: dataClone,
|
|
3209
|
+
S_DSPEC: func() any { return origspec },
|
|
3210
|
+
"$BT": func() any { return S_BT },
|
|
3211
|
+
"$DS": func() any { return S_DS },
|
|
3212
|
+
"$WHEN": func() any {
|
|
3213
|
+
return time.Now().UTC().Format(time.RFC3339)
|
|
3214
|
+
},
|
|
3215
|
+
"$DELETE": Transform_DELETE,
|
|
3216
|
+
"$COPY": Transform_COPY,
|
|
3217
|
+
"$KEY": Transform_KEY,
|
|
3218
|
+
"$META": Transform_META,
|
|
3219
|
+
"$ANNO": Transform_ANNO,
|
|
3220
|
+
"$MERGE": Transform_MERGE,
|
|
3221
|
+
"$EACH": Transform_EACH,
|
|
3222
|
+
"$PACK": Transform_PACK,
|
|
3223
|
+
"$REF": Transform_REF,
|
|
3224
|
+
"$APPLY": Transform_APPLY,
|
|
3225
|
+
"$FORMAT": Transform_FORMAT,
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
for k, v := range extraTransforms {
|
|
3229
|
+
store[k] = v
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
if errs == nil {
|
|
3233
|
+
errs = ListRefCreate[any]()
|
|
3234
|
+
}
|
|
3235
|
+
store[S_DERRS] = errs
|
|
3236
|
+
|
|
3237
|
+
// Create injection inj with handler
|
|
3238
|
+
injState := &Injection{
|
|
3239
|
+
Modify: modify,
|
|
3240
|
+
Handler: handler,
|
|
3241
|
+
Errs: errs,
|
|
3242
|
+
Meta: meta,
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
out := Inject(spec, store, injState)
|
|
3246
|
+
out = CloneFlags(out, map[string]bool{"unwrap": true})
|
|
3247
|
+
return out
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
// transformModifyCore is the internal implementation that collects errors.
|
|
3251
|
+
func transformModifyCore(
|
|
3252
|
+
data any,
|
|
3253
|
+
spec any,
|
|
3254
|
+
extra any,
|
|
3255
|
+
modify Modify,
|
|
3256
|
+
) (any, *ListRef[any]) {
|
|
3257
|
+
|
|
3258
|
+
// Clone and wrap: clone the structures and convert bare lists to ListRefs
|
|
3259
|
+
// for reference stability, in a single pass.
|
|
3260
|
+
wrapFlags := map[string]bool{"wrap": true}
|
|
3261
|
+
|
|
3262
|
+
// Save original spec for $REF as a separate wrapped clone (not modified by injection)
|
|
3263
|
+
origspec := CloneFlags(spec, wrapFlags)
|
|
3264
|
+
spec = CloneFlags(spec, wrapFlags)
|
|
3265
|
+
|
|
3266
|
+
// Split extra transforms from extra data
|
|
3267
|
+
extraTransforms := map[string]any{}
|
|
3268
|
+
extraData := map[string]any{}
|
|
3269
|
+
|
|
3270
|
+
if extra != nil {
|
|
3271
|
+
pairs := Items(extra)
|
|
3272
|
+
for _, kv := range pairs {
|
|
3273
|
+
k, _ := kv[0].(string)
|
|
3274
|
+
v := kv[1]
|
|
3275
|
+
if strings.HasPrefix(k, S_DS) {
|
|
3276
|
+
extraTransforms[k] = v
|
|
3277
|
+
} else {
|
|
3278
|
+
extraData[k] = v
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
// Create empty maps if nil
|
|
3284
|
+
if extraData == nil {
|
|
3285
|
+
extraData = map[string]any{}
|
|
3286
|
+
}
|
|
3287
|
+
if data == nil {
|
|
3288
|
+
data = map[string]any{}
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
// Merge extraData + data, clone+wrap in one pass
|
|
3292
|
+
dataClone := Merge([]any{
|
|
3293
|
+
CloneFlags(extraData, wrapFlags),
|
|
3294
|
+
CloneFlags(data, wrapFlags),
|
|
3295
|
+
})
|
|
3296
|
+
|
|
3297
|
+
// Collect errors from transform operations
|
|
3298
|
+
errs := ListRefCreate[any]()
|
|
3299
|
+
|
|
3300
|
+
// The injection store with transform functions
|
|
3301
|
+
store := map[string]any{
|
|
3302
|
+
// Merged data is at $TOP
|
|
3303
|
+
S_DTOP: dataClone,
|
|
3304
|
+
|
|
3305
|
+
// Reference to original spec for $REF
|
|
3306
|
+
S_DSPEC: func() any { return origspec },
|
|
3307
|
+
|
|
3308
|
+
// Handy escapes
|
|
3309
|
+
"$BT": func() any { return S_BT },
|
|
3310
|
+
"$DS": func() any { return S_DS },
|
|
3311
|
+
|
|
3312
|
+
// Insert current date/time
|
|
3313
|
+
"$WHEN": func() any {
|
|
3314
|
+
return time.Now().UTC().Format(time.RFC3339)
|
|
3315
|
+
},
|
|
3316
|
+
|
|
3317
|
+
// Built-in transform functions
|
|
3318
|
+
"$DELETE": Transform_DELETE,
|
|
3319
|
+
"$COPY": Transform_COPY,
|
|
3320
|
+
"$KEY": Transform_KEY,
|
|
3321
|
+
"$META": Transform_META,
|
|
3322
|
+
"$ANNO": Transform_ANNO,
|
|
3323
|
+
"$MERGE": Transform_MERGE,
|
|
3324
|
+
"$EACH": Transform_EACH,
|
|
3325
|
+
"$PACK": Transform_PACK,
|
|
3326
|
+
"$REF": Transform_REF,
|
|
3327
|
+
"$APPLY": Transform_APPLY,
|
|
3328
|
+
"$FORMAT": Transform_FORMAT,
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
// Add any extra transforms
|
|
3332
|
+
for k, v := range extraTransforms {
|
|
3333
|
+
store[k] = v
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
// Pass errs via injection inj to avoid Merge converting ListRef to []any
|
|
3337
|
+
injState := &Injection{
|
|
3338
|
+
Modify: modify,
|
|
3339
|
+
Errs: errs,
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
out := Inject(spec, store, injState)
|
|
3343
|
+
|
|
3344
|
+
// Clone output, unwrapping ListRefs back to bare lists.
|
|
3345
|
+
out = CloneFlags(out, map[string]bool{"unwrap": true})
|
|
3346
|
+
|
|
3347
|
+
return out, errs
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
func TransformModify(
|
|
3351
|
+
data any, // source data
|
|
3352
|
+
spec any, // transform specification
|
|
3353
|
+
extra any, // extra store
|
|
3354
|
+
modify Modify, // optional modify
|
|
3355
|
+
) any {
|
|
3356
|
+
out, _ := transformModifyCore(data, spec, extra, modify)
|
|
3357
|
+
return out
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
// TransformCollect is like Transform but also returns collected error strings.
|
|
3361
|
+
func TransformCollect(
|
|
3362
|
+
data any,
|
|
3363
|
+
spec any,
|
|
3364
|
+
) (any, []string) {
|
|
3365
|
+
out, errs := transformModifyCore(data, spec, nil, nil)
|
|
3366
|
+
errStrs := make([]string, 0, len(errs.List))
|
|
3367
|
+
for _, e := range errs.List {
|
|
3368
|
+
if s, ok := e.(string); ok {
|
|
3369
|
+
errStrs = append(errStrs, s)
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
return out, errStrs
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
var validate_STRING Injector = func(
|
|
3376
|
+
inj *Injection,
|
|
3377
|
+
_val any,
|
|
3378
|
+
ref *string,
|
|
3379
|
+
store any,
|
|
3380
|
+
) any {
|
|
3381
|
+
out := GetProp(inj.Dparent, inj.Key)
|
|
3382
|
+
|
|
3383
|
+
t := Typify(out)
|
|
3384
|
+
if 0 == (T_string & t) {
|
|
3385
|
+
msg := _invalidTypeMsg(inj.Path.List, S_string, Typename(t), out)
|
|
3386
|
+
inj.Errs.Append(msg)
|
|
3387
|
+
return nil
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
if S_MT == out.(string) {
|
|
3391
|
+
msg := "Empty string at " + Pathify(inj.Path.List, 1)
|
|
3392
|
+
inj.Errs.Append(msg)
|
|
3393
|
+
return nil
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
return out
|
|
3397
|
+
}
|
|
3398
|
+
|
|
3399
|
+
var validate_NUMBER Injector = func(
|
|
3400
|
+
inj *Injection,
|
|
3401
|
+
_val any,
|
|
3402
|
+
ref *string,
|
|
3403
|
+
store any,
|
|
3404
|
+
) any {
|
|
3405
|
+
out := GetProp(inj.Dparent, inj.Key)
|
|
3406
|
+
|
|
3407
|
+
t := Typify(out)
|
|
3408
|
+
if 0 == (T_number & t) {
|
|
3409
|
+
msg := _invalidTypeMsg(inj.Path.List, S_number, Typename(t), out)
|
|
3410
|
+
inj.Errs.Append(msg)
|
|
3411
|
+
return nil
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
return out
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3417
|
+
var validate_BOOLEAN Injector = func(
|
|
3418
|
+
inj *Injection,
|
|
3419
|
+
_val any,
|
|
3420
|
+
ref *string,
|
|
3421
|
+
store any,
|
|
3422
|
+
) any {
|
|
3423
|
+
out := GetProp(inj.Dparent, inj.Key)
|
|
3424
|
+
|
|
3425
|
+
t := Typify(out)
|
|
3426
|
+
if 0 == (T_boolean & t) {
|
|
3427
|
+
msg := _invalidTypeMsg(inj.Path.List, S_boolean, Typename(t), out)
|
|
3428
|
+
inj.Errs.Append(msg)
|
|
3429
|
+
return nil
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
return out
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
var validate_OBJECT Injector = func(
|
|
3436
|
+
inj *Injection,
|
|
3437
|
+
_val any,
|
|
3438
|
+
ref *string,
|
|
3439
|
+
store any,
|
|
3440
|
+
) any {
|
|
3441
|
+
out := GetProp(inj.Dparent, inj.Key)
|
|
3442
|
+
|
|
3443
|
+
t := Typify(out)
|
|
3444
|
+
|
|
3445
|
+
if 0 == (T_map & t) {
|
|
3446
|
+
msg := _invalidTypeMsg(inj.Path.List, S_object, Typename(t), out)
|
|
3447
|
+
inj.Errs.Append(msg)
|
|
3448
|
+
|
|
3449
|
+
return nil
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
return out
|
|
3453
|
+
}
|
|
3454
|
+
|
|
3455
|
+
var validate_ARRAY Injector = func(
|
|
3456
|
+
inj *Injection,
|
|
3457
|
+
_val any,
|
|
3458
|
+
ref *string,
|
|
3459
|
+
store any,
|
|
3460
|
+
) any {
|
|
3461
|
+
out := GetProp(inj.Dparent, inj.Key)
|
|
3462
|
+
|
|
3463
|
+
t := Typify(out)
|
|
3464
|
+
if 0 == (T_list & t) {
|
|
3465
|
+
msg := _invalidTypeMsg(inj.Path.List, S_array, Typename(t), out)
|
|
3466
|
+
inj.Errs.Append(msg)
|
|
3467
|
+
return nil
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3470
|
+
return out
|
|
3471
|
+
}
|
|
3472
|
+
|
|
3473
|
+
var validate_FUNCTION Injector = func(
|
|
3474
|
+
inj *Injection,
|
|
3475
|
+
_val any,
|
|
3476
|
+
ref *string,
|
|
3477
|
+
store any,
|
|
3478
|
+
) any {
|
|
3479
|
+
out := GetProp(inj.Dparent, inj.Key)
|
|
3480
|
+
|
|
3481
|
+
t := Typify(out)
|
|
3482
|
+
if 0 == (T_function & t) {
|
|
3483
|
+
msg := _invalidTypeMsg(inj.Path.List, S_function, Typename(t), out)
|
|
3484
|
+
inj.Errs.Append(msg)
|
|
3485
|
+
return nil
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3488
|
+
return out
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
var validate_ANY Injector = func(
|
|
3492
|
+
inj *Injection,
|
|
3493
|
+
_val any,
|
|
3494
|
+
ref *string,
|
|
3495
|
+
store any,
|
|
3496
|
+
) any {
|
|
3497
|
+
return GetProp(inj.Dparent, inj.Key)
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3500
|
+
// Generic type validator: handles $INTEGER, $DECIMAL, $NULL, $NIL, $MAP, $LIST, $INSTANCE
|
|
3501
|
+
var validate_TYPE Injector = func(
|
|
3502
|
+
inj *Injection,
|
|
3503
|
+
_val any,
|
|
3504
|
+
ref *string,
|
|
3505
|
+
store any,
|
|
3506
|
+
) any {
|
|
3507
|
+
if ref == nil {
|
|
3508
|
+
return nil
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3511
|
+
tname := strings.ToLower((*ref)[1:]) // e.g. "$DECIMAL" → "decimal"
|
|
3512
|
+
|
|
3513
|
+
// Find the type bit from the TYPENAME array
|
|
3514
|
+
var typev int
|
|
3515
|
+
for i, name := range TYPENAME {
|
|
3516
|
+
if name == tname {
|
|
3517
|
+
typev = 1 << (31 - i)
|
|
3518
|
+
break
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3522
|
+
out := GetProp(inj.Dparent, inj.Key)
|
|
3523
|
+
t := Typify(out)
|
|
3524
|
+
|
|
3525
|
+
// In Go, nil represents both null and noval (undefined).
|
|
3526
|
+
// $NIL should match nil values, and $NULL should also match nil.
|
|
3527
|
+
if tname == "nil" && out == nil {
|
|
3528
|
+
return out
|
|
3529
|
+
}
|
|
3530
|
+
if tname == "null" && out == nil {
|
|
3531
|
+
return out
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3534
|
+
if 0 == (t & typev) {
|
|
3535
|
+
msg := _invalidTypeMsg(inj.Path.List, tname, Typename(t), out)
|
|
3536
|
+
inj.Errs.Append(msg)
|
|
3537
|
+
return nil
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
return out
|
|
3541
|
+
}
|
|
3542
|
+
|
|
3543
|
+
var validate_CHILD Injector = func(
|
|
3544
|
+
inj *Injection,
|
|
3545
|
+
_val any,
|
|
3546
|
+
ref *string,
|
|
3547
|
+
store any,
|
|
3548
|
+
) any {
|
|
3549
|
+
// Map syntax
|
|
3550
|
+
if inj.Mode == M_KEYPRE {
|
|
3551
|
+
child := GetProp(inj.Parent, inj.Key)
|
|
3552
|
+
|
|
3553
|
+
pkey := inj.Path.List[len(inj.Path.List)-2]
|
|
3554
|
+
tval := GetProp(inj.Dparent, pkey)
|
|
3555
|
+
|
|
3556
|
+
if nil == tval {
|
|
3557
|
+
tval = map[string]any{}
|
|
3558
|
+
|
|
3559
|
+
} else if !IsMap(tval) {
|
|
3560
|
+
inj.Errs.Append(
|
|
3561
|
+
_invalidTypeMsg(
|
|
3562
|
+
inj.Path.List[:len(inj.Path.List)-1],
|
|
3563
|
+
S_object,
|
|
3564
|
+
Typename(Typify(tval)),
|
|
3565
|
+
tval,
|
|
3566
|
+
))
|
|
3567
|
+
return nil
|
|
3568
|
+
}
|
|
3569
|
+
|
|
3570
|
+
// For each key in tval, clone the child into parent
|
|
3571
|
+
ckeys := KeysOf(tval)
|
|
3572
|
+
for _, ckey := range ckeys {
|
|
3573
|
+
SetProp(inj.Parent, ckey, Clone(child))
|
|
3574
|
+
inj.Keys.Append(ckey)
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
DelProp(inj.Parent, inj.Key)
|
|
3578
|
+
|
|
3579
|
+
return nil
|
|
3580
|
+
}
|
|
3581
|
+
|
|
3582
|
+
// List syntax
|
|
3583
|
+
if inj.Mode == M_VAL {
|
|
3584
|
+
|
|
3585
|
+
// We expect 'parent' to be a slice of any, like ["`$CHILD`", childTemplate].
|
|
3586
|
+
if !IsList(inj.Parent) {
|
|
3587
|
+
inj.Errs.Append("Invalid $CHILD as value")
|
|
3588
|
+
return nil
|
|
3589
|
+
}
|
|
3590
|
+
|
|
3591
|
+
child := GetProp(inj.Parent, 1)
|
|
3592
|
+
dparent := inj.Dparent
|
|
3593
|
+
|
|
3594
|
+
// If dparent is nil => empty list default
|
|
3595
|
+
if nil == dparent {
|
|
3596
|
+
if lr, ok := inj.Parent.(*ListRef[any]); ok {
|
|
3597
|
+
lr.List = []any{}
|
|
3598
|
+
} else {
|
|
3599
|
+
inj.Parent = []any{}
|
|
3600
|
+
}
|
|
3601
|
+
return nil
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
// If dparent is not a list => error
|
|
3605
|
+
if !IsList(dparent) {
|
|
3606
|
+
inj.Errs.Append(
|
|
3607
|
+
_invalidTypeMsg(
|
|
3608
|
+
inj.Path.List[:len(inj.Path.List)-1],
|
|
3609
|
+
S_array,
|
|
3610
|
+
Typename(Typify(dparent)),
|
|
3611
|
+
dparent,
|
|
3612
|
+
))
|
|
3613
|
+
parentList := _listify(inj.Parent)
|
|
3614
|
+
inj.KeyI = len(parentList)
|
|
3615
|
+
return dparent
|
|
3616
|
+
}
|
|
3617
|
+
|
|
3618
|
+
// Otherwise, dparent is a list => clone child for each element
|
|
3619
|
+
currentList := _listify(dparent)
|
|
3620
|
+
length := len(currentList)
|
|
3621
|
+
|
|
3622
|
+
// Make a new slice to hold the child clones, sized to length
|
|
3623
|
+
newParent := make([]any, length)
|
|
3624
|
+
for i := 0; i < length; i++ {
|
|
3625
|
+
newParent[i] = Clone(child)
|
|
3626
|
+
}
|
|
3627
|
+
|
|
3628
|
+
// Replace parent with the new slice
|
|
3629
|
+
if lr, ok := inj.Parent.(*ListRef[any]); ok {
|
|
3630
|
+
lr.List = newParent
|
|
3631
|
+
} else {
|
|
3632
|
+
inj.Parent = newParent
|
|
3633
|
+
}
|
|
3634
|
+
|
|
3635
|
+
inj.KeyI = 0
|
|
3636
|
+
|
|
3637
|
+
out := GetProp(dparent, 0)
|
|
3638
|
+
return out
|
|
3639
|
+
}
|
|
3640
|
+
|
|
3641
|
+
return nil
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3644
|
+
// Forward declaration for validate_ONE
|
|
3645
|
+
var validate_ONE Injector
|
|
3646
|
+
|
|
3647
|
+
// Forward declaration for validate_EXACT
|
|
3648
|
+
var validate_EXACT Injector
|
|
3649
|
+
|
|
3650
|
+
// Implementation will be set after Validate is defined
|
|
3651
|
+
func init_validate_ONE() {
|
|
3652
|
+
validate_ONE = func(
|
|
3653
|
+
inj *Injection,
|
|
3654
|
+
_val any,
|
|
3655
|
+
ref *string,
|
|
3656
|
+
store any,
|
|
3657
|
+
) any {
|
|
3658
|
+
// Only operate in "val mode" (list mode).
|
|
3659
|
+
if inj.Mode == M_VAL {
|
|
3660
|
+
// Validate that parent is a list and we're at the first element
|
|
3661
|
+
if !IsList(inj.Parent) || inj.KeyI != 0 {
|
|
3662
|
+
inj.Errs.Append("The $ONE validator at field " +
|
|
3663
|
+
Pathify(inj.Path.List, 1, 1) +
|
|
3664
|
+
" must be the first element of an array.")
|
|
3665
|
+
return nil
|
|
3666
|
+
}
|
|
3667
|
+
|
|
3668
|
+
// Once we handle `$ONE`, we skip further iteration by setting KeyI to keys.length
|
|
3669
|
+
inj.KeyI = len(inj.Keys.List)
|
|
3670
|
+
|
|
3671
|
+
// The parent is assumed to be a slice: ["`$ONE`", alt0, alt1, ...].
|
|
3672
|
+
parentSlice, ok := _asList(inj.Parent)
|
|
3673
|
+
if !ok {
|
|
3674
|
+
return nil
|
|
3675
|
+
}
|
|
3676
|
+
|
|
3677
|
+
// Get grandparent and grandkey to replace the structure
|
|
3678
|
+
grandparent := inj.Nodes.List[len(inj.Nodes.List)-2]
|
|
3679
|
+
grandkey := inj.Path.List[len(inj.Path.List)-2]
|
|
3680
|
+
|
|
3681
|
+
// Clean up structure by replacing [$ONE, ...] with current value
|
|
3682
|
+
SetProp(grandparent, grandkey, inj.Dparent)
|
|
3683
|
+
inj.Parent = inj.Dparent
|
|
3684
|
+
|
|
3685
|
+
// Adjust the path
|
|
3686
|
+
inj.Path.List = inj.Path.List[:len(inj.Path.List)-1]
|
|
3687
|
+
inj.Key = inj.Path.List[len(inj.Path.List)-1]
|
|
3688
|
+
|
|
3689
|
+
// The shape alternatives are everything after the first element.
|
|
3690
|
+
tvals := parentSlice[1:] // alt0, alt1, ...
|
|
3691
|
+
|
|
3692
|
+
// Ensure we have at least one alternative
|
|
3693
|
+
if len(tvals) == 0 {
|
|
3694
|
+
inj.Errs.Append("The $ONE validator at field " +
|
|
3695
|
+
Pathify(inj.Path.List, 1, 1) +
|
|
3696
|
+
" must have at least one argument.")
|
|
3697
|
+
return nil
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
// Try each alternative shape
|
|
3701
|
+
for _, tval := range tvals {
|
|
3702
|
+
// Collect errors in a temporary slice
|
|
3703
|
+
var terrs = ListRefCreate[any]()
|
|
3704
|
+
|
|
3705
|
+
// Create a new store for validation
|
|
3706
|
+
vstore := Clone(store).(map[string]any)
|
|
3707
|
+
vstore["$TOP"] = inj.Dparent
|
|
3708
|
+
|
|
3709
|
+
// Attempt validation of data with shape `tval`
|
|
3710
|
+
vcurrent, err := Validate(inj.Dparent, tval, &Injection{Extra: vstore, Errs: terrs})
|
|
3711
|
+
|
|
3712
|
+
// Update the value in the grandparent
|
|
3713
|
+
SetProp(grandparent, grandkey, vcurrent)
|
|
3714
|
+
|
|
3715
|
+
// If no errors, we found a match
|
|
3716
|
+
if err == nil && len(terrs.List) == 0 {
|
|
3717
|
+
return nil
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
|
|
3721
|
+
// If we get here, there was no match
|
|
3722
|
+
mapped := make([]string, len(tvals))
|
|
3723
|
+
for i, v := range tvals {
|
|
3724
|
+
mapped[i] = Stringify(v)
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3727
|
+
joined := strings.Join(mapped, ", ")
|
|
3728
|
+
|
|
3729
|
+
re := regexp.MustCompile("`\\$([A-Z]+)`")
|
|
3730
|
+
valdesc := re.ReplaceAllStringFunc(joined, func(match string) string {
|
|
3731
|
+
submatches := re.FindStringSubmatch(match)
|
|
3732
|
+
if len(submatches) == 2 {
|
|
3733
|
+
return strings.ToLower(submatches[1])
|
|
3734
|
+
}
|
|
3735
|
+
return match
|
|
3736
|
+
})
|
|
3737
|
+
|
|
3738
|
+
prefix := ""
|
|
3739
|
+
if len(tvals) > 1 {
|
|
3740
|
+
prefix = "one of "
|
|
3741
|
+
}
|
|
3742
|
+
|
|
3743
|
+
msg := _invalidTypeMsg(
|
|
3744
|
+
inj.Path.List,
|
|
3745
|
+
prefix+valdesc,
|
|
3746
|
+
Typename(Typify(inj.Dparent)),
|
|
3747
|
+
inj.Dparent,
|
|
3748
|
+
"V0210",
|
|
3749
|
+
)
|
|
3750
|
+
inj.Errs.Append(msg)
|
|
3751
|
+
}
|
|
3752
|
+
|
|
3753
|
+
return nil
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
|
|
3757
|
+
func init_validate_EXACT() {
|
|
3758
|
+
validate_EXACT = func(
|
|
3759
|
+
inj *Injection,
|
|
3760
|
+
_val any,
|
|
3761
|
+
ref *string,
|
|
3762
|
+
_store any,
|
|
3763
|
+
) any {
|
|
3764
|
+
// Only operate in "val mode" (list mode).
|
|
3765
|
+
if inj.Mode == M_VAL {
|
|
3766
|
+
// Validate that parent is a list and we're at the first element
|
|
3767
|
+
if !IsList(inj.Parent) || inj.KeyI != 0 {
|
|
3768
|
+
inj.Errs.Append("The $EXACT validator at field " +
|
|
3769
|
+
Pathify(inj.Path.List, 1, 1) +
|
|
3770
|
+
" must be the first element of an array.")
|
|
3771
|
+
return nil
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3774
|
+
// Once we handle `$EXACT`, we skip further iteration by setting KeyI to keys.length
|
|
3775
|
+
inj.KeyI = len(inj.Keys.List)
|
|
3776
|
+
|
|
3777
|
+
// The parent is assumed to be a slice: ["`$EXACT`", alt0, alt1, ...].
|
|
3778
|
+
parentSlice, ok := _asList(inj.Parent)
|
|
3779
|
+
if !ok {
|
|
3780
|
+
return nil
|
|
3781
|
+
}
|
|
3782
|
+
|
|
3783
|
+
// Get grandparent and grandkey to replace the structure
|
|
3784
|
+
grandparent := inj.Nodes.List[len(inj.Nodes.List)-2]
|
|
3785
|
+
grandkey := inj.Path.List[len(inj.Path.List)-2]
|
|
3786
|
+
|
|
3787
|
+
// Clean up structure by replacing [$EXACT, ...] with current value
|
|
3788
|
+
SetProp(grandparent, grandkey, inj.Dparent)
|
|
3789
|
+
inj.Parent = inj.Dparent
|
|
3790
|
+
|
|
3791
|
+
// Adjust the path
|
|
3792
|
+
inj.Path.List = inj.Path.List[:len(inj.Path.List)-1]
|
|
3793
|
+
inj.Key = inj.Path.List[len(inj.Path.List)-1]
|
|
3794
|
+
|
|
3795
|
+
// The exact values to match are everything after the first element.
|
|
3796
|
+
tvals := parentSlice[1:] // alt0, alt1, ...
|
|
3797
|
+
|
|
3798
|
+
// Ensure we have at least one alternative
|
|
3799
|
+
if len(tvals) == 0 {
|
|
3800
|
+
inj.Errs.Append("The $EXACT validator at field " +
|
|
3801
|
+
Pathify(inj.Path.List, 1, 1) +
|
|
3802
|
+
" must have at least one argument.")
|
|
3803
|
+
return nil
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3806
|
+
// See if we can find an exact value match
|
|
3807
|
+
var currentStr *string
|
|
3808
|
+
for _, tval := range tvals {
|
|
3809
|
+
exactMatch := false
|
|
3810
|
+
|
|
3811
|
+
if !exactMatch {
|
|
3812
|
+
// Unwrap ListRefs for comparison since data and spec may have
|
|
3813
|
+
// different wrapping levels.
|
|
3814
|
+
unwrapFlags := map[string]bool{"unwrap": true}
|
|
3815
|
+
utval := CloneFlags(tval, unwrapFlags)
|
|
3816
|
+
ucurrent := CloneFlags(inj.Dparent, unwrapFlags)
|
|
3817
|
+
exactMatch = reflect.DeepEqual(utval, ucurrent)
|
|
3818
|
+
}
|
|
3819
|
+
|
|
3820
|
+
if !exactMatch && IsNode(tval) {
|
|
3821
|
+
if nil == currentStr {
|
|
3822
|
+
tmpstr := Stringify(inj.Dparent)
|
|
3823
|
+
currentStr = &tmpstr
|
|
3824
|
+
}
|
|
3825
|
+
tvalStr := Stringify(tval)
|
|
3826
|
+
exactMatch = tvalStr == *currentStr
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
if exactMatch {
|
|
3830
|
+
return nil
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
// If we get here, there was no match
|
|
3835
|
+
mapped := make([]string, len(tvals))
|
|
3836
|
+
for i, v := range tvals {
|
|
3837
|
+
mapped[i] = Stringify(v)
|
|
3838
|
+
}
|
|
3839
|
+
|
|
3840
|
+
joined := strings.Join(mapped, ", ")
|
|
3841
|
+
|
|
3842
|
+
re := regexp.MustCompile("`\\$([A-Z]+)`")
|
|
3843
|
+
valdesc := re.ReplaceAllStringFunc(joined, func(match string) string {
|
|
3844
|
+
submatches := re.FindStringSubmatch(match)
|
|
3845
|
+
if len(submatches) == 2 {
|
|
3846
|
+
return strings.ToLower(submatches[1])
|
|
3847
|
+
}
|
|
3848
|
+
return match
|
|
3849
|
+
})
|
|
3850
|
+
|
|
3851
|
+
prefix := ""
|
|
3852
|
+
if len(inj.Path.List) <= 1 {
|
|
3853
|
+
prefix = "value "
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
oneOf := ""
|
|
3857
|
+
if len(tvals) > 1 {
|
|
3858
|
+
oneOf = "one of "
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3861
|
+
msg := _invalidTypeMsg(
|
|
3862
|
+
inj.Path.List,
|
|
3863
|
+
prefix+"exactly equal to "+oneOf+valdesc,
|
|
3864
|
+
Typename(Typify(inj.Dparent)),
|
|
3865
|
+
inj.Dparent,
|
|
3866
|
+
"V0110",
|
|
3867
|
+
)
|
|
3868
|
+
inj.Errs.Append(msg)
|
|
3869
|
+
} else {
|
|
3870
|
+
DelProp(inj.Parent, inj.Key)
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3873
|
+
return nil
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
|
|
3877
|
+
func makeValidation(exact bool) Modify {
|
|
3878
|
+
return func(
|
|
3879
|
+
val any,
|
|
3880
|
+
key any,
|
|
3881
|
+
parent any,
|
|
3882
|
+
inj *Injection,
|
|
3883
|
+
_store any,
|
|
3884
|
+
) {
|
|
3885
|
+
if inj == nil {
|
|
3886
|
+
return
|
|
3887
|
+
}
|
|
3888
|
+
|
|
3889
|
+
if val == SKIP {
|
|
3890
|
+
return
|
|
3891
|
+
}
|
|
3892
|
+
|
|
3893
|
+
// Current val to verify — use dparent from injection inj.
|
|
3894
|
+
cval := GetProp(inj.Dparent, key)
|
|
3895
|
+
if !exact && cval == nil {
|
|
3896
|
+
return
|
|
3897
|
+
}
|
|
3898
|
+
|
|
3899
|
+
pval := GetProp(parent, key)
|
|
3900
|
+
ptype := Typify(pval)
|
|
3901
|
+
|
|
3902
|
+
// Delete any special commands remaining.
|
|
3903
|
+
if 0 < (T_string & ptype) && pval != nil {
|
|
3904
|
+
if strVal, ok := pval.(string); ok && strings.Contains(strVal, S_DS) {
|
|
3905
|
+
return
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3909
|
+
ctype := Typify(cval)
|
|
3910
|
+
|
|
3911
|
+
// Type mismatch.
|
|
3912
|
+
if ptype != ctype && pval != nil {
|
|
3913
|
+
inj.Errs.Append(_invalidTypeMsg(inj.Path.List, Typename(ptype), Typename(ctype), cval))
|
|
3914
|
+
return
|
|
3915
|
+
}
|
|
3916
|
+
|
|
3917
|
+
if IsMap(cval) {
|
|
3918
|
+
if !IsMap(val) {
|
|
3919
|
+
var errType string
|
|
3920
|
+
if IsList(val) {
|
|
3921
|
+
errType = S_array
|
|
3922
|
+
} else {
|
|
3923
|
+
errType = Typename(ptype)
|
|
3924
|
+
}
|
|
3925
|
+
inj.Errs.Append(_invalidTypeMsg(inj.Path.List, errType, Typename(ctype), cval))
|
|
3926
|
+
return
|
|
3927
|
+
}
|
|
3928
|
+
|
|
3929
|
+
ckeys := KeysOf(cval)
|
|
3930
|
+
pkeys := KeysOf(pval)
|
|
3931
|
+
|
|
3932
|
+
// Empty spec object {} means object can be open (any keys).
|
|
3933
|
+
if len(pkeys) > 0 && GetProp(pval, "`$OPEN`") != true {
|
|
3934
|
+
badkeys := []string{}
|
|
3935
|
+
for _, ckey := range ckeys {
|
|
3936
|
+
if !HasKey(val, ckey) {
|
|
3937
|
+
badkeys = append(badkeys, ckey)
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
|
|
3941
|
+
// Closed object, so reject extra keys not in shape.
|
|
3942
|
+
if len(badkeys) > 0 {
|
|
3943
|
+
inj.Errs.Append("Unexpected keys at field " + Pathify(inj.Path.List, 1) +
|
|
3944
|
+
": " + strings.Join(badkeys, ", "))
|
|
3945
|
+
}
|
|
3946
|
+
} else {
|
|
3947
|
+
// Object is open, so merge in extra keys.
|
|
3948
|
+
Merge([]any{pval, cval})
|
|
3949
|
+
if IsNode(pval) {
|
|
3950
|
+
DelProp(pval, "`$OPEN`")
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
} else if IsList(cval) {
|
|
3954
|
+
if !IsList(val) {
|
|
3955
|
+
inj.Errs.Append(_invalidTypeMsg(inj.Path.List, Typename(ptype), Typename(ctype), cval))
|
|
3956
|
+
}
|
|
3957
|
+
} else if exact {
|
|
3958
|
+
// Select needs exact matches for scalar values.
|
|
3959
|
+
if cval != pval {
|
|
3960
|
+
pathmsg := ""
|
|
3961
|
+
if len(inj.Path.List) > 1 {
|
|
3962
|
+
pathmsg = "at field " + Pathify(inj.Path.List, 1) + ": "
|
|
3963
|
+
}
|
|
3964
|
+
inj.Errs.Append("Value " + pathmsg + fmt.Sprintf("%v", cval) +
|
|
3965
|
+
" should equal " + fmt.Sprintf("%v", pval) + ".")
|
|
3966
|
+
} else if pval == nil {
|
|
3967
|
+
// Both nil: in Go, nil represents both null and undefined.
|
|
3968
|
+
// For exact matching (Select), verify the key actually exists in the data.
|
|
3969
|
+
keyExists := false
|
|
3970
|
+
if m, ok := inj.Dparent.(map[string]any); ok {
|
|
3971
|
+
if keyStr, ok := key.(string); ok {
|
|
3972
|
+
_, keyExists = m[keyStr]
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
3975
|
+
if !keyExists {
|
|
3976
|
+
pathmsg := ""
|
|
3977
|
+
if len(inj.Path.List) > 1 {
|
|
3978
|
+
pathmsg = "at field " + Pathify(inj.Path.List, 1) + ": "
|
|
3979
|
+
}
|
|
3980
|
+
inj.Errs.Append("Value " + pathmsg + "undefined" +
|
|
3981
|
+
" should equal " + fmt.Sprintf("%v", pval) + ".")
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
} else {
|
|
3985
|
+
// Spec value was a default, copy over data
|
|
3986
|
+
SetProp(parent, key, cval)
|
|
3987
|
+
}
|
|
3988
|
+
|
|
3989
|
+
return
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
|
|
3993
|
+
// Default validation modify (non-exact mode).
|
|
3994
|
+
var validation Modify = makeValidation(false)
|
|
3995
|
+
|
|
3996
|
+
// _validatehandler processes meta path operators in validation.
|
|
3997
|
+
var _validatehandler Injector = func(
|
|
3998
|
+
inj *Injection,
|
|
3999
|
+
val any,
|
|
4000
|
+
ref *string,
|
|
4001
|
+
store any,
|
|
4002
|
+
) any {
|
|
4003
|
+
out := val
|
|
4004
|
+
|
|
4005
|
+
refStr := ""
|
|
4006
|
+
if ref != nil {
|
|
4007
|
+
refStr = *ref
|
|
4008
|
+
}
|
|
4009
|
+
m := reMetaPath.FindStringSubmatch(refStr)
|
|
4010
|
+
ismetapath := m != nil
|
|
4011
|
+
|
|
4012
|
+
if ismetapath {
|
|
4013
|
+
if m[2] == "=" {
|
|
4014
|
+
inj.setval([]any{S_BEXACT, val})
|
|
4015
|
+
} else {
|
|
4016
|
+
inj.setval(val)
|
|
4017
|
+
}
|
|
4018
|
+
inj.KeyI = -1
|
|
4019
|
+
out = SKIP
|
|
4020
|
+
} else {
|
|
4021
|
+
out = injectHandler(inj, val, ref, store)
|
|
4022
|
+
}
|
|
4023
|
+
|
|
4024
|
+
return out
|
|
4025
|
+
}
|
|
4026
|
+
|
|
4027
|
+
func Validate(
|
|
4028
|
+
data any, // The input data
|
|
4029
|
+
spec any, // The shape specification
|
|
4030
|
+
injdefs ...*Injection,
|
|
4031
|
+
) (any, error) {
|
|
4032
|
+
var extra map[string]any
|
|
4033
|
+
var collecterrs *ListRef[any]
|
|
4034
|
+
if len(injdefs) > 0 && injdefs[0] != nil {
|
|
4035
|
+
if e, ok := injdefs[0].Extra.(map[string]any); ok {
|
|
4036
|
+
extra = e
|
|
4037
|
+
}
|
|
4038
|
+
collecterrs = injdefs[0].Errs
|
|
4039
|
+
}
|
|
4040
|
+
|
|
4041
|
+
// Use the provided error collection or create a new one
|
|
4042
|
+
errs := collecterrs
|
|
4043
|
+
if nil == errs {
|
|
4044
|
+
errs = ListRefCreate[any]()
|
|
4045
|
+
}
|
|
4046
|
+
|
|
4047
|
+
|
|
4048
|
+
// Initialize validate_ONE if not already initialized.
|
|
4049
|
+
// This avoids a circular reference error, validate_ONE calls Validate.
|
|
4050
|
+
if validate_ONE == nil {
|
|
4051
|
+
init_validate_ONE()
|
|
4052
|
+
}
|
|
4053
|
+
|
|
4054
|
+
// Initialize validate_EXACT if not already initialized.
|
|
4055
|
+
if validate_EXACT == nil {
|
|
4056
|
+
init_validate_EXACT()
|
|
4057
|
+
}
|
|
4058
|
+
|
|
4059
|
+
// Create the store with validation commands
|
|
4060
|
+
store := map[string]any{
|
|
4061
|
+
// Remove the transform commands
|
|
4062
|
+
"$DELETE": nil,
|
|
4063
|
+
"$COPY": nil,
|
|
4064
|
+
"$KEY": nil,
|
|
4065
|
+
"$META": nil,
|
|
4066
|
+
"$MERGE": nil,
|
|
4067
|
+
"$EACH": nil,
|
|
4068
|
+
"$PACK": nil,
|
|
4069
|
+
"$BT": nil,
|
|
4070
|
+
"$DS": nil,
|
|
4071
|
+
"$WHEN": nil,
|
|
4072
|
+
|
|
4073
|
+
// Add validation commands
|
|
4074
|
+
"$STRING": validate_STRING,
|
|
4075
|
+
"$NUMBER": validate_TYPE,
|
|
4076
|
+
"$INTEGER": validate_TYPE,
|
|
4077
|
+
"$DECIMAL": validate_TYPE,
|
|
4078
|
+
"$BOOLEAN": validate_TYPE,
|
|
4079
|
+
"$NULL": validate_TYPE,
|
|
4080
|
+
"$NIL": validate_TYPE,
|
|
4081
|
+
"$MAP": validate_TYPE,
|
|
4082
|
+
"$LIST": validate_TYPE,
|
|
4083
|
+
"$FUNCTION": validate_TYPE,
|
|
4084
|
+
"$INSTANCE": validate_TYPE,
|
|
4085
|
+
"$OBJECT": validate_OBJECT,
|
|
4086
|
+
"$ARRAY": validate_ARRAY,
|
|
4087
|
+
"$ANY": validate_ANY,
|
|
4088
|
+
"$CHILD": validate_CHILD,
|
|
4089
|
+
"$ONE": validate_ONE,
|
|
4090
|
+
"$EXACT": validate_EXACT,
|
|
4091
|
+
}
|
|
4092
|
+
|
|
4093
|
+
// Add any extra validation commands
|
|
4094
|
+
if extra != nil {
|
|
4095
|
+
for k, fn := range extra {
|
|
4096
|
+
store[k] = fn
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
4099
|
+
|
|
4100
|
+
// A special top level value to collect errors
|
|
4101
|
+
store["$ERRS"] = errs
|
|
4102
|
+
|
|
4103
|
+
// Set up meta with exact mode.
|
|
4104
|
+
meta := map[string]any{}
|
|
4105
|
+
if extra != nil {
|
|
4106
|
+
if metaVal, ok := extra["meta"]; ok {
|
|
4107
|
+
if metaMap, ok := metaVal.(map[string]any); ok {
|
|
4108
|
+
meta = metaMap
|
|
4109
|
+
}
|
|
4110
|
+
delete(store, "meta")
|
|
4111
|
+
}
|
|
4112
|
+
}
|
|
4113
|
+
if _, ok := meta[S_BEXACT]; !ok {
|
|
4114
|
+
meta[S_BEXACT] = false
|
|
4115
|
+
}
|
|
4116
|
+
|
|
4117
|
+
// Check if exact mode is requested via meta.
|
|
4118
|
+
validationFn := validation
|
|
4119
|
+
exactVal, _ := meta[S_BEXACT].(bool)
|
|
4120
|
+
if exactVal {
|
|
4121
|
+
validationFn = makeValidation(true)
|
|
4122
|
+
}
|
|
4123
|
+
|
|
4124
|
+
// Run the transformation with validation and _validatehandler
|
|
4125
|
+
out := TransformModifyHandler(data, spec, store, validationFn, _validatehandler, errs, meta)
|
|
4126
|
+
|
|
4127
|
+
// Generate an error if we collected any errors and the caller didn't provide
|
|
4128
|
+
// their own error collection
|
|
4129
|
+
var err error
|
|
4130
|
+
generr := 0 < len(errs.List) && collecterrs == nil
|
|
4131
|
+
if generr {
|
|
4132
|
+
// Join error messages
|
|
4133
|
+
errmsgs := make([]string, len(errs.List))
|
|
4134
|
+
for i, e := range errs.List {
|
|
4135
|
+
if s, ok := e.(string); ok {
|
|
4136
|
+
errmsgs[i] = s
|
|
4137
|
+
} else {
|
|
4138
|
+
errmsgs[i] = fmt.Sprintf("%v", e)
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
err = fmt.Errorf("Invalid data: %s", strings.Join(errmsgs, " | "))
|
|
4142
|
+
}
|
|
4143
|
+
|
|
4144
|
+
return out, err
|
|
4145
|
+
}
|
|
4146
|
+
|
|
4147
|
+
|
|
4148
|
+
// Mode names for injection modes.
|
|
4149
|
+
var MODENAME = map[int]string{
|
|
4150
|
+
M_VAL: "val",
|
|
4151
|
+
M_KEYPRE: "key:pre",
|
|
4152
|
+
M_KEYPOST: "key:post",
|
|
4153
|
+
}
|
|
4154
|
+
|
|
4155
|
+
// Placement names for injection modes.
|
|
4156
|
+
var PLACEMENT = map[int]string{
|
|
4157
|
+
M_VAL: "value",
|
|
4158
|
+
M_KEYPRE: S_key,
|
|
4159
|
+
M_KEYPOST: S_key,
|
|
4160
|
+
}
|
|
4161
|
+
|
|
4162
|
+
// Validate that an injector is placed in a valid mode and parent type.
|
|
4163
|
+
func CheckPlacement(modes int, ijname string, parentTypes int, inj *Injection) bool {
|
|
4164
|
+
if 0 == (modes & inj.Mode) {
|
|
4165
|
+
allModes := []int{M_KEYPRE, M_KEYPOST, M_VAL}
|
|
4166
|
+
expected := []string{}
|
|
4167
|
+
for _, m := range allModes {
|
|
4168
|
+
if 0 != (modes & m) {
|
|
4169
|
+
expected = append(expected, PLACEMENT[m])
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
4172
|
+
inj.Errs.Append("$" + ijname + ": invalid placement as " + PLACEMENT[inj.Mode] +
|
|
4173
|
+
", expected: " + strings.Join(expected, ",") + ".")
|
|
4174
|
+
return false
|
|
4175
|
+
}
|
|
4176
|
+
if !IsEmpty(parentTypes) {
|
|
4177
|
+
ptype := Typify(inj.Parent)
|
|
4178
|
+
if 0 == (parentTypes & ptype) {
|
|
4179
|
+
inj.Errs.Append("$" + ijname + ": invalid placement in parent " + Typename(ptype) +
|
|
4180
|
+
", expected: " + Typename(parentTypes) + ".")
|
|
4181
|
+
return false
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
4184
|
+
return true
|
|
4185
|
+
}
|
|
4186
|
+
|
|
4187
|
+
// Validate and extract injector arguments against expected type bitmasks.
|
|
4188
|
+
// Returns a slice where [0] is nil on success or an error string on failure,
|
|
4189
|
+
// and [1..N] are the validated arguments.
|
|
4190
|
+
func InjectorArgs(argTypes []int, args []any) []any {
|
|
4191
|
+
numargs := len(argTypes)
|
|
4192
|
+
found := make([]any, 1+numargs)
|
|
4193
|
+
found[0] = nil
|
|
4194
|
+
for argI := 0; argI < numargs; argI++ {
|
|
4195
|
+
arg := args[argI]
|
|
4196
|
+
argType := Typify(arg)
|
|
4197
|
+
if 0 == (argTypes[argI] & argType) {
|
|
4198
|
+
found[0] = "invalid argument: " + Stringify(arg, 22) +
|
|
4199
|
+
" (" + Typename(argType) + " at position " + strconv.Itoa(1+argI) +
|
|
4200
|
+
") is not of type: " + Typename(argTypes[argI]) + "."
|
|
4201
|
+
break
|
|
4202
|
+
}
|
|
4203
|
+
found[1+argI] = arg
|
|
4204
|
+
}
|
|
4205
|
+
return found
|
|
4206
|
+
}
|
|
4207
|
+
|
|
4208
|
+
|
|
4209
|
+
// Select helpers - internal injectors for query matching.
|
|
4210
|
+
|
|
4211
|
+
var select_AND Injector = func(
|
|
4212
|
+
inj *Injection,
|
|
4213
|
+
val any,
|
|
4214
|
+
ref *string,
|
|
4215
|
+
store any,
|
|
4216
|
+
) any {
|
|
4217
|
+
if M_KEYPRE == inj.Mode {
|
|
4218
|
+
terms := GetProp(inj.Parent, inj.Key)
|
|
4219
|
+
|
|
4220
|
+
pathList := inj.Path.List
|
|
4221
|
+
ppath := pathList[:len(pathList)-1]
|
|
4222
|
+
point := GetPath(ppath, store)
|
|
4223
|
+
|
|
4224
|
+
vstore := Merge([]any{map[string]any{}, store})
|
|
4225
|
+
SetProp(vstore, S_DTOP, point)
|
|
4226
|
+
|
|
4227
|
+
termList, _ := _asList(terms)
|
|
4228
|
+
for _, term := range termList {
|
|
4229
|
+
terrs := ListRefCreate[any]()
|
|
4230
|
+
vstoreMap, _ := vstore.(map[string]any)
|
|
4231
|
+
validateCollectExact(point, term, vstoreMap, terrs)
|
|
4232
|
+
if 0 != len(terrs.List) {
|
|
4233
|
+
inj.Errs.Append("AND:" + Pathify(ppath) + S_VIZ +
|
|
4234
|
+
Stringify(point) + " fail:" + Stringify(terms))
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
|
|
4238
|
+
if len(pathList) >= 2 {
|
|
4239
|
+
gkey := pathList[len(pathList)-2]
|
|
4240
|
+
gp := inj.Nodes.List[len(inj.Nodes.List)-2]
|
|
4241
|
+
SetProp(gp, gkey, point)
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
return nil
|
|
4245
|
+
}
|
|
4246
|
+
|
|
4247
|
+
var select_OR Injector = func(
|
|
4248
|
+
inj *Injection,
|
|
4249
|
+
val any,
|
|
4250
|
+
ref *string,
|
|
4251
|
+
store any,
|
|
4252
|
+
) any {
|
|
4253
|
+
if M_KEYPRE == inj.Mode {
|
|
4254
|
+
terms := GetProp(inj.Parent, inj.Key)
|
|
4255
|
+
|
|
4256
|
+
pathList := inj.Path.List
|
|
4257
|
+
ppath := pathList[:len(pathList)-1]
|
|
4258
|
+
point := GetPath(ppath, store)
|
|
4259
|
+
|
|
4260
|
+
vstore := Merge([]any{map[string]any{}, store})
|
|
4261
|
+
SetProp(vstore, S_DTOP, point)
|
|
4262
|
+
|
|
4263
|
+
termList, _ := _asList(terms)
|
|
4264
|
+
for _, term := range termList {
|
|
4265
|
+
terrs := ListRefCreate[any]()
|
|
4266
|
+
vstoreMap, _ := vstore.(map[string]any)
|
|
4267
|
+
validateCollectExact(point, term, vstoreMap, terrs)
|
|
4268
|
+
if 0 == len(terrs.List) {
|
|
4269
|
+
if len(pathList) >= 2 {
|
|
4270
|
+
gkey := pathList[len(pathList)-2]
|
|
4271
|
+
gp := inj.Nodes.List[len(inj.Nodes.List)-2]
|
|
4272
|
+
SetProp(gp, gkey, point)
|
|
4273
|
+
}
|
|
4274
|
+
return nil
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
|
|
4278
|
+
inj.Errs.Append("OR:" + Pathify(ppath) + S_VIZ +
|
|
4279
|
+
Stringify(point) + " fail:" + Stringify(terms))
|
|
4280
|
+
}
|
|
4281
|
+
return nil
|
|
4282
|
+
}
|
|
4283
|
+
|
|
4284
|
+
var select_NOT Injector = func(
|
|
4285
|
+
inj *Injection,
|
|
4286
|
+
val any,
|
|
4287
|
+
ref *string,
|
|
4288
|
+
store any,
|
|
4289
|
+
) any {
|
|
4290
|
+
if M_KEYPRE == inj.Mode {
|
|
4291
|
+
term := GetProp(inj.Parent, inj.Key)
|
|
4292
|
+
|
|
4293
|
+
pathList := inj.Path.List
|
|
4294
|
+
ppath := pathList[:len(pathList)-1]
|
|
4295
|
+
point := GetPath(ppath, store)
|
|
4296
|
+
|
|
4297
|
+
vstore := Merge([]any{map[string]any{}, store})
|
|
4298
|
+
SetProp(vstore, S_DTOP, point)
|
|
4299
|
+
|
|
4300
|
+
terrs := ListRefCreate[any]()
|
|
4301
|
+
vstoreMap, _ := vstore.(map[string]any)
|
|
4302
|
+
validateCollectExact(point, term, vstoreMap, terrs)
|
|
4303
|
+
|
|
4304
|
+
if 0 == len(terrs.List) {
|
|
4305
|
+
inj.Errs.Append("NOT:" + Pathify(ppath) + S_VIZ +
|
|
4306
|
+
Stringify(point) + " fail:" + Stringify(term))
|
|
4307
|
+
}
|
|
4308
|
+
|
|
4309
|
+
if len(pathList) >= 2 {
|
|
4310
|
+
gkey := pathList[len(pathList)-2]
|
|
4311
|
+
gp := inj.Nodes.List[len(inj.Nodes.List)-2]
|
|
4312
|
+
SetProp(gp, gkey, point)
|
|
4313
|
+
}
|
|
4314
|
+
}
|
|
4315
|
+
return nil
|
|
4316
|
+
}
|
|
4317
|
+
|
|
4318
|
+
var select_CMP Injector = func(
|
|
4319
|
+
inj *Injection,
|
|
4320
|
+
val any,
|
|
4321
|
+
ref *string,
|
|
4322
|
+
store any,
|
|
4323
|
+
) any {
|
|
4324
|
+
if M_KEYPRE == inj.Mode {
|
|
4325
|
+
term := GetProp(inj.Parent, inj.Key)
|
|
4326
|
+
|
|
4327
|
+
pathList := inj.Path.List
|
|
4328
|
+
ppath := pathList[:len(pathList)-1]
|
|
4329
|
+
point := GetPath(ppath, store)
|
|
4330
|
+
|
|
4331
|
+
pass := false
|
|
4332
|
+
refStr := ""
|
|
4333
|
+
if ref != nil {
|
|
4334
|
+
refStr = *ref
|
|
4335
|
+
}
|
|
4336
|
+
|
|
4337
|
+
pf, pErr := _toFloat64(point)
|
|
4338
|
+
tf, tErr := _toFloat64(term)
|
|
4339
|
+
|
|
4340
|
+
switch refStr {
|
|
4341
|
+
case "$GT":
|
|
4342
|
+
if pErr == nil && tErr == nil {
|
|
4343
|
+
pass = pf > tf
|
|
4344
|
+
}
|
|
4345
|
+
case "$LT":
|
|
4346
|
+
if pErr == nil && tErr == nil {
|
|
4347
|
+
pass = pf < tf
|
|
4348
|
+
}
|
|
4349
|
+
case "$GTE":
|
|
4350
|
+
if pErr == nil && tErr == nil {
|
|
4351
|
+
pass = pf >= tf
|
|
4352
|
+
}
|
|
4353
|
+
case "$LTE":
|
|
4354
|
+
if pErr == nil && tErr == nil {
|
|
4355
|
+
pass = pf <= tf
|
|
4356
|
+
}
|
|
4357
|
+
case "$LIKE":
|
|
4358
|
+
if ts, ok := term.(string); ok {
|
|
4359
|
+
re, err := regexp.Compile(ts)
|
|
4360
|
+
if err == nil {
|
|
4361
|
+
pass = re.MatchString(Stringify(point))
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
|
|
4366
|
+
if pass {
|
|
4367
|
+
if len(pathList) >= 2 {
|
|
4368
|
+
gkey := pathList[len(pathList)-2]
|
|
4369
|
+
gp := inj.Nodes.List[len(inj.Nodes.List)-2]
|
|
4370
|
+
SetProp(gp, gkey, point)
|
|
4371
|
+
}
|
|
4372
|
+
} else {
|
|
4373
|
+
inj.Errs.Append("CMP: " + Pathify(ppath) + S_VIZ +
|
|
4374
|
+
Stringify(point) + " fail:" + refStr + " " + Stringify(term))
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
return nil
|
|
4378
|
+
}
|
|
4379
|
+
|
|
4380
|
+
|
|
4381
|
+
// Internal exact-mode validation for Select.
|
|
4382
|
+
// Like Validate but uses exact scalar comparison.
|
|
4383
|
+
func validateCollectExact(
|
|
4384
|
+
data any,
|
|
4385
|
+
spec any,
|
|
4386
|
+
extra map[string]any,
|
|
4387
|
+
collecterrs *ListRef[any],
|
|
4388
|
+
) {
|
|
4389
|
+
errs := collecterrs
|
|
4390
|
+
if nil == errs {
|
|
4391
|
+
errs = ListRefCreate[any]()
|
|
4392
|
+
}
|
|
4393
|
+
|
|
4394
|
+
if validate_ONE == nil {
|
|
4395
|
+
init_validate_ONE()
|
|
4396
|
+
}
|
|
4397
|
+
if validate_EXACT == nil {
|
|
4398
|
+
init_validate_EXACT()
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4401
|
+
store := map[string]any{
|
|
4402
|
+
"$DELETE": nil,
|
|
4403
|
+
"$COPY": nil,
|
|
4404
|
+
"$KEY": nil,
|
|
4405
|
+
"$META": nil,
|
|
4406
|
+
"$MERGE": nil,
|
|
4407
|
+
"$EACH": nil,
|
|
4408
|
+
"$PACK": nil,
|
|
4409
|
+
"$BT": nil,
|
|
4410
|
+
"$DS": nil,
|
|
4411
|
+
"$WHEN": nil,
|
|
4412
|
+
|
|
4413
|
+
"$STRING": validate_STRING,
|
|
4414
|
+
"$NUMBER": validate_TYPE,
|
|
4415
|
+
"$INTEGER": validate_TYPE,
|
|
4416
|
+
"$DECIMAL": validate_TYPE,
|
|
4417
|
+
"$BOOLEAN": validate_TYPE,
|
|
4418
|
+
"$NULL": validate_TYPE,
|
|
4419
|
+
"$NIL": validate_TYPE,
|
|
4420
|
+
"$MAP": validate_TYPE,
|
|
4421
|
+
"$LIST": validate_TYPE,
|
|
4422
|
+
"$FUNCTION": validate_TYPE,
|
|
4423
|
+
"$INSTANCE": validate_TYPE,
|
|
4424
|
+
"$OBJECT": validate_OBJECT,
|
|
4425
|
+
"$ARRAY": validate_ARRAY,
|
|
4426
|
+
"$ANY": validate_ANY,
|
|
4427
|
+
"$CHILD": validate_CHILD,
|
|
4428
|
+
"$ONE": validate_ONE,
|
|
4429
|
+
"$EXACT": validate_EXACT,
|
|
4430
|
+
}
|
|
4431
|
+
|
|
4432
|
+
if extra != nil {
|
|
4433
|
+
for k, fn := range extra {
|
|
4434
|
+
store[k] = fn
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
|
|
4438
|
+
store["$ERRS"] = errs
|
|
4439
|
+
|
|
4440
|
+
meta := map[string]any{S_BEXACT: true}
|
|
4441
|
+
TransformModifyHandler(data, spec, store, makeValidation(true), _validatehandler, errs, meta)
|
|
4442
|
+
}
|
|
4443
|
+
|
|
4444
|
+
|
|
4445
|
+
// Select children from a node that match a query.
|
|
4446
|
+
// Uses validate internally with query operators ($AND, $OR, $NOT,
|
|
4447
|
+
// $GT, $LT, $GTE, $LTE, $LIKE).
|
|
4448
|
+
// For maps, children are values (tagged with $KEY). For lists, children are elements.
|
|
4449
|
+
func Select(children any, query any) []any {
|
|
4450
|
+
if !IsNode(children) {
|
|
4451
|
+
return []any{}
|
|
4452
|
+
}
|
|
4453
|
+
|
|
4454
|
+
var childList []any
|
|
4455
|
+
|
|
4456
|
+
if IsMap(children) {
|
|
4457
|
+
pairs := Items(children)
|
|
4458
|
+
childList = make([]any, len(pairs))
|
|
4459
|
+
for i, pair := range pairs {
|
|
4460
|
+
child := pair[1]
|
|
4461
|
+
if IsMap(child) {
|
|
4462
|
+
SetProp(child, "$KEY", pair[0])
|
|
4463
|
+
}
|
|
4464
|
+
childList[i] = child
|
|
4465
|
+
}
|
|
4466
|
+
} else {
|
|
4467
|
+
list, _ := _asList(children)
|
|
4468
|
+
if list == nil {
|
|
4469
|
+
list = _listify(children)
|
|
4470
|
+
}
|
|
4471
|
+
childList = make([]any, len(list))
|
|
4472
|
+
for i, child := range list {
|
|
4473
|
+
if IsMap(child) {
|
|
4474
|
+
SetProp(child, "$KEY", i)
|
|
4475
|
+
}
|
|
4476
|
+
childList[i] = child
|
|
4477
|
+
}
|
|
4478
|
+
}
|
|
4479
|
+
|
|
4480
|
+
results := []any{}
|
|
4481
|
+
extra := map[string]any{
|
|
4482
|
+
"$AND": select_AND,
|
|
4483
|
+
"$OR": select_OR,
|
|
4484
|
+
"$NOT": select_NOT,
|
|
4485
|
+
"$GT": select_CMP,
|
|
4486
|
+
"$LT": select_CMP,
|
|
4487
|
+
"$GTE": select_CMP,
|
|
4488
|
+
"$LTE": select_CMP,
|
|
4489
|
+
"$LIKE": select_CMP,
|
|
4490
|
+
}
|
|
4491
|
+
|
|
4492
|
+
q := Clone(query)
|
|
4493
|
+
|
|
4494
|
+
// Mark all map nodes as open so extra keys don't fail validation.
|
|
4495
|
+
Walk(q, func(key *string, v any, parent any, path []string) any {
|
|
4496
|
+
if IsMap(v) {
|
|
4497
|
+
m := v.(map[string]any)
|
|
4498
|
+
if _, has := m[S_BOPEN]; !has {
|
|
4499
|
+
m[S_BOPEN] = true
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4502
|
+
return v
|
|
4503
|
+
})
|
|
4504
|
+
|
|
4505
|
+
for _, child := range childList {
|
|
4506
|
+
errs := ListRefCreate[any]()
|
|
4507
|
+
validateCollectExact(child, Clone(q), extra, errs)
|
|
4508
|
+
if 0 == len(errs.List) {
|
|
4509
|
+
results = append(results, child)
|
|
4510
|
+
}
|
|
4511
|
+
}
|
|
4512
|
+
|
|
4513
|
+
return results
|
|
4514
|
+
}
|
|
4515
|
+
|
|
4516
|
+
|
|
4517
|
+
// Internal utilities
|
|
4518
|
+
// ==================
|
|
4519
|
+
|
|
4520
|
+
type ListRef[T any] struct {
|
|
4521
|
+
List []T
|
|
4522
|
+
}
|
|
4523
|
+
|
|
4524
|
+
func ListRefCreate[T any]() *ListRef[T] {
|
|
4525
|
+
return &ListRef[T]{
|
|
4526
|
+
List: make([]T, 0),
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
|
|
4530
|
+
|
|
4531
|
+
func (l *ListRef[T]) Append(elem T) {
|
|
4532
|
+
l.List = append(l.List, elem)
|
|
4533
|
+
}
|
|
4534
|
+
|
|
4535
|
+
|
|
4536
|
+
func (l *ListRef[T]) Prepend(elem T) {
|
|
4537
|
+
l.List = append([]T{elem}, l.List...)
|
|
4538
|
+
}
|
|
4539
|
+
|
|
4540
|
+
func _join(vals []any, sep string) string {
|
|
4541
|
+
strVals := make([]string, len(vals))
|
|
4542
|
+
for i, v := range vals {
|
|
4543
|
+
strVals[i] = fmt.Sprint(v)
|
|
4544
|
+
}
|
|
4545
|
+
return strings.Join(strVals, sep)
|
|
4546
|
+
}
|
|
4547
|
+
|
|
4548
|
+
|
|
4549
|
+
func _invalidTypeMsg(path []string, needtype string, vt string, v any, whence ...string) string {
|
|
4550
|
+
vs := "no value"
|
|
4551
|
+
if v != nil {
|
|
4552
|
+
vs = Stringify(v)
|
|
4553
|
+
}
|
|
4554
|
+
|
|
4555
|
+
fieldPart := ""
|
|
4556
|
+
if len(path) > 1 {
|
|
4557
|
+
fieldPart = "field " + Pathify(path, 1) + " to be "
|
|
4558
|
+
}
|
|
4559
|
+
|
|
4560
|
+
typePart := ""
|
|
4561
|
+
if v != nil {
|
|
4562
|
+
typePart = vt + ": "
|
|
4563
|
+
}
|
|
4564
|
+
|
|
4565
|
+
// Build the main error message
|
|
4566
|
+
message := "Expected " + fieldPart + needtype + ", but found " + typePart + vs
|
|
4567
|
+
|
|
4568
|
+
// Uncomment to help debug validation errors
|
|
4569
|
+
// if len(whence) > 0 {
|
|
4570
|
+
// message += " [" + whence[0] + "]"
|
|
4571
|
+
// }
|
|
4572
|
+
|
|
4573
|
+
return message + "."
|
|
4574
|
+
}
|
|
4575
|
+
|
|
4576
|
+
func _getType(v any) string {
|
|
4577
|
+
if nil == v {
|
|
4578
|
+
return "nil"
|
|
4579
|
+
}
|
|
4580
|
+
return reflect.TypeOf(v).String()
|
|
4581
|
+
}
|
|
4582
|
+
|
|
4583
|
+
|
|
4584
|
+
// StrKey converts different types of keys to string representation.
|
|
4585
|
+
// String keys are returned as is.
|
|
4586
|
+
// Number keys are converted to strings.
|
|
4587
|
+
// Floats are truncated to integers.
|
|
4588
|
+
// Booleans, objects, arrays, null, undefined all return empty string.
|
|
4589
|
+
|
|
4590
|
+
// TODO: rename to _strKey
|
|
4591
|
+
func StrKey(key any) string {
|
|
4592
|
+
if nil == key {
|
|
4593
|
+
return S_MT
|
|
4594
|
+
}
|
|
4595
|
+
|
|
4596
|
+
switch v := key.(type) {
|
|
4597
|
+
case string:
|
|
4598
|
+
return v
|
|
4599
|
+
case *string:
|
|
4600
|
+
if nil != v {
|
|
4601
|
+
return *v
|
|
4602
|
+
}
|
|
4603
|
+
return S_MT
|
|
4604
|
+
case int:
|
|
4605
|
+
return strconv.Itoa(v)
|
|
4606
|
+
case int64:
|
|
4607
|
+
return strconv.FormatInt(v, 10)
|
|
4608
|
+
case int32:
|
|
4609
|
+
return strconv.FormatInt(int64(v), 10)
|
|
4610
|
+
case float64:
|
|
4611
|
+
return strconv.FormatInt(int64(v), 10)
|
|
4612
|
+
case float32:
|
|
4613
|
+
return strconv.FormatInt(int64(v), 10)
|
|
4614
|
+
case bool:
|
|
4615
|
+
return S_MT
|
|
4616
|
+
default:
|
|
4617
|
+
return S_MT
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
|
|
4621
|
+
|
|
4622
|
+
func _resolveStrings(input []any) []string {
|
|
4623
|
+
var result []string
|
|
4624
|
+
|
|
4625
|
+
for _, v := range input {
|
|
4626
|
+
if str, ok := v.(string); ok {
|
|
4627
|
+
result = append(result, str)
|
|
4628
|
+
} else {
|
|
4629
|
+
result = append(result, StrKey(v))
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4632
|
+
|
|
4633
|
+
return result
|
|
4634
|
+
}
|
|
4635
|
+
|
|
4636
|
+
|
|
4637
|
+
// Extract a bare []any from either a []any or a *ListRef[any].
|
|
4638
|
+
// Recursively unwrap *ListRef[any] to []any for JSON marshaling.
|
|
4639
|
+
func _unwrapListRefs(val any) any {
|
|
4640
|
+
return _unwrapListRefsD(val, 0)
|
|
4641
|
+
}
|
|
4642
|
+
|
|
4643
|
+
func _unwrapListRefsD(val any, depth int) any {
|
|
4644
|
+
if depth > 32 {
|
|
4645
|
+
return val
|
|
4646
|
+
}
|
|
4647
|
+
if lr, ok := val.(*ListRef[any]); ok {
|
|
4648
|
+
out := make([]any, len(lr.List))
|
|
4649
|
+
for i, v := range lr.List {
|
|
4650
|
+
out[i] = _unwrapListRefsD(v, depth+1)
|
|
4651
|
+
}
|
|
4652
|
+
return out
|
|
4653
|
+
}
|
|
4654
|
+
if m, ok := val.(map[string]any); ok {
|
|
4655
|
+
out := make(map[string]any, len(m))
|
|
4656
|
+
for k, v := range m {
|
|
4657
|
+
out[k] = _unwrapListRefsD(v, depth+1)
|
|
4658
|
+
}
|
|
4659
|
+
return out
|
|
4660
|
+
}
|
|
4661
|
+
if list, ok := val.([]any); ok {
|
|
4662
|
+
out := make([]any, len(list))
|
|
4663
|
+
for i, v := range list {
|
|
4664
|
+
out[i] = _unwrapListRefsD(v, depth+1)
|
|
4665
|
+
}
|
|
4666
|
+
return out
|
|
4667
|
+
}
|
|
4668
|
+
return val
|
|
4669
|
+
}
|
|
4670
|
+
|
|
4671
|
+
func _asList(val any) ([]any, bool) {
|
|
4672
|
+
if lr, ok := val.(*ListRef[any]); ok {
|
|
4673
|
+
return lr.List, true
|
|
4674
|
+
}
|
|
4675
|
+
if list, ok := val.([]any); ok {
|
|
4676
|
+
return list, true
|
|
4677
|
+
}
|
|
4678
|
+
return nil, false
|
|
4679
|
+
}
|
|
4680
|
+
|
|
4681
|
+
|
|
4682
|
+
func _listify(src any) []any {
|
|
4683
|
+
if lr, ok := src.(*ListRef[any]); ok {
|
|
4684
|
+
return lr.List
|
|
4685
|
+
}
|
|
4686
|
+
|
|
4687
|
+
if list, ok := src.([]any); ok {
|
|
4688
|
+
return list
|
|
4689
|
+
}
|
|
4690
|
+
|
|
4691
|
+
if src == nil {
|
|
4692
|
+
return nil
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4695
|
+
val := reflect.ValueOf(src)
|
|
4696
|
+
if val.Kind() == reflect.Slice {
|
|
4697
|
+
length := val.Len()
|
|
4698
|
+
result := make([]any, length)
|
|
4699
|
+
|
|
4700
|
+
for i := 0; i < length; i++ {
|
|
4701
|
+
result[i] = val.Index(i).Interface()
|
|
4702
|
+
}
|
|
4703
|
+
return result
|
|
4704
|
+
}
|
|
4705
|
+
|
|
4706
|
+
return nil
|
|
4707
|
+
}
|
|
4708
|
+
|
|
4709
|
+
|
|
4710
|
+
// toFloat64 helps unify numeric types for floor conversion.
|
|
4711
|
+
func _toFloat64(val any) (float64, error) {
|
|
4712
|
+
switch n := val.(type) {
|
|
4713
|
+
case float64:
|
|
4714
|
+
return n, nil
|
|
4715
|
+
case float32:
|
|
4716
|
+
return float64(n), nil
|
|
4717
|
+
case int:
|
|
4718
|
+
return float64(n), nil
|
|
4719
|
+
case int8:
|
|
4720
|
+
return float64(n), nil
|
|
4721
|
+
case int16:
|
|
4722
|
+
return float64(n), nil
|
|
4723
|
+
case int32:
|
|
4724
|
+
return float64(n), nil
|
|
4725
|
+
case int64:
|
|
4726
|
+
return float64(n), nil
|
|
4727
|
+
case uint:
|
|
4728
|
+
return float64(n), nil
|
|
4729
|
+
case uint8:
|
|
4730
|
+
return float64(n), nil
|
|
4731
|
+
case uint16:
|
|
4732
|
+
return float64(n), nil
|
|
4733
|
+
case uint32:
|
|
4734
|
+
return float64(n), nil
|
|
4735
|
+
case uint64:
|
|
4736
|
+
// might overflow if > math.MaxFloat64, but for demonstration that's rare
|
|
4737
|
+
return float64(n), nil
|
|
4738
|
+
default:
|
|
4739
|
+
return 0, fmt.Errorf("not a numeric type")
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4742
|
+
|
|
4743
|
+
|
|
4744
|
+
// _parseInt is a helper to convert a string to int safely.
|
|
4745
|
+
func _parseInt(s string) (int, error) {
|
|
4746
|
+
// We'll do a very simple parse:
|
|
4747
|
+
var x int
|
|
4748
|
+
var sign int = 1
|
|
4749
|
+
for i, c := range s {
|
|
4750
|
+
if c == '-' && i == 0 {
|
|
4751
|
+
sign = -1
|
|
4752
|
+
continue
|
|
4753
|
+
}
|
|
4754
|
+
if c < '0' || c > '9' {
|
|
4755
|
+
return 0, &ParseIntError{s}
|
|
4756
|
+
}
|
|
4757
|
+
x = 10*x + int(c-'0')
|
|
4758
|
+
}
|
|
4759
|
+
return x * sign, nil
|
|
4760
|
+
}
|
|
4761
|
+
|
|
4762
|
+
|
|
4763
|
+
type ParseIntError struct{ input string }
|
|
4764
|
+
|
|
4765
|
+
|
|
4766
|
+
func (e *ParseIntError) Error() string {
|
|
4767
|
+
return "cannot parse int from: " + e.input
|
|
4768
|
+
}
|
|
4769
|
+
|
|
4770
|
+
|
|
4771
|
+
func _makeArrayType(values []any, target any) any {
|
|
4772
|
+
targetElem := reflect.TypeOf(target).Elem()
|
|
4773
|
+
out := reflect.MakeSlice(reflect.SliceOf(targetElem), len(values), len(values))
|
|
4774
|
+
|
|
4775
|
+
for i, v := range values {
|
|
4776
|
+
elemVal := reflect.ValueOf(v)
|
|
4777
|
+
if !elemVal.Type().ConvertibleTo(targetElem) {
|
|
4778
|
+
return values
|
|
4779
|
+
}
|
|
4780
|
+
|
|
4781
|
+
out.Index(i).Set(elemVal.Convert(targetElem))
|
|
4782
|
+
}
|
|
4783
|
+
|
|
4784
|
+
return out.Interface()
|
|
4785
|
+
}
|
|
4786
|
+
|
|
4787
|
+
|
|
4788
|
+
func _stringifyValue(v any) string {
|
|
4789
|
+
switch vv := v.(type) {
|
|
4790
|
+
case string:
|
|
4791
|
+
return vv
|
|
4792
|
+
case float64, int, bool:
|
|
4793
|
+
return Stringify(v)
|
|
4794
|
+
default:
|
|
4795
|
+
return Stringify(v)
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
|
|
4799
|
+
|
|
4800
|
+
|
|
4801
|
+
|
|
4802
|
+
// DEBUG
|
|
4803
|
+
|
|
4804
|
+
func fdt(data any) string {
|
|
4805
|
+
return fdti(data, "")
|
|
4806
|
+
}
|
|
4807
|
+
|
|
4808
|
+
func fdti(data any, indent string) string {
|
|
4809
|
+
result := ""
|
|
4810
|
+
|
|
4811
|
+
if data == nil {
|
|
4812
|
+
return indent + "nil\n"
|
|
4813
|
+
}
|
|
4814
|
+
|
|
4815
|
+
// Get a pointer for memory address
|
|
4816
|
+
memoryAddr := "0x???"
|
|
4817
|
+
val := reflect.ValueOf(data)
|
|
4818
|
+
|
|
4819
|
+
// For non-pointer types that are addressable, get their pointer
|
|
4820
|
+
if val.Kind() != reflect.Ptr && val.CanAddr() {
|
|
4821
|
+
ptr := val.Addr()
|
|
4822
|
+
memoryAddr = fmt.Sprintf("0x%x", ptr.Pointer())
|
|
4823
|
+
} else if val.Kind() == reflect.Ptr {
|
|
4824
|
+
// For pointer types, use the pointer value directly
|
|
4825
|
+
memoryAddr = fmt.Sprintf("0x%x", val.Pointer())
|
|
4826
|
+
} else if val.Kind() == reflect.Map || val.Kind() == reflect.Slice {
|
|
4827
|
+
// For maps and slices, use the pointer to internal data
|
|
4828
|
+
memoryAddr = fmt.Sprintf("0x%x", val.Pointer())
|
|
4829
|
+
}
|
|
4830
|
+
|
|
4831
|
+
switch v := data.(type) {
|
|
4832
|
+
case map[string]any:
|
|
4833
|
+
result += indent + fmt.Sprintf("{ @%s\n", memoryAddr)
|
|
4834
|
+
for key, value := range v {
|
|
4835
|
+
result += fmt.Sprintf("%s \"%s\": %s", indent, key, fdti(value, indent+" "))
|
|
4836
|
+
}
|
|
4837
|
+
result += indent + "}\n"
|
|
4838
|
+
|
|
4839
|
+
case []any:
|
|
4840
|
+
result += indent + fmt.Sprintf("[ @%s\n", memoryAddr)
|
|
4841
|
+
for _, value := range v {
|
|
4842
|
+
result += fmt.Sprintf("%s - %s", indent, fdti(value, indent+" "))
|
|
4843
|
+
}
|
|
4844
|
+
result += indent + "]\n"
|
|
4845
|
+
|
|
4846
|
+
default:
|
|
4847
|
+
// Check if it's a struct using reflection
|
|
4848
|
+
typ := val.Type()
|
|
4849
|
+
|
|
4850
|
+
// Handle pointers by dereferencing
|
|
4851
|
+
isPtr := false
|
|
4852
|
+
if val.Kind() == reflect.Ptr {
|
|
4853
|
+
isPtr = true
|
|
4854
|
+
if val.IsNil() {
|
|
4855
|
+
return indent + "nil\n"
|
|
4856
|
+
}
|
|
4857
|
+
val = val.Elem()
|
|
4858
|
+
typ = val.Type()
|
|
4859
|
+
}
|
|
4860
|
+
|
|
4861
|
+
if val.Kind() == reflect.Struct {
|
|
4862
|
+
structName := typ.Name()
|
|
4863
|
+
if isPtr {
|
|
4864
|
+
structName = "*" + structName
|
|
4865
|
+
}
|
|
4866
|
+
result += indent + fmt.Sprintf("struct %s @%s {\n", structName, memoryAddr)
|
|
4867
|
+
|
|
4868
|
+
// Iterate over all fields of the struct
|
|
4869
|
+
for i := 0; i < val.NumField(); i++ {
|
|
4870
|
+
field := val.Field(i)
|
|
4871
|
+
fieldType := typ.Field(i)
|
|
4872
|
+
|
|
4873
|
+
// Skip unexported fields (lowercase field names)
|
|
4874
|
+
if !fieldType.IsExported() {
|
|
4875
|
+
continue
|
|
4876
|
+
}
|
|
4877
|
+
|
|
4878
|
+
fieldName := fieldType.Name
|
|
4879
|
+
fieldValue := field.Interface()
|
|
4880
|
+
|
|
4881
|
+
result += fmt.Sprintf("%s %s: %s", indent, fieldName, fdti(fieldValue, indent+" "))
|
|
4882
|
+
}
|
|
4883
|
+
result += indent + "}\n"
|
|
4884
|
+
} else {
|
|
4885
|
+
// For non-struct types, just format value with its type
|
|
4886
|
+
result += fmt.Sprintf("%v (%s) @%s\n", v, reflect.TypeOf(v), memoryAddr)
|
|
4887
|
+
}
|
|
4888
|
+
}
|
|
4889
|
+
|
|
4890
|
+
return result
|
|
4891
|
+
}
|