hecks 1.4.0 → 1.5.1

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 (275) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/claude_code.rb +65 -0
  3. data/lib/hecks/adapters/driven/folder.rb +73 -0
  4. data/lib/hecks/adapters/driven/google_authentication.rb +25 -4
  5. data/lib/hecks/adapters/driven/governance_authorization.rb +57 -6
  6. data/lib/hecks/adapters/driven/heki/journal.rb +8 -0
  7. data/lib/hecks/adapters/driven/heki/saga_store.rb +53 -7
  8. data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
  9. data/lib/hecks/adapters/driven/heki.rb +93 -2
  10. data/lib/hecks/adapters/driven/identity_registry.rb +10 -0
  11. data/lib/hecks/adapters/driven/in_memory_ordering.rb +22 -0
  12. data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
  13. data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
  14. data/lib/hecks/adapters/driven/lambda/client.rb +35 -7
  15. data/lib/hecks/adapters/driven/lambda.rb +43 -2
  16. data/lib/hecks/adapters/driven/local_storage.rb +67 -1
  17. data/lib/hecks/adapters/driven/memory.rb +13 -13
  18. data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +20 -0
  19. data/lib/hecks/adapters/driven/prism.rb +46 -7
  20. data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
  21. data/lib/hecks/adapters/driven/system_clock.rb +3 -0
  22. data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
  23. data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
  24. data/lib/hecks/adapters/driven.rb +2 -0
  25. data/lib/hecks/adapters/driving/github_webhook.rb +14 -1
  26. data/lib/hecks/behaviors/dsl.rb +58 -0
  27. data/lib/hecks/behaviors/expectations.rb +168 -7
  28. data/lib/hecks/behaviors/ir.rb +11 -0
  29. data/lib/hecks/behaviors/rspec.rb +8 -0
  30. data/lib/hecks/behaviors/runner.rb +19 -0
  31. data/lib/hecks/behaviors.rb +8 -0
  32. data/lib/hecks/bluebook/aggregate.rb +30 -0
  33. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +7 -0
  34. data/lib/hecks/bluebook/assembly/build.rb +25 -4
  35. data/lib/hecks/bluebook/assembly/contract.rb +66 -14
  36. data/lib/hecks/bluebook/assembly/contracts.rb +24 -19
  37. data/lib/hecks/bluebook/assembly/marks.rb +143 -14
  38. data/lib/hecks/bluebook/assembly/specializer.rb +19 -0
  39. data/lib/hecks/bluebook/assembly.rb +25 -7
  40. data/lib/hecks/bluebook/attribute.rb +17 -3
  41. data/lib/hecks/bluebook/behaviour/aggregate.rb +32 -0
  42. data/lib/hecks/bluebook/behaviour/attribute.rb +13 -0
  43. data/lib/hecks/bluebook/behaviour/chapter.rb +55 -2
  44. data/lib/hecks/bluebook/behaviour/command.rb +33 -3
  45. data/lib/hecks/bluebook/behaviour/domain_port.rb +20 -0
  46. data/lib/hecks/bluebook/behaviour/entity.rb +12 -0
  47. data/lib/hecks/bluebook/behaviour/hexagon.rb +26 -0
  48. data/lib/hecks/bluebook/behaviour/lifecycle.rb +23 -2
  49. data/lib/hecks/bluebook/behaviour/policy.rb +34 -9
  50. data/lib/hecks/bluebook/behaviour/process_manager.rb +32 -1
  51. data/lib/hecks/bluebook/behaviour/query.rb +5 -0
  52. data/lib/hecks/bluebook/behaviour/read_model.rb +21 -0
  53. data/lib/hecks/bluebook/behaviour/traits.rb +36 -0
  54. data/lib/hecks/bluebook/behaviour/value_object.rb +17 -5
  55. data/lib/hecks/bluebook/chapter.rb +23 -0
  56. data/lib/hecks/bluebook/command.rb +53 -8
  57. data/lib/hecks/bluebook/domain_port.rb +25 -0
  58. data/lib/hecks/bluebook/dsl/aggregate_builder.rb +196 -37
  59. data/lib/hecks/bluebook/dsl/attribute_collector.rb +79 -42
  60. data/lib/hecks/bluebook/dsl/binding_proxy.rb +59 -0
  61. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +385 -53
  62. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +135 -21
  63. data/lib/hecks/bluebook/dsl/command_builder.rb +189 -27
  64. data/lib/hecks/bluebook/dsl/entity_builder.rb +139 -9
  65. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +41 -0
  66. data/lib/hecks/bluebook/dsl/identity_declaration.rb +23 -6
  67. data/lib/hecks/bluebook/dsl/policy_builder.rb +60 -19
  68. data/lib/hecks/bluebook/dsl/process_manager_builder.rb +79 -15
  69. data/lib/hecks/bluebook/dsl/query_builder.rb +33 -4
  70. data/lib/hecks/bluebook/dsl/read_model_builder.rb +102 -27
  71. data/lib/hecks/bluebook/dsl/rule_reference.rb +60 -8
  72. data/lib/hecks/bluebook/dsl/translation_builder.rb +146 -40
  73. data/lib/hecks/bluebook/dsl/value_object_builder.rb +55 -7
  74. data/lib/hecks/bluebook/entity.rb +29 -0
  75. data/lib/hecks/bluebook/expression/ast_json.rb +115 -23
  76. data/lib/hecks/bluebook/expression/ast_reader.rb +29 -0
  77. data/lib/hecks/bluebook/expression/canonical_form.rb +48 -9
  78. data/lib/hecks/bluebook/expression/evaluator.rb +207 -29
  79. data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +36 -0
  80. data/lib/hecks/bluebook/expression/resolver.rb +320 -77
  81. data/lib/hecks/bluebook/hexagon.rb +34 -0
  82. data/lib/hecks/bluebook/lifecycle.rb +11 -0
  83. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -0
  84. data/lib/hecks/bluebook/meta_validator/judge.rb +30 -26
  85. data/lib/hecks/bluebook/meta_validator/plan.rb +43 -8
  86. data/lib/hecks/bluebook/meta_validator/port_judge.rb +1 -0
  87. data/lib/hecks/bluebook/meta_validator/readings.rb +157 -7
  88. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -4
  89. data/lib/hecks/bluebook/meta_validator/shapes.rb +141 -0
  90. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +149 -23
  91. data/lib/hecks/bluebook/meta_validator/translation_judge.rb +5 -4
  92. data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -0
  93. data/lib/hecks/bluebook/meta_validator.rb +180 -84
  94. data/lib/hecks/bluebook/model_check.rb +268 -24
  95. data/lib/hecks/bluebook/pattern_subset.rb +23 -1
  96. data/lib/hecks/bluebook/process_manager.rb +13 -0
  97. data/lib/hecks/bluebook/project_discovery.rb +5 -0
  98. data/lib/hecks/bluebook/project_loader.rb +40 -0
  99. data/lib/hecks/bluebook/project_register.rb +44 -0
  100. data/lib/hecks/bluebook/query.rb +27 -0
  101. data/lib/hecks/bluebook/read_model.rb +21 -1
  102. data/lib/hecks/bluebook/reference.rb +21 -8
  103. data/lib/hecks/bluebook/smoke_test.rb +29 -6
  104. data/lib/hecks/bluebook/synthesizer.rb +34 -0
  105. data/lib/hecks/bluebook/translation.rb +30 -1
  106. data/lib/hecks/bluebook/value_object.rb +23 -5
  107. data/lib/hecks/bluebook.rb +3 -4
  108. data/lib/hecks/codemod.rb +107 -20
  109. data/lib/hecks/construct.rb +15 -1
  110. data/lib/hecks/corpus.rb +146 -25
  111. data/lib/hecks/deploy/bluebook/deploy.bluebook +105 -0
  112. data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
  113. data/lib/hecks/deploy/oidc.json +5 -0
  114. data/lib/hecks/doc/reference.rb +185 -16
  115. data/lib/hecks/embryonaut_bluebook.rb +32 -9
  116. data/lib/hecks/facade/handle.rb +76 -3
  117. data/lib/hecks/facade/surface/aggregate_door.rb +8 -0
  118. data/lib/hecks/forms/field_shape.rb +3 -0
  119. data/lib/hecks/forms/page.rb +14 -0
  120. data/lib/hecks/forms/port_argument.rb +12 -0
  121. data/lib/hecks/forms/query_form_renderer.rb +63 -0
  122. data/lib/hecks/forms/record_renderer.rb +58 -0
  123. data/lib/hecks/forms/record_table.rb +27 -0
  124. data/lib/hecks/forms/reference_options.rb +24 -0
  125. data/lib/hecks/forms/value_object_shape.rb +10 -0
  126. data/lib/hecks/fqn.rb +58 -0
  127. data/lib/hecks/framework/bluebook/compliance.bluebook +221 -0
  128. data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
  129. data/lib/hecks/framework/oidc.json +15 -0
  130. data/lib/hecks/framework.rb +43 -20
  131. data/lib/hecks/freezer.rb +17 -1
  132. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +159 -24
  133. data/lib/hecks/fuzzing/combination_miner.rb +59 -0
  134. data/lib/hecks/fuzzing/concurrent_dispatch.rb +109 -8
  135. data/lib/hecks/fuzzing/coverage_campaign.rb +56 -13
  136. data/lib/hecks/fuzzing/differential.rb +34 -0
  137. data/lib/hecks/fuzzing/domain_generator.rb +188 -11
  138. data/lib/hecks/fuzzing/era_boundary.rb +45 -15
  139. data/lib/hecks/fuzzing/form_census.rb +86 -0
  140. data/lib/hecks/fuzzing/generated_domain_check.rb +76 -0
  141. data/lib/hecks/fuzzing/invalid_value_generator.rb +39 -0
  142. data/lib/hecks/fuzzing/isolated_boot.rb +79 -22
  143. data/lib/hecks/fuzzing/nondeterministic.rb +13 -1
  144. data/lib/hecks/fuzzing/persistence_parity.rb +95 -3
  145. data/lib/hecks/fuzzing/properties/corrections.rb +25 -0
  146. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +158 -14
  147. data/lib/hecks/fuzzing/properties/guards.rb +44 -0
  148. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +48 -0
  149. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +18 -0
  150. data/lib/hecks/fuzzing/properties/outbox.rb +49 -11
  151. data/lib/hecks/fuzzing/properties/querying.rb +68 -14
  152. data/lib/hecks/fuzzing/properties.rb +24 -15
  153. data/lib/hecks/fuzzing/qa_settings.rb +12 -0
  154. data/lib/hecks/fuzzing/replay.rb +137 -29
  155. data/lib/hecks/fuzzing/rotation_priority.rb +41 -21
  156. data/lib/hecks/fuzzing/rust_gap_manifest.rb +46 -20
  157. data/lib/hecks/fuzzing/self_consistency.rb +189 -40
  158. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +12 -6
  159. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +1 -1
  160. data/lib/hecks/fuzzing/sequence_generator.rb +47 -0
  161. data/lib/hecks/fuzzing/shrinker.rb +114 -2
  162. data/lib/hecks/fuzzing/structural_skips.rb +18 -4
  163. data/lib/hecks/fuzzing/sweep_depth.rb +8 -0
  164. data/lib/hecks/fuzzing/target_capabilities.rb +61 -14
  165. data/lib/hecks/fuzzing/value_generator.rb +98 -10
  166. data/lib/hecks/grammar/evolve.rb +178 -2
  167. data/lib/hecks/grammar.rb +46 -0
  168. data/lib/hecks/ir.rb +38 -7
  169. data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
  170. data/lib/hecks/literal.rb +32 -0
  171. data/lib/hecks/naming.rb +88 -7
  172. data/lib/hecks/ports/access_control.rb +5 -10
  173. data/lib/hecks/ports/authorization.rb +3 -6
  174. data/lib/hecks/ports/identity_assignment.rb +1 -2
  175. data/lib/hecks/ports/identity_resolution.rb +1 -2
  176. data/lib/hecks/ports/key_vault.port +6 -0
  177. data/lib/hecks/ports/key_vault.rb +85 -0
  178. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +6 -0
  179. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +38 -2
  180. data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -0
  181. data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +40 -0
  182. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +92 -1
  183. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
  184. data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +8 -0
  185. data/lib/hecks/ports/query/in_memory.rb +39 -6
  186. data/lib/hecks/ports/query/ordering.rb +15 -0
  187. data/lib/hecks/ports.rb +1 -0
  188. data/lib/hecks/projections/bootstrap_table.rb +43 -8
  189. data/lib/hecks/projections/deploy/fargate.rb +666 -0
  190. data/lib/hecks/projections/deploy/lambda.rb +2423 -0
  191. data/lib/hecks/projections/deploy/shared.rb +624 -0
  192. data/lib/hecks/projections/diagrams.rb +243 -7
  193. data/lib/hecks/projections/glossary/html.rb +88 -0
  194. data/lib/hecks/projections/glossary/markdown.rb +47 -0
  195. data/lib/hecks/projections/glossary/mermaid.rb +48 -0
  196. data/lib/hecks/projections/glossary/sentences.rb +105 -0
  197. data/lib/hecks/projections/glossary.rb +161 -19
  198. data/lib/hecks/projections/model/deviations.rb +44 -0
  199. data/lib/hecks/projections/model.rb +51 -1
  200. data/lib/hecks/projections/oidc.rb +40 -3
  201. data/lib/hecks/projections/parser_table.rb +49 -8
  202. data/lib/hecks/projections/reference.rb +12 -0
  203. data/lib/hecks/projections/rust_vocabulary.rb +219 -16
  204. data/lib/hecks/projections/shape.rb +8 -0
  205. data/lib/hecks/projections/statements.rb +63 -16
  206. data/lib/hecks/projections/vocabulary.rb +17 -0
  207. data/lib/hecks/projections.rb +3 -0
  208. data/lib/hecks/projector/cli_projector.rb +218 -10
  209. data/lib/hecks/projector/docs_projector.rb +145 -19
  210. data/lib/hecks/projector/exporter.rb +65 -11
  211. data/lib/hecks/projector/ir_projector.rb +6 -0
  212. data/lib/hecks/projector/narrate_projector.rb +136 -15
  213. data/lib/hecks/projector/target.rb +47 -10
  214. data/lib/hecks/projector.rb +101 -19
  215. data/lib/hecks/query_ir.rb +47 -0
  216. data/lib/hecks/query_specification/common/null_policy.rb +5 -3
  217. data/lib/hecks/rendering.rb +6 -0
  218. data/lib/hecks/router/namespace_installer.rb +13 -0
  219. data/lib/hecks/router.rb +55 -0
  220. data/lib/hecks/runtime/aggregate_lock.rb +9 -0
  221. data/lib/hecks/runtime/boot_gates.rb +18 -0
  222. data/lib/hecks/runtime/caller.rb +32 -0
  223. data/lib/hecks/runtime/capability_graph.rb +11 -0
  224. data/lib/hecks/runtime/command_interpreter/argument_gate.rb +23 -21
  225. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +14 -15
  226. data/lib/hecks/runtime/command_interpreter.rb +42 -17
  227. data/lib/hecks/runtime/command_rules/admissibility.rb +165 -14
  228. data/lib/hecks/runtime/command_rules/arithmetic.rb +17 -5
  229. data/lib/hecks/runtime/command_rules/references.rb +118 -28
  230. data/lib/hecks/runtime/dependency_planning.rb +45 -0
  231. data/lib/hecks/runtime/dispatcher.rb +28 -50
  232. data/lib/hecks/runtime/entity_element.rb +161 -8
  233. data/lib/hecks/runtime/entity_interpreter.rb +44 -9
  234. data/lib/hecks/runtime/errors.rb +18 -4
  235. data/lib/hecks/runtime/event.rb +10 -5
  236. data/lib/hecks/runtime/identity.rb +71 -3
  237. data/lib/hecks/runtime/instance.rb +67 -7
  238. data/lib/hecks/runtime/interpreting.rb +13 -5
  239. data/lib/hecks/runtime/invocation.rb +118 -36
  240. data/lib/hecks/runtime/loader.rb +94 -8
  241. data/lib/hecks/runtime/outbox.rb +145 -7
  242. data/lib/hecks/runtime/policy_interpreter.rb +22 -9
  243. data/lib/hecks/runtime/port_operation_interpreter.rb +20 -0
  244. data/lib/hecks/runtime/query_interpreter.rb +40 -12
  245. data/lib/hecks/runtime/reaction_invocation.rb +53 -8
  246. data/lib/hecks/runtime/read_model_interpreter.rb +23 -7
  247. data/lib/hecks/runtime/rebuild_sweep.rb +28 -0
  248. data/lib/hecks/runtime/reference_hop.rb +42 -0
  249. data/lib/hecks/runtime/refusal_wording.rb +50 -0
  250. data/lib/hecks/runtime/registry/saga_persistence.rb +11 -0
  251. data/lib/hecks/runtime/registry/verification.rb +119 -4
  252. data/lib/hecks/runtime/registry.rb +157 -4
  253. data/lib/hecks/runtime/remote_dispatcher.rb +92 -6
  254. data/lib/hecks/runtime/routing.rb +27 -2
  255. data/lib/hecks/runtime/saga_interpreter/correlation.rb +10 -12
  256. data/lib/hecks/runtime/saga_interpreter.rb +27 -13
  257. data/lib/hecks/runtime/tenant_check.rb +26 -6
  258. data/lib/hecks/runtime/tenant_scope.rb +18 -0
  259. data/lib/hecks/runtime/value/coercion.rb +255 -33
  260. data/lib/hecks/runtime/value/entity_list_coercion.rb +102 -30
  261. data/lib/hecks/runtime/value.rb +50 -0
  262. data/lib/hecks/runtime.rb +32 -0
  263. data/lib/hecks/storehouse.rb +305 -9
  264. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
  265. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
  266. data/lib/hecks/version.rb +1 -1
  267. data/lib/hecks.rb +79 -1
  268. data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -0
  269. data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +12 -2
  270. data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +29 -5
  271. metadata +14 -5
  272. data/lib/hecks/codemod/legacy_dispatch_args.rb +0 -299
  273. data/lib/hecks/codemod/legacy_dispatch_recorder.rb +0 -186
  274. data/lib/hecks/deprecation.rb +0 -95
  275. data/lib/hecks/framework/bluebook/compliance.bluebook +0 -1
