@voxgig/sdkgen 4.8.2 → 4.8.3

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 (376) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/action/doctor.d.ts +1 -0
  3. package/dist/action/doctor.js +50 -2
  4. package/dist/action/doctor.js.map +1 -1
  5. package/dist/helpers/naming.js +4 -2
  6. package/dist/helpers/naming.js.map +1 -1
  7. package/dist/tsconfig.tsbuildinfo +1 -1
  8. package/model/sdkgen.aon +21 -0
  9. package/package.json +1 -1
  10. package/project/.sdk/model/feature/secrets.aon +120 -18
  11. package/project/.sdk/model/target/c.aon +10 -0
  12. package/project/.sdk/model/target/clojure.aon +13 -0
  13. package/project/.sdk/model/target/cpp.aon +10 -0
  14. package/project/.sdk/model/target/csharp.aon +9 -0
  15. package/project/.sdk/model/target/dart.aon +10 -0
  16. package/project/.sdk/model/target/elixir.aon +9 -0
  17. package/project/.sdk/model/target/go.aon +13 -0
  18. package/project/.sdk/model/target/java.aon +9 -0
  19. package/project/.sdk/model/target/js.aon +7 -0
  20. package/project/.sdk/model/target/kotlin.aon +9 -0
  21. package/project/.sdk/model/target/lean.aon +17 -0
  22. package/project/.sdk/model/target/lua.aon +8 -0
  23. package/project/.sdk/model/target/ocaml.aon +9 -0
  24. package/project/.sdk/model/target/perl.aon +8 -0
  25. package/project/.sdk/model/target/php.aon +8 -0
  26. package/project/.sdk/model/target/py.aon +13 -0
  27. package/project/.sdk/model/target/rb.aon +8 -0
  28. package/project/.sdk/model/target/rust.aon +10 -0
  29. package/project/.sdk/model/target/scala.aon +21 -2
  30. package/project/.sdk/model/target/swift.aon +18 -0
  31. package/project/.sdk/model/target/ts.aon +13 -0
  32. package/project/.sdk/src/cmp/clojure/Package_clojure.ts +9 -2
  33. package/project/.sdk/src/cmp/cpp/Gitignore_cpp.ts +3 -0
  34. package/project/.sdk/src/cmp/dart/Test_dart.ts +2 -0
  35. package/project/.sdk/src/cmp/elixir/Package_elixir.ts +4 -1
  36. package/project/.sdk/src/cmp/go/Config_go.ts +59 -2
  37. package/project/.sdk/src/cmp/go/Main_go.ts +8 -2
  38. package/project/.sdk/src/cmp/java/Test_java.ts +6 -5
  39. package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +6 -5
  40. package/project/.sdk/src/cmp/lean/Package_lean.ts +23 -0
  41. package/project/.sdk/src/cmp/lean/ReadmeInstall_lean.ts +1 -0
  42. package/project/.sdk/src/cmp/lean/ReadmeTopTest_lean.ts +1 -0
  43. package/project/.sdk/src/cmp/ocaml/Gitignore_ocaml.ts +1 -0
  44. package/project/.sdk/src/cmp/ocaml/Main_ocaml.ts +7 -3
  45. package/project/.sdk/src/cmp/py/Config_py.ts +82 -1
  46. package/project/.sdk/src/cmp/py/Main_py.ts +44 -0
  47. package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +2 -0
  48. package/project/.sdk/src/cmp/swift/Main_swift.ts +10 -0
  49. package/project/.sdk/src/cmp/swift/Package_swift.ts +13 -1
  50. package/project/.sdk/src/cmp/ts/Config_ts.ts +42 -17
  51. package/project/.sdk/src/cmp/ts/fragment/Config.data.fragment.ts +13 -1
  52. package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +13 -1
  53. package/project/.sdk/tm/c/Makefile +38 -3
  54. package/project/.sdk/tm/c/tests/omni_resolver.h +361 -0
  55. package/project/.sdk/tm/c/tests/omni_smoke_test.c +237 -0
  56. package/project/.sdk/tm/c/tests/primary_corpus_test.c +205 -284
  57. package/project/.sdk/tm/c/tests/struct_corpus_test.c +292 -212
  58. package/project/.sdk/tm/c/tests/vendor/omni/json.c +811 -0
  59. package/project/.sdk/tm/c/tests/vendor/omni/omni.h +240 -0
  60. package/project/.sdk/tm/c/tests/vendor/omni/runner.c +940 -0
  61. package/project/.sdk/tm/c/tests/vendor/omni/util.c +351 -0
  62. package/project/.sdk/tm/c/utility/struct/utility.c +7 -2
  63. package/project/.sdk/tm/clojure/test/sdk/test/omni.clj +401 -0
  64. package/project/.sdk/tm/clojure/test/sdk/test/omni_smoke.clj +81 -0
  65. package/project/.sdk/tm/clojure/test/sdk/test/primary.clj +92 -58
  66. package/project/.sdk/tm/clojure/test/sdk/test/struct_corpus.clj +122 -149
  67. package/project/.sdk/tm/clojure/test/sdk/test_runner.clj +8 -3
  68. package/project/.sdk/tm/clojure/test/vendor/omni/voxgig/omni/json.clj +145 -0
  69. package/project/.sdk/tm/clojure/test/vendor/omni/voxgig/omni/runner.clj +445 -0
  70. package/project/.sdk/tm/clojure/test/vendor/omni/voxgig/omni/util.clj +145 -0
  71. package/project/.sdk/tm/cpp/Makefile +5 -1
  72. package/project/.sdk/tm/cpp/test/omni_resolver.hpp +473 -0
  73. package/project/.sdk/tm/cpp/test/omni_smoke_test.cpp +209 -0
  74. package/project/.sdk/tm/cpp/test/primary_utility_test.cpp +243 -184
  75. package/project/.sdk/tm/cpp/test/runner_support.hpp +68 -181
  76. package/project/.sdk/tm/cpp/test/struct_corpus_test.cpp +183 -56
  77. package/project/.sdk/tm/cpp/test/vendor/omni/json.hpp +406 -0
  78. package/project/.sdk/tm/cpp/test/vendor/omni/omni.hpp +751 -0
  79. package/project/.sdk/tm/cpp/test/vendor/omni/util.hpp +256 -0
  80. package/project/.sdk/tm/cpp/utility/pipeline.hpp +7 -1
  81. package/project/.sdk/tm/csharp/test/OmniResolver.cs +491 -0
  82. package/project/.sdk/tm/csharp/test/OmniSmokeTest.cs +106 -0
  83. package/project/.sdk/tm/csharp/test/PrimaryUtilityTest.cs +299 -316
  84. package/project/.sdk/tm/csharp/test/Runner.cs +104 -269
  85. package/project/.sdk/tm/csharp/test/StructUtilityTest.cs +535 -724
  86. package/project/.sdk/tm/csharp/test/vendor/omni/Runner.cs +864 -0
  87. package/project/.sdk/tm/csharp/test/vendor/omni/Util.cs +347 -0
  88. package/project/.sdk/tm/csharp/utility/MakeSpec.cs +3 -1
  89. package/project/.sdk/tm/csharp/utility/PrepareMethod.cs +6 -1
  90. package/project/.sdk/tm/csharp/utility/struct/Struct.cs +21 -5
  91. package/project/.sdk/tm/dart/test/omni.dart +520 -0
  92. package/project/.sdk/tm/dart/test/omni_smoke_test.dart +143 -0
  93. package/project/.sdk/tm/dart/test/primary_test.dart +86 -28
  94. package/project/.sdk/tm/dart/test/struct_test.dart +438 -11
  95. package/project/.sdk/tm/dart/test/vendor/omni/omni.dart +7 -0
  96. package/project/.sdk/tm/dart/test/vendor/omni/runner.dart +653 -0
  97. package/project/.sdk/tm/dart/test/vendor/omni/util.dart +232 -0
  98. package/project/.sdk/tm/elixir/Makefile +11 -3
  99. package/project/.sdk/tm/elixir/test/omni_smoke_test.exs +117 -0
  100. package/project/.sdk/tm/elixir/test/primary_utility_test.exs +341 -16
  101. package/project/.sdk/tm/elixir/test/struct_corpus_test.exs +554 -9
  102. package/project/.sdk/tm/elixir/test/support/omni.ex +377 -0
  103. package/project/.sdk/tm/elixir/test/vendor/omni/json.ex +122 -0
  104. package/project/.sdk/tm/elixir/test/vendor/omni/runner.ex +623 -0
  105. package/project/.sdk/tm/elixir/test/vendor/omni/util.ex +187 -0
  106. package/project/.sdk/tm/go/core/context.go +32 -16
  107. package/project/.sdk/tm/go/feature/secrets/plugin/capability.go +261 -0
  108. package/project/.sdk/tm/go/feature/secrets/plugin/catalog.go +78 -0
  109. package/project/.sdk/tm/go/feature/secrets/plugin/config.go +652 -0
  110. package/project/.sdk/tm/go/feature/secrets/plugin/depend.go +250 -0
  111. package/project/.sdk/tm/go/feature/secrets/plugin/env.go +194 -0
  112. package/project/.sdk/tm/go/feature/secrets/plugin/export.go +80 -0
  113. package/project/.sdk/tm/go/feature/secrets/plugin/graph.go +238 -0
  114. package/project/.sdk/tm/go/feature/secrets/plugin/host.go +1575 -0
  115. package/project/.sdk/tm/go/feature/secrets/plugin/order.go +248 -0
  116. package/project/.sdk/tm/go/feature/secrets/plugin/point.go +184 -0
  117. package/project/.sdk/tm/go/feature/secrets/plugin/ref.go +123 -0
  118. package/project/.sdk/tm/go/feature/secrets/plugin/resolve.go +79 -0
  119. package/project/.sdk/tm/go/feature/secrets/plugin/types.go +296 -0
  120. package/project/.sdk/tm/go/feature/secrets/plugin/util.go +87 -0
  121. package/project/.sdk/tm/go/feature/secrets/plugin/version.go +161 -0
  122. package/project/.sdk/tm/go/feature/secrets/plugins/aws/aws.go +292 -0
  123. package/project/.sdk/tm/go/feature/secrets/plugins/aws/sigv4.go +201 -0
  124. package/project/.sdk/tm/go/feature/secrets/plugins/azuresecrets/azuresecrets.go +187 -0
  125. package/project/.sdk/tm/go/feature/secrets/plugins/boru/boru.go +184 -0
  126. package/project/.sdk/tm/go/feature/secrets/plugins/doppler/doppler.go +114 -0
  127. package/project/.sdk/tm/go/feature/secrets/plugins/gcpsecrets/gcpsecrets.go +156 -0
  128. package/project/.sdk/tm/go/feature/secrets/plugins/hashicorp/hashicorp.go +209 -0
  129. package/project/.sdk/tm/go/feature/secrets/plugins/httpjson/httpjson.go +241 -0
  130. package/project/.sdk/tm/go/feature/secrets/plugins/infisical/infisical.go +151 -0
  131. package/project/.sdk/tm/go/feature/secrets/plugins/onepassword/onepassword.go +152 -0
  132. package/project/.sdk/tm/go/feature/secrets/plugins/secretspec/secretspec.go +143 -0
  133. package/project/.sdk/tm/go/feature/secrets/sekreto/addr.go +120 -0
  134. package/project/.sdk/tm/go/feature/secrets/sekreto/providers.go +439 -0
  135. package/project/.sdk/tm/go/feature/secrets/sekreto/sekreto.go +685 -0
  136. package/project/.sdk/tm/go/feature/secrets_feature.go +675 -0
  137. package/project/.sdk/tm/go/src/feature/secrets/.gitkeep +0 -0
  138. package/project/.sdk/tm/go/test/feature/secrets/secrets_feature_test.go +799 -0
  139. package/project/.sdk/tm/go/test/omni/omni.go +897 -0
  140. package/project/.sdk/tm/go/test/omni/util.go +360 -0
  141. package/project/.sdk/tm/go/test/omniresolver_test.go +748 -0
  142. package/project/.sdk/tm/go/test/omnismoke_test.go +112 -0
  143. package/project/.sdk/tm/go/test/primary_utility_test.go +123 -191
  144. package/project/.sdk/tm/go/test/struct_utility_test.go +323 -16
  145. package/project/.sdk/tm/go/test/testsupport_test.go +294 -0
  146. package/project/.sdk/tm/go/utility/make_options.go +8 -1
  147. package/project/.sdk/tm/go/utility/prepare_method.go +5 -1
  148. package/project/.sdk/tm/go/utility/struct/voxgigstruct.go +2 -2
  149. package/project/.sdk/tm/java/test/OmniResolver.java +355 -0
  150. package/project/.sdk/tm/java/test/OmniSmokeTest.java +99 -0
  151. package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +254 -280
  152. package/project/.sdk/tm/java/test/RunnerSupport.java +9 -194
  153. package/project/.sdk/tm/java/test/StructCorpusTest.java +119 -42
  154. package/project/.sdk/tm/java/test/vendor/omni/Json.java +287 -0
  155. package/project/.sdk/tm/java/test/vendor/omni/Runner.java +851 -0
  156. package/project/.sdk/tm/java/test/vendor/omni/Util.java +322 -0
  157. package/project/.sdk/tm/java/utility/MakeSpec.java +3 -1
  158. package/project/.sdk/tm/java/utility/PrepareMethod.java +5 -5
  159. package/project/.sdk/tm/java/utility/struct/Struct.java +17 -4
  160. package/project/.sdk/tm/js/src/utility/StructUtility.js +3 -3
  161. package/project/.sdk/tm/js/test/omni.js +251 -0
  162. package/project/.sdk/tm/js/test/omni.test.js +105 -0
  163. package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +6 -2
  164. package/project/.sdk/tm/js/test/utility/StructUtility.test.js +27 -2
  165. package/project/.sdk/tm/js/test/vendor/omni/index.js +77 -0
  166. package/project/.sdk/tm/js/test/vendor/omni/runner.js +613 -0
  167. package/project/.sdk/tm/js/test/vendor/omni/util.js +212 -0
  168. package/project/.sdk/tm/kotlin/core/Utility.kt +1 -1
  169. package/project/.sdk/tm/kotlin/test/OmniResolver.kt +343 -0
  170. package/project/.sdk/tm/kotlin/test/OmniSmokeTest.kt +100 -0
  171. package/project/.sdk/tm/kotlin/test/PrimaryUtilityTest.kt +244 -262
  172. package/project/.sdk/tm/kotlin/test/RunnerSupport.kt +9 -178
  173. package/project/.sdk/tm/kotlin/test/StructCorpusTest.kt +121 -32
  174. package/project/.sdk/tm/kotlin/test/vendor/omni/Json.kt +264 -0
  175. package/project/.sdk/tm/kotlin/test/vendor/omni/Runner.kt +615 -0
  176. package/project/.sdk/tm/kotlin/test/vendor/omni/Util.kt +139 -0
  177. package/project/.sdk/tm/kotlin/utility/MakeSpecUrl.kt +6 -3
  178. package/project/.sdk/tm/kotlin/utility/Prepare.kt +6 -6
  179. package/project/.sdk/tm/kotlin/utility/struct/Struct.kt +18 -2
  180. package/project/.sdk/tm/lean/Makefile +12 -5
  181. package/project/.sdk/tm/lean/src/SdkUtility.lean +14 -1
  182. package/project/.sdk/tm/lean/test/OmniResolver.lean +358 -0
  183. package/project/.sdk/tm/lean/test/OmniSmoke.lean +174 -0
  184. package/project/.sdk/tm/lean/test/StructCorpus.lean +245 -574
  185. package/project/.sdk/tm/lean/test/TPrimaryUtility.lean +155 -291
  186. package/project/.sdk/tm/lean/test/vendor/omni/Omni.lean +997 -0
  187. package/project/.sdk/tm/lua/test/omni.lua +456 -0
  188. package/project/.sdk/tm/lua/test/omni_smoke_test.lua +103 -0
  189. package/project/.sdk/tm/lua/test/primary_utility_test.lua +250 -509
  190. package/project/.sdk/tm/lua/test/struct_utility_test.lua +161 -70
  191. package/project/.sdk/tm/lua/test/vendor/omni/json.lua +268 -0
  192. package/project/.sdk/tm/lua/test/vendor/omni/regex.lua +385 -0
  193. package/project/.sdk/tm/lua/test/vendor/omni/runner.lua +641 -0
  194. package/project/.sdk/tm/lua/test/vendor/omni/util.lua +282 -0
  195. package/project/.sdk/tm/lua/utility/prepare_method.lua +5 -1
  196. package/project/.sdk/tm/lua/utility/struct/regex.lua +663 -0
  197. package/project/.sdk/tm/lua/utility/struct/struct.lua +660 -420
  198. package/project/.sdk/tm/ocaml/Makefile +37 -12
  199. package/project/.sdk/tm/ocaml/test/omni_resolver.ml +284 -0
  200. package/project/.sdk/tm/ocaml/test/omni_smoke_test.ml +169 -0
  201. package/project/.sdk/tm/ocaml/test/primary_utility_test.ml +84 -100
  202. package/project/.sdk/tm/ocaml/test/struct_corpus.ml +225 -201
  203. package/project/.sdk/tm/ocaml/test/vendor/omni/omni.ml +1157 -0
  204. package/project/.sdk/tm/perl/lib/Voxgig/Struct.pm +88 -12
  205. package/project/.sdk/tm/perl/t/omni.pm +647 -0
  206. package/project/.sdk/tm/perl/t/omni_smoke.t +87 -0
  207. package/project/.sdk/tm/perl/t/primary_utility.t +189 -487
  208. package/project/.sdk/tm/perl/t/struct_utility.t +455 -389
  209. package/project/.sdk/tm/perl/t/vendor/omni/Voxgig/Omni/Runner.pm +649 -0
  210. package/project/.sdk/tm/perl/t/vendor/omni/Voxgig/Omni/Util.pm +253 -0
  211. package/project/.sdk/tm/perl/t/vendor/omni/Voxgig/Omni.pm +24 -0
  212. package/project/.sdk/tm/perl/utility/prepare_method.pm +7 -2
  213. package/project/.sdk/tm/php/test/Omni.php +691 -0
  214. package/project/.sdk/tm/php/test/OmniSmokeTest.php +93 -0
  215. package/project/.sdk/tm/php/test/PrimaryUtilityTest.php +210 -571
  216. package/project/.sdk/tm/php/test/StructUtilityTest.php +320 -342
  217. package/project/.sdk/tm/php/test/vendor/omni/Runner.php +644 -0
  218. package/project/.sdk/tm/php/test/vendor/omni/Util.php +283 -0
  219. package/project/.sdk/tm/php/utility/MakeSpec.php +10 -3
  220. package/project/.sdk/tm/php/utility/PrepareBody.php +7 -1
  221. package/project/.sdk/tm/php/utility/PrepareMethod.php +6 -2
  222. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/__init__.py +36 -0
  223. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/capability.py +141 -0
  224. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/catalog.py +50 -0
  225. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/config.py +333 -0
  226. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/depend.py +198 -0
  227. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/env.py +147 -0
  228. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/export.py +52 -0
  229. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/graph.py +139 -0
  230. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/host.py +1053 -0
  231. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/order.py +171 -0
  232. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/point.py +95 -0
  233. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/ref.py +116 -0
  234. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/resolve.py +59 -0
  235. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/types.py +87 -0
  236. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/version.py +112 -0
  237. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/__init__.py +74 -0
  238. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/addr.py +116 -0
  239. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/aws.py +192 -0
  240. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/azuresecrets.py +120 -0
  241. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/boru.py +140 -0
  242. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/doppler.py +62 -0
  243. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/gcpsecrets.py +101 -0
  244. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/hashicorp.py +152 -0
  245. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/httpjson.py +115 -0
  246. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/infisical.py +92 -0
  247. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/onepassword.py +101 -0
  248. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/secretspec.py +119 -0
  249. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/sigv4.py +125 -0
  250. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/providers.py +211 -0
  251. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/sekreto.py +455 -0
  252. package/project/.sdk/tm/py/pkg/feature/secrets_feature.py +467 -0
  253. package/project/.sdk/tm/py/pkg/utility/prepare_method.py +5 -1
  254. package/project/.sdk/tm/py/pkg/utility/voxgig_struct/__init__.py +0 -2
  255. package/project/.sdk/tm/py/pkg/utility/voxgig_struct/voxgig_struct.py +780 -529
  256. package/project/.sdk/tm/py/src/feature/secrets/.gitkeep +0 -0
  257. package/project/.sdk/tm/py/test/feature/__init__.py +0 -0
  258. package/project/.sdk/tm/py/test/feature/secrets/__init__.py +0 -0
  259. package/project/.sdk/tm/py/test/feature/secrets/test_secrets.py +337 -0
  260. package/project/.sdk/tm/py/test/omni.py +415 -0
  261. package/project/.sdk/tm/py/test/test_omni_smoke.py +74 -0
  262. package/project/.sdk/tm/py/test/test_primary_utility.py +144 -668
  263. package/project/.sdk/tm/py/test/test_struct_utility.py +74 -27
  264. package/project/.sdk/tm/py/test/voxgig_omni/__init__.py +62 -0
  265. package/project/.sdk/tm/py/test/voxgig_omni/runner.py +566 -0
  266. package/project/.sdk/tm/py/test/voxgig_omni/util.py +237 -0
  267. package/project/.sdk/tm/rb/test/omni.rb +505 -0
  268. package/project/.sdk/tm/rb/test/omni_smoke_test.rb +72 -0
  269. package/project/.sdk/tm/rb/test/primary_utility_test.rb +174 -560
  270. package/project/.sdk/tm/rb/test/struct_utility_test.rb +13 -7
  271. package/project/.sdk/tm/rb/test/vendor/omni/runner.rb +493 -0
  272. package/project/.sdk/tm/rb/test/vendor/omni/util.rb +199 -0
  273. package/project/.sdk/tm/rb/test/vendor/omni/voxgig_omni.rb +15 -0
  274. package/project/.sdk/tm/rb/utility/prepare_method.rb +5 -1
  275. package/project/.sdk/tm/rust/tests/common/mod.rs +19 -242
  276. package/project/.sdk/tm/rust/tests/omni_resolver/mod.rs +888 -0
  277. package/project/.sdk/tm/rust/tests/omni_smoke_test.rs +286 -0
  278. package/project/.sdk/tm/rust/tests/primary_utility_test.rs +318 -312
  279. package/project/.sdk/tm/rust/tests/struct_utility_test.rs +66 -28
  280. package/project/.sdk/tm/rust/tests/vendor/omni/json.rs +412 -0
  281. package/project/.sdk/tm/rust/tests/vendor/omni/mod.rs +38 -0
  282. package/project/.sdk/tm/rust/tests/vendor/omni/regex.rs +392 -0
  283. package/project/.sdk/tm/rust/tests/vendor/omni/runner.rs +1001 -0
  284. package/project/.sdk/tm/rust/tests/vendor/omni/util.rs +161 -0
  285. package/project/.sdk/tm/rust/utility/prepare_method.rs +5 -1
  286. package/project/.sdk/tm/scala/Makefile +16 -6
  287. package/project/.sdk/tm/scala/sdktest/OmniResolver.scala +497 -0
  288. package/project/.sdk/tm/scala/sdktest/OmniSmoke.scala +192 -0
  289. package/project/.sdk/tm/scala/sdktest/PrimaryCorpus.scala +47 -19
  290. package/project/.sdk/tm/scala/sdktest/StructCorpus.scala +0 -0
  291. package/project/.sdk/tm/scala/sdktest/vendor/omni/Json.scala +229 -0
  292. package/project/.sdk/tm/scala/sdktest/vendor/omni/Runner.scala +552 -0
  293. package/project/.sdk/tm/scala/sdktest/vendor/omni/Util.scala +104 -0
  294. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/Struct/Constants.swift +10 -0
  295. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/Struct/Inject.swift +28 -5
  296. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/Struct/Injection.swift +5 -1
  297. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/Struct/Minor.swift +4 -1
  298. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/Struct/Validate.swift +15 -5
  299. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +6 -1
  300. package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/OmniResolver.swift +474 -0
  301. package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/OmniSmokeTest.swift +107 -0
  302. package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/PrimaryUtilityTest.swift +141 -178
  303. package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/Runner.swift +17 -113
  304. package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/StructUtilityTest.swift +295 -446
  305. package/project/.sdk/tm/swift/Tests/vendor/omni/Json.swift +364 -0
  306. package/project/.sdk/tm/swift/Tests/vendor/omni/Runner.swift +730 -0
  307. package/project/.sdk/tm/swift/Tests/vendor/omni/Util.swift +162 -0
  308. package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +7 -0
  309. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Capability.ts +148 -0
  310. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Catalog.ts +55 -0
  311. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Config.ts +344 -0
  312. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Depend.ts +205 -0
  313. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Env.ts +138 -0
  314. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Export.ts +46 -0
  315. package/project/.sdk/tm/ts/src/feature/secrets/plugin/FeatureHost.ts +349 -0
  316. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Graph.ts +181 -0
  317. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Host.ts +1132 -0
  318. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Order.ts +177 -0
  319. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Point.ts +119 -0
  320. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Ref.ts +102 -0
  321. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Resolve.ts +61 -0
  322. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Types.ts +112 -0
  323. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Version.ts +100 -0
  324. package/project/.sdk/tm/ts/src/feature/secrets/plugin/index.ts +37 -0
  325. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +163 -30
  326. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +27 -22
  327. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/aws.ts +26 -36
  328. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/azuresecrets.ts +19 -21
  329. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/boru.ts +36 -17
  330. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/doppler.ts +14 -21
  331. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/gcpsecrets.ts +27 -27
  332. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/hashicorp.ts +25 -39
  333. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/plugins/httpjson.ts +99 -0
  334. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/infisical.ts +14 -16
  335. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/onepassword.ts +15 -22
  336. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/plugins/secretspec.ts +98 -0
  337. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{Sigv4.ts → plugins/sigv4.ts} +7 -6
  338. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/addr.ts +97 -23
  339. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/builtin.ts +37 -0
  340. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/dotenv.ts +4 -17
  341. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/env.ts +4 -16
  342. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/file.ts +11 -25
  343. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/memory.ts +6 -23
  344. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/support.ts +106 -39
  345. package/project/.sdk/tm/ts/src/utility/StructUtility.ts +3 -3
  346. package/project/.sdk/tm/ts/test/omni.ts +104 -32
  347. package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +27 -0
  348. package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +32 -5
  349. package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +2 -2
  350. package/project/.sdk/tm/ts/test/vendor/omni/index.ts +2 -2
  351. package/project/.sdk/tm/zig/test/primary_utility_test.zig +1 -1
  352. package/project/.sdk/tm/zig/test/struct_runner.zig +5 -4
  353. package/project/sdkgen-package.json +1 -1
  354. package/src/action/doctor.ts +66 -2
  355. package/src/helpers/naming.ts +4 -2
  356. package/project/.sdk/tm/c/tests/runner.h +0 -274
  357. package/project/.sdk/tm/cpp/test/struct_runner.hpp +0 -314
  358. package/project/.sdk/tm/csharp/test/StructRunner.cs +0 -276
  359. package/project/.sdk/tm/dart/test/runner.dart +0 -406
  360. package/project/.sdk/tm/dart/test/struct_corpus.dart +0 -531
  361. package/project/.sdk/tm/elixir/test/support/struct_corpus.ex +0 -1088
  362. package/project/.sdk/tm/go/test/runner_test.go +0 -629
  363. package/project/.sdk/tm/go/test/struct_runner_test.go +0 -1094
  364. package/project/.sdk/tm/java/test/StructRunner.java +0 -281
  365. package/project/.sdk/tm/js/test/runner.js +0 -387
  366. package/project/.sdk/tm/kotlin/test/StructRunner.kt +0 -177
  367. package/project/.sdk/tm/lua/test/struct_runner.lua +0 -602
  368. package/project/.sdk/tm/ocaml/test/corpus_runner.ml +0 -185
  369. package/project/.sdk/tm/perl/t/struct_runner.pm +0 -275
  370. package/project/.sdk/tm/php/test/StructRunner.php +0 -275
  371. package/project/.sdk/tm/py/test/struct_runner.py +0 -411
  372. package/project/.sdk/tm/rb/test/struct_runner.rb +0 -309
  373. package/project/.sdk/tm/rust/tests/struct_runner/mod.rs +0 -424
  374. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/Registry.ts +0 -90
  375. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/http.ts +0 -66
  376. package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +0 -188
