@voxgig/sdkgen 0.35.2 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/voxgig-sdkgen +1 -1
- package/dist/cmp/Main.js +0 -12
- package/dist/cmp/Main.js.map +1 -1
- package/dist/cmp/Readme.js +5 -5
- package/dist/cmp/Readme.js.map +1 -1
- package/dist/cmp/ReadmeEntity.js +74 -24
- package/dist/cmp/ReadmeEntity.js.map +1 -1
- package/dist/cmp/ReadmeExplanation.d.ts +2 -0
- package/dist/cmp/ReadmeExplanation.js +308 -0
- package/dist/cmp/ReadmeExplanation.js.map +1 -0
- package/dist/cmp/ReadmeHowto.d.ts +2 -0
- package/dist/cmp/ReadmeHowto.js +18 -0
- package/dist/cmp/ReadmeHowto.js.map +1 -0
- package/dist/cmp/ReadmeIntro.js +8 -23
- package/dist/cmp/ReadmeIntro.js.map +1 -1
- package/dist/cmp/ReadmeModel.js +55 -91
- package/dist/cmp/ReadmeModel.js.map +1 -1
- package/dist/cmp/ReadmeOptions.js +35 -11
- package/dist/cmp/ReadmeOptions.js.map +1 -1
- package/dist/cmp/ReadmeQuick.js +4 -1
- package/dist/cmp/ReadmeQuick.js.map +1 -1
- package/dist/cmp/ReadmeRef.js +1042 -40
- package/dist/cmp/ReadmeRef.js.map +1 -1
- package/dist/cmp/ReadmeTop.d.ts +2 -0
- package/dist/cmp/ReadmeTop.js +171 -0
- package/dist/cmp/ReadmeTop.js.map +1 -0
- package/dist/sdkgen.d.ts +7 -1
- package/dist/sdkgen.js +13 -1
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/project/.sdk/model/feature/feature-index.jsonic +5 -0
- package/project/.sdk/model/feature/log.jsonic +5 -1
- package/project/.sdk/model/feature/test.jsonic +1 -1
- package/project/.sdk/model/target/lua.jsonic +23 -0
- package/project/.sdk/model/target/php.jsonic +22 -0
- package/project/.sdk/model/target/py.jsonic +23 -0
- package/project/.sdk/model/target/rb.jsonic +23 -0
- package/project/.sdk/src/cmp/go/Config_go.ts +6 -2
- package/project/.sdk/src/cmp/go/Main_go.ts +2 -6
- package/project/.sdk/src/cmp/go/ReadmeExplanation_go.ts +42 -0
- package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +112 -0
- package/project/.sdk/src/cmp/go/ReadmeInstall_go.ts +29 -0
- package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +129 -0
- package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +163 -0
- package/project/.sdk/src/cmp/go/ReadmeTopHowto_go.ts +24 -0
- package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +67 -0
- package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +40 -0
- package/project/.sdk/src/cmp/go/TestDirect_go.ts +46 -2
- package/project/.sdk/src/cmp/go/TestEntity_go.ts +360 -160
- package/project/.sdk/src/cmp/go/Test_go.ts +24 -1
- package/project/.sdk/src/cmp/js/EntityBase_js.ts +34 -0
- package/project/.sdk/src/cmp/js/Main_js.ts +10 -0
- package/project/.sdk/src/cmp/js/ReadmeExplanation_js.ts +33 -0
- package/project/.sdk/src/cmp/js/ReadmeHowto_js.ts +123 -0
- package/project/.sdk/src/cmp/js/ReadmeModel_js.ts +152 -0
- package/project/.sdk/src/cmp/js/ReadmeQuick_js.ts +1 -1
- package/project/.sdk/src/cmp/js/ReadmeTopHowto_js.ts +25 -0
- package/project/.sdk/src/cmp/js/ReadmeTopQuick_js.ts +65 -0
- package/project/.sdk/src/cmp/js/ReadmeTopTest_js.ts +36 -0
- package/project/.sdk/src/cmp/js/TestDirect_js.ts +53 -5
- package/project/.sdk/src/cmp/js/TestEntity_js.ts +114 -20
- package/project/.sdk/src/cmp/js/fragment/Entity.fragment.js +7 -139
- package/project/.sdk/src/cmp/js/fragment/EntityBase.fragment.js +149 -0
- package/project/.sdk/src/cmp/js/fragment/EntityCreateOp.fragment.js +6 -10
- package/project/.sdk/src/cmp/js/fragment/EntityListOp.fragment.js +6 -10
- package/project/.sdk/src/cmp/js/fragment/EntityLoadOp.fragment.js +7 -11
- package/project/.sdk/src/cmp/js/fragment/EntityRemoveOp.fragment.js +7 -11
- package/project/.sdk/src/cmp/js/fragment/EntityUpdateOp.fragment.js +7 -11
- package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +2 -0
- package/project/.sdk/src/cmp/js/fragment/SdkError.fragment.js +0 -2
- package/project/.sdk/src/cmp/lua/Config_lua.ts +116 -0
- package/project/.sdk/src/cmp/lua/EntityOperation_lua.ts +44 -0
- package/project/.sdk/src/cmp/lua/Entity_lua.ts +62 -0
- package/project/.sdk/src/cmp/lua/MainEntity_lua.ts +23 -0
- package/project/.sdk/src/cmp/lua/Main_lua.ts +133 -0
- package/project/.sdk/src/cmp/lua/Package_lua.ts +80 -0
- package/project/.sdk/src/cmp/lua/ReadmeExplanation_lua.ts +42 -0
- package/project/.sdk/src/cmp/lua/ReadmeHowto_lua.ts +100 -0
- package/project/.sdk/src/cmp/lua/ReadmeInstall_lua.ts +26 -0
- package/project/.sdk/src/cmp/lua/ReadmeModel_lua.ts +129 -0
- package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +99 -0
- package/project/.sdk/src/cmp/lua/ReadmeTopHowto_lua.ts +24 -0
- package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +55 -0
- package/project/.sdk/src/cmp/lua/ReadmeTopTest_lua.ts +38 -0
- package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +261 -0
- package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +485 -0
- package/project/.sdk/src/cmp/lua/Test_lua.ts +49 -0
- package/project/.sdk/src/cmp/lua/fragment/Entity.fragment.lua +147 -0
- package/project/.sdk/src/cmp/lua/fragment/EntityCreateOp.fragment.lua +27 -0
- package/project/.sdk/src/cmp/lua/fragment/EntityListOp.fragment.lua +24 -0
- package/project/.sdk/src/cmp/lua/fragment/EntityLoadOp.fragment.lua +30 -0
- package/project/.sdk/src/cmp/lua/fragment/EntityRemoveOp.fragment.lua +30 -0
- package/project/.sdk/src/cmp/lua/fragment/EntityUpdateOp.fragment.lua +30 -0
- package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +256 -0
- package/project/.sdk/src/cmp/lua/fragment/SdkError.fragment.lua +26 -0
- package/project/.sdk/src/cmp/lua/tsconfig.json +15 -0
- package/project/.sdk/src/cmp/lua/utility_lua.ts +84 -0
- package/project/.sdk/src/cmp/php/Config_php.ts +111 -0
- package/project/.sdk/src/cmp/php/EntityOperation_php.ts +44 -0
- package/project/.sdk/src/cmp/php/Entity_php.ts +62 -0
- package/project/.sdk/src/cmp/php/MainEntity_php.ts +24 -0
- package/project/.sdk/src/cmp/php/Main_php.ts +135 -0
- package/project/.sdk/src/cmp/php/Package_php.ts +87 -0
- package/project/.sdk/src/cmp/php/ReadmeExplanation_php.ts +42 -0
- package/project/.sdk/src/cmp/php/ReadmeHowto_php.ts +101 -0
- package/project/.sdk/src/cmp/php/ReadmeInstall_php.ts +19 -0
- package/project/.sdk/src/cmp/php/ReadmeModel_php.ts +129 -0
- package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +100 -0
- package/project/.sdk/src/cmp/php/ReadmeTopHowto_php.ts +24 -0
- package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +56 -0
- package/project/.sdk/src/cmp/php/ReadmeTopTest_php.ts +38 -0
- package/project/.sdk/src/cmp/php/TestDirect_php.ts +270 -0
- package/project/.sdk/src/cmp/php/TestEntity_php.ts +479 -0
- package/project/.sdk/src/cmp/php/Test_php.ts +56 -0
- package/project/.sdk/src/cmp/php/fragment/Entity.fragment.php +146 -0
- package/project/.sdk/src/cmp/php/fragment/EntityCreateOp.fragment.php +26 -0
- package/project/.sdk/src/cmp/php/fragment/EntityListOp.fragment.php +23 -0
- package/project/.sdk/src/cmp/php/fragment/EntityLoadOp.fragment.php +29 -0
- package/project/.sdk/src/cmp/php/fragment/EntityRemoveOp.fragment.php +29 -0
- package/project/.sdk/src/cmp/php/fragment/EntityUpdateOp.fragment.php +29 -0
- package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +233 -0
- package/project/.sdk/src/cmp/php/fragment/SdkError.fragment.php +24 -0
- package/project/.sdk/src/cmp/php/tsconfig.json +15 -0
- package/project/.sdk/src/cmp/php/utility_php.ts +85 -0
- package/project/.sdk/src/cmp/py/Config_py.ts +97 -0
- package/project/.sdk/src/cmp/py/EntityOperation_py.ts +44 -0
- package/project/.sdk/src/cmp/py/Entity_py.ts +62 -0
- package/project/.sdk/src/cmp/py/MainEntity_py.ts +22 -0
- package/project/.sdk/src/cmp/py/Main_py.ts +156 -0
- package/project/.sdk/src/cmp/py/Package_py.ts +75 -0
- package/project/.sdk/src/cmp/py/ReadmeExplanation_py.ts +41 -0
- package/project/.sdk/src/cmp/py/ReadmeHowto_py.ts +98 -0
- package/project/.sdk/src/cmp/py/ReadmeInstall_py.ts +25 -0
- package/project/.sdk/src/cmp/py/ReadmeModel_py.ts +130 -0
- package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +100 -0
- package/project/.sdk/src/cmp/py/ReadmeTopHowto_py.ts +24 -0
- package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +56 -0
- package/project/.sdk/src/cmp/py/ReadmeTopTest_py.ts +38 -0
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +248 -0
- package/project/.sdk/src/cmp/py/TestEntity_py.ts +477 -0
- package/project/.sdk/src/cmp/py/Test_py.ts +55 -0
- package/project/.sdk/src/cmp/py/fragment/Entity.fragment.py +112 -0
- package/project/.sdk/src/cmp/py/fragment/EntityCreateOp.fragment.py +25 -0
- package/project/.sdk/src/cmp/py/fragment/EntityListOp.fragment.py +22 -0
- package/project/.sdk/src/cmp/py/fragment/EntityLoadOp.fragment.py +27 -0
- package/project/.sdk/src/cmp/py/fragment/EntityRemoveOp.fragment.py +27 -0
- package/project/.sdk/src/cmp/py/fragment/EntityUpdateOp.fragment.py +27 -0
- package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +222 -0
- package/project/.sdk/src/cmp/py/fragment/SdkError.fragment.py +16 -0
- package/project/.sdk/src/cmp/py/tsconfig.json +15 -0
- package/project/.sdk/src/cmp/py/utility_py.ts +84 -0
- package/project/.sdk/src/cmp/rb/Config_rb.ts +105 -0
- package/project/.sdk/src/cmp/rb/EntityOperation_rb.ts +44 -0
- package/project/.sdk/src/cmp/rb/Entity_rb.ts +62 -0
- package/project/.sdk/src/cmp/rb/MainEntity_rb.ts +23 -0
- package/project/.sdk/src/cmp/rb/Main_rb.ts +130 -0
- package/project/.sdk/src/cmp/rb/Package_rb.ts +111 -0
- package/project/.sdk/src/cmp/rb/ReadmeExplanation_rb.ts +42 -0
- package/project/.sdk/src/cmp/rb/ReadmeHowto_rb.ts +98 -0
- package/project/.sdk/src/cmp/rb/ReadmeInstall_rb.ts +31 -0
- package/project/.sdk/src/cmp/rb/ReadmeModel_rb.ts +129 -0
- package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +99 -0
- package/project/.sdk/src/cmp/rb/ReadmeTopHowto_rb.ts +24 -0
- package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +55 -0
- package/project/.sdk/src/cmp/rb/ReadmeTopTest_rb.ts +38 -0
- package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +260 -0
- package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +476 -0
- package/project/.sdk/src/cmp/rb/Test_rb.ts +50 -0
- package/project/.sdk/src/cmp/rb/fragment/Entity.fragment.rb +116 -0
- package/project/.sdk/src/cmp/rb/fragment/EntityCreateOp.fragment.rb +25 -0
- package/project/.sdk/src/cmp/rb/fragment/EntityListOp.fragment.rb +20 -0
- package/project/.sdk/src/cmp/rb/fragment/EntityLoadOp.fragment.rb +26 -0
- package/project/.sdk/src/cmp/rb/fragment/EntityRemoveOp.fragment.rb +26 -0
- package/project/.sdk/src/cmp/rb/fragment/EntityUpdateOp.fragment.rb +26 -0
- package/project/.sdk/src/cmp/rb/fragment/Main.fragment.rb +203 -0
- package/project/.sdk/src/cmp/rb/fragment/SdkError.fragment.rb +16 -0
- package/project/.sdk/src/cmp/rb/tsconfig.json +15 -0
- package/project/.sdk/src/cmp/rb/utility_rb.ts +84 -0
- package/project/.sdk/src/cmp/ts/Main_ts.ts +7 -0
- package/project/.sdk/src/cmp/ts/ReadmeExplanation_ts.ts +34 -0
- package/project/.sdk/src/cmp/ts/ReadmeHowto_ts.ts +123 -0
- package/project/.sdk/src/cmp/ts/ReadmeInstall_ts.ts +1 -1
- package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +159 -0
- package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +69 -54
- package/project/.sdk/src/cmp/ts/ReadmeTopHowto_ts.ts +25 -0
- package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +65 -0
- package/project/.sdk/src/cmp/ts/ReadmeTopTest_ts.ts +36 -0
- package/project/.sdk/tm/go/feature/log_feature.go +133 -0
- package/project/.sdk/tm/go/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/go/test/runner_test.go +16 -2
- package/project/.sdk/tm/js/src/Context.js +142 -0
- package/project/.sdk/tm/js/src/Control.js +16 -0
- package/project/.sdk/tm/js/src/Operation.js +19 -0
- package/project/.sdk/tm/js/src/Point.js +24 -0
- package/project/.sdk/tm/js/src/README.md +1 -0
- package/project/.sdk/tm/js/src/Response.js +19 -0
- package/project/.sdk/tm/js/src/Result.js +21 -0
- package/project/.sdk/tm/js/src/Spec.js +26 -0
- package/project/.sdk/tm/js/src/feature/README.md +1 -0
- package/project/.sdk/tm/js/src/feature/base/BaseFeature.js +45 -0
- package/project/.sdk/tm/js/src/feature/log/LogFeature.js +46 -47
- package/project/.sdk/tm/js/src/feature/test/TestFeature.js +207 -0
- package/project/.sdk/tm/js/src/types.js +22 -0
- package/project/.sdk/tm/js/src/utility/CleanUtility.js +31 -0
- package/project/.sdk/tm/js/src/utility/DoneUtility.js +11 -4
- package/project/.sdk/tm/js/src/utility/FeatureAddUtility.js +42 -0
- package/project/.sdk/tm/js/src/utility/FeatureHookUtility.js +25 -0
- package/project/.sdk/tm/js/src/utility/FeatureInitUtility.js +11 -0
- package/project/.sdk/tm/js/src/utility/FetcherUtility.js +28 -0
- package/project/.sdk/tm/js/src/utility/MakeContextUtility.js +11 -0
- package/project/.sdk/tm/js/src/utility/MakeErrorUtility.js +55 -0
- package/project/.sdk/tm/js/src/utility/MakeFetchDefUtility.js +44 -0
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +93 -0
- package/project/.sdk/tm/js/src/utility/MakePointUtility.js +77 -0
- package/project/.sdk/tm/js/src/utility/MakeRequestUtility.js +63 -0
- package/project/.sdk/tm/js/src/utility/MakeResponseUtility.js +55 -0
- package/project/.sdk/tm/js/src/utility/MakeResultUtility.js +54 -0
- package/project/.sdk/tm/js/src/utility/MakeSpecUtility.js +58 -0
- package/project/.sdk/tm/js/src/utility/MakeUrlUtility.js +40 -0
- package/project/.sdk/tm/js/src/utility/ParamUtility.js +61 -0
- package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +41 -0
- package/project/.sdk/tm/js/src/utility/PrepareBodyUtility.js +25 -0
- package/project/.sdk/tm/js/src/utility/PrepareHeadersUtility.js +18 -0
- package/project/.sdk/tm/js/src/utility/{MethodUtility.js → PrepareMethodUtility.js} +7 -7
- package/project/.sdk/tm/js/src/utility/PrepareParamsUtility.js +25 -0
- package/project/.sdk/tm/js/src/utility/PreparePathUtility.js +13 -0
- package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +26 -0
- package/project/.sdk/tm/js/src/utility/README.md +1 -0
- package/project/.sdk/tm/js/src/utility/ResultBasicUtility.js +34 -0
- package/project/.sdk/tm/js/src/utility/ResultBodyUtility.js +18 -0
- package/project/.sdk/tm/js/src/utility/ResultHeadersUtility.js +22 -0
- package/project/.sdk/tm/js/src/utility/StructUtility.js +2219 -1078
- package/project/.sdk/tm/js/src/utility/TransformRequestUtility.js +28 -0
- package/project/.sdk/tm/js/src/utility/TransformResponseUtility.js +31 -0
- package/project/.sdk/tm/js/src/utility/Utility.js +61 -61
- package/project/.sdk/tm/js/test/README.md +1 -0
- package/project/.sdk/tm/js/test/exists.test.js +16 -0
- package/project/.sdk/tm/js/test/runner.js +323 -107
- package/project/.sdk/tm/js/test/utility/Custom.test.js +41 -63
- package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +390 -116
- package/project/.sdk/tm/js/test/utility/StructUtility.test.js +728 -175
- package/project/.sdk/tm/js/test/utility/index.js +9 -0
- package/project/.sdk/tm/js/test/utility.js +72 -0
- package/project/.sdk/tm/lua/LICENSE +22 -0
- package/project/.sdk/tm/lua/Makefile +10 -0
- package/project/.sdk/tm/lua/core/context.lua +208 -0
- package/project/.sdk/tm/lua/core/control.lua +17 -0
- package/project/.sdk/tm/lua/core/error.lua +30 -0
- package/project/.sdk/tm/lua/core/helpers.lua +30 -0
- package/project/.sdk/tm/lua/core/operation.lua +51 -0
- package/project/.sdk/tm/lua/core/response.lua +45 -0
- package/project/.sdk/tm/lua/core/result.lua +58 -0
- package/project/.sdk/tm/lua/core/spec.lua +29 -0
- package/project/.sdk/tm/lua/core/utility_type.lua +90 -0
- package/project/.sdk/tm/lua/feature/base_feature.lua +35 -0
- package/project/.sdk/tm/lua/feature/log_feature.lua +80 -0
- package/project/.sdk/tm/lua/feature/test_feature.lua +202 -0
- package/project/.sdk/tm/lua/src/feature/README.md +3 -0
- package/project/.sdk/tm/lua/src/feature/base/.gitkeep +0 -0
- package/project/.sdk/tm/lua/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/lua/src/feature/test/.gitkeep +0 -0
- package/project/.sdk/tm/lua/test/primary_utility_test.lua +1213 -0
- package/project/.sdk/tm/lua/test/runner.lua +86 -0
- package/project/.sdk/tm/lua/test/struct_runner.lua +602 -0
- package/project/.sdk/tm/lua/test/struct_utility_test.lua +959 -0
- package/project/.sdk/tm/lua/utility/clean.lua +7 -0
- package/project/.sdk/tm/lua/utility/done.lua +19 -0
- package/project/.sdk/tm/lua/utility/feature_add.lua +8 -0
- package/project/.sdk/tm/lua/utility/feature_hook.lua +21 -0
- package/project/.sdk/tm/lua/utility/feature_init.lua +24 -0
- package/project/.sdk/tm/lua/utility/fetcher.lua +96 -0
- package/project/.sdk/tm/lua/utility/make_context.lua +9 -0
- package/project/.sdk/tm/lua/utility/make_error.lua +73 -0
- package/project/.sdk/tm/lua/utility/make_fetch_def.lua +43 -0
- package/project/.sdk/tm/lua/utility/make_options.lua +116 -0
- package/project/.sdk/tm/lua/utility/make_point.lua +92 -0
- package/project/.sdk/tm/lua/utility/make_request.lua +58 -0
- package/project/.sdk/tm/lua/utility/make_response.lua +44 -0
- package/project/.sdk/tm/lua/utility/make_result.lua +51 -0
- package/project/.sdk/tm/lua/utility/make_spec.lua +72 -0
- package/project/.sdk/tm/lua/utility/make_url.lua +46 -0
- package/project/.sdk/tm/lua/utility/param.lua +68 -0
- package/project/.sdk/tm/lua/utility/prepare_auth.lua +39 -0
- package/project/.sdk/tm/lua/utility/prepare_body.lua +14 -0
- package/project/.sdk/tm/lua/utility/prepare_headers.lua +20 -0
- package/project/.sdk/tm/lua/utility/prepare_method.lua +17 -0
- package/project/.sdk/tm/lua/utility/prepare_params.lua +36 -0
- package/project/.sdk/tm/lua/utility/prepare_path.lua +19 -0
- package/project/.sdk/tm/lua/utility/prepare_query.lua +41 -0
- package/project/.sdk/tm/lua/utility/register.lua +71 -0
- package/project/.sdk/tm/lua/utility/result_basic.lua +32 -0
- package/project/.sdk/tm/lua/utility/result_body.lua +17 -0
- package/project/.sdk/tm/lua/utility/result_headers.lua +22 -0
- package/project/.sdk/tm/lua/utility/struct/struct.lua +3427 -0
- package/project/.sdk/tm/lua/utility/transform_request.lua +31 -0
- package/project/.sdk/tm/lua/utility/transform_response.lua +44 -0
- package/project/.sdk/tm/php/LICENSE +22 -0
- package/project/.sdk/tm/php/Makefile +10 -0
- package/project/.sdk/tm/php/core/Context.php +139 -0
- package/project/.sdk/tm/php/core/Control.php +18 -0
- package/project/.sdk/tm/php/core/Error.php +37 -0
- package/project/.sdk/tm/php/core/Helpers.php +25 -0
- package/project/.sdk/tm/php/core/Operation.php +36 -0
- package/project/.sdk/tm/php/core/Response.php +30 -0
- package/project/.sdk/tm/php/core/Result.php +35 -0
- package/project/.sdk/tm/php/core/Spec.php +38 -0
- package/project/.sdk/tm/php/core/UtilityType.php +89 -0
- package/project/.sdk/tm/php/feature/BaseFeature.php +37 -0
- package/project/.sdk/tm/php/feature/LogFeature.php +65 -0
- package/project/.sdk/tm/php/feature/TestFeature.php +156 -0
- package/project/.sdk/tm/php/src/feature/README.md +3 -0
- package/project/.sdk/tm/php/src/feature/base/.gitkeep +0 -0
- package/project/.sdk/tm/php/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/php/src/feature/test/.gitkeep +0 -0
- package/project/.sdk/tm/php/test/PrimaryUtilityTest.php +1309 -0
- package/project/.sdk/tm/php/test/Runner.php +112 -0
- package/project/.sdk/tm/php/test/StructRunner.php +275 -0
- package/project/.sdk/tm/php/test/StructUtilityTest.php +1336 -0
- package/project/.sdk/tm/php/utility/Clean.php +12 -0
- package/project/.sdk/tm/php/utility/Done.php +26 -0
- package/project/.sdk/tm/php/utility/FeatureAdd.php +12 -0
- package/project/.sdk/tm/php/utility/FeatureHook.php +23 -0
- package/project/.sdk/tm/php/utility/FeatureInit.php +25 -0
- package/project/.sdk/tm/php/utility/Fetcher.php +105 -0
- package/project/.sdk/tm/php/utility/MakeContext.php +14 -0
- package/project/.sdk/tm/php/utility/MakeError.php +59 -0
- package/project/.sdk/tm/php/utility/MakeFetchDef.php +36 -0
- package/project/.sdk/tm/php/utility/MakeOptions.php +102 -0
- package/project/.sdk/tm/php/utility/MakePoint.php +87 -0
- package/project/.sdk/tm/php/utility/MakeRequest.php +57 -0
- package/project/.sdk/tm/php/utility/MakeResponse.php +43 -0
- package/project/.sdk/tm/php/utility/MakeResult.php +53 -0
- package/project/.sdk/tm/php/utility/MakeSpec.php +64 -0
- package/project/.sdk/tm/php/utility/MakeUrl.php +41 -0
- package/project/.sdk/tm/php/utility/Param.php +68 -0
- package/project/.sdk/tm/php/utility/PrepareAuth.php +33 -0
- package/project/.sdk/tm/php/utility/PrepareBody.php +15 -0
- package/project/.sdk/tm/php/utility/PrepareHeaders.php +18 -0
- package/project/.sdk/tm/php/utility/PrepareMethod.php +21 -0
- package/project/.sdk/tm/php/utility/PrepareParams.php +34 -0
- package/project/.sdk/tm/php/utility/PreparePath.php +20 -0
- package/project/.sdk/tm/php/utility/PrepareQuery.php +32 -0
- package/project/.sdk/tm/php/utility/Register.php +67 -0
- package/project/.sdk/tm/php/utility/ResultBasic.php +29 -0
- package/project/.sdk/tm/php/utility/ResultBody.php +17 -0
- package/project/.sdk/tm/php/utility/ResultHeaders.php +21 -0
- package/project/.sdk/tm/php/utility/TransformRequest.php +27 -0
- package/project/.sdk/tm/php/utility/TransformResponse.php +42 -0
- package/project/.sdk/tm/php/utility/struct/Struct.php +3535 -0
- package/project/.sdk/tm/py/Makefile +10 -0
- package/project/.sdk/tm/py/core/__init__.py +0 -0
- package/project/.sdk/tm/py/core/context.py +199 -0
- package/project/.sdk/tm/py/core/control.py +12 -0
- package/project/.sdk/tm/py/core/error.py +18 -0
- package/project/.sdk/tm/py/core/helpers.py +15 -0
- package/project/.sdk/tm/py/core/operation.py +37 -0
- package/project/.sdk/tm/py/core/response.py +34 -0
- package/project/.sdk/tm/py/core/result.py +44 -0
- package/project/.sdk/tm/py/core/spec.py +23 -0
- package/project/.sdk/tm/py/core/utility_type.py +82 -0
- package/project/.sdk/tm/py/entity/__init__.py +0 -0
- package/project/.sdk/tm/py/feature/__init__.py +0 -0
- package/project/.sdk/tm/py/feature/base_feature.py +61 -0
- package/project/.sdk/tm/py/feature/log_feature.py +84 -0
- package/project/.sdk/tm/py/feature/test_feature.py +164 -0
- package/project/.sdk/tm/py/src/feature/README.md +3 -0
- package/project/.sdk/tm/py/src/feature/base/.gitkeep +0 -0
- package/project/.sdk/tm/py/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/py/src/feature/test/.gitkeep +0 -0
- package/project/.sdk/tm/py/test/__init__.py +0 -0
- package/project/.sdk/tm/py/test/runner.py +90 -0
- package/project/.sdk/tm/py/test/struct_runner.py +411 -0
- package/project/.sdk/tm/py/test/test_primary_utility.py +1101 -0
- package/project/.sdk/tm/py/test/test_struct_utility.py +751 -0
- package/project/.sdk/tm/py/utility/__init__.py +0 -0
- package/project/.sdk/tm/py/utility/clean.py +5 -0
- package/project/.sdk/tm/py/utility/done.py +14 -0
- package/project/.sdk/tm/py/utility/feature_add.py +6 -0
- package/project/.sdk/tm/py/utility/feature_hook.py +15 -0
- package/project/.sdk/tm/py/utility/feature_init.py +18 -0
- package/project/.sdk/tm/py/utility/fetcher.py +95 -0
- package/project/.sdk/tm/py/utility/make_context.py +7 -0
- package/project/.sdk/tm/py/utility/make_error.py +64 -0
- package/project/.sdk/tm/py/utility/make_fetch_def.py +37 -0
- package/project/.sdk/tm/py/utility/make_options.py +103 -0
- package/project/.sdk/tm/py/utility/make_point.py +74 -0
- package/project/.sdk/tm/py/utility/make_request.py +52 -0
- package/project/.sdk/tm/py/utility/make_response.py +36 -0
- package/project/.sdk/tm/py/utility/make_result.py +41 -0
- package/project/.sdk/tm/py/utility/make_spec.py +68 -0
- package/project/.sdk/tm/py/utility/make_url.py +34 -0
- package/project/.sdk/tm/py/utility/param.py +55 -0
- package/project/.sdk/tm/py/utility/prepare_auth.py +34 -0
- package/project/.sdk/tm/py/utility/prepare_body.py +11 -0
- package/project/.sdk/tm/py/utility/prepare_headers.py +17 -0
- package/project/.sdk/tm/py/utility/prepare_method.py +15 -0
- package/project/.sdk/tm/py/utility/prepare_params.py +28 -0
- package/project/.sdk/tm/py/utility/prepare_path.py +16 -0
- package/project/.sdk/tm/py/utility/prepare_query.py +33 -0
- package/project/.sdk/tm/py/utility/register.py +68 -0
- package/project/.sdk/tm/py/utility/result_basic.py +26 -0
- package/project/.sdk/tm/py/utility/result_body.py +13 -0
- package/project/.sdk/tm/py/utility/result_headers.py +17 -0
- package/project/.sdk/tm/py/utility/transform_request.py +27 -0
- package/project/.sdk/tm/py/utility/transform_response.py +39 -0
- package/project/.sdk/tm/py/utility/voxgig_struct/__init__.py +72 -0
- package/project/.sdk/tm/py/utility/voxgig_struct/voxgig_struct.py +2770 -0
- package/project/.sdk/tm/rb/Gemfile +4 -0
- package/project/.sdk/tm/rb/LICENSE +22 -0
- package/project/.sdk/tm/rb/Makefile +10 -0
- package/project/.sdk/tm/rb/core/context.rb +105 -0
- package/project/.sdk/tm/rb/core/control.rb +11 -0
- package/project/.sdk/tm/rb/core/error.rb +24 -0
- package/project/.sdk/tm/rb/core/helpers.rb +16 -0
- package/project/.sdk/tm/rb/core/operation.rb +26 -0
- package/project/.sdk/tm/rb/core/response.rb +20 -0
- package/project/.sdk/tm/rb/core/result.rb +23 -0
- package/project/.sdk/tm/rb/core/spec.rb +23 -0
- package/project/.sdk/tm/rb/core/utility_type.rb +32 -0
- package/project/.sdk/tm/rb/feature/base_feature.rb +30 -0
- package/project/.sdk/tm/rb/feature/log_feature.rb +50 -0
- package/project/.sdk/tm/rb/feature/test_feature.rb +154 -0
- package/project/.sdk/tm/rb/src/feature/README.md +3 -0
- package/project/.sdk/tm/rb/src/feature/base/.gitkeep +0 -0
- package/project/.sdk/tm/rb/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/rb/src/feature/test/.gitkeep +0 -0
- package/project/.sdk/tm/rb/test/primary_utility_test.rb +1083 -0
- package/project/.sdk/tm/rb/test/runner.rb +70 -0
- package/project/.sdk/tm/rb/test/struct_runner.rb +309 -0
- package/project/.sdk/tm/rb/test/struct_utility_test.rb +670 -0
- package/project/.sdk/tm/rb/utility/clean.rb +4 -0
- package/project/.sdk/tm/rb/utility/done.rb +14 -0
- package/project/.sdk/tm/rb/utility/feature_add.rb +6 -0
- package/project/.sdk/tm/rb/utility/feature_hook.rb +11 -0
- package/project/.sdk/tm/rb/utility/feature_init.rb +16 -0
- package/project/.sdk/tm/rb/utility/fetcher.rb +67 -0
- package/project/.sdk/tm/rb/utility/make_context.rb +7 -0
- package/project/.sdk/tm/rb/utility/make_error.rb +44 -0
- package/project/.sdk/tm/rb/utility/make_fetch_def.rb +24 -0
- package/project/.sdk/tm/rb/utility/make_options.rb +57 -0
- package/project/.sdk/tm/rb/utility/make_point.rb +77 -0
- package/project/.sdk/tm/rb/utility/make_request.rb +44 -0
- package/project/.sdk/tm/rb/utility/make_response.rb +25 -0
- package/project/.sdk/tm/rb/utility/make_result.rb +33 -0
- package/project/.sdk/tm/rb/utility/make_spec.rb +50 -0
- package/project/.sdk/tm/rb/utility/make_url.rb +32 -0
- package/project/.sdk/tm/rb/utility/param.rb +48 -0
- package/project/.sdk/tm/rb/utility/prepare_auth.rb +26 -0
- package/project/.sdk/tm/rb/utility/prepare_body.rb +6 -0
- package/project/.sdk/tm/rb/utility/prepare_headers.rb +11 -0
- package/project/.sdk/tm/rb/utility/prepare_method.rb +5 -0
- package/project/.sdk/tm/rb/utility/prepare_params.rb +25 -0
- package/project/.sdk/tm/rb/utility/prepare_path.rb +13 -0
- package/project/.sdk/tm/rb/utility/prepare_query.rb +22 -0
- package/project/.sdk/tm/rb/utility/register.rb +63 -0
- package/project/.sdk/tm/rb/utility/result_basic.rb +23 -0
- package/project/.sdk/tm/rb/utility/result_body.rb +11 -0
- package/project/.sdk/tm/rb/utility/result_headers.rb +15 -0
- package/project/.sdk/tm/rb/utility/struct/voxgig_struct.rb +2256 -0
- package/project/.sdk/tm/rb/utility/transform_request.rb +15 -0
- package/project/.sdk/tm/rb/utility/transform_response.rb +23 -0
- package/src/cmp/Main.ts +1 -16
- package/src/cmp/Readme.ts +5 -5
- package/src/cmp/ReadmeEntity.ts +77 -25
- package/src/cmp/ReadmeExplanation.ts +333 -0
- package/src/cmp/ReadmeHowto.ts +28 -0
- package/src/cmp/ReadmeIntro.ts +10 -24
- package/src/cmp/ReadmeModel.ts +57 -88
- package/src/cmp/ReadmeOptions.ts +40 -11
- package/src/cmp/ReadmeQuick.ts +4 -1
- package/src/cmp/ReadmeRef.ts +1057 -40
- package/src/cmp/ReadmeTop.ts +213 -0
- package/src/sdkgen.ts +12 -0
- package/project/.sdk/src/cmp/js/Quick_js.ts +0 -78
- package/project/.sdk/src/cmp/js/TestAcceptEntity_js.ts +0 -13
- package/project/.sdk/src/cmp/js/TestAccept_js.ts +0 -18
- package/project/.sdk/tm/go/test/exists_test.go +0 -16
- package/project/.sdk/tm/js/src/utility/AuthUtility.js +0 -21
- package/project/.sdk/tm/js/src/utility/BodyUtility.js +0 -29
- package/project/.sdk/tm/js/src/utility/ErrorUtility.js +0 -33
- package/project/.sdk/tm/js/src/utility/FindparamUtility.js +0 -31
- package/project/.sdk/tm/js/src/utility/FullurlUtility.js +0 -39
- package/project/.sdk/tm/js/src/utility/HeadersUtility.js +0 -13
- package/project/.sdk/tm/js/src/utility/JoinurlUtility.js +0 -14
- package/project/.sdk/tm/js/src/utility/OperatorUtility.js +0 -44
- package/project/.sdk/tm/js/src/utility/OptionsUtility.js +0 -54
- package/project/.sdk/tm/js/src/utility/ParamsUtility.js +0 -21
- package/project/.sdk/tm/js/src/utility/QueryUtility.js +0 -21
- package/project/.sdk/tm/js/src/utility/ReqformUtility.js +0 -32
- package/project/.sdk/tm/js/src/utility/RequestUtility.js +0 -48
- package/project/.sdk/tm/js/src/utility/ResbasicUtility.js +0 -27
- package/project/.sdk/tm/js/src/utility/ResbodyUtility.js +0 -15
- package/project/.sdk/tm/js/src/utility/ResformUtility.js +0 -34
- package/project/.sdk/tm/js/src/utility/ResheadersUtility.js +0 -19
- package/project/.sdk/tm/js/src/utility/ResponseUtility.js +0 -37
- package/project/.sdk/tm/js/src/utility/ResultUtility.js +0 -28
- package/project/.sdk/tm/js/src/utility/SpecUtility.js +0 -35
|
@@ -0,0 +1,3427 @@
|
|
|
1
|
+
-- Copyright (c) 2025-2026 Voxgig Ltd. MIT LICENSE.
|
|
2
|
+
|
|
3
|
+
-- VERSION: @voxgig/struct 0.0.10
|
|
4
|
+
|
|
5
|
+
--[[
|
|
6
|
+
Voxgig Struct
|
|
7
|
+
=============
|
|
8
|
+
|
|
9
|
+
Utility functions to manipulate in-memory JSON-like data
|
|
10
|
+
structures. These structures assumed to be composed of nested
|
|
11
|
+
"nodes", where a node is a list or map, and has named or indexed
|
|
12
|
+
fields. The general design principle is "by-example". Transform
|
|
13
|
+
specifications mirror the desired output. This implementation is
|
|
14
|
+
designed for porting to multiple language, and to be tolerant of
|
|
15
|
+
undefined values.
|
|
16
|
+
|
|
17
|
+
Main utilities
|
|
18
|
+
- getpath: get the value at a key path deep inside an object.
|
|
19
|
+
- merge: merge multiple nodes, overriding values in earlier nodes.
|
|
20
|
+
- walk: walk a node tree, applying a function at each node and leaf.
|
|
21
|
+
- inject: inject values from a data store into a new data structure.
|
|
22
|
+
- transform: transform a data structure to an example structure.
|
|
23
|
+
- validate: validate a data structure against a shape specification.
|
|
24
|
+
|
|
25
|
+
Minor utilities
|
|
26
|
+
- isnode, islist, ismap, iskey, isfunc: identify value kinds.
|
|
27
|
+
- isempty: undefined values, or empty nodes.
|
|
28
|
+
- keysof: sorted list of node keys (ascending).
|
|
29
|
+
- haskey: true if key value is defined.
|
|
30
|
+
- clone: create a copy of a JSON-like data structure.
|
|
31
|
+
- items: list entries of a map or list as [key, value] pairs.
|
|
32
|
+
- getprop: safely get a property value by key.
|
|
33
|
+
- setprop: safely set a property value by key.
|
|
34
|
+
- stringify: human-friendly string version of a value.
|
|
35
|
+
- escre: escape a regular expresion string.
|
|
36
|
+
- escurl: escape a url.
|
|
37
|
+
- join: join parts of a url, merging forward slashes.
|
|
38
|
+
|
|
39
|
+
This set of functions and supporting utilities is designed to work
|
|
40
|
+
uniformly across many languages, meaning that some code that may be
|
|
41
|
+
functionally redundant in specific languages is still retained to
|
|
42
|
+
keep the code human comparable.
|
|
43
|
+
|
|
44
|
+
NOTE: Lists are assumed to be mutable and reference stable.
|
|
45
|
+
|
|
46
|
+
NOTE: In this code JSON nulls are in general *not* considered the
|
|
47
|
+
same as undefined values in the given language. However most
|
|
48
|
+
JSON parsers do use the undefined value to represent JSON
|
|
49
|
+
null. This is ambiguous as JSON null is a separate value, not an
|
|
50
|
+
undefined value. You should convert such values to a special value
|
|
51
|
+
to represent JSON null, if this ambiguity creates issues
|
|
52
|
+
(thankfully in most APIs, JSON nulls are not used). For example,
|
|
53
|
+
the unit tests use the string "__NULL__" where necessary.
|
|
54
|
+
]] ----------------------------------------------------------
|
|
55
|
+
-- String constants are explicitly defined.
|
|
56
|
+
----------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
-- Mode value for inject step (bitfield).
|
|
59
|
+
local M_KEYPRE = 1
|
|
60
|
+
local M_KEYPOST = 2
|
|
61
|
+
local M_VAL = 4
|
|
62
|
+
|
|
63
|
+
local MODENAME = {
|
|
64
|
+
[M_VAL] = 'val',
|
|
65
|
+
[M_KEYPRE] = 'key:pre',
|
|
66
|
+
[M_KEYPOST] = 'key:post',
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
-- Special strings.
|
|
70
|
+
local S_BKEY = '`$KEY`'
|
|
71
|
+
local S_BANNO = '`$ANNO`'
|
|
72
|
+
local S_BEXACT = '`$EXACT`'
|
|
73
|
+
local S_BVAL = '`$VAL`'
|
|
74
|
+
|
|
75
|
+
local S_DKEY = '$KEY'
|
|
76
|
+
local S_DTOP = '$TOP'
|
|
77
|
+
local S_DERRS = '$ERRS'
|
|
78
|
+
local S_DSPEC = '$SPEC'
|
|
79
|
+
|
|
80
|
+
-- General strings.
|
|
81
|
+
local S_list = 'list'
|
|
82
|
+
local S_base = 'base'
|
|
83
|
+
local S_boolean = 'boolean'
|
|
84
|
+
local S_function = 'function'
|
|
85
|
+
local S_symbol = 'symbol'
|
|
86
|
+
local S_instance = 'instance'
|
|
87
|
+
local S_key = 'key'
|
|
88
|
+
local S_any = 'any'
|
|
89
|
+
local S_nil = 'nil'
|
|
90
|
+
local S_null = 'null'
|
|
91
|
+
local S_number = 'number'
|
|
92
|
+
local S_object = 'object'
|
|
93
|
+
local S_string = 'string'
|
|
94
|
+
local S_decimal = 'decimal'
|
|
95
|
+
local S_integer = 'integer'
|
|
96
|
+
local S_map = 'map'
|
|
97
|
+
local S_scalar = 'scalar'
|
|
98
|
+
local S_node = 'node'
|
|
99
|
+
|
|
100
|
+
-- Character strings.
|
|
101
|
+
local S_BT = '`'
|
|
102
|
+
local S_CN = ':'
|
|
103
|
+
local S_CS = ']'
|
|
104
|
+
local S_DS = '$'
|
|
105
|
+
local S_DT = '.'
|
|
106
|
+
local S_FS = '/'
|
|
107
|
+
local S_KEY = 'KEY'
|
|
108
|
+
local S_MT = ''
|
|
109
|
+
local S_OS = '['
|
|
110
|
+
local S_SP = ' '
|
|
111
|
+
local S_CM = ','
|
|
112
|
+
local S_VIZ = ': '
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
-- Types (bit flags)
|
|
116
|
+
-- Using explicit bit positions to match TS implementation
|
|
117
|
+
local T_any = (1 << 31) - 1 -- All bits set
|
|
118
|
+
local T_noval = 1 << 30 -- Property absent, undefined
|
|
119
|
+
local T_boolean = 1 << 29
|
|
120
|
+
local T_decimal = 1 << 28
|
|
121
|
+
local T_integer = 1 << 27
|
|
122
|
+
local T_number = 1 << 26
|
|
123
|
+
local T_string = 1 << 25
|
|
124
|
+
local T_function = 1 << 24
|
|
125
|
+
local T_symbol = 1 << 23
|
|
126
|
+
local T_null = 1 << 22 -- Actual JSON null value
|
|
127
|
+
-- gap of 7
|
|
128
|
+
local T_list = 1 << 14
|
|
129
|
+
local T_map = 1 << 13
|
|
130
|
+
local T_instance = 1 << 12
|
|
131
|
+
-- gap of 4
|
|
132
|
+
local T_scalar = 1 << 7
|
|
133
|
+
local T_node = 1 << 6
|
|
134
|
+
|
|
135
|
+
local TYPENAME = {
|
|
136
|
+
S_any,
|
|
137
|
+
S_nil,
|
|
138
|
+
S_boolean,
|
|
139
|
+
S_decimal,
|
|
140
|
+
S_integer,
|
|
141
|
+
S_number,
|
|
142
|
+
S_string,
|
|
143
|
+
S_function,
|
|
144
|
+
S_symbol,
|
|
145
|
+
S_null,
|
|
146
|
+
'', '', '',
|
|
147
|
+
'', '', '', '',
|
|
148
|
+
S_list,
|
|
149
|
+
S_map,
|
|
150
|
+
S_instance,
|
|
151
|
+
'', '', '', '',
|
|
152
|
+
S_scalar,
|
|
153
|
+
S_node,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
-- The standard undefined value for this language.
|
|
158
|
+
local NONE = nil
|
|
159
|
+
|
|
160
|
+
-- Private markers
|
|
161
|
+
local SKIP = { ['`$SKIP`'] = true }
|
|
162
|
+
local DELETE = { ['`$DELETE`'] = true }
|
|
163
|
+
|
|
164
|
+
local MAXDEPTH = 32
|
|
165
|
+
|
|
166
|
+
----------------------------------------------------------
|
|
167
|
+
-- Forward declarations to work around the lack of function hoisting
|
|
168
|
+
----------------------------------------------------------
|
|
169
|
+
local _injectstr
|
|
170
|
+
local _injecthandler
|
|
171
|
+
local _validatehandler
|
|
172
|
+
local _invalidTypeMsg
|
|
173
|
+
local _validation
|
|
174
|
+
local ismap
|
|
175
|
+
local islist
|
|
176
|
+
local getpath
|
|
177
|
+
local setprop
|
|
178
|
+
local delprop
|
|
179
|
+
local checkPlacement
|
|
180
|
+
local injectorArgs
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
-- Return type string for narrowest type.
|
|
184
|
+
local function typename(t)
|
|
185
|
+
-- Math.clz32 equivalent: count leading zeros in a 32-bit integer
|
|
186
|
+
local function clz32(x)
|
|
187
|
+
if x == 0 then return 32 end
|
|
188
|
+
local n = 0
|
|
189
|
+
if (x & 0xFFFF0000) == 0 then n = n + 16; x = x << 16 end
|
|
190
|
+
if (x & 0xFF000000) == 0 then n = n + 8; x = x << 8 end
|
|
191
|
+
if (x & 0xF0000000) == 0 then n = n + 4; x = x << 4 end
|
|
192
|
+
if (x & 0xC0000000) == 0 then n = n + 2; x = x << 2 end
|
|
193
|
+
if (x & 0x80000000) == 0 then n = n + 1 end
|
|
194
|
+
return n
|
|
195
|
+
end
|
|
196
|
+
local idx = clz32(t) + 1 -- 1-based index
|
|
197
|
+
if idx >= 1 and idx <= #TYPENAME then
|
|
198
|
+
return TYPENAME[idx]
|
|
199
|
+
end
|
|
200
|
+
return TYPENAME[1] -- S_any
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
-- Value is a node - defined, and a map (hash) or list (array).
|
|
204
|
+
-- @param val (any) The value to check
|
|
205
|
+
-- @return (boolean) True if value is a node
|
|
206
|
+
local function isnode(val)
|
|
207
|
+
if val == nil then
|
|
208
|
+
return false
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
return ismap(val) or islist(val)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
-- Value is a defined map (hash) with string keys.
|
|
216
|
+
-- @param val (any) The value to check
|
|
217
|
+
-- @return (boolean) True if value is a map
|
|
218
|
+
ismap = function(val)
|
|
219
|
+
-- Check if the value is a table
|
|
220
|
+
if type(val) ~= "table" or
|
|
221
|
+
(getmetatable(val) and getmetatable(val).__jsontype == "array") then
|
|
222
|
+
return false
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
-- Check for explicit object metatable
|
|
226
|
+
if getmetatable(val) and getmetatable(val).__jsontype == "object" then
|
|
227
|
+
return true
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
-- Iterate over the table to check if it has string keys
|
|
231
|
+
for k, _ in pairs(val) do
|
|
232
|
+
if type(k) ~= "string" then
|
|
233
|
+
return false
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
return true
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
-- Value is a defined list (array) with integer keys (indexes).
|
|
242
|
+
-- @param val (any) The value to check
|
|
243
|
+
-- @return (boolean) True if value is a list
|
|
244
|
+
islist = function(val)
|
|
245
|
+
-- First check metatable indicators (preferred approach)
|
|
246
|
+
if getmetatable(val) and ((getmetatable(val).__jsontype == "array") or
|
|
247
|
+
(getmetatable(val).__jsontype and getmetatable(val).__jsontype.type ==
|
|
248
|
+
"array")) then
|
|
249
|
+
return true
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
-- Check if it's a table
|
|
253
|
+
if type(val) ~= "table" or
|
|
254
|
+
(getmetatable(val) and getmetatable(val).__jsontype == "object") then
|
|
255
|
+
return false
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
-- Count total elements and max integer key
|
|
259
|
+
local count = 0
|
|
260
|
+
local max = 0
|
|
261
|
+
for k, _ in pairs(val) do
|
|
262
|
+
if type(k) == S_number then
|
|
263
|
+
if k > max then
|
|
264
|
+
max = k
|
|
265
|
+
end
|
|
266
|
+
count = count + 1
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
-- Check if all keys are consecutive integers starting from 1
|
|
271
|
+
return count > 0 and max == count
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
-- Value is a defined string (non-empty) or integer key.
|
|
276
|
+
-- @param key (any) The key to check
|
|
277
|
+
-- @return (boolean) True if key is valid
|
|
278
|
+
local function iskey(key)
|
|
279
|
+
local keytype = type(key)
|
|
280
|
+
return (keytype == S_string and key ~= S_MT and key ~= S_null) or keytype ==
|
|
281
|
+
S_number
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
-- Get a defined value. Returns alt if val is nil.
|
|
286
|
+
local function getdef(val, alt)
|
|
287
|
+
if nil == val then
|
|
288
|
+
return alt
|
|
289
|
+
end
|
|
290
|
+
return val
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
-- The integer size of the value.
|
|
295
|
+
local function size(val)
|
|
296
|
+
if islist(val) then
|
|
297
|
+
return #val
|
|
298
|
+
elseif ismap(val) then
|
|
299
|
+
local count = 0
|
|
300
|
+
for _ in pairs(val) do count = count + 1 end
|
|
301
|
+
return count
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
local valtype = type(val)
|
|
305
|
+
|
|
306
|
+
if S_string == valtype then
|
|
307
|
+
return #val
|
|
308
|
+
elseif S_number == valtype then
|
|
309
|
+
return math.floor(val)
|
|
310
|
+
elseif S_boolean == valtype then
|
|
311
|
+
return val and 1 or 0
|
|
312
|
+
else
|
|
313
|
+
return 0
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
-- Check for an "empty" value - nil, empty string, array, object.
|
|
319
|
+
-- @param val (any) The value to check
|
|
320
|
+
-- @return (boolean) True if value is empty
|
|
321
|
+
local function isempty(val)
|
|
322
|
+
-- Check if the value is nil
|
|
323
|
+
if val == nil or val == S_null then
|
|
324
|
+
return true
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
-- Check if the value is an empty string
|
|
328
|
+
if type(val) == "string" and val == S_MT then
|
|
329
|
+
return true
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
-- Check if the value is an empty table (array or map)
|
|
333
|
+
if type(val) == "table" then
|
|
334
|
+
return next(val) == nil
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
-- If none of the above, the value is not empty
|
|
338
|
+
return false
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
-- Value is a function.
|
|
343
|
+
-- @param val (any) The value to check
|
|
344
|
+
-- @return (boolean) True if value is a function
|
|
345
|
+
local function isfunc(val)
|
|
346
|
+
return type(val) == 'function'
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
-- Determine the type of a value as a bit code.
|
|
351
|
+
-- @param value (any) The value to check
|
|
352
|
+
-- @return (number) The type as a bit flag
|
|
353
|
+
local function typify(value)
|
|
354
|
+
if value == nil then
|
|
355
|
+
return T_null
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
local luatype = type(value)
|
|
359
|
+
|
|
360
|
+
if luatype == S_number then
|
|
361
|
+
if value ~= value then -- NaN check
|
|
362
|
+
return T_noval
|
|
363
|
+
elseif math.type(value) == 'integer' or (value % 1 == 0) then
|
|
364
|
+
return T_scalar | T_number | T_integer
|
|
365
|
+
else
|
|
366
|
+
return T_scalar | T_number | T_decimal
|
|
367
|
+
end
|
|
368
|
+
elseif luatype == S_string then
|
|
369
|
+
return T_scalar | T_string
|
|
370
|
+
elseif luatype == S_boolean then
|
|
371
|
+
return T_scalar | T_boolean
|
|
372
|
+
elseif luatype == S_function then
|
|
373
|
+
return T_scalar | T_function
|
|
374
|
+
elseif luatype == 'table' then
|
|
375
|
+
if islist(value) then
|
|
376
|
+
return T_node | T_list
|
|
377
|
+
elseif ismap(value) then
|
|
378
|
+
return T_node | T_map
|
|
379
|
+
end
|
|
380
|
+
return T_node | T_map
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
-- Anything else is considered T_any
|
|
384
|
+
return T_any
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
-- Safely get a property of a node. Nil arguments return nil.
|
|
389
|
+
-- If the key is not found, return the alternative value, if any.
|
|
390
|
+
-- @param val (any) The parent object/table
|
|
391
|
+
-- @param key (any) The key to access
|
|
392
|
+
-- @param alt (any) The alternative value if key not found
|
|
393
|
+
-- @return (any) The property value or alternative
|
|
394
|
+
local function getprop(val, key, alt)
|
|
395
|
+
-- Handle nil arguments
|
|
396
|
+
if val == NONE or key == NONE then
|
|
397
|
+
return alt
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
local out = nil
|
|
401
|
+
|
|
402
|
+
-- Handle tables (maps and arrays in Lua)
|
|
403
|
+
if type(val) == "table" then
|
|
404
|
+
-- Convert key to string if it's a number
|
|
405
|
+
local lookup_key = key
|
|
406
|
+
if type(key) == "number" then
|
|
407
|
+
-- Lua arrays are 1-based
|
|
408
|
+
lookup_key = tostring(math.floor(key))
|
|
409
|
+
elseif type(key) ~= "string" then
|
|
410
|
+
-- Convert other types to string
|
|
411
|
+
lookup_key = tostring(key)
|
|
412
|
+
end
|
|
413
|
+
if islist(val) then
|
|
414
|
+
-- Lua arrays are 1-based, so we need to adjust the index
|
|
415
|
+
for i = 1, #val do
|
|
416
|
+
local zero_based_index = i - 1
|
|
417
|
+
if lookup_key == tostring(zero_based_index) then
|
|
418
|
+
out = val[i]
|
|
419
|
+
break
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
else
|
|
423
|
+
out = val[lookup_key]
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
-- Return alternative if out is nil
|
|
428
|
+
if out == nil then
|
|
429
|
+
return alt
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
return out
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
-- Get a list element. The key should be an integer, or a string
|
|
437
|
+
-- that can parse to an integer only. Negative integers count from the end of the list.
|
|
438
|
+
local function getelem(val, key, alt)
|
|
439
|
+
local out = NONE
|
|
440
|
+
|
|
441
|
+
if NONE == val or NONE == key then
|
|
442
|
+
return alt
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
if islist(val) then
|
|
446
|
+
local nkey = tonumber(key)
|
|
447
|
+
if nkey ~= nil and nkey == math.floor(nkey) then
|
|
448
|
+
if nkey < 0 then
|
|
449
|
+
nkey = #val + nkey
|
|
450
|
+
end
|
|
451
|
+
-- Convert 0-based to 1-based
|
|
452
|
+
out = val[nkey + 1]
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
if NONE == out then
|
|
457
|
+
if NONE ~= alt and type(alt) == S_function then
|
|
458
|
+
return alt()
|
|
459
|
+
end
|
|
460
|
+
return alt
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
return out
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
-- Convert different types of keys to string representation.
|
|
468
|
+
-- String keys are returned as is.
|
|
469
|
+
-- Number keys are converted to strings.
|
|
470
|
+
-- Floats are truncated to integers.
|
|
471
|
+
-- Booleans, objects, arrays, null, undefined all return empty string.
|
|
472
|
+
-- @param key (any) The key to convert
|
|
473
|
+
-- @return (string) The string representation of the key
|
|
474
|
+
local function strkey(key)
|
|
475
|
+
if key == NONE or key == S_null then
|
|
476
|
+
return S_MT
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
if type(key) == S_string then
|
|
480
|
+
return key
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
if type(key) == S_boolean then
|
|
484
|
+
return S_MT
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
if type(key) == S_number then
|
|
488
|
+
return key % 1 == 0 and tostring(key) or tostring(math.floor(key))
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
return S_MT
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
-- Sorted keys of a map, or indexes of a list.
|
|
496
|
+
-- @param val (any) The object or array to get keys from
|
|
497
|
+
-- @return (table) Array of keys as strings
|
|
498
|
+
local function keysof(val)
|
|
499
|
+
if not isnode(val) then
|
|
500
|
+
return {}
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
if ismap(val) then
|
|
504
|
+
-- For maps, collect all keys and sort them
|
|
505
|
+
local keys = {}
|
|
506
|
+
for k, _ in pairs(val) do
|
|
507
|
+
table.insert(keys, k)
|
|
508
|
+
end
|
|
509
|
+
table.sort(keys)
|
|
510
|
+
return keys
|
|
511
|
+
else
|
|
512
|
+
-- For lists, create array of stringified indices (0-based to match JS/Go)
|
|
513
|
+
local indexes = {}
|
|
514
|
+
for i = 1, #val do
|
|
515
|
+
-- Subtract 1 to convert from Lua's 1-based to 0-based indexing
|
|
516
|
+
table.insert(indexes, tostring(i - 1))
|
|
517
|
+
end
|
|
518
|
+
return indexes
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
-- Value of property with name key in node val is defined.
|
|
524
|
+
-- @param val (any) The object to check
|
|
525
|
+
-- @param key (any) The key to check
|
|
526
|
+
-- @return (boolean) True if key exists in val
|
|
527
|
+
local function haskey(val, key)
|
|
528
|
+
return getprop(val, key) ~= NONE
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
-- List the sorted keys of a map or list as an array of tuples of the form {key, value}
|
|
533
|
+
-- @param val (any) The object or array to convert to key-value pairs
|
|
534
|
+
-- @return (table) Array of {key, value} pairs
|
|
535
|
+
local function items(val)
|
|
536
|
+
if type(val) ~= "table" then
|
|
537
|
+
return {}
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
local result = {}
|
|
541
|
+
|
|
542
|
+
if islist(val) then
|
|
543
|
+
-- Handle array-like tables (0-based string keys like JS Object.entries)
|
|
544
|
+
for i, v in ipairs(val) do
|
|
545
|
+
table.insert(result, { tostring(i - 1), v })
|
|
546
|
+
end
|
|
547
|
+
else
|
|
548
|
+
local keys = {}
|
|
549
|
+
for k in pairs(val) do
|
|
550
|
+
table.insert(keys, k)
|
|
551
|
+
end
|
|
552
|
+
table.sort(keys)
|
|
553
|
+
|
|
554
|
+
for _, k in ipairs(keys) do
|
|
555
|
+
table.insert(result, { k, val[k] })
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
return result
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
-- Filter item values using check function.
|
|
564
|
+
-- check receives {key, value} pairs (1-indexed: [1]=key, [2]=value).
|
|
565
|
+
-- Returns array of values where check returns true.
|
|
566
|
+
local function filter(val, check)
|
|
567
|
+
local all = items(val)
|
|
568
|
+
local numall = size(all)
|
|
569
|
+
local out = {}
|
|
570
|
+
setmetatable(out, { __jsontype = "array" })
|
|
571
|
+
for i = 1, numall do
|
|
572
|
+
if check(all[i]) then
|
|
573
|
+
table.insert(out, all[i][2])
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
return out
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
-- Escape regular expression.
|
|
581
|
+
-- @param s (string) The string to escape
|
|
582
|
+
-- @return (string) The escaped string
|
|
583
|
+
local function escre(s)
|
|
584
|
+
s = s or S_MT
|
|
585
|
+
local result, _ = s:gsub("([.*+?^${}%(%)%[%]\\|])", "\\%1")
|
|
586
|
+
return result
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
-- Escape URLs.
|
|
591
|
+
-- @param s (string) The string to escape
|
|
592
|
+
-- @return (string) The URL-encoded string
|
|
593
|
+
local function escurl(s)
|
|
594
|
+
s = s or S_MT
|
|
595
|
+
-- Match encodeURIComponent: preserve A-Za-z0-9 - _ . ~ ! ' ( ) *
|
|
596
|
+
local result, _ = s:gsub("([^%w%-_%.~!'%(%)%*])", function(c)
|
|
597
|
+
return string.format("%%%02X", string.byte(c))
|
|
598
|
+
end)
|
|
599
|
+
return result
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
-- Replace a search string (all), or a pattern, in a source string.
|
|
604
|
+
local function replace(s, from, to)
|
|
605
|
+
local rs = s
|
|
606
|
+
local ts = typify(s)
|
|
607
|
+
if 0 == (T_string & ts) then
|
|
608
|
+
rs = stringify(s)
|
|
609
|
+
elseif 0 < ((T_noval | T_null) & ts) then
|
|
610
|
+
rs = S_MT
|
|
611
|
+
else
|
|
612
|
+
rs = stringify(s)
|
|
613
|
+
end
|
|
614
|
+
if type(from) == 'string' then
|
|
615
|
+
-- Plain string replacement (all occurrences)
|
|
616
|
+
return (rs:gsub(escre(from), to))
|
|
617
|
+
else
|
|
618
|
+
-- Pattern replacement
|
|
619
|
+
return (rs:gsub(from, to))
|
|
620
|
+
end
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
-- Return a sub-array. Start and end are 0-based, end is exclusive.
|
|
625
|
+
-- For numbers: clamp between start and end-1.
|
|
626
|
+
-- For strings: substring from start to end.
|
|
627
|
+
-- For lists: sub-list from start to end.
|
|
628
|
+
-- For other types: return val unchanged (if no start given).
|
|
629
|
+
local function slice(val, start, endidx, mutate)
|
|
630
|
+
-- Number clamping: slice(num, min) or slice(num, min, max)
|
|
631
|
+
if S_number == type(val) then
|
|
632
|
+
local minv = (start ~= nil and S_number == type(start)) and start or (-1 / 0)
|
|
633
|
+
local maxv = (endidx ~= nil and S_number == type(endidx)) and (endidx - 1) or (1 / 0)
|
|
634
|
+
return math.min(math.max(val, minv), maxv)
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
local vlen = size(val)
|
|
638
|
+
|
|
639
|
+
if endidx ~= nil and start == nil then
|
|
640
|
+
start = 0
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
if start ~= nil then
|
|
644
|
+
if start < 0 then
|
|
645
|
+
endidx = vlen + start
|
|
646
|
+
if endidx < 0 then endidx = 0 end
|
|
647
|
+
start = 0
|
|
648
|
+
elseif endidx ~= nil then
|
|
649
|
+
if endidx < 0 then
|
|
650
|
+
endidx = vlen + endidx
|
|
651
|
+
if endidx < 0 then endidx = 0 end
|
|
652
|
+
elseif vlen < endidx then
|
|
653
|
+
endidx = vlen
|
|
654
|
+
end
|
|
655
|
+
else
|
|
656
|
+
endidx = vlen
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
if vlen < start then
|
|
660
|
+
start = vlen
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
if -1 < start and start <= endidx and endidx <= vlen then
|
|
664
|
+
if islist(val) then
|
|
665
|
+
if mutate then
|
|
666
|
+
local j = start + 1
|
|
667
|
+
for i = 1, endidx - start do
|
|
668
|
+
val[i] = val[j]
|
|
669
|
+
j = j + 1
|
|
670
|
+
end
|
|
671
|
+
for i = endidx - start + 1, #val do
|
|
672
|
+
val[i] = nil
|
|
673
|
+
end
|
|
674
|
+
return val
|
|
675
|
+
else
|
|
676
|
+
local result = {}
|
|
677
|
+
setmetatable(result, { __jsontype = "array" })
|
|
678
|
+
for i = start + 1, endidx do
|
|
679
|
+
table.insert(result, val[i])
|
|
680
|
+
end
|
|
681
|
+
return result
|
|
682
|
+
end
|
|
683
|
+
elseif S_string == type(val) then
|
|
684
|
+
return string.sub(val, start + 1, endidx)
|
|
685
|
+
end
|
|
686
|
+
else
|
|
687
|
+
if islist(val) then
|
|
688
|
+
if mutate then
|
|
689
|
+
for i = 1, #val do val[i] = nil end
|
|
690
|
+
return val
|
|
691
|
+
end
|
|
692
|
+
return setmetatable({}, { __jsontype = "array" })
|
|
693
|
+
elseif S_string == type(val) then
|
|
694
|
+
return S_MT
|
|
695
|
+
end
|
|
696
|
+
end
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
return val
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
-- Flatten nested lists to a given depth.
|
|
704
|
+
local function flatten(val, depth)
|
|
705
|
+
if not islist(val) then
|
|
706
|
+
return val
|
|
707
|
+
end
|
|
708
|
+
depth = depth or 1
|
|
709
|
+
local result = {}
|
|
710
|
+
setmetatable(result, { __jsontype = "array" })
|
|
711
|
+
|
|
712
|
+
for _, item in ipairs(val) do
|
|
713
|
+
if (islist(item) or (type(item) == 'table' and next(item) == nil)) and depth > 0 then
|
|
714
|
+
local sub = flatten(item, depth - 1)
|
|
715
|
+
for _, v in ipairs(sub) do
|
|
716
|
+
table.insert(result, v)
|
|
717
|
+
end
|
|
718
|
+
else
|
|
719
|
+
table.insert(result, item)
|
|
720
|
+
end
|
|
721
|
+
end
|
|
722
|
+
return result
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
-- Pad a string or number.
|
|
727
|
+
-- Positive padlen = right-pad (padEnd), negative padlen = left-pad (padStart).
|
|
728
|
+
local function pad(val, padlen, padchar)
|
|
729
|
+
val = S_string == type(val) and val or stringify(val)
|
|
730
|
+
padlen = padlen or 44
|
|
731
|
+
padchar = padchar or S_SP
|
|
732
|
+
if #padchar > 1 then padchar = padchar:sub(1, 1) end
|
|
733
|
+
|
|
734
|
+
if padlen >= 0 then
|
|
735
|
+
-- Right-pad (padEnd)
|
|
736
|
+
while #val < padlen do
|
|
737
|
+
val = val .. padchar
|
|
738
|
+
end
|
|
739
|
+
else
|
|
740
|
+
-- Left-pad (padStart)
|
|
741
|
+
local abslen = -padlen
|
|
742
|
+
while #val < abslen do
|
|
743
|
+
val = padchar .. val
|
|
744
|
+
end
|
|
745
|
+
end
|
|
746
|
+
return val
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
-- Delete a property from a node.
|
|
751
|
+
delprop = function(parent, key)
|
|
752
|
+
if not iskey(key) then
|
|
753
|
+
return parent
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
if ismap(parent) then
|
|
757
|
+
key = tostring(key)
|
|
758
|
+
parent[key] = nil
|
|
759
|
+
elseif islist(parent) then
|
|
760
|
+
local keyI = tonumber(key)
|
|
761
|
+
if keyI ~= nil then
|
|
762
|
+
keyI = math.floor(keyI)
|
|
763
|
+
-- Convert 0-based to 1-based
|
|
764
|
+
local luaIndex = keyI + 1
|
|
765
|
+
if luaIndex >= 1 and luaIndex <= #parent then
|
|
766
|
+
table.remove(parent, luaIndex)
|
|
767
|
+
end
|
|
768
|
+
end
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
return parent
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
-- Build a JSON map from alternating key, value arguments.
|
|
776
|
+
local function jm(...)
|
|
777
|
+
local kv = { ... }
|
|
778
|
+
local kvsize = size(kv)
|
|
779
|
+
local o = {}
|
|
780
|
+
local i = 0
|
|
781
|
+
while i < kvsize do
|
|
782
|
+
local k = getprop(kv, i, '$KEY' .. i)
|
|
783
|
+
k = S_string == type(k) and k or stringify(k)
|
|
784
|
+
o[k] = getprop(kv, i + 1, nil)
|
|
785
|
+
i = i + 2
|
|
786
|
+
end
|
|
787
|
+
return o
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
-- Define a JSON Array using function arguments.
|
|
792
|
+
local function jt(...)
|
|
793
|
+
local v = { ... }
|
|
794
|
+
local vsize = size(v)
|
|
795
|
+
local a = {}
|
|
796
|
+
setmetatable(a, { __jsontype = "array" })
|
|
797
|
+
for i = 0, vsize - 1 do
|
|
798
|
+
a[i + 1] = getprop(v, i, nil)
|
|
799
|
+
end
|
|
800
|
+
return a
|
|
801
|
+
end
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
-- Concatenate strings, merging separator char as needed.
|
|
805
|
+
-- Default separator is comma. When url=true, preserve protocol slashes.
|
|
806
|
+
-- @param arr (table) Array of parts to join
|
|
807
|
+
-- @param sep (string) Separator character (default: ',')
|
|
808
|
+
-- @param url (boolean) URL mode preserves leading protocol slashes
|
|
809
|
+
-- @return (string) The combined string
|
|
810
|
+
local function join(arr, sep, url)
|
|
811
|
+
if not islist(arr) then
|
|
812
|
+
return S_MT
|
|
813
|
+
end
|
|
814
|
+
|
|
815
|
+
local arrsize = size(arr)
|
|
816
|
+
local sepdef = getdef(sep, S_CM)
|
|
817
|
+
|
|
818
|
+
-- Escape separator for Lua patterns
|
|
819
|
+
local function lua_pat_escape(c)
|
|
820
|
+
return c:gsub("([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1")
|
|
821
|
+
end
|
|
822
|
+
local seppat = (size(sepdef) == 1) and lua_pat_escape(sepdef) or nil
|
|
823
|
+
|
|
824
|
+
-- Step 1: Filter to only string, non-empty values, keeping original indices
|
|
825
|
+
local string_items = {}
|
|
826
|
+
for i = 1, #arr do
|
|
827
|
+
local v = arr[i]
|
|
828
|
+
local ts = typify(v)
|
|
829
|
+
if (0 < (T_string & ts)) and v ~= S_MT and v ~= S_null then
|
|
830
|
+
table.insert(string_items, { i - 1, v }) -- 0-based index, value
|
|
831
|
+
end
|
|
832
|
+
end
|
|
833
|
+
|
|
834
|
+
-- Step 2: Process each element to clean separators
|
|
835
|
+
local processed = {}
|
|
836
|
+
for _, item in ipairs(string_items) do
|
|
837
|
+
local idx = item[1] -- 0-based original index
|
|
838
|
+
local s = item[2]
|
|
839
|
+
|
|
840
|
+
if seppat ~= nil and seppat ~= S_MT then
|
|
841
|
+
if url and idx == 0 then
|
|
842
|
+
-- First element in URL mode: strip trailing seps only
|
|
843
|
+
s = s:gsub(seppat .. "+$", S_MT)
|
|
844
|
+
else
|
|
845
|
+
if idx > 0 then
|
|
846
|
+
-- Strip leading seps
|
|
847
|
+
s = s:gsub("^" .. seppat .. "+", S_MT)
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
if idx < arrsize - 1 or not url then
|
|
851
|
+
-- Strip trailing seps
|
|
852
|
+
s = s:gsub(seppat .. "+$", S_MT)
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
-- Collapse multiple seps between non-sep chars
|
|
856
|
+
s = s:gsub("([^" .. seppat .. "])" .. seppat .. "+([^" .. seppat .. "])",
|
|
857
|
+
"%1" .. sepdef .. "%2")
|
|
858
|
+
end
|
|
859
|
+
end
|
|
860
|
+
|
|
861
|
+
if s ~= S_MT then
|
|
862
|
+
table.insert(processed, s)
|
|
863
|
+
end
|
|
864
|
+
end
|
|
865
|
+
|
|
866
|
+
return table.concat(processed, sepdef)
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
-- Safely stringify a value for humans (NOT JSON!)
|
|
871
|
+
-- Strings are returned as-is (not quoted).
|
|
872
|
+
-- @param val (any) The value to stringify
|
|
873
|
+
-- @param maxlen (number) Optional maximum length for result
|
|
874
|
+
-- @param pretty (boolean) Optional pretty mode with ANSI colors
|
|
875
|
+
-- @return (string) String representation of the value
|
|
876
|
+
local function stringify(val, maxlen, pretty)
|
|
877
|
+
local valstr = S_MT
|
|
878
|
+
pretty = pretty and true or false
|
|
879
|
+
|
|
880
|
+
if val == nil then
|
|
881
|
+
return pretty and '<>' or valstr
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
if type(val) == S_string then
|
|
885
|
+
valstr = val
|
|
886
|
+
else
|
|
887
|
+
local function sort_keys(t)
|
|
888
|
+
local keys = {}
|
|
889
|
+
for k in pairs(t) do
|
|
890
|
+
table.insert(keys, k)
|
|
891
|
+
end
|
|
892
|
+
table.sort(keys)
|
|
893
|
+
return keys
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
local function serialize(obj, seen)
|
|
897
|
+
seen = seen or {}
|
|
898
|
+
|
|
899
|
+
if type(obj) == 'table' and seen[obj] then
|
|
900
|
+
return '...'
|
|
901
|
+
end
|
|
902
|
+
|
|
903
|
+
local obj_type = type(obj)
|
|
904
|
+
|
|
905
|
+
if obj == nil then
|
|
906
|
+
return 'null'
|
|
907
|
+
elseif obj_type == S_number then
|
|
908
|
+
if obj ~= obj then return 'null' end -- NaN
|
|
909
|
+
-- Use integer representation for whole numbers
|
|
910
|
+
if obj % 1 == 0 then
|
|
911
|
+
return string.format('%d', obj)
|
|
912
|
+
end
|
|
913
|
+
return tostring(obj)
|
|
914
|
+
elseif obj_type == S_boolean then
|
|
915
|
+
return tostring(obj)
|
|
916
|
+
elseif obj_type == S_function then
|
|
917
|
+
return 'null'
|
|
918
|
+
elseif obj_type ~= 'table' then
|
|
919
|
+
return tostring(obj)
|
|
920
|
+
end
|
|
921
|
+
|
|
922
|
+
seen[obj] = true
|
|
923
|
+
|
|
924
|
+
local parts = {}
|
|
925
|
+
local is_arr = islist(obj)
|
|
926
|
+
|
|
927
|
+
if is_arr then
|
|
928
|
+
for i = 1, #obj do
|
|
929
|
+
table.insert(parts, serialize(obj[i], seen))
|
|
930
|
+
end
|
|
931
|
+
else
|
|
932
|
+
local keys = sort_keys(obj)
|
|
933
|
+
for _, k in ipairs(keys) do
|
|
934
|
+
table.insert(parts, k .. S_CN .. serialize(obj[k], seen))
|
|
935
|
+
end
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
seen[obj] = nil
|
|
939
|
+
|
|
940
|
+
if is_arr then
|
|
941
|
+
return S_OS .. table.concat(parts, ',') .. S_CS
|
|
942
|
+
else
|
|
943
|
+
return '{' .. table.concat(parts, ',') .. '}'
|
|
944
|
+
end
|
|
945
|
+
end
|
|
946
|
+
|
|
947
|
+
local success, result = pcall(function()
|
|
948
|
+
return serialize(val)
|
|
949
|
+
end)
|
|
950
|
+
|
|
951
|
+
if success then
|
|
952
|
+
valstr = result
|
|
953
|
+
else
|
|
954
|
+
valstr = '__STRINGIFY_FAILED__'
|
|
955
|
+
end
|
|
956
|
+
end
|
|
957
|
+
|
|
958
|
+
-- Handle maxlen
|
|
959
|
+
if maxlen ~= nil and maxlen > -1 then
|
|
960
|
+
if maxlen < #valstr then
|
|
961
|
+
valstr = string.sub(valstr, 1, maxlen - 3) .. '...'
|
|
962
|
+
end
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
if pretty then
|
|
966
|
+
local c = { 81, 118, 213, 39, 208, 201, 45, 190, 129, 51, 160, 121, 226, 33, 207, 69 }
|
|
967
|
+
local r = '\x1b[0m'
|
|
968
|
+
local d = 0
|
|
969
|
+
local function cc(n) return '\x1b[38;5;' .. n .. 'm' end
|
|
970
|
+
local o = cc(c[1])
|
|
971
|
+
local t = o
|
|
972
|
+
for i = 1, #valstr do
|
|
973
|
+
local ch = valstr:sub(i, i)
|
|
974
|
+
if ch == '{' or ch == S_OS then
|
|
975
|
+
d = d + 1
|
|
976
|
+
o = cc(c[(d % #c) + 1])
|
|
977
|
+
t = t .. o .. ch
|
|
978
|
+
elseif ch == '}' or ch == S_CS then
|
|
979
|
+
t = t .. o .. ch
|
|
980
|
+
d = d - 1
|
|
981
|
+
o = cc(c[(d % #c) + 1])
|
|
982
|
+
else
|
|
983
|
+
t = t .. o .. ch
|
|
984
|
+
end
|
|
985
|
+
end
|
|
986
|
+
return t .. r
|
|
987
|
+
end
|
|
988
|
+
|
|
989
|
+
return valstr
|
|
990
|
+
end
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
-- Convert a value to JSON string representation (matching JSON.stringify behavior).
|
|
994
|
+
local function jsonify(val, flags)
|
|
995
|
+
local str = S_null
|
|
996
|
+
|
|
997
|
+
if val ~= nil then
|
|
998
|
+
local ok, result = pcall(function()
|
|
999
|
+
local indent_size = getprop(flags, 'indent', 2)
|
|
1000
|
+
local offset = getprop(flags, 'offset', 0)
|
|
1001
|
+
|
|
1002
|
+
-- Recursive JSON serializer matching JSON.stringify(val, null, indent)
|
|
1003
|
+
local function ser(v, depth)
|
|
1004
|
+
if v == nil then
|
|
1005
|
+
return S_null
|
|
1006
|
+
elseif type(v) == S_boolean then
|
|
1007
|
+
return tostring(v)
|
|
1008
|
+
elseif type(v) == S_number then
|
|
1009
|
+
if v ~= v then return S_null end -- NaN
|
|
1010
|
+
if v % 1 == 0 then
|
|
1011
|
+
return string.format('%d', v)
|
|
1012
|
+
end
|
|
1013
|
+
return tostring(v)
|
|
1014
|
+
elseif type(v) == S_string then
|
|
1015
|
+
-- Escape string for JSON
|
|
1016
|
+
local escaped = v:gsub('\\', '\\\\'):gsub('"', '\\"')
|
|
1017
|
+
:gsub('\n', '\\n'):gsub('\r', '\\r'):gsub('\t', '\\t')
|
|
1018
|
+
return '"' .. escaped .. '"'
|
|
1019
|
+
elseif type(v) == S_function then
|
|
1020
|
+
return nil -- Functions are omitted in JSON
|
|
1021
|
+
elseif type(v) == 'table' then
|
|
1022
|
+
if islist(v) then
|
|
1023
|
+
if #v == 0 then
|
|
1024
|
+
return '[]'
|
|
1025
|
+
end
|
|
1026
|
+
local parts = {}
|
|
1027
|
+
for i = 1, #v do
|
|
1028
|
+
local sv = ser(v[i], depth + 1)
|
|
1029
|
+
table.insert(parts, sv or S_null)
|
|
1030
|
+
end
|
|
1031
|
+
if indent_size == 0 then
|
|
1032
|
+
return '[' .. table.concat(parts, ',') .. ']'
|
|
1033
|
+
end
|
|
1034
|
+
local pad_str = string.rep(' ', indent_size * (depth + 1) + offset)
|
|
1035
|
+
local close_pad = string.rep(' ', indent_size * depth + offset)
|
|
1036
|
+
return '[\n' .. pad_str .. table.concat(parts, ',\n' .. pad_str) ..
|
|
1037
|
+
'\n' .. close_pad .. ']'
|
|
1038
|
+
else
|
|
1039
|
+
-- Map/object
|
|
1040
|
+
local keys_list = keysof(v)
|
|
1041
|
+
if #keys_list == 0 then
|
|
1042
|
+
return '{}'
|
|
1043
|
+
end
|
|
1044
|
+
local parts = {}
|
|
1045
|
+
for _, k in ipairs(keys_list) do
|
|
1046
|
+
local sv = ser(v[k], depth + 1)
|
|
1047
|
+
if sv ~= nil then -- Skip undefined values
|
|
1048
|
+
table.insert(parts, '"' .. k .. '": ' .. sv)
|
|
1049
|
+
end
|
|
1050
|
+
end
|
|
1051
|
+
if #parts == 0 then
|
|
1052
|
+
return '{}'
|
|
1053
|
+
end
|
|
1054
|
+
if indent_size == 0 then
|
|
1055
|
+
return '{' .. table.concat(parts, ',') .. '}'
|
|
1056
|
+
end
|
|
1057
|
+
local pad_str = string.rep(' ', indent_size * (depth + 1) + offset)
|
|
1058
|
+
local close_pad = string.rep(' ', indent_size * depth + offset)
|
|
1059
|
+
return '{\n' .. pad_str .. table.concat(parts, ',\n' .. pad_str) ..
|
|
1060
|
+
'\n' .. close_pad .. '}'
|
|
1061
|
+
end
|
|
1062
|
+
end
|
|
1063
|
+
return S_null
|
|
1064
|
+
end
|
|
1065
|
+
|
|
1066
|
+
local jsonstr = ser(val, 0)
|
|
1067
|
+
if jsonstr == nil then
|
|
1068
|
+
return S_null
|
|
1069
|
+
end
|
|
1070
|
+
return jsonstr
|
|
1071
|
+
end)
|
|
1072
|
+
|
|
1073
|
+
if ok and result ~= nil then
|
|
1074
|
+
str = result
|
|
1075
|
+
else
|
|
1076
|
+
str = '__JSONIFY_FAILED__'
|
|
1077
|
+
end
|
|
1078
|
+
end
|
|
1079
|
+
|
|
1080
|
+
return str
|
|
1081
|
+
end
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
-- Build a human friendly path string.
|
|
1085
|
+
-- @param val (any) The path as array or string
|
|
1086
|
+
-- @param startin (number) Optional start index
|
|
1087
|
+
-- @param endin (number) Optional end index
|
|
1088
|
+
-- @return (string) Formatted path string
|
|
1089
|
+
local function pathify(val, startin, endin)
|
|
1090
|
+
local pathstr = NONE
|
|
1091
|
+
local path = NONE
|
|
1092
|
+
|
|
1093
|
+
-- Convert input to path array
|
|
1094
|
+
if islist(val) then
|
|
1095
|
+
path = val
|
|
1096
|
+
elseif type(val) == S_string then
|
|
1097
|
+
path = { val }
|
|
1098
|
+
setmetatable(path, {
|
|
1099
|
+
__jsontype = "array"
|
|
1100
|
+
})
|
|
1101
|
+
elseif type(val) == S_number then
|
|
1102
|
+
path = { val }
|
|
1103
|
+
setmetatable(path, {
|
|
1104
|
+
__jsontype = "array"
|
|
1105
|
+
})
|
|
1106
|
+
end
|
|
1107
|
+
|
|
1108
|
+
-- Calculate start and end indices
|
|
1109
|
+
local start = startin == nil and 0 or startin >= 0 and startin or 0
|
|
1110
|
+
local endidx = endin == nil and 0 or endin >= 0 and endin or 0
|
|
1111
|
+
|
|
1112
|
+
if path ~= NONE and start >= 0 then
|
|
1113
|
+
-- Slice path array from start to end
|
|
1114
|
+
local sliced = {}
|
|
1115
|
+
for i = start + 1, #path - endidx do
|
|
1116
|
+
table.insert(sliced, path[i])
|
|
1117
|
+
end
|
|
1118
|
+
path = sliced
|
|
1119
|
+
|
|
1120
|
+
if #path == 0 then
|
|
1121
|
+
pathstr = '<root>'
|
|
1122
|
+
else
|
|
1123
|
+
-- Filter valid path elements using iskey
|
|
1124
|
+
local filtered = {}
|
|
1125
|
+
for _, p in ipairs(path) do
|
|
1126
|
+
if iskey(p) then
|
|
1127
|
+
table.insert(filtered, p)
|
|
1128
|
+
end
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
-- Map elements to strings with special handling
|
|
1132
|
+
local mapped = {}
|
|
1133
|
+
for _, p in ipairs(filtered) do
|
|
1134
|
+
if type(p) == S_number then
|
|
1135
|
+
-- Floor number and convert to string
|
|
1136
|
+
table.insert(mapped, S_MT .. tostring(math.floor(p)))
|
|
1137
|
+
else
|
|
1138
|
+
-- Replace dots with S_MT for strings
|
|
1139
|
+
local replacedP = string.gsub(p, "%.", S_MT)
|
|
1140
|
+
table.insert(mapped, replacedP)
|
|
1141
|
+
end
|
|
1142
|
+
end
|
|
1143
|
+
|
|
1144
|
+
-- Join with dots
|
|
1145
|
+
pathstr = table.concat(mapped, S_DT)
|
|
1146
|
+
end
|
|
1147
|
+
end
|
|
1148
|
+
|
|
1149
|
+
-- Handle unknown paths
|
|
1150
|
+
if pathstr == NONE then
|
|
1151
|
+
pathstr = '<unknown-path'
|
|
1152
|
+
if val == NONE then
|
|
1153
|
+
pathstr = pathstr .. S_MT
|
|
1154
|
+
else
|
|
1155
|
+
pathstr = pathstr .. (S_CN .. stringify(val, 47))
|
|
1156
|
+
end
|
|
1157
|
+
pathstr = pathstr .. '>'
|
|
1158
|
+
end
|
|
1159
|
+
|
|
1160
|
+
return pathstr
|
|
1161
|
+
end
|
|
1162
|
+
|
|
1163
|
+
|
|
1164
|
+
-- Set a value deep inside a node at a key path.
|
|
1165
|
+
local function setpath(store, path, val, injdef)
|
|
1166
|
+
local pathType = typify(path)
|
|
1167
|
+
|
|
1168
|
+
local parts
|
|
1169
|
+
if 0 < (T_list & pathType) then
|
|
1170
|
+
parts = path
|
|
1171
|
+
elseif 0 < (T_string & pathType) then
|
|
1172
|
+
parts = {}
|
|
1173
|
+
for part in string.gmatch(path, "([^%.]+)") do
|
|
1174
|
+
table.insert(parts, part)
|
|
1175
|
+
end
|
|
1176
|
+
elseif 0 < (T_number & pathType) then
|
|
1177
|
+
parts = { path }
|
|
1178
|
+
setmetatable(parts, { __jsontype = "array" })
|
|
1179
|
+
else
|
|
1180
|
+
return NONE
|
|
1181
|
+
end
|
|
1182
|
+
|
|
1183
|
+
local base = getprop(injdef, S_base)
|
|
1184
|
+
local numparts = size(parts)
|
|
1185
|
+
local parent = getprop(store, base, store)
|
|
1186
|
+
|
|
1187
|
+
for pI = 0, numparts - 2 do
|
|
1188
|
+
local partKey = getelem(parts, pI)
|
|
1189
|
+
local nextParent = getprop(parent, partKey)
|
|
1190
|
+
if not isnode(nextParent) then
|
|
1191
|
+
local nextKey = getelem(parts, pI + 1)
|
|
1192
|
+
if 0 < (T_number & typify(nextKey)) then
|
|
1193
|
+
nextParent = {}
|
|
1194
|
+
setmetatable(nextParent, { __jsontype = "array" })
|
|
1195
|
+
else
|
|
1196
|
+
nextParent = {}
|
|
1197
|
+
end
|
|
1198
|
+
setprop(parent, partKey, nextParent)
|
|
1199
|
+
end
|
|
1200
|
+
parent = nextParent
|
|
1201
|
+
end
|
|
1202
|
+
|
|
1203
|
+
local lastKey = getelem(parts, -1)
|
|
1204
|
+
|
|
1205
|
+
if type(val) == 'table' and val['`$DELETE`'] then
|
|
1206
|
+
delprop(parent, lastKey)
|
|
1207
|
+
else
|
|
1208
|
+
setprop(parent, lastKey, val)
|
|
1209
|
+
end
|
|
1210
|
+
|
|
1211
|
+
return parent
|
|
1212
|
+
end
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
-- Clone a JSON-like data structure.
|
|
1216
|
+
-- NOTE: function value references are copied, *not* cloned.
|
|
1217
|
+
-- @param val (any) The value to clone
|
|
1218
|
+
-- @param flags (table) Optional flags to control cloning behavior
|
|
1219
|
+
-- @return (any) Deep copy of the value
|
|
1220
|
+
local function clone(val, flags)
|
|
1221
|
+
-- Handle nil value
|
|
1222
|
+
if val == nil then
|
|
1223
|
+
return nil
|
|
1224
|
+
end
|
|
1225
|
+
|
|
1226
|
+
-- Initialize flags if not provided
|
|
1227
|
+
flags = flags or {}
|
|
1228
|
+
if flags.func == nil then
|
|
1229
|
+
flags.func = true
|
|
1230
|
+
end
|
|
1231
|
+
|
|
1232
|
+
-- Handle functions
|
|
1233
|
+
if type(val) == "function" then
|
|
1234
|
+
if flags.func then
|
|
1235
|
+
return val
|
|
1236
|
+
end
|
|
1237
|
+
return nil
|
|
1238
|
+
end
|
|
1239
|
+
|
|
1240
|
+
-- Handle tables (both arrays and objects)
|
|
1241
|
+
if type(val) == "table" then
|
|
1242
|
+
local refs = {} -- To store function references
|
|
1243
|
+
local new_table = {}
|
|
1244
|
+
|
|
1245
|
+
-- Get the original metatable if any
|
|
1246
|
+
local mt = getmetatable(val)
|
|
1247
|
+
|
|
1248
|
+
-- Clone table contents
|
|
1249
|
+
for k, v in pairs(val) do
|
|
1250
|
+
-- Handle function values specially
|
|
1251
|
+
if type(v) == "function" then
|
|
1252
|
+
if flags.func then
|
|
1253
|
+
refs[#refs + 1] = v
|
|
1254
|
+
new_table[k] = ("$FUNCTION:" .. #refs)
|
|
1255
|
+
end
|
|
1256
|
+
else
|
|
1257
|
+
new_table[k] = clone(v, flags)
|
|
1258
|
+
end
|
|
1259
|
+
end
|
|
1260
|
+
|
|
1261
|
+
-- If we have function references, we need to restore them
|
|
1262
|
+
if #refs > 0 then
|
|
1263
|
+
-- Replace function placeholders with actual functions
|
|
1264
|
+
for k, v in pairs(new_table) do
|
|
1265
|
+
if type(v) == "string" then
|
|
1266
|
+
local func_idx = v:match("^%$FUNCTION:(%d+)$")
|
|
1267
|
+
if func_idx then
|
|
1268
|
+
new_table[k] = refs[tonumber(func_idx)]
|
|
1269
|
+
end
|
|
1270
|
+
end
|
|
1271
|
+
end
|
|
1272
|
+
end
|
|
1273
|
+
|
|
1274
|
+
-- Restore the original metatable if it existed
|
|
1275
|
+
if mt then
|
|
1276
|
+
setmetatable(new_table, mt)
|
|
1277
|
+
end
|
|
1278
|
+
|
|
1279
|
+
return new_table
|
|
1280
|
+
end
|
|
1281
|
+
|
|
1282
|
+
-- For all other types (numbers, strings, booleans), return as is
|
|
1283
|
+
return val
|
|
1284
|
+
end
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
-- Safely set a property. Undefined arguments and invalid keys are ignored.
|
|
1288
|
+
-- Returns the (possibly modified) parent.
|
|
1289
|
+
-- If the parent is a list, and the key is negative, prepend the value.
|
|
1290
|
+
-- NOTE: If the key is above the list size, append the value; below, prepend.
|
|
1291
|
+
-- @param parent (table) The parent object or array
|
|
1292
|
+
-- @param key (any) The key to set
|
|
1293
|
+
-- @param val (any) The value to set
|
|
1294
|
+
-- @return (table) The modified parent
|
|
1295
|
+
setprop = function(parent, key, val)
|
|
1296
|
+
if not iskey(key) then
|
|
1297
|
+
return parent
|
|
1298
|
+
end
|
|
1299
|
+
|
|
1300
|
+
if ismap(parent) then
|
|
1301
|
+
key = tostring(key)
|
|
1302
|
+
parent[key] = val
|
|
1303
|
+
elseif islist(parent) then
|
|
1304
|
+
-- Ensure key is an integer
|
|
1305
|
+
local keyI = tonumber(key)
|
|
1306
|
+
|
|
1307
|
+
if keyI == nil then
|
|
1308
|
+
return parent
|
|
1309
|
+
end
|
|
1310
|
+
|
|
1311
|
+
keyI = math.floor(keyI)
|
|
1312
|
+
|
|
1313
|
+
-- Set or append value at position keyI
|
|
1314
|
+
if keyI >= 0 then
|
|
1315
|
+
-- Convert from 0-based indexing to Lua 1-based indexing
|
|
1316
|
+
local luaIndex = keyI + 1
|
|
1317
|
+
|
|
1318
|
+
-- Clamp: if index is beyond current length, append to end
|
|
1319
|
+
if luaIndex > #parent + 1 then
|
|
1320
|
+
luaIndex = #parent + 1
|
|
1321
|
+
end
|
|
1322
|
+
parent[luaIndex] = val
|
|
1323
|
+
-- Prepend value if keyI is negative
|
|
1324
|
+
else
|
|
1325
|
+
table.insert(parent, 1, val)
|
|
1326
|
+
end
|
|
1327
|
+
end
|
|
1328
|
+
|
|
1329
|
+
return parent
|
|
1330
|
+
end
|
|
1331
|
+
|
|
1332
|
+
|
|
1333
|
+
-- Walk a data structure depth first, applying a function to each value.
|
|
1334
|
+
-- @param val (any) The value to walk
|
|
1335
|
+
-- @param before (function) Applied before descending into a node
|
|
1336
|
+
-- @param after (function) Applied after descending into a node
|
|
1337
|
+
-- @param maxdepth (number) Maximum recursive depth (default MAXDEPTH)
|
|
1338
|
+
-- @param key (any) Current key (for recursive calls)
|
|
1339
|
+
-- @param parent (table) Current parent (for recursive calls)
|
|
1340
|
+
-- @param path (table) Current path (for recursive calls)
|
|
1341
|
+
-- @return (any) The transformed value
|
|
1342
|
+
local function walk(val, before, after, maxdepth,
|
|
1343
|
+
key, parent, path)
|
|
1344
|
+
if NONE == path then
|
|
1345
|
+
path = {}
|
|
1346
|
+
setmetatable(path, { __jsontype = "array" })
|
|
1347
|
+
end
|
|
1348
|
+
|
|
1349
|
+
local out
|
|
1350
|
+
if nil == before then
|
|
1351
|
+
out = val
|
|
1352
|
+
else
|
|
1353
|
+
out = before(key, val, parent, path)
|
|
1354
|
+
end
|
|
1355
|
+
|
|
1356
|
+
maxdepth = (maxdepth ~= nil and maxdepth >= 0) and maxdepth or MAXDEPTH
|
|
1357
|
+
if 0 == maxdepth or (path ~= nil and 0 < maxdepth and maxdepth <= #path) then
|
|
1358
|
+
return out
|
|
1359
|
+
end
|
|
1360
|
+
|
|
1361
|
+
if isnode(out) then
|
|
1362
|
+
for _, item in ipairs(items(out)) do
|
|
1363
|
+
local ckey, child = item[1], item[2]
|
|
1364
|
+
|
|
1365
|
+
local childPath = flatten({ getdef(path, {}), S_MT .. tostring(ckey) })
|
|
1366
|
+
setmetatable(childPath, { __jsontype = "array" })
|
|
1367
|
+
|
|
1368
|
+
setprop(out, ckey, walk(child, before, after, maxdepth, ckey, out, childPath))
|
|
1369
|
+
end
|
|
1370
|
+
end
|
|
1371
|
+
|
|
1372
|
+
if nil ~= after then
|
|
1373
|
+
out = after(key, out, parent, path)
|
|
1374
|
+
end
|
|
1375
|
+
|
|
1376
|
+
return out
|
|
1377
|
+
end
|
|
1378
|
+
|
|
1379
|
+
|
|
1380
|
+
-- Merge a list of values into each other. Later values have
|
|
1381
|
+
-- precedence. Nodes override scalars. Node kinds (list or map)
|
|
1382
|
+
-- override each other, and do *not* merge. The first element is
|
|
1383
|
+
-- modified.
|
|
1384
|
+
-- @param val (any) Array of values to merge
|
|
1385
|
+
-- @param maxdepth (number) Optional maximum depth for merge
|
|
1386
|
+
-- @return (any) The merged result
|
|
1387
|
+
local function merge(val, maxdepth)
|
|
1388
|
+
local md = slice(getdef(maxdepth, MAXDEPTH), 0)
|
|
1389
|
+
local out = NONE
|
|
1390
|
+
|
|
1391
|
+
-- Handle edge cases
|
|
1392
|
+
if not islist(val) then
|
|
1393
|
+
return val
|
|
1394
|
+
end
|
|
1395
|
+
|
|
1396
|
+
local list = val
|
|
1397
|
+
|
|
1398
|
+
-- Use rawlen or find max integer key to handle nil gaps in arrays.
|
|
1399
|
+
local lenlist = #list
|
|
1400
|
+
if lenlist == 0 then
|
|
1401
|
+
-- Check for nil gaps: find the actual max integer key.
|
|
1402
|
+
for k in pairs(list) do
|
|
1403
|
+
if type(k) == S_number and k > lenlist then
|
|
1404
|
+
lenlist = k
|
|
1405
|
+
end
|
|
1406
|
+
end
|
|
1407
|
+
end
|
|
1408
|
+
|
|
1409
|
+
if lenlist == 0 then
|
|
1410
|
+
return NONE
|
|
1411
|
+
elseif lenlist == 1 then
|
|
1412
|
+
return list[1]
|
|
1413
|
+
end
|
|
1414
|
+
|
|
1415
|
+
out = getprop(list, 0, {})
|
|
1416
|
+
|
|
1417
|
+
for oI = 2, lenlist do
|
|
1418
|
+
local obj = list[oI]
|
|
1419
|
+
|
|
1420
|
+
if not isnode(obj) then
|
|
1421
|
+
-- Nodes win
|
|
1422
|
+
out = obj
|
|
1423
|
+
else
|
|
1424
|
+
-- Current value at path end in overriding node.
|
|
1425
|
+
local cur = { out }
|
|
1426
|
+
|
|
1427
|
+
-- Current value at path end in destination node.
|
|
1428
|
+
local dst = { out }
|
|
1429
|
+
|
|
1430
|
+
local function before(key, bval, _parent, path)
|
|
1431
|
+
local pI = size(path)
|
|
1432
|
+
|
|
1433
|
+
if md <= pI then
|
|
1434
|
+
setprop(cur[pI], key, bval)
|
|
1435
|
+
|
|
1436
|
+
-- Scalars just override directly.
|
|
1437
|
+
elseif not isnode(bval) then
|
|
1438
|
+
cur[pI + 1] = bval
|
|
1439
|
+
|
|
1440
|
+
-- Descend into override node.
|
|
1441
|
+
else
|
|
1442
|
+
-- Descend into destination node using same key.
|
|
1443
|
+
dst[pI + 1] = 0 < pI and getprop(dst[pI], key) or dst[pI + 1]
|
|
1444
|
+
local tval = dst[pI + 1]
|
|
1445
|
+
|
|
1446
|
+
-- Destination empty, so create node (unless override is class instance).
|
|
1447
|
+
if NONE == tval and 0 == (T_instance & typify(bval)) then
|
|
1448
|
+
cur[pI + 1] = islist(bval) and
|
|
1449
|
+
setmetatable({}, { __jsontype = "array" }) or {}
|
|
1450
|
+
|
|
1451
|
+
-- Matching override and destination so continue with their values.
|
|
1452
|
+
elseif typify(bval) == typify(tval) then
|
|
1453
|
+
cur[pI + 1] = tval
|
|
1454
|
+
|
|
1455
|
+
-- Override wins.
|
|
1456
|
+
else
|
|
1457
|
+
cur[pI + 1] = bval
|
|
1458
|
+
-- No need to descend when override wins.
|
|
1459
|
+
bval = NONE
|
|
1460
|
+
end
|
|
1461
|
+
end
|
|
1462
|
+
|
|
1463
|
+
return bval
|
|
1464
|
+
end
|
|
1465
|
+
|
|
1466
|
+
local function after(key, _aval, _parent, path)
|
|
1467
|
+
local cI = size(path)
|
|
1468
|
+
local target = cur[cI]
|
|
1469
|
+
local value = cur[cI + 1]
|
|
1470
|
+
|
|
1471
|
+
setprop(target, key, value)
|
|
1472
|
+
return value
|
|
1473
|
+
end
|
|
1474
|
+
|
|
1475
|
+
-- Walk overriding node, creating paths in output as needed.
|
|
1476
|
+
out = walk(obj, before, after, maxdepth)
|
|
1477
|
+
end
|
|
1478
|
+
end
|
|
1479
|
+
|
|
1480
|
+
if 0 == md then
|
|
1481
|
+
out = getelem(list, -1)
|
|
1482
|
+
out = islist(out) and setmetatable({}, { __jsontype = "array" })
|
|
1483
|
+
or ismap(out) and {} or out
|
|
1484
|
+
end
|
|
1485
|
+
|
|
1486
|
+
return out
|
|
1487
|
+
end
|
|
1488
|
+
|
|
1489
|
+
|
|
1490
|
+
-- Get a value deep inside a node using a key path.
|
|
1491
|
+
-- @param store (table) The data store to search in
|
|
1492
|
+
-- @param path (string|table|number) The path to the value
|
|
1493
|
+
-- @param injdef (table) Optional injection definition
|
|
1494
|
+
-- @return (any) The value at the path
|
|
1495
|
+
getpath = function(store, path, injdef)
|
|
1496
|
+
-- Operate on a string array.
|
|
1497
|
+
local parts
|
|
1498
|
+
if islist(path) then
|
|
1499
|
+
parts = path
|
|
1500
|
+
elseif type(path) == S_string then
|
|
1501
|
+
-- Split by '.' like JS split('.'): "a.b" -> ["a","b"], "." -> ["",""], "" -> [""]
|
|
1502
|
+
parts = {}
|
|
1503
|
+
local pos = 1
|
|
1504
|
+
local len = #path
|
|
1505
|
+
while pos <= len do
|
|
1506
|
+
local dotpos = path:find('.', pos, true)
|
|
1507
|
+
if dotpos then
|
|
1508
|
+
table.insert(parts, path:sub(pos, dotpos - 1))
|
|
1509
|
+
pos = dotpos + 1
|
|
1510
|
+
else
|
|
1511
|
+
table.insert(parts, path:sub(pos))
|
|
1512
|
+
pos = len + 1
|
|
1513
|
+
end
|
|
1514
|
+
end
|
|
1515
|
+
if pos == 1 then
|
|
1516
|
+
-- Empty string
|
|
1517
|
+
parts = { S_MT }
|
|
1518
|
+
elseif pos == len + 1 then
|
|
1519
|
+
-- Normal end
|
|
1520
|
+
else
|
|
1521
|
+
-- Path ends with a dot
|
|
1522
|
+
table.insert(parts, S_MT)
|
|
1523
|
+
end
|
|
1524
|
+
-- Handle trailing dot: "a." -> ["a", ""]
|
|
1525
|
+
if len > 0 and path:sub(len, len) == '.' then
|
|
1526
|
+
table.insert(parts, S_MT)
|
|
1527
|
+
end
|
|
1528
|
+
elseif type(path) == S_number then
|
|
1529
|
+
parts = { strkey(path) }
|
|
1530
|
+
else
|
|
1531
|
+
return NONE
|
|
1532
|
+
end
|
|
1533
|
+
|
|
1534
|
+
local val = store
|
|
1535
|
+
local base = getprop(injdef, S_base)
|
|
1536
|
+
local src = getprop(store, base, store)
|
|
1537
|
+
local numparts = #parts
|
|
1538
|
+
local dparent = getprop(injdef, 'dparent')
|
|
1539
|
+
|
|
1540
|
+
-- An empty path (incl empty string) just finds the store.
|
|
1541
|
+
if path == nil or store == nil or (1 == numparts and S_MT == parts[1]) then
|
|
1542
|
+
val = src
|
|
1543
|
+
elseif 0 < numparts then
|
|
1544
|
+
|
|
1545
|
+
-- Check for $ACTIONs
|
|
1546
|
+
if 1 == numparts then
|
|
1547
|
+
val = getprop(store, parts[1])
|
|
1548
|
+
end
|
|
1549
|
+
|
|
1550
|
+
if not isfunc(val) then
|
|
1551
|
+
val = src
|
|
1552
|
+
|
|
1553
|
+
-- Check for meta path syntax: field$=value or field$~value
|
|
1554
|
+
local m1, m2, m3 = parts[1]:match("^([^$]+)%$([=~])(.+)$")
|
|
1555
|
+
if m1 and injdef and injdef.meta then
|
|
1556
|
+
val = getprop(injdef.meta, m1)
|
|
1557
|
+
parts[1] = m3
|
|
1558
|
+
end
|
|
1559
|
+
|
|
1560
|
+
local dpath = getprop(injdef, 'dpath')
|
|
1561
|
+
|
|
1562
|
+
local pI = 0
|
|
1563
|
+
while NONE ~= val and pI < numparts do
|
|
1564
|
+
local part = parts[pI + 1] -- Lua 1-based
|
|
1565
|
+
|
|
1566
|
+
if injdef and S_DKEY == part then
|
|
1567
|
+
part = getprop(injdef, S_key)
|
|
1568
|
+
elseif injdef and part and #part > 5 and part:sub(1, 5) == '$GET:' then
|
|
1569
|
+
-- $GET:path$ -> get store value, use as path part (strip trailing $)
|
|
1570
|
+
part = stringify(getpath(src, slice(part, 5, -1)))
|
|
1571
|
+
elseif injdef and part and #part > 5 and part:sub(1, 5) == '$REF:' then
|
|
1572
|
+
-- $REF:refpath$ -> get spec value, use as path part (strip trailing $)
|
|
1573
|
+
part = stringify(getpath(getprop(store, S_DSPEC), slice(part, 5, -1)))
|
|
1574
|
+
elseif injdef and part and #part > 6 and part:sub(1, 6) == '$META:' then
|
|
1575
|
+
-- $META:metapath$ -> get meta value, use as path part (strip trailing $)
|
|
1576
|
+
part = stringify(getpath(getprop(injdef, 'meta'), slice(part, 6, -1)))
|
|
1577
|
+
end
|
|
1578
|
+
|
|
1579
|
+
-- $$ escapes $
|
|
1580
|
+
if part and type(part) == S_string then
|
|
1581
|
+
part = part:gsub('%$%$', '$')
|
|
1582
|
+
end
|
|
1583
|
+
|
|
1584
|
+
if S_MT == part then
|
|
1585
|
+
local ascends = 0
|
|
1586
|
+
while pI + 1 < numparts and S_MT == parts[pI + 2] do
|
|
1587
|
+
ascends = ascends + 1
|
|
1588
|
+
pI = pI + 1
|
|
1589
|
+
end
|
|
1590
|
+
|
|
1591
|
+
if injdef and 0 < ascends then
|
|
1592
|
+
if pI == numparts - 1 then
|
|
1593
|
+
ascends = ascends - 1
|
|
1594
|
+
end
|
|
1595
|
+
|
|
1596
|
+
if 0 == ascends then
|
|
1597
|
+
val = dparent
|
|
1598
|
+
else
|
|
1599
|
+
local remaining = {}
|
|
1600
|
+
setmetatable(remaining, { __jsontype = "array" })
|
|
1601
|
+
for ri = pI + 2, numparts do
|
|
1602
|
+
table.insert(remaining, parts[ri])
|
|
1603
|
+
end
|
|
1604
|
+
local fullpath = flatten({ slice(dpath, 0 - ascends), remaining })
|
|
1605
|
+
|
|
1606
|
+
if ascends <= size(dpath) then
|
|
1607
|
+
val = getpath(store, fullpath)
|
|
1608
|
+
else
|
|
1609
|
+
val = NONE
|
|
1610
|
+
end
|
|
1611
|
+
break
|
|
1612
|
+
end
|
|
1613
|
+
else
|
|
1614
|
+
val = dparent
|
|
1615
|
+
end
|
|
1616
|
+
else
|
|
1617
|
+
val = getprop(val, part)
|
|
1618
|
+
end
|
|
1619
|
+
|
|
1620
|
+
pI = pI + 1
|
|
1621
|
+
end
|
|
1622
|
+
end
|
|
1623
|
+
end
|
|
1624
|
+
|
|
1625
|
+
-- Injdef may provide a custom handler to modify found value.
|
|
1626
|
+
local handler = getprop(injdef, 'handler')
|
|
1627
|
+
if nil ~= injdef and isfunc(handler) then
|
|
1628
|
+
local ref = pathify(path)
|
|
1629
|
+
val = handler(injdef, val, ref, store)
|
|
1630
|
+
end
|
|
1631
|
+
|
|
1632
|
+
return val
|
|
1633
|
+
end
|
|
1634
|
+
|
|
1635
|
+
|
|
1636
|
+
-- Injection "class" for managing injection state.
|
|
1637
|
+
-- Methods: descend, child, setval
|
|
1638
|
+
|
|
1639
|
+
local Injection = {}
|
|
1640
|
+
Injection.__index = Injection
|
|
1641
|
+
|
|
1642
|
+
function Injection:new(val, parent)
|
|
1643
|
+
local o = {
|
|
1644
|
+
mode = M_VAL,
|
|
1645
|
+
full = false,
|
|
1646
|
+
keyI = 0,
|
|
1647
|
+
keys = { S_DTOP },
|
|
1648
|
+
key = S_DTOP,
|
|
1649
|
+
val = val,
|
|
1650
|
+
parent = parent,
|
|
1651
|
+
path = { S_DTOP },
|
|
1652
|
+
nodes = { parent },
|
|
1653
|
+
handler = _injecthandler,
|
|
1654
|
+
errs = {},
|
|
1655
|
+
meta = {},
|
|
1656
|
+
dparent = NONE,
|
|
1657
|
+
dpath = { S_DTOP },
|
|
1658
|
+
base = S_DTOP,
|
|
1659
|
+
modify = NONE,
|
|
1660
|
+
prior = NONE,
|
|
1661
|
+
extra = NONE,
|
|
1662
|
+
}
|
|
1663
|
+
setmetatable(o, self)
|
|
1664
|
+
return o
|
|
1665
|
+
end
|
|
1666
|
+
|
|
1667
|
+
|
|
1668
|
+
function Injection:__tostring()
|
|
1669
|
+
return 'INJ' .. S_CN ..
|
|
1670
|
+
pad(pathify(self.path, 1)) ..
|
|
1671
|
+
(MODENAME[self.mode] or '') .. (self.full and '/full' or '') .. S_CN ..
|
|
1672
|
+
'key=' .. self.keyI .. S_FS .. tostring(self.key) .. S_FS .. S_OS .. table.concat(self.keys, ',') .. S_CS ..
|
|
1673
|
+
' p=' .. stringify(self.parent, -1, 1) ..
|
|
1674
|
+
' m=' .. stringify(self.meta, -1, 1) ..
|
|
1675
|
+
' d/' .. pathify(self.dpath, 1) .. '=' .. stringify(self.dparent, -1, 1) ..
|
|
1676
|
+
' r=' .. stringify(getprop(getprop(self.nodes, 0), S_DTOP), -1, 1)
|
|
1677
|
+
end
|
|
1678
|
+
|
|
1679
|
+
|
|
1680
|
+
function Injection:descend()
|
|
1681
|
+
if self.meta.__d == nil then self.meta.__d = 0 end
|
|
1682
|
+
self.meta.__d = self.meta.__d + 1
|
|
1683
|
+
|
|
1684
|
+
local parentkey = getelem(self.path, -2)
|
|
1685
|
+
|
|
1686
|
+
if NONE == self.dparent then
|
|
1687
|
+
if 1 < size(self.dpath) then
|
|
1688
|
+
self.dpath = flatten({ self.dpath, parentkey })
|
|
1689
|
+
end
|
|
1690
|
+
else
|
|
1691
|
+
if parentkey ~= nil then
|
|
1692
|
+
self.dparent = getprop(self.dparent, parentkey)
|
|
1693
|
+
|
|
1694
|
+
local lastpart = getelem(self.dpath, -1)
|
|
1695
|
+
if lastpart == '$:' .. tostring(parentkey) then
|
|
1696
|
+
self.dpath = slice(self.dpath, -1)
|
|
1697
|
+
else
|
|
1698
|
+
self.dpath = flatten({ self.dpath, parentkey })
|
|
1699
|
+
end
|
|
1700
|
+
end
|
|
1701
|
+
end
|
|
1702
|
+
|
|
1703
|
+
return self.dparent
|
|
1704
|
+
end
|
|
1705
|
+
|
|
1706
|
+
|
|
1707
|
+
function Injection:child(keyI, keys)
|
|
1708
|
+
local key = strkey(keys[keyI + 1]) -- Lua 1-based
|
|
1709
|
+
local val = self.val
|
|
1710
|
+
|
|
1711
|
+
local cinj = Injection:new(getprop(val, key), val)
|
|
1712
|
+
cinj.keyI = keyI
|
|
1713
|
+
cinj.keys = keys
|
|
1714
|
+
cinj.key = key
|
|
1715
|
+
|
|
1716
|
+
cinj.path = flatten({ getdef(self.path, {}), key })
|
|
1717
|
+
cinj.nodes = flatten({ getdef(self.nodes, {}), { val } })
|
|
1718
|
+
|
|
1719
|
+
cinj.mode = self.mode
|
|
1720
|
+
cinj.handler = self.handler
|
|
1721
|
+
cinj.modify = self.modify
|
|
1722
|
+
cinj.base = self.base
|
|
1723
|
+
cinj.meta = self.meta
|
|
1724
|
+
cinj.errs = self.errs
|
|
1725
|
+
cinj.prior = self
|
|
1726
|
+
|
|
1727
|
+
cinj.dpath = flatten({ self.dpath })
|
|
1728
|
+
cinj.dparent = self.dparent
|
|
1729
|
+
|
|
1730
|
+
return cinj
|
|
1731
|
+
end
|
|
1732
|
+
|
|
1733
|
+
|
|
1734
|
+
function Injection:setval(val, ancestor)
|
|
1735
|
+
local parent = NONE
|
|
1736
|
+
if ancestor == nil or ancestor < 2 then
|
|
1737
|
+
if NONE == val then
|
|
1738
|
+
self.parent = delprop(self.parent, self.key)
|
|
1739
|
+
parent = self.parent
|
|
1740
|
+
else
|
|
1741
|
+
parent = setprop(self.parent, self.key, val)
|
|
1742
|
+
end
|
|
1743
|
+
else
|
|
1744
|
+
local aval = getelem(self.nodes, 0 - ancestor)
|
|
1745
|
+
local akey = getelem(self.path, 0 - ancestor)
|
|
1746
|
+
if NONE == val then
|
|
1747
|
+
parent = delprop(aval, akey)
|
|
1748
|
+
else
|
|
1749
|
+
parent = setprop(aval, akey, val)
|
|
1750
|
+
end
|
|
1751
|
+
end
|
|
1752
|
+
return parent
|
|
1753
|
+
end
|
|
1754
|
+
|
|
1755
|
+
|
|
1756
|
+
-- Inject values from a data store into a node recursively.
|
|
1757
|
+
-- @param val (any) The value to inject into
|
|
1758
|
+
-- @param store (table) The data store
|
|
1759
|
+
-- @param injdef (table) Optional injection definition
|
|
1760
|
+
-- @return (any) The injected result
|
|
1761
|
+
local function inject(val, store, injdef)
|
|
1762
|
+
local valtype = type(val)
|
|
1763
|
+
local inj = injdef
|
|
1764
|
+
|
|
1765
|
+
-- Create state if at root of injection.
|
|
1766
|
+
if NONE == injdef or (injdef and injdef.mode == nil) then
|
|
1767
|
+
local parent = { [S_DTOP] = val }
|
|
1768
|
+
inj = Injection:new(val, parent)
|
|
1769
|
+
inj.dparent = store
|
|
1770
|
+
inj.errs = getprop(store, S_DERRS, {})
|
|
1771
|
+
inj.meta.__d = 0
|
|
1772
|
+
|
|
1773
|
+
if NONE ~= injdef then
|
|
1774
|
+
inj.modify = injdef.modify ~= nil and injdef.modify or inj.modify
|
|
1775
|
+
inj.extra = injdef.extra ~= nil and injdef.extra or inj.extra
|
|
1776
|
+
inj.meta = injdef.meta ~= nil and injdef.meta or inj.meta
|
|
1777
|
+
inj.handler = injdef.handler ~= nil and injdef.handler or inj.handler
|
|
1778
|
+
end
|
|
1779
|
+
end
|
|
1780
|
+
|
|
1781
|
+
inj:descend()
|
|
1782
|
+
|
|
1783
|
+
-- Descend into node.
|
|
1784
|
+
if isnode(val) then
|
|
1785
|
+
local nodekeys
|
|
1786
|
+
|
|
1787
|
+
if ismap(val) then
|
|
1788
|
+
local regular_keys = {}
|
|
1789
|
+
local ds_keys = {}
|
|
1790
|
+
for k, _ in pairs(val) do
|
|
1791
|
+
if type(k) == S_string and k:find(S_DS, 1, true) then
|
|
1792
|
+
table.insert(ds_keys, k)
|
|
1793
|
+
else
|
|
1794
|
+
table.insert(regular_keys, k)
|
|
1795
|
+
end
|
|
1796
|
+
end
|
|
1797
|
+
table.sort(regular_keys)
|
|
1798
|
+
table.sort(ds_keys)
|
|
1799
|
+
nodekeys = flatten({ regular_keys, ds_keys })
|
|
1800
|
+
else
|
|
1801
|
+
nodekeys = {}
|
|
1802
|
+
for i = 1, #val do
|
|
1803
|
+
table.insert(nodekeys, i - 1) -- 0-based indices
|
|
1804
|
+
end
|
|
1805
|
+
end
|
|
1806
|
+
|
|
1807
|
+
local nkI = 0
|
|
1808
|
+
while nkI < #nodekeys do
|
|
1809
|
+
local childinj = inj:child(nkI, nodekeys)
|
|
1810
|
+
local nodekey = childinj.key
|
|
1811
|
+
childinj.mode = M_KEYPRE
|
|
1812
|
+
|
|
1813
|
+
-- Perform key:pre mode injection
|
|
1814
|
+
local prekey = _injectstr(nodekey, store, childinj)
|
|
1815
|
+
|
|
1816
|
+
-- The injection may modify child processing.
|
|
1817
|
+
nkI = childinj.keyI
|
|
1818
|
+
nodekeys = childinj.keys
|
|
1819
|
+
|
|
1820
|
+
-- Prevent further processing by returning undefined prekey
|
|
1821
|
+
if prekey ~= NONE then
|
|
1822
|
+
childinj.val = getprop(val, prekey)
|
|
1823
|
+
childinj.mode = M_VAL
|
|
1824
|
+
|
|
1825
|
+
-- Perform val mode injection
|
|
1826
|
+
inject(childinj.val, store, childinj)
|
|
1827
|
+
|
|
1828
|
+
-- The injection may modify child processing.
|
|
1829
|
+
nkI = childinj.keyI
|
|
1830
|
+
nodekeys = childinj.keys
|
|
1831
|
+
|
|
1832
|
+
-- Perform key:post mode injection
|
|
1833
|
+
childinj.mode = M_KEYPOST
|
|
1834
|
+
_injectstr(nodekey, store, childinj)
|
|
1835
|
+
|
|
1836
|
+
nkI = childinj.keyI
|
|
1837
|
+
nodekeys = childinj.keys
|
|
1838
|
+
end
|
|
1839
|
+
|
|
1840
|
+
nkI = nkI + 1
|
|
1841
|
+
end
|
|
1842
|
+
|
|
1843
|
+
elseif S_string == valtype then
|
|
1844
|
+
inj.mode = M_VAL
|
|
1845
|
+
val = _injectstr(val, store, inj)
|
|
1846
|
+
if SKIP ~= val then
|
|
1847
|
+
inj:setval(val)
|
|
1848
|
+
end
|
|
1849
|
+
end
|
|
1850
|
+
|
|
1851
|
+
-- Custom modification
|
|
1852
|
+
if inj.modify and SKIP ~= val then
|
|
1853
|
+
local mkey = inj.key
|
|
1854
|
+
local mparent = inj.parent
|
|
1855
|
+
local mval = getprop(mparent, mkey)
|
|
1856
|
+
inj.modify(mval, mkey, mparent, inj, store)
|
|
1857
|
+
end
|
|
1858
|
+
|
|
1859
|
+
inj.val = val
|
|
1860
|
+
|
|
1861
|
+
return getprop(inj.parent, S_DTOP)
|
|
1862
|
+
end
|
|
1863
|
+
|
|
1864
|
+
|
|
1865
|
+
-- Delete a key from a map or list.
|
|
1866
|
+
local function transform_DELETE(inj)
|
|
1867
|
+
inj:setval(NONE)
|
|
1868
|
+
return NONE
|
|
1869
|
+
end
|
|
1870
|
+
|
|
1871
|
+
|
|
1872
|
+
-- Copy value from source data.
|
|
1873
|
+
local function transform_COPY(inj, _val)
|
|
1874
|
+
local ijname = 'COPY'
|
|
1875
|
+
|
|
1876
|
+
if not checkPlacement(M_VAL, ijname, T_any, inj) then
|
|
1877
|
+
return NONE
|
|
1878
|
+
end
|
|
1879
|
+
|
|
1880
|
+
local out = getprop(inj.dparent, inj.key)
|
|
1881
|
+
inj:setval(out)
|
|
1882
|
+
return out
|
|
1883
|
+
end
|
|
1884
|
+
|
|
1885
|
+
|
|
1886
|
+
-- As a value, inject the key of the parent node.
|
|
1887
|
+
local function transform_KEY(inj)
|
|
1888
|
+
local mode, path, parent = inj.mode, inj.path, inj.parent
|
|
1889
|
+
|
|
1890
|
+
if M_VAL ~= mode then
|
|
1891
|
+
return NONE
|
|
1892
|
+
end
|
|
1893
|
+
|
|
1894
|
+
-- Key is defined by $KEY meta property.
|
|
1895
|
+
local keyspec = getprop(parent, S_BKEY)
|
|
1896
|
+
if keyspec ~= NONE then
|
|
1897
|
+
delprop(parent, S_BKEY)
|
|
1898
|
+
return getprop(inj.dparent, keyspec)
|
|
1899
|
+
end
|
|
1900
|
+
|
|
1901
|
+
return getprop(getprop(parent, S_BANNO), S_KEY, getelem(path, -2))
|
|
1902
|
+
end
|
|
1903
|
+
|
|
1904
|
+
|
|
1905
|
+
-- Store annotation data about a node.
|
|
1906
|
+
local function transform_ANNO(inj)
|
|
1907
|
+
delprop(inj.parent, S_BANNO)
|
|
1908
|
+
return NONE
|
|
1909
|
+
end
|
|
1910
|
+
|
|
1911
|
+
|
|
1912
|
+
-- Merge a list of objects into the current object.
|
|
1913
|
+
local function transform_MERGE(inj)
|
|
1914
|
+
local mode, key, parent = inj.mode, inj.key, inj.parent
|
|
1915
|
+
|
|
1916
|
+
local out = NONE
|
|
1917
|
+
|
|
1918
|
+
if M_KEYPRE == mode then
|
|
1919
|
+
out = key
|
|
1920
|
+
|
|
1921
|
+
elseif M_KEYPOST == mode then
|
|
1922
|
+
out = key
|
|
1923
|
+
|
|
1924
|
+
local args = getprop(parent, key)
|
|
1925
|
+
if not islist(args) then
|
|
1926
|
+
args = { args }
|
|
1927
|
+
setmetatable(args, { __jsontype = "array" })
|
|
1928
|
+
end
|
|
1929
|
+
|
|
1930
|
+
-- Remove the $MERGE command from parent.
|
|
1931
|
+
inj:setval(NONE)
|
|
1932
|
+
|
|
1933
|
+
local mergelist = flatten({ { parent }, args, { clone(parent) } })
|
|
1934
|
+
setmetatable(mergelist, { __jsontype = "array" })
|
|
1935
|
+
merge(mergelist)
|
|
1936
|
+
end
|
|
1937
|
+
|
|
1938
|
+
return out
|
|
1939
|
+
end
|
|
1940
|
+
|
|
1941
|
+
|
|
1942
|
+
-- Helper: injectChild
|
|
1943
|
+
local function injectChild(child, store, inj)
|
|
1944
|
+
local cinj = inj
|
|
1945
|
+
|
|
1946
|
+
if nil ~= inj.prior then
|
|
1947
|
+
if nil ~= inj.prior.prior then
|
|
1948
|
+
cinj = inj.prior.prior:child(inj.prior.keyI, inj.prior.keys)
|
|
1949
|
+
cinj.val = child
|
|
1950
|
+
setprop(cinj.parent, inj.prior.key, child)
|
|
1951
|
+
else
|
|
1952
|
+
cinj = inj.prior:child(inj.keyI, inj.keys)
|
|
1953
|
+
cinj.val = child
|
|
1954
|
+
setprop(cinj.parent, inj.key, child)
|
|
1955
|
+
end
|
|
1956
|
+
end
|
|
1957
|
+
|
|
1958
|
+
inject(child, store, cinj)
|
|
1959
|
+
return cinj
|
|
1960
|
+
end
|
|
1961
|
+
|
|
1962
|
+
|
|
1963
|
+
-- Convert a node to a list.
|
|
1964
|
+
-- Format: ['`$EACH`', '`source-path-of-node`', child-template]
|
|
1965
|
+
local function transform_EACH(inj, _val, _ref, store)
|
|
1966
|
+
local ijname = 'EACH'
|
|
1967
|
+
|
|
1968
|
+
if not checkPlacement(M_VAL, ijname, T_list, inj) then
|
|
1969
|
+
return NONE
|
|
1970
|
+
end
|
|
1971
|
+
|
|
1972
|
+
-- Remove remaining keys to avoid spurious processing.
|
|
1973
|
+
local trimmed = slice(inj.keys, 0, 1)
|
|
1974
|
+
-- Replace keys in-place
|
|
1975
|
+
for i = #inj.keys, 1, -1 do inj.keys[i] = nil end
|
|
1976
|
+
for i, v in ipairs(trimmed) do inj.keys[i] = v end
|
|
1977
|
+
|
|
1978
|
+
-- Get arguments: ['`$EACH`', 'source-path', child-template]
|
|
1979
|
+
local each_args = injectorArgs({ T_string, T_any }, slice(inj.parent, 1))
|
|
1980
|
+
local err = each_args[1]
|
|
1981
|
+
if NONE ~= err then
|
|
1982
|
+
table.insert(inj.errs, '$' .. ijname .. ': ' .. err)
|
|
1983
|
+
return NONE
|
|
1984
|
+
end
|
|
1985
|
+
local srcpath = each_args[2]
|
|
1986
|
+
local child = clone(each_args[3])
|
|
1987
|
+
|
|
1988
|
+
-- Source data.
|
|
1989
|
+
local srcstore = getprop(store, inj.base, store)
|
|
1990
|
+
local src = getpath(srcstore, srcpath, inj)
|
|
1991
|
+
local srctype = typify(src)
|
|
1992
|
+
|
|
1993
|
+
local tcur = {}
|
|
1994
|
+
local tval = {}
|
|
1995
|
+
setmetatable(tval, { __jsontype = "array" })
|
|
1996
|
+
|
|
1997
|
+
local tkey = getelem(inj.path, -2)
|
|
1998
|
+
local target = getelem(inj.nodes, -2, function() return getelem(inj.nodes, -1) end)
|
|
1999
|
+
|
|
2000
|
+
-- Create clones of the child template for each value of the source.
|
|
2001
|
+
if 0 < (T_list & srctype) then
|
|
2002
|
+
for _, item in ipairs(items(src)) do
|
|
2003
|
+
table.insert(tval, clone(child))
|
|
2004
|
+
end
|
|
2005
|
+
elseif 0 < (T_map & srctype) then
|
|
2006
|
+
for _, item in ipairs(items(src)) do
|
|
2007
|
+
local merged = merge({ clone(child), { [S_BANNO] = { KEY = item[1] } } }, 1)
|
|
2008
|
+
table.insert(tval, merged)
|
|
2009
|
+
end
|
|
2010
|
+
end
|
|
2011
|
+
|
|
2012
|
+
local rval = {}
|
|
2013
|
+
setmetatable(rval, { __jsontype = "array" })
|
|
2014
|
+
|
|
2015
|
+
if 0 < size(tval) then
|
|
2016
|
+
-- Get source values
|
|
2017
|
+
local srcvals = {}
|
|
2018
|
+
setmetatable(srcvals, { __jsontype = "array" })
|
|
2019
|
+
if islist(src) then
|
|
2020
|
+
for i = 1, #src do table.insert(srcvals, src[i]) end
|
|
2021
|
+
elseif ismap(src) then
|
|
2022
|
+
for _, item in ipairs(items(src)) do
|
|
2023
|
+
table.insert(srcvals, item[2])
|
|
2024
|
+
end
|
|
2025
|
+
end
|
|
2026
|
+
|
|
2027
|
+
local ckey = getelem(inj.path, -2)
|
|
2028
|
+
local tpath = slice(inj.path, -1)
|
|
2029
|
+
|
|
2030
|
+
-- Split srcpath into parts
|
|
2031
|
+
local srcparts = {}
|
|
2032
|
+
if type(srcpath) == S_string then
|
|
2033
|
+
for p in srcpath:gmatch("([^%.]+)") do
|
|
2034
|
+
table.insert(srcparts, p)
|
|
2035
|
+
end
|
|
2036
|
+
end
|
|
2037
|
+
local dpath = flatten({ S_DTOP, srcparts, '$:' .. tostring(ckey) })
|
|
2038
|
+
|
|
2039
|
+
tcur = { [ckey] = srcvals }
|
|
2040
|
+
|
|
2041
|
+
if 1 < size(tpath) then
|
|
2042
|
+
local pkey = getelem(inj.path, -3, S_DTOP)
|
|
2043
|
+
tcur = { [pkey] = tcur }
|
|
2044
|
+
table.insert(dpath, '$:' .. tostring(pkey))
|
|
2045
|
+
end
|
|
2046
|
+
|
|
2047
|
+
local tinj = inj:child(0, { ckey })
|
|
2048
|
+
tinj.path = tpath
|
|
2049
|
+
tinj.nodes = slice(inj.nodes, -1)
|
|
2050
|
+
tinj.parent = getelem(tinj.nodes, -1)
|
|
2051
|
+
setprop(tinj.parent, ckey, tval)
|
|
2052
|
+
tinj.val = tval
|
|
2053
|
+
tinj.dpath = dpath
|
|
2054
|
+
tinj.dparent = tcur
|
|
2055
|
+
|
|
2056
|
+
inject(tval, store, tinj)
|
|
2057
|
+
rval = tinj.val
|
|
2058
|
+
end
|
|
2059
|
+
|
|
2060
|
+
setprop(target, tkey, rval)
|
|
2061
|
+
|
|
2062
|
+
-- Prevent callee from damaging first list entry.
|
|
2063
|
+
return getelem(rval, 0)
|
|
2064
|
+
end
|
|
2065
|
+
|
|
2066
|
+
|
|
2067
|
+
-- Convert a node to a map.
|
|
2068
|
+
-- Format: { '`$PACK`':['`source-path`', child-template]}
|
|
2069
|
+
local function transform_PACK(inj, _val, _ref, store)
|
|
2070
|
+
local mode, key, path, parent, nodes = inj.mode, inj.key, inj.path,
|
|
2071
|
+
inj.parent, inj.nodes
|
|
2072
|
+
|
|
2073
|
+
local ijname = 'EACH'
|
|
2074
|
+
|
|
2075
|
+
if not checkPlacement(M_KEYPRE, ijname, T_map, inj) then
|
|
2076
|
+
return NONE
|
|
2077
|
+
end
|
|
2078
|
+
|
|
2079
|
+
-- Get arguments.
|
|
2080
|
+
local args = getprop(parent, key)
|
|
2081
|
+
local pack_args = injectorArgs({ T_string, T_any }, args)
|
|
2082
|
+
local err = pack_args[1]
|
|
2083
|
+
if NONE ~= err then
|
|
2084
|
+
table.insert(inj.errs, '$' .. ijname .. ': ' .. err)
|
|
2085
|
+
return NONE
|
|
2086
|
+
end
|
|
2087
|
+
local srcpath = pack_args[2]
|
|
2088
|
+
local origchildspec = pack_args[3]
|
|
2089
|
+
|
|
2090
|
+
-- Find key and target node.
|
|
2091
|
+
local tkey = getelem(path, -2)
|
|
2092
|
+
local pathsize = size(path)
|
|
2093
|
+
local target = getelem(nodes, pathsize - 2, function()
|
|
2094
|
+
return getelem(nodes, pathsize - 1)
|
|
2095
|
+
end)
|
|
2096
|
+
|
|
2097
|
+
-- Source data
|
|
2098
|
+
local srcstore = getprop(store, inj.base, store)
|
|
2099
|
+
local src = getpath(srcstore, srcpath, inj)
|
|
2100
|
+
|
|
2101
|
+
-- Prepare source as a list.
|
|
2102
|
+
if not islist(src) then
|
|
2103
|
+
if ismap(src) then
|
|
2104
|
+
local newsrc = {}
|
|
2105
|
+
setmetatable(newsrc, { __jsontype = "array" })
|
|
2106
|
+
for _, item in ipairs(items(src)) do
|
|
2107
|
+
setprop(item[2], S_BANNO, { KEY = item[1] })
|
|
2108
|
+
table.insert(newsrc, item[2])
|
|
2109
|
+
end
|
|
2110
|
+
src = newsrc
|
|
2111
|
+
else
|
|
2112
|
+
src = NONE
|
|
2113
|
+
end
|
|
2114
|
+
end
|
|
2115
|
+
|
|
2116
|
+
if src == nil then
|
|
2117
|
+
return NONE
|
|
2118
|
+
end
|
|
2119
|
+
|
|
2120
|
+
-- Get keypath.
|
|
2121
|
+
local keypath = getprop(origchildspec, S_BKEY)
|
|
2122
|
+
delprop(origchildspec, S_BKEY)
|
|
2123
|
+
|
|
2124
|
+
local child = getprop(origchildspec, S_BVAL, origchildspec)
|
|
2125
|
+
|
|
2126
|
+
-- Build parallel target object.
|
|
2127
|
+
local tval = {}
|
|
2128
|
+
|
|
2129
|
+
for _, item in ipairs(items(src)) do
|
|
2130
|
+
local srckey = item[1]
|
|
2131
|
+
local srcnode = item[2]
|
|
2132
|
+
|
|
2133
|
+
local kn = srckey
|
|
2134
|
+
if NONE ~= keypath then
|
|
2135
|
+
if type(keypath) == S_string and keypath:sub(1, 1) == S_BT then
|
|
2136
|
+
kn = inject(keypath, merge({ {}, store, { [S_DTOP] = srcnode } }, 1))
|
|
2137
|
+
else
|
|
2138
|
+
kn = getpath(srcnode, keypath, inj)
|
|
2139
|
+
end
|
|
2140
|
+
end
|
|
2141
|
+
|
|
2142
|
+
local tchild = clone(child)
|
|
2143
|
+
setprop(tval, kn, tchild)
|
|
2144
|
+
|
|
2145
|
+
local anno = getprop(srcnode, S_BANNO)
|
|
2146
|
+
if NONE == anno then
|
|
2147
|
+
delprop(tchild, S_BANNO)
|
|
2148
|
+
else
|
|
2149
|
+
setprop(tchild, S_BANNO, anno)
|
|
2150
|
+
end
|
|
2151
|
+
end
|
|
2152
|
+
|
|
2153
|
+
local rval = {}
|
|
2154
|
+
|
|
2155
|
+
if not isempty(tval) then
|
|
2156
|
+
-- Build parallel source object.
|
|
2157
|
+
local tsrc = {}
|
|
2158
|
+
for srcI, item in ipairs(items(src)) do
|
|
2159
|
+
local srcnode = item[2]
|
|
2160
|
+
local kn
|
|
2161
|
+
if keypath == nil then
|
|
2162
|
+
kn = srcI - 1 -- 0-based
|
|
2163
|
+
elseif type(keypath) == S_string and keypath:sub(1, 1) == S_BT then
|
|
2164
|
+
kn = inject(keypath, merge({ {}, store, { [S_DTOP] = srcnode } }, 1))
|
|
2165
|
+
else
|
|
2166
|
+
kn = getpath(srcnode, keypath, inj)
|
|
2167
|
+
end
|
|
2168
|
+
setprop(tsrc, kn, srcnode)
|
|
2169
|
+
end
|
|
2170
|
+
|
|
2171
|
+
local tpath = slice(inj.path, -1)
|
|
2172
|
+
local ckey = getelem(inj.path, -2)
|
|
2173
|
+
|
|
2174
|
+
local srcparts = {}
|
|
2175
|
+
if type(srcpath) == S_string then
|
|
2176
|
+
for p in srcpath:gmatch("([^%.]+)") do
|
|
2177
|
+
table.insert(srcparts, p)
|
|
2178
|
+
end
|
|
2179
|
+
end
|
|
2180
|
+
local dpath = flatten({ S_DTOP, srcparts, '$:' .. tostring(ckey) })
|
|
2181
|
+
|
|
2182
|
+
local tcur = { [ckey] = tsrc }
|
|
2183
|
+
|
|
2184
|
+
if 1 < size(tpath) then
|
|
2185
|
+
local pkey = getelem(inj.path, -3, S_DTOP)
|
|
2186
|
+
tcur = { [pkey] = tcur }
|
|
2187
|
+
table.insert(dpath, '$:' .. tostring(pkey))
|
|
2188
|
+
end
|
|
2189
|
+
|
|
2190
|
+
local tinj = inj:child(0, { ckey })
|
|
2191
|
+
tinj.path = tpath
|
|
2192
|
+
tinj.nodes = slice(inj.nodes, -1)
|
|
2193
|
+
tinj.parent = getelem(tinj.nodes, -1)
|
|
2194
|
+
tinj.val = tval
|
|
2195
|
+
tinj.dpath = dpath
|
|
2196
|
+
tinj.dparent = tcur
|
|
2197
|
+
|
|
2198
|
+
inject(tval, store, tinj)
|
|
2199
|
+
rval = tinj.val
|
|
2200
|
+
end
|
|
2201
|
+
|
|
2202
|
+
setprop(target, tkey, rval)
|
|
2203
|
+
|
|
2204
|
+
-- Drop transform key.
|
|
2205
|
+
return NONE
|
|
2206
|
+
end
|
|
2207
|
+
|
|
2208
|
+
|
|
2209
|
+
-- Placement labels for error messages.
|
|
2210
|
+
local PLACEMENT = {
|
|
2211
|
+
[M_VAL] = 'value',
|
|
2212
|
+
[M_KEYPRE] = S_key,
|
|
2213
|
+
[M_KEYPOST] = S_key,
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
|
|
2217
|
+
-- Check that a transform is used in the correct mode and parent type.
|
|
2218
|
+
checkPlacement = function(modes, ijname, parentTypes, inj)
|
|
2219
|
+
if 0 == (modes & inj.mode) then
|
|
2220
|
+
local expected = {}
|
|
2221
|
+
local allModes = { M_KEYPRE, M_KEYPOST, M_VAL }
|
|
2222
|
+
for _, m in ipairs(allModes) do
|
|
2223
|
+
if 0 ~= (modes & m) then
|
|
2224
|
+
table.insert(expected, PLACEMENT[m])
|
|
2225
|
+
end
|
|
2226
|
+
end
|
|
2227
|
+
table.insert(inj.errs, '$' .. ijname .. ': invalid placement as ' ..
|
|
2228
|
+
PLACEMENT[inj.mode] .. ', expected: ' ..
|
|
2229
|
+
table.concat(expected, ',') .. '.')
|
|
2230
|
+
return false
|
|
2231
|
+
end
|
|
2232
|
+
if not isempty(parentTypes) then
|
|
2233
|
+
local ptype = typify(inj.parent)
|
|
2234
|
+
if 0 == (parentTypes & ptype) then
|
|
2235
|
+
table.insert(inj.errs, '$' .. ijname .. ': invalid placement in parent ' ..
|
|
2236
|
+
typename(ptype) .. ', expected: ' .. typename(parentTypes) .. '.')
|
|
2237
|
+
return false
|
|
2238
|
+
end
|
|
2239
|
+
end
|
|
2240
|
+
return true
|
|
2241
|
+
end
|
|
2242
|
+
|
|
2243
|
+
|
|
2244
|
+
-- Validate and extract typed arguments from a list.
|
|
2245
|
+
injectorArgs = function(argTypes, args)
|
|
2246
|
+
local numargs = size(argTypes)
|
|
2247
|
+
local found = {}
|
|
2248
|
+
found[1] = NONE -- err slot (1-based)
|
|
2249
|
+
for argI = 1, numargs do
|
|
2250
|
+
local arg = getprop(args, argI - 1) -- 0-based access
|
|
2251
|
+
local argType = typify(arg)
|
|
2252
|
+
if 0 == (argTypes[argI] & argType) then
|
|
2253
|
+
found[1] = 'invalid argument: ' .. stringify(arg, 22) ..
|
|
2254
|
+
' (' .. typename(argType) .. ' at position ' .. argI ..
|
|
2255
|
+
') is not of type: ' .. typename(argTypes[argI]) .. '.'
|
|
2256
|
+
break
|
|
2257
|
+
end
|
|
2258
|
+
found[1 + argI] = arg
|
|
2259
|
+
end
|
|
2260
|
+
return found
|
|
2261
|
+
end
|
|
2262
|
+
|
|
2263
|
+
|
|
2264
|
+
-- Transform: resolve a reference to another part of the spec.
|
|
2265
|
+
-- Format: ['`$REF`', 'ref-path']
|
|
2266
|
+
local function transform_REF(inj, val, _ref, store)
|
|
2267
|
+
local nodes = inj.nodes
|
|
2268
|
+
|
|
2269
|
+
if M_VAL ~= inj.mode then
|
|
2270
|
+
return NONE
|
|
2271
|
+
end
|
|
2272
|
+
|
|
2273
|
+
-- Get arguments: ['`$REF`', 'ref-path'].
|
|
2274
|
+
local refpath = getprop(inj.parent, 1)
|
|
2275
|
+
inj.keyI = size(inj.keys)
|
|
2276
|
+
|
|
2277
|
+
-- Spec reference.
|
|
2278
|
+
local specfn = getprop(store, S_DSPEC)
|
|
2279
|
+
local spec = specfn()
|
|
2280
|
+
|
|
2281
|
+
local dpath = slice(inj.path, 1)
|
|
2282
|
+
local ref = getpath(spec, refpath, {
|
|
2283
|
+
dpath = dpath,
|
|
2284
|
+
dparent = getpath(spec, dpath),
|
|
2285
|
+
})
|
|
2286
|
+
|
|
2287
|
+
local hasSubRef = false
|
|
2288
|
+
if isnode(ref) then
|
|
2289
|
+
walk(ref, function(_k, v)
|
|
2290
|
+
if '`$REF`' == v then
|
|
2291
|
+
hasSubRef = true
|
|
2292
|
+
end
|
|
2293
|
+
return v
|
|
2294
|
+
end)
|
|
2295
|
+
end
|
|
2296
|
+
|
|
2297
|
+
local tref = clone(ref)
|
|
2298
|
+
|
|
2299
|
+
local cpath = slice(inj.path, -3)
|
|
2300
|
+
local tpath = slice(inj.path, -1)
|
|
2301
|
+
local tcur = getpath(store, cpath)
|
|
2302
|
+
local tval = getpath(store, tpath)
|
|
2303
|
+
local rval = NONE
|
|
2304
|
+
|
|
2305
|
+
if not hasSubRef or NONE ~= tval then
|
|
2306
|
+
local tinj = inj:child(0, { getelem(tpath, -1) })
|
|
2307
|
+
|
|
2308
|
+
tinj.path = tpath
|
|
2309
|
+
tinj.nodes = slice(inj.nodes, -1)
|
|
2310
|
+
tinj.parent = getelem(nodes, -2)
|
|
2311
|
+
tinj.val = tref
|
|
2312
|
+
|
|
2313
|
+
tinj.dpath = flatten({ cpath })
|
|
2314
|
+
tinj.dparent = tcur
|
|
2315
|
+
|
|
2316
|
+
inject(tref, store, tinj)
|
|
2317
|
+
|
|
2318
|
+
rval = tinj.val
|
|
2319
|
+
else
|
|
2320
|
+
rval = NONE
|
|
2321
|
+
end
|
|
2322
|
+
|
|
2323
|
+
local grandparent = inj:setval(rval, 2)
|
|
2324
|
+
|
|
2325
|
+
if islist(grandparent) and inj.prior then
|
|
2326
|
+
inj.prior.keyI = inj.prior.keyI - 1
|
|
2327
|
+
end
|
|
2328
|
+
|
|
2329
|
+
return val
|
|
2330
|
+
end
|
|
2331
|
+
|
|
2332
|
+
|
|
2333
|
+
-- Named formatters for transform_FORMAT.
|
|
2334
|
+
local FORMATTER = {
|
|
2335
|
+
identity = function(_k, v) return v end,
|
|
2336
|
+
upper = function(_k, v)
|
|
2337
|
+
return isnode(v) and v or string.upper(tostring(v))
|
|
2338
|
+
end,
|
|
2339
|
+
lower = function(_k, v)
|
|
2340
|
+
return isnode(v) and v or string.lower(tostring(v))
|
|
2341
|
+
end,
|
|
2342
|
+
string = function(_k, v)
|
|
2343
|
+
return isnode(v) and v or tostring(v)
|
|
2344
|
+
end,
|
|
2345
|
+
number = function(_k, v)
|
|
2346
|
+
if isnode(v) then return v end
|
|
2347
|
+
local n = tonumber(v)
|
|
2348
|
+
return (n == nil or n ~= n) and 0 or n
|
|
2349
|
+
end,
|
|
2350
|
+
integer = function(_k, v)
|
|
2351
|
+
if isnode(v) then return v end
|
|
2352
|
+
local n = tonumber(v)
|
|
2353
|
+
if n == nil or n ~= n then n = 0 end
|
|
2354
|
+
return math.floor(n)
|
|
2355
|
+
end,
|
|
2356
|
+
concat = function(k, v)
|
|
2357
|
+
if k == nil and islist(v) then
|
|
2358
|
+
local parts = {}
|
|
2359
|
+
for _, item in ipairs(items(v)) do
|
|
2360
|
+
local val = item[2]
|
|
2361
|
+
table.insert(parts, isnode(val) and '' or tostring(val))
|
|
2362
|
+
end
|
|
2363
|
+
return table.concat(parts)
|
|
2364
|
+
end
|
|
2365
|
+
return v
|
|
2366
|
+
end,
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
|
|
2370
|
+
-- Transform: format values using named formatters.
|
|
2371
|
+
-- Format: ['`$FORMAT`', 'name', child]
|
|
2372
|
+
local function transform_FORMAT(inj, _val, _ref, store)
|
|
2373
|
+
-- Remove remaining keys to avoid spurious processing.
|
|
2374
|
+
slice(inj.keys, 0, 1, true)
|
|
2375
|
+
|
|
2376
|
+
if M_VAL ~= inj.mode then
|
|
2377
|
+
return NONE
|
|
2378
|
+
end
|
|
2379
|
+
|
|
2380
|
+
-- Get arguments: ['`$FORMAT`', 'name', child].
|
|
2381
|
+
local name = getprop(inj.parent, 1)
|
|
2382
|
+
local child = getprop(inj.parent, 2)
|
|
2383
|
+
|
|
2384
|
+
-- Source data.
|
|
2385
|
+
local tkey = getelem(inj.path, -2)
|
|
2386
|
+
local target = getelem(inj.nodes, -2, function() return getelem(inj.nodes, -1) end)
|
|
2387
|
+
|
|
2388
|
+
local cinj = injectChild(child, store, inj)
|
|
2389
|
+
local resolved = cinj.val
|
|
2390
|
+
|
|
2391
|
+
local formatter = (0 < (T_function & typify(name))) and name or getprop(FORMATTER, name)
|
|
2392
|
+
|
|
2393
|
+
if NONE == formatter then
|
|
2394
|
+
table.insert(inj.errs, '$FORMAT: unknown format: ' .. tostring(name) .. '.')
|
|
2395
|
+
return NONE
|
|
2396
|
+
end
|
|
2397
|
+
|
|
2398
|
+
local out = walk(resolved, formatter)
|
|
2399
|
+
|
|
2400
|
+
setprop(target, tkey, out)
|
|
2401
|
+
|
|
2402
|
+
return out
|
|
2403
|
+
end
|
|
2404
|
+
|
|
2405
|
+
|
|
2406
|
+
-- Apply a function to a value.
|
|
2407
|
+
-- Format: ['`$APPLY`', function, child]
|
|
2408
|
+
local function transform_APPLY(inj, _val, _ref, store)
|
|
2409
|
+
local ijname = 'APPLY'
|
|
2410
|
+
|
|
2411
|
+
if not checkPlacement(M_VAL, ijname, T_list, inj) then
|
|
2412
|
+
return NONE
|
|
2413
|
+
end
|
|
2414
|
+
|
|
2415
|
+
local found = injectorArgs({ T_function, T_any }, slice(inj.parent, 1))
|
|
2416
|
+
local err, apply, child = found[1], found[2], found[3]
|
|
2417
|
+
if NONE ~= err then
|
|
2418
|
+
table.insert(inj.errs, '$' .. ijname .. ': ' .. err)
|
|
2419
|
+
return NONE
|
|
2420
|
+
end
|
|
2421
|
+
|
|
2422
|
+
local tkey = getelem(inj.path, -2)
|
|
2423
|
+
local target = getelem(inj.nodes, -2, function() return getelem(inj.nodes, -1) end)
|
|
2424
|
+
|
|
2425
|
+
local cinj = injectChild(child, store, inj)
|
|
2426
|
+
local resolved = cinj.val
|
|
2427
|
+
|
|
2428
|
+
local out = apply(resolved, store, cinj)
|
|
2429
|
+
|
|
2430
|
+
setprop(target, tkey, out)
|
|
2431
|
+
return out
|
|
2432
|
+
end
|
|
2433
|
+
|
|
2434
|
+
|
|
2435
|
+
-- Transform data using spec.
|
|
2436
|
+
-- @param data (any) Source data to transform
|
|
2437
|
+
-- @param spec (any) Transform specification
|
|
2438
|
+
-- @param injdef (table) Optional injection definition with modify, extra, errs
|
|
2439
|
+
-- @return (any) The transformed data
|
|
2440
|
+
local function transform(data, spec, injdef)
|
|
2441
|
+
local origspec = spec
|
|
2442
|
+
spec = clone(origspec)
|
|
2443
|
+
|
|
2444
|
+
local extra = injdef and injdef.extra or NONE
|
|
2445
|
+
local collect = injdef ~= nil and injdef.errs ~= nil
|
|
2446
|
+
local errs = (injdef and injdef.errs) or {}
|
|
2447
|
+
|
|
2448
|
+
local extraTransforms = {}
|
|
2449
|
+
local extraData = NONE
|
|
2450
|
+
|
|
2451
|
+
if extra ~= nil then
|
|
2452
|
+
extraData = {}
|
|
2453
|
+
for _, item in ipairs(items(extra)) do
|
|
2454
|
+
local k, v = item[1], item[2]
|
|
2455
|
+
if type(k) == S_string and k:sub(1, 1) == S_DS then
|
|
2456
|
+
extraTransforms[k] = v
|
|
2457
|
+
else
|
|
2458
|
+
extraData[k] = v
|
|
2459
|
+
end
|
|
2460
|
+
end
|
|
2461
|
+
end
|
|
2462
|
+
|
|
2463
|
+
local dataClone
|
|
2464
|
+
if isempty(extraData) then
|
|
2465
|
+
dataClone = clone(data)
|
|
2466
|
+
else
|
|
2467
|
+
dataClone = merge({ clone(extraData), clone(data) })
|
|
2468
|
+
end
|
|
2469
|
+
|
|
2470
|
+
-- Define a top level store that provides transform operations.
|
|
2471
|
+
local store = merge({
|
|
2472
|
+
{
|
|
2473
|
+
[S_DTOP] = dataClone,
|
|
2474
|
+
|
|
2475
|
+
[S_DSPEC] = function() return origspec end,
|
|
2476
|
+
|
|
2477
|
+
['$BT'] = function() return S_BT end,
|
|
2478
|
+
['$DS'] = function() return S_DS end,
|
|
2479
|
+
['$WHEN'] = function() return os.date('!%Y-%m-%dT%H:%M:%S.000Z') end,
|
|
2480
|
+
|
|
2481
|
+
['$DELETE'] = transform_DELETE,
|
|
2482
|
+
['$COPY'] = transform_COPY,
|
|
2483
|
+
['$KEY'] = transform_KEY,
|
|
2484
|
+
['$ANNO'] = transform_ANNO,
|
|
2485
|
+
['$MERGE'] = transform_MERGE,
|
|
2486
|
+
['$EACH'] = transform_EACH,
|
|
2487
|
+
['$PACK'] = transform_PACK,
|
|
2488
|
+
['$REF'] = transform_REF,
|
|
2489
|
+
['$FORMAT'] = transform_FORMAT,
|
|
2490
|
+
['$APPLY'] = transform_APPLY,
|
|
2491
|
+
},
|
|
2492
|
+
extraTransforms,
|
|
2493
|
+
{ ['$ERRS'] = errs },
|
|
2494
|
+
}, 1)
|
|
2495
|
+
|
|
2496
|
+
local out = inject(spec, store, injdef)
|
|
2497
|
+
|
|
2498
|
+
local generr = 0 < size(errs) and not collect
|
|
2499
|
+
if generr then
|
|
2500
|
+
error(table.concat(errs, ' | '))
|
|
2501
|
+
end
|
|
2502
|
+
|
|
2503
|
+
return out
|
|
2504
|
+
end
|
|
2505
|
+
|
|
2506
|
+
|
|
2507
|
+
-- A required string value. NOTE: Rejects empty strings.
|
|
2508
|
+
local function validate_STRING(inj)
|
|
2509
|
+
local out = getprop(inj.dparent, inj.key)
|
|
2510
|
+
|
|
2511
|
+
local t = typify(out)
|
|
2512
|
+
if 0 == (T_string & t) then
|
|
2513
|
+
local msg = _invalidTypeMsg(inj.path, S_string, t, out, 'V1010')
|
|
2514
|
+
table.insert(inj.errs, msg)
|
|
2515
|
+
return NONE
|
|
2516
|
+
end
|
|
2517
|
+
|
|
2518
|
+
if S_MT == out then
|
|
2519
|
+
local msg = 'Empty string at ' .. pathify(inj.path, 1)
|
|
2520
|
+
table.insert(inj.errs, msg)
|
|
2521
|
+
return NONE
|
|
2522
|
+
end
|
|
2523
|
+
|
|
2524
|
+
return out
|
|
2525
|
+
end
|
|
2526
|
+
|
|
2527
|
+
|
|
2528
|
+
-- A generic type validator. Ref is used to determine which type to check.
|
|
2529
|
+
local function validate_TYPE(inj, _val, ref)
|
|
2530
|
+
local tname = slice(ref, 1):lower()
|
|
2531
|
+
|
|
2532
|
+
-- Find type index in TYPENAME
|
|
2533
|
+
local typev = 0
|
|
2534
|
+
for i, tn in ipairs(TYPENAME) do
|
|
2535
|
+
if tn == tname then
|
|
2536
|
+
typev = 1 << (32 - i)
|
|
2537
|
+
break
|
|
2538
|
+
end
|
|
2539
|
+
end
|
|
2540
|
+
|
|
2541
|
+
-- Lua has no undefined; $NIL is equivalent to $NULL.
|
|
2542
|
+
if tname == S_nil then
|
|
2543
|
+
typev = typev | T_null
|
|
2544
|
+
end
|
|
2545
|
+
|
|
2546
|
+
local out = getprop(inj.dparent, inj.key)
|
|
2547
|
+
|
|
2548
|
+
local t = typify(out)
|
|
2549
|
+
if 0 == (t & typev) then
|
|
2550
|
+
table.insert(inj.errs, _invalidTypeMsg(inj.path, tname, t, out, 'V1001'))
|
|
2551
|
+
return NONE
|
|
2552
|
+
end
|
|
2553
|
+
|
|
2554
|
+
return out
|
|
2555
|
+
end
|
|
2556
|
+
|
|
2557
|
+
|
|
2558
|
+
-- Allow any value.
|
|
2559
|
+
local function validate_ANY(inj)
|
|
2560
|
+
local out = getprop(inj.dparent, inj.key)
|
|
2561
|
+
return out
|
|
2562
|
+
end
|
|
2563
|
+
|
|
2564
|
+
|
|
2565
|
+
-- Specify child values for map or list.
|
|
2566
|
+
-- Map syntax: {'`$CHILD`': child-template }
|
|
2567
|
+
-- List syntax: ['`$CHILD`', child-template ]
|
|
2568
|
+
local function validate_CHILD(inj)
|
|
2569
|
+
local mode, key, parent, keys, path = inj.mode, inj.key, inj.parent,
|
|
2570
|
+
inj.keys, inj.path
|
|
2571
|
+
|
|
2572
|
+
-- Map syntax.
|
|
2573
|
+
if M_KEYPRE == mode then
|
|
2574
|
+
local childtm = getprop(parent, key)
|
|
2575
|
+
|
|
2576
|
+
-- Get corresponding current object.
|
|
2577
|
+
local pkey = getelem(path, -2)
|
|
2578
|
+
local tval = getprop(inj.dparent, pkey)
|
|
2579
|
+
|
|
2580
|
+
if NONE == tval then
|
|
2581
|
+
tval = {}
|
|
2582
|
+
elseif not ismap(tval) then
|
|
2583
|
+
table.insert(inj.errs, _invalidTypeMsg(
|
|
2584
|
+
slice(inj.path, 0, -1), S_object, typify(tval), tval, 'V0220'))
|
|
2585
|
+
return NONE
|
|
2586
|
+
end
|
|
2587
|
+
|
|
2588
|
+
local ckeys = keysof(tval)
|
|
2589
|
+
for _, ckey in ipairs(ckeys) do
|
|
2590
|
+
setprop(parent, ckey, clone(childtm))
|
|
2591
|
+
|
|
2592
|
+
-- NOTE: modifying inj! This extends the child value loop in inject.
|
|
2593
|
+
table.insert(keys, ckey)
|
|
2594
|
+
end
|
|
2595
|
+
|
|
2596
|
+
-- Remove $CHILD to cleanup output.
|
|
2597
|
+
inj:setval(NONE)
|
|
2598
|
+
return NONE
|
|
2599
|
+
end
|
|
2600
|
+
|
|
2601
|
+
-- List syntax.
|
|
2602
|
+
if M_VAL == mode then
|
|
2603
|
+
if not islist(parent) then
|
|
2604
|
+
-- $CHILD was not inside a list.
|
|
2605
|
+
table.insert(inj.errs, 'Invalid $CHILD as value')
|
|
2606
|
+
return NONE
|
|
2607
|
+
end
|
|
2608
|
+
|
|
2609
|
+
local childtm = getprop(parent, 1)
|
|
2610
|
+
|
|
2611
|
+
if NONE == inj.dparent then
|
|
2612
|
+
-- Empty list as default.
|
|
2613
|
+
slice(parent, 0, 0, true)
|
|
2614
|
+
return NONE
|
|
2615
|
+
end
|
|
2616
|
+
|
|
2617
|
+
if not islist(inj.dparent) then
|
|
2618
|
+
local msg = _invalidTypeMsg(
|
|
2619
|
+
slice(inj.path, 0, -1), S_list, typify(inj.dparent), inj.dparent, 'V0230')
|
|
2620
|
+
table.insert(inj.errs, msg)
|
|
2621
|
+
inj.keyI = size(parent)
|
|
2622
|
+
return inj.dparent
|
|
2623
|
+
end
|
|
2624
|
+
|
|
2625
|
+
-- Clone children and reset inj key index.
|
|
2626
|
+
for i = 1, #inj.dparent do
|
|
2627
|
+
parent[i] = clone(childtm)
|
|
2628
|
+
end
|
|
2629
|
+
slice(parent, 0, #inj.dparent, true)
|
|
2630
|
+
inj.keyI = 0
|
|
2631
|
+
|
|
2632
|
+
local out = getprop(inj.dparent, 0)
|
|
2633
|
+
return out
|
|
2634
|
+
end
|
|
2635
|
+
|
|
2636
|
+
return NONE
|
|
2637
|
+
end
|
|
2638
|
+
|
|
2639
|
+
|
|
2640
|
+
----------------------------------------------------------
|
|
2641
|
+
-- Forward declaration for validate to resolve lack of function hoisting
|
|
2642
|
+
----------------------------------------------------------
|
|
2643
|
+
local validate
|
|
2644
|
+
|
|
2645
|
+
|
|
2646
|
+
-- Match at least one of the specified shapes.
|
|
2647
|
+
-- Syntax: ['`$ONE`', alt0, alt1, ...]
|
|
2648
|
+
local function validate_ONE(inj, _val, _ref, store)
|
|
2649
|
+
local mode, parent, keyI = inj.mode, inj.parent, inj.keyI
|
|
2650
|
+
|
|
2651
|
+
-- Only operate in val mode, since parent is a list.
|
|
2652
|
+
if M_VAL == mode then
|
|
2653
|
+
if not islist(parent) or 0 ~= keyI then
|
|
2654
|
+
table.insert(inj.errs,
|
|
2655
|
+
'The $ONE validator at field ' .. pathify(inj.path, 1, 1) ..
|
|
2656
|
+
' must be the first element of an array.')
|
|
2657
|
+
return
|
|
2658
|
+
end
|
|
2659
|
+
|
|
2660
|
+
inj.keyI = size(inj.keys)
|
|
2661
|
+
|
|
2662
|
+
-- Clean up structure, replacing [$ONE, ...] with current
|
|
2663
|
+
inj:setval(inj.dparent, 2)
|
|
2664
|
+
|
|
2665
|
+
inj.path = slice(inj.path, 0, -1)
|
|
2666
|
+
inj.key = getelem(inj.path, -1)
|
|
2667
|
+
|
|
2668
|
+
local tvals = slice(parent, 1)
|
|
2669
|
+
if 0 == size(tvals) then
|
|
2670
|
+
table.insert(inj.errs,
|
|
2671
|
+
'The $ONE validator at field ' .. pathify(inj.path, 1, 1) ..
|
|
2672
|
+
' must have at least one argument.')
|
|
2673
|
+
return
|
|
2674
|
+
end
|
|
2675
|
+
|
|
2676
|
+
-- See if we can find a match.
|
|
2677
|
+
for _, tval in ipairs(tvals) do
|
|
2678
|
+
local terrs = {}
|
|
2679
|
+
setmetatable(terrs, { __jsontype = "array" })
|
|
2680
|
+
|
|
2681
|
+
local vstore = merge({ {}, store }, 1)
|
|
2682
|
+
vstore["$TOP"] = inj.dparent
|
|
2683
|
+
|
|
2684
|
+
local vcurrent = validate(inj.dparent, tval, {
|
|
2685
|
+
extra = vstore,
|
|
2686
|
+
errs = terrs,
|
|
2687
|
+
meta = inj.meta,
|
|
2688
|
+
})
|
|
2689
|
+
|
|
2690
|
+
inj:setval(vcurrent, -2)
|
|
2691
|
+
|
|
2692
|
+
-- Accept current value if there was a match
|
|
2693
|
+
if 0 == size(terrs) then
|
|
2694
|
+
return
|
|
2695
|
+
end
|
|
2696
|
+
end
|
|
2697
|
+
|
|
2698
|
+
-- There was no match.
|
|
2699
|
+
local valdesc = {}
|
|
2700
|
+
for _, v in ipairs(tvals) do
|
|
2701
|
+
table.insert(valdesc, stringify(v))
|
|
2702
|
+
end
|
|
2703
|
+
local valdesc_str = table.concat(valdesc, ', ')
|
|
2704
|
+
valdesc_str = valdesc_str:gsub('`%$([A-Z]+)`', function(p1)
|
|
2705
|
+
return string.lower(p1)
|
|
2706
|
+
end)
|
|
2707
|
+
|
|
2708
|
+
table.insert(inj.errs,
|
|
2709
|
+
_invalidTypeMsg(inj.path,
|
|
2710
|
+
(1 < size(tvals) and 'one of ' or '') .. valdesc_str, typify(inj.dparent),
|
|
2711
|
+
inj.dparent, 'V0210'))
|
|
2712
|
+
end
|
|
2713
|
+
end
|
|
2714
|
+
|
|
2715
|
+
|
|
2716
|
+
-- Match exactly one of the specified values.
|
|
2717
|
+
-- Syntax: ['`$EXACT`', val1, val2, ...]
|
|
2718
|
+
local function validate_EXACT(inj)
|
|
2719
|
+
local mode, parent, key, keyI = inj.mode, inj.parent, inj.key, inj.keyI
|
|
2720
|
+
|
|
2721
|
+
-- Only operate in val mode, since parent is a list.
|
|
2722
|
+
if M_VAL == mode then
|
|
2723
|
+
if not islist(parent) or 0 ~= keyI then
|
|
2724
|
+
table.insert(inj.errs, 'The $EXACT validator at field ' ..
|
|
2725
|
+
pathify(inj.path, 1, 1) ..
|
|
2726
|
+
' must be the first element of an array.')
|
|
2727
|
+
return
|
|
2728
|
+
end
|
|
2729
|
+
|
|
2730
|
+
inj.keyI = size(inj.keys)
|
|
2731
|
+
|
|
2732
|
+
-- Clean up structure, replacing [$EXACT, ...] with current data parent
|
|
2733
|
+
inj:setval(inj.dparent, 2)
|
|
2734
|
+
|
|
2735
|
+
inj.path = slice(inj.path, 0, -1)
|
|
2736
|
+
inj.key = getelem(inj.path, -1)
|
|
2737
|
+
|
|
2738
|
+
local tvals = slice(parent, 1)
|
|
2739
|
+
if 0 == size(tvals) then
|
|
2740
|
+
table.insert(inj.errs, 'The $EXACT validator at field ' ..
|
|
2741
|
+
pathify(inj.path, 1, 1) ..
|
|
2742
|
+
' must have at least one argument.')
|
|
2743
|
+
return
|
|
2744
|
+
end
|
|
2745
|
+
|
|
2746
|
+
-- See if we can find an exact value match.
|
|
2747
|
+
local currentstr = nil
|
|
2748
|
+
for _, tval in ipairs(tvals) do
|
|
2749
|
+
local exactmatch = tval == inj.dparent
|
|
2750
|
+
|
|
2751
|
+
if not exactmatch and isnode(tval) then
|
|
2752
|
+
if currentstr == nil then
|
|
2753
|
+
currentstr = stringify(inj.dparent)
|
|
2754
|
+
end
|
|
2755
|
+
local tvalstr = stringify(tval)
|
|
2756
|
+
exactmatch = tvalstr == currentstr
|
|
2757
|
+
end
|
|
2758
|
+
|
|
2759
|
+
if exactmatch then
|
|
2760
|
+
return
|
|
2761
|
+
end
|
|
2762
|
+
end
|
|
2763
|
+
|
|
2764
|
+
local valdesc = {}
|
|
2765
|
+
for _, v in ipairs(tvals) do
|
|
2766
|
+
table.insert(valdesc, stringify(v))
|
|
2767
|
+
end
|
|
2768
|
+
local valdesc_str = table.concat(valdesc, ', ')
|
|
2769
|
+
|
|
2770
|
+
table.insert(inj.errs, _invalidTypeMsg(
|
|
2771
|
+
inj.path,
|
|
2772
|
+
(1 < size(inj.path) and '' or 'value ') ..
|
|
2773
|
+
'exactly equal to ' .. (1 == size(tvals) and '' or 'one of ') .. valdesc_str,
|
|
2774
|
+
typify(inj.dparent), inj.dparent, 'V0110'))
|
|
2775
|
+
else
|
|
2776
|
+
delprop(parent, key)
|
|
2777
|
+
end
|
|
2778
|
+
end
|
|
2779
|
+
|
|
2780
|
+
|
|
2781
|
+
-- This is the "modify" argument to inject. Use this to perform
|
|
2782
|
+
-- generic validation. Runs *after* any special commands.
|
|
2783
|
+
_validation = function(pval, key, parent, inj)
|
|
2784
|
+
if NONE == inj then
|
|
2785
|
+
return
|
|
2786
|
+
end
|
|
2787
|
+
|
|
2788
|
+
if SKIP == pval then
|
|
2789
|
+
return
|
|
2790
|
+
end
|
|
2791
|
+
|
|
2792
|
+
-- select needs exact matches
|
|
2793
|
+
local exact = getprop(inj.meta, S_BEXACT, false)
|
|
2794
|
+
|
|
2795
|
+
-- Current val to verify.
|
|
2796
|
+
local cval = getprop(inj.dparent, key)
|
|
2797
|
+
|
|
2798
|
+
if NONE == inj or (not exact and NONE == cval) then
|
|
2799
|
+
return
|
|
2800
|
+
end
|
|
2801
|
+
|
|
2802
|
+
local ptype = typify(pval)
|
|
2803
|
+
|
|
2804
|
+
-- Delete any special commands remaining.
|
|
2805
|
+
if 0 < (T_string & ptype) and string.find(pval, S_DS, 1, true) then
|
|
2806
|
+
return
|
|
2807
|
+
end
|
|
2808
|
+
|
|
2809
|
+
local ctype = typify(cval)
|
|
2810
|
+
|
|
2811
|
+
-- Type mismatch.
|
|
2812
|
+
if ptype ~= ctype and NONE ~= pval then
|
|
2813
|
+
table.insert(inj.errs, _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0010'))
|
|
2814
|
+
return
|
|
2815
|
+
end
|
|
2816
|
+
|
|
2817
|
+
if ismap(cval) then
|
|
2818
|
+
if not ismap(pval) then
|
|
2819
|
+
table.insert(inj.errs, _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0020'))
|
|
2820
|
+
return
|
|
2821
|
+
end
|
|
2822
|
+
|
|
2823
|
+
local ckeys = keysof(cval)
|
|
2824
|
+
local pkeys = keysof(pval)
|
|
2825
|
+
|
|
2826
|
+
-- Empty spec object {} means object can be open (any keys).
|
|
2827
|
+
if 0 < size(pkeys) and true ~= getprop(pval, '`$OPEN`') then
|
|
2828
|
+
local badkeys = {}
|
|
2829
|
+
|
|
2830
|
+
for _, ckey in ipairs(ckeys) do
|
|
2831
|
+
if not haskey(pval, ckey) then
|
|
2832
|
+
table.insert(badkeys, ckey)
|
|
2833
|
+
end
|
|
2834
|
+
end
|
|
2835
|
+
|
|
2836
|
+
-- Closed object, so reject extra keys not in shape.
|
|
2837
|
+
if 0 < size(badkeys) then
|
|
2838
|
+
local msg =
|
|
2839
|
+
'Unexpected keys at field ' .. pathify(inj.path, 1) .. S_VIZ .. table.concat(badkeys, ', ')
|
|
2840
|
+
table.insert(inj.errs, msg)
|
|
2841
|
+
end
|
|
2842
|
+
else
|
|
2843
|
+
-- Object is open, so merge in extra keys.
|
|
2844
|
+
merge({ pval, cval })
|
|
2845
|
+
if isnode(pval) then
|
|
2846
|
+
delprop(pval, '`$OPEN`')
|
|
2847
|
+
end
|
|
2848
|
+
end
|
|
2849
|
+
elseif islist(cval) then
|
|
2850
|
+
if not islist(pval) then
|
|
2851
|
+
table.insert(inj.errs, _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0030'))
|
|
2852
|
+
end
|
|
2853
|
+
elseif exact then
|
|
2854
|
+
if cval ~= pval then
|
|
2855
|
+
local pathmsg = 1 < size(inj.path)
|
|
2856
|
+
and ('at field ' .. pathify(inj.path, 1) .. S_VIZ) or S_MT
|
|
2857
|
+
table.insert(inj.errs, 'Value ' .. pathmsg .. tostring(cval) ..
|
|
2858
|
+
' should equal ' .. tostring(pval) .. '.')
|
|
2859
|
+
end
|
|
2860
|
+
else
|
|
2861
|
+
-- Spec value was a default, copy over data
|
|
2862
|
+
setprop(parent, key, cval)
|
|
2863
|
+
end
|
|
2864
|
+
end
|
|
2865
|
+
|
|
2866
|
+
|
|
2867
|
+
-- Validate a data structure against a shape specification. The shape
|
|
2868
|
+
-- specification follows the "by example" principle. Plain data in
|
|
2869
|
+
-- the shape is treated as default values that also specify the
|
|
2870
|
+
-- required type. Thus shape {a=1} validates {a=2}, since the types
|
|
2871
|
+
-- (number) match, but not {a='A'}. Shape {a=1} against data {}
|
|
2872
|
+
-- returns {a=1} as a=1 is the default value of the a key. Special
|
|
2873
|
+
-- validation commands (in the same syntax as transform) are also
|
|
2874
|
+
-- provided to specify required values. Thus shape {a='`$STRING`'}
|
|
2875
|
+
-- validates {a='A'} but not {a=1}. Empty map or list means the node
|
|
2876
|
+
-- is open, and if missing an empty default is inserted.
|
|
2877
|
+
-- @param data (any) Source data to validate
|
|
2878
|
+
-- @param spec (any) Validation specification
|
|
2879
|
+
-- @param extra (any) Additional custom checks
|
|
2880
|
+
-- @param collecterrs (table) Optional array to collect error messages
|
|
2881
|
+
-- @return (any) The validated data
|
|
2882
|
+
validate = function(data, spec, injdef)
|
|
2883
|
+
local extra = injdef and injdef.extra or nil
|
|
2884
|
+
|
|
2885
|
+
local collect = injdef ~= nil and injdef.errs ~= nil
|
|
2886
|
+
local errs = (injdef and injdef.errs) or {}
|
|
2887
|
+
setmetatable(errs, { __jsontype = "array" })
|
|
2888
|
+
|
|
2889
|
+
local store = merge({
|
|
2890
|
+
{
|
|
2891
|
+
-- Remove the transform commands.
|
|
2892
|
+
["$DELETE"] = false,
|
|
2893
|
+
["$COPY"] = false,
|
|
2894
|
+
["$KEY"] = false,
|
|
2895
|
+
["$META"] = false,
|
|
2896
|
+
["$MERGE"] = false,
|
|
2897
|
+
["$EACH"] = false,
|
|
2898
|
+
["$PACK"] = false,
|
|
2899
|
+
|
|
2900
|
+
-- Validation functions
|
|
2901
|
+
["$STRING"] = validate_STRING,
|
|
2902
|
+
["$NUMBER"] = validate_TYPE,
|
|
2903
|
+
["$INTEGER"] = validate_TYPE,
|
|
2904
|
+
["$DECIMAL"] = validate_TYPE,
|
|
2905
|
+
["$BOOLEAN"] = validate_TYPE,
|
|
2906
|
+
["$NULL"] = validate_TYPE,
|
|
2907
|
+
["$NIL"] = validate_TYPE,
|
|
2908
|
+
["$MAP"] = validate_TYPE,
|
|
2909
|
+
["$LIST"] = validate_TYPE,
|
|
2910
|
+
["$FUNCTION"] = validate_TYPE,
|
|
2911
|
+
["$INSTANCE"] = validate_TYPE,
|
|
2912
|
+
["$ANY"] = validate_ANY,
|
|
2913
|
+
["$CHILD"] = validate_CHILD,
|
|
2914
|
+
["$ONE"] = validate_ONE,
|
|
2915
|
+
["$EXACT"] = validate_EXACT,
|
|
2916
|
+
},
|
|
2917
|
+
|
|
2918
|
+
getdef(extra, {}),
|
|
2919
|
+
|
|
2920
|
+
-- A special top level value to collect errors.
|
|
2921
|
+
{
|
|
2922
|
+
["$ERRS"] = errs,
|
|
2923
|
+
}
|
|
2924
|
+
}, 1)
|
|
2925
|
+
|
|
2926
|
+
local meta = (injdef and injdef.meta) or {}
|
|
2927
|
+
setprop(meta, S_BEXACT, getprop(meta, S_BEXACT, false))
|
|
2928
|
+
|
|
2929
|
+
local out = transform(data, spec, {
|
|
2930
|
+
meta = meta,
|
|
2931
|
+
extra = store,
|
|
2932
|
+
modify = _validation,
|
|
2933
|
+
handler = _validatehandler,
|
|
2934
|
+
errs = errs,
|
|
2935
|
+
})
|
|
2936
|
+
|
|
2937
|
+
local generr = (0 < size(errs) and not collect)
|
|
2938
|
+
|
|
2939
|
+
if generr then
|
|
2940
|
+
error(table.concat(errs, ' | '))
|
|
2941
|
+
end
|
|
2942
|
+
|
|
2943
|
+
return out
|
|
2944
|
+
end
|
|
2945
|
+
|
|
2946
|
+
|
|
2947
|
+
-- Select query operators
|
|
2948
|
+
-- ======================
|
|
2949
|
+
|
|
2950
|
+
|
|
2951
|
+
local function select_AND(inj, _val, _ref, store)
|
|
2952
|
+
if M_KEYPRE == inj.mode then
|
|
2953
|
+
local terms = getprop(inj.parent, inj.key)
|
|
2954
|
+
|
|
2955
|
+
local ppath = slice(inj.path, 0, -1)
|
|
2956
|
+
local point = getpath(store, ppath)
|
|
2957
|
+
|
|
2958
|
+
local vstore = merge({ {}, store }, 1)
|
|
2959
|
+
vstore["$TOP"] = point
|
|
2960
|
+
|
|
2961
|
+
for _, term in ipairs(terms) do
|
|
2962
|
+
local terrs = {}
|
|
2963
|
+
|
|
2964
|
+
validate(point, term, {
|
|
2965
|
+
extra = vstore,
|
|
2966
|
+
errs = terrs,
|
|
2967
|
+
meta = inj.meta,
|
|
2968
|
+
})
|
|
2969
|
+
|
|
2970
|
+
if 0 ~= size(terrs) then
|
|
2971
|
+
table.insert(inj.errs,
|
|
2972
|
+
'AND:' .. pathify(ppath) .. S_VIZ .. stringify(point) .. ' fail:' .. stringify(terms))
|
|
2973
|
+
end
|
|
2974
|
+
end
|
|
2975
|
+
|
|
2976
|
+
local gkey = getelem(inj.path, -2)
|
|
2977
|
+
local gp = getelem(inj.nodes, -2)
|
|
2978
|
+
setprop(gp, gkey, point)
|
|
2979
|
+
end
|
|
2980
|
+
end
|
|
2981
|
+
|
|
2982
|
+
|
|
2983
|
+
local function select_OR(inj, _val, _ref, store)
|
|
2984
|
+
if M_KEYPRE == inj.mode then
|
|
2985
|
+
local terms = getprop(inj.parent, inj.key)
|
|
2986
|
+
|
|
2987
|
+
local ppath = slice(inj.path, 0, -1)
|
|
2988
|
+
local point = getpath(store, ppath)
|
|
2989
|
+
|
|
2990
|
+
local vstore = merge({ {}, store }, 1)
|
|
2991
|
+
vstore["$TOP"] = point
|
|
2992
|
+
|
|
2993
|
+
for _, term in ipairs(terms) do
|
|
2994
|
+
local terrs = {}
|
|
2995
|
+
|
|
2996
|
+
validate(point, term, {
|
|
2997
|
+
extra = vstore,
|
|
2998
|
+
errs = terrs,
|
|
2999
|
+
meta = inj.meta,
|
|
3000
|
+
})
|
|
3001
|
+
|
|
3002
|
+
if 0 == size(terrs) then
|
|
3003
|
+
local gkey = getelem(inj.path, -2)
|
|
3004
|
+
local gp = getelem(inj.nodes, -2)
|
|
3005
|
+
setprop(gp, gkey, point)
|
|
3006
|
+
|
|
3007
|
+
return
|
|
3008
|
+
end
|
|
3009
|
+
end
|
|
3010
|
+
|
|
3011
|
+
table.insert(inj.errs,
|
|
3012
|
+
'OR:' .. pathify(ppath) .. S_VIZ .. stringify(point) .. ' fail:' .. stringify(terms))
|
|
3013
|
+
end
|
|
3014
|
+
end
|
|
3015
|
+
|
|
3016
|
+
|
|
3017
|
+
local function select_NOT(inj, _val, _ref, store)
|
|
3018
|
+
if M_KEYPRE == inj.mode then
|
|
3019
|
+
local term = getprop(inj.parent, inj.key)
|
|
3020
|
+
|
|
3021
|
+
local ppath = slice(inj.path, 0, -1)
|
|
3022
|
+
local point = getpath(store, ppath)
|
|
3023
|
+
|
|
3024
|
+
local vstore = merge({ {}, store }, 1)
|
|
3025
|
+
vstore["$TOP"] = point
|
|
3026
|
+
|
|
3027
|
+
local terrs = {}
|
|
3028
|
+
|
|
3029
|
+
validate(point, term, {
|
|
3030
|
+
extra = vstore,
|
|
3031
|
+
errs = terrs,
|
|
3032
|
+
meta = inj.meta,
|
|
3033
|
+
})
|
|
3034
|
+
|
|
3035
|
+
if 0 == size(terrs) then
|
|
3036
|
+
table.insert(inj.errs,
|
|
3037
|
+
'NOT:' .. pathify(ppath) .. S_VIZ .. stringify(point) .. ' fail:' .. stringify(term))
|
|
3038
|
+
end
|
|
3039
|
+
|
|
3040
|
+
local gkey = getelem(inj.path, -2)
|
|
3041
|
+
local gp = getelem(inj.nodes, -2)
|
|
3042
|
+
setprop(gp, gkey, point)
|
|
3043
|
+
end
|
|
3044
|
+
end
|
|
3045
|
+
|
|
3046
|
+
|
|
3047
|
+
local function select_CMP(inj, _val, ref, store)
|
|
3048
|
+
if M_KEYPRE == inj.mode then
|
|
3049
|
+
local term = getprop(inj.parent, inj.key)
|
|
3050
|
+
local gkey = getelem(inj.path, -2)
|
|
3051
|
+
|
|
3052
|
+
local ppath = slice(inj.path, 0, -1)
|
|
3053
|
+
local point = getpath(store, ppath)
|
|
3054
|
+
|
|
3055
|
+
local pass = false
|
|
3056
|
+
|
|
3057
|
+
if '$GT' == ref and point > term then
|
|
3058
|
+
pass = true
|
|
3059
|
+
elseif '$LT' == ref and point < term then
|
|
3060
|
+
pass = true
|
|
3061
|
+
elseif '$GTE' == ref and point >= term then
|
|
3062
|
+
pass = true
|
|
3063
|
+
elseif '$LTE' == ref and point <= term then
|
|
3064
|
+
pass = true
|
|
3065
|
+
elseif '$LIKE' == ref and stringify(point):match(term) then
|
|
3066
|
+
pass = true
|
|
3067
|
+
end
|
|
3068
|
+
|
|
3069
|
+
if pass then
|
|
3070
|
+
local gp = getelem(inj.nodes, -2)
|
|
3071
|
+
setprop(gp, gkey, point)
|
|
3072
|
+
else
|
|
3073
|
+
table.insert(inj.errs, 'CMP: ' .. pathify(ppath) .. S_VIZ .. stringify(point) ..
|
|
3074
|
+
' fail:' .. ref .. ' ' .. stringify(term))
|
|
3075
|
+
end
|
|
3076
|
+
end
|
|
3077
|
+
|
|
3078
|
+
return NONE
|
|
3079
|
+
end
|
|
3080
|
+
|
|
3081
|
+
|
|
3082
|
+
-- Select children matching a query.
|
|
3083
|
+
local function select_fn(children, query)
|
|
3084
|
+
if not isnode(children) then
|
|
3085
|
+
return {}
|
|
3086
|
+
end
|
|
3087
|
+
|
|
3088
|
+
if ismap(children) then
|
|
3089
|
+
local child_list = {}
|
|
3090
|
+
for _, entry in ipairs(items(children)) do
|
|
3091
|
+
setprop(entry[2], S_DKEY, entry[1])
|
|
3092
|
+
table.insert(child_list, entry[2])
|
|
3093
|
+
end
|
|
3094
|
+
children = child_list
|
|
3095
|
+
else
|
|
3096
|
+
for i, n in ipairs(children) do
|
|
3097
|
+
setprop(n, S_DKEY, i - 1)
|
|
3098
|
+
end
|
|
3099
|
+
end
|
|
3100
|
+
|
|
3101
|
+
local results = {}
|
|
3102
|
+
local injdef = {
|
|
3103
|
+
errs = {},
|
|
3104
|
+
meta = { [S_BEXACT] = true },
|
|
3105
|
+
extra = {
|
|
3106
|
+
["$AND"] = select_AND,
|
|
3107
|
+
["$OR"] = select_OR,
|
|
3108
|
+
["$NOT"] = select_NOT,
|
|
3109
|
+
["$GT"] = select_CMP,
|
|
3110
|
+
["$LT"] = select_CMP,
|
|
3111
|
+
["$GTE"] = select_CMP,
|
|
3112
|
+
["$LTE"] = select_CMP,
|
|
3113
|
+
["$LIKE"] = select_CMP,
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
local q = clone(query)
|
|
3118
|
+
|
|
3119
|
+
walk(q, function(_k, v)
|
|
3120
|
+
if ismap(v) then
|
|
3121
|
+
setprop(v, '`$OPEN`', getprop(v, '`$OPEN`', true))
|
|
3122
|
+
end
|
|
3123
|
+
return v
|
|
3124
|
+
end)
|
|
3125
|
+
|
|
3126
|
+
for _, child in ipairs(children) do
|
|
3127
|
+
injdef.errs = {}
|
|
3128
|
+
|
|
3129
|
+
validate(child, clone(q), injdef)
|
|
3130
|
+
|
|
3131
|
+
if 0 == size(injdef.errs) then
|
|
3132
|
+
table.insert(results, child)
|
|
3133
|
+
end
|
|
3134
|
+
end
|
|
3135
|
+
|
|
3136
|
+
return results
|
|
3137
|
+
end
|
|
3138
|
+
|
|
3139
|
+
|
|
3140
|
+
-- Internal utilities
|
|
3141
|
+
-- ==================
|
|
3142
|
+
|
|
3143
|
+
|
|
3144
|
+
-- Build a type validation error message.
|
|
3145
|
+
_invalidTypeMsg = function(path, needtype, vt, v, _whence)
|
|
3146
|
+
local vs = (v == nil or v == S_null) and 'no value' or stringify(v)
|
|
3147
|
+
local vtname = type(vt) == S_number and typename(vt) or tostring(vt)
|
|
3148
|
+
|
|
3149
|
+
local msg = 'Expected ' .. (1 < #path and ('field ' .. pathify(path, 1)
|
|
3150
|
+
.. ' to be ') or '') .. needtype .. ', but found ' .. ((v ~= nil and v ~= S_null)
|
|
3151
|
+
and (vtname .. S_VIZ) or '') .. vs
|
|
3152
|
+
|
|
3153
|
+
msg = msg .. '.'
|
|
3154
|
+
return msg
|
|
3155
|
+
end
|
|
3156
|
+
|
|
3157
|
+
|
|
3158
|
+
-- Default inject handler for transforms.
|
|
3159
|
+
_injecthandler = function(inj, val, ref, store)
|
|
3160
|
+
local out = val
|
|
3161
|
+
local iscmd = isfunc(val) and (NONE == ref or (type(ref) == S_string and ref:sub(1, 1) == S_DS))
|
|
3162
|
+
|
|
3163
|
+
-- Only call val function if it is a special command ($NAME format).
|
|
3164
|
+
if iscmd then
|
|
3165
|
+
out = val(inj, val, ref, store)
|
|
3166
|
+
|
|
3167
|
+
-- Update parent with value. Ensures references remain in node tree.
|
|
3168
|
+
elseif M_VAL == inj.mode and inj.full then
|
|
3169
|
+
inj:setval(val)
|
|
3170
|
+
end
|
|
3171
|
+
|
|
3172
|
+
return out
|
|
3173
|
+
end
|
|
3174
|
+
|
|
3175
|
+
|
|
3176
|
+
-- Validate handler - intercepts meta paths for validation.
|
|
3177
|
+
_validatehandler = function(inj, val, ref, store)
|
|
3178
|
+
local out = val
|
|
3179
|
+
|
|
3180
|
+
-- Check for meta path syntax: field$=value or field$~value
|
|
3181
|
+
local m = ref:match("^([^$]+)%$([=~])(.+)$")
|
|
3182
|
+
local ismetapath = m ~= nil
|
|
3183
|
+
|
|
3184
|
+
if ismetapath then
|
|
3185
|
+
local eq = ref:match("^[^$]+%$(.)") -- '=' or '~'
|
|
3186
|
+
if '=' == eq then
|
|
3187
|
+
inj:setval({ S_BEXACT, val })
|
|
3188
|
+
else
|
|
3189
|
+
inj:setval(val)
|
|
3190
|
+
end
|
|
3191
|
+
inj.keyI = -1
|
|
3192
|
+
|
|
3193
|
+
out = SKIP
|
|
3194
|
+
else
|
|
3195
|
+
out = _injecthandler(inj, val, ref, store)
|
|
3196
|
+
end
|
|
3197
|
+
|
|
3198
|
+
return out
|
|
3199
|
+
end
|
|
3200
|
+
|
|
3201
|
+
|
|
3202
|
+
-- Inject store values into a string.
|
|
3203
|
+
_injectstr = function(val, store, inj)
|
|
3204
|
+
-- Can't inject into non-strings
|
|
3205
|
+
if type(val) ~= S_string or val == S_MT then
|
|
3206
|
+
return S_MT
|
|
3207
|
+
end
|
|
3208
|
+
|
|
3209
|
+
local out = val
|
|
3210
|
+
|
|
3211
|
+
-- Full value wrapped in backticks
|
|
3212
|
+
-- R_INJECTION_FULL: /^`(\$[A-Z]+|[^`]*)[0-9]*`$/
|
|
3213
|
+
-- Matches full backtick injection, including empty `` and optional trailing digits
|
|
3214
|
+
local full_match = val:match("^`([^`]*)`$")
|
|
3215
|
+
if full_match then
|
|
3216
|
+
-- Strip optional trailing digits from $NAME patterns
|
|
3217
|
+
local name_part = full_match:match("^(%$[A-Z]+)%d+$")
|
|
3218
|
+
if name_part then
|
|
3219
|
+
full_match = name_part
|
|
3220
|
+
end
|
|
3221
|
+
end
|
|
3222
|
+
|
|
3223
|
+
if full_match then
|
|
3224
|
+
if inj then
|
|
3225
|
+
inj.full = true
|
|
3226
|
+
end
|
|
3227
|
+
|
|
3228
|
+
local pathref = full_match
|
|
3229
|
+
|
|
3230
|
+
if #pathref > 3 then
|
|
3231
|
+
pathref = pathref:gsub("%$BT", S_BT):gsub("%$DS", S_DS)
|
|
3232
|
+
end
|
|
3233
|
+
|
|
3234
|
+
out = getpath(store, pathref, inj)
|
|
3235
|
+
else
|
|
3236
|
+
-- Check for partial injections within the string.
|
|
3237
|
+
out = val:gsub("`([^`]+)`", function(ref)
|
|
3238
|
+
if #ref > 3 then
|
|
3239
|
+
ref = ref:gsub("%$BT", S_BT):gsub("%$DS", S_DS)
|
|
3240
|
+
end
|
|
3241
|
+
|
|
3242
|
+
if inj then
|
|
3243
|
+
inj.full = false
|
|
3244
|
+
end
|
|
3245
|
+
|
|
3246
|
+
local found = getpath(store, ref, inj)
|
|
3247
|
+
|
|
3248
|
+
if found == NONE then
|
|
3249
|
+
return S_MT
|
|
3250
|
+
elseif type(found) == S_string then
|
|
3251
|
+
return found
|
|
3252
|
+
elseif type(found) == 'table' then
|
|
3253
|
+
local dkjson = require("dkjson")
|
|
3254
|
+
local ok, result = pcall(dkjson.encode, found)
|
|
3255
|
+
if ok and result then return result end
|
|
3256
|
+
return islist(found) and '[...]' or '{...}'
|
|
3257
|
+
else
|
|
3258
|
+
return tostring(found)
|
|
3259
|
+
end
|
|
3260
|
+
end)
|
|
3261
|
+
|
|
3262
|
+
-- Also call the inj handler on the entire string.
|
|
3263
|
+
if nil ~= inj and isfunc(inj.handler) then
|
|
3264
|
+
inj.full = true
|
|
3265
|
+
out = inj.handler(inj, out, val, store)
|
|
3266
|
+
end
|
|
3267
|
+
end
|
|
3268
|
+
|
|
3269
|
+
return out
|
|
3270
|
+
end
|
|
3271
|
+
|
|
3272
|
+
|
|
3273
|
+
-- Define the StructUtility "class"
|
|
3274
|
+
local StructUtility = {
|
|
3275
|
+
clone = clone,
|
|
3276
|
+
delprop = delprop,
|
|
3277
|
+
escre = escre,
|
|
3278
|
+
escurl = escurl,
|
|
3279
|
+
filter = filter,
|
|
3280
|
+
flatten = flatten,
|
|
3281
|
+
getdef = getdef,
|
|
3282
|
+
getelem = getelem,
|
|
3283
|
+
getpath = getpath,
|
|
3284
|
+
getprop = getprop,
|
|
3285
|
+
haskey = haskey,
|
|
3286
|
+
inject = inject,
|
|
3287
|
+
isempty = isempty,
|
|
3288
|
+
isfunc = isfunc,
|
|
3289
|
+
iskey = iskey,
|
|
3290
|
+
islist = islist,
|
|
3291
|
+
ismap = ismap,
|
|
3292
|
+
isnode = isnode,
|
|
3293
|
+
items = items,
|
|
3294
|
+
join = join,
|
|
3295
|
+
jsonify = jsonify,
|
|
3296
|
+
keysof = keysof,
|
|
3297
|
+
merge = merge,
|
|
3298
|
+
pad = pad,
|
|
3299
|
+
pathify = pathify,
|
|
3300
|
+
replace = replace,
|
|
3301
|
+
select = select_fn,
|
|
3302
|
+
setpath = setpath,
|
|
3303
|
+
setprop = setprop,
|
|
3304
|
+
size = size,
|
|
3305
|
+
slice = slice,
|
|
3306
|
+
strkey = strkey,
|
|
3307
|
+
stringify = stringify,
|
|
3308
|
+
transform = transform,
|
|
3309
|
+
typify = typify,
|
|
3310
|
+
typename = typename,
|
|
3311
|
+
validate = validate,
|
|
3312
|
+
walk = walk,
|
|
3313
|
+
|
|
3314
|
+
SKIP = SKIP,
|
|
3315
|
+
DELETE = DELETE,
|
|
3316
|
+
|
|
3317
|
+
jm = jm,
|
|
3318
|
+
jt = jt,
|
|
3319
|
+
tn = typename,
|
|
3320
|
+
|
|
3321
|
+
T_any = T_any,
|
|
3322
|
+
T_noval = T_noval,
|
|
3323
|
+
T_boolean = T_boolean,
|
|
3324
|
+
T_decimal = T_decimal,
|
|
3325
|
+
T_integer = T_integer,
|
|
3326
|
+
T_number = T_number,
|
|
3327
|
+
T_string = T_string,
|
|
3328
|
+
T_function = T_function,
|
|
3329
|
+
T_symbol = T_symbol,
|
|
3330
|
+
T_null = T_null,
|
|
3331
|
+
T_list = T_list,
|
|
3332
|
+
T_map = T_map,
|
|
3333
|
+
T_instance = T_instance,
|
|
3334
|
+
T_scalar = T_scalar,
|
|
3335
|
+
T_node = T_node,
|
|
3336
|
+
|
|
3337
|
+
checkPlacement = checkPlacement,
|
|
3338
|
+
injectorArgs = injectorArgs,
|
|
3339
|
+
injectChild = injectChild,
|
|
3340
|
+
|
|
3341
|
+
M_KEYPRE = M_KEYPRE,
|
|
3342
|
+
M_KEYPOST = M_KEYPOST,
|
|
3343
|
+
M_VAL = M_VAL,
|
|
3344
|
+
MODENAME = MODENAME,
|
|
3345
|
+
}
|
|
3346
|
+
StructUtility.__index = StructUtility
|
|
3347
|
+
|
|
3348
|
+
-- Constructor for StructUtility
|
|
3349
|
+
function StructUtility:new(o)
|
|
3350
|
+
o = o or {}
|
|
3351
|
+
setmetatable(o, self)
|
|
3352
|
+
return o
|
|
3353
|
+
end
|
|
3354
|
+
|
|
3355
|
+
return {
|
|
3356
|
+
StructUtility = StructUtility,
|
|
3357
|
+
clone = clone,
|
|
3358
|
+
delprop = delprop,
|
|
3359
|
+
escre = escre,
|
|
3360
|
+
escurl = escurl,
|
|
3361
|
+
filter = filter,
|
|
3362
|
+
flatten = flatten,
|
|
3363
|
+
getdef = getdef,
|
|
3364
|
+
getelem = getelem,
|
|
3365
|
+
getpath = getpath,
|
|
3366
|
+
getprop = getprop,
|
|
3367
|
+
haskey = haskey,
|
|
3368
|
+
inject = inject,
|
|
3369
|
+
isempty = isempty,
|
|
3370
|
+
isfunc = isfunc,
|
|
3371
|
+
iskey = iskey,
|
|
3372
|
+
islist = islist,
|
|
3373
|
+
ismap = ismap,
|
|
3374
|
+
isnode = isnode,
|
|
3375
|
+
items = items,
|
|
3376
|
+
join = join,
|
|
3377
|
+
jsonify = jsonify,
|
|
3378
|
+
keysof = keysof,
|
|
3379
|
+
merge = merge,
|
|
3380
|
+
pad = pad,
|
|
3381
|
+
pathify = pathify,
|
|
3382
|
+
replace = replace,
|
|
3383
|
+
select = select_fn,
|
|
3384
|
+
setpath = setpath,
|
|
3385
|
+
setprop = setprop,
|
|
3386
|
+
size = size,
|
|
3387
|
+
slice = slice,
|
|
3388
|
+
strkey = strkey,
|
|
3389
|
+
stringify = stringify,
|
|
3390
|
+
transform = transform,
|
|
3391
|
+
typify = typify,
|
|
3392
|
+
typename = typename,
|
|
3393
|
+
validate = validate,
|
|
3394
|
+
walk = walk,
|
|
3395
|
+
|
|
3396
|
+
SKIP = SKIP,
|
|
3397
|
+
DELETE = DELETE,
|
|
3398
|
+
|
|
3399
|
+
jm = jm,
|
|
3400
|
+
jt = jt,
|
|
3401
|
+
|
|
3402
|
+
T_any = T_any,
|
|
3403
|
+
T_noval = T_noval,
|
|
3404
|
+
T_boolean = T_boolean,
|
|
3405
|
+
T_decimal = T_decimal,
|
|
3406
|
+
T_integer = T_integer,
|
|
3407
|
+
T_number = T_number,
|
|
3408
|
+
T_string = T_string,
|
|
3409
|
+
T_function = T_function,
|
|
3410
|
+
T_symbol = T_symbol,
|
|
3411
|
+
T_null = T_null,
|
|
3412
|
+
T_list = T_list,
|
|
3413
|
+
T_map = T_map,
|
|
3414
|
+
T_instance = T_instance,
|
|
3415
|
+
T_scalar = T_scalar,
|
|
3416
|
+
T_node = T_node,
|
|
3417
|
+
|
|
3418
|
+
M_KEYPRE = M_KEYPRE,
|
|
3419
|
+
M_KEYPOST = M_KEYPOST,
|
|
3420
|
+
M_VAL = M_VAL,
|
|
3421
|
+
|
|
3422
|
+
MODENAME = MODENAME,
|
|
3423
|
+
|
|
3424
|
+
checkPlacement = checkPlacement,
|
|
3425
|
+
injectorArgs = injectorArgs,
|
|
3426
|
+
injectChild = injectChild,
|
|
3427
|
+
}
|