@@ -16,25 +16,47 @@ module Hecks
16
16
 
17
17
  module_function
18
18
 
19
+ # Projects the Rust parser's keyword and closed-set tables for one chapter.
20
+ #
21
+ # @param bluebook [Bluebook::Chapter] the chapter to project; the language's own
22
+ # for the ordinary `bin/project_parser_table` run
23
+ # @param options [Hash{Symbol => Object}] ignored; present to satisfy the
24
+ # `Projector::Target` calling convention
25
+ # @return [String] the rendered Rust source
19
26
  def call(bluebook:, options: {}) = render(bluebook)
20
27
 
21
28
  module_function
22
29
 
23
- # The chapter is handed over, not reached for. This used to open the
24
- # grammar registry itself, which meant the projection could only ever
25
- # project one chapter — the language's own. Taking it as an argument is
26
- # what the projector protocol asks for, and it costs nothing.
30
+ # The chapter is handed over, not reached for — reaching for the grammar
31
+ # registry directly would pin the projection to one chapter, the language's
32
+ # own, and the projector protocol already hands over the chapter as an
33
+ # argument, at no cost.
34
+ #
35
+ # @param bluebook [Bluebook::Chapter] the chapter to read the Syntax aggregate
36
+ # from
37
+ # @return [Bluebook::Aggregate, nil] the chapter's `"Syntax"` aggregate, `nil` if
38
+ # it declares none
27
39
  def syntax(bluebook) = bluebook.aggregate("Syntax")