@@ -1,3 +1,6 @@
1
+ # VENDORED: @voxgig/struct 0.1.1 (python/voxgig_struct/voxgig_struct.py)
2
+ # Source: https://github.com/voxgig/struct @ 2caf7f448f265144c18dd6fab6ba270a7f3bca07 [tag: sdk-20260904-1610-0]
3
+ # License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
1
4
  # Copyright (c) 2025 Voxgig Ltd. MIT LICENSE.
2
5
  #
3
6
  # Voxgig Struct
@@ -34,23 +37,23 @@
34
37
  # - joinurl: join parts of a url, merging forward slashes.
35
38
 
36
39
 
37
- from typing import *
38
- from datetime import datetime
39
- import urllib.parse
40
+ import inspect
40
41
  import json
41
- import re
42
42
  import math
43
- import inspect
43
+ import re
44
+ import urllib.parse
45
+ from datetime import datetime
46
+ from typing import Any
44
47
 
45
48
  # Regex patterns for path processing
46
49
  R_META_PATH = re.compile(r'^([^$]+)\$([=~])(.+)$') # Meta path syntax.
47
- R_DOUBLE_DOLLAR = re.compile(r'\$\$') # Double dollar escape sequence.
50
+ R_DOUBLE_DOLLAR = re.compile(r'\$\$') # Double dollar escape sequence.
48
51
 
49
52
  # Mode value for inject step.
50
- S_MKEYPRE = 'key:pre'
51
- S_MKEYPOST = 'key:post'
52
- S_MVAL = 'val'
53
- S_MKEY = 'key'
53
+ S_MKEYPRE = 'key:pre'
54
+ S_MKEYPOST = 'key:post'
55
+ S_MVAL = 'val'
56
+ S_MKEY = 'key'
54
57
 
55
58
  M_KEYPRE = 1
56
59
  M_KEYPOST = 2
@@ -60,18 +63,18 @@ _PLACEMENT = {M_VAL: 'value', M_KEYPRE: S_MKEY, M_KEYPOST: S_MKEY}
60
63
  MODENAME = {M_VAL: 'val', M_KEYPRE: 'key:pre', M_KEYPOST: 'key:post'}
61
64
 
62
65
  # Special keys.
63
- S_DKEY = '$KEY'
64
- S_BANNO = '`$ANNO`'
65
- S_DTOP = '$TOP'
66
- S_DERRS = '$ERRS'
67
- S_DSPEC = '$SPEC'
68
- S_BMETA = 'meta'
69
- S_BEXACT = '`$EXACT`'
66
+ S_DKEY = '$KEY'
67
+ S_BANNO = '`$ANNO`'
68
+ S_DTOP = '$TOP'
69
+ S_DERRS = '$ERRS'
70
+ S_DSPEC = '$SPEC'
71
+ S_BMETA = 'meta'
72
+ S_BEXACT = '`$EXACT`'
70
73
  S_BVAL = '`$VAL`'
71
74
  S_BKEY = '`$KEY`'
72
75
 
73
76
  # General strings.
74
- S_array = 'array'
77
+ S_array = 'array'
75
78
  S_integer = 'integer'
76
79
  S_decimal = 'decimal'
77
80
  S_map = 'map'
@@ -81,23 +84,23 @@ S_instance = 'instance'
81
84
  S_node = 'node'
82
85
  S_scalar = 'scalar'
83
86
  S_any = 'any'
84
- S_base = 'base'
85
- S_boolean = 'boolean'
86
- S_function = 'function'
87
- S_number = 'number'
88
- S_object = 'object'
89
- S_string = 'string'
90
- S_null = 'null'
91
- S_key = 'key'
92
- S_parent = 'parent'
93
- S_MT = ''
94
- S_BT = '`'
95
- S_DS = '$'
96
- S_DT = '.'
97
- S_CM = ','
98
- S_CN = ':'
99
- S_FS = '/'
100
- S_KEY = 'KEY'
87
+ S_base = 'base'
88
+ S_boolean = 'boolean'
89
+ S_function = 'function'
90
+ S_number = 'number'
91
+ S_object = 'object'
92
+ S_string = 'string'
93
+ S_null = 'null'
94
+ S_key = 'key'
95
+ S_parent = 'parent'
96
+ S_MT = ''
97
+ S_BT = '`'
98
+ S_DS = '$'
99
+ S_DT = '.'
100
+ S_CM = ','
101
+ S_CN = ':'
102
+ S_FS = '/'
103
+ S_KEY = 'KEY'
101
104
 
102
105
 
103
106
  # Type bit flags (mirroring TypeScript)
@@ -131,12 +134,20 @@ TYPENAME = [
131
134
  S_function,
132
135
  'symbol',
133
136
  S_null,
134
- '', '', '',
135
- '', '', '', '',
137
+ '',
138
+ '',
139
+ '',
140
+ '',
141
+ '',
142
+ '',
143
+ '',
136
144
  S_list,
137
145
  S_map,
138
146
  S_instance,
139
- '', '', '', '',
147
+ '',
148
+ '',
149
+ '',
150
+ '',
140
151
  S_scalar,
141
152
  S_node,
142
153
  ]
