hecks 3.9.1 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
- data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
- data/exe/hecks +4 -4
- data/lib/hecks/adapters/driven/d1.rb +2 -10
- data/lib/hecks/adapters/driven/heki.adapter +1 -1
- data/lib/hecks/adapters/driven/heki.rb +2 -12
- data/lib/hecks/adapters/driven/lambda.adapter +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +3 -18
- data/lib/hecks/adapters/driven/postgres.rb +4 -11
- data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
- data/lib/hecks/adapters/driven/sqlite.rb +2 -10
- data/lib/hecks/adapters/driving/admission.rb +46 -0
- data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
- data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
- data/lib/hecks/adapters/driving/cli.rb +127 -0
- data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +155 -0
- data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
- data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
- data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +69 -0
- data/lib/hecks/adapters/driving/command_request.rb +121 -0
- data/lib/hecks/adapters/driving/handle.rb +214 -0
- data/lib/hecks/adapters/driving/json.rb +136 -0
- data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
- data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
- data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
- data/lib/hecks/adapters/driving/mcp.rb +159 -0
- data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
- data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
- data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
- data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
- data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
- data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
- data/lib/hecks/adapters/driving/ruby.rb +83 -0
- data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
- data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
- data/lib/hecks/behaviors/expectations/boot.rb +1 -1
- data/lib/hecks/bench/ruby_runner.rb +1 -1
- data/lib/hecks/bluebook/ask_resolution.rb +130 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
- data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
- data/lib/hecks/bluebook/assembly/marks.rb +1 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/command.rb +13 -8
- data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
- data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
- data/lib/hecks/bluebook/capabilities.rb +91 -9
- data/lib/hecks/bluebook/domain_port.rb +16 -4
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
- data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
- data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
- data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
- data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
- data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
- data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
- data/lib/hecks/bluebook/expression/projection.json +36 -0
- data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
- data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
- data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
- data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
- data/lib/hecks/bluebook/expression/resolver.rb +20 -1
- data/lib/hecks/bluebook/hecksagon.rb +23 -3
- data/lib/hecks/bluebook/lifecycle.rb +22 -3
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
- data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/readings.rb +18 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
- data/lib/hecks/bluebook/meta_validator.rb +12 -12
- data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
- data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
- data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
- data/lib/hecks/bluebook/model_check.rb +8 -2
- data/lib/hecks/bluebook/policy.rb +4 -2
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- data/lib/hecks/bluebook/synthesizer.rb +2 -0
- data/lib/hecks/bluebook.rb +1 -0
- data/lib/hecks/cli/command_table.rb +1 -1
- data/lib/hecks/cli/console.rb +2 -1
- data/lib/hecks/cli/document.rb +1 -1
- data/lib/hecks/cli/launcher_words.rb +4 -4
- data/lib/hecks/cli/mcp.rb +4 -4
- data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
- data/lib/hecks/cli/project_cli/templates.rb +5 -5
- data/lib/hecks/cli/project_cli.rb +9 -6
- data/lib/hecks/cli/run.rb +2 -2
- data/lib/hecks/cli/smoke_http/checks.rb +4 -1
- data/lib/hecks/cli/smoke_http/transport.rb +3 -3
- data/lib/hecks/cli/smoke_http.rb +3 -0
- data/lib/hecks/codemod.rb +5 -2
- data/lib/hecks/corpus/routes.rb +7 -0
- data/lib/hecks/corpus/rust.rb +3 -2
- data/lib/hecks/deploy/bluebook/deploy.bluebook +124 -2
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +8 -0
- data/lib/hecks/deploy/oidc.json +16 -3
- data/lib/hecks/doc/reference/rendering.rb +6 -3
- data/lib/hecks/forms/app/commands.rb +8 -7
- data/lib/hecks/forms/app/queries.rb +2 -1
- data/lib/hecks/forms/app.rb +1 -1
- data/lib/hecks/forms/record_renderer.rb +1 -1
- data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
- data/lib/hecks/fuzzing/failure_triage.rb +2 -1
- data/lib/hecks/fuzzing/mutation/run.rb +11 -4
- data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
- data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
- data/lib/hecks/fuzzing/properties/guards.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency.rb +6 -2
- data/lib/hecks/fuzzing/shrinker.rb +3 -1
- data/lib/hecks/fuzzing/value_generator.rb +19 -5
- data/lib/hecks/gate/stages.yml +4 -10
- data/lib/hecks/grammar/evolve.rb +1 -1
- data/lib/hecks/grammar/expression_operators.json +372 -0
- data/lib/hecks/grammar/operators.rb +3 -1
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
- data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
- data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
- data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
- data/lib/hecks/hecks/adapters/deploy_toolchain/clearing.rb +51 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -0
- data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
- data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
- data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
- data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
- data/lib/hecks/hecks/adapters/local_files.rb +2 -2
- data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
- data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
- data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
- data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +129 -0
- data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
- data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
- data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
- data/lib/hecks/hecks/adapters/site_toolchain/role_prober.rb +78 -0
- data/lib/hecks/hecks/adapters/site_toolchain.rb +29 -4
- data/lib/hecks/hecks/adapters/terminal.rb +13 -11
- data/lib/hecks/hecks/adapters/world_clearing.rb +65 -0
- data/lib/hecks/hecks/codebase.behaviors +13 -1
- data/lib/hecks/hecks/codebase.bluebook +239 -5
- data/lib/hecks/hecks/custodian.behaviors +24 -6
- data/lib/hecks/hecks/custodian.bluebook +6 -1227
- data/lib/hecks/hecks/deploy.behaviors +1 -1
- data/lib/hecks/hecks/deploy.world +1 -3
- data/lib/hecks/hecks/hecks.behaviors +1 -1
- data/lib/hecks/hecks/hecks.hecksagon +61 -18
- data/lib/hecks/hecks/hecks.world +11 -10
- data/lib/hecks/hecks/rehearsal.bluebook +477 -0
- data/lib/hecks/hecks/site.behaviors +1 -1
- data/lib/hecks/hecks/site.world +0 -1
- data/lib/hecks/hecks/tooling.bluebook +1238 -0
- data/lib/hecks/indifferent_key.rb +20 -0
- data/lib/hecks/language/adapter.bluebook +13 -3
- data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
- data/lib/hecks/language/bluebook/entity.bluebook +18 -0
- data/lib/hecks/language/bluebook/policy.bluebook +9 -1
- data/lib/hecks/language/bluebook/query.bluebook +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
- data/lib/hecks/language/oidc.json +10 -0
- data/lib/hecks/language/port.bluebook +4 -4
- data/lib/hecks/literal.rb +12 -3
- data/lib/hecks/ports/key_vault.rb +7 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
- data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +15 -0
- data/lib/hecks/projections/deploy/fargate/settings.rb +3 -1
- data/lib/hecks/projections/deploy/fargate/stack/readers.rb +1 -0
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/shared.rb +5 -2
- data/lib/hecks/projections/deploy/templates/fargate/Makefile.tmpl +7 -4
- data/lib/hecks/projections/model/deviations.rb +2 -2
- data/lib/hecks/projections/model.rb +2 -2
- data/lib/hecks/projections/oidc.rb +13 -1
- data/lib/hecks/projections/shape.rb +5 -2
- data/lib/hecks/projections/site/cms_editor/attributes.rb +95 -0
- data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
- data/lib/hecks/projections/site/cms_editor/checks.rb +77 -0
- data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
- data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
- data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
- data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
- data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
- data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
- data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
- data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
- data/lib/hecks/projections/site/cms_editor/media.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
- data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
- data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/pretty.rb +40 -0
- data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
- data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
- data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
- data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
- data/lib/hecks/projections/site/cms_editor/schema.rb +161 -0
- data/lib/hecks/projections/site/cms_editor/setting.rb +130 -0
- data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +370 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/session.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/sso.ts.tmpl +20 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +133 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +29 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +177 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/multipart.ts.tmpl +53 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/sniff.ts.tmpl +94 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/storage.ts.tmpl +50 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +214 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/server.ts.tmpl +65 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +123 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_parse.js.tmpl +320 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +92 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +63 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
- data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +17 -0
- data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
- data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
- data/lib/hecks/projections/site/cms_editor.rb +133 -0
- data/lib/hecks/projections/site/role_probe.rb +74 -0
- data/lib/hecks/projector/cli_audience.rb +2 -1
- data/lib/hecks/projector/cli_projector/listing.rb +1 -1
- data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
- data/lib/hecks/projector/docs_projector/closing.rb +1 -1
- data/lib/hecks/projector/docs_projector.rb +1 -1
- data/lib/hecks/projector/exporter/bindings.rb +40 -10
- data/lib/hecks/projector/exporter/capability_values.rb +55 -0
- data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
- data/lib/hecks/projector/target.rb +11 -0
- data/lib/hecks/projector.rb +32 -5
- data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
- data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
- data/lib/hecks/quality_control/quality_control.bluebook +6 -2
- data/lib/hecks/release/approval.rb +64 -0
- data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
- data/lib/hecks/router/namespace_installer.rb +16 -16
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/best_effort.rb +22 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
- data/lib/hecks/runtime/command_interpreter.rb +3 -2
- data/lib/hecks/runtime/command_rules/emission.rb +1 -1
- data/lib/hecks/runtime/dispatcher.rb +4 -4
- data/lib/hecks/runtime/entity_element/location.rb +1 -1
- data/lib/hecks/runtime/event.rb +5 -0
- data/lib/hecks/runtime/identity.rb +7 -6
- data/lib/hecks/runtime/interpreting.rb +2 -1
- data/lib/hecks/runtime/invocation_builder.rb +1 -1
- data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
- data/lib/hecks/runtime/loader.rb +10 -10
- data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
- data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
- data/lib/hecks/runtime/policy_interpreter.rb +6 -6
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
- data/lib/hecks/runtime/reaction_invocation.rb +1 -1
- data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
- data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
- data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
- data/lib/hecks/runtime/registry/merging.rb +1 -1
- data/lib/hecks/runtime/registry/providers.rb +16 -0
- data/lib/hecks/runtime/registry/read_repository.rb +6 -4
- data/lib/hecks/runtime/registry/verification.rb +4 -1
- data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
- data/lib/hecks/runtime/registry.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
- data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter.rb +3 -3
- data/lib/hecks/runtime/tenant_check.rb +5 -4
- data/lib/hecks/runtime/value/coercion.rb +2 -2
- data/lib/hecks/runtime/value/validation.rb +16 -4
- data/lib/hecks/runtime.rb +9 -9
- data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
- data/lib/hecks/rust_build/wasm.rb +1 -1
- data/lib/hecks/rust_build/wasm_browser.rb +1 -1
- data/lib/hecks/rust_build.rb +11 -0
- data/lib/hecks/served_options.rb +53 -0
- data/lib/hecks/site/bluebook/site.bluebook +29 -1
- data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
- data/lib/hecks/storehouse/audit.rb +1 -1
- data/lib/hecks/storehouse/dispatching.rb +8 -6
- data/lib/hecks/storehouse/introspection.rb +2 -2
- data/lib/hecks/storehouse/querying.rb +6 -4
- data/lib/hecks/storehouse.rb +4 -4
- data/lib/hecks/tools/comment_style/source_file.rb +4 -1
- data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
- data/lib/hecks/tools/lanes/rows.rb +6 -0
- data/lib/hecks/tools/oidc_manifests.rb +4 -5
- data/lib/hecks/tools/regeneration_run.rb +11 -4
- data/lib/hecks/tools/site_routes/cli.rb +1 -1
- data/lib/hecks/tools/site_routes/editor_files.rb +45 -0
- data/lib/hecks/tools/site_routes.rb +14 -7
- data/lib/hecks/tools/tenant_provisioning.rb +2 -2
- data/lib/hecks/tools/tools_doc.rb +4 -4
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +4 -4
- data/lib/hecks.rb +15 -15
- data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
- data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
- data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
- data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
- data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
- data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
- data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
- data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
- data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
- data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
- data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
- data/rust/.gitignore +6 -0
- data/rust/Cargo.toml +9 -1
- data/rust/build/src/binding_pass.rs +103 -0
- data/rust/build/src/cargo_sync.rs +6 -0
- data/rust/build/src/lineage_pass.rs +3 -3
- data/rust/build/src/main.rs +17 -5
- data/rust/build/src/pipeline.rs +48 -5
- data/rust/build/src/seams_pass.rs +123 -0
- data/rust/build/src/translation_pass.rs +167 -0
- data/rust/codegen/src/asks.rs +103 -0
- data/rust/codegen/src/bridging.rs +1 -1
- data/rust/codegen/src/commands.rs +13 -8
- data/rust/codegen/src/constraints.rs +25 -0
- data/rust/codegen/src/domain_generator.rs +11 -9
- data/rust/codegen/src/expr_emitter.rs +13 -0
- data/rust/codegen/src/json_codec.rs +54 -15
- data/rust/codegen/src/main.rs +2 -1
- data/rust/codegen/src/mutations.rs +174 -4
- data/rust/codegen/src/naming.rs +30 -0
- data/rust/codegen/src/queries.rs +11 -2
- data/rust/codegen/src/reactions.rs +1 -1
- data/rust/codegen/src/types.rs +166 -8
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +4 -4
- data/rust/host/src/bin/validate_harness.rs +43 -0
- data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
- data/rust/host/src/boundary_fuzz/ir.rs +1 -0
- data/rust/host/src/boundary_fuzz/server.rs +1 -1
- data/rust/host/src/checkout.rs +35 -19
- data/rust/host/src/commerce_ir.rs +408 -2
- data/rust/host/src/expr_json.rs +118 -0
- data/rust/host/src/mint.rs +1 -1
- data/rust/host/src/payments/tests.rs +27 -1
- data/rust/host/src/payments.rs +12 -4
- data/rust/host/src/query_step.rs +22 -4
- data/rust/host/src/reference_validate.rs +653 -108
- data/rust/host/src/resend.rs +2 -0
- data/rust/host/src/server.rs +19 -6
- data/rust/host/src/ui_schema.rs +132 -5
- data/rust/host/src/web/newsletter.rs +7 -8
- data/rust/host/src/web/newsletter_send.rs +38 -13
- data/rust/host/src/web/registrations.rs +229 -26
- data/rust/host/src/web.rs +3 -3
- data/rust/parser/src/emit.rs +38 -6
- data/rust/parser/src/expr/ast_json.rs +7 -1
- data/rust/parser/src/expr/resolver.rs +171 -6
- data/rust/parser/src/ir.rs +7 -0
- data/rust/parser/src/keywords.rs +24 -0
- data/rust/parser/src/main.rs +3 -0
- data/rust/parser/src/parse/aggregate.rs +2 -1
- data/rust/parser/src/parse/chapter.rs +2 -2
- data/rust/parser/src/parse/command.rs +2 -0
- data/rust/parser/src/parse/hecksagon.rs +5 -2
- data/rust/parser/src/parse/lifecycle.rs +81 -0
- data/rust/parser/src/parse/mod.rs +15 -1
- data/rust/parser/src/parse/policy.rs +13 -2
- data/rust/parser/src/parse/value_object.rs +13 -8
- data/rust/parser/src/ruby_value.rs +1 -1
- data/rust/src/exemplar/commands.rs +4 -4
- data/rust/src/exemplar/constraints.rs +1 -1
- data/rust/src/exemplar/json.rs +43 -1
- data/rust/src/kernel/cli.rs +7 -5
- data/rust/src/kernel/dispatch.rs +2 -2
- data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
- data/rust/src/kernel/expr.rs +73 -4
- data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
- data/rust/src/kernel/expression_operators/presence.rs +41 -3
- data/rust/src/kernel/expression_operators/text.rs +26 -2
- data/rust/src/kernel/json.rs +15 -0
- data/rust/src/kernel/mod.rs +1 -1
- data/rust/src/kernel/orchestrate.rs +54 -5
- data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
- data/rust/src/kernel/pattern.rs +60 -0
- data/rust/src/kernel/reference_lookup.rs +90 -7
- metadata +174 -36
- data/lib/hecks/doors/cli_door/nesting.rb +0 -34
- data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
- data/lib/hecks/doors/cli_door.rb +0 -124
- data/lib/hecks/doors/cli_runner/answers.rb +0 -138
- data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
- data/lib/hecks/doors/cli_runner/settling.rb +0 -115
- data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
- data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
- data/lib/hecks/doors/cli_runner.rb +0 -66
- data/lib/hecks/doors/command_request.rb +0 -119
- data/lib/hecks/doors/handle.rb +0 -208
- data/lib/hecks/doors/json_door.rb +0 -132
- data/lib/hecks/doors/launcher_options.rb +0 -247
- data/lib/hecks/doors/mcp_door/properties.rb +0 -47
- data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
- data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
- data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
- data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
- data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
- data/lib/hecks/doors/mcp_door.rb +0 -156
- data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
- data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
- data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
- data/lib/hecks/doors/mcp_door_scope.rb +0 -201
- data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
- data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
- data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
- data/lib/hecks/doors/ruby_door.rb +0 -80
- data/lib/hecks/doors/usage_cache.rb +0 -139
- data/prebuilt/0ee7f83cba59ce7d1c45c65ba166b071779f70143449f584f33ff7989f64eccb.marshal +0 -0
- data/prebuilt/6e4a6bcf0aa880e2e6b056e1846b70e5a768090b9dc998bbadeaf5d6bcfd3db8.marshal +0 -0
- data/prebuilt/verdicts-91aaf93ce8981bd9a754948f075d95650dc2ec6435015376f5c60992b36a24b1.json +0 -1
data/rust/.gitignore
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
/target
|
|
2
2
|
/dist
|
|
3
|
+
|
|
4
|
+
# Generated from the bluebooks by `hecks regeneration_run.regenerate_corpus --confirm`; mod.rs and the
|
|
5
|
+
# Cargo features stay tracked. pizzas stays committed: its translations carry SQL only the Ruby
|
|
6
|
+
# toolchain compiles, so hecks-build cannot reproduce it.
|
|
7
|
+
/src/generated/*/
|
|
8
|
+
!/src/generated/pizzas/
|
data/rust/Cargo.toml
CHANGED
|
@@ -5,7 +5,15 @@
|
|
|
5
5
|
# domain was generated most recently; any other stays reachable via
|
|
6
6
|
# `--no-default-features --features <name>`.
|
|
7
7
|
[features]
|
|
8
|
-
default = ["
|
|
8
|
+
default = ["reserved_names_fixture"]
|
|
9
|
+
attribute_constraints_fixture = []
|
|
10
|
+
optional_nested_value_object_fixture = []
|
|
11
|
+
computed_sets_fixture = []
|
|
12
|
+
fan_out_row_fields_fixture = []
|
|
13
|
+
for_each_identity_fixture = []
|
|
14
|
+
projected_single_field_fixture = []
|
|
15
|
+
optional_value_object_fixture = []
|
|
16
|
+
reserved_names_fixture = []
|
|
9
17
|
composite_line_ledger = []
|
|
10
18
|
read_model_on_target_fixture = []
|
|
11
19
|
group_by_collision_fixture = []
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//! Rust port of the binding facts `lib/hecks/rust_build/project_rust/target_ir.rb` adds beside the
|
|
2
|
+
//! declared IR: `persistence`, `translations` and `source_text`. The capability seams
|
|
3
|
+
//! (authorization, identity, payments, ...) are `seams_pass`'s.
|
|
4
|
+
|
|
5
|
+
use std::collections::HashMap;
|
|
6
|
+
use std::path::{Path, PathBuf};
|
|
7
|
+
|
|
8
|
+
use crate::json::Json;
|
|
9
|
+
use crate::lineage_pass;
|
|
10
|
+
use crate::translation_pass;
|
|
11
|
+
|
|
12
|
+
/// Sets `persistence`, the first of the binding facts, in `TargetIr#call`'s order.
|
|
13
|
+
pub fn run(ir: &mut Json, hecksagon_path: Option<&Path>, world_path: Option<&Path>) -> Result<(), String> {
|
|
14
|
+
let chapter_name = ir.get("name").and_then(Json::as_str).unwrap_or_default().to_string();
|
|
15
|
+
let persistence = persistence(ir, &chapter_name, hecksagon_path, world_path)?;
|
|
16
|
+
ir.set("persistence", persistence);
|
|
17
|
+
Ok(())
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Sets `translations` and `source_text`, after the capability seams so the key order matches
|
|
21
|
+
/// `TargetIr#call`.
|
|
22
|
+
///
|
|
23
|
+
/// `translations` carries each `translations/*.bluebook` edge with its precompiled SQL, from
|
|
24
|
+
/// `translation_pass`; an edge using a rule kind that pass does not compile is refused, never
|
|
25
|
+
/// shipped without its SQL. A `translates` declaration in a bluebook is refused outright.
|
|
26
|
+
pub fn finish(ir: &mut Json, bluebook_files: &[PathBuf], bluebook_dir: &Path) -> Result<(), String> {
|
|
27
|
+
let mut texts = Vec::new();
|
|
28
|
+
for path in bluebook_files {
|
|
29
|
+
let text = read(path)?;
|
|
30
|
+
if text.lines().any(declares_translation) {
|
|
31
|
+
return Err(format!(
|
|
32
|
+
"{} declares a translation; hecks-build cannot compile its SQL yet, so generate this domain with the Ruby toolchain",
|
|
33
|
+
path.display()
|
|
34
|
+
));
|
|
35
|
+
}
|
|
36
|
+
texts.push(text);
|
|
37
|
+
}
|
|
38
|
+
ir.set("translations", Json::Array(translation_pass::edges(bluebook_dir)?));
|
|
39
|
+
if !texts.is_empty() {
|
|
40
|
+
ir.set("source_text", Json::String(texts.join("\n")));
|
|
41
|
+
}
|
|
42
|
+
Ok(())
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fn declares_translation(line: &str) -> bool {
|
|
46
|
+
let line = line.trim_start();
|
|
47
|
+
line.starts_with("translates") || line.starts_with("translation ")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fn persistence(ir: &Json, chapter: &str, hecksagon: Option<&Path>, world: Option<&Path>) -> Result<Json, String> {
|
|
51
|
+
let (binds, hecksagon_default) = match hecksagon {
|
|
52
|
+
Some(path) => {
|
|
53
|
+
let text = read(path)?;
|
|
54
|
+
(lineage_pass::persistence_binds(&text), domain_default(&text, chapter))
|
|
55
|
+
}
|
|
56
|
+
None => (HashMap::new(), None),
|
|
57
|
+
};
|
|
58
|
+
let world_default = match world {
|
|
59
|
+
Some(path) => lineage_pass::default_adapter_name(&read(path)?, chapter),
|
|
60
|
+
None => None,
|
|
61
|
+
};
|
|
62
|
+
let declared = hecksagon_default.or(world_default);
|
|
63
|
+
let mut aggregates = Vec::new();
|
|
64
|
+
for aggregate in ir.get("aggregates").map(Json::each).unwrap_or(&[]) {
|
|
65
|
+
let name = aggregate.get("name").and_then(Json::as_str).unwrap_or_default().to_string();
|
|
66
|
+
let adapter = match (binds.get(&name), &declared, hecksagon.is_some()) {
|
|
67
|
+
(Some(adapter), _, _) => adapter.clone(),
|
|
68
|
+
(None, Some(adapter), _) => adapter.clone(),
|
|
69
|
+
(None, None, false) => "Memory".to_string(),
|
|
70
|
+
(None, None, true) => {
|
|
71
|
+
return Err(format!("{chapter}::{name} has no persisted_by bind, and its hecksagon names no default adapter"));
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
aggregates.push(Json::Object(vec![
|
|
75
|
+
("name".to_string(), Json::String(name.clone())),
|
|
76
|
+
("storage_name".to_string(), Json::String(lineage_pass::snake_case(&name))),
|
|
77
|
+
("adapter".to_string(), Json::String(adapter)),
|
|
78
|
+
]));
|
|
79
|
+
}
|
|
80
|
+
Ok(Json::Object(vec![("aggregates".to_string(), Json::Array(aggregates))]))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// The bare `persisted_by "X"` line of the chapter's own `Hecks.hecksagon` block: every aggregate
|
|
84
|
+
// without a bind of its own inherits it. The last one in the block wins.
|
|
85
|
+
fn domain_default(text: &str, chapter: &str) -> Option<String> {
|
|
86
|
+
let mut in_chapter = false;
|
|
87
|
+
let mut found = None;
|
|
88
|
+
for line in text.lines() {
|
|
89
|
+
if let Some(rest) = line.strip_prefix("Hecks.hecksagon") {
|
|
90
|
+
in_chapter = rest.contains(&format!("\"{chapter}\""));
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
let Some(rest) = line.trim_start().strip_prefix("persisted_by ") else { continue };
|
|
94
|
+
let Some(open) = rest.find('"').filter(|_| in_chapter) else { continue };
|
|
95
|
+
let quoted = &rest[open + 1..];
|
|
96
|
+
found = quoted.find('"').map(|close| quoted[..close].to_string());
|
|
97
|
+
}
|
|
98
|
+
found
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
fn read(path: &Path) -> Result<String, String> {
|
|
102
|
+
std::fs::read_to_string(path).map_err(|e| format!("reading {}: {e}", path.display()))
|
|
103
|
+
}
|
|
@@ -212,6 +212,12 @@ fn is_default_line(line_content: &str) -> bool {
|
|
|
212
212
|
trimmed.strip_prefix("default").map(|rest| rest.trim_start().starts_with('=')).unwrap_or(false)
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
/// Rewrites only the root `mod.rs`, for a generation into a directory other than `rust/src/generated`.
|
|
216
|
+
pub fn write_mod_only(out_root: &Path, target_mod_name: &str) -> Result<(), String> {
|
|
217
|
+
let (all_dirs, domains) = scan_generated_dirs(out_root)?;
|
|
218
|
+
write_root_mod(out_root, &all_dirs, &domains, target_mod_name)
|
|
219
|
+
}
|
|
220
|
+
|
|
215
221
|
/// Rewrites the root `mod.rs` and syncs `rust/Cargo.toml`'s `[features]` for the domains on disk.
|
|
216
222
|
pub fn run(out_root: &Path, cargo_toml_path: &Path, target_mod_name: &str) -> Result<(), String> {
|
|
217
223
|
let (all_dirs, domains) = scan_generated_dirs(out_root)?;
|
|
@@ -57,7 +57,7 @@ pub fn run(ir: &mut Json, hecksagon_path: Option<&Path>, world_path: Option<&Pat
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
// Skips any line naming `role:` — only a role-less bind is authoritative.
|
|
60
|
-
fn persistence_binds(text: &str) -> HashMap<String, String> {
|
|
60
|
+
pub(crate) fn persistence_binds(text: &str) -> HashMap<String, String> {
|
|
61
61
|
let mut binds = HashMap::new();
|
|
62
62
|
|
|
63
63
|
for line in text.lines() {
|
|
@@ -87,7 +87,7 @@ fn persistence_binds(text: &str) -> HashMap<String, String> {
|
|
|
87
87
|
|
|
88
88
|
// Only lines inside the target chapter's own `Hecks.world` block count; the
|
|
89
89
|
// last `default_adapter` there wins, matching the Ruby builder.
|
|
90
|
-
fn default_adapter_name(text: &str, chapter: &str) -> Option<String> {
|
|
90
|
+
pub(crate) fn default_adapter_name(text: &str, chapter: &str) -> Option<String> {
|
|
91
91
|
let mut in_target_world = false;
|
|
92
92
|
let mut found = None;
|
|
93
93
|
|
|
@@ -182,7 +182,7 @@ fn top_level_class_name(text: &str) -> Option<String> {
|
|
|
182
182
|
|
|
183
183
|
// Hand-ported `Naming.snake` (Ruby): split acronym boundaries, then camelCase
|
|
184
184
|
// boundaries, then downcase — no regex dependency.
|
|
185
|
-
fn snake_case(name: &str) -> String {
|
|
185
|
+
pub(crate) fn snake_case(name: &str) -> String {
|
|
186
186
|
let after_pass1 = split_acronym_boundaries(name);
|
|
187
187
|
let after_pass2 = split_camel_boundaries(&after_pass1);
|
|
188
188
|
after_pass2.to_ascii_lowercase()
|
data/rust/build/src/main.rs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! `hecks-build <domain> [--wasm] [--no-build]` compiles a domain's `.bluebook` to a binary.
|
|
1
|
+
//! `hecks-build <domain> [--wasm] [--no-build] [--out <dir>]` compiles a domain's `.bluebook` to a binary.
|
|
2
2
|
//! `<domain>` is a domain directory such as `examples/pizzas`; `--wasm` also cross-compiles to
|
|
3
3
|
//! `wasm32-wasip1`, and `--no-build` generates source only.
|
|
4
4
|
|
|
@@ -9,13 +9,16 @@ mod cargo_sync;
|
|
|
9
9
|
#[allow(dead_code)]
|
|
10
10
|
#[path = "../../codegen/src/json.rs"]
|
|
11
11
|
mod json;
|
|
12
|
+
mod binding_pass;
|
|
12
13
|
mod lineage_pass;
|
|
13
14
|
mod pipeline;
|
|
14
15
|
mod reserved_names;
|
|
15
16
|
mod resolve;
|
|
16
17
|
mod root;
|
|
18
|
+
mod seams_pass;
|
|
17
19
|
mod subprocess;
|
|
18
20
|
mod tmp;
|
|
21
|
+
mod translation_pass;
|
|
19
22
|
|
|
20
23
|
use std::process::ExitCode;
|
|
21
24
|
|
|
@@ -34,20 +37,29 @@ fn run(args: &[String]) -> Result<(), String> {
|
|
|
34
37
|
let mut domain: Option<String> = None;
|
|
35
38
|
let mut build_wasm = false;
|
|
36
39
|
let mut no_build = false;
|
|
40
|
+
let mut out_dir: Option<String> = None;
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
let mut iter = args.iter();
|
|
43
|
+
while let Some(arg) = iter.next() {
|
|
39
44
|
match arg.as_str() {
|
|
45
|
+
"--out" => out_dir = Some(iter.next().ok_or("--out needs a directory")?.clone()),
|
|
40
46
|
"--wasm" => build_wasm = true,
|
|
41
47
|
"--no-build" => no_build = true,
|
|
42
48
|
other if !other.starts_with("--") && domain.is_none() => domain = Some(other.to_string()),
|
|
43
|
-
other => return Err(format!("unrecognized argument {other:?} — usage: hecks-build <domain> [--wasm] [--no-build]")),
|
|
49
|
+
other => return Err(format!("unrecognized argument {other:?} — usage: hecks-build <domain> [--wasm] [--no-build] [--out <dir>]")),
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
let domain = domain.ok_or_else(|| "usage: hecks-build <domain> [--wasm] [--no-build]".to_string())?;
|
|
53
|
+
let domain = domain.ok_or_else(|| "usage: hecks-build <domain> [--wasm] [--no-build] [--out <dir>]".to_string())?;
|
|
48
54
|
let root = root::find()?;
|
|
49
55
|
|
|
50
|
-
|
|
56
|
+
// `--out` generates the module tree into that directory only: no `Cargo.toml` rewrite, no
|
|
57
|
+
// native or wasm build, so a `build.rs` can call it from inside a cargo build.
|
|
58
|
+
let opts = pipeline::Options {
|
|
59
|
+
build_native: !no_build && out_dir.is_none(),
|
|
60
|
+
build_wasm: build_wasm && !no_build && out_dir.is_none(),
|
|
61
|
+
out_dir: out_dir.map(std::path::PathBuf::from),
|
|
62
|
+
};
|
|
51
63
|
|
|
52
64
|
pipeline::run(&root, &domain, &opts)
|
|
53
65
|
}
|
data/rust/build/src/pipeline.rs
CHANGED
|
@@ -3,17 +3,20 @@
|
|
|
3
3
|
|
|
4
4
|
use std::path::{Path, PathBuf};
|
|
5
5
|
|
|
6
|
+
use crate::binding_pass;
|
|
6
7
|
use crate::build_artifact;
|
|
7
8
|
use crate::cargo_sync;
|
|
8
9
|
use crate::json::Json;
|
|
9
10
|
use crate::lineage_pass;
|
|
10
11
|
use crate::resolve;
|
|
12
|
+
use crate::seams_pass;
|
|
11
13
|
use crate::subprocess;
|
|
12
14
|
use crate::tmp::TempDir;
|
|
13
15
|
|
|
14
16
|
pub struct Options {
|
|
15
17
|
pub build_native: bool,
|
|
16
18
|
pub build_wasm: bool,
|
|
19
|
+
pub out_dir: Option<PathBuf>,
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
struct Chapter {
|
|
@@ -58,10 +61,19 @@ pub fn run(root: &Path, domain: &str, opts: &Options) -> Result<(), String> {
|
|
|
58
61
|
|
|
59
62
|
let target_chapter_name = resolve::header_chapter_name(first_bluebook)?;
|
|
60
63
|
let mut target_files = Vec::new();
|
|
64
|
+
// Chapters declared beside the target but not attached to it: the registry still loads them, so
|
|
65
|
+
// one may provide a capability (payments) the target uses.
|
|
66
|
+
let mut sibling_files: Vec<(String, Vec<PathBuf>)> = Vec::new();
|
|
61
67
|
for path in bluebook_paths {
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
let chapter = resolve::header_chapter_name(&path)?;
|
|
69
|
+
if chapter != target_chapter_name {
|
|
70
|
+
match sibling_files.iter_mut().find(|(name, _)| *name == chapter) {
|
|
71
|
+
Some((_, files)) => files.push(path),
|
|
72
|
+
None => sibling_files.push((chapter, vec![path])),
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
64
75
|
}
|
|
76
|
+
target_files.push(path);
|
|
65
77
|
}
|
|
66
78
|
|
|
67
79
|
let (gem_chapter_names, vendored_package_names) = match &hecksagon_path {
|
|
@@ -72,6 +84,7 @@ pub fn run(root: &Path, domain: &str, opts: &Options) -> Result<(), String> {
|
|
|
72
84
|
None => (Vec::new(), Vec::new()),
|
|
73
85
|
};
|
|
74
86
|
|
|
87
|
+
let source_files = target_files.clone();
|
|
75
88
|
if let Some(p) = &hecksagon_path {
|
|
76
89
|
target_files.push(p.clone());
|
|
77
90
|
}
|
|
@@ -81,7 +94,7 @@ pub fn run(root: &Path, domain: &str, opts: &Options) -> Result<(), String> {
|
|
|
81
94
|
// rather than chaining a live object through both passes.
|
|
82
95
|
let mut target_ir = Json::parse(&target_ir_text).map_err(|e| format!("re-parsing target ir.json for lineage: {e}"))?;
|
|
83
96
|
lineage_pass::run(&mut target_ir, hecksagon_path.as_deref(), world_path.as_deref(), root)?;
|
|
84
|
-
|
|
97
|
+
binding_pass::run(&mut target_ir, hecksagon_path.as_deref(), world_path.as_deref())?;
|
|
85
98
|
|
|
86
99
|
// Every other chapter the gem supplies by `attaches "Name"`, resolved through the
|
|
87
100
|
// same directory listing (`resolve::framework_members`) the Ruby
|
|
@@ -150,6 +163,22 @@ pub fn run(root: &Path, domain: &str, opts: &Options) -> Result<(), String> {
|
|
|
150
163
|
});
|
|
151
164
|
}
|
|
152
165
|
|
|
166
|
+
// A chapter declared in the domain's own directory is a chapter of the build too, as every
|
|
167
|
+
// bluebook the registry loads is; its ports come from its own block of the domain's hecksagon.
|
|
168
|
+
// The registry loads the directory's bluebooks before it resolves what the hecksagon attaches,
|
|
169
|
+
// so they come first.
|
|
170
|
+
let mut siblings = Vec::new();
|
|
171
|
+
for (name, files) in &sibling_files {
|
|
172
|
+
let mut sources = files.clone();
|
|
173
|
+
sources.extend(hecksagon_path.iter().cloned());
|
|
174
|
+
siblings.push(Chapter {
|
|
175
|
+
mod_name: name.to_lowercase(),
|
|
176
|
+
source_label: format!("{domain} (attaches {name:?})"),
|
|
177
|
+
ir_text: parse_chapter(&parser_bin, name, &sources)?,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
chapters.splice(0..0, siblings);
|
|
181
|
+
|
|
153
182
|
// Guards against a gem-chapter and a vendored-package
|
|
154
183
|
// entry sharing a `mod_name`, which would silently overwrite one
|
|
155
184
|
// chapter's sidecars with another's.
|
|
@@ -167,12 +196,22 @@ pub fn run(root: &Path, domain: &str, opts: &Options) -> Result<(), String> {
|
|
|
167
196
|
}
|
|
168
197
|
}
|
|
169
198
|
|
|
199
|
+
// The capability seams read the attached chapters' own `provides`, so they are decided only
|
|
200
|
+
// once every chapter is loaded; `translations` and `source_text` close the binding facts.
|
|
201
|
+
let mut member_irs = Vec::new();
|
|
202
|
+
for c in &chapters {
|
|
203
|
+
member_irs.push(Json::parse(&c.ir_text).map_err(|e| format!("re-parsing {} ir.json for its seams: {e}", c.mod_name))?);
|
|
204
|
+
}
|
|
205
|
+
seams_pass::run(&mut target_ir, &member_irs)?;
|
|
206
|
+
binding_pass::finish(&mut target_ir, &source_files, &bluebook_directory)?;
|
|
207
|
+
let target_ir_text = crate::json::write(&target_ir);
|
|
208
|
+
|
|
170
209
|
// The self-hosted language, compiled in too, under the same "Bluebook"
|
|
171
210
|
// chapter name.
|
|
172
211
|
let grammar_files = resolve::grammar_files(root)?;
|
|
173
212
|
let meta_ir_text = parse_chapter(&parser_bin, "Bluebook", &grammar_files)?;
|
|
174
213
|
|
|
175
|
-
let out_root = root.join("rust/src/generated");
|
|
214
|
+
let out_root = opts.out_dir.clone().unwrap_or_else(|| root.join("rust/src/generated"));
|
|
176
215
|
std::fs::create_dir_all(&out_root).map_err(|e| format!("creating {}: {e}", out_root.display()))?;
|
|
177
216
|
|
|
178
217
|
// Scoped clearing — identical reasoning to both Ruby pipelines'
|
|
@@ -212,7 +251,11 @@ pub fn run(root: &Path, domain: &str, opts: &Options) -> Result<(), String> {
|
|
|
212
251
|
run_codegen_full(&codegen_bin, &codegen_args)?;
|
|
213
252
|
|
|
214
253
|
let cargo_toml_path = root.join("rust/Cargo.toml");
|
|
215
|
-
|
|
254
|
+
if opts.out_dir.is_some() {
|
|
255
|
+
cargo_sync::write_mod_only(&out_root, &target_mod_name)?;
|
|
256
|
+
} else {
|
|
257
|
+
cargo_sync::run(&out_root, &cargo_toml_path, &target_mod_name)?;
|
|
258
|
+
}
|
|
216
259
|
|
|
217
260
|
let rust_dir = root.join("rust");
|
|
218
261
|
if opts.build_native {
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
//! Rust port of `Exporter::Bindings` (`lib/hecks/projector/exporter/bindings.rb`) and the provider
|
|
2
|
+
//! lookups of `Registry::Providers`: which attached chapter answers each capability the target
|
|
3
|
+
//! domain uses, read from every chapter's own `provides` entries in its IR.
|
|
4
|
+
|
|
5
|
+
use crate::json::Json;
|
|
6
|
+
|
|
7
|
+
/// One capability's exported shape: the keys it exports as qualified verbs, then each derived
|
|
8
|
+
/// `name => verb key` aggregate, in the order `Bindings` builds them, then its optional marks.
|
|
9
|
+
struct Seam {
|
|
10
|
+
capability: &'static str,
|
|
11
|
+
export_as: &'static str,
|
|
12
|
+
verbs: &'static [&'static str],
|
|
13
|
+
aggregates: &'static [(&'static str, &'static str)],
|
|
14
|
+
marks: &'static [&'static str],
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// In `TargetIr::SEAMS` order, which is the order the keys land in `ir.json`.
|
|
18
|
+
const SEAMS: &[Seam] = &[
|
|
19
|
+
Seam { capability: "authorization", export_as: "authorization", verbs: &["grant", "assignments"], aggregates: &[("assignment_aggregate", "assignments")], marks: &[] },
|
|
20
|
+
Seam { capability: "membership", export_as: "membership", verbs: &["admit", "grant", "people"], aggregates: &[("aggregate", "admit")], marks: &[] },
|
|
21
|
+
Seam { capability: "identity", export_as: "identity", verbs: &["register", "link", "resolve"], aggregates: &[], marks: &[] },
|
|
22
|
+
Seam {
|
|
23
|
+
capability: "newsletter",
|
|
24
|
+
export_as: "newsletter",
|
|
25
|
+
verbs: &["subscribe", "add_name", "confirm", "unsubscribe"],
|
|
26
|
+
aggregates: &[("aggregate", "subscribe")],
|
|
27
|
+
marks: &["awaiting_confirmation", "receives_issues", "left"],
|
|
28
|
+
},
|
|
29
|
+
Seam {
|
|
30
|
+
capability: "newsletter_issues",
|
|
31
|
+
export_as: "newsletter_issues",
|
|
32
|
+
verbs: &["send_issue", "record_delivery"],
|
|
33
|
+
aggregates: &[("issue_aggregate", "send_issue"), ("delivery_aggregate", "record_delivery")],
|
|
34
|
+
marks: &[],
|
|
35
|
+
},
|
|
36
|
+
Seam { capability: "payments", export_as: "payments", verbs: &["initiate", "succeeded", "failed"], aggregates: &[("aggregate", "initiate")], marks: &["holds_seat"] },
|
|
37
|
+
Seam {
|
|
38
|
+
capability: "registrations",
|
|
39
|
+
export_as: "registrations",
|
|
40
|
+
verbs: &["schedule", "request"],
|
|
41
|
+
aggregates: &[("event_aggregate", "schedule"), ("registration_aggregate", "request")],
|
|
42
|
+
marks: &["frees_seat"],
|
|
43
|
+
},
|
|
44
|
+
Seam {
|
|
45
|
+
capability: "payment_connection",
|
|
46
|
+
export_as: "payment_connection",
|
|
47
|
+
verbs: &["connect", "reconnect", "disconnect", "suspend", "resume", "enable", "disable"],
|
|
48
|
+
aggregates: &[("aggregate", "connect")],
|
|
49
|
+
marks: &[],
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/// Sets each seam some chapter provides on `target`, leaving it out otherwise, as
|
|
54
|
+
/// `TargetIr#add_seams` does. `members` are the chapters the target attaches, in attach order; the
|
|
55
|
+
/// target itself is searched first.
|
|
56
|
+
pub fn run(target: &mut Json, members: &[Json]) -> Result<(), String> {
|
|
57
|
+
let mut chapters: Vec<Json> = vec![target.clone()];
|
|
58
|
+
chapters.extend(members.iter().cloned());
|
|
59
|
+
|
|
60
|
+
let mut membership = false;
|
|
61
|
+
let mut identity = false;
|
|
62
|
+
for seam in SEAMS {
|
|
63
|
+
let Some(provider) = chapters.iter().find(|chapter| provision(chapter, seam.capability).is_some()) else { continue };
|
|
64
|
+
let exported = export(seam, provider);
|
|
65
|
+
membership |= seam.capability == "membership";
|
|
66
|
+
identity |= seam.capability == "identity";
|
|
67
|
+
target.set(seam.export_as, exported);
|
|
68
|
+
}
|
|
69
|
+
if membership && !identity {
|
|
70
|
+
return Err(format!(
|
|
71
|
+
"{} provides membership but not identity — rust/host Google sign-in cannot register or link an identity from ir.json. \
|
|
72
|
+
Attach Identity so the hecksagon, not a deploy-time guess, names the identity verbs.",
|
|
73
|
+
name_of(target)
|
|
74
|
+
));
|
|
75
|
+
}
|
|
76
|
+
Ok(())
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
fn export(seam: &Seam, provider: &Json) -> Json {
|
|
80
|
+
let name = name_of(provider);
|
|
81
|
+
let entries = provision(provider, seam.capability).unwrap_or_default();
|
|
82
|
+
let verb = |key: &str| entries.iter().find(|(k, _)| k == key).map(|(_, v)| format!("{name}::{v}"));
|
|
83
|
+
|
|
84
|
+
let mut out = vec![("provider".to_string(), Json::String(name.clone()))];
|
|
85
|
+
for key in seam.verbs {
|
|
86
|
+
out.push((key.to_string(), verb(key).map_or(Json::Null, Json::String)));
|
|
87
|
+
}
|
|
88
|
+
for (exported, key) in seam.aggregates {
|
|
89
|
+
let aggregate = verb(key).map(|v| v.split('.').next().unwrap_or_default().to_string());
|
|
90
|
+
out.push((exported.to_string(), aggregate.map_or(Json::Null, Json::String)));
|
|
91
|
+
}
|
|
92
|
+
for key in seam.marks {
|
|
93
|
+
if let Some(states) = marked_states(provider, entries.iter().find(|(k, _)| k == key).map(|(_, v)| v.as_str())) {
|
|
94
|
+
out.push((key.to_string(), states));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
Json::Object(out)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// `[(key, verb)]` this chapter provides under `capability`, or `None` when it provides none.
|
|
101
|
+
fn provision(chapter: &Json, capability: &str) -> Option<Vec<(String, String)>> {
|
|
102
|
+
let entries: Vec<(String, String)> = chapter
|
|
103
|
+
.get("provides")
|
|
104
|
+
.map(Json::each)
|
|
105
|
+
.unwrap_or(&[])
|
|
106
|
+
.iter()
|
|
107
|
+
.filter(|entry| entry.get("capability").and_then(Json::as_str) == Some(capability))
|
|
108
|
+
.filter_map(|entry| Some((entry.get("key")?.as_str()?.to_string(), entry.get("verb")?.as_str()?.to_string())))
|
|
109
|
+
.collect();
|
|
110
|
+
(!entries.is_empty()).then_some(entries)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// The states of the aggregate lifecycle mark a `"Aggregate.mark"` entry names; `None` when the
|
|
114
|
+
// entry is not declared or the mark is.
|
|
115
|
+
fn marked_states(provider: &Json, entry: Option<&str>) -> Option<Json> {
|
|
116
|
+
let (aggregate_name, mark) = entry?.split_once('.')?;
|
|
117
|
+
let aggregate = provider.get("aggregates").map(Json::each)?.iter().find(|a| a.get("name").and_then(Json::as_str) == Some(aggregate_name))?;
|
|
118
|
+
aggregate.get("lifecycle")?.get("marks")?.get(mark).cloned()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fn name_of(chapter: &Json) -> String {
|
|
122
|
+
chapter.get("name").and_then(Json::as_str).unwrap_or_default().to_string()
|
|
123
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
//! Rust port of the part of `Projector::Exporter::Translations` that a `translations/*.bluebook`
|
|
2
|
+
//! edge made of renames and moves needs: the declared rules plus the SQL expression
|
|
3
|
+
//! `Translation::RuleCompiler.compile_rules` builds for them.
|
|
4
|
+
//!
|
|
5
|
+
//! A rule kind this pass does not compile (convert, drop, retype, compute, rekey, backfill) is
|
|
6
|
+
//! refused by name, so a domain never ships an edge whose SQL was silently left out.
|
|
7
|
+
|
|
8
|
+
use std::path::Path;
|
|
9
|
+
|
|
10
|
+
use crate::json::Json;
|
|
11
|
+
|
|
12
|
+
/// One aggregate's rules, in declaration order.
|
|
13
|
+
struct Aggregate {
|
|
14
|
+
name: String,
|
|
15
|
+
was: Option<String>,
|
|
16
|
+
renames: Vec<(String, String)>,
|
|
17
|
+
moves: Vec<(String, String)>,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// The `translations` IR list for the `.bluebook` files under `bluebook_dir/translations`, ordered
|
|
21
|
+
/// by file name (an era-step id), as the registry loads them.
|
|
22
|
+
pub fn edges(bluebook_dir: &Path) -> Result<Vec<Json>, String> {
|
|
23
|
+
let Ok(entries) = std::fs::read_dir(bluebook_dir.join("translations")) else { return Ok(Vec::new()) };
|
|
24
|
+
let mut paths: Vec<_> = entries
|
|
25
|
+
.filter_map(Result::ok)
|
|
26
|
+
.map(|entry| entry.path())
|
|
27
|
+
.filter(|path| path.extension().and_then(|e| e.to_str()) == Some("bluebook"))
|
|
28
|
+
.collect();
|
|
29
|
+
paths.sort();
|
|
30
|
+
let mut edges = Vec::new();
|
|
31
|
+
for path in paths {
|
|
32
|
+
let text = std::fs::read_to_string(&path).map_err(|e| format!("reading {}: {e}", path.display()))?;
|
|
33
|
+
edges.push(edge(&text).map_err(|why| format!("{}: {why}", path.display()))?);
|
|
34
|
+
}
|
|
35
|
+
Ok(edges)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fn edge(text: &str) -> Result<Json, String> {
|
|
39
|
+
let mut header: Option<(String, String, String)> = None;
|
|
40
|
+
let mut aggregates: Vec<Aggregate> = Vec::new();
|
|
41
|
+
for raw in text.lines() {
|
|
42
|
+
let line = raw.trim();
|
|
43
|
+
if line.is_empty() || line.starts_with('#') || line == "end" {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if let Some(rest) = line.strip_prefix("Hecks.data_translation") {
|
|
47
|
+
header = Some((first_string(rest)?, keyword(rest, "from")?, keyword(rest, "to")?));
|
|
48
|
+
} else if let Some(rest) = line.strip_prefix("aggregate ") {
|
|
49
|
+
let was = keyword(rest, "was").ok();
|
|
50
|
+
aggregates.push(Aggregate { name: first_string(rest)?, was, renames: Vec::new(), moves: Vec::new() });
|
|
51
|
+
} else if let Some(rest) = line.strip_prefix("move ") {
|
|
52
|
+
let aggregate = aggregates.last_mut().ok_or("a move outside an aggregate")?;
|
|
53
|
+
aggregate.moves.push((first_word(rest)?, keyword(rest, "to")?));
|
|
54
|
+
} else if let Some(rest) = line.strip_prefix("rename ") {
|
|
55
|
+
let aggregate = aggregates.last_mut().ok_or("a rename outside an aggregate")?;
|
|
56
|
+
aggregate.renames.push((first_word(rest)?, keyword(rest, "to")?));
|
|
57
|
+
} else {
|
|
58
|
+
return Err(format!(
|
|
59
|
+
"hecks-build cannot compile the translation rule `{line}` yet (only rename and move are ported), \
|
|
60
|
+
so generate this domain with the Ruby toolchain"
|
|
61
|
+
));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
let (domain, from, to) = header.ok_or("no Hecks.data_translation header")?;
|
|
65
|
+
Ok(Json::Object(vec![
|
|
66
|
+
("domain".into(), Json::String(domain)),
|
|
67
|
+
("from".into(), Json::String(from)),
|
|
68
|
+
("to".into(), Json::String(to)),
|
|
69
|
+
("retired".into(), Json::Array(Vec::new())),
|
|
70
|
+
("aggregates".into(), Json::Array(aggregates.iter().map(aggregate_json).collect())),
|
|
71
|
+
]))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fn aggregate_json(aggregate: &Aggregate) -> Json {
|
|
75
|
+
let renames = aggregate.renames.iter().map(|(old, new)| (old.clone(), Json::String(new.clone()))).collect();
|
|
76
|
+
let moves = aggregate
|
|
77
|
+
.moves
|
|
78
|
+
.iter()
|
|
79
|
+
.map(|(from, to)| Json::Object(vec![("from".into(), Json::String(from.clone())), ("to".into(), Json::String(to.clone()))]))
|
|
80
|
+
.collect();
|
|
81
|
+
let empty = || Json::Array(Vec::new());
|
|
82
|
+
Json::Object(vec![
|
|
83
|
+
("name".into(), Json::String(aggregate.name.clone())),
|
|
84
|
+
("was".into(), aggregate.was.clone().map_or(Json::Null, Json::String)),
|
|
85
|
+
("renames".into(), Json::Object(renames)),
|
|
86
|
+
("moves".into(), Json::Array(moves)),
|
|
87
|
+
("converts".into(), empty()),
|
|
88
|
+
("drops".into(), empty()),
|
|
89
|
+
("retypes".into(), empty()),
|
|
90
|
+
("computes".into(), empty()),
|
|
91
|
+
("rekeys".into(), empty()),
|
|
92
|
+
("backfills".into(), empty()),
|
|
93
|
+
("compiled_state_expression".into(), Json::String(compile(aggregate))),
|
|
94
|
+
("compiled_id_expression".into(), Json::Null),
|
|
95
|
+
])
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// `RuleCompiler.compile_rules`: renames, then moves, each wrapping the expression so far.
|
|
99
|
+
fn compile(aggregate: &Aggregate) -> String {
|
|
100
|
+
let mut sql = "state".to_string();
|
|
101
|
+
for (old, new) in &aggregate.renames {
|
|
102
|
+
sql = format!("hecks_tr_rename({sql}, {}, {})", text_literal(old), text_literal(new));
|
|
103
|
+
}
|
|
104
|
+
for (from, to) in &aggregate.moves {
|
|
105
|
+
sql = format!(
|
|
106
|
+
"hecks_tr_move({sql}, {}, {}, {})",
|
|
107
|
+
path_literal(from),
|
|
108
|
+
path_literal(to),
|
|
109
|
+
text_literal(&format!("move {from} to: {to}"))
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
sql
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
fn text_literal(text: &str) -> String {
|
|
116
|
+
format!("'{}'", text.replace('\'', "''"))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fn path_literal(path: &str) -> String {
|
|
120
|
+
let segments: Vec<String> = path.split('.').map(text_literal).collect();
|
|
121
|
+
format!("ARRAY[{}]::text[]", segments.join(", "))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// The first `"quoted"` string of `text`.
|
|
125
|
+
fn first_string(text: &str) -> Result<String, String> {
|
|
126
|
+
let open = text.find('"').ok_or_else(|| format!("no quoted name in `{text}`"))?;
|
|
127
|
+
let rest = &text[open + 1..];
|
|
128
|
+
let close = rest.find('"').ok_or_else(|| format!("unterminated string in `{text}`"))?;
|
|
129
|
+
Ok(rest[..close].to_string())
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// The leading argument, a `:symbol` or a `"string"`, before the first comma.
|
|
133
|
+
fn first_word(text: &str) -> Result<String, String> {
|
|
134
|
+
let word = text.split(',').next().unwrap_or_default().trim();
|
|
135
|
+
let word = word.strip_prefix(':').unwrap_or(word).trim_matches('"');
|
|
136
|
+
if word.is_empty() { Err(format!("no source in `{text}`")) } else { Ok(word.to_string()) }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// The value of `key: value` in `text`, a string or a symbol.
|
|
140
|
+
fn keyword(text: &str, key: &str) -> Result<String, String> {
|
|
141
|
+
let marker = format!("{key}:");
|
|
142
|
+
let at = text.find(&marker).ok_or_else(|| format!("no `{key}:` in `{text}`"))?;
|
|
143
|
+
let value = text[at + marker.len()..].trim_start();
|
|
144
|
+
if let Some(symbol) = value.strip_prefix(':') {
|
|
145
|
+
let end = symbol.find(|c: char| !(c.is_alphanumeric() || c == '_')).unwrap_or(symbol.len());
|
|
146
|
+
return Ok(symbol[..end].to_string());
|
|
147
|
+
}
|
|
148
|
+
first_string(value)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#[cfg(test)]
|
|
152
|
+
mod tests {
|
|
153
|
+
use super::*;
|
|
154
|
+
|
|
155
|
+
#[test]
|
|
156
|
+
fn compiles_moves_as_ruby_does() {
|
|
157
|
+
let text = "Hecks.data_translation \"Pizzas\", from: \"e3f3d7\", to: \"77625c\" do\n aggregate \"Order\", was: \"Pizza\" do\n move :price_cents, to: \"pizza.price_cents\"\n move :size, to: \"pizza.size\"\n end\nend\n";
|
|
158
|
+
let json = crate::json::write(&edge(text).unwrap());
|
|
159
|
+
assert!(json.contains("hecks_tr_move(hecks_tr_move(state, ARRAY['price_cents']::text[], ARRAY['pizza', 'price_cents']::text[], 'move price_cents to: pizza.price_cents'), ARRAY['size']::text[], ARRAY['pizza', 'size']::text[], 'move size to: pizza.size')"));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
#[test]
|
|
163
|
+
fn refuses_a_rule_it_cannot_compile() {
|
|
164
|
+
let text = "Hecks.data_translation \"A\", from: \"1\", to: \"2\" do\n aggregate \"B\" do\n compute :a, to: \"b\", sql: \"1\"\n end\nend\n";
|
|
165
|
+
assert!(edge(text).unwrap_err().contains("cannot compile"));
|
|
166
|
+
}
|
|
167
|
+
}
|