28
40
 
29
41
  # Every cell as text — exactly spec/syntax_conformance_spec.rb's own
30
42
  # `rows` helper, reused rather than re-derived: a member's fields decode
31
43
  # back through typed literal decoding on the way out of reconstruction,
32
44
  # and this reads it back as what was written.
45
+ #
46
+ # @param bluebook [Bluebook::Chapter] the chapter to read from
47
+ # @param name [String] the closed-set value object's name, such as `"Context"`
48
+ # @return [Array<Hash{Symbol => String}>] one row per declared member, every
49
+ # field stringified
33
50
  def rows(bluebook, name)
34
51
  syntax(bluebook).value_objects.find { |vo| vo.hecks_name == name }
35
52
  .members.map { |row| row.to_h.transform_values(&:to_s) }
36
53
  end
37
54
 
55
+ # Reads one closed set's member names, for a Rust `&[&str]` constant.
56
+ #
57
+ # @param bluebook [Bluebook::Chapter] the chapter to read from
58
+ # @param name [String] the closed-set value object's name
59
+ # @return [Array<String>] every declared member's `:name` field
38
60
  def closed_set_members(bluebook, name) = rows(bluebook, name).map { |row| row[:name] }
39
61
 
40
62
  KEYWORD_FIELDS = %i[word context body inner opens fills status was resolves_via disambiguator].freeze