@@ -144,32 +155,48 @@ TYPENAME = [
144
155
  S_VIZ = ': '
145
156
 
146
157
  # The standard undefined value for this language.
147
- UNDEF = None
158
+ UNDEF: Any = None
148
159
  SKIP = {'`$SKIP`': True}
149
160
  DELETE = {'`$DELETE`': True}
150
161
 
151
162
 
163
+ # Python collapses the canonical "undefined" (TS `undefined`) and JSON `null`
164
+ # both onto None, which loses a distinction the corpus depends on (e.g.
165
+ # stringify(null) is "null" but stringify(undefined) is ""). _ABSENT is a
166
+ # private sentinel used ONLY as a default argument value so a function can tell
167
+ # "no argument supplied" (undefined) apart from an explicit None (JSON null).
168
+ class _Absent:
169
+ __slots__ = ()
170
+
171
+ def __repr__(self):
172
+ return '<absent>'
173
+
174
+
175
+ _ABSENT = _Absent()
176
+
177
+
152
178
  class Injection:
153
179
  """
154
180
  Injection state used for recursive injection into JSON-like data structures.
155
181
  """
182
+
156
183
  def __init__(
157
184
  self,
158
- mode: str, # Injection mode: key:pre, val, key:post.
159
- full: bool, # Transform escape was full key name.
160
- keyI: int, # Index of parent key in list of parent keys.
161
- keys: List[str], # List of parent keys.
162
- key: str, # Current parent key.
163
- val: Any, # Current child value.
164
- parent: Any, # Current parent (in transform specification).
165
- path: List[str], # Path to current node.
166
- nodes: List[Any], # Stack of ancestor nodes
167
- handler: Any, # Custom handler for injections.
168
- errs: List[Any] = None, # Error collector.
169
- meta: Dict[str, Any] = None, # Custom meta data.
170
- base: Optional[str] = None, # Base key for data in store, if any.
171
- modify: Optional[Any] = None, # Modify injection output.
172
- extra: Optional[Any] = None # Extra data for injection.
185
+ mode: str, # Injection mode: key:pre, val, key:post.
186
+ full: bool, # Transform escape was full key name.
187
+ keyI: int, # Index of parent key in list of parent keys.
188
+ keys: list[str], # List of parent keys.
189
+ key: str, # Current parent key.
190
+ val: Any, # Current child value.
191
+ parent: Any, # Current parent (in transform specification).
192
+ path: list[str], # Path to current node.
193
+ nodes: list[Any], # Stack of ancestor nodes
194
+ handler: Any, # Custom handler for injections.
195
+ errs: list[Any] = UNDEF, # Error collector.
196
+ meta: dict[str, Any] = UNDEF, # Custom meta data.
197
+ base: str | None = None, # Base key for data in store, if any.
198
+ modify: Any | None = None, # Modify injection output.
199
+ extra: Any | None = None, # Extra data for injection.
173
200
  ) -> None:
174
201
  self.mode = mode
175
202
  self.full = full
@@ -186,10 +213,12 @@ class Injection:
186
213
  self.base = base
187
214
  self.modify = modify
188
215
  self.extra = extra
189
- self.prior = None
216
+ self.prior: Injection | None = None
190
217
  self.dparent = UNDEF
191
218
  self.dpath = [S_DTOP]
192
- self.root = None # Virtual root parent; set at top level so we can return it after transforms
219
+ self.root = (
220
+ None # Virtual root parent; set at top level so we can return it after transforms
221
+ )
193
222
 
194
223
  def descend(self):
195
224
  if '__d' not in self.meta:
@@ -199,8 +228,8 @@ class Injection:
199
228
  parentkey = getelem(self.path, -2)
200
229
 
201
230
  if self.dparent is UNDEF:
202
- if 1 < size(self.dpath):
203
- self.dpath = self.dpath + [parentkey]
231
+ if size(self.dpath) > 1:
232
+ self.dpath = [*self.dpath, parentkey]
204
233
  else:
205
234
  if parentkey is not None:
206
235
  self.dparent = getprop(self.dparent, parentkey)
@@ -209,15 +238,15 @@ class Injection:
209
238
  if lastpart == '$:' + str(parentkey):
210
239
  self.dpath = slice(self.dpath, -1)
211
240
  else:
212
- self.dpath = self.dpath + [parentkey]
241
+ self.dpath = [*self.dpath, parentkey]
213
242
 
214
243
  return self.dparent
215
244
 
216
- def child(self, keyI: int, keys: List[str]) -> 'Injection':
245
+ def child(self, keyI: int, keys: list[str]) -> 'Injection':
217
246
  """Create a child state object with the given key index and keys."""
218
247
  key = strkey(keys[keyI])
219
248
  val = self.val
220
-
249
+
221
250
  cinj = Injection(
222
251
  mode=self.mode,
223
252
  full=self.full,
@@ -226,28 +255,40 @@ class Injection:
226
255
  key=key,
227
256
  val=getprop(val, key),
228
257
  parent=val,
229
- path=self.path + [key],
230
- nodes=self.nodes + [val],
258
+ path=[*self.path, key],
259
+ nodes=[*self.nodes, val],
231
260
  handler=self.handler,
232
261
  errs=self.errs,
233
262
  meta=self.meta,
234
263
  base=self.base,
235
- modify=self.modify
264
+ modify=self.modify,
236
265
  )
237
266
  cinj.prior = self
238
267
  cinj.dpath = self.dpath[:]
239
268
  cinj.dparent = self.dparent
240
269
  cinj.extra = self.extra # Preserve extra (contains transform functions)
241
270
  cinj.root = getattr(self, 'root', None)
242
-
271
+
243
272
  return cinj
244
273
 
245
- def setval(self, val: Any, ancestor: Optional[int] = None) -> Any:
246
- """Set the value in the parent node at the specified ancestor level."""
274
+ def setval(self, val: Any, ancestor: int | None = None) -> Any:
275
+ """Inject-state setter. Mirrors the canonical TS Injection.setval:
276
+ val of UNDEF (= None in Python — distinguishability is impossible
277
+ in any of the dynamic-typed ports anyway) deletes the slot, any
278
+ other value sets it. setprop itself remains a pure assignment per
279
+ the Group B rule — this delete-on-undef shortcut lives only here
280
+ so transform/validate injectors can signal "no value at this slot"
281
+ through their return value.
282
+ """
247
283
  if ancestor is None or ancestor < 2:
248
- return setprop(self.parent, self.key, val)
284
+ target = self.parent
285
+ key = self.key
249
286
  else:
250
- return setprop(getelem(self.nodes, 0 - ancestor), getelem(self.path, 0 - ancestor), val)
287
+ target = getelem(self.nodes, 0 - ancestor)
288
+ key = getelem(self.path, 0 - ancestor)
289
+ if val is None:
290
+ return delprop(target, key)
291
+ return setprop(target, key, val)
251
292
 
252
293
 
253
294
  def getdef(val, alt):
@@ -281,9 +322,7 @@ def iskey(key: Any = UNDEF) -> bool:
281
322
  return False
282
323
  if isinstance(key, int):
283
324
  return True
284
- if isinstance(key, float):
285
- return True
286
- return False
325
+ return isinstance(key, float)
287
326
 
288
327
 
289
328
  def size(val: Any = UNDEF) -> int:
@@ -294,7 +333,7 @@ def size(val: Any = UNDEF) -> int:
294
333
  return len(val)
295
334
  elif ismap(val):
296
335
  return len(val.keys())
297
-
336
+
298
337
  if isinstance(val, str):
299
338
  return len(val)
300
339
  elif isinstance(val, (int, float)):
@@ -309,16 +348,17 @@ def size(val: Any = UNDEF) -> int:
309
348
 
310
349
  def slice(val: Any, start: int = UNDEF, end: int = UNDEF, mutate: bool = False) -> Any:
311
350
  """Return a part of a list, string, or clamp a number"""
312
- # Handle numbers - acts like clamp function
313
- if isinstance(val, (int, float)):
351
+ # Handle numbers - acts like clamp function.
352
+ # bool is a subclass of int in python, but `typeof true` is not
353
+ # "number" in the canonical TypeScript, so a boolean must fall through
354
+ # to the container path and be returned unchanged.
355
+ if isinstance(val, (int, float)) and not isinstance(val, bool):
314
356
  if start is None:
315
357
  start = float('-inf')
316
- if end is None:
317
- end = float('inf')
318
- else:
319
- end = end - 1 # TypeScript uses exclusive end, so subtract 1
358
+ # TypeScript uses exclusive end, so subtract 1 when bounded.
359
+ end = float('inf') if end is None else end - 1
320
360
  return max(start, min(val, end))
321
-
361
+
322
362
  if islist(val) or isinstance(val, str):
323
363
  vlen = size(val)
324
364
  if end is not None and start is None:
@@ -342,13 +382,13 @@ def slice(val: Any, start: int = UNDEF, end: int = UNDEF, mutate: bool = False)
342
382
  if vlen < start:
343
383
  start = vlen
344
384
 
345
- if -1 < start and start <= end and end <= vlen:
385
+ if start > -1 and start <= end and end <= vlen:
346
386
  if islist(val) and mutate:
347
387
  j = start
348
388
  for i in range(end - start):
349
389
  val[i] = val[j]
350
390
  j += 1
351
- del val[end - start:]
391
+ del val[end - start :]
352
392
  return val
353
393
  return val[start:end]
354
394
  else:
@@ -356,7 +396,7 @@ def slice(val: Any, start: int = UNDEF, end: int = UNDEF, mutate: bool = False)
356
396
  if mutate:
357
397
  del val[:]
358
398
  return val if mutate else []
359
- return ""
399
+ return ''
360
400
 
361
401
  # No slice performed; return original value unchanged
362
402
  return val
@@ -364,10 +404,13 @@ def slice(val: Any, start: int = UNDEF, end: int = UNDEF, mutate: bool = False)
364
404
 
365
405
  def pad(s: Any, padding: int = UNDEF, padchar: str = UNDEF) -> str:
366
406
  """Pad a string to a specified length"""
367
- s = stringify(s)
407
+ # Mirror TS canonical: stringify(null) is "null", distinct from
408
+ # stringify(undefined) which is "". Python's UNDEF=None conflates the
409
+ # two; explicitly coerce JSON-null to the literal string "null" first.
410
+ s = 'null' if s is None else stringify(s)
368
411
  padding = 44 if padding is UNDEF else padding
369
412
  padchar = ' ' if padchar is UNDEF else (padchar + ' ')[0]
370
-
413
+
371
414
  if padding > -1:
372
415
  return s.ljust(padding, padchar)
373
416
  else:
@@ -375,7 +418,7 @@ def pad(s: Any, padding: int = UNDEF, padchar: str = UNDEF) -> str:
375
418
 
376
419
 
377
420
  def strkey(key: Any = UNDEF) -> str:
378
- if UNDEF == key:
421
+ if key == UNDEF:
379
422
  return S_MT
380
423
 
381
424
  if isinstance(key, str):
@@ -395,19 +438,16 @@ def strkey(key: Any = UNDEF) -> str:
395
438
 
396
439
  def isempty(val: Any = UNDEF) -> bool:
397
440
  "Check for an 'empty' value - None, empty string, array, object."
398
- if UNDEF == val:
441
+ if val == UNDEF:
399
442
  return True
400
-
443
+
401
444
  if val == S_MT:
402
445
  return True
403
-
446
+
404
447
  if islist(val) and len(val) == 0:
405
448
  return True
406
-
407
- if ismap(val) and len(val) == 0:
408
- return True
409
-
410
- return False
449
+
450
+ return ismap(val) and len(val) == 0
411
451
 
412
452
 
413
453
  def isfunc(val: Any = UNDEF) -> bool:
@@ -439,6 +479,7 @@ def typify(value: Any = _TYPIFY_NO_ARG) -> int:
439
479
  return T_scalar | T_number | T_integer
440
480
  if isinstance(value, float):
441
481
  import math
482
+
442
483
  if math.isnan(value):
443
484
  return T_noval
444
485
  return T_scalar | T_number | T_decimal
@@ -455,12 +496,12 @@ def typify(value: Any = _TYPIFY_NO_ARG) -> int:
455
496
 
456
497
  def getelem(val: Any, key: Any, alt: Any = UNDEF) -> Any:
457
498
  """
458
- Get a list element. The key should be an integer, or a string
459
- that can parse to an integer only. Negative integers count from the end of the list.
499
+ Get a list element. Group A semantics: a stored None at the slot
500
+ returns alt, just like an out-of-range index.
460
501
  """
461
502
  out = UNDEF
462
503
 
463
- if UNDEF == val or UNDEF == key:
504
+ if val is UNDEF or key is UNDEF:
464
505
  return alt
465
506
 
466
507
  if islist(val):
@@ -473,45 +514,65 @@ def getelem(val: Any, key: Any, alt: Any = UNDEF) -> Any:
473
514
  except (ValueError, IndexError):
474
515
  pass
475
516
 
476
- if UNDEF == out:
477
- return alt() if 0 < (T_function & typify(alt)) else alt
517
+ if out is UNDEF or out is None:
518
+ return alt() if (T_function & typify(alt)) > 0 else alt
478
519
 
479
520
  return out
480
521
 
481
522
 
482
523
  def getprop(val: Any = UNDEF, key: Any = UNDEF, alt: Any = UNDEF) -> Any:
483
524
  """
484
- Safely get a property of a node. Undefined arguments return undefined.
485
- If the key is not found, return the alternative value.
525
+ Safely get a property of a node. Group A semantics (per design/UNDEF_SPEC.md):
526
+ a stored None is treated as "no value" and returns alt. Internal callers
527
+ that need to inspect the raw stored value at a slot (preserving null)
528
+ use _lookup.
486
529
  """
487
- if UNDEF == val:
488
- return alt
489
-
490
- if UNDEF == key:
530
+ if val is UNDEF or key is UNDEF:
491
531
  return alt
492
532
 
493
533
  out = alt
494
-
534
+
495
535
  if ismap(val):
496
- out = val.get(str(key), alt)
497
-
536
+ skey = str(key)
537
+ if skey in val:
538
+ out = val[skey]
539
+
498
540
  elif islist(val):
499
541
  try:
500
- key = int(key)
501
- except:
542
+ ki = int(key)
543
+ except (ValueError, TypeError):
502
544
  return alt
503
545
 
504
- if 0 <= key < len(val):
505
- return val[key]
506
- else:
507
- return alt
546
+ if 0 <= ki < len(val):
547
+ out = val[ki]
508
548
 
509
- if UNDEF == out:
549
+ if out is None:
510
550
  return alt
511
-
551
+
512
552
  return out
513
553
 
514
554
 
555
+ def _lookup(val: Any, key: Any) -> Any:
556
+ """Internal raw lookup: returns the literally-stored value (including
557
+ None for JSON null), or UNDEF when the slot doesn't exist. Used by
558
+ Group B callers that must distinguish a null slot from an absent one
559
+ while still using a unified test runner."""
560
+ if val is UNDEF or key is UNDEF:
561
+ return UNDEF
562
+ if ismap(val):
563
+ skey = str(key)
564
+ return val.get(skey, UNDEF)
565
+ if islist(val):
566
+ try:
567
+ ki = int(key)
568
+ except (ValueError, TypeError):
569
+ return UNDEF
570
+ if 0 <= ki < len(val):
571
+ return val[ki]
572
+ return UNDEF
573
+ return UNDEF
574
+
575
+
515
576
  def keysof(val: Any = UNDEF) -> list[str]:
516
577
  "Sorted keys of a map, or indexes of a list."
517
578
  if not isnode(val):
@@ -523,10 +584,11 @@ def keysof(val: Any = UNDEF) -> list[str]:
523
584
 
524
585
 
525
586
  def haskey(val: Any = UNDEF, key: Any = UNDEF) -> bool:
526
- "Value of property with name key in node val is defined."
527
- return UNDEF != getprop(val, key)
587
+ "Value of property with name key in node val is defined (and not None)."
588
+ out = getprop(val, key)
589
+ return out is not UNDEF and out is not None
590
+
528
591
 
529
-
530
592
  def items(val: Any = UNDEF, apply=None):
531
593
  "List the keys of a map or list as an array of [key, value] tuples."
532
594
  if not isnode(val):
@@ -536,7 +598,7 @@ def items(val: Any = UNDEF, apply=None):
536
598
  if apply is not None:
537
599
  out = [apply(item) for item in out]
538
600
  return out
539
-
601
+
540
602
 
541
603
  def flatten(lst, depth=None):
542
604
  if depth is None:
@@ -564,26 +626,104 @@ def filter(val, check):
564
626
 
565
627
  def escre(s: Any):
566
628
  "Escape regular expression."
567
- if UNDEF == s:
568
- s = ""
629
+ if s == UNDEF:
630
+ s = ''
569
631
  pattern = r'([.*+?^${}()|\[\]\\])'
570
632
  return re.sub(pattern, r'\\\1', s)
571
633
 
572
634
 
635
+ # ---------------------------------------------------------------------------
636
+ # Regex utility — uniform re_* API (see /design/REGEX_API.md). The Python port
637
+ # wraps the stdlib `re` module; the dialect is the RE2 subset.
638
+ # ---------------------------------------------------------------------------
639
+
640
+
641
+ def re_compile(pattern, flags=0):
642
+ "Compile a regex (or return as-is if already a compiled pattern)."
643
+ if hasattr(pattern, 'pattern'):
644
+ return pattern
645
+ return re.compile(pattern, flags)
646
+
647
+
648
+ def re_find(pattern, input):
649
+ "First match. Returns [whole, capture1, ...] or None."
650
+ rx = pattern if hasattr(pattern, 'search') else re.compile(pattern)
651
+ m = rx.search(input)
652
+ if not m:
653
+ return None
654
+ return [m.group(0)] + [g if g is not None else '' for g in m.groups()]
655
+
656
+
657
+ def re_find_all(pattern, input):
658
+ "All non-overlapping matches."
659
+ rx = pattern if hasattr(pattern, 'finditer') else re.compile(pattern)
660
+ out = []
661
+ for m in rx.finditer(input):
662
+ out.append([m.group(0)] + [g if g is not None else '' for g in m.groups()])
663
+ return out
664
+
665
+
666
+ def re_replace(pattern, input, replacement):
667
+ "Replace every match. `replacement` may be a string with $&/$1 or a callable."
668
+ rx = pattern if hasattr(pattern, 'sub') else re.compile(pattern)
669
+ if callable(replacement):
670
+
671
+ def _cb(m):
672
+ return replacement([m.group(0)] + [g if g is not None else '' for g in m.groups()])
673
+
674
+ return rx.sub(_cb, input)
675
+ # Translate $& and $1..$9 to Python's \g<0>/\1..\9
676
+ py_repl = []
677
+ i = 0
678
+ while i < len(replacement):
679
+ c = replacement[i]
680
+ if c == '$' and i + 1 < len(replacement):
681
+ nxt = replacement[i + 1]
682
+ if nxt == '&':
683
+ py_repl.append('\\g<0>')
684
+ i += 2
685
+ continue
686
+ if nxt.isdigit():
687
+ py_repl.append('\\' + nxt)
688
+ i += 2
689
+ continue
690
+ if nxt == '$':
691
+ py_repl.append('$')
692
+ i += 2
693
+ continue
694
+ if c == '\\':
695
+ py_repl.append('\\\\')
696
+ else:
697
+ py_repl.append(c)
698
+ i += 1
699
+ return rx.sub(''.join(py_repl), input)
700
+
701
+
702
+ def re_test(pattern, input):
703
+ "Boolean test."
704
+ rx = pattern if hasattr(pattern, 'search') else re.compile(pattern)
705
+ return rx.search(input) is not None
706
+
707
+
708
+ def re_escape(s):
709
+ "Alias of escre."
710
+ return escre(s)
711
+
712
+
573
713
  def escurl(s: Any):
574
714
  "Escape URLs."
575
- if UNDEF == s:
715
+ if s == UNDEF:
576
716
  s = S_MT
577
- return urllib.parse.quote(s, safe="")
717
+ return urllib.parse.quote(s, safe='')
578
718
 
579
719
 
580
720
  def replace(s, from_pat, to_str):
581
721
  "Replace a search string (all), or a regexp, in a source string."
582
722
  rs = s
583
723
  ts = typify(s)
584
- if 0 == (T_string & ts):
724
+ if (T_string & ts) == 0:
585
725
  rs = stringify(s)
586
- elif 0 < ((T_noval | T_null) & ts):
726
+ elif ((T_noval | T_null) & ts) > 0:
587
727
  rs = S_MT
588
728
  else:
589
729
  rs = stringify(s)
@@ -597,27 +737,27 @@ def join(arr, sep=UNDEF, url=UNDEF):
597
737
  if not islist(arr):
598
738
  return S_MT
599
739
  sepdef = S_CM if sep is UNDEF or sep is None else sep
600
- sepre = escre(sepdef) if 1 == size(sepdef) else UNDEF
740
+ sepre = escre(sepdef) if size(sepdef) == 1 else UNDEF
601
741
 
602
742
  sarr = size(arr)
603
- filtered = [(i, s) for i, s in enumerate(arr)
604
- if isinstance(s, str) and S_MT != s]
743
+ filtered = [(i, s) for i, s in enumerate(arr) if isinstance(s, str) and s != S_MT]
605
744
 
606
745
  result = []
607
746
  for idx, s in filtered:
608
- if sepre is not UNDEF and S_MT != sepre:
609
- if url and 0 == idx:
747
+ if sepre is not UNDEF and sepre != S_MT:
748
+ if url and idx == 0:
610
749
  s = re.sub(sepre + '+$', S_MT, s)
611
750
  result.append(s)
612
751
  continue
613
- if 0 < idx:
752
+ if idx > 0:
614
753
  s = re.sub('^' + sepre + '+', S_MT, s)
615
754
  if idx < sarr - 1 or not url:
616
755
  s = re.sub(sepre + '+$', S_MT, s)
617
- s = re.sub('([^' + sepre + '])' + sepre + '+([^' + sepre + '])',
618
- r'\1' + sepdef + r'\2', s)
756
+ s = re.sub(
757
+ '([^' + sepre + '])' + sepre + '+([^' + sepre + '])', r'\1' + sepdef + r'\2', s
758
+ )
619
759
 
620
- if S_MT != s:
760
+ if s != S_MT:
621
761
  result.append(s)
622
762
 
623
763
  return sepdef.join(result)
@@ -659,43 +799,48 @@ def delprop(parent: Any, key: Any):
659
799
  return parent
660
800
 
661
801
 
662
- def jsonify(val: Any = UNDEF, flags: Dict[str, Any] = None) -> str:
802
+ def jsonify(val: Any = UNDEF, flags: dict[str, Any] = UNDEF) -> str:
663
803
  """
664
804
  Convert a value to a formatted JSON string.
665
805
  In general, the behavior of JavaScript's JSON.stringify(val, null, 2) is followed.
666
806
  """
667
807
  flags = flags or {}
668
-
808
+
669
809
  if val is UNDEF:
670
810
  return S_null
671
-
811
+
672
812
  indent = getprop(flags, 'indent', 2)
673
-
813
+
674
814
  try:
675
- json_str = json.dumps(val, indent=indent, separators=(',', ': ') if indent else (',', ':'))
815
+ if indent and indent > 0:
816
+ # JS-style pretty print: newlines, spaces, ": " between key/value.
817
+ json_str = json.dumps(val, indent=indent, separators=(',', ': '))
818
+ else:
819
+ # indent=0 = compact single-line, matching the C/Rust/Lua printers.
820
+ json_str = json.dumps(val, separators=(',', ':'))
676
821
  except Exception:
677
822
  return S_null
678
-
823
+
679
824
  if json_str is None:
680
825
  return S_null
681
-
826
+
682
827
  offset = getprop(flags, 'offset', 0)
683
- if 0 < offset:
828
+ if offset > 0:
684
829
  lines = json_str.split('\n')
685
830
  padded = [pad(n[1], 0 - offset - size(n[1])) for n in items(lines[1:])]
686
831
  json_str = '{\n' + '\n'.join(padded)
687
-
832
+
688
833
  return json_str
689
834
 
690
835
 
691
- def jo(*kv: Any) -> Dict[str, Any]:
836
+ def jm(*kv: Any) -> dict[str, Any]:
692
837
  """
693
838
  Define a JSON Object using function arguments.
694
839
  Arguments are treated as key-value pairs.
695
840
  """
696
841
  kvsize = len(kv)
697
842
  o = {}
698
-
843
+
699
844
  for i in range(0, kvsize, 2):
700
845
  k = kv[i] if i < kvsize else f'$KEY{i}'
701
846
  # Handle None specially to become "null" for keys
@@ -706,30 +851,25 @@ def jo(*kv: Any) -> Dict[str, Any]:
706
851
  else:
707
852
  k = stringify(k)
708
853
  o[k] = kv[i + 1] if i + 1 < kvsize else None
709
-
854
+
710
855
  return o
711
856
 
712
857
 
713
- def ja(*v: Any) -> List[Any]:
858
+ def jt(*v: Any) -> list[Any]:
714
859
  """
715
860
  Define a JSON Array using function arguments.
716
861
  """
717
862
  vsize = len(v)
718
863
  a = [None] * vsize
719
-
864
+
720
865
  for i in range(vsize):
721
866
  a[i] = v[i] if i < vsize else None
722
-
723
- return a
724
-
725
867
 
726
- # Aliases to match TS canonical names
727
- jm = jo
728
- jt = ja
868
+ return a
729
869
 
730
870
 
731
871
  def select_AND(state, _val, _ref, store):
732
- if S_MKEYPRE == state.mode:
872
+ if state.mode == S_MKEYPRE:
733
873
  terms = getprop(state.parent, state.key)
734
874
  ppath = slice(state.path, -1)
735
875
  point = getpath(store, ppath)
@@ -739,15 +879,24 @@ def select_AND(state, _val, _ref, store):
739
879
 
740
880
  for term in terms:
741
881
  terrs = []
742
- validate(point, term, {
743
- 'extra': vstore,
744
- 'errs': terrs,
745
- 'meta': state.meta,
746
- })
747
- if 0 != len(terrs):
882
+ validate(
883
+ point,
884
+ term,
885
+ {
886
+ 'extra': vstore,
887
+ 'errs': terrs,
888
+ 'meta': state.meta,
889
+ },
890
+ )
891
+ if len(terrs) != 0:
748
892
  state.errs.append(
749
- 'AND:' + pathify(ppath) + '\u2A2F' + stringify(point) +
750
- ' fail:' + stringify(terms))
893
+ 'AND:'
894
+ + pathify(ppath)
895
+ + '\u2a2f'
896
+ + stringify(point)
897
+ + ' fail:'
898
+ + stringify(terms)
899
+ )
751
900
 
