@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.
Files changed (387) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/Readme.js +5 -5
  3. package/dist/cmp/Readme.js.map +1 -1
  4. package/dist/cmp/ReadmeEntity.js +74 -24
  5. package/dist/cmp/ReadmeEntity.js.map +1 -1
  6. package/dist/cmp/ReadmeExplanation.d.ts +2 -0
  7. package/dist/cmp/ReadmeExplanation.js +308 -0
  8. package/dist/cmp/ReadmeExplanation.js.map +1 -0
  9. package/dist/cmp/ReadmeHowto.d.ts +2 -0
  10. package/dist/cmp/ReadmeHowto.js +18 -0
  11. package/dist/cmp/ReadmeHowto.js.map +1 -0
  12. package/dist/cmp/ReadmeIntro.js +8 -23
  13. package/dist/cmp/ReadmeIntro.js.map +1 -1
  14. package/dist/cmp/ReadmeModel.js +55 -91
  15. package/dist/cmp/ReadmeModel.js.map +1 -1
  16. package/dist/cmp/ReadmeOptions.js +35 -11
  17. package/dist/cmp/ReadmeOptions.js.map +1 -1
  18. package/dist/cmp/ReadmeQuick.js +4 -1
  19. package/dist/cmp/ReadmeQuick.js.map +1 -1
  20. package/dist/cmp/ReadmeRef.js +1042 -40
  21. package/dist/cmp/ReadmeRef.js.map +1 -1
  22. package/dist/cmp/ReadmeTop.d.ts +2 -0
  23. package/dist/cmp/ReadmeTop.js +171 -0
  24. package/dist/cmp/ReadmeTop.js.map +1 -0
  25. package/dist/sdkgen.d.ts +7 -1
  26. package/dist/sdkgen.js +13 -1
  27. package/dist/sdkgen.js.map +1 -1
  28. package/dist/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +2 -2
  30. package/project/.sdk/model/feature/feature-index.jsonic +5 -0
  31. package/project/.sdk/model/feature/log.jsonic +5 -1
  32. package/project/.sdk/model/feature/test.jsonic +1 -1
  33. package/project/.sdk/model/target/lua.jsonic +23 -0
  34. package/project/.sdk/model/target/php.jsonic +22 -0
  35. package/project/.sdk/model/target/py.jsonic +23 -0
  36. package/project/.sdk/model/target/rb.jsonic +23 -0
  37. package/project/.sdk/src/cmp/go/Main_go.ts +2 -6
  38. package/project/.sdk/src/cmp/go/ReadmeExplanation_go.ts +42 -0
  39. package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +112 -0
  40. package/project/.sdk/src/cmp/go/ReadmeInstall_go.ts +29 -0
  41. package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +129 -0
  42. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +163 -0
  43. package/project/.sdk/src/cmp/go/ReadmeTopHowto_go.ts +24 -0
  44. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +67 -0
  45. package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +40 -0
  46. package/project/.sdk/src/cmp/go/TestDirect_go.ts +46 -2
  47. package/project/.sdk/src/cmp/go/TestEntity_go.ts +360 -160
  48. package/project/.sdk/src/cmp/go/Test_go.ts +24 -1
  49. package/project/.sdk/src/cmp/js/ReadmeQuick_js.ts +1 -1
  50. package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +0 -4
  51. package/project/.sdk/src/cmp/lua/Config_lua.ts +112 -0
  52. package/project/.sdk/src/cmp/lua/EntityOperation_lua.ts +44 -0
  53. package/project/.sdk/src/cmp/lua/Entity_lua.ts +62 -0
  54. package/project/.sdk/src/cmp/lua/MainEntity_lua.ts +23 -0
  55. package/project/.sdk/src/cmp/lua/Main_lua.ts +133 -0
  56. package/project/.sdk/src/cmp/lua/Package_lua.ts +80 -0
  57. package/project/.sdk/src/cmp/lua/ReadmeExplanation_lua.ts +42 -0
  58. package/project/.sdk/src/cmp/lua/ReadmeHowto_lua.ts +100 -0
  59. package/project/.sdk/src/cmp/lua/ReadmeInstall_lua.ts +26 -0
  60. package/project/.sdk/src/cmp/lua/ReadmeModel_lua.ts +129 -0
  61. package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +99 -0
  62. package/project/.sdk/src/cmp/lua/ReadmeTopHowto_lua.ts +24 -0
  63. package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +55 -0
  64. package/project/.sdk/src/cmp/lua/ReadmeTopTest_lua.ts +38 -0
  65. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +261 -0
  66. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +483 -0
  67. package/project/.sdk/src/cmp/lua/Test_lua.ts +49 -0
  68. package/project/.sdk/src/cmp/lua/fragment/Entity.fragment.lua +147 -0
  69. package/project/.sdk/src/cmp/lua/fragment/EntityCreateOp.fragment.lua +27 -0
  70. package/project/.sdk/src/cmp/lua/fragment/EntityListOp.fragment.lua +24 -0
  71. package/project/.sdk/src/cmp/lua/fragment/EntityLoadOp.fragment.lua +30 -0
  72. package/project/.sdk/src/cmp/lua/fragment/EntityRemoveOp.fragment.lua +30 -0
  73. package/project/.sdk/src/cmp/lua/fragment/EntityUpdateOp.fragment.lua +30 -0
  74. package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +256 -0
  75. package/project/.sdk/src/cmp/lua/fragment/SdkError.fragment.lua +26 -0
  76. package/project/.sdk/src/cmp/lua/tsconfig.json +15 -0
  77. package/project/.sdk/src/cmp/lua/utility_lua.ts +84 -0
  78. package/project/.sdk/src/cmp/php/Config_php.ts +107 -0
  79. package/project/.sdk/src/cmp/php/EntityOperation_php.ts +44 -0
  80. package/project/.sdk/src/cmp/php/Entity_php.ts +62 -0
  81. package/project/.sdk/src/cmp/php/MainEntity_php.ts +24 -0
  82. package/project/.sdk/src/cmp/php/Main_php.ts +135 -0
  83. package/project/.sdk/src/cmp/php/Package_php.ts +87 -0
  84. package/project/.sdk/src/cmp/php/ReadmeExplanation_php.ts +42 -0
  85. package/project/.sdk/src/cmp/php/ReadmeHowto_php.ts +101 -0
  86. package/project/.sdk/src/cmp/php/ReadmeInstall_php.ts +19 -0
  87. package/project/.sdk/src/cmp/php/ReadmeModel_php.ts +129 -0
  88. package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +100 -0
  89. package/project/.sdk/src/cmp/php/ReadmeTopHowto_php.ts +24 -0
  90. package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +56 -0
  91. package/project/.sdk/src/cmp/php/ReadmeTopTest_php.ts +38 -0
  92. package/project/.sdk/src/cmp/php/TestDirect_php.ts +270 -0
  93. package/project/.sdk/src/cmp/php/TestEntity_php.ts +484 -0
  94. package/project/.sdk/src/cmp/php/Test_php.ts +56 -0
  95. package/project/.sdk/src/cmp/php/fragment/Entity.fragment.php +146 -0
  96. package/project/.sdk/src/cmp/php/fragment/EntityCreateOp.fragment.php +26 -0
  97. package/project/.sdk/src/cmp/php/fragment/EntityListOp.fragment.php +23 -0
  98. package/project/.sdk/src/cmp/php/fragment/EntityLoadOp.fragment.php +29 -0
  99. package/project/.sdk/src/cmp/php/fragment/EntityRemoveOp.fragment.php +29 -0
  100. package/project/.sdk/src/cmp/php/fragment/EntityUpdateOp.fragment.php +29 -0
  101. package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +233 -0
  102. package/project/.sdk/src/cmp/php/fragment/SdkError.fragment.php +24 -0
  103. package/project/.sdk/src/cmp/php/tsconfig.json +15 -0
  104. package/project/.sdk/src/cmp/php/utility_php.ts +85 -0
  105. package/project/.sdk/src/cmp/py/Config_py.ts +93 -0
  106. package/project/.sdk/src/cmp/py/EntityOperation_py.ts +44 -0
  107. package/project/.sdk/src/cmp/py/Entity_py.ts +62 -0
  108. package/project/.sdk/src/cmp/py/MainEntity_py.ts +22 -0
  109. package/project/.sdk/src/cmp/py/Main_py.ts +156 -0
  110. package/project/.sdk/src/cmp/py/Package_py.ts +75 -0
  111. package/project/.sdk/src/cmp/py/ReadmeExplanation_py.ts +41 -0
  112. package/project/.sdk/src/cmp/py/ReadmeHowto_py.ts +98 -0
  113. package/project/.sdk/src/cmp/py/ReadmeInstall_py.ts +25 -0
  114. package/project/.sdk/src/cmp/py/ReadmeModel_py.ts +130 -0
  115. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +100 -0
  116. package/project/.sdk/src/cmp/py/ReadmeTopHowto_py.ts +24 -0
  117. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +56 -0
  118. package/project/.sdk/src/cmp/py/ReadmeTopTest_py.ts +38 -0
  119. package/project/.sdk/src/cmp/py/TestDirect_py.ts +248 -0
  120. package/project/.sdk/src/cmp/py/TestEntity_py.ts +475 -0
  121. package/project/.sdk/src/cmp/py/Test_py.ts +55 -0
  122. package/project/.sdk/src/cmp/py/fragment/Entity.fragment.py +112 -0
  123. package/project/.sdk/src/cmp/py/fragment/EntityCreateOp.fragment.py +25 -0
  124. package/project/.sdk/src/cmp/py/fragment/EntityListOp.fragment.py +22 -0
  125. package/project/.sdk/src/cmp/py/fragment/EntityLoadOp.fragment.py +27 -0
  126. package/project/.sdk/src/cmp/py/fragment/EntityRemoveOp.fragment.py +27 -0
  127. package/project/.sdk/src/cmp/py/fragment/EntityUpdateOp.fragment.py +27 -0
  128. package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +222 -0
  129. package/project/.sdk/src/cmp/py/fragment/SdkError.fragment.py +16 -0
  130. package/project/.sdk/src/cmp/py/tsconfig.json +15 -0
  131. package/project/.sdk/src/cmp/py/utility_py.ts +84 -0
  132. package/project/.sdk/src/cmp/rb/Config_rb.ts +101 -0
  133. package/project/.sdk/src/cmp/rb/EntityOperation_rb.ts +44 -0
  134. package/project/.sdk/src/cmp/rb/Entity_rb.ts +62 -0
  135. package/project/.sdk/src/cmp/rb/MainEntity_rb.ts +23 -0
  136. package/project/.sdk/src/cmp/rb/Main_rb.ts +130 -0
  137. package/project/.sdk/src/cmp/rb/Package_rb.ts +111 -0
  138. package/project/.sdk/src/cmp/rb/ReadmeExplanation_rb.ts +42 -0
  139. package/project/.sdk/src/cmp/rb/ReadmeHowto_rb.ts +98 -0
  140. package/project/.sdk/src/cmp/rb/ReadmeInstall_rb.ts +31 -0
  141. package/project/.sdk/src/cmp/rb/ReadmeModel_rb.ts +129 -0
  142. package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +99 -0
  143. package/project/.sdk/src/cmp/rb/ReadmeTopHowto_rb.ts +24 -0
  144. package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +55 -0
  145. package/project/.sdk/src/cmp/rb/ReadmeTopTest_rb.ts +38 -0
  146. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +260 -0
  147. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +476 -0
  148. package/project/.sdk/src/cmp/rb/Test_rb.ts +50 -0
  149. package/project/.sdk/src/cmp/rb/fragment/Entity.fragment.rb +116 -0
  150. package/project/.sdk/src/cmp/rb/fragment/EntityCreateOp.fragment.rb +25 -0
  151. package/project/.sdk/src/cmp/rb/fragment/EntityListOp.fragment.rb +20 -0
  152. package/project/.sdk/src/cmp/rb/fragment/EntityLoadOp.fragment.rb +26 -0
  153. package/project/.sdk/src/cmp/rb/fragment/EntityRemoveOp.fragment.rb +26 -0
  154. package/project/.sdk/src/cmp/rb/fragment/EntityUpdateOp.fragment.rb +26 -0
  155. package/project/.sdk/src/cmp/rb/fragment/Main.fragment.rb +203 -0
  156. package/project/.sdk/src/cmp/rb/fragment/SdkError.fragment.rb +16 -0
  157. package/project/.sdk/src/cmp/rb/tsconfig.json +15 -0
  158. package/project/.sdk/src/cmp/rb/utility_rb.ts +84 -0
  159. package/project/.sdk/src/cmp/ts/ReadmeExplanation_ts.ts +34 -0
  160. package/project/.sdk/src/cmp/ts/ReadmeHowto_ts.ts +123 -0
  161. package/project/.sdk/src/cmp/ts/ReadmeInstall_ts.ts +1 -1
  162. package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +159 -0
  163. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +69 -54
  164. package/project/.sdk/src/cmp/ts/ReadmeTopHowto_ts.ts +25 -0
  165. package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +65 -0
  166. package/project/.sdk/src/cmp/ts/ReadmeTopTest_ts.ts +36 -0
  167. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +0 -3
  168. package/project/.sdk/tm/go/feature/log_feature.go +133 -0
  169. package/project/.sdk/tm/go/src/feature/log/.gitkeep +0 -0
  170. package/project/.sdk/tm/lua/LICENSE +22 -0
  171. package/project/.sdk/tm/lua/Makefile +10 -0
  172. package/project/.sdk/tm/lua/core/context.lua +208 -0
  173. package/project/.sdk/tm/lua/core/control.lua +17 -0
  174. package/project/.sdk/tm/lua/core/error.lua +30 -0
  175. package/project/.sdk/tm/lua/core/helpers.lua +30 -0
  176. package/project/.sdk/tm/lua/core/operation.lua +51 -0
  177. package/project/.sdk/tm/lua/core/response.lua +45 -0
  178. package/project/.sdk/tm/lua/core/result.lua +58 -0
  179. package/project/.sdk/tm/lua/core/spec.lua +29 -0
  180. package/project/.sdk/tm/lua/core/utility_type.lua +90 -0
  181. package/project/.sdk/tm/lua/feature/base_feature.lua +35 -0
  182. package/project/.sdk/tm/lua/feature/log_feature.lua +80 -0
  183. package/project/.sdk/tm/lua/feature/test_feature.lua +202 -0
  184. package/project/.sdk/tm/lua/src/feature/README.md +3 -0
  185. package/project/.sdk/tm/lua/src/feature/base/.gitkeep +0 -0
  186. package/project/.sdk/tm/lua/src/feature/log/.gitkeep +0 -0
  187. package/project/.sdk/tm/lua/src/feature/test/.gitkeep +0 -0
  188. package/project/.sdk/tm/lua/test/runner.lua +86 -0
  189. package/project/.sdk/tm/lua/utility/clean.lua +7 -0
  190. package/project/.sdk/tm/lua/utility/done.lua +19 -0
  191. package/project/.sdk/tm/lua/utility/feature_add.lua +8 -0
  192. package/project/.sdk/tm/lua/utility/feature_hook.lua +21 -0
  193. package/project/.sdk/tm/lua/utility/feature_init.lua +24 -0
  194. package/project/.sdk/tm/lua/utility/fetcher.lua +96 -0
  195. package/project/.sdk/tm/lua/utility/make_context.lua +9 -0
  196. package/project/.sdk/tm/lua/utility/make_error.lua +73 -0
  197. package/project/.sdk/tm/lua/utility/make_fetch_def.lua +43 -0
  198. package/project/.sdk/tm/lua/utility/make_options.lua +116 -0
  199. package/project/.sdk/tm/lua/utility/make_point.lua +92 -0
  200. package/project/.sdk/tm/lua/utility/make_request.lua +58 -0
  201. package/project/.sdk/tm/lua/utility/make_response.lua +44 -0
  202. package/project/.sdk/tm/lua/utility/make_result.lua +51 -0
  203. package/project/.sdk/tm/lua/utility/make_spec.lua +72 -0
  204. package/project/.sdk/tm/lua/utility/make_url.lua +46 -0
  205. package/project/.sdk/tm/lua/utility/param.lua +68 -0
  206. package/project/.sdk/tm/lua/utility/prepare_auth.lua +39 -0
  207. package/project/.sdk/tm/lua/utility/prepare_body.lua +14 -0
  208. package/project/.sdk/tm/lua/utility/prepare_headers.lua +20 -0
  209. package/project/.sdk/tm/lua/utility/prepare_method.lua +17 -0
  210. package/project/.sdk/tm/lua/utility/prepare_params.lua +36 -0
  211. package/project/.sdk/tm/lua/utility/prepare_path.lua +19 -0
  212. package/project/.sdk/tm/lua/utility/prepare_query.lua +41 -0
  213. package/project/.sdk/tm/lua/utility/register.lua +71 -0
  214. package/project/.sdk/tm/lua/utility/result_basic.lua +32 -0
  215. package/project/.sdk/tm/lua/utility/result_body.lua +17 -0
  216. package/project/.sdk/tm/lua/utility/result_headers.lua +22 -0
  217. package/project/.sdk/tm/lua/utility/struct/struct.lua +3417 -0
  218. package/project/.sdk/tm/lua/utility/transform_request.lua +31 -0
  219. package/project/.sdk/tm/lua/utility/transform_response.lua +44 -0
  220. package/project/.sdk/tm/php/LICENSE +22 -0
  221. package/project/.sdk/tm/php/Makefile +10 -0
  222. package/project/.sdk/tm/php/core/Context.php +139 -0
  223. package/project/.sdk/tm/php/core/Control.php +18 -0
  224. package/project/.sdk/tm/php/core/Error.php +37 -0
  225. package/project/.sdk/tm/php/core/Helpers.php +25 -0
  226. package/project/.sdk/tm/php/core/Operation.php +36 -0
  227. package/project/.sdk/tm/php/core/Response.php +30 -0
  228. package/project/.sdk/tm/php/core/Result.php +35 -0
  229. package/project/.sdk/tm/php/core/Spec.php +38 -0
  230. package/project/.sdk/tm/php/core/UtilityType.php +89 -0
  231. package/project/.sdk/tm/php/feature/BaseFeature.php +37 -0
  232. package/project/.sdk/tm/php/feature/LogFeature.php +65 -0
  233. package/project/.sdk/tm/php/feature/TestFeature.php +193 -0
  234. package/project/.sdk/tm/php/src/feature/README.md +3 -0
  235. package/project/.sdk/tm/php/src/feature/base/.gitkeep +0 -0
  236. package/project/.sdk/tm/php/src/feature/log/.gitkeep +0 -0
  237. package/project/.sdk/tm/php/src/feature/test/.gitkeep +0 -0
  238. package/project/.sdk/tm/php/test/Runner.php +89 -0
  239. package/project/.sdk/tm/php/utility/Clean.php +12 -0
  240. package/project/.sdk/tm/php/utility/Done.php +26 -0
  241. package/project/.sdk/tm/php/utility/FeatureAdd.php +12 -0
  242. package/project/.sdk/tm/php/utility/FeatureHook.php +23 -0
  243. package/project/.sdk/tm/php/utility/FeatureInit.php +25 -0
  244. package/project/.sdk/tm/php/utility/Fetcher.php +101 -0
  245. package/project/.sdk/tm/php/utility/MakeContext.php +14 -0
  246. package/project/.sdk/tm/php/utility/MakeError.php +59 -0
  247. package/project/.sdk/tm/php/utility/MakeFetchDef.php +36 -0
  248. package/project/.sdk/tm/php/utility/MakeOptions.php +98 -0
  249. package/project/.sdk/tm/php/utility/MakePoint.php +87 -0
  250. package/project/.sdk/tm/php/utility/MakeRequest.php +57 -0
  251. package/project/.sdk/tm/php/utility/MakeResponse.php +43 -0
  252. package/project/.sdk/tm/php/utility/MakeResult.php +50 -0
  253. package/project/.sdk/tm/php/utility/MakeSpec.php +64 -0
  254. package/project/.sdk/tm/php/utility/MakeUrl.php +41 -0
  255. package/project/.sdk/tm/php/utility/Param.php +66 -0
  256. package/project/.sdk/tm/php/utility/PrepareAuth.php +33 -0
  257. package/project/.sdk/tm/php/utility/PrepareBody.php +15 -0
  258. package/project/.sdk/tm/php/utility/PrepareHeaders.php +18 -0
  259. package/project/.sdk/tm/php/utility/PrepareMethod.php +21 -0
  260. package/project/.sdk/tm/php/utility/PrepareParams.php +34 -0
  261. package/project/.sdk/tm/php/utility/PreparePath.php +20 -0
  262. package/project/.sdk/tm/php/utility/PrepareQuery.php +32 -0
  263. package/project/.sdk/tm/php/utility/Register.php +67 -0
  264. package/project/.sdk/tm/php/utility/ResultBasic.php +29 -0
  265. package/project/.sdk/tm/php/utility/ResultBody.php +17 -0
  266. package/project/.sdk/tm/php/utility/ResultHeaders.php +21 -0
  267. package/project/.sdk/tm/php/utility/TransformRequest.php +27 -0
  268. package/project/.sdk/tm/php/utility/TransformResponse.php +42 -0
  269. package/project/.sdk/tm/php/utility/struct/Struct.php +3431 -0
  270. package/project/.sdk/tm/py/Makefile +10 -0
  271. package/project/.sdk/tm/py/core/__init__.py +0 -0
  272. package/project/.sdk/tm/py/core/context.py +199 -0
  273. package/project/.sdk/tm/py/core/control.py +12 -0
  274. package/project/.sdk/tm/py/core/error.py +18 -0
  275. package/project/.sdk/tm/py/core/helpers.py +15 -0
  276. package/project/.sdk/tm/py/core/operation.py +37 -0
  277. package/project/.sdk/tm/py/core/response.py +34 -0
  278. package/project/.sdk/tm/py/core/result.py +44 -0
  279. package/project/.sdk/tm/py/core/spec.py +23 -0
  280. package/project/.sdk/tm/py/core/utility_type.py +82 -0
  281. package/project/.sdk/tm/py/entity/__init__.py +0 -0
  282. package/project/.sdk/tm/py/feature/__init__.py +0 -0
  283. package/project/.sdk/tm/py/feature/base_feature.py +61 -0
  284. package/project/.sdk/tm/py/feature/log_feature.py +84 -0
  285. package/project/.sdk/tm/py/feature/test_feature.py +164 -0
  286. package/project/.sdk/tm/py/src/feature/README.md +3 -0
  287. package/project/.sdk/tm/py/src/feature/base/.gitkeep +0 -0
  288. package/project/.sdk/tm/py/src/feature/log/.gitkeep +0 -0
  289. package/project/.sdk/tm/py/src/feature/test/.gitkeep +0 -0
  290. package/project/.sdk/tm/py/test/__init__.py +0 -0
  291. package/project/.sdk/tm/py/test/runner.py +77 -0
  292. package/project/.sdk/tm/py/utility/__init__.py +0 -0
  293. package/project/.sdk/tm/py/utility/clean.py +5 -0
  294. package/project/.sdk/tm/py/utility/done.py +14 -0
  295. package/project/.sdk/tm/py/utility/feature_add.py +6 -0
  296. package/project/.sdk/tm/py/utility/feature_hook.py +15 -0
  297. package/project/.sdk/tm/py/utility/feature_init.py +18 -0
  298. package/project/.sdk/tm/py/utility/fetcher.py +95 -0
  299. package/project/.sdk/tm/py/utility/make_context.py +7 -0
  300. package/project/.sdk/tm/py/utility/make_error.py +64 -0
  301. package/project/.sdk/tm/py/utility/make_fetch_def.py +37 -0
  302. package/project/.sdk/tm/py/utility/make_options.py +103 -0
  303. package/project/.sdk/tm/py/utility/make_point.py +74 -0
  304. package/project/.sdk/tm/py/utility/make_request.py +52 -0
  305. package/project/.sdk/tm/py/utility/make_response.py +36 -0
  306. package/project/.sdk/tm/py/utility/make_result.py +41 -0
  307. package/project/.sdk/tm/py/utility/make_spec.py +68 -0
  308. package/project/.sdk/tm/py/utility/make_url.py +34 -0
  309. package/project/.sdk/tm/py/utility/param.py +55 -0
  310. package/project/.sdk/tm/py/utility/prepare_auth.py +34 -0
  311. package/project/.sdk/tm/py/utility/prepare_body.py +11 -0
  312. package/project/.sdk/tm/py/utility/prepare_headers.py +17 -0
  313. package/project/.sdk/tm/py/utility/prepare_method.py +15 -0
  314. package/project/.sdk/tm/py/utility/prepare_params.py +28 -0
  315. package/project/.sdk/tm/py/utility/prepare_path.py +16 -0
  316. package/project/.sdk/tm/py/utility/prepare_query.py +33 -0
  317. package/project/.sdk/tm/py/utility/register.py +68 -0
  318. package/project/.sdk/tm/py/utility/result_basic.py +26 -0
  319. package/project/.sdk/tm/py/utility/result_body.py +13 -0
  320. package/project/.sdk/tm/py/utility/result_headers.py +17 -0
  321. package/project/.sdk/tm/py/utility/transform_request.py +27 -0
  322. package/project/.sdk/tm/py/utility/transform_response.py +39 -0
  323. package/project/.sdk/tm/py/utility/voxgig_struct/__init__.py +72 -0
  324. package/project/.sdk/tm/py/utility/voxgig_struct/voxgig_struct.py +2770 -0
  325. package/project/.sdk/tm/rb/Gemfile +4 -0
  326. package/project/.sdk/tm/rb/LICENSE +22 -0
  327. package/project/.sdk/tm/rb/Makefile +10 -0
  328. package/project/.sdk/tm/rb/core/context.rb +105 -0
  329. package/project/.sdk/tm/rb/core/control.rb +11 -0
  330. package/project/.sdk/tm/rb/core/error.rb +24 -0
  331. package/project/.sdk/tm/rb/core/helpers.rb +16 -0
  332. package/project/.sdk/tm/rb/core/operation.rb +26 -0
  333. package/project/.sdk/tm/rb/core/response.rb +20 -0
  334. package/project/.sdk/tm/rb/core/result.rb +23 -0
  335. package/project/.sdk/tm/rb/core/spec.rb +23 -0
  336. package/project/.sdk/tm/rb/core/utility_type.rb +32 -0
  337. package/project/.sdk/tm/rb/feature/base_feature.rb +30 -0
  338. package/project/.sdk/tm/rb/feature/log_feature.rb +50 -0
  339. package/project/.sdk/tm/rb/feature/test_feature.rb +154 -0
  340. package/project/.sdk/tm/rb/src/feature/README.md +3 -0
  341. package/project/.sdk/tm/rb/src/feature/base/.gitkeep +0 -0
  342. package/project/.sdk/tm/rb/src/feature/log/.gitkeep +0 -0
  343. package/project/.sdk/tm/rb/src/feature/test/.gitkeep +0 -0
  344. package/project/.sdk/tm/rb/test/runner.rb +65 -0
  345. package/project/.sdk/tm/rb/utility/clean.rb +4 -0
  346. package/project/.sdk/tm/rb/utility/done.rb +14 -0
  347. package/project/.sdk/tm/rb/utility/feature_add.rb +6 -0
  348. package/project/.sdk/tm/rb/utility/feature_hook.rb +11 -0
  349. package/project/.sdk/tm/rb/utility/feature_init.rb +16 -0
  350. package/project/.sdk/tm/rb/utility/fetcher.rb +67 -0
  351. package/project/.sdk/tm/rb/utility/make_context.rb +7 -0
  352. package/project/.sdk/tm/rb/utility/make_error.rb +44 -0
  353. package/project/.sdk/tm/rb/utility/make_fetch_def.rb +24 -0
  354. package/project/.sdk/tm/rb/utility/make_options.rb +57 -0
  355. package/project/.sdk/tm/rb/utility/make_point.rb +77 -0
  356. package/project/.sdk/tm/rb/utility/make_request.rb +44 -0
  357. package/project/.sdk/tm/rb/utility/make_response.rb +25 -0
  358. package/project/.sdk/tm/rb/utility/make_result.rb +33 -0
  359. package/project/.sdk/tm/rb/utility/make_spec.rb +50 -0
  360. package/project/.sdk/tm/rb/utility/make_url.rb +32 -0
  361. package/project/.sdk/tm/rb/utility/param.rb +48 -0
  362. package/project/.sdk/tm/rb/utility/prepare_auth.rb +26 -0
  363. package/project/.sdk/tm/rb/utility/prepare_body.rb +6 -0
  364. package/project/.sdk/tm/rb/utility/prepare_headers.rb +11 -0
  365. package/project/.sdk/tm/rb/utility/prepare_method.rb +5 -0
  366. package/project/.sdk/tm/rb/utility/prepare_params.rb +25 -0
  367. package/project/.sdk/tm/rb/utility/prepare_path.rb +13 -0
  368. package/project/.sdk/tm/rb/utility/prepare_query.rb +22 -0
  369. package/project/.sdk/tm/rb/utility/register.rb +63 -0
  370. package/project/.sdk/tm/rb/utility/result_basic.rb +23 -0
  371. package/project/.sdk/tm/rb/utility/result_body.rb +11 -0
  372. package/project/.sdk/tm/rb/utility/result_headers.rb +15 -0
  373. package/project/.sdk/tm/rb/utility/struct/voxgig_struct.rb +2256 -0
  374. package/project/.sdk/tm/rb/utility/transform_request.rb +15 -0
  375. package/project/.sdk/tm/rb/utility/transform_response.rb +23 -0
  376. package/src/cmp/Readme.ts +5 -5
  377. package/src/cmp/ReadmeEntity.ts +77 -25
  378. package/src/cmp/ReadmeExplanation.ts +333 -0
  379. package/src/cmp/ReadmeHowto.ts +28 -0
  380. package/src/cmp/ReadmeIntro.ts +10 -24
  381. package/src/cmp/ReadmeModel.ts +57 -88
  382. package/src/cmp/ReadmeOptions.ts +40 -11
  383. package/src/cmp/ReadmeQuick.ts +4 -1
  384. package/src/cmp/ReadmeRef.ts +1057 -40
  385. package/src/cmp/ReadmeTop.ts +213 -0
  386. package/src/sdkgen.ts +12 -0
  387. 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
+