@@ -45,18 +67,34 @@ module Hecks
45
67
  # plain ASCII (a word, a context name, a digit, "true"/"false"), so this
46
68
  # only has to be safe against the two characters Rust string literals
47
69
  # themselves reserve.
70
+ #
71
+ # @param value [Object] the field value to render; stringified before escaping
72
+ # @return [String] a quoted Rust string literal
48
73
  def rust_string(value) = "\"#{value.to_s.gsub('\\', '\\\\\\\\').gsub('"', '\\"')}\""
49
74
 
75
+ # Renders one `KEYWORDS` table entry.
76
+ #
77
+ # @param row [Hash{Symbol => String}] one keyword row, keyed by `KEYWORD_FIELDS`
78
+ # @return [String] one `KeywordRow { ... },` line
50
79
  def keyword_row(row)
51
80
  fields = KEYWORD_FIELDS.map { |field| rust_string(row[field]) }
52
81
  " KeywordRow { #{KEYWORD_FIELDS.zip(fields).map { |name, value| "#{name}: #{value}" }.join(', ')} },"
53
82
  end
54
83
 
84
+ # Renders one `ARGUMENTS` table entry.
85
+ #
86
+ # @param row [Hash{Symbol => String}] one argument row, keyed by `ARGUMENT_FIELDS`
87
+ # @return [String] one `ArgumentRow { ... },` line
55
88
  def argument_row(row)
56
89
  fields = ARGUMENT_FIELDS.map { |field| rust_string(row[field]) }
57
90
  " ArgumentRow { #{ARGUMENT_FIELDS.zip(fields).map { |name, value| "#{name}: #{value}" }.join(', ')} },"
58
91
  end
59
92
 
93
+ # Renders one `pub static NAME: &[&str]` constant.
94
+ #
95
+ # @param name [String] the Rust constant's name
96
+ # @param values [Array<String>] the constant's member strings
97
+ # @return [String] the rendered `pub static` declaration
60
98
  def const_str_array(name, values)
61
99
  lines = values.map { |value| " #{rust_string(value)}," }
62
100
  "pub static #{name}: &[&str] = &[\n#{lines.join("\n")}\n];\n"
@@ -66,10 +104,13 @@ module Hecks
66
104
  # now, dispatched (not merely declared) so their own `status`
67
105
  # really is a lifecycle. `SyntaxBoot.call` reads the still-static
68
106
  # seed rows (`KeywordSeed`/`ArgumentSeed`), dispatches each one
69
- # through the real admission/lifecycle door, and hands back the
70
- # exact same shape `rows` used to read straight off the closed set
71
- # — symbol keys, string values, `status` included — so nothing
72
- # else in this file needed to change.
107
+ # through the real admission/lifecycle door, and hands back rows in
108
+ # the same shape `rows` reads for every other closed set here —
109
+ # symbol keys, string values, `status` included — so nothing else
110
+ # in this file needs to change.
111
+ #
112
+ # @param bluebook [Bluebook::Chapter] the chapter to project
113
+ # @return [String] the rendered `KEYWORDS`/`ARGUMENTS`/closed-set Rust source
73
114
  def render(bluebook)
74
115
  table = Hecks::Bluebook::MetaValidator::SyntaxBoot.call
75
116
  keywords = table[:keywords]
@@ -25,6 +25,18 @@ module Hecks
25
25
 
26
26
  module_function
27
27
 