752
901
  gkey = getelem(state.path, -2)
753
902
  gp = getelem(state.nodes, -2)
@@ -757,7 +906,7 @@ def select_AND(state, _val, _ref, store):
757
906
 
758
907
 
759
908
  def select_OR(state, _val, _ref, store):
760
- if S_MKEYPRE == state.mode:
909
+ if state.mode == S_MKEYPRE:
761
910
  terms = getprop(state.parent, state.key)
762
911
  ppath = slice(state.path, -1)
763
912
  point = getpath(store, ppath)
@@ -767,26 +916,30 @@ def select_OR(state, _val, _ref, store):
767
916
 
768
917
  for term in terms:
769
918
  terrs = []
770
- validate(point, term, {
771
- 'extra': vstore,
772
- 'errs': terrs,
773
- 'meta': state.meta,
774
- })
775
- if 0 == len(terrs):
919
+ validate(
920
+ point,
921
+ term,
922
+ {
923
+ 'extra': vstore,
924
+ 'errs': terrs,
925
+ 'meta': state.meta,
926
+ },
927
+ )
928
+ if len(terrs) == 0:
776
929
  gkey = getelem(state.path, -2)
777
930
  gp = getelem(state.nodes, -2)
778
931
  setprop(gp, gkey, point)
779
932
  return UNDEF
780
933
 
781
934
  state.errs.append(
782
- 'OR:' + pathify(ppath) + '\u2A2F' + stringify(point) +
783
- ' fail:' + stringify(terms))
935
+ 'OR:' + pathify(ppath) + '\u2a2f' + stringify(point) + ' fail:' + stringify(terms)
936
+ )
784
937
 
785
938
  return UNDEF
786
939
 
787
940
 
788
941
  def select_NOT(state, _val, _ref, store):
789
- if S_MKEYPRE == state.mode:
942
+ if state.mode == S_MKEYPRE:
790
943
  term = getprop(state.parent, state.key)
791
944
  ppath = slice(state.path, -1)
792
945
  point = getpath(store, ppath)
@@ -795,16 +948,20 @@ def select_NOT(state, _val, _ref, store):
795
948
  vstore['$TOP'] = point
796
949
 
797
950
  terrs = []
798
- validate(point, term, {
799
- 'extra': vstore,
800
- 'errs': terrs,
801
- 'meta': state.meta,
802
- })
951
+ validate(
952
+ point,
953
+ term,
954
+ {
955
+ 'extra': vstore,
956
+ 'errs': terrs,
957
+ 'meta': state.meta,
958
+ },
959
+ )
803
960
 
804
- if 0 == len(terrs):
961
+ if len(terrs) == 0:
805
962
  state.errs.append(
806
- 'NOT:' + pathify(ppath) + '\u2A2F' + stringify(point) +
807
- ' fail:' + stringify(term))
963
+ 'NOT:' + pathify(ppath) + '\u2a2f' + stringify(point) + ' fail:' + stringify(term)
964
+ )
808
965
 
809
966
  gkey = getelem(state.path, -2)
810
967
  gp = getelem(state.nodes, -2)
@@ -814,7 +971,7 @@ def select_NOT(state, _val, _ref, store):
814
971
 
815
972
 
816
973
  def select_CMP(state, _val, ref, store):
817
- if S_MKEYPRE == state.mode:
974
+ if state.mode == S_MKEYPRE:
818
975
  term = getprop(state.parent, state.key)
819
976
  gkey = getelem(state.path, -2)
820
977
  ppath = slice(state.path, -1)
@@ -822,16 +979,16 @@ def select_CMP(state, _val, ref, store):
822
979
 
823
980
  pass_test = False
824
981
 
825
- if '$GT' == ref and point > term:
826
- pass_test = True
827
- elif '$LT' == ref and point < term:
828
- pass_test = True
829
- elif '$GTE' == ref and point >= term:
830
- pass_test = True
831
- elif '$LTE' == ref and point <= term:
982
+ if (
983
+ (ref == '$GT' and point > term)
984
+ or (ref == '$LT' and point < term)
985
+ or (ref == '$GTE' and point >= term)
986
+ or (ref == '$LTE' and point <= term)
987
+ ):
832
988
  pass_test = True
833
- elif '$LIKE' == ref:
989
+ elif ref == '$LIKE':
834
990
  import re as re_mod
991
+
835
992
  if re_mod.search(term, stringify(point)):
836
993
  pass_test = True
837
994
 
@@ -840,13 +997,20 @@ def select_CMP(state, _val, ref, store):
840
997
  setprop(gp, gkey, point)
841
998
  else:
842
999
  state.errs.append(
843
- 'CMP: ' + pathify(ppath) + '\u2A2F' + stringify(point) +
844
- ' fail:' + ref + ' ' + stringify(term))
1000
+ 'CMP: '
1001
+ + pathify(ppath)
1002
+ + '\u2a2f'
1003
+ + stringify(point)
1004
+ + ' fail:'
1005
+ + ref
1006
+ + ' '
1007
+ + stringify(term)
1008
+ )
845
1009
 
846
1010
  return UNDEF
847
1011
 
848
1012
 
849
- def select(children: Any, query: Any) -> List[Any]:
1013
+ def select(children: Any, query: Any) -> list[Any]:
850
1014
  """
851
1015
  Select children from a top-level object that match a MongoDB-style query.
852
1016
  Supports $and, $or, and equality comparisons.
@@ -854,12 +1018,12 @@ def select(children: Any, query: Any) -> List[Any]:
854
1018
  """
855
1019
  if not isnode(children):
856
1020
  return []
857
-
1021
+
858
1022
  if ismap(children):
859
1023
  children = [setprop(v, S_DKEY, k) or v for k, v in items(children)]
860
1024
  else:
861
1025
  children = [setprop(n, S_DKEY, i) or n if ismap(n) else n for i, n in enumerate(children)]
862
-
1026
+
863
1027
  results = []
864
1028
  injdef = {
865
1029
  'errs': [],
@@ -873,36 +1037,40 @@ def select(children: Any, query: Any) -> List[Any]:
873
1037
  '$GTE': select_CMP,
874
1038
  '$LTE': select_CMP,
875
1039
  '$LIKE': select_CMP,
876
- }
1040
+ },
877
1041
  }
878
-
1042
+
879
1043
  q = clone(query)
880
-
1044
+
881
1045
  # Add $OPEN to all maps in the query
882
1046
  def add_open(_k, v, _parent, _path):
883
1047
  if ismap(v):
884
1048
  setprop(v, '`$OPEN`', getprop(v, '`$OPEN`', True))
885
1049
  return v
886
-
1050
+
887
1051
  walk(q, add_open)
888
-
1052
+
889
1053
  for child in children:
890
1054
  injdef['errs'] = []
891
1055
  validate(child, clone(q), injdef)
892
-
1056
+
893
1057
  if size(injdef['errs']) == 0:
894
1058
  results.append(child)
895
-
1059
+
896
1060
  return results
897
1061
 
898
1062
 
899
- def stringify(val: Any, maxlen: int = UNDEF, pretty: Any = None):
1063
+ def stringify(val: Any = _ABSENT, maxlen: int = UNDEF, pretty: Any = None):
900
1064
  "Safely stringify a value for printing (NOT JSON!)."
901
1065
 
902
1066
  pretty = bool(pretty)
903
1067
  valstr = S_MT
