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