28
+ # Renders one reference page per DSL keyword context, carrying prose
29
+ # over from whatever is already committed under `options[:from]`.
30
+ #
31
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter declaring the
32
+ # Syntax aggregate to render pages from; unused beyond admission, since the
33
+ # keyword table this reads comes from the global `Syntax` grammar
34
+ # @param options [Hash] must include `:from`
35
+ # @option options [String] :from directory holding the already-committed
36
+ # reference pages, read to harvest their prose
37
+ # @return [Hash{String => String}] each page's filename => its rendered
38
+ # Markdown, including `"index.md"`
39
+ # @raise [ArgumentError] if `options[:from]` is missing or falsy
28
40
  def call(bluebook:, options: {})
29
41
  from = options[:from] or
30
42
  raise ArgumentError,
@@ -4,7 +4,7 @@ require_relative "vocabulary"
4
4
 
5
5
  module Hecks
6
6
  module Projections
7
- # **The language's closed sets, projected into the Rust kernel** — one
7
+ # The language's closed sets, projected into the Rust kernel — one
8
8
  # `rust/src/kernel/vocab/<table>.rs` per Vocabulary table, each a
9
9
  # `pub enum` plus `match`-based accessors with no wildcard arm, so a
10
10
  # row the chapter gains is a compile error at every exhaustive match
@@ -18,21 +18,24 @@ module Hecks
18
18
  # which read Runtime::RefusalWording::TEMPLATES — a hand-typed copy of
19
19
  # the RefusalTemplate rows. This reads the chapter itself.
20
20
  #
21
- # **Only TABLES the kernel needs**. Each entry in TABLES names its enum
22
- # and its generated extras; a table not listed is not projected. The
23
- # enum names that differ from the table name keep existing Rust call
24
- # sites (`RefusalSite`) and the dispatch-step names D1 consumes
25
- # (`AggregateStep`, `EntityStep`).
21
+ # ## Only `TABLES` the kernel needs
26
22
  #
27
- # TYPED REFUSAL ARGUMENTS. The templates table also reads
28
- # RefusalSiteArgument: every site gets a `<Variant>Args` struct whose
29
- # fields are exactly the site's declared arguments (a `&str` per
30
- # scalar, a `&[&str]` per list) and a `render_args` that formats each
31
- # one by its row. `render` itself stays private to the generated
32
- # module, so no call site can pass an argument list by hand — leaving
33
- # one out does not compile. The generated test pins every site's
34
- # `render_args` output (empty, single and multiple unsorted lists)
35
- # against Runtime::RefusalWording.render_with, computed here in Ruby.
23
+ # Each entry in `TABLES` names its enum and its generated extras; a
24
+ # table not listed is not projected. The enum names that differ from
25
+ # the table name keep existing Rust call sites (`RefusalSite`) and the
26
+ # dispatch-step names D1 consumes (`AggregateStep`, `EntityStep`).
27
+ #
28
+ # ## Typed refusal arguments
29
+ #
30
+ # The templates table also reads RefusalSiteArgument: every site gets a
31
+ # `<Variant>Args` struct whose fields are exactly the site's declared
32
+ # arguments (a `&str` per scalar, a `&[&str]` per list) and a
33
+ # `render_args` that formats each one by its row. `render` itself stays
34
+ # private to the generated module, so no call site can pass an argument
35
+ # list by hand — leaving one out does not compile. The generated test
36
+ # pins every site's `render_args` output (empty, single and multiple
37
+ # unsorted lists) against Runtime::RefusalWording.render_with, computed
38
+ # here in Ruby.
36
39
  module RustVocabulary
37
40
  extend Projector::Target
38
41
 
@@ -62,8 +65,24 @@ module Hecks
62
65
 
63
66
  module_function
64
67
 
68
+ # The projector protocol.
69
+ #
70
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter declaring the
71
+ # Vocabulary aggregate to project
72
+ # @param options [Hash] unused; accepted to satisfy the registry's call shape
73
+ # @return [Hash{String => String}] each generated file's path, relative to
74
+ # `rust/src/kernel/`, mapped to its full Rust source
65
75
  def call(bluebook:, options: {}) = render(bluebook)
66
76
 
77
+ # Renders every file `TABLES` projects: `vocab/mod.rs` plus one `.rs` file
78
+ # per table, each an exhaustive enum with accessors and a pinned test module.
79
+ #
80
+ # @param bluebook [Bluebook::Behaviour::Chapter] the chapter declaring the
81
+ # Vocabulary aggregate to render
82
+ # @return [Hash{String => String}] each generated file's path, relative to
83
+ # `rust/src/kernel/`, mapped to its full Rust source
84
+ # @raise [ArgumentError] if `bluebook` does not declare a `TABLES`-named table,
85
+ # or the `:arguments` table a `:templates` entry names
67
86
  def render(bluebook)
68
87
  tables = Vocabulary.tables(bluebook)
69
88
  files = TABLES.to_h do |table, spec|
@@ -76,6 +95,11 @@ module Hecks
76
95
  { "vocab/mod.rs" => mod_file }.merge(files)
77
96
  end
78
97
 
98
+ # The generated-file banner every projected file opens with.
99
+ #
100
+ # @param table [String] the Vocabulary table name being projected, such as
101
+ # `"RefusalTemplate"`
102
+ # @return [String] the banner, as Rust line comments
79
103
  def header(table)
80
104
  <<~RUST
81
105
  // GENERATED by #{GENERATOR} (Hecks::Projections::RustVocabulary,
@@ -85,6 +109,10 @@ module Hecks
85
109
  RUST
86
110
  end
87
111
 
112
+ # Renders `vocab/mod.rs`, the entry point that re-exports every projected enum.
113
+ #
114
+ # @return [String] `vocab/mod.rs`'s full source: one `pub mod` and `pub use`
115
+ # per entry in `TABLES`
88
116
  def mod_file
89
117
  mods = TABLES.values.map { |spec| "pub mod #{spec[:file]};" }.join("\n")
90
118
  exports = TABLES.values.map { |spec| "pub use #{spec[:file]}::#{spec[:enum]};" }.join("\n")
@@ -104,6 +132,18 @@ module Hecks
104
132
  RUST
105
133
  end
106
134
 