904
1068
 
905
- if UNDEF == val:
1069
+ # Canonical: stringify(undefined) is "" (or "<>" pretty), while
1070
+ # stringify(null) is "null". Python conflates both onto None, so we use the
1071
+ # _ABSENT sentinel to mean "no value supplied" (undefined). An explicit None
1072
+ # is JSON null and falls through to json.dumps below, which yields "null".
1073
+ if val is _ABSENT:
906
1074
  return '<>' if pretty else valstr
907
1075
 
908
1076
  if isinstance(val, str):
@@ -914,9 +1082,9 @@ def stringify(val: Any, maxlen: int = UNDEF, pretty: Any = None):
914
1082
  except Exception:
915
1083
  valstr = '__STRINGIFY_FAILED__'
916
1084
 
917
- if maxlen is not UNDEF and maxlen is not None and -1 < maxlen:
1085
+ if maxlen is not UNDEF and maxlen is not None and maxlen > -1:
918
1086
  js = valstr[:maxlen]
919
- valstr = (js[:maxlen - 3] + '...') if maxlen < len(valstr) else valstr
1087
+ valstr = (js[: maxlen - 3] + '...') if maxlen < len(valstr) else valstr
920
1088
 
921
1089
  if pretty:
922
1090
  colors = [81, 118, 213, 39, 208, 201, 45, 190, 129, 51, 160, 121, 226, 33, 207, 69]
@@ -941,31 +1109,28 @@ def stringify(val: Any, maxlen: int = UNDEF, pretty: Any = None):
941
1109
  return valstr
942
1110
 
943
1111
 
944
- def pathify(val: Any = UNDEF, startin: int = UNDEF, endin: int = UNDEF) -> str:
1112
+ def pathify(val: Any = _ABSENT, startin: int = UNDEF, endin: int = UNDEF) -> str:
945
1113
  pathstr = UNDEF
946
-
1114
+
947
1115
  # Convert input to a path array
948
- path = val if islist(val) else \
949
- [val] if iskey(val) else \
950
- UNDEF
1116
+ path = val if islist(val) else [val] if iskey(val) else UNDEF
951
1117
 
952
1118
  # [val] if isinstance(val, str) else \
953
- # [val] if isinstance(val, (int, float)) else \
1119
+ # [val] if isinstance(val, (int, float)) else \
954
1120
 
955
-
956
1121
  # Determine starting index and ending index
957
- start = 0 if startin is UNDEF else startin if -1 < startin else 0
958
- end = 0 if endin is UNDEF else endin if -1 < endin else 0
1122
+ start = 0 if startin is UNDEF else startin if startin > -1 else 0
1123
+ end = 0 if endin is UNDEF else endin if endin > -1 else 0
959
1124
 
960
- if UNDEF != path and 0 <= start:
961
- path = path[start:len(path)-end]
1125
+ if path != UNDEF and start >= 0:
1126
+ path = path[start : len(path) - end]
962
1127
 
963
- if 0 == len(path):
964
- pathstr = "<root>"
1128
+ if len(path) == 0:
1129
+ pathstr = '<root>'
965
1130
  else:
966
1131
  # Filter path parts to include only valid keys
967
1132
  filtered_path = [p for p in path if iskey(p)]
968
-
1133
+
969
1134
  # Map path parts: convert numbers to strings and remove any dots
970
1135
  mapped_path = []
971
1136
  for p in filtered_path:
@@ -973,12 +1138,15 @@ def pathify(val: Any = UNDEF, startin: int = UNDEF, endin: int = UNDEF) -> str:
973
1138
  mapped_path.append(S_MT + str(int(p)))
974
1139
  else:
975
1140
  mapped_path.append(str(p).replace('.', S_MT))
976
-
1141
+
977
1142
  pathstr = S_DT.join(mapped_path)
978
1143
 
979
- # Handle the case where we couldn't create a path
980
- if UNDEF == pathstr:
981
- pathstr = f"<unknown-path{S_MT if UNDEF == val else S_CN+stringify(val, 47)}>"
1144
+ # Handle the case where we couldn't create a path.
1145
+ # Canonical: an absent (undefined) value renders "<unknown-path>", while an
1146
+ # explicit JSON null renders "<unknown-path:null>". _ABSENT distinguishes
1147
+ # the two (Python conflates both onto None otherwise).
1148
+ if pathstr is UNDEF:
1149
+ pathstr = f'<unknown-path{S_MT if val is _ABSENT else S_CN + stringify(val, 47)}>'
982
1150
 
983
1151
  return pathstr
984
1152
 
@@ -988,9 +1156,18 @@ def clone(val: Any = UNDEF):
988
1156
  Clone a JSON-like data structure.
989
1157
  NOTE: function value references are copied, *not* cloned.
990
1158
  """
991
- if UNDEF == val:
1159
+ if val == UNDEF:
992
1160
  return UNDEF
993
1161
 
1162
+ try:
1163
+ # Fast path: plain JSON data round-trips directly, avoiding the two full
1164
+ # recursive passes (replacer + reviver) the ref-capturing path below
1165
+ # needs. Non-serialisable values (functions / instances) raise TypeError
1166
+ # and fall through to that path.
1167
+ return json.loads(json.dumps(val, separators=(',', ':')))
1168
+ except TypeError:
1169
+ pass
1170
+
994
1171
  refs = []
995
1172
 
996
1173
  def replacer(item):
@@ -1004,7 +1181,7 @@ def clone(val: Any = UNDEF):
1004
1181
  elif hasattr(item, 'to_json'):
1005
1182
  return item.to_json()
1006
1183
  elif hasattr(item, '__dict__'):
1007
- return item.__dict__
1184
+ return item.__dict__
1008
1185
  else:
1009
1186
  return item
1010
1187
 
@@ -1035,7 +1212,13 @@ def clone(val: Any = UNDEF):
1035
1212
  def setprop(parent: Any, key: Any, val: Any):
1036
1213
  """
1037
1214
  Safely set a property on a dictionary or list.
1038
- - None value deletes the key/element (mirrors JS undefined behavior).
1215
+
1216
+ Mirrors TS canonical: setprop SETS, including JSON null. Callers wanting
1217
+ to delete must use delprop. (Python's UNDEF==None previously conflated
1218
+ "absent" and "JSON null"; the delete-on-None behaviour silently dropped
1219
+ null values, breaking $ANY validation. See Injection.setval for the
1220
+ delete-on-undef logic.)
1221
+
1039
1222
  - For lists, negative key -> prepend.
1040
1223
  - For lists, key > len(list) -> append.
1041
1224
  """
@@ -1044,10 +1227,7 @@ def setprop(parent: Any, key: Any, val: Any):
1044
1227
 
1045
1228
  if ismap(parent):
1046
1229
  key = str(key)
1047
- if val is None:
1048
- parent.pop(key, None)
1049
- else:
1050
- parent[key] = val
1230
+ parent[key] = val
1051
1231
 
1052
1232
  elif islist(parent):
1053
1233
  try:
@@ -1055,11 +1235,8 @@ def setprop(parent: Any, key: Any, val: Any):
1055
1235
  except ValueError:
1056
1236
  return parent
1057
1237
 
1058
- if val is None:
1059
- if 0 <= key_i < len(parent):
1060
- for pI in range(key_i, len(parent) - 1):
1061
- parent[pI] = parent[pI + 1]
1062
- parent.pop()
1238
+ if False:
1239
+ pass
1063
1240
  else:
1064
1241
  if key_i >= 0:
1065
1242
  key_i = min(key_i, len(parent))
@@ -1077,39 +1254,68 @@ MAXDEPTH = 32
1077
1254
 
1078
1255
 
1079
1256
  def walk(
1080
- val: Any,
1081
- apply: Any = None,
1082
- key: Any = UNDEF,
1083
- parent: Any = UNDEF,
1084
- path: Any = UNDEF,
1085
- *,
1086
- before: Any = None,
1087
- after: Any = None,
1088
- maxdepth: Any = None,
1257
+ val: Any,
1258
+ apply: Any = None,
1259
+ key: Any = UNDEF,
1260
+ parent: Any = UNDEF,
1261
+ path: Any = UNDEF,
1262
+ *,
1263
+ before: Any = None,
1264
+ after: Any = None,
1265
+ maxdepth: Any = None,
1266
+ pool: Any = UNDEF,
1089
1267
  ):
1090
1268
  """
1091
1269
  Walk a data structure depth-first.
1092
1270
  Supports before (pre-descent) and after (post-descent) callbacks.
1093
1271
  For backward compat, `apply` is treated as the after callback.
1272
+
1273
+ The `path` argument passed to the before/after callbacks is a single
1274
+ mutable list per depth, shared across all callback invocations for
1275
+ the lifetime of this top-level walk call. Callbacks that need to
1276
+ store the path MUST clone it (e.g. ``path[:]`` or ``list(path)``);
1277
+ the contents will otherwise be overwritten by subsequent visits.
1094
1278
  """
1279
+ if pool is UNDEF:
1280
+ pool = [[]]
1095
1281
  if path is UNDEF:
1096
- path = []
1282
+ path = pool[0]
1097
1283
 
1098
1284
  _before = before
1099
1285
  _after = after if after is not None else apply
1100
1286
 
1287
+ depth = len(path)
1288
+
1101
1289
  out = val if _before is None else _before(key, val, parent, path)
1102
1290
 
1103
- md = maxdepth if maxdepth is not None and 0 <= maxdepth else MAXDEPTH
1104
- if 0 == md or (path is not None and 0 < md and md <= len(path)):
1291
+ md = maxdepth if maxdepth is not None and maxdepth >= 0 else MAXDEPTH
1292
+ if md == 0 or (md > 0 and md <= depth):
1105
1293
  return out
1106
1294
 
1107
1295
  if isnode(out):
1108
- for (ckey, child) in items(out):
1296
+ child_depth = depth + 1
1297
+ # Grow pool on demand: pool[n] is a reusable path list of length n.
1298
+ # Appending at index i creates a list of length i, matching index.
1299
+ while len(pool) <= child_depth:
1300
+ pool.append([None] * len(pool))
1301
+ child_path = pool[child_depth]
1302
+ # Sync prefix [0..depth-1] from the current path. Only needed once
1303
+ # per parent: siblings share the same prefix and will each
1304
+ # overwrite slot [depth] below.
1305
+ for i in range(depth):
1306
+ child_path[i] = path[i]
1307
+
1308
+ for ckey, child in items(out):
1309
+ child_path[depth] = str(ckey)
1109
1310
  result = walk(
1110
- child, key=ckey, parent=out,
1111
- path=flatten([path or [], str(ckey)]),
1112
- before=_before, after=_after, maxdepth=md,
1311
+ child,
1312
+ key=ckey,
1313
+ parent=out,
1314
+ path=child_path,
1315
+ before=_before,
1316
+ after=_after,
1317
+ maxdepth=md,
1318
+ pool=pool,
1113
1319
  )
1114
1320
  if ismap(out):
1115
1321
  out[str(ckey)] = result
@@ -1122,7 +1328,7 @@ def walk(
1122
1328
  return out
1123
1329
 
1124
1330
 
1125
- def merge(objs: List[Any] = None, maxdepth: Any = None) -> Any:
1331
+ def merge(objs: list[Any] = UNDEF, maxdepth: Any = None) -> Any:
1126
1332
  """
1127
1333
  Merge a list of values into each other. Later values have
1128
1334
  precedence. Nodes override scalars. Node kinds (list or map)
@@ -1137,9 +1343,9 @@ def merge(objs: List[Any] = None, maxdepth: Any = None) -> Any:
1137
1343
 
1138
1344
  lenlist = len(objs)
1139
1345
 
1140
- if 0 == lenlist:
1346
+ if lenlist == 0:
1141
1347
  return UNDEF
1142
- if 1 == lenlist:
1348
+ if lenlist == 1:
1143
1349
  return objs[0]
1144
1350
 
1145
1351
  out = getprop(objs, 0, {})
@@ -1153,7 +1359,7 @@ def merge(objs: List[Any] = None, maxdepth: Any = None) -> Any:
1153
1359
  cur = [out]
1154
1360
  dst = [out]
1155
1361
 
1156
- def before(key, val, _parent, path):
1362
+ def before(key, val, _parent, path, cur=cur, dst=dst):
1157
1363
  pI = size(path)
1158
1364
 
1159
1365
  if md <= pI:
@@ -1179,10 +1385,10 @@ def merge(objs: List[Any] = None, maxdepth: Any = None) -> Any:
1179
1385
  if pI >= cur_len:
1180
1386
  cur.extend([UNDEF] * (pI + 1 - cur_len))
1181
1387
 
1182
- dst[pI] = getprop(dst[pI - 1], key) if 0 < pI else dst[pI]
1388
+ dst[pI] = getprop(dst[pI - 1], key) if pI > 0 else dst[pI]
1183
1389
  tval = dst[pI]
1184
1390
 
1185
- if UNDEF == tval:
1391
+ if tval == UNDEF:
1186
1392
  cur[pI] = [] if islist(val) else {}
1187
1393
  elif (islist(val) and islist(tval)) or (ismap(val) and ismap(tval)):
1188
1394
  cur[pI] = tval
@@ -1192,7 +1398,7 @@ def merge(objs: List[Any] = None, maxdepth: Any = None) -> Any:
1192
1398
 
1193
1399
  return val
1194
1400
 
1195
- def after(key, _val, _parent, path):
1401
+ def after(key, _val, _parent, path, cur=cur):
1196
1402
  cI = size(path)
1197
1403
  if cI < 1:
1198
1404
  return cur[0] if len(cur) > 0 else _val
@@ -1205,7 +1411,7 @@ def merge(objs: List[Any] = None, maxdepth: Any = None) -> Any:
1205
1411
 
1206
1412
  out = walk(obj, before=before, after=after)
1207
1413
 
1208
- if 0 == md:
1414
+ if md == 0:
1209
1415
  out = getprop(objs, lenlist - 1, UNDEF)
1210
1416
  out = [] if islist(out) else {} if ismap(out) else out
1211
1417
 
@@ -1226,7 +1432,7 @@ def getpath(store, path, injdef=UNDEF):
1226
1432
  parts = [strkey(path)]
1227
1433
  else:
1228
1434
  return UNDEF
1229
-
1435
+
1230
1436
  val = store
1231
1437
  # Support both dict-style injdef and Injection instance
1232
1438
  if isinstance(injdef, Injection):
@@ -1244,33 +1450,31 @@ def getpath(store, path, injdef=UNDEF):
1244
1450
 
1245
1451
  src = getprop(store, base, store) if base else store
1246
1452
  numparts = size(parts)
1247
-
1453
+
1248
1454
  # An empty path (incl empty string) just finds the store.
1249
- if path is UNDEF or store is UNDEF or (1 == numparts and parts[0] == S_MT) or numparts == 0:
1455
+ if path is UNDEF or store is UNDEF or (numparts == 1 and parts[0] == S_MT) or numparts == 0:
1250
1456
  val = src
1251
1457
  return val
1252
1458
  elif numparts > 0:
1253
-
1254
1459
  # Check for $ACTIONs
1255
- if 1 == numparts:
1460
+ if numparts == 1:
1256
1461
  val = getprop(store, parts[0])
1257
-
1462
+
1258
1463
  if not isfunc(val):
1259
1464
  val = src
1260
-
1465
+
1261
1466
  # Check for meta path syntax
1262
1467
  m = R_META_PATH.match(parts[0]) if parts[0] else None
1263
1468
  if m and inj_meta:
1264
1469
  val = getprop(inj_meta, m.group(1))
1265
1470
  parts[0] = m.group(3)
1266
-
1267
-
1471
+
1268
1472
  for pI in range(numparts):
1269
1473
  if val is UNDEF:
1270
1474
  break
1271
-
1475
+
1272
1476
  part = parts[pI]
1273
-
1477
+
1274
1478
  # Handle special path components
1275
1479
  if injdef and part == S_DKEY:
1276
1480
  part = inj_key if inj_key is not UNDEF else part
@@ -1283,55 +1487,56 @@ def getpath(store, path, injdef=UNDEF):
1283
1487
  elif injdef and isinstance(part, str) and part.startswith('$META:'):
1284
1488
  # $META:metapath$ -> get meta value, use as path part (string)
1285
1489
  part = stringify(getpath(inj_meta, part[6:-1]))
1286
-
1287
- # $$ escapes $ (path parts can be int e.g. list indices)
1288
- if isinstance(part, str):
1289
- part = R_DOUBLE_DOLLAR.sub('$', part)
1290
- else:
1490
+
1491
+ # $$ escapes $ (path parts can be int e.g. list indices).
1492
+ # Skip the regex for the common segment with no '$$'.
1493
+ if not isinstance(part, str):
1291
1494
  part = strkey(part)
1292
-
1495
+ elif '$$' in part:
1496
+ part = R_DOUBLE_DOLLAR.sub('$', part)
1497
+
1293
1498
  if part == S_MT:
1294
1499
  ascends = 0
