@voxgig/sdkgen 0.35.1 → 0.36.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/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/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/ReadmeQuick_js.ts +1 -1
- package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +0 -4
- package/project/.sdk/src/cmp/lua/Config_lua.ts +112 -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 +483 -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 +107 -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 +484 -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 +93 -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 +475 -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 +101 -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/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/src/cmp/ts/fragment/Main.fragment.ts +0 -3
- 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/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/runner.lua +86 -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 +3417 -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 +193 -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/Runner.php +89 -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 +101 -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 +98 -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 +50 -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 +66 -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 +3431 -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 +77 -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/runner.rb +65 -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/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/tm/go/test/exists_test.go +0 -16
|
@@ -0,0 +1,2770 @@
|
|
|
1
|
+
# Copyright (c) 2025 Voxgig Ltd. MIT LICENSE.
|
|
2
|
+
#
|
|
3
|
+
# Voxgig Struct
|
|
4
|
+
# =============
|
|
5
|
+
#
|
|
6
|
+
# Utility functions to manipulate in-memory JSON-like data structures.
|
|
7
|
+
# This Python version follows the same design and logic as the original
|
|
8
|
+
# TypeScript version, using "by-example" transformation of data.
|
|
9
|
+
#
|
|
10
|
+
# Main utilities
|
|
11
|
+
# - getpath: get the value at a key path deep inside an object.
|
|
12
|
+
# - merge: merge multiple nodes, overriding values in earlier nodes.
|
|
13
|
+
# - walk: walk a node tree, applying a function at each node and leaf.
|
|
14
|
+
# - inject: inject values from a data store into a new data structure.
|
|
15
|
+
# - transform: transform a data structure to an example structure.
|
|
16
|
+
# - validate: validate a data structure against a shape specification.
|
|
17
|
+
#
|
|
18
|
+
# Minor utilities
|
|
19
|
+
# - isnode, islist, ismap, iskey, isfunc: identify value kinds.
|
|
20
|
+
# - isempty: undefined values, or empty nodes.
|
|
21
|
+
# - keysof: sorted list of node keys (ascending).
|
|
22
|
+
# - haskey: true if key value is defined.
|
|
23
|
+
# - clone: create a copy of a JSON-like data structure.
|
|
24
|
+
# - items: list entries of a map or list as [key, value] pairs.
|
|
25
|
+
# - getprop: safely get a property value by key.
|
|
26
|
+
# - getelem: safely get a list element value by key/index.
|
|
27
|
+
# - setprop: safely set a property value by key.
|
|
28
|
+
# - size: get the size of a value (length for lists, strings; count for maps).
|
|
29
|
+
# - slice: return a part of a list or other value.
|
|
30
|
+
# - pad: pad a string to a specified length.
|
|
31
|
+
# - stringify: human-friendly string version of a value.
|
|
32
|
+
# - escre: escape a regular expresion string.
|
|
33
|
+
# - escurl: escape a url.
|
|
34
|
+
# - joinurl: join parts of a url, merging forward slashes.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
from typing import *
|
|
38
|
+
from datetime import datetime
|
|
39
|
+
import urllib.parse
|
|
40
|
+
import json
|
|
41
|
+
import re
|
|
42
|
+
import math
|
|
43
|
+
import inspect
|
|
44
|
+
|
|
45
|
+
# Regex patterns for path processing
|
|
46
|
+
R_META_PATH = re.compile(r'^([^$]+)\$([=~])(.+)$') # Meta path syntax.
|
|
47
|
+
R_DOUBLE_DOLLAR = re.compile(r'\$\$') # Double dollar escape sequence.
|
|
48
|
+
|
|
49
|
+
# Mode value for inject step.
|
|
50
|
+
S_MKEYPRE = 'key:pre'
|
|
51
|
+
S_MKEYPOST = 'key:post'
|
|
52
|
+
S_MVAL = 'val'
|
|
53
|
+
S_MKEY = 'key'
|
|
54
|
+
|
|
55
|
+
M_KEYPRE = 1
|
|
56
|
+
M_KEYPOST = 2
|
|
57
|
+
M_VAL = 4
|
|
58
|
+
_MODE_TO_NUM = {S_MKEYPRE: M_KEYPRE, S_MKEYPOST: M_KEYPOST, S_MVAL: M_VAL}
|
|
59
|
+
_PLACEMENT = {M_VAL: 'value', M_KEYPRE: S_MKEY, M_KEYPOST: S_MKEY}
|
|
60
|
+
MODENAME = {M_VAL: 'val', M_KEYPRE: 'key:pre', M_KEYPOST: 'key:post'}
|
|
61
|
+
|
|
62
|
+
# Special keys.
|
|
63
|
+
S_DKEY = '$KEY'
|
|
64
|
+
S_BANNO = '`$ANNO`'
|
|
65
|
+
S_DTOP = '$TOP'
|
|
66
|
+
S_DERRS = '$ERRS'
|
|
67
|
+
S_DSPEC = '$SPEC'
|
|
68
|
+
S_BMETA = 'meta'
|
|
69
|
+
S_BEXACT = '`$EXACT`'
|
|
70
|
+
S_BVAL = '`$VAL`'
|
|
71
|
+
S_BKEY = '`$KEY`'
|
|
72
|
+
|
|
73
|
+
# General strings.
|
|
74
|
+
S_array = 'array'
|
|
75
|
+
S_integer = 'integer'
|
|
76
|
+
S_decimal = 'decimal'
|
|
77
|
+
S_map = 'map'
|
|
78
|
+
S_list = 'list'
|
|
79
|
+
S_nil = 'nil'
|
|
80
|
+
S_instance = 'instance'
|
|
81
|
+
S_node = 'node'
|
|
82
|
+
S_scalar = 'scalar'
|
|
83
|
+
S_any = 'any'
|
|
84
|
+
S_base = 'base'
|
|
85
|
+
S_boolean = 'boolean'
|
|
86
|
+
S_function = 'function'
|
|
87
|
+
S_number = 'number'
|
|
88
|
+
S_object = 'object'
|
|
89
|
+
S_string = 'string'
|
|
90
|
+
S_null = 'null'
|
|
91
|
+
S_key = 'key'
|
|
92
|
+
S_parent = 'parent'
|
|
93
|
+
S_MT = ''
|
|
94
|
+
S_BT = '`'
|
|
95
|
+
S_DS = '$'
|
|
96
|
+
S_DT = '.'
|
|
97
|
+
S_CM = ','
|
|
98
|
+
S_CN = ':'
|
|
99
|
+
S_FS = '/'
|
|
100
|
+
S_KEY = 'KEY'
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# Type bit flags (mirroring TypeScript)
|
|
104
|
+
_t = 31
|
|
105
|
+
T_any = (1 << _t) - 1
|
|
106
|
+
T_noval = 1 << (_t := _t - 1)
|
|
107
|
+
T_boolean = 1 << (_t := _t - 1)
|
|
108
|
+
T_decimal = 1 << (_t := _t - 1)
|
|
109
|
+
T_integer = 1 << (_t := _t - 1)
|
|
110
|
+
T_number = 1 << (_t := _t - 1)
|
|
111
|
+
T_string = 1 << (_t := _t - 1)
|
|
112
|
+
T_function = 1 << (_t := _t - 1)
|
|
113
|
+
T_symbol = 1 << (_t := _t - 1)
|
|
114
|
+
T_null = 1 << (_t := _t - 1)
|
|
115
|
+
_t -= 7
|
|
116
|
+
T_list = 1 << (_t := _t - 1)
|
|
117
|
+
T_map = 1 << (_t := _t - 1)
|
|
118
|
+
T_instance = 1 << (_t := _t - 1)
|
|
119
|
+
_t -= 4
|
|
120
|
+
T_scalar = 1 << (_t := _t - 1)
|
|
121
|
+
T_node = 1 << (_t := _t - 1)
|
|
122
|
+
|
|
123
|
+
TYPENAME = [
|
|
124
|
+
S_any,
|
|
125
|
+
S_nil,
|
|
126
|
+
S_boolean,
|
|
127
|
+
S_decimal,
|
|
128
|
+
S_integer,
|
|
129
|
+
S_number,
|
|
130
|
+
S_string,
|
|
131
|
+
S_function,
|
|
132
|
+
'symbol',
|
|
133
|
+
S_null,
|
|
134
|
+
'', '', '',
|
|
135
|
+
'', '', '', '',
|
|
136
|
+
S_list,
|
|
137
|
+
S_map,
|
|
138
|
+
S_instance,
|
|
139
|
+
'', '', '', '',
|
|
140
|
+
S_scalar,
|
|
141
|
+
S_node,
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
S_VIZ = ': '
|
|
145
|
+
|
|
146
|
+
# The standard undefined value for this language.
|
|
147
|
+
UNDEF = None
|
|
148
|
+
SKIP = {'`$SKIP`': True}
|
|
149
|
+
DELETE = {'`$DELETE`': True}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class Injection:
|
|
153
|
+
"""
|
|
154
|
+
Injection state used for recursive injection into JSON-like data structures.
|
|
155
|
+
"""
|
|
156
|
+
def __init__(
|
|
157
|
+
self,
|
|
158
|
+
mode: str, # Injection mode: key:pre, val, key:post.
|
|
159
|
+
full: bool, # Transform escape was full key name.
|
|
160
|
+
keyI: int, # Index of parent key in list of parent keys.
|
|
161
|
+
keys: List[str], # List of parent keys.
|
|
162
|
+
key: str, # Current parent key.
|
|
163
|
+
val: Any, # Current child value.
|
|
164
|
+
parent: Any, # Current parent (in transform specification).
|
|
165
|
+
path: List[str], # Path to current node.
|
|
166
|
+
nodes: List[Any], # Stack of ancestor nodes
|
|
167
|
+
handler: Any, # Custom handler for injections.
|
|
168
|
+
errs: List[Any] = None, # Error collector.
|
|
169
|
+
meta: Dict[str, Any] = None, # Custom meta data.
|
|
170
|
+
base: Optional[str] = None, # Base key for data in store, if any.
|
|
171
|
+
modify: Optional[Any] = None, # Modify injection output.
|
|
172
|
+
extra: Optional[Any] = None # Extra data for injection.
|
|
173
|
+
) -> None:
|
|
174
|
+
self.mode = mode
|
|
175
|
+
self.full = full
|
|
176
|
+
self.keyI = keyI
|
|
177
|
+
self.keys = keys
|
|
178
|
+
self.key = key
|
|
179
|
+
self.val = val
|
|
180
|
+
self.parent = parent
|
|
181
|
+
self.path = path
|
|
182
|
+
self.nodes = nodes
|
|
183
|
+
self.handler = handler
|
|
184
|
+
self.errs = errs
|
|
185
|
+
self.meta = meta or {}
|
|
186
|
+
self.base = base
|
|
187
|
+
self.modify = modify
|
|
188
|
+
self.extra = extra
|
|
189
|
+
self.prior = None
|
|
190
|
+
self.dparent = UNDEF
|
|
191
|
+
self.dpath = [S_DTOP]
|
|
192
|
+
self.root = None # Virtual root parent; set at top level so we can return it after transforms
|
|
193
|
+
|
|
194
|
+
def descend(self):
|
|
195
|
+
if '__d' not in self.meta:
|
|
196
|
+
self.meta['__d'] = 0
|
|
197
|
+
self.meta['__d'] += 1
|
|
198
|
+
|
|
199
|
+
parentkey = getelem(self.path, -2)
|
|
200
|
+
|
|
201
|
+
if self.dparent is UNDEF:
|
|
202
|
+
if 1 < size(self.dpath):
|
|
203
|
+
self.dpath = self.dpath + [parentkey]
|
|
204
|
+
else:
|
|
205
|
+
if parentkey is not None:
|
|
206
|
+
self.dparent = getprop(self.dparent, parentkey)
|
|
207
|
+
|
|
208
|
+
lastpart = getelem(self.dpath, -1)
|
|
209
|
+
if lastpart == '$:' + str(parentkey):
|
|
210
|
+
self.dpath = slice(self.dpath, -1)
|
|
211
|
+
else:
|
|
212
|
+
self.dpath = self.dpath + [parentkey]
|
|
213
|
+
|
|
214
|
+
return self.dparent
|
|
215
|
+
|
|
216
|
+
def child(self, keyI: int, keys: List[str]) -> 'Injection':
|
|
217
|
+
"""Create a child state object with the given key index and keys."""
|
|
218
|
+
key = strkey(keys[keyI])
|
|
219
|
+
val = self.val
|
|
220
|
+
|
|
221
|
+
cinj = Injection(
|
|
222
|
+
mode=self.mode,
|
|
223
|
+
full=self.full,
|
|
224
|
+
keyI=keyI,
|
|
225
|
+
keys=keys,
|
|
226
|
+
key=key,
|
|
227
|
+
val=getprop(val, key),
|
|
228
|
+
parent=val,
|
|
229
|
+
path=self.path + [key],
|
|
230
|
+
nodes=self.nodes + [val],
|
|
231
|
+
handler=self.handler,
|
|
232
|
+
errs=self.errs,
|
|
233
|
+
meta=self.meta,
|
|
234
|
+
base=self.base,
|
|
235
|
+
modify=self.modify
|
|
236
|
+
)
|
|
237
|
+
cinj.prior = self
|
|
238
|
+
cinj.dpath = self.dpath[:]
|
|
239
|
+
cinj.dparent = self.dparent
|
|
240
|
+
cinj.extra = self.extra # Preserve extra (contains transform functions)
|
|
241
|
+
cinj.root = getattr(self, 'root', None)
|
|
242
|
+
|
|
243
|
+
return cinj
|
|
244
|
+
|
|
245
|
+
def setval(self, val: Any, ancestor: Optional[int] = None) -> Any:
|
|
246
|
+
"""Set the value in the parent node at the specified ancestor level."""
|
|
247
|
+
if ancestor is None or ancestor < 2:
|
|
248
|
+
return setprop(self.parent, self.key, val)
|
|
249
|
+
else:
|
|
250
|
+
return setprop(getelem(self.nodes, 0 - ancestor), getelem(self.path, 0 - ancestor), val)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def getdef(val, alt):
|
|
254
|
+
"Get a defined value. Returns alt if val is undefined."
|
|
255
|
+
if val is UNDEF or val is None:
|
|
256
|
+
return alt
|
|
257
|
+
return val
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def isnode(val: Any = UNDEF) -> bool:
|
|
261
|
+
"Value is a node - defined, and a map (hash) or list (array)."
|
|
262
|
+
return isinstance(val, (dict, list))
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def ismap(val: Any = UNDEF) -> bool:
|
|
266
|
+
"Value is a defined map (hash) with string keys."
|
|
267
|
+
return isinstance(val, dict)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def islist(val: Any = UNDEF) -> bool:
|
|
271
|
+
"Value is a defined list (array) with integer keys (indexes)."
|
|
272
|
+
return isinstance(val, list)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def iskey(key: Any = UNDEF) -> bool:
|
|
276
|
+
"Value is a defined string (non-empty) or integer key."
|
|
277
|
+
if isinstance(key, str):
|
|
278
|
+
return len(key) > 0
|
|
279
|
+
# Exclude bool (which is a subclass of int)
|
|
280
|
+
if isinstance(key, bool):
|
|
281
|
+
return False
|
|
282
|
+
if isinstance(key, int):
|
|
283
|
+
return True
|
|
284
|
+
if isinstance(key, float):
|
|
285
|
+
return True
|
|
286
|
+
return False
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def size(val: Any = UNDEF) -> int:
|
|
290
|
+
"""Determine the size of a value (length for lists/strings, count for maps)"""
|
|
291
|
+
if val is UNDEF:
|
|
292
|
+
return 0
|
|
293
|
+
if islist(val):
|
|
294
|
+
return len(val)
|
|
295
|
+
elif ismap(val):
|
|
296
|
+
return len(val.keys())
|
|
297
|
+
|
|
298
|
+
if isinstance(val, str):
|
|
299
|
+
return len(val)
|
|
300
|
+
elif isinstance(val, (int, float)):
|
|
301
|
+
return math.floor(val)
|
|
302
|
+
elif isinstance(val, bool):
|
|
303
|
+
return 1 if val else 0
|
|
304
|
+
elif isinstance(val, tuple):
|
|
305
|
+
return len(val)
|
|
306
|
+
else:
|
|
307
|
+
return 0
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def slice(val: Any, start: int = UNDEF, end: int = UNDEF, mutate: bool = False) -> Any:
|
|
311
|
+
"""Return a part of a list, string, or clamp a number"""
|
|
312
|
+
# Handle numbers - acts like clamp function
|
|
313
|
+
if isinstance(val, (int, float)):
|
|
314
|
+
if start is None:
|
|
315
|
+
start = float('-inf')
|
|
316
|
+
if end is None:
|
|
317
|
+
end = float('inf')
|
|
318
|
+
else:
|
|
319
|
+
end = end - 1 # TypeScript uses exclusive end, so subtract 1
|
|
320
|
+
return max(start, min(val, end))
|
|
321
|
+
|
|
322
|
+
if islist(val) or isinstance(val, str):
|
|
323
|
+
vlen = size(val)
|
|
324
|
+
if end is not None and start is None:
|
|
325
|
+
start = 0
|
|
326
|
+
if start is not None:
|
|
327
|
+
if start < 0:
|
|
328
|
+
end = vlen + start
|
|
329
|
+
if end < 0:
|
|
330
|
+
end = 0
|
|
331
|
+
start = 0
|
|
332
|
+
elif end is not None:
|
|
333
|
+
if end < 0:
|
|
334
|
+
end = vlen + end
|
|
335
|
+
if end < 0:
|
|
336
|
+
end = 0
|
|
337
|
+
elif vlen < end:
|
|
338
|
+
end = len(val)
|
|
339
|
+
else:
|
|
340
|
+
end = len(val)
|
|
341
|
+
|
|
342
|
+
if vlen < start:
|
|
343
|
+
start = vlen
|
|
344
|
+
|
|
345
|
+
if -1 < start and start <= end and end <= vlen:
|
|
346
|
+
if islist(val) and mutate:
|
|
347
|
+
j = start
|
|
348
|
+
for i in range(end - start):
|
|
349
|
+
val[i] = val[j]
|
|
350
|
+
j += 1
|
|
351
|
+
del val[end - start:]
|
|
352
|
+
return val
|
|
353
|
+
return val[start:end]
|
|
354
|
+
else:
|
|
355
|
+
if islist(val):
|
|
356
|
+
if mutate:
|
|
357
|
+
del val[:]
|
|
358
|
+
return val if mutate else []
|
|
359
|
+
return ""
|
|
360
|
+
|
|
361
|
+
# No slice performed; return original value unchanged
|
|
362
|
+
return val
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def pad(s: Any, padding: int = UNDEF, padchar: str = UNDEF) -> str:
|
|
366
|
+
"""Pad a string to a specified length"""
|
|
367
|
+
s = stringify(s)
|
|
368
|
+
padding = 44 if padding is UNDEF else padding
|
|
369
|
+
padchar = ' ' if padchar is UNDEF else (padchar + ' ')[0]
|
|
370
|
+
|
|
371
|
+
if padding > -1:
|
|
372
|
+
return s.ljust(padding, padchar)
|
|
373
|
+
else:
|
|
374
|
+
return s.rjust(-padding, padchar)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def strkey(key: Any = UNDEF) -> str:
|
|
378
|
+
if UNDEF == key:
|
|
379
|
+
return S_MT
|
|
380
|
+
|
|
381
|
+
if isinstance(key, str):
|
|
382
|
+
return key
|
|
383
|
+
|
|
384
|
+
if isinstance(key, bool):
|
|
385
|
+
return S_MT
|
|
386
|
+
|
|
387
|
+
if isinstance(key, int):
|
|
388
|
+
return str(key)
|
|
389
|
+
|
|
390
|
+
if isinstance(key, float):
|
|
391
|
+
return str(int(key))
|
|
392
|
+
|
|
393
|
+
return S_MT
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def isempty(val: Any = UNDEF) -> bool:
|
|
397
|
+
"Check for an 'empty' value - None, empty string, array, object."
|
|
398
|
+
if UNDEF == val:
|
|
399
|
+
return True
|
|
400
|
+
|
|
401
|
+
if val == S_MT:
|
|
402
|
+
return True
|
|
403
|
+
|
|
404
|
+
if islist(val) and len(val) == 0:
|
|
405
|
+
return True
|
|
406
|
+
|
|
407
|
+
if ismap(val) and len(val) == 0:
|
|
408
|
+
return True
|
|
409
|
+
|
|
410
|
+
return False
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def isfunc(val: Any = UNDEF) -> bool:
|
|
414
|
+
"Value is a function."
|
|
415
|
+
return callable(val)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _clz32(n):
|
|
419
|
+
if n <= 0:
|
|
420
|
+
return 32
|
|
421
|
+
return 31 - n.bit_length() + 1
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def typename(t):
|
|
425
|
+
return getelem(TYPENAME, _clz32(t), TYPENAME[0])
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
_TYPIFY_NO_ARG = object()
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def typify(value: Any = _TYPIFY_NO_ARG) -> int:
|
|
432
|
+
if value is _TYPIFY_NO_ARG:
|
|
433
|
+
return T_noval
|
|
434
|
+
if value is None:
|
|
435
|
+
return T_scalar | T_null
|
|
436
|
+
if isinstance(value, bool):
|
|
437
|
+
return T_scalar | T_boolean
|
|
438
|
+
if isinstance(value, int):
|
|
439
|
+
return T_scalar | T_number | T_integer
|
|
440
|
+
if isinstance(value, float):
|
|
441
|
+
import math
|
|
442
|
+
if math.isnan(value):
|
|
443
|
+
return T_noval
|
|
444
|
+
return T_scalar | T_number | T_decimal
|
|
445
|
+
if isinstance(value, str):
|
|
446
|
+
return T_scalar | T_string
|
|
447
|
+
if callable(value):
|
|
448
|
+
return T_scalar | T_function
|
|
449
|
+
if isinstance(value, list):
|
|
450
|
+
return T_node | T_list
|
|
451
|
+
if isinstance(value, dict):
|
|
452
|
+
return T_node | T_map
|
|
453
|
+
return T_node | T_instance
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def getelem(val: Any, key: Any, alt: Any = UNDEF) -> Any:
|
|
457
|
+
"""
|
|
458
|
+
Get a list element. The key should be an integer, or a string
|
|
459
|
+
that can parse to an integer only. Negative integers count from the end of the list.
|
|
460
|
+
"""
|
|
461
|
+
out = UNDEF
|
|
462
|
+
|
|
463
|
+
if UNDEF == val or UNDEF == key:
|
|
464
|
+
return alt
|
|
465
|
+
|
|
466
|
+
if islist(val):
|
|
467
|
+
try:
|
|
468
|
+
nkey = int(key)
|
|
469
|
+
if isinstance(nkey, int) and str(key).strip('-').isdigit():
|
|
470
|
+
if nkey < 0:
|
|
471
|
+
nkey = len(val) + nkey
|
|
472
|
+
out = val[nkey] if 0 <= nkey < len(val) else UNDEF
|
|
473
|
+
except (ValueError, IndexError):
|
|
474
|
+
pass
|
|
475
|
+
|
|
476
|
+
if UNDEF == out:
|
|
477
|
+
return alt() if 0 < (T_function & typify(alt)) else alt
|
|
478
|
+
|
|
479
|
+
return out
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def getprop(val: Any = UNDEF, key: Any = UNDEF, alt: Any = UNDEF) -> Any:
|
|
483
|
+
"""
|
|
484
|
+
Safely get a property of a node. Undefined arguments return undefined.
|
|
485
|
+
If the key is not found, return the alternative value.
|
|
486
|
+
"""
|
|
487
|
+
if UNDEF == val:
|
|
488
|
+
return alt
|
|
489
|
+
|
|
490
|
+
if UNDEF == key:
|
|
491
|
+
return alt
|
|
492
|
+
|
|
493
|
+
out = alt
|
|
494
|
+
|
|
495
|
+
if ismap(val):
|
|
496
|
+
out = val.get(str(key), alt)
|
|
497
|
+
|
|
498
|
+
elif islist(val):
|
|
499
|
+
try:
|
|
500
|
+
key = int(key)
|
|
501
|
+
except:
|
|
502
|
+
return alt
|
|
503
|
+
|
|
504
|
+
if 0 <= key < len(val):
|
|
505
|
+
return val[key]
|
|
506
|
+
else:
|
|
507
|
+
return alt
|
|
508
|
+
|
|
509
|
+
if UNDEF == out:
|
|
510
|
+
return alt
|
|
511
|
+
|
|
512
|
+
return out
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def keysof(val: Any = UNDEF) -> list[str]:
|
|
516
|
+
"Sorted keys of a map, or indexes of a list."
|
|
517
|
+
if not isnode(val):
|
|
518
|
+
return []
|
|
519
|
+
elif ismap(val):
|
|
520
|
+
return sorted(val.keys())
|
|
521
|
+
else:
|
|
522
|
+
return [str(x) for x in list(range(len(val)))]
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def haskey(val: Any = UNDEF, key: Any = UNDEF) -> bool:
|
|
526
|
+
"Value of property with name key in node val is defined."
|
|
527
|
+
return UNDEF != getprop(val, key)
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def items(val: Any = UNDEF, apply=None):
|
|
531
|
+
"List the keys of a map or list as an array of [key, value] tuples."
|
|
532
|
+
if not isnode(val):
|
|
533
|
+
return []
|
|
534
|
+
keys = keysof(val)
|
|
535
|
+
out = [[k, val[k] if ismap(val) else val[int(k)]] for k in keys]
|
|
536
|
+
if apply is not None:
|
|
537
|
+
out = [apply(item) for item in out]
|
|
538
|
+
return out
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def flatten(lst, depth=None):
|
|
542
|
+
if depth is None:
|
|
543
|
+
depth = 1
|
|
544
|
+
if not islist(lst):
|
|
545
|
+
return lst
|
|
546
|
+
out = []
|
|
547
|
+
for item in lst:
|
|
548
|
+
if islist(item) and depth > 0:
|
|
549
|
+
out.extend(flatten(item, depth - 1))
|
|
550
|
+
else:
|
|
551
|
+
out.append(item)
|
|
552
|
+
return out
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def filter(val, check):
|
|
556
|
+
all_items = items(val)
|
|
557
|
+
numall = size(all_items)
|
|
558
|
+
out = []
|
|
559
|
+
for i in range(numall):
|
|
560
|
+
if check(all_items[i]):
|
|
561
|
+
out.append(all_items[i][1])
|
|
562
|
+
return out
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def escre(s: Any):
|
|
566
|
+
"Escape regular expression."
|
|
567
|
+
if UNDEF == s:
|
|
568
|
+
s = ""
|
|
569
|
+
pattern = r'([.*+?^${}()|\[\]\\])'
|
|
570
|
+
return re.sub(pattern, r'\\\1', s)
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def escurl(s: Any):
|
|
574
|
+
"Escape URLs."
|
|
575
|
+
if UNDEF == s:
|
|
576
|
+
s = S_MT
|
|
577
|
+
return urllib.parse.quote(s, safe="")
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def replace(s, from_pat, to_str):
|
|
581
|
+
"Replace a search string (all), or a regexp, in a source string."
|
|
582
|
+
rs = s
|
|
583
|
+
ts = typify(s)
|
|
584
|
+
if 0 == (T_string & ts):
|
|
585
|
+
rs = stringify(s)
|
|
586
|
+
elif 0 < ((T_noval | T_null) & ts):
|
|
587
|
+
rs = S_MT
|
|
588
|
+
else:
|
|
589
|
+
rs = stringify(s)
|
|
590
|
+
if isinstance(from_pat, str):
|
|
591
|
+
return rs.replace(from_pat, str(to_str))
|
|
592
|
+
else:
|
|
593
|
+
return re.sub(from_pat, str(to_str), rs)
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def join(arr, sep=UNDEF, url=UNDEF):
|
|
597
|
+
if not islist(arr):
|
|
598
|
+
return S_MT
|
|
599
|
+
sepdef = S_CM if sep is UNDEF or sep is None else sep
|
|
600
|
+
sepre = escre(sepdef) if 1 == size(sepdef) else UNDEF
|
|
601
|
+
|
|
602
|
+
sarr = size(arr)
|
|
603
|
+
filtered = [(i, s) for i, s in enumerate(arr)
|
|
604
|
+
if isinstance(s, str) and S_MT != s]
|
|
605
|
+
|
|
606
|
+
result = []
|
|
607
|
+
for idx, s in filtered:
|
|
608
|
+
if sepre is not UNDEF and S_MT != sepre:
|
|
609
|
+
if url and 0 == idx:
|
|
610
|
+
s = re.sub(sepre + '+$', S_MT, s)
|
|
611
|
+
result.append(s)
|
|
612
|
+
continue
|
|
613
|
+
if 0 < idx:
|
|
614
|
+
s = re.sub('^' + sepre + '+', S_MT, s)
|
|
615
|
+
if idx < sarr - 1 or not url:
|
|
616
|
+
s = re.sub(sepre + '+$', S_MT, s)
|
|
617
|
+
s = re.sub('([^' + sepre + '])' + sepre + '+([^' + sepre + '])',
|
|
618
|
+
r'\1' + sepdef + r'\2', s)
|
|
619
|
+
|
|
620
|
+
if S_MT != s:
|
|
621
|
+
result.append(s)
|
|
622
|
+
|
|
623
|
+
return sepdef.join(result)
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def joinurl(sarr):
|
|
627
|
+
"Concatenate url part strings, merging forward slashes as needed."
|
|
628
|
+
return join(sarr, '/', True)
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def delprop(parent: Any, key: Any):
|
|
632
|
+
"""
|
|
633
|
+
Delete a property from a dictionary or list.
|
|
634
|
+
For arrays, the element at the index is removed and remaining elements are shifted down.
|
|
635
|
+
"""
|
|
636
|
+
if not iskey(key):
|
|
637
|
+
return parent
|
|
638
|
+
|
|
639
|
+
if ismap(parent):
|
|
640
|
+
key = strkey(key)
|
|
641
|
+
if key in parent:
|
|
642
|
+
del parent[key]
|
|
643
|
+
|
|
644
|
+
elif islist(parent):
|
|
645
|
+
# Convert key to int
|
|
646
|
+
try:
|
|
647
|
+
key_i = int(key)
|
|
648
|
+
except ValueError:
|
|
649
|
+
return parent
|
|
650
|
+
|
|
651
|
+
key_i = int(key_i) # Floor the value
|
|
652
|
+
|
|
653
|
+
# Delete list element at position key_i, shifting later elements down
|
|
654
|
+
if 0 <= key_i < len(parent):
|
|
655
|
+
for pI in range(key_i, len(parent) - 1):
|
|
656
|
+
parent[pI] = parent[pI + 1]
|
|
657
|
+
parent.pop()
|
|
658
|
+
|
|
659
|
+
return parent
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def jsonify(val: Any = UNDEF, flags: Dict[str, Any] = None) -> str:
|
|
663
|
+
"""
|
|
664
|
+
Convert a value to a formatted JSON string.
|
|
665
|
+
In general, the behavior of JavaScript's JSON.stringify(val, null, 2) is followed.
|
|
666
|
+
"""
|
|
667
|
+
flags = flags or {}
|
|
668
|
+
|
|
669
|
+
if val is UNDEF:
|
|
670
|
+
return S_null
|
|
671
|
+
|
|
672
|
+
indent = getprop(flags, 'indent', 2)
|
|
673
|
+
|
|
674
|
+
try:
|
|
675
|
+
json_str = json.dumps(val, indent=indent, separators=(',', ': ') if indent else (',', ':'))
|
|
676
|
+
except Exception:
|
|
677
|
+
return S_null
|
|
678
|
+
|
|
679
|
+
if json_str is None:
|
|
680
|
+
return S_null
|
|
681
|
+
|
|
682
|
+
offset = getprop(flags, 'offset', 0)
|
|
683
|
+
if 0 < offset:
|
|
684
|
+
lines = json_str.split('\n')
|
|
685
|
+
padded = [pad(n[1], 0 - offset - size(n[1])) for n in items(lines[1:])]
|
|
686
|
+
json_str = '{\n' + '\n'.join(padded)
|
|
687
|
+
|
|
688
|
+
return json_str
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def jo(*kv: Any) -> Dict[str, Any]:
|
|
692
|
+
"""
|
|
693
|
+
Define a JSON Object using function arguments.
|
|
694
|
+
Arguments are treated as key-value pairs.
|
|
695
|
+
"""
|
|
696
|
+
kvsize = len(kv)
|
|
697
|
+
o = {}
|
|
698
|
+
|
|
699
|
+
for i in range(0, kvsize, 2):
|
|
700
|
+
k = kv[i] if i < kvsize else f'$KEY{i}'
|
|
701
|
+
# Handle None specially to become "null" for keys
|
|
702
|
+
if k is None:
|
|
703
|
+
k = 'null'
|
|
704
|
+
elif isinstance(k, str):
|
|
705
|
+
k = k
|
|
706
|
+
else:
|
|
707
|
+
k = stringify(k)
|
|
708
|
+
o[k] = kv[i + 1] if i + 1 < kvsize else None
|
|
709
|
+
|
|
710
|
+
return o
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
def ja(*v: Any) -> List[Any]:
|
|
714
|
+
"""
|
|
715
|
+
Define a JSON Array using function arguments.
|
|
716
|
+
"""
|
|
717
|
+
vsize = len(v)
|
|
718
|
+
a = [None] * vsize
|
|
719
|
+
|
|
720
|
+
for i in range(vsize):
|
|
721
|
+
a[i] = v[i] if i < vsize else None
|
|
722
|
+
|
|
723
|
+
return a
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
# Aliases to match TS canonical names
|
|
727
|
+
jm = jo
|
|
728
|
+
jt = ja
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def select_AND(state, _val, _ref, store):
|
|
732
|
+
if S_MKEYPRE == state.mode:
|
|
733
|
+
terms = getprop(state.parent, state.key)
|
|
734
|
+
ppath = slice(state.path, -1)
|
|
735
|
+
point = getpath(store, ppath)
|
|
736
|
+
|
|
737
|
+
vstore = merge([{}, store], 1)
|
|
738
|
+
vstore['$TOP'] = point
|
|
739
|
+
|
|
740
|
+
for term in terms:
|
|
741
|
+
terrs = []
|
|
742
|
+
validate(point, term, {
|
|
743
|
+
'extra': vstore,
|
|
744
|
+
'errs': terrs,
|
|
745
|
+
'meta': state.meta,
|
|
746
|
+
})
|
|
747
|
+
if 0 != len(terrs):
|
|
748
|
+
state.errs.append(
|
|
749
|
+
'AND:' + pathify(ppath) + '\u2A2F' + stringify(point) +
|
|
750
|
+
' fail:' + stringify(terms))
|
|
751
|
+
|
|
752
|
+
gkey = getelem(state.path, -2)
|
|
753
|
+
gp = getelem(state.nodes, -2)
|
|
754
|
+
setprop(gp, gkey, point)
|
|
755
|
+
|
|
756
|
+
return UNDEF
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
def select_OR(state, _val, _ref, store):
|
|
760
|
+
if S_MKEYPRE == state.mode:
|
|
761
|
+
terms = getprop(state.parent, state.key)
|
|
762
|
+
ppath = slice(state.path, -1)
|
|
763
|
+
point = getpath(store, ppath)
|
|
764
|
+
|
|
765
|
+
vstore = merge([{}, store], 1)
|
|
766
|
+
vstore['$TOP'] = point
|
|
767
|
+
|
|
768
|
+
for term in terms:
|
|
769
|
+
terrs = []
|
|
770
|
+
validate(point, term, {
|
|
771
|
+
'extra': vstore,
|
|
772
|
+
'errs': terrs,
|
|
773
|
+
'meta': state.meta,
|
|
774
|
+
})
|
|
775
|
+
if 0 == len(terrs):
|
|
776
|
+
gkey = getelem(state.path, -2)
|
|
777
|
+
gp = getelem(state.nodes, -2)
|
|
778
|
+
setprop(gp, gkey, point)
|
|
779
|
+
return UNDEF
|
|
780
|
+
|
|
781
|
+
state.errs.append(
|
|
782
|
+
'OR:' + pathify(ppath) + '\u2A2F' + stringify(point) +
|
|
783
|
+
' fail:' + stringify(terms))
|
|
784
|
+
|
|
785
|
+
return UNDEF
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
def select_NOT(state, _val, _ref, store):
|
|
789
|
+
if S_MKEYPRE == state.mode:
|
|
790
|
+
term = getprop(state.parent, state.key)
|
|
791
|
+
ppath = slice(state.path, -1)
|
|
792
|
+
point = getpath(store, ppath)
|
|
793
|
+
|
|
794
|
+
vstore = merge([{}, store], 1)
|
|
795
|
+
vstore['$TOP'] = point
|
|
796
|
+
|
|
797
|
+
terrs = []
|
|
798
|
+
validate(point, term, {
|
|
799
|
+
'extra': vstore,
|
|
800
|
+
'errs': terrs,
|
|
801
|
+
'meta': state.meta,
|
|
802
|
+
})
|
|
803
|
+
|
|
804
|
+
if 0 == len(terrs):
|
|
805
|
+
state.errs.append(
|
|
806
|
+
'NOT:' + pathify(ppath) + '\u2A2F' + stringify(point) +
|
|
807
|
+
' fail:' + stringify(term))
|
|
808
|
+
|
|
809
|
+
gkey = getelem(state.path, -2)
|
|
810
|
+
gp = getelem(state.nodes, -2)
|
|
811
|
+
setprop(gp, gkey, point)
|
|
812
|
+
|
|
813
|
+
return UNDEF
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def select_CMP(state, _val, ref, store):
|
|
817
|
+
if S_MKEYPRE == state.mode:
|
|
818
|
+
term = getprop(state.parent, state.key)
|
|
819
|
+
gkey = getelem(state.path, -2)
|
|
820
|
+
ppath = slice(state.path, -1)
|
|
821
|
+
point = getpath(store, ppath)
|
|
822
|
+
|
|
823
|
+
pass_test = False
|
|
824
|
+
|
|
825
|
+
if '$GT' == ref and point > term:
|
|
826
|
+
pass_test = True
|
|
827
|
+
elif '$LT' == ref and point < term:
|
|
828
|
+
pass_test = True
|
|
829
|
+
elif '$GTE' == ref and point >= term:
|
|
830
|
+
pass_test = True
|
|
831
|
+
elif '$LTE' == ref and point <= term:
|
|
832
|
+
pass_test = True
|
|
833
|
+
elif '$LIKE' == ref:
|
|
834
|
+
import re as re_mod
|
|
835
|
+
if re_mod.search(term, stringify(point)):
|
|
836
|
+
pass_test = True
|
|
837
|
+
|
|
838
|
+
if pass_test:
|
|
839
|
+
gp = getelem(state.nodes, -2)
|
|
840
|
+
setprop(gp, gkey, point)
|
|
841
|
+
else:
|
|
842
|
+
state.errs.append(
|
|
843
|
+
'CMP: ' + pathify(ppath) + '\u2A2F' + stringify(point) +
|
|
844
|
+
' fail:' + ref + ' ' + stringify(term))
|
|
845
|
+
|
|
846
|
+
return UNDEF
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
def select(children: Any, query: Any) -> List[Any]:
|
|
850
|
+
"""
|
|
851
|
+
Select children from a top-level object that match a MongoDB-style query.
|
|
852
|
+
Supports $and, $or, and equality comparisons.
|
|
853
|
+
For arrays, children are elements; for objects, children are values.
|
|
854
|
+
"""
|
|
855
|
+
if not isnode(children):
|
|
856
|
+
return []
|
|
857
|
+
|
|
858
|
+
if ismap(children):
|
|
859
|
+
children = [setprop(v, S_DKEY, k) or v for k, v in items(children)]
|
|
860
|
+
else:
|
|
861
|
+
children = [setprop(n, S_DKEY, i) or n if ismap(n) else n for i, n in enumerate(children)]
|
|
862
|
+
|
|
863
|
+
results = []
|
|
864
|
+
injdef = {
|
|
865
|
+
'errs': [],
|
|
866
|
+
'meta': {S_BEXACT: True},
|
|
867
|
+
'extra': {
|
|
868
|
+
'$AND': select_AND,
|
|
869
|
+
'$OR': select_OR,
|
|
870
|
+
'$NOT': select_NOT,
|
|
871
|
+
'$GT': select_CMP,
|
|
872
|
+
'$LT': select_CMP,
|
|
873
|
+
'$GTE': select_CMP,
|
|
874
|
+
'$LTE': select_CMP,
|
|
875
|
+
'$LIKE': select_CMP,
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
q = clone(query)
|
|
880
|
+
|
|
881
|
+
# Add $OPEN to all maps in the query
|
|
882
|
+
def add_open(_k, v, _parent, _path):
|
|
883
|
+
if ismap(v):
|
|
884
|
+
setprop(v, '`$OPEN`', getprop(v, '`$OPEN`', True))
|
|
885
|
+
return v
|
|
886
|
+
|
|
887
|
+
walk(q, add_open)
|
|
888
|
+
|
|
889
|
+
for child in children:
|
|
890
|
+
injdef['errs'] = []
|
|
891
|
+
validate(child, clone(q), injdef)
|
|
892
|
+
|
|
893
|
+
if size(injdef['errs']) == 0:
|
|
894
|
+
results.append(child)
|
|
895
|
+
|
|
896
|
+
return results
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
def stringify(val: Any, maxlen: int = UNDEF, pretty: Any = None):
|
|
900
|
+
"Safely stringify a value for printing (NOT JSON!)."
|
|
901
|
+
|
|
902
|
+
pretty = bool(pretty)
|
|
903
|
+
valstr = S_MT
|
|
904
|
+
|
|
905
|
+
if UNDEF == val:
|
|
906
|
+
return '<>' if pretty else valstr
|
|
907
|
+
|
|
908
|
+
if isinstance(val, str):
|
|
909
|
+
valstr = val
|
|
910
|
+
else:
|
|
911
|
+
try:
|
|
912
|
+
valstr = json.dumps(val, sort_keys=True, separators=(',', ':'))
|
|
913
|
+
valstr = valstr.replace('"', '')
|
|
914
|
+
except Exception:
|
|
915
|
+
valstr = '__STRINGIFY_FAILED__'
|
|
916
|
+
|
|
917
|
+
if maxlen is not UNDEF and maxlen is not None and -1 < maxlen:
|
|
918
|
+
js = valstr[:maxlen]
|
|
919
|
+
valstr = (js[:maxlen - 3] + '...') if maxlen < len(valstr) else valstr
|
|
920
|
+
|
|
921
|
+
if pretty:
|
|
922
|
+
colors = [81, 118, 213, 39, 208, 201, 45, 190, 129, 51, 160, 121, 226, 33, 207, 69]
|
|
923
|
+
c = ['\x1b[38;5;' + str(n) + 'm' for n in colors]
|
|
924
|
+
r = '\x1b[0m'
|
|
925
|
+
d = 0
|
|
926
|
+
o = c[0]
|
|
927
|
+
t = o
|
|
928
|
+
for ch in valstr:
|
|
929
|
+
if ch in ('{', '['):
|
|
930
|
+
d += 1
|
|
931
|
+
o = c[d % len(c)]
|
|
932
|
+
t += o + ch
|
|
933
|
+
elif ch in ('}', ']'):
|
|
934
|
+
t += o + ch
|
|
935
|
+
d -= 1
|
|
936
|
+
o = c[d % len(c)]
|
|
937
|
+
else:
|
|
938
|
+
t += o + ch
|
|
939
|
+
valstr = t + r
|
|
940
|
+
|
|
941
|
+
return valstr
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
def pathify(val: Any = UNDEF, startin: int = UNDEF, endin: int = UNDEF) -> str:
|
|
945
|
+
pathstr = UNDEF
|
|
946
|
+
|
|
947
|
+
# Convert input to a path array
|
|
948
|
+
path = val if islist(val) else \
|
|
949
|
+
[val] if iskey(val) else \
|
|
950
|
+
UNDEF
|
|
951
|
+
|
|
952
|
+
# [val] if isinstance(val, str) else \
|
|
953
|
+
# [val] if isinstance(val, (int, float)) else \
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
# Determine starting index and ending index
|
|
957
|
+
start = 0 if startin is UNDEF else startin if -1 < startin else 0
|
|
958
|
+
end = 0 if endin is UNDEF else endin if -1 < endin else 0
|
|
959
|
+
|
|
960
|
+
if UNDEF != path and 0 <= start:
|
|
961
|
+
path = path[start:len(path)-end]
|
|
962
|
+
|
|
963
|
+
if 0 == len(path):
|
|
964
|
+
pathstr = "<root>"
|
|
965
|
+
else:
|
|
966
|
+
# Filter path parts to include only valid keys
|
|
967
|
+
filtered_path = [p for p in path if iskey(p)]
|
|
968
|
+
|
|
969
|
+
# Map path parts: convert numbers to strings and remove any dots
|
|
970
|
+
mapped_path = []
|
|
971
|
+
for p in filtered_path:
|
|
972
|
+
if isinstance(p, (int, float)):
|
|
973
|
+
mapped_path.append(S_MT + str(int(p)))
|
|
974
|
+
else:
|
|
975
|
+
mapped_path.append(str(p).replace('.', S_MT))
|
|
976
|
+
|
|
977
|
+
pathstr = S_DT.join(mapped_path)
|
|
978
|
+
|
|
979
|
+
# Handle the case where we couldn't create a path
|
|
980
|
+
if UNDEF == pathstr:
|
|
981
|
+
pathstr = f"<unknown-path{S_MT if UNDEF == val else S_CN+stringify(val, 47)}>"
|
|
982
|
+
|
|
983
|
+
return pathstr
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
def clone(val: Any = UNDEF):
|
|
987
|
+
"""
|
|
988
|
+
Clone a JSON-like data structure.
|
|
989
|
+
NOTE: function value references are copied, *not* cloned.
|
|
990
|
+
"""
|
|
991
|
+
if UNDEF == val:
|
|
992
|
+
return UNDEF
|
|
993
|
+
|
|
994
|
+
refs = []
|
|
995
|
+
|
|
996
|
+
def replacer(item):
|
|
997
|
+
if callable(item):
|
|
998
|
+
refs.append(item)
|
|
999
|
+
return f'`$FUNCTION:{len(refs) - 1}`'
|
|
1000
|
+
elif isinstance(item, dict):
|
|
1001
|
+
return {k: replacer(v) for k, v in item.items()}
|
|
1002
|
+
elif isinstance(item, (list, tuple)):
|
|
1003
|
+
return [replacer(elem) for elem in item]
|
|
1004
|
+
elif hasattr(item, 'to_json'):
|
|
1005
|
+
return item.to_json()
|
|
1006
|
+
elif hasattr(item, '__dict__'):
|
|
1007
|
+
return item.__dict__
|
|
1008
|
+
else:
|
|
1009
|
+
return item
|
|
1010
|
+
|
|
1011
|
+
transformed = replacer(val)
|
|
1012
|
+
|
|
1013
|
+
json_str = json.dumps(transformed, separators=(',', ':'))
|
|
1014
|
+
|
|
1015
|
+
def reviver(item):
|
|
1016
|
+
if isinstance(item, str):
|
|
1017
|
+
match = re.match(r'^`\$FUNCTION:(\d+)`$', item)
|
|
1018
|
+
if match:
|
|
1019
|
+
index = int(match.group(1))
|
|
1020
|
+
return refs[index]
|
|
1021
|
+
else:
|
|
1022
|
+
return item
|
|
1023
|
+
elif isinstance(item, list):
|
|
1024
|
+
return [reviver(elem) for elem in item]
|
|
1025
|
+
elif isinstance(item, dict):
|
|
1026
|
+
return {k: reviver(v) for k, v in item.items()}
|
|
1027
|
+
else:
|
|
1028
|
+
return item
|
|
1029
|
+
|
|
1030
|
+
parsed = json.loads(json_str)
|
|
1031
|
+
|
|
1032
|
+
return reviver(parsed)
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
def setprop(parent: Any, key: Any, val: Any):
|
|
1036
|
+
"""
|
|
1037
|
+
Safely set a property on a dictionary or list.
|
|
1038
|
+
- None value deletes the key/element (mirrors JS undefined behavior).
|
|
1039
|
+
- For lists, negative key -> prepend.
|
|
1040
|
+
- For lists, key > len(list) -> append.
|
|
1041
|
+
"""
|
|
1042
|
+
if not iskey(key):
|
|
1043
|
+
return parent
|
|
1044
|
+
|
|
1045
|
+
if ismap(parent):
|
|
1046
|
+
key = str(key)
|
|
1047
|
+
if val is None:
|
|
1048
|
+
parent.pop(key, None)
|
|
1049
|
+
else:
|
|
1050
|
+
parent[key] = val
|
|
1051
|
+
|
|
1052
|
+
elif islist(parent):
|
|
1053
|
+
try:
|
|
1054
|
+
key_i = int(key)
|
|
1055
|
+
except ValueError:
|
|
1056
|
+
return parent
|
|
1057
|
+
|
|
1058
|
+
if val is None:
|
|
1059
|
+
if 0 <= key_i < len(parent):
|
|
1060
|
+
for pI in range(key_i, len(parent) - 1):
|
|
1061
|
+
parent[pI] = parent[pI + 1]
|
|
1062
|
+
parent.pop()
|
|
1063
|
+
else:
|
|
1064
|
+
if key_i >= 0:
|
|
1065
|
+
key_i = min(key_i, len(parent))
|
|
1066
|
+
if key_i >= len(parent):
|
|
1067
|
+
parent.append(val)
|
|
1068
|
+
else:
|
|
1069
|
+
parent[key_i] = val
|
|
1070
|
+
else:
|
|
1071
|
+
parent.insert(0, val)
|
|
1072
|
+
|
|
1073
|
+
return parent
|
|
1074
|
+
|
|
1075
|
+
|
|
1076
|
+
MAXDEPTH = 32
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
def walk(
|
|
1080
|
+
val: Any,
|
|
1081
|
+
apply: Any = None,
|
|
1082
|
+
key: Any = UNDEF,
|
|
1083
|
+
parent: Any = UNDEF,
|
|
1084
|
+
path: Any = UNDEF,
|
|
1085
|
+
*,
|
|
1086
|
+
before: Any = None,
|
|
1087
|
+
after: Any = None,
|
|
1088
|
+
maxdepth: Any = None,
|
|
1089
|
+
):
|
|
1090
|
+
"""
|
|
1091
|
+
Walk a data structure depth-first.
|
|
1092
|
+
Supports before (pre-descent) and after (post-descent) callbacks.
|
|
1093
|
+
For backward compat, `apply` is treated as the after callback.
|
|
1094
|
+
"""
|
|
1095
|
+
if path is UNDEF:
|
|
1096
|
+
path = []
|
|
1097
|
+
|
|
1098
|
+
_before = before
|
|
1099
|
+
_after = after if after is not None else apply
|
|
1100
|
+
|
|
1101
|
+
out = val if _before is None else _before(key, val, parent, path)
|
|
1102
|
+
|
|
1103
|
+
md = maxdepth if maxdepth is not None and 0 <= maxdepth else MAXDEPTH
|
|
1104
|
+
if 0 == md or (path is not None and 0 < md and md <= len(path)):
|
|
1105
|
+
return out
|
|
1106
|
+
|
|
1107
|
+
if isnode(out):
|
|
1108
|
+
for (ckey, child) in items(out):
|
|
1109
|
+
result = walk(
|
|
1110
|
+
child, key=ckey, parent=out,
|
|
1111
|
+
path=flatten([path or [], str(ckey)]),
|
|
1112
|
+
before=_before, after=_after, maxdepth=md,
|
|
1113
|
+
)
|
|
1114
|
+
if ismap(out):
|
|
1115
|
+
out[str(ckey)] = result
|
|
1116
|
+
elif islist(out):
|
|
1117
|
+
out[int(ckey)] = result
|
|
1118
|
+
|
|
1119
|
+
if _after is not None:
|
|
1120
|
+
out = _after(key, out, parent, path)
|
|
1121
|
+
|
|
1122
|
+
return out
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
def merge(objs: List[Any] = None, maxdepth: Any = None) -> Any:
|
|
1126
|
+
"""
|
|
1127
|
+
Merge a list of values into each other. Later values have
|
|
1128
|
+
precedence. Nodes override scalars. Node kinds (list or map)
|
|
1129
|
+
override each other, and do *not* merge. The first element is
|
|
1130
|
+
modified.
|
|
1131
|
+
"""
|
|
1132
|
+
|
|
1133
|
+
md = MAXDEPTH if maxdepth is None else max(maxdepth, 0)
|
|
1134
|
+
|
|
1135
|
+
if not islist(objs):
|
|
1136
|
+
return objs
|
|
1137
|
+
|
|
1138
|
+
lenlist = len(objs)
|
|
1139
|
+
|
|
1140
|
+
if 0 == lenlist:
|
|
1141
|
+
return UNDEF
|
|
1142
|
+
if 1 == lenlist:
|
|
1143
|
+
return objs[0]
|
|
1144
|
+
|
|
1145
|
+
out = getprop(objs, 0, {})
|
|
1146
|
+
|
|
1147
|
+
for oI in range(1, lenlist):
|
|
1148
|
+
obj = objs[oI]
|
|
1149
|
+
|
|
1150
|
+
if not isnode(obj):
|
|
1151
|
+
out = obj
|
|
1152
|
+
else:
|
|
1153
|
+
cur = [out]
|
|
1154
|
+
dst = [out]
|
|
1155
|
+
|
|
1156
|
+
def before(key, val, _parent, path):
|
|
1157
|
+
pI = size(path)
|
|
1158
|
+
|
|
1159
|
+
if md <= pI:
|
|
1160
|
+
cur_len = len(cur)
|
|
1161
|
+
if pI >= cur_len:
|
|
1162
|
+
cur.extend([UNDEF] * (pI + 1 - cur_len))
|
|
1163
|
+
cur[pI] = val
|
|
1164
|
+
if pI > 0 and pI - 1 < len(cur):
|
|
1165
|
+
setprop(cur[pI - 1], key, val)
|
|
1166
|
+
return UNDEF
|
|
1167
|
+
|
|
1168
|
+
elif not isnode(val):
|
|
1169
|
+
cur_len = len(cur)
|
|
1170
|
+
if pI >= cur_len:
|
|
1171
|
+
cur.extend([UNDEF] * (pI + 1 - cur_len))
|
|
1172
|
+
cur[pI] = val
|
|
1173
|
+
|
|
1174
|
+
else:
|
|
1175
|
+
dst_len = len(dst)
|
|
1176
|
+
if pI >= dst_len:
|
|
1177
|
+
dst.extend([UNDEF] * (pI + 1 - dst_len))
|
|
1178
|
+
cur_len = len(cur)
|
|
1179
|
+
if pI >= cur_len:
|
|
1180
|
+
cur.extend([UNDEF] * (pI + 1 - cur_len))
|
|
1181
|
+
|
|
1182
|
+
dst[pI] = getprop(dst[pI - 1], key) if 0 < pI else dst[pI]
|
|
1183
|
+
tval = dst[pI]
|
|
1184
|
+
|
|
1185
|
+
if UNDEF == tval:
|
|
1186
|
+
cur[pI] = [] if islist(val) else {}
|
|
1187
|
+
elif (islist(val) and islist(tval)) or (ismap(val) and ismap(tval)):
|
|
1188
|
+
cur[pI] = tval
|
|
1189
|
+
else:
|
|
1190
|
+
cur[pI] = val
|
|
1191
|
+
val = UNDEF
|
|
1192
|
+
|
|
1193
|
+
return val
|
|
1194
|
+
|
|
1195
|
+
def after(key, _val, _parent, path):
|
|
1196
|
+
cI = size(path)
|
|
1197
|
+
if cI < 1:
|
|
1198
|
+
return cur[0] if len(cur) > 0 else _val
|
|
1199
|
+
|
|
1200
|
+
target = cur[cI - 1] if cI - 1 < len(cur) else UNDEF
|
|
1201
|
+
value = cur[cI] if cI < len(cur) else UNDEF
|
|
1202
|
+
|
|
1203
|
+
setprop(target, key, value)
|
|
1204
|
+
return value
|
|
1205
|
+
|
|
1206
|
+
out = walk(obj, before=before, after=after)
|
|
1207
|
+
|
|
1208
|
+
if 0 == md:
|
|
1209
|
+
out = getprop(objs, lenlist - 1, UNDEF)
|
|
1210
|
+
out = [] if islist(out) else {} if ismap(out) else out
|
|
1211
|
+
|
|
1212
|
+
return out
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
def getpath(store, path, injdef=UNDEF):
|
|
1216
|
+
"""
|
|
1217
|
+
Get a value from the store using a path.
|
|
1218
|
+
Supports relative paths (..), escaping ($$), and special syntax.
|
|
1219
|
+
"""
|
|
1220
|
+
# Operate on a string array.
|
|
1221
|
+
if islist(path):
|
|
1222
|
+
parts = path[:]
|
|
1223
|
+
elif isinstance(path, str):
|
|
1224
|
+
parts = path.split(S_DT)
|
|
1225
|
+
elif isinstance(path, (int, float)) and not isinstance(path, bool):
|
|
1226
|
+
parts = [strkey(path)]
|
|
1227
|
+
else:
|
|
1228
|
+
return UNDEF
|
|
1229
|
+
|
|
1230
|
+
val = store
|
|
1231
|
+
# Support both dict-style injdef and Injection instance
|
|
1232
|
+
if isinstance(injdef, Injection):
|
|
1233
|
+
base = injdef.base
|
|
1234
|
+
dparent = injdef.dparent
|
|
1235
|
+
inj_meta = injdef.meta
|
|
1236
|
+
inj_key = injdef.key
|
|
1237
|
+
dpath = injdef.dpath
|
|
1238
|
+
else:
|
|
1239
|
+
base = getprop(injdef, S_base) if injdef else UNDEF
|
|
1240
|
+
dparent = getprop(injdef, 'dparent') if injdef else UNDEF
|
|
1241
|
+
inj_meta = getprop(injdef, 'meta') if injdef else UNDEF
|
|
1242
|
+
inj_key = getprop(injdef, 'key') if injdef else UNDEF
|
|
1243
|
+
dpath = getprop(injdef, 'dpath') if injdef else UNDEF
|
|
1244
|
+
|
|
1245
|
+
src = getprop(store, base, store) if base else store
|
|
1246
|
+
numparts = size(parts)
|
|
1247
|
+
|
|
1248
|
+
# An empty path (incl empty string) just finds the store.
|
|
1249
|
+
if path is UNDEF or store is UNDEF or (1 == numparts and parts[0] == S_MT) or numparts == 0:
|
|
1250
|
+
val = src
|
|
1251
|
+
return val
|
|
1252
|
+
elif numparts > 0:
|
|
1253
|
+
|
|
1254
|
+
# Check for $ACTIONs
|
|
1255
|
+
if 1 == numparts:
|
|
1256
|
+
val = getprop(store, parts[0])
|
|
1257
|
+
|
|
1258
|
+
if not isfunc(val):
|
|
1259
|
+
val = src
|
|
1260
|
+
|
|
1261
|
+
# Check for meta path syntax
|
|
1262
|
+
m = R_META_PATH.match(parts[0]) if parts[0] else None
|
|
1263
|
+
if m and inj_meta:
|
|
1264
|
+
val = getprop(inj_meta, m.group(1))
|
|
1265
|
+
parts[0] = m.group(3)
|
|
1266
|
+
|
|
1267
|
+
|
|
1268
|
+
for pI in range(numparts):
|
|
1269
|
+
if val is UNDEF:
|
|
1270
|
+
break
|
|
1271
|
+
|
|
1272
|
+
part = parts[pI]
|
|
1273
|
+
|
|
1274
|
+
# Handle special path components
|
|
1275
|
+
if injdef and part == S_DKEY:
|
|
1276
|
+
part = inj_key if inj_key is not UNDEF else part
|
|
1277
|
+
elif isinstance(part, str) and part.startswith('$GET:'):
|
|
1278
|
+
# $GET:path$ -> get store value, use as path part (string)
|
|
1279
|
+
part = stringify(getpath(src, part[5:-1]))
|
|
1280
|
+
elif isinstance(part, str) and part.startswith('$REF:'):
|
|
1281
|
+
# $REF:refpath$ -> get spec value, use as path part (string)
|
|
1282
|
+
part = stringify(getpath(getprop(store, S_DSPEC), part[5:-1]))
|
|
1283
|
+
elif injdef and isinstance(part, str) and part.startswith('$META:'):
|
|
1284
|
+
# $META:metapath$ -> get meta value, use as path part (string)
|
|
1285
|
+
part = stringify(getpath(inj_meta, part[6:-1]))
|
|
1286
|
+
|
|
1287
|
+
# $$ escapes $ (path parts can be int e.g. list indices)
|
|
1288
|
+
if isinstance(part, str):
|
|
1289
|
+
part = R_DOUBLE_DOLLAR.sub('$', part)
|
|
1290
|
+
else:
|
|
1291
|
+
part = strkey(part)
|
|
1292
|
+
|
|
1293
|
+
if part == S_MT:
|
|
1294
|
+
ascends = 0
|
|
1295
|
+
while pI + 1 < len(parts) and parts[pI + 1] == S_MT:
|
|
1296
|
+
ascends += 1
|
|
1297
|
+
pI += 1
|
|
1298
|
+
|
|
1299
|
+
if injdef and 0 < ascends:
|
|
1300
|
+
if pI == len(parts) - 1:
|
|
1301
|
+
ascends -= 1
|
|
1302
|
+
|
|
1303
|
+
if 0 == ascends:
|
|
1304
|
+
val = dparent
|
|
1305
|
+
else:
|
|
1306
|
+
fullpath = flatten(
|
|
1307
|
+
[slice(dpath, 0 - ascends), parts[pI + 1:]])
|
|
1308
|
+
if ascends <= size(dpath):
|
|
1309
|
+
val = getpath(store, fullpath)
|
|
1310
|
+
else:
|
|
1311
|
+
val = UNDEF
|
|
1312
|
+
break
|
|
1313
|
+
else:
|
|
1314
|
+
val = dparent
|
|
1315
|
+
else:
|
|
1316
|
+
val = getprop(val, part)
|
|
1317
|
+
|
|
1318
|
+
# Injdef may provide a custom handler to modify found value.
|
|
1319
|
+
handler = injdef.handler if isinstance(injdef, Injection) else (getprop(injdef, 'handler') if injdef else UNDEF)
|
|
1320
|
+
if handler and isfunc(handler):
|
|
1321
|
+
ref = pathify(path)
|
|
1322
|
+
val = handler(injdef, val, ref, store)
|
|
1323
|
+
|
|
1324
|
+
return val
|
|
1325
|
+
|
|
1326
|
+
|
|
1327
|
+
def setpath(store, path, val, injdef=UNDEF):
|
|
1328
|
+
pathType = typify(path)
|
|
1329
|
+
|
|
1330
|
+
if 0 < (T_list & pathType):
|
|
1331
|
+
parts = path
|
|
1332
|
+
elif 0 < (T_string & pathType):
|
|
1333
|
+
parts = path.split(S_DT)
|
|
1334
|
+
elif 0 < (T_number & pathType):
|
|
1335
|
+
parts = [path]
|
|
1336
|
+
else:
|
|
1337
|
+
return UNDEF
|
|
1338
|
+
|
|
1339
|
+
base = getprop(injdef, S_base) if injdef else UNDEF
|
|
1340
|
+
numparts = size(parts)
|
|
1341
|
+
parent = getprop(store, base, store) if base else store
|
|
1342
|
+
|
|
1343
|
+
for pI in range(numparts - 1):
|
|
1344
|
+
partKey = getelem(parts, pI)
|
|
1345
|
+
nextParent = getprop(parent, partKey)
|
|
1346
|
+
if not isnode(nextParent):
|
|
1347
|
+
nextPart = getelem(parts, pI + 1)
|
|
1348
|
+
nextParent = [] if 0 < (T_number & typify(nextPart)) else {}
|
|
1349
|
+
setprop(parent, partKey, nextParent)
|
|
1350
|
+
parent = nextParent
|
|
1351
|
+
|
|
1352
|
+
if DELETE is val:
|
|
1353
|
+
delprop(parent, getelem(parts, -1))
|
|
1354
|
+
else:
|
|
1355
|
+
setprop(parent, getelem(parts, -1), val)
|
|
1356
|
+
|
|
1357
|
+
return parent
|
|
1358
|
+
|
|
1359
|
+
|
|
1360
|
+
def inject(val, store, injdef=UNDEF):
|
|
1361
|
+
"""
|
|
1362
|
+
Inject values from `store` into `val` recursively, respecting backtick syntax.
|
|
1363
|
+
"""
|
|
1364
|
+
valtype = type(val)
|
|
1365
|
+
|
|
1366
|
+
# Reuse existing injection state during recursion; otherwise create a new one.
|
|
1367
|
+
if isinstance(injdef, Injection):
|
|
1368
|
+
inj = injdef
|
|
1369
|
+
else:
|
|
1370
|
+
inj = injdef # may be dict/UNDEF; used below via getprop
|
|
1371
|
+
# Create state if at root of injection. The input value is placed
|
|
1372
|
+
# inside a virtual parent holder to simplify edge cases.
|
|
1373
|
+
parent = {S_DTOP: val}
|
|
1374
|
+
inj = Injection(
|
|
1375
|
+
mode=S_MVAL,
|
|
1376
|
+
full=False,
|
|
1377
|
+
keyI=0,
|
|
1378
|
+
keys=[S_DTOP],
|
|
1379
|
+
key=S_DTOP,
|
|
1380
|
+
val=val,
|
|
1381
|
+
parent=parent,
|
|
1382
|
+
path=[S_DTOP],
|
|
1383
|
+
nodes=[parent],
|
|
1384
|
+
handler=_injecthandler,
|
|
1385
|
+
base=S_DTOP,
|
|
1386
|
+
modify=getprop(injdef, 'modify') if injdef else None,
|
|
1387
|
+
meta=getprop(injdef, 'meta', {}),
|
|
1388
|
+
errs=getprop(store, S_DERRS, [])
|
|
1389
|
+
)
|
|
1390
|
+
inj.dparent = store
|
|
1391
|
+
inj.dpath = [S_DTOP]
|
|
1392
|
+
inj.root = parent # Virtual root so we can return it after $EACH etc. replace it
|
|
1393
|
+
|
|
1394
|
+
if injdef is not UNDEF:
|
|
1395
|
+
if getprop(injdef, 'extra'):
|
|
1396
|
+
inj.extra = getprop(injdef, 'extra')
|
|
1397
|
+
if getprop(injdef, 'handler'):
|
|
1398
|
+
inj.handler = getprop(injdef, 'handler')
|
|
1399
|
+
if getprop(injdef, 'dparent'):
|
|
1400
|
+
inj.dparent = getprop(injdef, 'dparent')
|
|
1401
|
+
if getprop(injdef, 'dpath'):
|
|
1402
|
+
inj.dpath = getprop(injdef, 'dpath')
|
|
1403
|
+
|
|
1404
|
+
inj.descend()
|
|
1405
|
+
|
|
1406
|
+
# Descend into node.
|
|
1407
|
+
if isnode(val):
|
|
1408
|
+
# Keys are sorted alphanumerically to ensure determinism.
|
|
1409
|
+
# Injection transforms ($FOO) are processed *after* other keys.
|
|
1410
|
+
if ismap(val):
|
|
1411
|
+
normal_keys = [k for k in val.keys() if S_DS not in k]
|
|
1412
|
+
normal_keys.sort()
|
|
1413
|
+
transform_keys = [k for k in val.keys() if S_DS in k]
|
|
1414
|
+
transform_keys.sort()
|
|
1415
|
+
nodekeys = normal_keys + transform_keys
|
|
1416
|
+
else:
|
|
1417
|
+
nodekeys = list(range(len(val)))
|
|
1418
|
+
|
|
1419
|
+
# Each child key-value pair is processed in three injection phases:
|
|
1420
|
+
# 1. inj.mode='key:pre' - Key string is injected, returning a possibly altered key.
|
|
1421
|
+
# 2. inj.mode='val' - The child value is injected.
|
|
1422
|
+
# 3. inj.mode='key:post' - Key string is injected again, allowing child mutation.
|
|
1423
|
+
nkI = 0
|
|
1424
|
+
while nkI < len(nodekeys):
|
|
1425
|
+
childinj = inj.child(nkI, nodekeys)
|
|
1426
|
+
nodekey = childinj.key
|
|
1427
|
+
childinj.mode = S_MKEYPRE
|
|
1428
|
+
|
|
1429
|
+
# Perform the key:pre mode injection on the child key.
|
|
1430
|
+
prekey = _injectstr(nodekey, store, childinj)
|
|
1431
|
+
|
|
1432
|
+
# The injection may modify child processing.
|
|
1433
|
+
nkI = childinj.keyI
|
|
1434
|
+
nodekeys = childinj.keys
|
|
1435
|
+
|
|
1436
|
+
# Prevent further processing by returning an undefined prekey
|
|
1437
|
+
if prekey is not UNDEF:
|
|
1438
|
+
childinj.val = getprop(val, prekey)
|
|
1439
|
+
childinj.mode = S_MVAL
|
|
1440
|
+
|
|
1441
|
+
# Perform the val mode injection on the child value.
|
|
1442
|
+
inject(childinj.val, store, childinj)
|
|
1443
|
+
|
|
1444
|
+
# The injection may modify child processing.
|
|
1445
|
+
nkI = childinj.keyI
|
|
1446
|
+
nodekeys = childinj.keys
|
|
1447
|
+
|
|
1448
|
+
# Perform the key:post mode injection on the child key.
|
|
1449
|
+
childinj.mode = S_MKEYPOST
|
|
1450
|
+
_injectstr(nodekey, store, childinj)
|
|
1451
|
+
|
|
1452
|
+
# The injection may modify child processing.
|
|
1453
|
+
nkI = childinj.keyI
|
|
1454
|
+
nodekeys = childinj.keys
|
|
1455
|
+
|
|
1456
|
+
nkI += 1
|
|
1457
|
+
|
|
1458
|
+
# Inject paths into string scalars.
|
|
1459
|
+
elif isinstance(val, str):
|
|
1460
|
+
inj.mode = S_MVAL
|
|
1461
|
+
val = _injectstr(val, store, inj)
|
|
1462
|
+
if val is not SKIP:
|
|
1463
|
+
inj.setval(val)
|
|
1464
|
+
|
|
1465
|
+
# Custom modification.
|
|
1466
|
+
if inj.modify and val is not SKIP:
|
|
1467
|
+
mkey = inj.key
|
|
1468
|
+
mparent = inj.parent
|
|
1469
|
+
mval = getprop(mparent, mkey)
|
|
1470
|
+
|
|
1471
|
+
inj.modify(mval, mkey, mparent, inj)
|
|
1472
|
+
|
|
1473
|
+
inj.val = val
|
|
1474
|
+
|
|
1475
|
+
# Return the (possibly transform-replaced) root only at top level (prior is None).
|
|
1476
|
+
if getattr(inj, 'prior', None) is None and getattr(inj, 'root', None) is not None and haskey(inj.root, S_DTOP):
|
|
1477
|
+
return getprop(inj.root, S_DTOP)
|
|
1478
|
+
if inj.key == S_DTOP and inj.parent is not UNDEF and haskey(inj.parent, S_DTOP):
|
|
1479
|
+
return getprop(inj.parent, S_DTOP)
|
|
1480
|
+
return val
|
|
1481
|
+
|
|
1482
|
+
|
|
1483
|
+
# Default inject handler for transforms. If the path resolves to a function,
|
|
1484
|
+
# call the function passing the injection state. This is how transforms operate.
|
|
1485
|
+
def _injecthandler(inj, val, ref, store):
|
|
1486
|
+
out = val
|
|
1487
|
+
iscmd = isfunc(val) and (UNDEF == ref or (isinstance(ref, str) and ref.startswith(S_DS)))
|
|
1488
|
+
|
|
1489
|
+
# Only call val function if it is a special command ($NAME format).
|
|
1490
|
+
if iscmd:
|
|
1491
|
+
try:
|
|
1492
|
+
num_params = len(inspect.signature(val).parameters)
|
|
1493
|
+
except (ValueError, TypeError):
|
|
1494
|
+
num_params = 4
|
|
1495
|
+
if num_params >= 5:
|
|
1496
|
+
out = val(inj, val, inj.dparent, ref, store)
|
|
1497
|
+
else:
|
|
1498
|
+
out = val(inj, val, ref, store)
|
|
1499
|
+
|
|
1500
|
+
# Update parent with value. Ensures references remain in node tree.
|
|
1501
|
+
else:
|
|
1502
|
+
if inj.mode == S_MVAL and inj.full:
|
|
1503
|
+
inj.setval(val)
|
|
1504
|
+
|
|
1505
|
+
return out
|
|
1506
|
+
|
|
1507
|
+
|
|
1508
|
+
# -----------------------------------------------------------------------------
|
|
1509
|
+
# Transform helper functions (these are injection handlers).
|
|
1510
|
+
|
|
1511
|
+
|
|
1512
|
+
def transform_DELETE(inj, val, ref, store):
|
|
1513
|
+
"""
|
|
1514
|
+
Injection handler to delete a key from a map/list.
|
|
1515
|
+
"""
|
|
1516
|
+
inj.setval(UNDEF)
|
|
1517
|
+
return UNDEF
|
|
1518
|
+
|
|
1519
|
+
|
|
1520
|
+
def transform_COPY(inj, val, ref, store):
|
|
1521
|
+
"""
|
|
1522
|
+
Injection handler to copy a value from source data under the same key.
|
|
1523
|
+
"""
|
|
1524
|
+
mode = inj.mode
|
|
1525
|
+
key = inj.key
|
|
1526
|
+
parent = inj.parent
|
|
1527
|
+
|
|
1528
|
+
out = UNDEF
|
|
1529
|
+
if mode.startswith('key'):
|
|
1530
|
+
out = key
|
|
1531
|
+
else:
|
|
1532
|
+
# If dparent is a scalar (not a node): at root (path length 1) use whole data; when nested
|
|
1533
|
+
# (path length > 2) use dparent; at first level (path length 2): if key is a list index
|
|
1534
|
+
# we're at a list item (dparent already indexed) -> use dparent; else omit key (UNDEF).
|
|
1535
|
+
if not isnode(inj.dparent):
|
|
1536
|
+
if len(inj.path) != 2:
|
|
1537
|
+
out = inj.dparent
|
|
1538
|
+
else:
|
|
1539
|
+
try:
|
|
1540
|
+
int(key) # list index -> we're at the list item value
|
|
1541
|
+
out = inj.dparent
|
|
1542
|
+
except (ValueError, TypeError):
|
|
1543
|
+
out = UNDEF
|
|
1544
|
+
else:
|
|
1545
|
+
out = getprop(inj.dparent, key)
|
|
1546
|
+
# If getprop returned UNDEF and key looks like a list index,
|
|
1547
|
+
# we might be at the item level already - return dparent itself
|
|
1548
|
+
if out is UNDEF and key is not None:
|
|
1549
|
+
try:
|
|
1550
|
+
int(key) # key is a list index
|
|
1551
|
+
# We're at the item level, key is the list index
|
|
1552
|
+
# This shouldn't happen normally, but handle it
|
|
1553
|
+
out = inj.dparent
|
|
1554
|
+
except (ValueError, TypeError):
|
|
1555
|
+
pass
|
|
1556
|
+
inj.setval(out)
|
|
1557
|
+
|
|
1558
|
+
return out
|
|
1559
|
+
|
|
1560
|
+
|
|
1561
|
+
def transform_KEY(inj, val, ref, store):
|
|
1562
|
+
"""
|
|
1563
|
+
Injection handler to inject the parent's key (or a specified key).
|
|
1564
|
+
"""
|
|
1565
|
+
mode = inj.mode
|
|
1566
|
+
path = inj.path
|
|
1567
|
+
parent = inj.parent
|
|
1568
|
+
|
|
1569
|
+
if mode == S_MKEYPRE:
|
|
1570
|
+
# Preserve the key during pre phase so value phase runs
|
|
1571
|
+
return inj.key
|
|
1572
|
+
if mode != S_MVAL:
|
|
1573
|
+
return UNDEF
|
|
1574
|
+
|
|
1575
|
+
keyspec = getprop(parent, S_BKEY)
|
|
1576
|
+
if keyspec is not UNDEF:
|
|
1577
|
+
# Need to use setprop directly here since we're removing a specific key (S_DKEY)
|
|
1578
|
+
# not the current state's key
|
|
1579
|
+
setprop(parent, S_BKEY, UNDEF)
|
|
1580
|
+
return getprop(inj.dparent, keyspec)
|
|
1581
|
+
|
|
1582
|
+
# If no explicit keyspec, and current data has a field matching this key,
|
|
1583
|
+
# use that value (common case: { k: '`$KEY`' } to pull dparent['k']).
|
|
1584
|
+
if ismap(inj.dparent) and inj.key is not UNDEF and haskey(inj.dparent, inj.key):
|
|
1585
|
+
return getprop(inj.dparent, inj.key)
|
|
1586
|
+
|
|
1587
|
+
meta = getprop(parent, S_BANNO)
|
|
1588
|
+
return getprop(meta, S_KEY, getprop(path, len(path) - 2))
|
|
1589
|
+
|
|
1590
|
+
|
|
1591
|
+
def transform_ANNO(inj, val, ref, store):
|
|
1592
|
+
"""
|
|
1593
|
+
Annotate node. Does nothing itself, just used by other injectors, and is removed when called.
|
|
1594
|
+
"""
|
|
1595
|
+
parent = inj.parent
|
|
1596
|
+
setprop(parent, S_BANNO, UNDEF)
|
|
1597
|
+
return UNDEF
|
|
1598
|
+
|
|
1599
|
+
|
|
1600
|
+
def transform_MERGE(inj, val, ref, store):
|
|
1601
|
+
"""
|
|
1602
|
+
Injection handler to merge a list of objects onto the parent object.
|
|
1603
|
+
If the transform data is an empty string, merge the top-level store.
|
|
1604
|
+
"""
|
|
1605
|
+
mode = inj.mode
|
|
1606
|
+
key = inj.key
|
|
1607
|
+
parent = inj.parent
|
|
1608
|
+
|
|
1609
|
+
out = UNDEF
|
|
1610
|
+
|
|
1611
|
+
if mode == S_MKEYPRE:
|
|
1612
|
+
out = key
|
|
1613
|
+
|
|
1614
|
+
# Operate after child values have been transformed.
|
|
1615
|
+
elif mode == S_MKEYPOST:
|
|
1616
|
+
out = key
|
|
1617
|
+
|
|
1618
|
+
args = getprop(parent, key)
|
|
1619
|
+
args = args if islist(args) else [args]
|
|
1620
|
+
|
|
1621
|
+
# Remove the $MERGE command from a parent map.
|
|
1622
|
+
inj.setval(UNDEF)
|
|
1623
|
+
|
|
1624
|
+
# Literals in the parent have precedence, but we still merge onto
|
|
1625
|
+
# the parent object, so that node tree references are not changed.
|
|
1626
|
+
mergelist = [parent] + args + [clone(parent)]
|
|
1627
|
+
|
|
1628
|
+
merge(mergelist)
|
|
1629
|
+
|
|
1630
|
+
# List syntax: parent is an array like ['`$MERGE`', ...]
|
|
1631
|
+
elif mode == S_MVAL and islist(parent):
|
|
1632
|
+
# Only act on the transform element at index 0
|
|
1633
|
+
if strkey(inj.key) == '0' and size(parent) > 0:
|
|
1634
|
+
# Drop the command element so remaining args become the list content
|
|
1635
|
+
del parent[0]
|
|
1636
|
+
# Return the new first element as the injected scalar
|
|
1637
|
+
out = getprop(parent, 0)
|
|
1638
|
+
else:
|
|
1639
|
+
out = getprop(parent, inj.key)
|
|
1640
|
+
|
|
1641
|
+
return out
|
|
1642
|
+
|
|
1643
|
+
|
|
1644
|
+
def transform_EACH(inj, val, ref, store):
|
|
1645
|
+
"""
|
|
1646
|
+
Injection handler to convert the current node into a list by iterating over
|
|
1647
|
+
a source node. Format: ['`$EACH`','`source-path`', child-template]
|
|
1648
|
+
"""
|
|
1649
|
+
mode = inj.mode
|
|
1650
|
+
keys_ = inj.keys
|
|
1651
|
+
path = inj.path
|
|
1652
|
+
parent = inj.parent
|
|
1653
|
+
nodes_ = inj.nodes
|
|
1654
|
+
|
|
1655
|
+
if keys_ is not UNDEF:
|
|
1656
|
+
# Only keep the transform item (first). Avoid further spurious keys.
|
|
1657
|
+
keys_[:] = keys_[:1]
|
|
1658
|
+
|
|
1659
|
+
if mode != S_MVAL or path is UNDEF or nodes_ is UNDEF:
|
|
1660
|
+
return UNDEF
|
|
1661
|
+
|
|
1662
|
+
# parent here is the array [ '$EACH', 'source-path', {... child ...} ]
|
|
1663
|
+
srcpath = parent[1] if len(parent) > 1 else UNDEF
|
|
1664
|
+
child_template = clone(parent[2]) if len(parent) > 2 else UNDEF
|
|
1665
|
+
|
|
1666
|
+
# Source data
|
|
1667
|
+
srcstore = getprop(store, inj.base, store)
|
|
1668
|
+
src = getpath(srcstore, srcpath, inj)
|
|
1669
|
+
|
|
1670
|
+
# Create parallel data structures:
|
|
1671
|
+
# source entries :: child templates
|
|
1672
|
+
tcurrent = []
|
|
1673
|
+
tval = []
|
|
1674
|
+
|
|
1675
|
+
tkey = path[-2] if len(path) >= 2 else UNDEF
|
|
1676
|
+
target = nodes_[-2] if len(nodes_) >= 2 else nodes_[-1]
|
|
1677
|
+
|
|
1678
|
+
rval = []
|
|
1679
|
+
|
|
1680
|
+
if isnode(src):
|
|
1681
|
+
if islist(src):
|
|
1682
|
+
tval = [clone(child_template) for _ in src]
|
|
1683
|
+
else:
|
|
1684
|
+
# Convert dict to a list of child templates
|
|
1685
|
+
tval = []
|
|
1686
|
+
for k, v in src.items():
|
|
1687
|
+
# Keep key in meta for usage by `$KEY`
|
|
1688
|
+
copy_child = clone(child_template)
|
|
1689
|
+
if ismap(copy_child):
|
|
1690
|
+
setprop(copy_child, S_BANNO, {S_KEY: k})
|
|
1691
|
+
tval.append(copy_child)
|
|
1692
|
+
tcurrent = list(src.values()) if ismap(src) else src
|
|
1693
|
+
|
|
1694
|
+
if 0 < size(tval):
|
|
1695
|
+
# Build tcurrent structure matching TypeScript approach
|
|
1696
|
+
ckey = getelem(path, -2) if len(path) >= 2 else UNDEF
|
|
1697
|
+
tpath = path[:-1] if len(path) > 0 else []
|
|
1698
|
+
|
|
1699
|
+
# Build dpath: [S_DTOP, ...srcpath parts, '$:' + ckey]
|
|
1700
|
+
dpath = [S_DTOP]
|
|
1701
|
+
if isinstance(srcpath, str) and srcpath:
|
|
1702
|
+
for part in srcpath.split(S_DT):
|
|
1703
|
+
if part != S_MT:
|
|
1704
|
+
dpath.append(part)
|
|
1705
|
+
if ckey is not UNDEF:
|
|
1706
|
+
dpath.append('$:' + str(ckey))
|
|
1707
|
+
|
|
1708
|
+
tcur = {ckey: tcurrent}
|
|
1709
|
+
|
|
1710
|
+
if 1 < size(tpath):
|
|
1711
|
+
pkey = getelem(path, -3, S_DTOP)
|
|
1712
|
+
tcur = {pkey: tcur}
|
|
1713
|
+
dpath.append('$:' + str(pkey))
|
|
1714
|
+
|
|
1715
|
+
# Create child injection state
|
|
1716
|
+
tinj = inj.child(0, [ckey] if ckey is not UNDEF else [])
|
|
1717
|
+
tinj.path = tpath
|
|
1718
|
+
tinj.nodes = nodes_[:-1] if len(nodes_) > 0 else []
|
|
1719
|
+
tinj.parent = getelem(tinj.nodes, -1) if len(tinj.nodes) > 0 else UNDEF
|
|
1720
|
+
|
|
1721
|
+
if ckey is not UNDEF and tinj.parent is not UNDEF:
|
|
1722
|
+
setprop(tinj.parent, ckey, tval)
|
|
1723
|
+
|
|
1724
|
+
tinj.val = tval
|
|
1725
|
+
tinj.dpath = dpath
|
|
1726
|
+
tinj.dparent = tcur
|
|
1727
|
+
|
|
1728
|
+
# Inject the entire list at once
|
|
1729
|
+
inject(tval, store, tinj)
|
|
1730
|
+
rval = tinj.val
|
|
1731
|
+
|
|
1732
|
+
setprop(target, tkey, rval)
|
|
1733
|
+
|
|
1734
|
+
return rval[0] if islist(rval) and 0 < size(rval) else UNDEF
|
|
1735
|
+
|
|
1736
|
+
|
|
1737
|
+
def transform_PACK(inj, val, ref, store):
|
|
1738
|
+
mode = inj.mode
|
|
1739
|
+
key = inj.key
|
|
1740
|
+
path = inj.path
|
|
1741
|
+
parent = inj.parent
|
|
1742
|
+
nodes_ = inj.nodes
|
|
1743
|
+
|
|
1744
|
+
if (mode != S_MKEYPRE or not isinstance(key, str) or path is UNDEF or nodes_ is UNDEF):
|
|
1745
|
+
return UNDEF
|
|
1746
|
+
|
|
1747
|
+
args_val = getprop(parent, key)
|
|
1748
|
+
if not islist(args_val) or size(args_val) < 2:
|
|
1749
|
+
return UNDEF
|
|
1750
|
+
|
|
1751
|
+
srcpath = args_val[0]
|
|
1752
|
+
origchildspec = args_val[1]
|
|
1753
|
+
|
|
1754
|
+
tkey = getelem(path, -2)
|
|
1755
|
+
pathsize = size(path)
|
|
1756
|
+
target = getelem(nodes_, pathsize - 2, lambda: getelem(nodes_, pathsize - 1))
|
|
1757
|
+
|
|
1758
|
+
srcstore = getprop(store, inj.base, store)
|
|
1759
|
+
src = getpath(srcstore, srcpath, inj)
|
|
1760
|
+
|
|
1761
|
+
if not islist(src):
|
|
1762
|
+
if ismap(src):
|
|
1763
|
+
src_items = items(src)
|
|
1764
|
+
new_src = []
|
|
1765
|
+
for item in src_items:
|
|
1766
|
+
setprop(item[1], S_BANNO, {S_KEY: item[0]})
|
|
1767
|
+
new_src.append(item[1])
|
|
1768
|
+
src = new_src
|
|
1769
|
+
else:
|
|
1770
|
+
src = UNDEF
|
|
1771
|
+
|
|
1772
|
+
if src is UNDEF:
|
|
1773
|
+
return UNDEF
|
|
1774
|
+
|
|
1775
|
+
keypath = getprop(origchildspec, S_BKEY)
|
|
1776
|
+
childspec = delprop(origchildspec, S_BKEY)
|
|
1777
|
+
|
|
1778
|
+
child = getprop(childspec, S_BVAL, childspec)
|
|
1779
|
+
|
|
1780
|
+
tval = {}
|
|
1781
|
+
for item in items(src):
|
|
1782
|
+
srckey = item[0]
|
|
1783
|
+
srcnode = item[1]
|
|
1784
|
+
|
|
1785
|
+
k = srckey
|
|
1786
|
+
if keypath is not UNDEF:
|
|
1787
|
+
if isinstance(keypath, str) and keypath.startswith(S_BT):
|
|
1788
|
+
k = inject(keypath, merge([{}, store, {S_DTOP: srcnode}], 1))
|
|
1789
|
+
else:
|
|
1790
|
+
k = getpath(srcnode, keypath, inj)
|
|
1791
|
+
|
|
1792
|
+
tchild = clone(child)
|
|
1793
|
+
setprop(tval, k, tchild)
|
|
1794
|
+
|
|
1795
|
+
anno = getprop(srcnode, S_BANNO)
|
|
1796
|
+
if anno is UNDEF:
|
|
1797
|
+
delprop(tchild, S_BANNO)
|
|
1798
|
+
else:
|
|
1799
|
+
setprop(tchild, S_BANNO, anno)
|
|
1800
|
+
|
|
1801
|
+
rval = {}
|
|
1802
|
+
|
|
1803
|
+
if not isempty(tval):
|
|
1804
|
+
tsrc = {}
|
|
1805
|
+
for i, n in enumerate(src):
|
|
1806
|
+
if keypath is UNDEF:
|
|
1807
|
+
kn = i
|
|
1808
|
+
elif isinstance(keypath, str) and keypath.startswith(S_BT):
|
|
1809
|
+
kn = inject(keypath, merge([{}, store, {S_DTOP: n}], 1))
|
|
1810
|
+
else:
|
|
1811
|
+
kn = getpath(n, keypath, inj)
|
|
1812
|
+
setprop(tsrc, kn, n)
|
|
1813
|
+
|
|
1814
|
+
tpath = slice(inj.path, -1)
|
|
1815
|
+
ckey = getelem(inj.path, -2)
|
|
1816
|
+
dpath = flatten([S_DTOP, srcpath.split(S_DT), '$:' + str(ckey)])
|
|
1817
|
+
|
|
1818
|
+
tcur = {ckey: tsrc}
|
|
1819
|
+
|
|
1820
|
+
if 1 < size(tpath):
|
|
1821
|
+
pkey = getelem(inj.path, -3, S_DTOP)
|
|
1822
|
+
tcur = {pkey: tcur}
|
|
1823
|
+
dpath.append('$:' + str(pkey))
|
|
1824
|
+
|
|
1825
|
+
tinj = inj.child(0, [ckey])
|
|
1826
|
+
tinj.path = tpath
|
|
1827
|
+
tinj.nodes = slice(inj.nodes, -1)
|
|
1828
|
+
tinj.parent = getelem(tinj.nodes, -1)
|
|
1829
|
+
tinj.val = tval
|
|
1830
|
+
tinj.dpath = dpath
|
|
1831
|
+
tinj.dparent = tcur
|
|
1832
|
+
|
|
1833
|
+
inject(tval, store, tinj)
|
|
1834
|
+
rval = tinj.val
|
|
1835
|
+
|
|
1836
|
+
setprop(target, tkey, rval)
|
|
1837
|
+
|
|
1838
|
+
return UNDEF
|
|
1839
|
+
|
|
1840
|
+
|
|
1841
|
+
def transform_REF(inj, val, _ref, store):
|
|
1842
|
+
"""
|
|
1843
|
+
Reference original spec (enables recursive transformations)
|
|
1844
|
+
Format: ['`$REF`', '`spec-path`']
|
|
1845
|
+
"""
|
|
1846
|
+
nodes = inj.nodes
|
|
1847
|
+
modify = inj.modify
|
|
1848
|
+
|
|
1849
|
+
if inj.mode != S_MVAL:
|
|
1850
|
+
return UNDEF
|
|
1851
|
+
|
|
1852
|
+
# Get arguments: ['`$REF`', 'ref-path']
|
|
1853
|
+
refpath = getprop(inj.parent, 1)
|
|
1854
|
+
inj.keyI = len(inj.keys)
|
|
1855
|
+
|
|
1856
|
+
# Spec reference
|
|
1857
|
+
spec_func = getprop(store, S_DSPEC)
|
|
1858
|
+
if not callable(spec_func):
|
|
1859
|
+
return UNDEF
|
|
1860
|
+
spec = spec_func()
|
|
1861
|
+
ref = getpath(spec, refpath)
|
|
1862
|
+
|
|
1863
|
+
# Check if ref has another $REF inside
|
|
1864
|
+
hasSubRef = False
|
|
1865
|
+
if isnode(ref):
|
|
1866
|
+
def check_subref(k, v, parent, path):
|
|
1867
|
+
nonlocal hasSubRef
|
|
1868
|
+
if v == '`$REF`':
|
|
1869
|
+
hasSubRef = True
|
|
1870
|
+
return v
|
|
1871
|
+
|
|
1872
|
+
walk(ref, check_subref)
|
|
1873
|
+
|
|
1874
|
+
tref = clone(ref)
|
|
1875
|
+
|
|
1876
|
+
cpath = slice(inj.path, 0, len(inj.path)-3)
|
|
1877
|
+
tpath = slice(inj.path, 0, len(inj.path)-1)
|
|
1878
|
+
tcur = getpath(store, cpath)
|
|
1879
|
+
tval = getpath(store, tpath)
|
|
1880
|
+
rval = UNDEF
|
|
1881
|
+
|
|
1882
|
+
# When ref target not found, omit the key (setval UNDEF). Do not inject UNDEF.
|
|
1883
|
+
if ref is not UNDEF and (not hasSubRef or tval is not UNDEF):
|
|
1884
|
+
# Create child state for the next level
|
|
1885
|
+
child_state = inj.child(0, [getelem(tpath, -1)])
|
|
1886
|
+
child_state.path = tpath
|
|
1887
|
+
child_state.nodes = slice(inj.nodes, 0, len(inj.nodes)-1)
|
|
1888
|
+
child_state.parent = getelem(nodes, -2)
|
|
1889
|
+
child_state.val = tref
|
|
1890
|
+
|
|
1891
|
+
# Inject with child state
|
|
1892
|
+
child_state.dparent = tcur
|
|
1893
|
+
inject(tref, store, child_state)
|
|
1894
|
+
rval = child_state.val
|
|
1895
|
+
else:
|
|
1896
|
+
rval = UNDEF
|
|
1897
|
+
|
|
1898
|
+
# Set the value in grandparent, using setval
|
|
1899
|
+
inj.setval(rval, 2)
|
|
1900
|
+
|
|
1901
|
+
# Handle lists by decrementing keyI
|
|
1902
|
+
if islist(inj.parent) and inj.prior:
|
|
1903
|
+
inj.prior.keyI -= 1
|
|
1904
|
+
|
|
1905
|
+
return val
|
|
1906
|
+
|
|
1907
|
+
|
|
1908
|
+
def _fmt_number(_k, v, *_args):
|
|
1909
|
+
if isnode(v):
|
|
1910
|
+
return v
|
|
1911
|
+
try:
|
|
1912
|
+
n = float(v)
|
|
1913
|
+
except (ValueError, TypeError):
|
|
1914
|
+
n = 0
|
|
1915
|
+
if n != n:
|
|
1916
|
+
n = 0
|
|
1917
|
+
return int(n) if n == int(n) else n
|
|
1918
|
+
|
|
1919
|
+
|
|
1920
|
+
def _fmt_integer(_k, v, *_args):
|
|
1921
|
+
if isnode(v):
|
|
1922
|
+
return v
|
|
1923
|
+
try:
|
|
1924
|
+
n = float(v)
|
|
1925
|
+
except (ValueError, TypeError):
|
|
1926
|
+
n = 0
|
|
1927
|
+
if n != n:
|
|
1928
|
+
n = 0
|
|
1929
|
+
return int(n)
|
|
1930
|
+
|
|
1931
|
+
|
|
1932
|
+
def _jsstr(v):
|
|
1933
|
+
if v is None:
|
|
1934
|
+
return 'null'
|
|
1935
|
+
if isinstance(v, bool):
|
|
1936
|
+
return 'true' if v else 'false'
|
|
1937
|
+
return str(v)
|
|
1938
|
+
|
|
1939
|
+
|
|
1940
|
+
FORMATTER = {
|
|
1941
|
+
'identity': lambda _k, v, *_a: v,
|
|
1942
|
+
'upper': lambda _k, v, *_a: v if isnode(v) else _jsstr(v).upper(),
|
|
1943
|
+
'lower': lambda _k, v, *_a: v if isnode(v) else _jsstr(v).lower(),
|
|
1944
|
+
'string': lambda _k, v, *_a: v if isnode(v) else _jsstr(v),
|
|
1945
|
+
'number': _fmt_number,
|
|
1946
|
+
'integer': _fmt_integer,
|
|
1947
|
+
'concat': lambda k, v, *_a: join(
|
|
1948
|
+
items(v, lambda n: '' if isnode(n[1]) else _jsstr(n[1])), '') if k is None and islist(v) else v,
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
|
|
1952
|
+
def checkPlacement(modes, ijname, parentTypes, inj):
|
|
1953
|
+
mode_num = _MODE_TO_NUM.get(inj.mode, 0)
|
|
1954
|
+
if 0 == (modes & mode_num):
|
|
1955
|
+
allowed = [m for m in [M_KEYPRE, M_KEYPOST, M_VAL] if modes & m]
|
|
1956
|
+
placements = join(
|
|
1957
|
+
items(allowed, lambda n: _PLACEMENT.get(n[1], '')), ',')
|
|
1958
|
+
inj.errs.append('$' + ijname + ': invalid placement as ' +
|
|
1959
|
+
_PLACEMENT.get(mode_num, '') +
|
|
1960
|
+
', expected: ' + placements + '.')
|
|
1961
|
+
return False
|
|
1962
|
+
if not isempty(parentTypes):
|
|
1963
|
+
ptype = typify(inj.parent)
|
|
1964
|
+
if 0 == (parentTypes & ptype):
|
|
1965
|
+
inj.errs.append('$' + ijname + ': invalid placement in parent ' +
|
|
1966
|
+
typename(ptype) + ', expected: ' + typename(parentTypes) + '.')
|
|
1967
|
+
return False
|
|
1968
|
+
return True
|
|
1969
|
+
|
|
1970
|
+
|
|
1971
|
+
def injectorArgs(argTypes, args):
|
|
1972
|
+
numargs = size(argTypes)
|
|
1973
|
+
found = [UNDEF] * (1 + numargs)
|
|
1974
|
+
found[0] = UNDEF
|
|
1975
|
+
for argI in range(numargs):
|
|
1976
|
+
arg = getelem(args, argI)
|
|
1977
|
+
argType = typify(arg)
|
|
1978
|
+
if 0 == (argTypes[argI] & argType):
|
|
1979
|
+
found[0] = ('invalid argument: ' + stringify(arg, 22) +
|
|
1980
|
+
' (' + typename(argType) + ' at position ' + str(1 + argI) +
|
|
1981
|
+
') is not of type: ' + typename(argTypes[argI]) + '.')
|
|
1982
|
+
break
|
|
1983
|
+
found[1 + argI] = arg
|
|
1984
|
+
return found
|
|
1985
|
+
|
|
1986
|
+
|
|
1987
|
+
def injectChild(child, store, inj):
|
|
1988
|
+
cinj = inj
|
|
1989
|
+
if inj.prior is not UNDEF and inj.prior is not None:
|
|
1990
|
+
if inj.prior.prior is not UNDEF and inj.prior.prior is not None:
|
|
1991
|
+
cinj = inj.prior.prior.child(inj.prior.keyI, inj.prior.keys)
|
|
1992
|
+
cinj.val = child
|
|
1993
|
+
setprop(cinj.parent, inj.prior.key, child)
|
|
1994
|
+
else:
|
|
1995
|
+
cinj = inj.prior.child(inj.keyI, inj.keys)
|
|
1996
|
+
cinj.val = child
|
|
1997
|
+
setprop(cinj.parent, inj.key, child)
|
|
1998
|
+
inject(child, store, cinj)
|
|
1999
|
+
return cinj
|
|
2000
|
+
|
|
2001
|
+
|
|
2002
|
+
def transform_FORMAT(inj, _val, _ref, store):
|
|
2003
|
+
slice(inj.keys, 0, 1, True)
|
|
2004
|
+
|
|
2005
|
+
if S_MVAL != inj.mode:
|
|
2006
|
+
return UNDEF
|
|
2007
|
+
|
|
2008
|
+
name = getprop(inj.parent, 1)
|
|
2009
|
+
child = getprop(inj.parent, 2)
|
|
2010
|
+
|
|
2011
|
+
tkey = getelem(inj.path, -2)
|
|
2012
|
+
target = getelem(inj.nodes, -2, lambda: getelem(inj.nodes, -1))
|
|
2013
|
+
|
|
2014
|
+
cinj = injectChild(child, store, inj)
|
|
2015
|
+
resolved = cinj.val
|
|
2016
|
+
|
|
2017
|
+
formatter = name if 0 < (T_function & typify(name)) else getprop(FORMATTER, name)
|
|
2018
|
+
|
|
2019
|
+
if formatter is UNDEF:
|
|
2020
|
+
inj.errs.append('$FORMAT: unknown format: ' + str(name) + '.')
|
|
2021
|
+
return UNDEF
|
|
2022
|
+
|
|
2023
|
+
out = walk(resolved, formatter)
|
|
2024
|
+
|
|
2025
|
+
setprop(target, tkey, out)
|
|
2026
|
+
|
|
2027
|
+
return out
|
|
2028
|
+
|
|
2029
|
+
|
|
2030
|
+
def transform_APPLY(inj, _val, _ref, store):
|
|
2031
|
+
ijname = 'APPLY'
|
|
2032
|
+
|
|
2033
|
+
if not checkPlacement(M_VAL, ijname, T_list, inj):
|
|
2034
|
+
return UNDEF
|
|
2035
|
+
|
|
2036
|
+
err_apply_child = injectorArgs([T_function, T_any], slice(inj.parent, 1))
|
|
2037
|
+
err = err_apply_child[0]
|
|
2038
|
+
apply_fn = err_apply_child[1]
|
|
2039
|
+
child = err_apply_child[2] if len(err_apply_child) > 2 else UNDEF
|
|
2040
|
+
|
|
2041
|
+
if UNDEF != err:
|
|
2042
|
+
inj.errs.append('$' + ijname + ': ' + err)
|
|
2043
|
+
return UNDEF
|
|
2044
|
+
|
|
2045
|
+
tkey = getelem(inj.path, -2)
|
|
2046
|
+
target = getelem(inj.nodes, -2, lambda: getelem(inj.nodes, -1))
|
|
2047
|
+
|
|
2048
|
+
cinj = injectChild(child, store, inj)
|
|
2049
|
+
resolved = cinj.val
|
|
2050
|
+
|
|
2051
|
+
try:
|
|
2052
|
+
out = apply_fn(resolved, store, cinj)
|
|
2053
|
+
except TypeError:
|
|
2054
|
+
try:
|
|
2055
|
+
out = apply_fn(resolved, store)
|
|
2056
|
+
except TypeError:
|
|
2057
|
+
out = apply_fn(resolved)
|
|
2058
|
+
|
|
2059
|
+
setprop(target, tkey, out)
|
|
2060
|
+
|
|
2061
|
+
return out
|
|
2062
|
+
|
|
2063
|
+
|
|
2064
|
+
# Transform data using spec.
|
|
2065
|
+
# Only operates on static JSON-like data.
|
|
2066
|
+
# Arrays are treated as if they are objects with indices as keys.
|
|
2067
|
+
def transform(
|
|
2068
|
+
data,
|
|
2069
|
+
spec,
|
|
2070
|
+
injdef=UNDEF
|
|
2071
|
+
):
|
|
2072
|
+
# Clone the spec so that the clone can be modified in place as the transform result.
|
|
2073
|
+
origspec = spec
|
|
2074
|
+
spec = clone(spec)
|
|
2075
|
+
|
|
2076
|
+
extra = getprop(injdef, 'extra') if injdef else UNDEF
|
|
2077
|
+
|
|
2078
|
+
collect = getprop(injdef, 'errs') is not None and getprop(injdef, 'errs') is not UNDEF if injdef else False
|
|
2079
|
+
errs = getprop(injdef, 'errs') if collect else []
|
|
2080
|
+
|
|
2081
|
+
extraTransforms = {}
|
|
2082
|
+
extraData = {} if UNDEF == extra else {}
|
|
2083
|
+
|
|
2084
|
+
if extra:
|
|
2085
|
+
for k, v in items(extra):
|
|
2086
|
+
if isinstance(k, str) and k.startswith(S_DS):
|
|
2087
|
+
extraTransforms[k] = v
|
|
2088
|
+
else:
|
|
2089
|
+
extraData[k] = v
|
|
2090
|
+
|
|
2091
|
+
# Combine extra data with user data
|
|
2092
|
+
data_clone = merge([
|
|
2093
|
+
clone(extraData) if not isempty(extraData) else UNDEF,
|
|
2094
|
+
clone(data)
|
|
2095
|
+
])
|
|
2096
|
+
|
|
2097
|
+
# Top-level store used by inject
|
|
2098
|
+
store = {
|
|
2099
|
+
# The inject function recognises this special location for the root of the source data.
|
|
2100
|
+
# NOTE: to escape data that contains "`$FOO`" keys at the top level,
|
|
2101
|
+
# place that data inside a holding map: { myholder: mydata }.
|
|
2102
|
+
S_DTOP: data_clone,
|
|
2103
|
+
|
|
2104
|
+
# Original spec (before clone) for $REF to resolve refpath.
|
|
2105
|
+
S_DSPEC: lambda: origspec,
|
|
2106
|
+
|
|
2107
|
+
# Escape backtick (this also works inside backticks).
|
|
2108
|
+
'$BT': lambda *args, **kwargs: S_BT,
|
|
2109
|
+
|
|
2110
|
+
# Escape dollar sign (this also works inside backticks).
|
|
2111
|
+
'$DS': lambda *args, **kwargs: S_DS,
|
|
2112
|
+
|
|
2113
|
+
# Insert current date and time as an ISO string.
|
|
2114
|
+
'$WHEN': lambda *args, **kwargs: datetime.utcnow().isoformat(),
|
|
2115
|
+
|
|
2116
|
+
'$DELETE': transform_DELETE,
|
|
2117
|
+
'$COPY': transform_COPY,
|
|
2118
|
+
'$KEY': transform_KEY,
|
|
2119
|
+
'$ANNO': transform_ANNO,
|
|
2120
|
+
'$MERGE': transform_MERGE,
|
|
2121
|
+
'$EACH': transform_EACH,
|
|
2122
|
+
'$PACK': transform_PACK,
|
|
2123
|
+
'$REF': transform_REF,
|
|
2124
|
+
'$FORMAT': transform_FORMAT,
|
|
2125
|
+
'$APPLY': transform_APPLY,
|
|
2126
|
+
|
|
2127
|
+
# Custom extra transforms, if any.
|
|
2128
|
+
**extraTransforms,
|
|
2129
|
+
|
|
2130
|
+
S_DERRS: errs,
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
if injdef is UNDEF or injdef is None:
|
|
2134
|
+
injdef = {}
|
|
2135
|
+
if not isinstance(injdef, dict):
|
|
2136
|
+
injdef = {}
|
|
2137
|
+
injdef = {**injdef, 'errs': errs}
|
|
2138
|
+
|
|
2139
|
+
out = inject(spec, store, injdef)
|
|
2140
|
+
|
|
2141
|
+
generr = 0 < size(errs) and not collect
|
|
2142
|
+
if generr:
|
|
2143
|
+
raise ValueError(join(errs, ' | '))
|
|
2144
|
+
|
|
2145
|
+
return out
|
|
2146
|
+
|
|
2147
|
+
|
|
2148
|
+
def validate_STRING(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF):
|
|
2149
|
+
out = getprop(inj.dparent, inj.key)
|
|
2150
|
+
t = typify(out)
|
|
2151
|
+
|
|
2152
|
+
if 0 == (T_string & t):
|
|
2153
|
+
inj.errs.append(_invalidTypeMsg(inj.path, S_string, t, out, 'V1010'))
|
|
2154
|
+
return UNDEF
|
|
2155
|
+
|
|
2156
|
+
if S_MT == out:
|
|
2157
|
+
inj.errs.append('Empty string at ' + pathify(inj.path, 1))
|
|
2158
|
+
return UNDEF
|
|
2159
|
+
|
|
2160
|
+
return out
|
|
2161
|
+
|
|
2162
|
+
|
|
2163
|
+
TYPE_CHECKS = {
|
|
2164
|
+
S_number: lambda v: isinstance(v, (int, float)) and not isinstance(v, bool),
|
|
2165
|
+
S_integer: lambda v: isinstance(v, int) and not isinstance(v, bool),
|
|
2166
|
+
S_decimal: lambda v: isinstance(v, float),
|
|
2167
|
+
S_boolean: lambda v: isinstance(v, bool),
|
|
2168
|
+
S_null: lambda v: v is None,
|
|
2169
|
+
S_nil: lambda v: v is UNDEF,
|
|
2170
|
+
S_map: lambda v: isinstance(v, dict),
|
|
2171
|
+
S_list: lambda v: isinstance(v, list),
|
|
2172
|
+
S_function: lambda v: callable(v) and not isinstance(v, type),
|
|
2173
|
+
S_instance: lambda v: (not isinstance(v, (dict, list, str, int, float, bool))
|
|
2174
|
+
and v is not None and v is not UNDEF),
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
|
|
2178
|
+
def validate_TYPE(inj, _val=UNDEF, ref=UNDEF, _store=UNDEF):
|
|
2179
|
+
tname = slice(ref, 1).lower() if isinstance(ref, str) and len(ref) > 1 else S_any
|
|
2180
|
+
typev = 1 << (31 - TYPENAME.index(tname)) if tname in TYPENAME else 0
|
|
2181
|
+
if tname == S_nil:
|
|
2182
|
+
typev = typev | T_null
|
|
2183
|
+
out = getprop(inj.dparent, inj.key)
|
|
2184
|
+
t = typify(out)
|
|
2185
|
+
|
|
2186
|
+
if 0 == (t & typev):
|
|
2187
|
+
inj.errs.append(_invalidTypeMsg(inj.path, tname, t, out, 'V1001'))
|
|
2188
|
+
return UNDEF
|
|
2189
|
+
|
|
2190
|
+
return out
|
|
2191
|
+
|
|
2192
|
+
|
|
2193
|
+
def validate_ANY(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF):
|
|
2194
|
+
return getprop(inj.dparent, inj.key)
|
|
2195
|
+
|
|
2196
|
+
|
|
2197
|
+
def validate_CHILD(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF):
|
|
2198
|
+
mode = inj.mode
|
|
2199
|
+
key = inj.key
|
|
2200
|
+
parent = inj.parent
|
|
2201
|
+
path = inj.path
|
|
2202
|
+
keys = inj.keys
|
|
2203
|
+
|
|
2204
|
+
# Map syntax.
|
|
2205
|
+
if S_MKEYPRE == mode:
|
|
2206
|
+
childtm = getprop(parent, key)
|
|
2207
|
+
|
|
2208
|
+
pkey = getelem(path, -2)
|
|
2209
|
+
tval = getprop(inj.dparent, pkey)
|
|
2210
|
+
|
|
2211
|
+
if UNDEF == tval:
|
|
2212
|
+
tval = {}
|
|
2213
|
+
elif not ismap(tval):
|
|
2214
|
+
inj.errs.append(_invalidTypeMsg(
|
|
2215
|
+
path[:-1], S_object, typify(tval), tval, 'V0220'))
|
|
2216
|
+
return UNDEF
|
|
2217
|
+
|
|
2218
|
+
ckeys = keysof(tval)
|
|
2219
|
+
for ckey in ckeys:
|
|
2220
|
+
setprop(parent, ckey, clone(childtm))
|
|
2221
|
+
keys.append(ckey)
|
|
2222
|
+
|
|
2223
|
+
inj.setval(UNDEF)
|
|
2224
|
+
return UNDEF
|
|
2225
|
+
|
|
2226
|
+
# List syntax.
|
|
2227
|
+
if S_MVAL == mode:
|
|
2228
|
+
|
|
2229
|
+
if not islist(parent):
|
|
2230
|
+
inj.errs.append('Invalid $CHILD as value')
|
|
2231
|
+
return UNDEF
|
|
2232
|
+
|
|
2233
|
+
childtm = getprop(parent, 1)
|
|
2234
|
+
|
|
2235
|
+
if UNDEF == inj.dparent:
|
|
2236
|
+
del parent[:]
|
|
2237
|
+
return UNDEF
|
|
2238
|
+
|
|
2239
|
+
if not islist(inj.dparent):
|
|
2240
|
+
msg = _invalidTypeMsg(
|
|
2241
|
+
path[:-1], S_list, typify(inj.dparent), inj.dparent, 'V0230')
|
|
2242
|
+
inj.errs.append(msg)
|
|
2243
|
+
inj.keyI = size(parent)
|
|
2244
|
+
return inj.dparent
|
|
2245
|
+
|
|
2246
|
+
for n in items(inj.dparent):
|
|
2247
|
+
setprop(parent, n[0], clone(childtm))
|
|
2248
|
+
del parent[len(inj.dparent):]
|
|
2249
|
+
inj.keyI = 0
|
|
2250
|
+
|
|
2251
|
+
out = getprop(inj.dparent, 0)
|
|
2252
|
+
return out
|
|
2253
|
+
|
|
2254
|
+
return UNDEF
|
|
2255
|
+
|
|
2256
|
+
|
|
2257
|
+
def validate_ONE(inj, _val=UNDEF, _ref=UNDEF, store=UNDEF):
|
|
2258
|
+
mode = inj.mode
|
|
2259
|
+
parent = inj.parent
|
|
2260
|
+
keyI = inj.keyI
|
|
2261
|
+
|
|
2262
|
+
if S_MVAL == mode:
|
|
2263
|
+
if not islist(parent) or 0 != keyI:
|
|
2264
|
+
inj.errs.append('The $ONE validator at field ' +
|
|
2265
|
+
pathify(inj.path, 1, 1) +
|
|
2266
|
+
' must be the first element of an array.')
|
|
2267
|
+
return None
|
|
2268
|
+
|
|
2269
|
+
inj.keyI = size(inj.keys)
|
|
2270
|
+
|
|
2271
|
+
inj.setval(inj.dparent, 2)
|
|
2272
|
+
|
|
2273
|
+
inj.path = inj.path[:-1]
|
|
2274
|
+
inj.key = getelem(inj.path, -1)
|
|
2275
|
+
|
|
2276
|
+
tvals = parent[1:]
|
|
2277
|
+
if 0 == size(tvals):
|
|
2278
|
+
inj.errs.append('The $ONE validator at field ' +
|
|
2279
|
+
pathify(inj.path, 1, 1) +
|
|
2280
|
+
' must have at least one argument.')
|
|
2281
|
+
return None
|
|
2282
|
+
|
|
2283
|
+
for tval in tvals:
|
|
2284
|
+
terrs = []
|
|
2285
|
+
|
|
2286
|
+
vstore = merge([{}, store], 1)
|
|
2287
|
+
vstore[S_DTOP] = inj.dparent
|
|
2288
|
+
|
|
2289
|
+
vcurrent = validate(inj.dparent, tval, {
|
|
2290
|
+
'extra': vstore,
|
|
2291
|
+
'errs': terrs,
|
|
2292
|
+
'meta': inj.meta,
|
|
2293
|
+
})
|
|
2294
|
+
|
|
2295
|
+
inj.setval(vcurrent, -2)
|
|
2296
|
+
|
|
2297
|
+
if 0 == size(terrs):
|
|
2298
|
+
return None
|
|
2299
|
+
|
|
2300
|
+
valdesc = ', '.join(stringify(n[1]) for n in items(tvals))
|
|
2301
|
+
valdesc = re.sub(r'`\$([A-Z]+)`', lambda m: m.group(1).lower(), valdesc)
|
|
2302
|
+
|
|
2303
|
+
inj.errs.append(_invalidTypeMsg(
|
|
2304
|
+
inj.path,
|
|
2305
|
+
('one of ' if 1 < size(tvals) else '') + valdesc,
|
|
2306
|
+
typify(inj.dparent), inj.dparent, 'V0210'))
|
|
2307
|
+
|
|
2308
|
+
|
|
2309
|
+
def validate_EXACT(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF):
|
|
2310
|
+
mode = inj.mode
|
|
2311
|
+
parent = inj.parent
|
|
2312
|
+
key = inj.key
|
|
2313
|
+
keyI = inj.keyI
|
|
2314
|
+
|
|
2315
|
+
if S_MVAL == mode:
|
|
2316
|
+
if not islist(parent) or 0 != keyI:
|
|
2317
|
+
inj.errs.append('The $EXACT validator at field ' +
|
|
2318
|
+
pathify(inj.path, 1, 1) +
|
|
2319
|
+
' must be the first element of an array.')
|
|
2320
|
+
return None
|
|
2321
|
+
|
|
2322
|
+
inj.keyI = size(inj.keys)
|
|
2323
|
+
|
|
2324
|
+
inj.setval(inj.dparent, 2)
|
|
2325
|
+
|
|
2326
|
+
inj.path = inj.path[:-1]
|
|
2327
|
+
inj.key = getelem(inj.path, -1)
|
|
2328
|
+
|
|
2329
|
+
tvals = parent[1:]
|
|
2330
|
+
if 0 == size(tvals):
|
|
2331
|
+
inj.errs.append('The $EXACT validator at field ' +
|
|
2332
|
+
pathify(inj.path, 1, 1) +
|
|
2333
|
+
' must have at least one argument.')
|
|
2334
|
+
return None
|
|
2335
|
+
|
|
2336
|
+
currentstr = None
|
|
2337
|
+
for tval in tvals:
|
|
2338
|
+
exactmatch = tval == inj.dparent
|
|
2339
|
+
|
|
2340
|
+
if not exactmatch and isnode(tval):
|
|
2341
|
+
currentstr = stringify(inj.dparent) if currentstr is None else currentstr
|
|
2342
|
+
tvalstr = stringify(tval)
|
|
2343
|
+
exactmatch = tvalstr == currentstr
|
|
2344
|
+
|
|
2345
|
+
if exactmatch:
|
|
2346
|
+
return None
|
|
2347
|
+
|
|
2348
|
+
valdesc = ', '.join(stringify(n[1]) for n in items(tvals))
|
|
2349
|
+
valdesc = re.sub(r'`\$([A-Z]+)`', lambda m: m.group(1).lower(), valdesc)
|
|
2350
|
+
|
|
2351
|
+
inj.errs.append(_invalidTypeMsg(
|
|
2352
|
+
inj.path,
|
|
2353
|
+
('' if 1 < size(inj.path) else 'value ') +
|
|
2354
|
+
'exactly equal to ' + ('' if 1 == size(tvals) else 'one of ') + valdesc,
|
|
2355
|
+
typify(inj.dparent), inj.dparent, 'V0110'))
|
|
2356
|
+
else:
|
|
2357
|
+
delprop(parent, key)
|
|
2358
|
+
|
|
2359
|
+
|
|
2360
|
+
def _validation(
|
|
2361
|
+
pval,
|
|
2362
|
+
key,
|
|
2363
|
+
parent,
|
|
2364
|
+
inj
|
|
2365
|
+
):
|
|
2366
|
+
if UNDEF == inj:
|
|
2367
|
+
return
|
|
2368
|
+
|
|
2369
|
+
if pval == SKIP:
|
|
2370
|
+
return
|
|
2371
|
+
|
|
2372
|
+
# select needs exact matches
|
|
2373
|
+
exact = getprop(inj.meta, S_BEXACT, False)
|
|
2374
|
+
|
|
2375
|
+
# Current val to verify.
|
|
2376
|
+
cval = getprop(inj.dparent, key)
|
|
2377
|
+
|
|
2378
|
+
if UNDEF == inj or (not exact and UNDEF == cval):
|
|
2379
|
+
return
|
|
2380
|
+
|
|
2381
|
+
ptype = typify(pval)
|
|
2382
|
+
|
|
2383
|
+
if 0 < (T_string & ptype) and S_DS in str(pval):
|
|
2384
|
+
return
|
|
2385
|
+
|
|
2386
|
+
ctype = typify(cval)
|
|
2387
|
+
|
|
2388
|
+
if ptype != ctype and UNDEF != pval:
|
|
2389
|
+
inj.errs.append(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0010'))
|
|
2390
|
+
return
|
|
2391
|
+
|
|
2392
|
+
if ismap(cval):
|
|
2393
|
+
if not ismap(pval):
|
|
2394
|
+
inj.errs.append(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0020'))
|
|
2395
|
+
return
|
|
2396
|
+
|
|
2397
|
+
ckeys = keysof(cval)
|
|
2398
|
+
pkeys = keysof(pval)
|
|
2399
|
+
|
|
2400
|
+
# Empty spec object {} means object can be open (any keys).
|
|
2401
|
+
if 0 < len(pkeys) and True != getprop(pval, '`$OPEN`'):
|
|
2402
|
+
badkeys = []
|
|
2403
|
+
for ckey in ckeys:
|
|
2404
|
+
if not haskey(pval, ckey):
|
|
2405
|
+
badkeys.append(ckey)
|
|
2406
|
+
if 0 < size(badkeys):
|
|
2407
|
+
msg = 'Unexpected keys at field ' + pathify(inj.path, 1) + S_VIZ + join(badkeys, ', ')
|
|
2408
|
+
inj.errs.append(msg)
|
|
2409
|
+
else:
|
|
2410
|
+
# Object is open, so merge in extra keys.
|
|
2411
|
+
merge([pval, cval])
|
|
2412
|
+
if isnode(pval):
|
|
2413
|
+
delprop(pval, '`$OPEN`')
|
|
2414
|
+
|
|
2415
|
+
elif islist(cval):
|
|
2416
|
+
if not islist(pval):
|
|
2417
|
+
inj.errs.append(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0030'))
|
|
2418
|
+
|
|
2419
|
+
elif exact:
|
|
2420
|
+
if cval != pval:
|
|
2421
|
+
pathmsg = 'at field ' + pathify(inj.path, 1) + ': ' if 1 < size(inj.path) else ''
|
|
2422
|
+
inj.errs.append('Value ' + pathmsg + str(cval) +
|
|
2423
|
+
' should equal ' + str(pval) + '.')
|
|
2424
|
+
|
|
2425
|
+
else:
|
|
2426
|
+
# Spec value was a default, copy over data
|
|
2427
|
+
setprop(parent, key, cval)
|
|
2428
|
+
|
|
2429
|
+
return
|
|
2430
|
+
|
|
2431
|
+
|
|
2432
|
+
# Validate a data structure against a shape specification. The shape
|
|
2433
|
+
# specification follows the "by example" principle. Plain data in
|
|
2434
|
+
# teh shape is treated as default values that also specify the
|
|
2435
|
+
# required type. Thus shape {a:1} validates {a:2}, since the types
|
|
2436
|
+
# (number) match, but not {a:'A'}. Shape {a;1} against data {}
|
|
2437
|
+
# returns {a:1} as a=1 is the default value of the a key. Special
|
|
2438
|
+
# validation commands (in the same syntax as transform ) are also
|
|
2439
|
+
# provided to specify required values. Thus shape {a:'`$STRING`'}
|
|
2440
|
+
# validates {a:'A'} but not {a:1}. Empty map or list means the node
|
|
2441
|
+
# is open, and if missing an empty default is inserted.
|
|
2442
|
+
def validate(data, spec, injdef=UNDEF):
|
|
2443
|
+
extra = getprop(injdef, 'extra')
|
|
2444
|
+
|
|
2445
|
+
collect = getprop(injdef, 'errs') is not None and getprop(injdef, 'errs') is not UNDEF
|
|
2446
|
+
errs = getprop(injdef, 'errs') if collect else []
|
|
2447
|
+
|
|
2448
|
+
store = merge([
|
|
2449
|
+
{
|
|
2450
|
+
"$DELETE": None,
|
|
2451
|
+
"$COPY": None,
|
|
2452
|
+
"$KEY": None,
|
|
2453
|
+
"$META": None,
|
|
2454
|
+
"$MERGE": None,
|
|
2455
|
+
"$EACH": None,
|
|
2456
|
+
"$PACK": None,
|
|
2457
|
+
|
|
2458
|
+
"$STRING": validate_STRING,
|
|
2459
|
+
"$NUMBER": validate_TYPE,
|
|
2460
|
+
"$INTEGER": validate_TYPE,
|
|
2461
|
+
"$DECIMAL": validate_TYPE,
|
|
2462
|
+
"$BOOLEAN": validate_TYPE,
|
|
2463
|
+
"$NULL": validate_TYPE,
|
|
2464
|
+
"$NIL": validate_TYPE,
|
|
2465
|
+
"$MAP": validate_TYPE,
|
|
2466
|
+
"$LIST": validate_TYPE,
|
|
2467
|
+
"$FUNCTION": validate_TYPE,
|
|
2468
|
+
"$INSTANCE": validate_TYPE,
|
|
2469
|
+
"$ANY": validate_ANY,
|
|
2470
|
+
"$CHILD": validate_CHILD,
|
|
2471
|
+
"$ONE": validate_ONE,
|
|
2472
|
+
"$EXACT": validate_EXACT,
|
|
2473
|
+
},
|
|
2474
|
+
|
|
2475
|
+
({} if extra is UNDEF or extra is None else extra),
|
|
2476
|
+
|
|
2477
|
+
{
|
|
2478
|
+
"$ERRS": errs,
|
|
2479
|
+
}
|
|
2480
|
+
], 1)
|
|
2481
|
+
|
|
2482
|
+
meta = getprop(injdef, 'meta', {})
|
|
2483
|
+
setprop(meta, S_BEXACT, getprop(meta, S_BEXACT, False))
|
|
2484
|
+
|
|
2485
|
+
out = transform(data, spec, {
|
|
2486
|
+
'meta': meta,
|
|
2487
|
+
'extra': store,
|
|
2488
|
+
'modify': _validation,
|
|
2489
|
+
'handler': _validatehandler,
|
|
2490
|
+
'errs': errs,
|
|
2491
|
+
})
|
|
2492
|
+
|
|
2493
|
+
generr = 0 < len(errs) and not collect
|
|
2494
|
+
if generr:
|
|
2495
|
+
raise ValueError(' | '.join(errs))
|
|
2496
|
+
|
|
2497
|
+
return out
|
|
2498
|
+
|
|
2499
|
+
|
|
2500
|
+
|
|
2501
|
+
# Internal utilities
|
|
2502
|
+
# ==================
|
|
2503
|
+
|
|
2504
|
+
def _validatehandler(inj, val, ref, store):
|
|
2505
|
+
out = val
|
|
2506
|
+
|
|
2507
|
+
m = R_META_PATH.match(ref) if ref else None
|
|
2508
|
+
ismetapath = m is not None
|
|
2509
|
+
|
|
2510
|
+
if ismetapath:
|
|
2511
|
+
if m.group(2) == '=':
|
|
2512
|
+
inj.setval([S_BEXACT, val])
|
|
2513
|
+
else:
|
|
2514
|
+
inj.setval(val)
|
|
2515
|
+
inj.keyI = -1
|
|
2516
|
+
|
|
2517
|
+
out = SKIP
|
|
2518
|
+
else:
|
|
2519
|
+
out = _injecthandler(inj, val, ref, store)
|
|
2520
|
+
|
|
2521
|
+
return out
|
|
2522
|
+
|
|
2523
|
+
|
|
2524
|
+
# Set state.key property of state.parent node, ensuring reference consistency
|
|
2525
|
+
# when needed by implementation language.
|
|
2526
|
+
def _setparentprop(state, val):
|
|
2527
|
+
setprop(state.parent, state.key, val)
|
|
2528
|
+
|
|
2529
|
+
|
|
2530
|
+
# Update all references to target in state.nodes.
|
|
2531
|
+
def _updateAncestors(_state, target, tkey, tval):
|
|
2532
|
+
# SetProp is sufficient in Python as target reference remains consistent even for lists.
|
|
2533
|
+
setprop(target, tkey, tval)
|
|
2534
|
+
|
|
2535
|
+
|
|
2536
|
+
# Inject values from a data store into a string. Not a public utility - used by
|
|
2537
|
+
# `inject`. Inject are marked with `path` where path is resolved
|
|
2538
|
+
# with getpath against the store or current (if defined)
|
|
2539
|
+
# arguments. See `getpath`. Custom injection handling can be
|
|
2540
|
+
# provided by state.handler (this is used for transform functions).
|
|
2541
|
+
# The path can also have the special syntax $NAME999 where NAME is
|
|
2542
|
+
# upper case letters only, and 999 is any digits, which are
|
|
2543
|
+
# discarded. This syntax specifies the name of a transform, and
|
|
2544
|
+
# optionally allows transforms to be ordered by alphanumeric sorting.
|
|
2545
|
+
def _injectstr(val, store, inj=UNDEF):
|
|
2546
|
+
# Can't inject into non-strings
|
|
2547
|
+
full_re = re.compile(r'^`(\$[A-Z]+|[^`]*)[0-9]*`$')
|
|
2548
|
+
part_re = re.compile(r'`([^`]*)`')
|
|
2549
|
+
|
|
2550
|
+
if not isinstance(val, str) or S_MT == val:
|
|
2551
|
+
return S_MT
|
|
2552
|
+
|
|
2553
|
+
out = val
|
|
2554
|
+
|
|
2555
|
+
# Pattern examples: "`a.b.c`", "`$NAME`", "`$NAME1`"
|
|
2556
|
+
m = full_re.match(val)
|
|
2557
|
+
|
|
2558
|
+
# Full string of the val is an injection.
|
|
2559
|
+
if m:
|
|
2560
|
+
if UNDEF != inj:
|
|
2561
|
+
inj.full = True
|
|
2562
|
+
|
|
2563
|
+
pathref = m.group(1)
|
|
2564
|
+
|
|
2565
|
+
# Special escapes inside injection.
|
|
2566
|
+
if 3 < len(pathref):
|
|
2567
|
+
pathref = pathref.replace(r'$BT', S_BT).replace(r'$DS', S_DS)
|
|
2568
|
+
|
|
2569
|
+
# Get the extracted path reference.
|
|
2570
|
+
out = getpath(store, pathref, inj)
|
|
2571
|
+
|
|
2572
|
+
else:
|
|
2573
|
+
|
|
2574
|
+
# Check for injections within the string.
|
|
2575
|
+
def partial(mobj):
|
|
2576
|
+
ref = mobj.group(1)
|
|
2577
|
+
|
|
2578
|
+
# Special escapes inside injection.
|
|
2579
|
+
if 3 < len(ref):
|
|
2580
|
+
ref = ref.replace(r'$BT', S_BT).replace(r'$DS', S_DS)
|
|
2581
|
+
|
|
2582
|
+
if UNDEF != inj:
|
|
2583
|
+
inj.full = False
|
|
2584
|
+
|
|
2585
|
+
found = getpath(store, ref, inj)
|
|
2586
|
+
|
|
2587
|
+
# Ensure inject value is a string.
|
|
2588
|
+
if UNDEF == found:
|
|
2589
|
+
return S_MT
|
|
2590
|
+
|
|
2591
|
+
if isinstance(found, str):
|
|
2592
|
+
# Convert test NULL marker to JSON 'null' when injecting into strings
|
|
2593
|
+
if found == '__NULL__':
|
|
2594
|
+
return 'null'
|
|
2595
|
+
return found
|
|
2596
|
+
|
|
2597
|
+
if isfunc(found):
|
|
2598
|
+
return found
|
|
2599
|
+
|
|
2600
|
+
try:
|
|
2601
|
+
return json.dumps(found, separators=(',', ':'))
|
|
2602
|
+
except (TypeError, ValueError):
|
|
2603
|
+
return stringify(found)
|
|
2604
|
+
|
|
2605
|
+
out = part_re.sub(partial, val)
|
|
2606
|
+
|
|
2607
|
+
# Also call the inj handler on the entire string, providing the
|
|
2608
|
+
# option for custom injection.
|
|
2609
|
+
if UNDEF != inj and isfunc(inj.handler):
|
|
2610
|
+
inj.full = True
|
|
2611
|
+
out = inj.handler(inj, out, val, store)
|
|
2612
|
+
|
|
2613
|
+
return out
|
|
2614
|
+
|
|
2615
|
+
|
|
2616
|
+
def _invalidTypeMsg(path, needtype, vt, v, _whence=None):
|
|
2617
|
+
vs = 'no value' if v is None or v is UNDEF else stringify(v)
|
|
2618
|
+
return (
|
|
2619
|
+
'Expected ' +
|
|
2620
|
+
('field ' + pathify(path, 1) + ' to be ' if 1 < size(path) else '') +
|
|
2621
|
+
str(needtype) + ', but found ' +
|
|
2622
|
+
(typename(vt) + S_VIZ if v is not None and v is not UNDEF else '') + vs +
|
|
2623
|
+
'.'
|
|
2624
|
+
)
|
|
2625
|
+
|
|
2626
|
+
|
|
2627
|
+
# Create a StructUtils class with all utility functions as attributes
|
|
2628
|
+
class StructUtility:
|
|
2629
|
+
def __init__(self):
|
|
2630
|
+
self.clone = clone
|
|
2631
|
+
self.delprop = delprop
|
|
2632
|
+
self.escre = escre
|
|
2633
|
+
self.escurl = escurl
|
|
2634
|
+
self.filter = filter
|
|
2635
|
+
self.flatten = flatten
|
|
2636
|
+
self.getdef = getdef
|
|
2637
|
+
self.getelem = getelem
|
|
2638
|
+
self.getpath = getpath
|
|
2639
|
+
self.getprop = getprop
|
|
2640
|
+
self.haskey = haskey
|
|
2641
|
+
self.inject = inject
|
|
2642
|
+
self.isempty = isempty
|
|
2643
|
+
self.isfunc = isfunc
|
|
2644
|
+
self.iskey = iskey
|
|
2645
|
+
self.islist = islist
|
|
2646
|
+
self.ismap = ismap
|
|
2647
|
+
self.isnode = isnode
|
|
2648
|
+
self.items = items
|
|
2649
|
+
self.jm = jm
|
|
2650
|
+
self.jt = jt
|
|
2651
|
+
self.jo = jo
|
|
2652
|
+
self.ja = ja
|
|
2653
|
+
self.join = join
|
|
2654
|
+
self.joinurl = joinurl
|
|
2655
|
+
self.jsonify = jsonify
|
|
2656
|
+
self.keysof = keysof
|
|
2657
|
+
self.merge = merge
|
|
2658
|
+
self.pad = pad
|
|
2659
|
+
self.pathify = pathify
|
|
2660
|
+
self.replace = replace
|
|
2661
|
+
self.select = select
|
|
2662
|
+
self.setpath = setpath
|
|
2663
|
+
self.setprop = setprop
|
|
2664
|
+
self.size = size
|
|
2665
|
+
self.slice = slice
|
|
2666
|
+
self.stringify = stringify
|
|
2667
|
+
self.strkey = strkey
|
|
2668
|
+
self.transform = transform
|
|
2669
|
+
self.typify = typify
|
|
2670
|
+
self.typename = typename
|
|
2671
|
+
self.validate = validate
|
|
2672
|
+
self.walk = walk
|
|
2673
|
+
|
|
2674
|
+
self.SKIP = SKIP
|
|
2675
|
+
self.DELETE = DELETE
|
|
2676
|
+
self.tn = typename
|
|
2677
|
+
|
|
2678
|
+
self.T_any = T_any
|
|
2679
|
+
self.T_noval = T_noval
|
|
2680
|
+
self.T_boolean = T_boolean
|
|
2681
|
+
self.T_decimal = T_decimal
|
|
2682
|
+
self.T_integer = T_integer
|
|
2683
|
+
self.T_number = T_number
|
|
2684
|
+
self.T_string = T_string
|
|
2685
|
+
self.T_function = T_function
|
|
2686
|
+
self.T_symbol = T_symbol
|
|
2687
|
+
self.T_null = T_null
|
|
2688
|
+
self.T_list = T_list
|
|
2689
|
+
self.T_map = T_map
|
|
2690
|
+
self.T_instance = T_instance
|
|
2691
|
+
self.T_scalar = T_scalar
|
|
2692
|
+
self.T_node = T_node
|
|
2693
|
+
|
|
2694
|
+
self.checkPlacement = checkPlacement
|
|
2695
|
+
self.injectorArgs = injectorArgs
|
|
2696
|
+
self.injectChild = injectChild
|
|
2697
|
+
|
|
2698
|
+
|
|
2699
|
+
__all__ = [
|
|
2700
|
+
'Injection',
|
|
2701
|
+
'StructUtility',
|
|
2702
|
+
'checkPlacement',
|
|
2703
|
+
'clone',
|
|
2704
|
+
'delprop',
|
|
2705
|
+
'escre',
|
|
2706
|
+
'escurl',
|
|
2707
|
+
'filter',
|
|
2708
|
+
'flatten',
|
|
2709
|
+
'getdef',
|
|
2710
|
+
'getelem',
|
|
2711
|
+
'getpath',
|
|
2712
|
+
'getprop',
|
|
2713
|
+
'haskey',
|
|
2714
|
+
'inject',
|
|
2715
|
+
'injectChild',
|
|
2716
|
+
'injectorArgs',
|
|
2717
|
+
'isempty',
|
|
2718
|
+
'isfunc',
|
|
2719
|
+
'iskey',
|
|
2720
|
+
'islist',
|
|
2721
|
+
'ismap',
|
|
2722
|
+
'isnode',
|
|
2723
|
+
'items',
|
|
2724
|
+
'ja',
|
|
2725
|
+
'jm',
|
|
2726
|
+
'jo',
|
|
2727
|
+
'join',
|
|
2728
|
+
'joinurl',
|
|
2729
|
+
'jsonify',
|
|
2730
|
+
'jt',
|
|
2731
|
+
'keysof',
|
|
2732
|
+
'merge',
|
|
2733
|
+
'pad',
|
|
2734
|
+
'pathify',
|
|
2735
|
+
'replace',
|
|
2736
|
+
'select',
|
|
2737
|
+
'setpath',
|
|
2738
|
+
'setprop',
|
|
2739
|
+
'size',
|
|
2740
|
+
'slice',
|
|
2741
|
+
'stringify',
|
|
2742
|
+
'strkey',
|
|
2743
|
+
'transform',
|
|
2744
|
+
'typename',
|
|
2745
|
+
'typify',
|
|
2746
|
+
'validate',
|
|
2747
|
+
'walk',
|
|
2748
|
+
'SKIP',
|
|
2749
|
+
'DELETE',
|
|
2750
|
+
'T_any',
|
|
2751
|
+
'T_noval',
|
|
2752
|
+
'T_boolean',
|
|
2753
|
+
'T_decimal',
|
|
2754
|
+
'T_integer',
|
|
2755
|
+
'T_number',
|
|
2756
|
+
'T_string',
|
|
2757
|
+
'T_function',
|
|
2758
|
+
'T_symbol',
|
|
2759
|
+
'T_null',
|
|
2760
|
+
'T_list',
|
|
2761
|
+
'T_map',
|
|
2762
|
+
'T_instance',
|
|
2763
|
+
'T_scalar',
|
|
2764
|
+
'T_node',
|
|
2765
|
+
'M_KEYPRE',
|
|
2766
|
+
'M_KEYPOST',
|
|
2767
|
+
'M_VAL',
|
|
2768
|
+
'MODENAME',
|
|
2769
|
+
]
|
|
2770
|
+
|