135
+ # One table's full `.rs` source: its exhaustive enum, per-field accessors,
136
+ # `from_name`, its `:order`/`:templates` extras, and its `#[cfg(test)]` module.
137
+ #
138
+ # @param table [String] the Vocabulary table name, such as `"RefusalTemplate"`
139
+ # @param spec [Hash] the table's `TABLES` entry (`:enum`, `:variant_from`,
140
+ # `:file`, `:kind`, and, for `:templates`, `:arguments`)
141
+ # @param rows [Array<Hash{String => String}>] the table's rows, as read off
142
+ # the chapter by `Vocabulary.tables`
143
+ # @param argument_rows [Array<Hash{String => String}>, nil] the
144
+ # `RefusalSiteArgument` rows, only when `spec[:kind] == :templates`
145
+ # @return [String] the table's full `.rs` file source
146
+ # @raise [ArgumentError] if two rows would generate the same enum variant name
107
147
  def table_file(table, spec, rows, argument_rows = nil)
108
148
  variants = rows.map { |row| variant_name(row, spec[:variant_from]) }
109
149
  duplicates = variants.tally.select { |_, count| count > 1 }.keys
@@ -132,6 +172,14 @@ module Hecks
132
172
  "#{body.flatten.join("\n").rstrip}\n"
133
173
  end
134
174
 
175
+ # Renders the enum's declared-order constant: `ORDER` for `:order`, `ALL` otherwise.
176
+ #
177
+ # @param enum [String] the Rust enum name, such as `"RefusalSite"`
178
+ # @param variants [Array<String>] each row's generated variant name, in
179
+ # declared order
180
+ # @param kind [Symbol] the table's `:kind` (`:order`, `:templates`, or `:set`)
181
+ # @return [Array<String>] the lines declaring the enum's `ORDER` constant
182
+ # (`:order`) or `ALL` constant (otherwise)
135
183
  def constants(enum, variants, kind)
136
184
  list = variants.map { |variant| " #{enum}::#{variant}," }
137
185
  if kind == :order
@@ -143,6 +191,16 @@ module Hecks
143
191
  end
144
192
  end
145
193
 
194
+ # Renders one field's `pub fn` accessor, an exhaustive match over every variant.
195
+ #
196
+ # @param enum [String] the Rust enum name
197
+ # @param field [String] the row field this accessor reads, such as `"name"`
198
+ # @param variants [Array<String>] each row's generated variant name, in
199
+ # declared order, matching `rows`
200
+ # @param rows [Array<Hash{String => String}>] the table's rows, in the same
201
+ # order as `variants`
202
+ # @return [Array<String>] the lines for the field's `pub fn` accessor, one
203
+ # match arm per variant
146
204
  def accessor(enum, field, variants, rows)
147
205
  arms = variants.zip(rows).map { |variant, row| " #{enum}::#{variant} => #{rust_string(row.fetch(field))}," }
148
206
  [" /// The row's declared `#{field}`.",
@@ -156,6 +214,10 @@ module Hecks
156
214
 
157
215
  # Built on the enum's own list and name accessor, not a string
158
216
  # match, so it needs no wildcard arm either.
217
+ #
218
+ # @param enum [String] the Rust enum name
219
+ # @param kind [Symbol] the table's `:kind` (`:order`, `:templates`, or `:set`)
220
+ # @return [Array<String>] the lines for the enum's `from_name` associated function
159
221
  def from_name(enum, kind)
160
222
  list = kind == :order ? "ORDER" : "ALL"
161
223
  field = kind == :templates ? "site" : name_field(kind)
@@ -166,6 +228,14 @@ module Hecks
166
228
  ""]
167
229
  end
168
230
 
231
+ # Renders the extra methods a table's `:kind` adds beyond the shared accessors.
232
+ #
233
+ # @param enum [String] the Rust enum name
234
+ # @param variants [Array<String>] each row's generated variant name, in
235
+ # declared order
236
+ # @param kind [Symbol] the table's `:kind` (`:order`, `:templates`, or `:set`)
237
+ # @return [Array<String>] `:order`'s `position` method, `:templates`'s private
238
+ # `render` method, or `[]` for `:set`
169
239
  def extras(enum, variants, kind)
170
240
  case kind
171
241
  when :order
@@ -196,6 +266,17 @@ module Hecks
196
266
  # [refusal, site] => that site's argument rows, in declared order —
197
267
  # refused unless they name exactly the template's own placeholders,
198
268
  # in the order each first appears, with only known formatting rules.
269
+ #
270
+ # @param rows [Array<Hash{String => String}>] the `RefusalTemplate` rows,
271
+ # in declared order
272
+ # @param argument_rows [Array<Hash{String => String}>] the `RefusalSiteArgument`
273
+ # rows for every site
274
+ # @return [Hash{Array<String> => Array<Hash{String => String}>}] each
275
+ # `[refusal, site]` pair mapped to its declared argument rows, in declared order
276
+ # @raise [ArgumentError] if an argument row names a `refusal`/`site` no
277
+ # `RefusalTemplate` row declares, if a site's declared arguments do not match
278
+ # its template's `{placeholder}`s in order, or if `check_rule!` rejects a
279
+ # row's `"shape"`, `"quoting"`, or `"sorted"` value
199
280
  def site_arguments(rows, argument_rows)
200
281
  grouped = argument_rows.group_by { |row| [row.fetch("refusal"), row.fetch("site")] }
201
282
  orphans = grouped.keys - rows.map { |row| [row.fetch("refusal"), row.fetch("site")] }
@@ -215,6 +296,18 @@ module Hecks
215
296
  end
216
297
  end
217
298
 
299
+ # Refuses a `RefusalSiteArgument` row whose formatting fields are not
300
+ # recognized rules.
301
+ #
302
+ # @param key [Array<String>] the `[refusal, site]` pair `spec` belongs to,
303
+ # used in the raised message
304
+ # @param spec [Hash{String => String}] one `RefusalSiteArgument` row
305
+ # (`"shape"`, `"quoting"`, `"sorted"`)
306
+ # @return [void]
307
+ # @raise [ArgumentError] if `spec`'s `"shape"` is not in
308
+ # `Runtime::RefusalWording::SHAPES`, its `"quoting"` is not in
309
+ # `Runtime::RefusalWording::QUOTINGS`, or its `"sorted"` is not
310
+ # `"true"`/`"false"`
218
311
  def check_rule!(key, spec)
219
312
  where = "#{key.join('/')}.#{spec.fetch('argument')}"
220
313
  unless Runtime::RefusalWording::SHAPES.include?(spec.fetch("shape"))
@@ -228,6 +321,17 @@ module Hecks
228
321
  raise ArgumentError, "#{where}: sorted must be \"true\" or \"false\""