1295
1500
  while pI + 1 < len(parts) and parts[pI + 1] == S_MT:
1296
1501
  ascends += 1
1297
1502
  pI += 1
1298
1503
 
1299
- if injdef and 0 < ascends:
1504
+ if injdef and ascends > 0:
1300
1505
  if pI == len(parts) - 1:
1301
1506
  ascends -= 1
1302
1507
 
1303
- if 0 == ascends:
1508
+ if ascends == 0:
1304
1509
  val = dparent
1305
1510
  else:
1306
- fullpath = flatten(
1307
- [slice(dpath, 0 - ascends), parts[pI + 1:]])
1308
- if ascends <= size(dpath):
1309
- val = getpath(store, fullpath)
1310
- else:
1311
- val = UNDEF
1511
+ fullpath = flatten([slice(dpath, 0 - ascends), parts[pI + 1 :]])
1512
+ val = getpath(store, fullpath) if ascends <= size(dpath) else UNDEF
1312
1513
  break
1313
1514
  else:
1314
1515
  val = dparent
1315
1516
  else:
1316
1517
  val = getprop(val, part)
1317
-
1518
+
1318
1519
  # Injdef may provide a custom handler to modify found value.
1319
- handler = injdef.handler if isinstance(injdef, Injection) else (getprop(injdef, 'handler') if injdef else UNDEF)
1520
+ handler = (
1521
+ injdef.handler
1522
+ if isinstance(injdef, Injection)
1523
+ else (getprop(injdef, 'handler') if injdef else UNDEF)
1524
+ )
1320
1525
  if handler and isfunc(handler):
1321
1526
  ref = pathify(path)
1322
1527
  val = handler(injdef, val, ref, store)
1323
-
1528
+
1324
1529
  return val
1325
1530
 
1326
1531
 
1327
1532
  def setpath(store, path, val, injdef=UNDEF):
1328
1533
  pathType = typify(path)
1329
1534
 
1330
- if 0 < (T_list & pathType):
1535
+ if (T_list & pathType) > 0:
1331
1536
  parts = path
1332
- elif 0 < (T_string & pathType):
1537
+ elif (T_string & pathType) > 0:
1333
1538
  parts = path.split(S_DT)
1334
- elif 0 < (T_number & pathType):
1539
+ elif (T_number & pathType) > 0:
1335
1540
  parts = [path]
1336
1541
  else:
1337
1542
  return UNDEF
@@ -1345,7 +1550,7 @@ def setpath(store, path, val, injdef=UNDEF):
1345
1550
  nextParent = getprop(parent, partKey)
1346
1551
  if not isnode(nextParent):
1347
1552
  nextPart = getelem(parts, pI + 1)
1348
- nextParent = [] if 0 < (T_number & typify(nextPart)) else {}
1553
+ nextParent = [] if (T_number & typify(nextPart)) > 0 else {}
1349
1554
  setprop(parent, partKey, nextParent)
1350
1555
  parent = nextParent
1351
1556
 
@@ -1361,8 +1566,6 @@ def inject(val, store, injdef=UNDEF):
1361
1566
  """
1362
1567
  Inject values from `store` into `val` recursively, respecting backtick syntax.
1363
1568
  """
1364
- valtype = type(val)
1365
-
1366
1569
  # Reuse existing injection state during recursion; otherwise create a new one.
1367
1570
  if isinstance(injdef, Injection):
1368
1571
  inj = injdef
@@ -1385,7 +1588,7 @@ def inject(val, store, injdef=UNDEF):
1385
1588
  base=S_DTOP,
1386
1589
  modify=getprop(injdef, 'modify') if injdef else None,
1387
1590
  meta=getprop(injdef, 'meta', {}),
1388
- errs=getprop(store, S_DERRS, [])
1591
+ errs=getprop(store, S_DERRS, []),
1389
1592
  )
1390
1593
  inj.dparent = store
1391
1594
  inj.dpath = [S_DTOP]
@@ -1408,9 +1611,9 @@ def inject(val, store, injdef=UNDEF):
1408
1611
  # Keys are sorted alphanumerically to ensure determinism.
1409
1612
  # Injection transforms ($FOO) are processed *after* other keys.
1410
1613
  if ismap(val):
1411
- normal_keys = [k for k in val.keys() if S_DS not in k]
1614
+ normal_keys = [k for k in val if S_DS not in k]
1412
1615
  normal_keys.sort()
1413
- transform_keys = [k for k in val.keys() if S_DS in k]
1616
+ transform_keys = [k for k in val if S_DS in k]
1414
1617
  transform_keys.sort()
1415
1618
  nodekeys = normal_keys + transform_keys
1416
1619
  else:
@@ -1473,7 +1676,11 @@ def inject(val, store, injdef=UNDEF):
1473
1676
  inj.val = val
1474
1677
 
1475
1678
  # Return the (possibly transform-replaced) root only at top level (prior is None).
1476
- if getattr(inj, 'prior', None) is None and getattr(inj, 'root', None) is not None and haskey(inj.root, S_DTOP):
1679
+ if (
1680
+ getattr(inj, 'prior', None) is None
1681
+ and getattr(inj, 'root', None) is not None
1682
+ and haskey(inj.root, S_DTOP)
1683
+ ):
1477
1684
  return getprop(inj.root, S_DTOP)
1478
1685
  if inj.key == S_DTOP and inj.parent is not UNDEF and haskey(inj.parent, S_DTOP):
1479
1686
  return getprop(inj.parent, S_DTOP)
@@ -1484,7 +1691,7 @@ def inject(val, store, injdef=UNDEF):
1484
1691
  # call the function passing the injection state. This is how transforms operate.
1485
1692
  def _injecthandler(inj, val, ref, store):
1486
1693
  out = val
1487
- iscmd = isfunc(val) and (UNDEF == ref or (isinstance(ref, str) and ref.startswith(S_DS)))
1694
+ iscmd = isfunc(val) and (ref == UNDEF or (isinstance(ref, str) and ref.startswith(S_DS)))
1488
1695
 
1489
1696
  # Only call val function if it is a special command ($NAME format).
1490
1697
  if iscmd:
@@ -1513,7 +1720,7 @@ def transform_DELETE(inj, val, ref, store):
1513
1720
  """
1514
1721
  Injection handler to delete a key from a map/list.
1515
1722
  """
1516
- inj.setval(UNDEF)
1723
+ delprop(inj.parent, inj.key)
1517
1724
  return UNDEF
1518
1725
 
1519
1726
 
@@ -1523,7 +1730,6 @@ def transform_COPY(inj, val, ref, store):
1523
1730
  """
1524
1731
  mode = inj.mode
1525
1732
  key = inj.key
1526
- parent = inj.parent
1527
1733
 
1528
1734
  out = UNDEF
1529
1735
  if mode.startswith('key'):
@@ -1574,9 +1780,8 @@ def transform_KEY(inj, val, ref, store):
1574
1780
 
1575
1781
  keyspec = getprop(parent, S_BKEY)
1576
1782
  if keyspec is not UNDEF:
1577
- # Need to use setprop directly here since we're removing a specific key (S_DKEY)
1578
- # not the current state's key
1579
- setprop(parent, S_BKEY, UNDEF)
1783
+ # Explicitly delete the marker key.
1784
+ delprop(parent, S_BKEY)
1580
1785
  return getprop(inj.dparent, keyspec)
1581
1786
 
1582
1787
  # If no explicit keyspec, and current data has a field matching this key,
@@ -1593,7 +1798,7 @@ def transform_ANNO(inj, val, ref, store):
1593
1798
  Annotate node. Does nothing itself, just used by other injectors, and is removed when called.
1594
1799
  """
1595
1800
  parent = inj.parent
1596
- setprop(parent, S_BANNO, UNDEF)
1801
+ delprop(parent, S_BANNO)
1597
1802
  return UNDEF
1598
1803
 
1599
1804
 
@@ -1619,11 +1824,11 @@ def transform_MERGE(inj, val, ref, store):
1619
1824
  args = args if islist(args) else [args]
1620
1825
 
1621
1826
  # Remove the $MERGE command from a parent map.
1622
- inj.setval(UNDEF)
1827
+ delprop(inj.parent, inj.key)
1623
1828
 
1624
1829
  # Literals in the parent have precedence, but we still merge onto
1625
1830
  # the parent object, so that node tree references are not changed.
1626
- mergelist = [parent] + args + [clone(parent)]
1831
+ mergelist = [parent, *args, clone(parent)]
1627
1832
 
1628
1833
  merge(mergelist)
1629
1834
 
@@ -1666,7 +1871,7 @@ def transform_EACH(inj, val, ref, store):
1666
1871
  # Source data
1667
1872
  srcstore = getprop(store, inj.base, store)
1668
1873
  src = getpath(srcstore, srcpath, inj)
1669
-
1874
+
1670
1875
  # Create parallel data structures:
1671
1876
  # source entries :: child templates
1672
1877
  tcurrent = []
@@ -1676,26 +1881,26 @@ def transform_EACH(inj, val, ref, store):
1676
1881
  target = nodes_[-2] if len(nodes_) >= 2 else nodes_[-1]
1677
1882
 
1678
1883
  rval = []
1679
-
1884
+
1680
1885
  if isnode(src):
1681
1886
  if islist(src):
1682
1887
  tval = [clone(child_template) for _ in src]
1683
1888
  else:
1684
1889
  # Convert dict to a list of child templates
1685
1890
  tval = []
1686
- for k, v in src.items():
1891
+ for k in src:
1687
1892
  # Keep key in meta for usage by `$KEY`
1688
1893
  copy_child = clone(child_template)
1689
1894
  if ismap(copy_child):
1690
1895
  setprop(copy_child, S_BANNO, {S_KEY: k})
1691
1896
  tval.append(copy_child)
1692
1897
  tcurrent = list(src.values()) if ismap(src) else src
1693
-
1694
- if 0 < size(tval):
1898
+
1899
+ if size(tval) > 0:
1695
1900
  # Build tcurrent structure matching TypeScript approach
1696
1901
  ckey = getelem(path, -2) if len(path) >= 2 else UNDEF
1697
1902
  tpath = path[:-1] if len(path) > 0 else []
1698
-
1903
+
1699
1904
  # Build dpath: [S_DTOP, ...srcpath parts, '$:' + ckey]
1700
1905
  dpath = [S_DTOP]
1701
1906
  if isinstance(srcpath, str) and srcpath:
@@ -1704,34 +1909,34 @@ def transform_EACH(inj, val, ref, store):
1704
1909
  dpath.append(part)
1705
1910
  if ckey is not UNDEF:
1706
1911
  dpath.append('$:' + str(ckey))
1707
-
1912
+
1708
1913
  tcur = {ckey: tcurrent}
1709
1914
 
1710
- if 1 < size(tpath):
1915
+ if size(tpath) > 1:
1711
1916
  pkey = getelem(path, -3, S_DTOP)
1712
1917
  tcur = {pkey: tcur}
1713
1918
  dpath.append('$:' + str(pkey))
1714
-
1919
+
1715
1920
  # Create child injection state
1716
1921
  tinj = inj.child(0, [ckey] if ckey is not UNDEF else [])
1717
1922
  tinj.path = tpath
1718
1923
  tinj.nodes = nodes_[:-1] if len(nodes_) > 0 else []
1719
1924
  tinj.parent = getelem(tinj.nodes, -1) if len(tinj.nodes) > 0 else UNDEF
1720
-
1925
+
1721
1926
  if ckey is not UNDEF and tinj.parent is not UNDEF:
1722
1927
  setprop(tinj.parent, ckey, tval)
1723
-
1928
+
1724
1929
  tinj.val = tval
1725
1930
  tinj.dpath = dpath
1726
1931
  tinj.dparent = tcur
1727
-
1932
+
1728
1933
  # Inject the entire list at once
1729
1934
  inject(tval, store, tinj)
1730
1935
  rval = tinj.val
1731
1936
 
1732
1937
  setprop(target, tkey, rval)
1733
1938
 
1734
- return rval[0] if islist(rval) and 0 < size(rval) else UNDEF
1939
+ return rval[0] if islist(rval) and size(rval) > 0 else UNDEF
1735
1940
 
1736
1941
 
1737
1942
  def transform_PACK(inj, val, ref, store):
@@ -1741,7 +1946,7 @@ def transform_PACK(inj, val, ref, store):
1741
1946
  parent = inj.parent
1742
1947
  nodes_ = inj.nodes
1743
1948
 
1744
- if (mode != S_MKEYPRE or not isinstance(key, str) or path is UNDEF or nodes_ is UNDEF):
1949
+ if mode != S_MKEYPRE or not isinstance(key, str) or path is UNDEF or nodes_ is UNDEF:
1745
1950
  return UNDEF
1746
1951
 
1747
1952
  args_val = getprop(parent, key)
@@ -1817,7 +2022,7 @@ def transform_PACK(inj, val, ref, store):
1817
2022
 
1818
2023
  tcur = {ckey: tsrc}
1819
2024
 
1820
- if 1 < size(tpath):
2025
+ if size(tpath) > 1:
1821
2026
  pkey = getelem(inj.path, -3, S_DTOP)
1822
2027
  tcur = {pkey: tcur}
1823
2028
  dpath.append('$:' + str(pkey))
@@ -1844,7 +2049,6 @@ def transform_REF(inj, val, _ref, store):
1844
2049
  Format: ['`$REF`', '`spec-path`']