229
322
  end
230
323
 
324
+ # Renders every `:templates` variant's typed argument struct, plus the two
325
+ # helper functions (`quoted`, `list`) they share.
326
+ #
327
+ # @param enum [String] the Rust enum name
328
+ # @param variants [Array<String>] each row's generated variant name, matching `rows`
329
+ # @param rows [Array<Hash{String => String}>] the `RefusalTemplate` rows, in
330
+ # the same order as `variants`
331
+ # @param by_site [Hash{Array<String> => Array<Hash{String => String}>}] each
332
+ # `[refusal, site]` pair's declared argument rows, as built by `site_arguments`
333
+ # @return [Array<String>] the shared `quoted`/`list` helper functions, followed
334
+ # by every variant's `<Variant>Args` struct and `render_args` implementation
231
335
  def argument_types(enum, variants, rows, by_site)
232
336
  support = [
233
337
  "/// Ruby's `#inspect` of a name, as a refusal quotes it: `{:?}` on a",
@@ -258,6 +362,15 @@ module Hecks
258
362
  end
259
363
  end
260
364
 
365
+ # Renders one variant's `<Variant>Args` struct and its `render_args` method.
366
+ #
367
+ # @param enum [String] the Rust enum name
368
+ # @param variant [String] the row's generated variant name
369
+ # @param row [Hash{String => String}] the `RefusalTemplate` row this variant projects
370
+ # @param specs [Array<Hash{String => String}>] the variant's declared
371
+ # `RefusalSiteArgument` rows, in declared order
372
+ # @return [Array<String>] the lines for the variant's `<Variant>Args` struct
373
+ # and its `render_args` method
261
374
  def args_struct(enum, variant, row, specs)
262
375
  fields = specs.flat_map do |spec|
263
376
  type = spec.fetch("shape") == "list" ? "&'a [&'a str]" : "&'a str"
@@ -288,6 +401,15 @@ module Hecks
288
401
  ""]
289
402
  end
290
403
 
404
+ # The `let` binding, if any, `args_struct` inserts before a variant's
405
+ # `render_args` computes its argument pairs.
406
+ #
407
+ # @param spec [Hash{String => String}] one `RefusalSiteArgument` row
408
+ # (`"argument"`, `"shape"`, `"quoting"`, and, for a list, `"sorted"`,
409
+ # `"separator"`, `"when_empty"`)
410
+ # @return [String, nil] the `let` line formatting the argument through
411
+ # `list`/`quoted`, or `nil` if the argument needs no formatting local
412
+ # (a scalar written as-is)
291
413
  def formatted_local(spec)
292
414
  name = spec.fetch("argument")
293
415
  field = "self.#{rust_field(name)}"
@@ -299,6 +421,13 @@ module Hecks
299
421
  end
300
422
  end
301
423
 
424
+ # Describes one argument's formatting rule in English, for its struct field's doc comment.
425
+ #
426
+ # @param spec [Hash{String => String}] one `RefusalSiteArgument` row
427
+ # (`"shape"`, `"quoting"`, and, for a list, `"sorted"`, `"separator"`,
428
+ # `"when_empty"`)
429
+ # @return [String] a short English reading of the row's formatting rule,
430
+ # used as the struct field's own doc comment
302
431
  def rule_reading(spec)
303
432
  quoting = spec.fetch("quoting") == "inspect" ? ", quoted" : ""
304
433
  return "scalar#{quoting}" unless spec.fetch("shape") == "list"
@@ -307,10 +436,33 @@ module Hecks
307
436
  "list#{sorted}#{quoting}, joined #{spec.fetch('separator').inspect}, empty reads #{spec.fetch('when_empty').inspect}"
308
437
  end
309
438
 
439
+ # The local variable name `formatted_local` binds an argument's formatted value to.
440
+ #
441
+ # @param argument [String] the `RefusalSiteArgument` row's `"argument"` name
442
+ # @return [String] the Rust local variable name `formatted_local` binds the
443
+ # formatted value to
310
444
  def local_name(argument) = "#{argument}_text"
311
445
 
446
+ # An argument name, escaped for use as a Rust field/parameter identifier.
447
+ #
448
+ # @param argument [String] the `RefusalSiteArgument` row's `"argument"` name
449
+ # @return [String] `argument`, prefixed with `r#` if it collides with a Rust
450
+ # keyword in `RUST_KEYWORDS`
312
451
  def rust_field(argument) = RUST_KEYWORDS.include?(argument) ? "r##{argument}" : argument
313
452
 
453
+ # Renders the table's `#[cfg(test)]` module: every kind's `from_name` round-trip
454
+ # test, plus `:order`'s and `:templates`' own tests.
455
+ #
456
+ # @param enum [String] the Rust enum name
457
+ # @param kind [Symbol] the table's `:kind` (`:order`, `:templates`, or `:set`)
458
+ # @param variants [Array<String>] each row's generated variant name, only
459
+ # read when `kind == :templates`
460
+ # @param rows [Array<Hash{String => String}>] the table's rows, only read
461
+ # when `kind == :templates`
462
+ # @param by_site [Hash{Array<String> => Array<Hash{String => String}>}, nil]
463
+ # each `[refusal, site]` pair's declared argument rows, only read when
464
+ # `kind == :templates`
465
+ # @return [Array<String>] the `#[cfg(test)]` module's full source lines
314
466
  def tests(enum, kind, variants = [], rows = [], by_site = nil)
315
467
  list = kind == :order ? "ORDER" : "ALL"
316
468
  name = accessor_name(kind == :templates ? "site" : name_field(kind))
@@ -336,6 +488,11 @@ module Hecks
336
488
  lines + ["}"]
337
489
  end
338
490
 
491
+ # The `:order` kind's own test, checked into every order table's test module.
492
+ #
493
+ # @param enum [String] the Rust enum name
494
+ # @return [Array<String>] the test asserting `ORDER`'s index matches each
495
+ # variant's `position`
339
496
  def order_test(enum)
340
497
  [" #[test]",
341
498
  " fn position_is_the_index_in_order() {",
@@ -348,6 +505,10 @@ module Hecks
348
505
 
349
506
  # Placeholders are read off each template's own text, never a second
350
507
  # hand-kept list.
508
+ #
509
+ # @param enum [String] the Rust enum name
510
+ # @return [Array<String>] the test asserting every site's `render` leaves
511
+ # no `{...}` placeholder unfilled
351
512
  def placeholder_test(enum)
352
513
  [" #[test]",
353
514
  " fn every_site_renders_with_no_leftover_placeholder() {",
@@ -377,12 +538,21 @@ module Hecks
377
538
  " }"]
378
539
  end
379
540
 
380
- # THE RUBY ORACLE, PINNED. Every site renders through its typed
541
+ # The Ruby oracle, pinned. Every site renders through its typed
381
542
  # `render_args` for each edge case a list argument has — empty, one
382
543
  # item, several out of order — and must equal what
383
544
  # Runtime::RefusalWording.render_with answers for the same values,
384
545
  # computed here at generation time. Scalars carry a `"` so quoting
385
546
  # is compared too.
547
+ #
548
+ # @param variants [Array<String>] each row's generated variant name, matching `rows`
549
+ # @param rows [Array<Hash{String => String}>] the `RefusalTemplate` rows, in
550
+ # the same order as `variants`
551
+ # @param by_site [Hash{Array<String> => Array<Hash{String => String}>}] each
552
+ # `[refusal, site]` pair's declared argument rows, as built by `site_arguments`
553
+ # @return [Array<String>] the test asserting every variant's `render_args`
554
+ # output for each `argument_cases` case matches
555
+ # `Runtime::RefusalWording.render_with`, computed here in Ruby
386
556
  def render_args_test(variants, rows, by_site)
387
557
  asserts = variants.zip(rows).flat_map do |variant, row|
388
558
  specs = by_site.fetch([row.fetch("refusal"), row.fetch("site")])
@@ -407,6 +577,14 @@ module Hecks
407
577
 
408
578
  LIST_CASES = [[], ["only \"one\""], %w[zeta alpha mid]].freeze
409
579
 
580
+ # Builds the argument-value cases `render_args_test` checks a site's
581
+ # `render_args` against.
582
+ #
583
+ # @param specs [Array<Hash{String => String}>] one site's declared
584
+ # `RefusalSiteArgument` rows
585
+ # @return [Array<Hash{Symbol => Object}>] one argument Hash per test case —
586
+ # `LIST_CASES` (empty, one item, several unsorted) if any spec is
587
+ # list-shaped, else a single scalar case, each scalar value carrying a `"`
410
588
  def argument_cases(specs)
411
589
  cases = specs.any? { |spec| spec.fetch("shape") == "list" } ? LIST_CASES : [nil]
412
590
  cases.map do |items|
@@ -417,12 +595,29 @@ module Hecks
417
595
  end
418
596
  end
419
597
 
598
+ # The row field a table's `from_name`/round-trip test treats as its "name".
599
+ #
600
+ # @param kind [Symbol] the table's `:kind` (`:order`, `:templates`, or `:set`)
601
+ # @return [String] the row field used as this kind's "name" — `"step"` for
602
+ # `:order`, `"name"` otherwise
420
603
  def name_field(kind) = kind == :order ? "step" : "name"
421
604
 
422
605
  # `refusal` is a Rust-safe word but reads ambiguously beside the
423
606
  # kernel's own `Refusal` enum; the accessor says what it answers.
607
+ #
608
+ # @param field [String] a row field name, such as `"name"` or `"refusal"`
609
+ # @return [String] the Rust accessor method name for `field` —
610
+ # `"refusal_class"` for `"refusal"`, `field` otherwise
424
611
  def accessor_name(field) = field == "refusal" ? "refusal_class" : field
425
612
 
613
+ # Builds one row's Rust enum variant name from its declared `variant_from` fields.
614
+ #
615
+ # @param row [Hash{String => String}] the table row to name
616
+ # @param fields [Array<String>] the row fields, PascalCased and concatenated
617
+ # in order to build the variant name
618
+ # @return [String] the row's generated Rust enum variant name
619
+ # @raise [ArgumentError] if the built name is not a valid Rust variant
620
+ # identifier (uppercase first letter, alphanumeric only)
426
621
  def variant_name(row, fields)
427
622
  name = fields.map { |field| pascal(row.fetch(field)) }.join
428
623
  raise ArgumentError, "#{name.inspect} is not a Rust variant name" unless name.match?(/\A[A-Z][A-Za-z0-9]*\z/)
@@ -430,10 +625,18 @@ module Hecks
430
625
  name
431
626
  end
432
627
 
628
+ # PascalCases one field value for use inside a generated variant name.
629
+ #
630
+ # @param text [String, Symbol, nil] the value to PascalCase; each
631
+ # `_`-separated part's first letter is upcased
632
+ # @return [String] `text` converted to PascalCase, or `""` if `text` is `nil`
433
633
  def pascal(text) = text.to_s.split("_").map { |part| part[0].to_s.upcase + part[1..].to_s }.join
434
634
 
435
635
  # A Rust string literal: only `\`, `"` and newlines need escaping in
436
636
  # the text any vocabulary row holds.
637
+ #
638
+ # @param text [String] the value to render as a Rust string literal
639
+ # @return [String] `text`, backslash/quote/newline-escaped and wrapped in `"..."`
437
640
  def rust_string(text)
438
641
  escaped = text.to_s.gsub("\\") { "\\\\" }.gsub('"') { "\\\"" }.gsub("\n") { "\\n" }
439
642
  "\"#{escaped}\""
@@ -31,6 +31,14 @@ module Hecks
31
31
  # is "every file here registers," not "every projection always
32
32
  # succeeds") but refuses clearly if asked to run with the plugin
33
33
  # unloaded, rather than raising on an undefined constant.
34
+ # Projects the storage shape of `bluebook` — the structural form an era
35
+ # bump/no-bump question is answered by diffing.
36
+ #
37
+ # @param bluebook [Bluebook::Chapter] the bluebook to reduce to its storage shape
38
+ # @param options [Hash] unused; accepted to satisfy the registry's call shape
39
+ # @return [Hash{String => Object}] `Runtime::StorageShape.project`'s Hash: `"name"`
40
+ # and a sorted `"aggregates"` array
41
+ # @raise [RuntimeError] if the era persistence plugin is not loaded
34
42
  def call(bluebook:, options: {})
35
43
  unless Ports::Persistence.plugin?(:era)
36
44
  raise "the :shape projection needs the era persistence plugin loaded " \