1845
2050
  """
1846
2051
  nodes = inj.nodes
1847
- modify = inj.modify
1848
2052
 
1849
2053
  if inj.mode != S_MVAL:
1850
2054
  return UNDEF
@@ -1863,6 +2067,7 @@ def transform_REF(inj, val, _ref, store):
1863
2067
  # Check if ref has another $REF inside
1864
2068
  hasSubRef = False
1865
2069
  if isnode(ref):
2070
+
1866
2071
  def check_subref(k, v, parent, path):
1867
2072
  nonlocal hasSubRef
1868
2073
  if v == '`$REF`':
@@ -1873,8 +2078,8 @@ def transform_REF(inj, val, _ref, store):
1873
2078
 
1874
2079
  tref = clone(ref)
1875
2080
 
1876
- cpath = slice(inj.path, 0, len(inj.path)-3)
1877
- tpath = slice(inj.path, 0, len(inj.path)-1)
2081
+ cpath = slice(inj.path, 0, len(inj.path) - 3)
2082
+ tpath = slice(inj.path, 0, len(inj.path) - 1)
1878
2083
  tcur = getpath(store, cpath)
1879
2084
  tval = getpath(store, tpath)
1880
2085
  rval = UNDEF
@@ -1884,7 +2089,7 @@ def transform_REF(inj, val, _ref, store):
1884
2089
  # Create child state for the next level
1885
2090
  child_state = inj.child(0, [getelem(tpath, -1)])
1886
2091
  child_state.path = tpath
1887
- child_state.nodes = slice(inj.nodes, 0, len(inj.nodes)-1)
2092
+ child_state.nodes = slice(inj.nodes, 0, len(inj.nodes) - 1)
1888
2093
  child_state.parent = getelem(nodes, -2)
1889
2094
  child_state.val = tref
1890
2095
 
@@ -1897,7 +2102,7 @@ def transform_REF(inj, val, _ref, store):
1897
2102
 
1898
2103
  # Set the value in grandparent, using setval
1899
2104
  inj.setval(rval, 2)
1900
-
2105
+
1901
2106
  # Handle lists by decrementing keyI
1902
2107
  if islist(inj.parent) and inj.prior:
1903
2108
  inj.prior.keyI -= 1
@@ -1944,26 +2149,41 @@ FORMATTER = {
1944
2149
  'string': lambda _k, v, *_a: v if isnode(v) else _jsstr(v),
1945
2150
  'number': _fmt_number,
1946
2151
  'integer': _fmt_integer,
1947
- 'concat': lambda k, v, *_a: join(
1948
- items(v, lambda n: '' if isnode(n[1]) else _jsstr(n[1])), '') if k is None and islist(v) else v,
2152
+ 'concat': lambda k, v, *_a: (
2153
+ join(items(v, lambda n: '' if isnode(n[1]) else _jsstr(n[1])), '')
2154
+ if k is None and islist(v)
2155
+ else v
2156
+ ),
1949
2157
  }
1950
2158
 
1951
2159
 
1952
2160
  def checkPlacement(modes, ijname, parentTypes, inj):
1953
2161
  mode_num = _MODE_TO_NUM.get(inj.mode, 0)
1954
- if 0 == (modes & mode_num):
2162
+ if (modes & mode_num) == 0:
1955
2163
  allowed = [m for m in [M_KEYPRE, M_KEYPOST, M_VAL] if modes & m]
1956
- placements = join(
1957
- items(allowed, lambda n: _PLACEMENT.get(n[1], '')), ',')
1958
- inj.errs.append('$' + ijname + ': invalid placement as ' +
1959
- _PLACEMENT.get(mode_num, '') +
1960
- ', expected: ' + placements + '.')
2164
+ placements = join(items(allowed, lambda n: _PLACEMENT.get(n[1], '')), ',')
2165
+ inj.errs.append(
2166
+ '$'
2167
+ + ijname
2168
+ + ': invalid placement as '
2169
+ + _PLACEMENT.get(mode_num, '')
2170
+ + ', expected: '
2171
+ + placements
2172
+ + '.'
2173
+ )
1961
2174
  return False
1962
2175
  if not isempty(parentTypes):
1963
2176
  ptype = typify(inj.parent)
1964
- if 0 == (parentTypes & ptype):
1965
- inj.errs.append('$' + ijname + ': invalid placement in parent ' +
1966
- typename(ptype) + ', expected: ' + typename(parentTypes) + '.')
2177
+ if (parentTypes & ptype) == 0:
2178
+ inj.errs.append(
2179
+ '$'
2180
+ + ijname
2181
+ + ': invalid placement in parent '
2182
+ + typename(ptype)
2183
+ + ', expected: '
2184
+ + typename(parentTypes)
2185
+ + '.'
2186
+ )
1967
2187
  return False
1968
2188
  return True
1969
2189
 
@@ -1975,10 +2195,18 @@ def injectorArgs(argTypes, args):
1975
2195
  for argI in range(numargs):
1976
2196
  arg = getelem(args, argI)
1977
2197
  argType = typify(arg)
1978
- if 0 == (argTypes[argI] & argType):
1979
- found[0] = ('invalid argument: ' + stringify(arg, 22) +
1980
- ' (' + typename(argType) + ' at position ' + str(1 + argI) +
1981
- ') is not of type: ' + typename(argTypes[argI]) + '.')
2198
+ if (argTypes[argI] & argType) == 0:
2199
+ found[0] = (
2200
+ 'invalid argument: '
2201
+ + stringify(arg, 22)
2202
+ + ' ('
2203
+ + typename(argType)
2204
+ + ' at position '
2205
+ + str(1 + argI)
2206
+ + ') is not of type: '
2207
+ + typename(argTypes[argI])
2208
+ + '.'
2209
+ )
1982
2210
  break
1983
2211
  found[1 + argI] = arg
1984
2212
  return found
@@ -2002,7 +2230,7 @@ def injectChild(child, store, inj):
2002
2230
  def transform_FORMAT(inj, _val, _ref, store):
2003
2231
  slice(inj.keys, 0, 1, True)
2004
2232
 
2005
- if S_MVAL != inj.mode:
2233
+ if inj.mode != S_MVAL:
2006
2234
  return UNDEF
2007
2235
 
2008
2236
  name = getprop(inj.parent, 1)
@@ -2014,7 +2242,7 @@ def transform_FORMAT(inj, _val, _ref, store):
2014
2242
  cinj = injectChild(child, store, inj)
2015
2243
  resolved = cinj.val
2016
2244
 
2017
- formatter = name if 0 < (T_function & typify(name)) else getprop(FORMATTER, name)
2245
+ formatter = name if (T_function & typify(name)) > 0 else getprop(FORMATTER, name)
2018
2246
 
2019
2247
  if formatter is UNDEF:
2020
2248
  inj.errs.append('$FORMAT: unknown format: ' + str(name) + '.')
@@ -2038,7 +2266,7 @@ def transform_APPLY(inj, _val, _ref, store):
2038
2266
  apply_fn = err_apply_child[1]
2039
2267
  child = err_apply_child[2] if len(err_apply_child) > 2 else UNDEF
2040
2268
 
2041
- if UNDEF != err:
2269
+ if err != UNDEF:
2042
2270
  inj.errs.append('$' + ijname + ': ' + err)
2043
2271
  return UNDEF
2044
2272
 
@@ -2064,23 +2292,23 @@ def transform_APPLY(inj, _val, _ref, store):
2064
2292
  # Transform data using spec.
2065
2293
  # Only operates on static JSON-like data.
2066
2294
  # Arrays are treated as if they are objects with indices as keys.
2067
- def transform(
2068
- data,
2069
- spec,
2070
- injdef=UNDEF
2071
- ):
2295
+ def transform(data, spec, injdef=UNDEF):
2072
2296
  # Clone the spec so that the clone can be modified in place as the transform result.
2073
2297
  origspec = spec
2074
2298
  spec = clone(spec)
2075
2299
 
2076
2300
  extra = getprop(injdef, 'extra') if injdef else UNDEF
2077
2301
 
2078
- collect = getprop(injdef, 'errs') is not None and getprop(injdef, 'errs') is not UNDEF if injdef else False
2302
+ collect = (
2303
+ getprop(injdef, 'errs') is not None and getprop(injdef, 'errs') is not UNDEF
2304
+ if injdef
2305
+ else False
2306
+ )
2079
2307
  errs = getprop(injdef, 'errs') if collect else []
2080
2308
 
2081
2309
  extraTransforms = {}
2082
- extraData = {} if UNDEF == extra else {}
2083
-
2310
+ extraData = {}
2311
+
2084
2312
  if extra:
2085
2313
  for k, v in items(extra):
2086
2314
  if isinstance(k, str) and k.startswith(S_DS):
@@ -2089,10 +2317,7 @@ def transform(
2089
2317
  extraData[k] = v
2090
2318
 
2091
2319
  # Combine extra data with user data
2092
- data_clone = merge([
2093
- clone(extraData) if not isempty(extraData) else UNDEF,
2094
- clone(data)
2095
- ])
2320
+ data_clone = merge([clone(extraData) if not isempty(extraData) else UNDEF, clone(data)])
2096
2321
 
2097
2322
  # Top-level store used by inject
2098
2323
  store = {
@@ -2100,19 +2325,14 @@ def transform(
2100
2325
  # NOTE: to escape data that contains "`$FOO`" keys at the top level,
2101
2326
  # place that data inside a holding map: { myholder: mydata }.
2102
2327
  S_DTOP: data_clone,
2103
-
2104
2328
  # Original spec (before clone) for $REF to resolve refpath.
2105
2329
  S_DSPEC: lambda: origspec,
2106
-
2107
2330
  # Escape backtick (this also works inside backticks).
2108
2331
  '$BT': lambda *args, **kwargs: S_BT,
2109
-
2110
2332
  # Escape dollar sign (this also works inside backticks).
2111
2333
  '$DS': lambda *args, **kwargs: S_DS,
2112
-
2113
2334
  # Insert current date and time as an ISO string.
2114
2335
  '$WHEN': lambda *args, **kwargs: datetime.utcnow().isoformat(),
2115
-
2116
2336
  '$DELETE': transform_DELETE,
2117
2337
  '$COPY': transform_COPY,
2118
2338
  '$KEY': transform_KEY,
@@ -2123,10 +2343,8 @@ def transform(
2123
2343
  '$REF': transform_REF,
2124
2344
  '$FORMAT': transform_FORMAT,
2125
2345
  '$APPLY': transform_APPLY,
2126
-
2127
2346
  # Custom extra transforms, if any.
2128
2347
  **extraTransforms,
2129
-
2130
2348
  S_DERRS: errs,
2131
2349
  }
2132
2350
 
@@ -2138,7 +2356,7 @@ def transform(
2138
2356
 
2139
2357
  out = inject(spec, store, injdef)
2140
2358
 
2141
- generr = 0 < size(errs) and not collect
2359
+ generr = size(errs) > 0 and not collect
2142
2360
  if generr:
2143
2361
  raise ValueError(join(errs, ' | '))
2144
2362
 
@@ -2149,11 +2367,11 @@ def validate_STRING(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF):
2149
2367
  out = getprop(inj.dparent, inj.key)
2150
2368
  t = typify(out)
2151
2369
 
2152
- if 0 == (T_string & t):
2370
+ if (T_string & t) == 0:
2153
2371
  inj.errs.append(_invalidTypeMsg(inj.path, S_string, t, out, 'V1010'))
2154
2372
  return UNDEF
2155
2373
 
2156
- if S_MT == out:
2374
+ if out == S_MT:
2157
2375
  inj.errs.append('Empty string at ' + pathify(inj.path, 1))
2158
2376
  return UNDEF
2159
2377
 
@@ -2170,8 +2388,9 @@ TYPE_CHECKS = {
2170
2388
  S_map: lambda v: isinstance(v, dict),
2171
2389
  S_list: lambda v: isinstance(v, list),
2172
2390
  S_function: lambda v: callable(v) and not isinstance(v, type),
2173
- S_instance: lambda v: (not isinstance(v, (dict, list, str, int, float, bool))
2174
- and v is not None and v is not UNDEF),
2391
+ S_instance: lambda v: (
2392
+ not isinstance(v, (dict, list, str, int, float, bool)) and v is not None and v is not UNDEF
2393
+ ),
2175
2394
  }
2176
2395
 
2177
2396
 
@@ -2183,7 +2402,7 @@ def validate_TYPE(inj, _val=UNDEF, ref=UNDEF, _store=UNDEF):
2183
2402
  out = getprop(inj.dparent, inj.key)
2184
2403
  t = typify(out)
2185
2404
 
2186
- if 0 == (t & typev):
2405
+ if (t & typev) == 0:
2187
2406
  inj.errs.append(_invalidTypeMsg(inj.path, tname, t, out, 'V1001'))
2188
2407
  return UNDEF
2189
2408
 
@@ -2202,17 +2421,16 @@ def validate_CHILD(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF):
2202
2421
  keys = inj.keys
2203
2422
 
2204
2423
  # Map syntax.
2205
- if S_MKEYPRE == mode:
2424
+ if mode == S_MKEYPRE:
2206
2425
  childtm = getprop(parent, key)
2207
2426
 
2208
2427
  pkey = getelem(path, -2)
2209
2428
  tval = getprop(inj.dparent, pkey)
2210
2429
 
2211
- if UNDEF == tval:
2430
+ if tval == UNDEF:
2212
2431
  tval = {}
2213
2432
  elif not ismap(tval):
2214
- inj.errs.append(_invalidTypeMsg(
2215
- path[:-1], S_object, typify(tval), tval, 'V0220'))
2433
+ inj.errs.append(_invalidTypeMsg(path[:-1], S_object, typify(tval), tval, 'V0220'))
2216
2434
  return UNDEF
2217
2435
 
2218
2436
  ckeys = keysof(tval)
@@ -2220,36 +2438,44 @@ def validate_CHILD(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF):
2220
2438
  setprop(parent, ckey, clone(childtm))
2221
2439
  keys.append(ckey)
2222
2440
 
2223
- inj.setval(UNDEF)
2441
+ delprop(inj.parent, inj.key)
2224
2442
  return UNDEF
2225
2443
 
2226
2444
  # List syntax.
2227
- if S_MVAL == mode:
2228
-
2445
+ if mode == S_MVAL:
2229
2446
  if not islist(parent):
2230
2447
  inj.errs.append('Invalid $CHILD as value')
2231
2448
  return UNDEF
2232
2449
 
2233
2450
  childtm = getprop(parent, 1)
2234
2451
 
2235
- if UNDEF == inj.dparent:
2452
+ if inj.dparent == UNDEF:
2236
2453
  del parent[:]
2237
2454
  return UNDEF
2238
2455
 
2239
2456
  if not islist(inj.dparent):
2240
- msg = _invalidTypeMsg(
2241
- path[:-1], S_list, typify(inj.dparent), inj.dparent, 'V0230')
2457
+ msg = _invalidTypeMsg(path[:-1], S_list, typify(inj.dparent), inj.dparent, 'V0230')
2242
2458
  inj.errs.append(msg)
2243
2459
  inj.keyI = size(parent)
2244
2460
  return inj.dparent
2245
2461
 
2246
2462
  for n in items(inj.dparent):
2247
2463
  setprop(parent, n[0], clone(childtm))
2248
- del parent[len(inj.dparent):]
2249
- inj.keyI = 0
2464
+ del parent[len(inj.dparent) :]
2250
2465
 
2251
- out = getprop(inj.dparent, 0)
2252
- return out
2466
+ # NOTE: modifying inj! This extends the child value loop in inject
2467
+ # to cover every cloned child.
2468
+ for ckeyI in range(size(keys), size(parent)):
2469
+ keys.append(strkey(ckeyI))
2470
+
2471
+ # Restart the child value loop at the first element (the loop
2472
+ # increments keyI on resume) so that the first element is also
2473
+ # validated against the child template.
2474
+ inj.keyI = -1
2475
+
2476
+ # SKIP leaves the cloned child template in place at the first
2477
+ # element so the resumed loop can validate it.
2478
+ return SKIP
2253
2479
 
2254
2480
  return UNDEF
2255
2481
 
@@ -2259,11 +2485,13 @@ def validate_ONE(inj, _val=UNDEF, _ref=UNDEF, store=UNDEF):
2259
2485
  parent = inj.parent
2260
2486
  keyI = inj.keyI
2261
2487
 
2262
- if S_MVAL == mode:
2263
- if not islist(parent) or 0 != keyI:
2264
- inj.errs.append('The $ONE validator at field ' +
2265
- pathify(inj.path, 1, 1) +
2266
- ' must be the first element of an array.')
2488
+ if mode == S_MVAL:
2489
+ if not islist(parent) or keyI != 0:
2490
+ inj.errs.append(
2491
+ 'The $ONE validator at field '
2492
+ + pathify(inj.path, 1, 1)
2493
+ + ' must be the first element of an array.'
2494
+ )
2267
2495
  return None
2268
2496
 
2269
2497
  inj.keyI = size(inj.keys)
@@ -2274,10 +2502,12 @@ def validate_ONE(inj, _val=UNDEF, _ref=UNDEF, store=UNDEF):
2274
2502
  inj.key = getelem(inj.path, -1)
2275
2503
 
2276
2504
  tvals = parent[1:]
2277
- if 0 == size(tvals):
2278
- inj.errs.append('The $ONE validator at field ' +
2279
- pathify(inj.path, 1, 1) +
2280
- ' must have at least one argument.')
2505
+ if size(tvals) == 0:
2506
+ inj.errs.append(
2507
+ 'The $ONE validator at field '
2508
+ + pathify(inj.path, 1, 1)
2509
+ + ' must have at least one argument.'
2510
+ )
2281
2511
  return None
2282
2512
 
2283
2513
  for tval in tvals:
@@ -2286,24 +2516,33 @@ def validate_ONE(inj, _val=UNDEF, _ref=UNDEF, store=UNDEF):
2286
2516
  vstore = merge([{}, store], 1)
2287
2517
  vstore[S_DTOP] = inj.dparent
2288
2518
 
2289
- vcurrent = validate(inj.dparent, tval, {
2290
- 'extra': vstore,
2291
- 'errs': terrs,
2292
- 'meta': inj.meta,
2293
- })
2519
+ vcurrent = validate(
2520
+ inj.dparent,
2521
+ tval,
2522
+ {
2523
+ 'extra': vstore,
2524
+ 'errs': terrs,
2525
+ 'meta': inj.meta,
2526
+ },
2527
+ )
2294
2528
 
2295
2529
  inj.setval(vcurrent, -2)
2296
2530
 
2297
- if 0 == size(terrs):
2531
+ if size(terrs) == 0:
2298
2532
  return None
2299
2533
 
2300
2534
  valdesc = ', '.join(stringify(n[1]) for n in items(tvals))
2301
2535
  valdesc = re.sub(r'`\$([A-Z]+)`', lambda m: m.group(1).lower(), valdesc)
2302
2536
 
2303
- inj.errs.append(_invalidTypeMsg(
2304
- inj.path,
2305
- ('one of ' if 1 < size(tvals) else '') + valdesc,
2306
- typify(inj.dparent), inj.dparent, 'V0210'))
2537
+ inj.errs.append(
2538
+ _invalidTypeMsg(
2539
+ inj.path,
2540
+ ('one of ' if size(tvals) > 1 else '') + valdesc,
2541
+ typify(inj.dparent),
2542
+ inj.dparent,
2543
+ 'V0210',
2544
+ )
2545
+ )
2307
2546
 
2308
2547
 
2309
2548
  def validate_EXACT(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF):
@@ -2312,11 +2551,13 @@ def validate_EXACT(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF):
2312
2551
  key = inj.key
2313
2552
  keyI = inj.keyI
2314
2553
 
2315
- if S_MVAL == mode:
2316
- if not islist(parent) or 0 != keyI:
2317
- inj.errs.append('The $EXACT validator at field ' +
2318
- pathify(inj.path, 1, 1) +
2319
- ' must be the first element of an array.')
2554
+ if mode == S_MVAL:
2555
+ if not islist(parent) or keyI != 0:
2556
+ inj.errs.append(
2557
+ 'The $EXACT validator at field '
2558
+ + pathify(inj.path, 1, 1)
2559
+ + ' must be the first element of an array.'
2560
+ )
2320
2561
  return None
2321
2562
 
2322
2563
  inj.keyI = size(inj.keys)
@@ -2327,10 +2568,12 @@ def validate_EXACT(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF):
2327
2568
  inj.key = getelem(inj.path, -1)
2328
2569
 
2329
2570
  tvals = parent[1:]
2330
- if 0 == size(tvals):
2331
- inj.errs.append('The $EXACT validator at field ' +
2332
- pathify(inj.path, 1, 1) +
2333
- ' must have at least one argument.')
2571
+ if size(tvals) == 0:
2572
+ inj.errs.append(
2573
+ 'The $EXACT validator at field '
2574
+ + pathify(inj.path, 1, 1)
2575
+ + ' must have at least one argument.'
2576
+ )
2334
2577
  return None
2335
2578
 
2336
2579
  currentstr = None
@@ -2348,22 +2591,24 @@ def validate_EXACT(inj, _val=UNDEF, _ref=UNDEF, _store=UNDEF):
2348
2591
  valdesc = ', '.join(stringify(n[1]) for n in items(tvals))
2349
2592
  valdesc = re.sub(r'`\$([A-Z]+)`', lambda m: m.group(1).lower(), valdesc)
2350
2593
 
2351
- inj.errs.append(_invalidTypeMsg(
2352
- inj.path,
2353
- ('' if 1 < size(inj.path) else 'value ') +
2354
- 'exactly equal to ' + ('' if 1 == size(tvals) else 'one of ') + valdesc,
2355
- typify(inj.dparent), inj.dparent, 'V0110'))
2594
+ inj.errs.append(
2595
+ _invalidTypeMsg(
2596
+ inj.path,
2597
+ ('' if size(inj.path) > 1 else 'value ')
2598
+ + 'exactly equal to '
2599
+ + ('' if size(tvals) == 1 else 'one of ')
2600
+ + valdesc,
2601
+ typify(inj.dparent),
2602
+ inj.dparent,
2603
+ 'V0110',
2604
+ )
2605
+ )
2356
2606
  else:
2357
2607
  delprop(parent, key)
2358
2608
 
2359
-
2360
- def _validation(
2361
- pval,
2362
- key,
2363
- parent,
2364
- inj
2365
- ):
2366
- if UNDEF == inj:
2609
+
2610
+ def _validation(pval, key, parent, inj):
2611
+ if inj == UNDEF:
2367
2612
  return
2368
2613
 
2369
2614
  if pval == SKIP:
@@ -2375,17 +2620,17 @@ def _validation(
2375
2620
  # Current val to verify.
2376
2621
  cval = getprop(inj.dparent, key)
2377
2622
 
2378
- if UNDEF == inj or (not exact and UNDEF == cval):
2623
+ if inj == UNDEF or (not exact and cval == UNDEF):
2379
2624
  return
2380
2625
 
2381
2626
  ptype = typify(pval)
2382
2627
 
2383
- if 0 < (T_string & ptype) and S_DS in str(pval):
2628
+ if (T_string & ptype) > 0 and S_DS in str(pval):
2384
2629
  return
2385
2630
 
2386
2631
  ctype = typify(cval)
2387
2632
 
2388
- if ptype != ctype and UNDEF != pval:
2633
+ if ptype != ctype and pval != UNDEF:
2389
2634
  inj.errs.append(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0010'))
2390
2635
  return
2391
2636
 
@@ -2398,13 +2643,20 @@ def _validation(
2398
2643
  pkeys = keysof(pval)
2399
2644
 
2400
2645
  # Empty spec object {} means object can be open (any keys).
2401
- if 0 < len(pkeys) and True != getprop(pval, '`$OPEN`'):
2646
+ if len(pkeys) > 0 and getprop(pval, '`$OPEN`') is not True:
2402
2647
  badkeys = []
2403
2648
  for ckey in ckeys:
2649
+ # Literal presence: the shape DECLARES ckey even when its value
2650
+ # is null. Canonical uses `NONE === _lookup(pval, ckey)`; here
2651
+ # `haskey` is Group A (value-based) and would wrongly flag a
2652
+ # null-valued shape slot as unexpected — dropping records with a
2653
+ # null field from an open ($AND) select. Test map presence.
2404
2654
  if str(ckey) not in pval:
2405
2655
  badkeys.append(ckey)
2406
- if 0 < size(badkeys):
2407
- msg = 'Unexpected keys at field ' + pathify(inj.path, 1) + S_VIZ + join(badkeys, ', ')
2656
+ if size(badkeys) > 0:
2657
+ msg = (
2658
+ 'Unexpected keys at field ' + pathify(inj.path, 1) + S_VIZ + join(badkeys, ', ')
2659
+ )
2408
2660
  inj.errs.append(msg)
2409
2661
  else:
2410
2662
  # Object is open, so merge in extra keys.
@@ -2418,9 +2670,8 @@ def _validation(
2418
2670
 
2419
2671
  elif exact:
2420
2672
  if cval != pval:
2421
- pathmsg = 'at field ' + pathify(inj.path, 1) + ': ' if 1 < size(inj.path) else ''
2422
- inj.errs.append('Value ' + pathmsg + str(cval) +
2423
- ' should equal ' + str(pval) + '.')
2673
+ pathmsg = 'at field ' + pathify(inj.path, 1) + ': ' if size(inj.path) > 1 else ''
2674
+ inj.errs.append('Value ' + pathmsg + str(cval) + ' should equal ' + str(pval) + '.')
2424
2675
 
2425
2676
  else:
2426
2677
  # Spec value was a default, copy over data
@@ -2444,80 +2695,84 @@ def validate(data, spec, injdef=UNDEF):
2444
2695
 
2445
2696
  collect = getprop(injdef, 'errs') is not None and getprop(injdef, 'errs') is not UNDEF
2446
2697
  errs = getprop(injdef, 'errs') if collect else []
2447
-
2448
- store = merge([
2449
- {
2450
- "$DELETE": None,
2451
- "$COPY": None,
2452
- "$KEY": None,
2453
- "$META": None,
2454
- "$MERGE": None,
2455
- "$EACH": None,
2456
- "$PACK": None,
2457
-
2458
- "$STRING": validate_STRING,
2459
- "$NUMBER": validate_TYPE,
2460
- "$INTEGER": validate_TYPE,
2461
- "$DECIMAL": validate_TYPE,
2462
- "$BOOLEAN": validate_TYPE,
2463
- "$NULL": validate_TYPE,
2464
- "$NIL": validate_TYPE,
2465
- "$MAP": validate_TYPE,
2466
- "$LIST": validate_TYPE,
2467
- "$FUNCTION": validate_TYPE,
2468
- "$INSTANCE": validate_TYPE,
2469
- "$ANY": validate_ANY,
2470
- "$CHILD": validate_CHILD,
2471
- "$ONE": validate_ONE,
2472
- "$EXACT": validate_EXACT,
2473
- },
2474
2698
 
2475
- ({} if extra is UNDEF or extra is None else extra),
2476
-
2477
- {
2478
- "$ERRS": errs,
2479
- }
2480
- ], 1)
2699
+ store = merge(
2700
+ [
2701
+ {
2702
+ '$DELETE': None,
2703
+ '$COPY': None,
2704
+ '$KEY': None,
2705
+ '$META': None,
2706
+ '$MERGE': None,
2707
+ '$EACH': None,
2708
+ '$PACK': None,
2709
+ '$STRING': validate_STRING,
2710
+ '$NUMBER': validate_TYPE,
2711
+ '$INTEGER': validate_TYPE,
2712
+ '$DECIMAL': validate_TYPE,
2713
+ '$BOOLEAN': validate_TYPE,
2714
+ '$NULL': validate_TYPE,
2715
+ '$NIL': validate_TYPE,
2716
+ '$MAP': validate_TYPE,
2717
+ '$LIST': validate_TYPE,
2718
+ '$FUNCTION': validate_TYPE,
2719
+ '$INSTANCE': validate_TYPE,
2720
+ '$ANY': validate_ANY,
2721
+ '$CHILD': validate_CHILD,
2722
+ '$ONE': validate_ONE,
2723
+ '$EXACT': validate_EXACT,
2724
+ },
2725
+ ({} if extra is UNDEF or extra is None else extra),
2726
+ {
2727
+ '$ERRS': errs,
2728
+ },
2729
+ ],
2730
+ 1,
2731
+ )
2481
2732
 
2482
2733
  meta = getprop(injdef, 'meta', {})
2483
2734
  setprop(meta, S_BEXACT, getprop(meta, S_BEXACT, False))
2484
2735
 
2485
- out = transform(data, spec, {
2486
- 'meta': meta,
2487
- 'extra': store,
2488
- 'modify': _validation,
2489
- 'handler': _validatehandler,
2490
- 'errs': errs,
2491
- })
2736
+ out = transform(
2737
+ data,
2738
+ spec,
2739
+ {
2740
+ 'meta': meta,
2741
+ 'extra': store,
2742
+ 'modify': _validation,
2743
+ 'handler': _validatehandler,
2744
+ 'errs': errs,
2745
+ },
2746
+ )
2492
2747
 
2493
- generr = 0 < len(errs) and not collect
2748
+ generr = len(errs) > 0 and not collect
2494
2749
  if generr:
2495
2750
  raise ValueError(' | '.join(errs))
2496
2751
 
2497
2752
  return out
2498
2753
 
2499
2754
 
2500
-
2501
2755
  # Internal utilities
2502
2756
  # ==================
2503
2757
 
2758
+
2504
2759
  def _validatehandler(inj, val, ref, store):
2505
2760
  out = val
2506
-
2761
+
2507
2762
  m = R_META_PATH.match(ref) if ref else None
2508
2763
  ismetapath = m is not None
2509
-
2764
+
2510
2765
  if ismetapath:
2511
2766
  if m.group(2) == '=':
2512
2767
  inj.setval([S_BEXACT, val])
2513
2768
  else:
2514
2769
  inj.setval(val)
2515
2770
  inj.keyI = -1
2516
-
2771
+
2517
2772
  out = SKIP
2518
2773
  else:
2519
2774
  out = _injecthandler(inj, val, ref, store)
2520
-
2775
+
2521
2776
  return out
2522
2777
 
2523
2778
 
@@ -2525,8 +2780,8 @@ def _validatehandler(inj, val, ref, store):
2525
2780
  # when needed by implementation language.
2526
2781
  def _setparentprop(state, val):
2527
2782
  setprop(state.parent, state.key, val)
2528
-
2529
-
2783
+
2784
+
2530
2785
  # Update all references to target in state.nodes.
2531
2786
  def _updateAncestors(_state, target, tkey, tval):
2532
2787
  # SetProp is sufficient in Python as target reference remains consistent even for lists.
@@ -2547,53 +2802,52 @@ def _injectstr(val, store, inj=UNDEF):
2547
2802
  full_re = re.compile(r'^`(\$[A-Z]+|[^`]*)[0-9]*`$')
2548
2803
  part_re = re.compile(r'`([^`]*)`')
2549
2804
 
2550
- if not isinstance(val, str) or S_MT == val:
2805
+ if not isinstance(val, str) or val == S_MT:
2551
2806
  return S_MT
2552
2807
 
2553
2808
  out = val
2554
-
2809
+
2555
2810
  # Pattern examples: "`a.b.c`", "`$NAME`", "`$NAME1`"
2556
2811
  m = full_re.match(val)
2557
-
2812
+
2558
2813
  # Full string of the val is an injection.
2559
2814
  if m:
2560
- if UNDEF != inj:
2815
+ if inj != UNDEF:
2561
2816
  inj.full = True
2562
2817
 
2563
2818
  pathref = m.group(1)
2564
2819
 
2565
2820
  # Special escapes inside injection.
2566
- if 3 < len(pathref):
2821
+ if len(pathref) > 3:
2567
2822
  pathref = pathref.replace(r'$BT', S_BT).replace(r'$DS', S_DS)
2568
2823
 
2569
2824
  # Get the extracted path reference.
2570
2825
  out = getpath(store, pathref, inj)
2571
2826
 
2572
2827
  else:
2573
-
2574
2828
  # Check for injections within the string.
2575
2829
  def partial(mobj):
2576
2830
  ref = mobj.group(1)
2577
2831
 
2578
2832
  # Special escapes inside injection.
2579
- if 3 < len(ref):
2833
+ if len(ref) > 3:
2580
2834
  ref = ref.replace(r'$BT', S_BT).replace(r'$DS', S_DS)
2581
-
2582
- if UNDEF != inj:
2835
+
2836
+ if inj != UNDEF:
2583
2837
  inj.full = False
2584
2838
 
2585
2839
  found = getpath(store, ref, inj)
2586
-
2840
+
2587
2841
  # Ensure inject value is a string.
2588
- if UNDEF == found:
2842
+ if found == UNDEF:
2589
2843
  return S_MT
2590
-
2844
+
2591
2845
  if isinstance(found, str):
2592
2846
  # Convert test NULL marker to JSON 'null' when injecting into strings
2593
2847
  if found == '__NULL__':
2594
2848
  return 'null'
2595
2849
  return found
2596
-
2850
+
2597
2851
  if isfunc(found):
2598
2852
  return found
2599
2853
 
@@ -2606,7 +2860,7 @@ def _injectstr(val, store, inj=UNDEF):
2606
2860
 
2607
2861
  # Also call the inj handler on the entire string, providing the
2608
2862
  # option for custom injection.
2609
- if UNDEF != inj and isfunc(inj.handler):
2863
+ if inj != UNDEF and isfunc(inj.handler):
2610
2864
  inj.full = True
2611
2865
  out = inj.handler(inj, out, val, store)
2612
2866
 
@@ -2616,11 +2870,13 @@ def _injectstr(val, store, inj=UNDEF):
2616
2870
  def _invalidTypeMsg(path, needtype, vt, v, _whence=None):
2617
2871
  vs = 'no value' if v is None or v is UNDEF else stringify(v)
2618
2872
  return (
2619
- 'Expected ' +
2620
- ('field ' + pathify(path, 1) + ' to be ' if 1 < size(path) else '') +
2621
- str(needtype) + ', but found ' +
2622
- (typename(vt) + S_VIZ if v is not None and v is not UNDEF else '') + vs +
2623
- '.'
2873
+ 'Expected '
2874
+ + ('field ' + pathify(path, 1) + ' to be ' if size(path) > 1 else '')
2875
+ + str(needtype)
2876
+ + ', but found '
2877
+ + (typename(vt) + S_VIZ if v is not None and v is not UNDEF else '')
2878
+ + vs
2879
+ + '.'
2624
2880
  )
2625
2881
 
2626
2882
 
@@ -2648,8 +2904,6 @@ class StructUtility:
2648
2904
  self.items = items
2649
2905
  self.jm = jm
2650
2906
  self.jt = jt
2651
- self.jo = jo
2652
- self.ja = ja
2653
2907
  self.join = join
2654
2908
  self.joinurl = joinurl
2655
2909
  self.jsonify = jsonify
@@ -2694,11 +2948,32 @@ class StructUtility:
2694
2948
  self.checkPlacement = checkPlacement
2695
2949
  self.injectorArgs = injectorArgs
2696
2950
  self.injectChild = injectChild
2697
-
2951
+
2698
2952
 
2699
2953
  __all__ = [
2954
+ 'DELETE',
2955
+ 'MODENAME',
2956
+ 'M_KEYPOST',
2957
+ 'M_KEYPRE',
2958
+ 'M_VAL',
2959
+ 'SKIP',
2700
2960
  'Injection',
2701
2961
  'StructUtility',
2962
+ 'T_any',
2963
+ 'T_boolean',
2964
+ 'T_decimal',
2965
+ 'T_function',
2966
+ 'T_instance',
2967
+ 'T_integer',
2968
+ 'T_list',
2969
+ 'T_map',
2970
+ 'T_node',
2971
+ 'T_noval',
2972
+ 'T_null',
2973
+ 'T_number',
2974
+ 'T_scalar',
2975
+ 'T_string',
2976
+ 'T_symbol',
2702
2977
  'checkPlacement',
2703
2978
  'clone',
2704
2979
  'delprop',
@@ -2721,9 +2996,7 @@ __all__ = [
2721
2996
  'ismap',
2722
2997
  'isnode',
2723
2998
  'items',
2724
- 'ja',
2725
2999
  'jm',
2726
- 'jo',
2727
3000
  'join',
2728
3001
  'joinurl',
2729
3002
  'jsonify',
@@ -2745,26 +3018,4 @@ __all__ = [
2745
3018
  'typify',
2746
3019
  'validate',
2747
3020
  'walk',
2748
- 'SKIP',
2749
- 'DELETE',
2750
- 'T_any',
2751
- 'T_noval',
2752
- 'T_boolean',
2753
- 'T_decimal',
2754
- 'T_integer',
2755
- 'T_number',
2756
- 'T_string',
2757
- 'T_function',
2758
- 'T_symbol',
2759
- 'T_null',
2760
- 'T_list',
2761
- 'T_map',
2762
- 'T_instance',
2763
- 'T_scalar',
2764
- 'T_node',
2765
- 'M_KEYPRE',
2766
- 'M_KEYPOST',
2767
- 'M_VAL',
2768
- 'MODENAME',
2769
3021
  ]
2770
-