hecks 3.14.0 → 5.0.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/adapters/fake_processor.rb +34 -0
- data/examples/pizzas/adapters/payment_signature.rb +66 -0
- data/examples/pizzas/adapters/payment_webhook.rb +87 -0
- data/examples/pizzas/bluebook/pizzas.behaviors +33 -0
- data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
- data/examples/pizzas/config.ru +22 -0
- data/exe/hecks +1 -1
- data/lib/hecks/adapters/driven/prism.rb +0 -2
- data/lib/hecks/adapters/driven.rb +0 -2
- data/lib/hecks/adapters/driving/json.rb +9 -16
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -2
- data/lib/hecks/bluebook/dsl/derivation_words.rb +58 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +6 -37
- data/lib/hecks/bluebook/expression/ast_json.rb +0 -9
- data/lib/hecks/bluebook/hecksagon.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/translation_judge/rules.rb +7 -10
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +4 -8
- data/lib/hecks/bluebook/meta_validator.rb +0 -11
- data/lib/hecks/bluebook/translation.rb +18 -10
- data/lib/hecks/bluebook/translation_expression/evaluator.rb +63 -0
- data/lib/hecks/bluebook/translation_expression/parser.rb +127 -0
- data/lib/hecks/bluebook/translation_expression.rb +39 -0
- data/lib/hecks/bluebook/translation_plan.rb +70 -0
- data/lib/hecks/bluebook.rb +2 -0
- data/lib/hecks/chapters.rb +3 -3
- data/lib/hecks/cli/project_cli/launcher_source.rb +1 -1
- data/lib/hecks/composer/adapters/claude_composer.adapter +3 -0
- data/lib/hecks/composer/adapters/claude_composer.rb +83 -0
- data/lib/hecks/composer/adapters/fake_agent.adapter +3 -0
- data/lib/hecks/composer/adapters/fake_agent.rb +55 -0
- data/lib/hecks/composer/adapters/jev.adapter +3 -0
- data/lib/hecks/composer/adapters/jev.rb +66 -0
- data/lib/hecks/composer/agent.rb +113 -0
- data/lib/hecks/composer/assistant.rb +110 -0
- data/lib/hecks/composer/behavior_run.rb +53 -0
- data/lib/hecks/composer/bluebook/composer.bluebook +178 -0
- data/lib/hecks/composer/bluebook/composer.hecksagon +16 -0
- data/lib/hecks/composer/board.rb +65 -0
- data/lib/hecks/composer/board_edit/command_text.rb +39 -0
- data/lib/hecks/composer/board_edit/source.rb +84 -0
- data/lib/hecks/composer/board_edit.rb +144 -0
- data/lib/hecks/composer/check.rb +89 -0
- data/lib/hecks/composer/domains.rb +81 -0
- data/lib/hecks/composer/jobs.rb +134 -0
- data/lib/hecks/composer/layout.rb +37 -0
- data/lib/hecks/composer/live.rb +150 -0
- data/lib/hecks/composer/outline.rb +48 -0
- data/lib/hecks/composer/page.html +989 -0
- data/lib/hecks/composer/panels.rb +142 -0
- data/lib/hecks/composer/parse.rb +83 -0
- data/lib/hecks/composer/plain.rb +28 -0
- data/lib/hecks/composer/ports/composer_agent.port +7 -0
- data/lib/hecks/composer/session.rb +141 -0
- data/lib/hecks/composer/toolbox.rb +166 -0
- data/lib/hecks/composer/web.rb +156 -0
- data/lib/hecks/composer/workbench.rb +51 -0
- data/lib/hecks/composer/workspace.rb +81 -0
- data/lib/hecks/composer.rb +17 -0
- data/lib/hecks/corpus/adapters.rb +22 -0
- data/lib/hecks/corpus/rust.rb +1 -1
- data/lib/hecks/corpus/worlds.rb +19 -0
- data/lib/hecks/corpus.rb +5 -0
- data/lib/hecks/forms/app/commands.rb +2 -3
- data/lib/hecks/forms/app/queries.rb +2 -3
- data/lib/hecks/forms/app/records.rb +17 -5
- data/lib/hecks/forms/app/responses.rb +3 -1
- data/lib/hecks/forms/command_form_renderer.rb +42 -16
- data/lib/hecks/forms/field_controls.rb +2 -2
- data/lib/hecks/forms/field_renderer.rb +1 -1
- data/lib/hecks/forms/field_shape.rb +36 -17
- data/lib/hecks/forms/index_renderer.rb +2 -1
- data/lib/hecks/forms/page.rb +1 -1
- data/lib/hecks/forms/params.rb +0 -13
- data/lib/hecks/forms/query_form_renderer.rb +7 -6
- data/lib/hecks/forms/record_renderer.rb +11 -7
- data/lib/hecks/forms/rule_words.rb +100 -0
- data/lib/hecks/forms/value_object_shape.rb +2 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
- data/lib/hecks/fuzzing/isolated_boot/worlds.rb +3 -6
- data/lib/hecks/fuzzing/replay/filters.rb +2 -6
- data/lib/hecks/gate/stages.yml +25 -0
- data/lib/hecks/grammar/evolve/seeds.rb +0 -6
- data/lib/hecks/grammar/evolve.rb +0 -3
- data/lib/hecks/grammar/translation.bluebook +1 -1
- data/lib/hecks/hecks/adapters/codebase/composing.rb +56 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
- data/lib/hecks/hecks/adapters/first_admin.rb +3 -3
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +0 -26
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +2 -23
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +3 -11
- data/lib/hecks/hecks/adapters/journal_store.rb +0 -11
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +4 -11
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +0 -9
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +0 -4
- data/lib/hecks/hecks/codebase.behaviors +6 -0
- data/lib/hecks/hecks/codebase.bluebook +21 -0
- data/lib/hecks/hecks/composer.behaviors +68 -0
- data/lib/hecks/hecks/context_map.hecksagon +1 -1
- data/lib/hecks/hecks/custodian.behaviors +0 -6
- data/lib/hecks/hecks/custodian.bluebook +3 -80
- data/lib/hecks/hecks/hecks.hecksagon +2 -2
- data/lib/hecks/hecks/hecks.world +1 -1
- data/lib/hecks/hecks/rehearsal.bluebook +7 -13
- data/lib/hecks/kernel_capabilities/templates.rb +4 -4
- data/lib/hecks/kernel_capabilities.rb +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
- data/lib/hecks/language/translation/translation_aggregate.bluebook +21 -24
- data/lib/hecks/ports/access_control.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -41
- data/lib/hecks/ports/persistence/plugins/era/lineage/coverage.rb +7 -7
- data/lib/hecks/ports/persistence/plugins/era/lineage/rewrite.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/lineage/rules.rb +4 -4
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +17 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/base_sql.rb +0 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/domain_rename.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -22
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +3 -29
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/refusals.rb +2 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -33
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +2 -4
- data/lib/hecks/ports/persistence/plugins/era/translation/expression_sql.rb +52 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +23 -29
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +0 -1
- data/lib/hecks/ports/persistence/plugins/era.rb +3 -6
- data/lib/hecks/ports.rb +0 -1
- data/lib/hecks/projection_files.rb +3 -1
- data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
- data/lib/hecks/projections/glossary/entries.rb +10 -4
- data/lib/hecks/projections/glossary/groups.rb +42 -0
- data/lib/hecks/projections/glossary/html/page.rb +7 -2
- data/lib/hecks/projections/glossary/html/parser.rb +1 -1
- data/lib/hecks/projections/glossary/html/sections.rb +31 -11
- data/lib/hecks/projections/glossary/html.rb +1 -2
- data/lib/hecks/projections/glossary/markdown.rb +31 -28
- data/lib/hecks/projections/glossary/mermaid.rb +3 -0
- data/lib/hecks/projections/glossary/page.css +143 -153
- data/lib/hecks/projections/glossary/page.js +63 -3
- data/lib/hecks/projections/glossary/sentences/actions.rb +88 -0
- data/lib/hecks/projections/glossary/sentences/paths.rb +77 -0
- data/lib/hecks/projections/glossary/sentences/queries.rb +90 -0
- data/lib/hecks/projections/glossary/sentences/value_objects.rb +32 -5
- data/lib/hecks/projections/glossary/sentences.rb +13 -3
- data/lib/hecks/projections/glossary/slugs.rb +4 -1
- data/lib/hecks/projections/glossary.rb +2 -1
- data/lib/hecks/projections/plain_english.rb +97 -0
- data/lib/hecks/projections/rust_vocabulary/arguments.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary/enum_body.rb +20 -25
- data/lib/hecks/projections/rust_vocabulary/tests.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary.rb +14 -6
- data/lib/hecks/projections/site/cms_editor/color.rb +0 -6
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +7 -2
- data/lib/hecks/projections/statements/actions.rb +99 -0
- data/lib/hecks/projections/statements/relationships.rb +86 -0
- data/lib/hecks/projections/statements.rb +15 -24
- data/lib/hecks/projector/exporter/adapters.rb +112 -0
- data/lib/hecks/projector/exporter/translations.rb +18 -35
- data/lib/hecks/projector/exporter/worlds.rb +103 -0
- data/lib/hecks/projector/exporter.rb +5 -1
- data/lib/hecks/quality_control/adapters/agent.rb +6 -7
- data/lib/hecks/quality_control/adapters/agent_profile.rb +16 -114
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +1 -1
- data/lib/hecks/runtime/loader/boot_steps.rb +0 -20
- data/lib/hecks/runtime/outbox.rb +2 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -9
- data/lib/hecks/runtime/remote_dispatcher.rb +2 -2
- data/lib/hecks/runtime/value/unwrapping.rb +60 -0
- data/lib/hecks/runtime/value.rb +10 -21
- data/lib/hecks/rust_build/project_rust/target_ir.rb +23 -4
- data/lib/hecks/rust_build/wasm.rb +0 -12
- data/lib/hecks/rust_build/write_if_changed.rb +12 -12
- data/lib/hecks/rust_format.rb +77 -0
- data/lib/hecks/tools/fuzz_sweep/findings.rb +3 -9
- data/lib/hecks/tools/fuzz_sweep.rb +1 -3
- data/lib/hecks/tools/translation_audit/verdicts.rb +3 -28
- data/lib/hecks/tools/translation_audit.rb +8 -10
- data/lib/hecks/tools/translation_scaffold/reporting.rb +1 -1
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +2 -1
- data/lib/hecks.rb +3 -0
- data/prebuilt/{1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal → ad68f9e06c8068c2a4176b57abf3083d591d5d8980c2b3e897b97331f965b643.marshal} +0 -0
- data/prebuilt/{a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal → ee22ec8642c4006717ba2b80f46e5efb81de459eef9dfec043e838c0ad4fd720.marshal} +0 -0
- data/prebuilt/{verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json → verdicts-b69f5dcbba79c20daa0493476fa7c317004a30aa2bc3d3dc6a722ea70ae2b43b.json} +1 -1
- data/rust/Cargo.toml +40 -0
- data/rust/build/Cargo.toml +36 -0
- data/rust/build/clippy.toml +4 -0
- data/rust/build/src/adapters_pass.rs +196 -0
- data/rust/build/src/binding_pass.rs +54 -13
- data/rust/build/src/build_artifact.rs +23 -5
- data/rust/build/src/cargo_sync.rs +69 -26
- data/rust/build/src/lineage_pass.rs +101 -87
- data/rust/build/src/main.rs +11 -2
- data/rust/build/src/pipeline.rs +80 -19
- data/rust/build/src/reserved_names.rs +6 -50
- data/rust/build/src/resolve.rs +82 -35
- data/rust/build/src/rustfmt.rs +100 -0
- data/rust/build/src/seams_pass.rs +85 -16
- data/rust/build/src/subprocess.rs +30 -6
- data/rust/build/src/tmp.rs +5 -2
- data/rust/build/src/translation_pass.rs +72 -22
- data/rust/build/src/world_pass.rs +109 -0
- data/rust/clippy.toml +4 -0
- data/rust/codegen/Cargo.toml +36 -0
- data/rust/codegen/clippy.toml +4 -0
- data/rust/codegen/src/asks.rs +40 -14
- data/rust/codegen/src/attr.rs +2 -2
- data/rust/codegen/src/bridging.rs +676 -117
- data/rust/codegen/src/commands.rs +979 -373
- data/rust/codegen/src/constraints.rs +88 -25
- data/rust/codegen/src/dependency_planning.rs +128 -34
- data/rust/codegen/src/domain_generator.rs +306 -170
- data/rust/codegen/src/exemplar.rs +87 -32
- data/rust/codegen/src/expr_emitter.rs +54 -6
- data/rust/codegen/src/fielded.rs +193 -41
- data/rust/codegen/src/hecks_naming.rs +124 -16
- data/rust/codegen/src/hecks_naming_tests.rs +81 -0
- data/rust/codegen/src/json.rs +80 -44
- data/rust/codegen/src/json_codec.rs +585 -149
- data/rust/codegen/src/literal.rs +104 -138
- data/rust/codegen/src/main.rs +151 -52
- data/rust/codegen/src/manifest.rs +76 -14
- data/rust/codegen/src/mutations.rs +1212 -239
- data/rust/codegen/src/naming.rs +111 -60
- data/rust/codegen/src/optional_pass.rs +90 -28
- data/rust/codegen/src/ports.rs +5 -4
- data/rust/codegen/src/prelude.rs +139 -49
- data/rust/codegen/src/presenters.rs +335 -0
- data/rust/codegen/src/queries.rs +601 -226
- data/rust/codegen/src/reactions.rs +268 -74
- data/rust/codegen/src/read_models.rs +574 -229
- data/rust/codegen/src/reference_specs.rs +16 -3
- data/rust/codegen/src/registry.rs +128 -79
- data/rust/codegen/src/reserved_names.rs +6 -50
- data/rust/codegen/src/shared.rs +71 -22
- data/rust/codegen/src/sidecars.rs +15 -5
- data/rust/codegen/src/skip_reason.rs +8 -2
- data/rust/codegen/src/types.rs +306 -108
- data/rust/host/Cargo.lock +1 -0
- data/rust/host/Cargo.toml +40 -4
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/clippy.toml +4 -0
- data/rust/host/src/api.rs +698 -218
- data/rust/host/src/auth.rs +491 -214
- data/rust/host/src/b64.rs +91 -0
- data/rust/host/src/bin/expr_harness.rs +6 -2
- data/rust/host/src/bin/lineage_harness.rs +47 -11
- data/rust/host/src/bin/mint_harness.rs +70 -37
- data/rust/host/src/bin/validate_harness.rs +16 -8
- data/rust/host/src/boot.rs +144 -68
- data/rust/host/src/boundary_fuzz/api.rs +130 -32
- data/rust/host/src/boundary_fuzz/auth.rs +108 -23
- data/rust/host/src/boundary_fuzz/expr_json.rs +85 -11
- data/rust/host/src/boundary_fuzz/ir.rs +164 -12
- data/rust/host/src/boundary_fuzz/server.rs +283 -36
- data/rust/host/src/boundary_fuzz/web.rs +56 -8
- data/rust/host/src/checkout.rs +78 -32
- data/rust/host/src/clock.rs +8 -0
- data/rust/host/src/commerce_ir.rs +333 -95
- data/rust/host/src/crypto.rs +33 -0
- data/rust/host/src/dispatch.rs +1037 -331
- data/rust/host/src/expr_json.rs +1021 -273
- data/rust/host/src/extension.rs +24 -4
- data/rust/host/src/field_hints.rs +11 -7
- data/rust/host/src/fuzz_support.rs +85 -13
- data/rust/host/src/host.rs +100 -0
- data/rust/host/src/http.rs +22 -0
- data/rust/host/src/ir.rs +326 -16
- data/rust/host/src/journal.rs +277 -176
- data/rust/host/src/lambda_client.rs +219 -53
- data/rust/host/src/lib.rs +16 -4
- data/rust/host/src/log.rs +62 -11
- data/rust/host/src/mint.rs +1153 -326
- data/rust/host/src/needs.rs +185 -44
- data/rust/host/src/payments/keystore.rs +197 -45
- data/rust/host/src/payments/roles.rs +39 -7
- data/rust/host/src/payments/tests.rs +1427 -282
- data/rust/host/src/payments.rs +391 -112
- data/rust/host/src/platform_signin/attack_tests.rs +5 -5
- data/rust/host/src/platform_signin/config.rs +11 -11
- data/rust/host/src/platform_signin/cookie.rs +2 -2
- data/rust/host/src/platform_signin/extension.rs +2 -1
- data/rust/host/src/platform_signin/handler.rs +9 -8
- data/rust/host/src/platform_signin/jti.rs +4 -2
- data/rust/host/src/platform_signin/jwks.rs +2 -1
- data/rust/host/src/platform_signin/jws.rs +2 -37
- data/rust/host/src/platform_signin/known_answer_tests.rs +5 -1
- data/rust/host/src/platform_signin/mode_tests.rs +5 -3
- data/rust/host/src/platform_signin/pages.rs +20 -20
- data/rust/host/src/platform_signin/session.rs +8 -1
- data/rust/host/src/platform_signin/settings.rs +6 -6
- data/rust/host/src/platform_signin/strict_json.rs +12 -9
- data/rust/host/src/platform_signin/verify.rs +41 -41
- data/rust/host/src/platform_signin/wiring_support.rs +13 -18
- data/rust/host/src/presentation.rs +207 -63
- data/rust/host/src/presentation_write.rs +502 -187
- data/rust/host/src/query_step.rs +69 -18
- data/rust/host/src/rate_limit.rs +541 -142
- data/rust/host/src/reference_transform.rs +77 -25
- data/rust/host/src/reference_validate.rs +443 -104
- data/rust/host/src/resend.rs +190 -55
- data/rust/host/src/secrets.rs +116 -21
- data/rust/host/src/server.rs +555 -104
- data/rust/host/src/storage_shape.rs +88 -21
- data/rust/host/src/test_pg.rs +17 -7
- data/rust/host/src/ui_schema.rs +495 -207
- data/rust/host/src/wasm_runner.rs +21 -6
- data/rust/host/src/web/commerce.rs +101 -23
- data/rust/host/src/web/newsletter/cooldown.rs +10 -3
- data/rust/host/src/web/newsletter/template.rs +26 -7
- data/rust/host/src/web/newsletter/tests.rs +112 -23
- data/rust/host/src/web/newsletter.rs +209 -59
- data/rust/host/src/web/newsletter_send.rs +251 -79
- data/rust/host/src/web/registration_receipt/tests.rs +16 -4
- data/rust/host/src/web/registration_receipt.rs +100 -24
- data/rust/host/src/web/registrations.rs +1384 -332
- data/rust/host/src/web/resend_webhook.rs +138 -80
- data/rust/host/src/web.rs +2416 -508
- data/rust/lsp/Cargo.toml +36 -0
- data/rust/lsp/clippy.toml +4 -0
- data/rust/lsp/src/json.rs +29 -22
- data/rust/lsp/src/main.rs +81 -31
- data/rust/lsp/src/outline.rs +23 -7
- data/rust/lsp/src/rpc.rs +17 -4
- data/rust/parser/Cargo.toml +36 -0
- data/rust/parser/clippy.toml +4 -0
- data/rust/parser/src/build/closed_sets.rs +6 -1
- data/rust/parser/src/build/identity.rs +4 -3
- data/rust/parser/src/build/mod.rs +1 -1
- data/rust/parser/src/build/named_pattern.rs +4 -1
- data/rust/parser/src/build/naming.rs +2 -80
- data/rust/parser/src/build/pattern_subset.rs +6 -8
- data/rust/parser/src/build/query_derive.rs +3 -1
- data/rust/parser/src/build/query_inference.rs +14 -5
- data/rust/parser/src/build/read_model.rs +4 -1
- data/rust/parser/src/build/references.rs +4 -3
- data/rust/parser/src/canonical.rs +34 -15
- data/rust/parser/src/emit.rs +187 -105
- data/rust/parser/src/expr/ast_json.rs +134 -42
- data/rust/parser/src/expr/evaluator.rs +27 -9
- data/rust/parser/src/expr/mod.rs +54 -13
- data/rust/parser/src/expr/resolver.rs +299 -92
- data/rust/parser/src/ir.rs +35 -3
- data/rust/parser/src/keywords.rs +7168 -516
- data/rust/parser/src/lex.rs +11 -25
- data/rust/parser/src/lib.rs +1 -0
- data/rust/parser/src/main.rs +65 -6
- data/rust/parser/src/parse/adapter.rs +157 -0
- data/rust/parser/src/parse/aggregate.rs +19 -10
- data/rust/parser/src/parse/chapter.rs +16 -17
- data/rust/parser/src/parse/command.rs +35 -16
- data/rust/parser/src/parse/domain_port.rs +4 -7
- data/rust/parser/src/parse/entity.rs +21 -11
- data/rust/parser/src/parse/hecksagon.rs +10 -4
- data/rust/parser/src/parse/lifecycle.rs +9 -5
- data/rust/parser/src/parse/mod.rs +22 -91
- data/rust/parser/src/parse/needs.rs +21 -12
- data/rust/parser/src/parse/policy.rs +2 -2
- data/rust/parser/src/parse/process_manager.rs +9 -11
- data/rust/parser/src/parse/query.rs +8 -4
- data/rust/parser/src/parse/read_model.rs +57 -16
- data/rust/parser/src/parse/value_object.rs +7 -2
- data/rust/parser/src/parse/world.rs +322 -0
- data/rust/parser/src/ruby_value.rs +7 -6
- data/rust/src/exemplar/json.rs +4 -4
- data/rust/src/exemplar/mutations.rs +18 -0
- data/rust/src/exemplar/read_models.rs +1 -10
- data/rust/src/kernel/attribute_shapes/composite.rs +54 -14
- data/rust/src/kernel/attribute_shapes/list.rs +2 -2
- data/rust/src/kernel/attribute_shapes/mod.rs +8 -8
- data/rust/src/kernel/attribute_shapes/scalar.rs +2 -2
- data/rust/src/kernel/cli.rs +244 -114
- data/rust/src/kernel/dispatch.rs +323 -115
- data/rust/src/kernel/dispatch_guard_tests.rs +327 -65
- data/rust/src/kernel/expr.rs +203 -86
- data/rust/src/kernel/expression_operators/arithmetic.rs +138 -25
- data/rust/src/kernel/expression_operators/comparison.rs +87 -2
- data/rust/src/kernel/expression_operators/enumeration.rs +106 -22
- data/rust/src/kernel/expression_operators/logical.rs +9 -3
- data/rust/src/kernel/expression_operators/membership.rs +3 -1
- data/rust/src/kernel/expression_operators/mod.rs +34 -21
- data/rust/src/kernel/expression_operators/pattern_match.rs +46 -11
- data/rust/src/kernel/expression_operators/positional.rs +84 -30
- data/rust/src/kernel/expression_operators/presence.rs +52 -13
- data/rust/src/kernel/expression_operators/sign_test.rs +3 -1
- data/rust/src/kernel/expression_operators/sized.rs +24 -8
- data/rust/src/kernel/expression_operators/text.rs +120 -27
- data/rust/src/kernel/expression_operators/to_string.rs +10 -4
- data/rust/src/kernel/json.rs +244 -148
- data/rust/src/kernel/mod.rs +47 -33
- data/rust/src/kernel/named_query.rs +63 -15
- data/rust/src/kernel/needs.rs +112 -25
- data/rust/src/kernel/orchestrate.rs +980 -581
- data/rust/src/kernel/orchestrate_guard_tests.rs +538 -140
- data/rust/src/kernel/pattern.rs +107 -34
- data/rust/src/kernel/present.rs +261 -0
- data/rust/src/kernel/query_comparators.rs +118 -41
- data/rust/src/kernel/query_ordering.rs +13 -5
- data/rust/src/kernel/read_model.rs +479 -181
- data/rust/src/kernel/reference_lookup.rs +122 -26
- data/rust/src/kernel/repository.rs +293 -66
- data/rust/src/kernel/routing.rs +58 -42
- data/rust/src/kernel/ruby_float.rs +139 -0
- data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +57 -57
- data/rust/src/kernel/vocab/entity_dispatch_order.rs +54 -54
- data/rust/src/kernel/vocab/field_hint.rs +19 -25
- data/rust/src/kernel/vocab/mod.rs +6 -6
- data/rust/src/kernel/vocab/query_comparator.rs +23 -24
- data/rust/src/kernel/vocab/refusal_template.rs +501 -297
- data/rust/src/lib.rs +50 -0
- data/rust/src/main.rs +4 -1
- data/rust/web/Cargo.toml +36 -0
- data/rust/web/clippy.toml +4 -0
- metadata +85 -15
- data/examples/pizzas/bluebook/hecksagon/mock_stripe_payment_adapter.rb +0 -36
- data/lib/hecks/adapters/driven/google_authentication.adapter +0 -3
- data/lib/hecks/adapters/driven/google_authentication.rb +0 -99
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +0 -3
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +0 -32
- data/lib/hecks/ports/authentication.port +0 -6
- data/lib/hecks/ports/authentication.rb +0 -67
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +0 -255
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +0 -22
- data/rust/host/src/approval.rs +0 -652
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
require_relative "../../plain_english"
|
|
2
|
+
require_relative "../../statements"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Projections
|
|
6
|
+
module Glossary
|
|
7
|
+
module Sentences
|
|
8
|
+
# The paragraphs an action's term carries: what it is for and who does it, what it needs,
|
|
9
|
+
# what it checks first, what it changes, what it guarantees after, and what it records.
|
|
10
|
+
module Actions
|
|
11
|
+
# How each kind of change is said, before the field it changes.
|
|
12
|
+
CHANGES = {
|
|
13
|
+
"set" => "sets", "append" => "adds to", "increment" => "raises", "decrement" => "lowers",
|
|
14
|
+
"multiply" => "scales", "clamp" => "limits", "remove" => "removes from",
|
|
15
|
+
"delegate" => "passes on to", "corrects" => "corrects"
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# @param facts [Hash] the entry's facts: `:command`, `:holder` and `:value_objects`
|
|
21
|
+
# @param index [Glossary::Index] resolves links to other terms
|
|
22
|
+
# @return [Array<String>] the paragraphs, each present only when it has something to say
|
|
23
|
+
def paragraphs(facts, index)
|
|
24
|
+
command, holder = facts.values_at(:command, :holder)
|
|
25
|
+
[Sentences.command_sentence(command), needs(command, index, facts[:within]),
|
|
26
|
+
checks(holder, command), changes(holder, command), afterwards(command),
|
|
27
|
+
records(command, index)].compact
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# "Needs a topping (Topping name) and an amount (Topping amount)."
|
|
31
|
+
def needs(command, index, within)
|
|
32
|
+
fields = command.attributes.map { |field| need_phrase(field, index, within) }
|
|
33
|
+
fields.empty? ? nil : "Needs #{Naming.to_sentence_list(fields)}."
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def need_phrase(field, index, within)
|
|
37
|
+
name = PlainEnglish.noun(field.name)
|
|
38
|
+
name = PlainEnglish.a(name) unless field.list?
|
|
39
|
+
detail = [ValueObjects.type_words(field, index, within), ("optional" if field.optional?)].compact
|
|
40
|
+
"#{name} (#{detail.join(", ")})"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# "Allowed only when: the order is available; at most 10 toppings."
|
|
44
|
+
def checks(holder, command)
|
|
45
|
+
rules = Statements::Actions.checks(holder, command, said_by_move: false)
|
|
46
|
+
rules.empty? ? nil : "Allowed only when: #{rules.join("; ")}."
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# "Sets the customer name and moves the order from available to sold."
|
|
50
|
+
def changes(holder, command)
|
|
51
|
+
parts = command.mutations.group_by { |mutation| CHANGES.fetch(mutation.op.to_s, "changes") }
|
|
52
|
+
.map { |verb, mutations| change_phrase(verb, mutations) }
|
|
53
|
+
parts << move(holder, command)
|
|
54
|
+
parts.compact!
|
|
55
|
+
parts.empty? ? nil : "#{PlainEnglish.upper(Naming.to_sentence_list(parts))}."
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# "sets the name and the pizza"
|
|
59
|
+
def change_phrase(verb, mutations)
|
|
60
|
+
fields = mutations.map { |mutation| "the #{PlainEnglish.noun(mutation.target.to_s.split(".").last)}" }
|
|
61
|
+
"#{verb} #{Naming.to_sentence_list(fields.uniq)}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The lifecycle move a command makes, as "moves the order from available to sold".
|
|
65
|
+
def move(holder, command)
|
|
66
|
+
lifecycle = holder.respond_to?(:lifecycle) && holder.lifecycle
|
|
67
|
+
return nil unless lifecycle
|
|
68
|
+
|
|
69
|
+
found = Statements::Actions.moves(lifecycle).find { |name, _move| name == command.hecks_name }
|
|
70
|
+
found && Statements::Actions.move_phrase(found.last, "the #{PlainEnglish.noun(holder.hecks_name)}")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# "Afterwards, always: the balance fell by exactly the amount."
|
|
74
|
+
def afterwards(command)
|
|
75
|
+
rules = command.ensures.map { |rule| Statements::Actions.text(rule) }
|
|
76
|
+
rules.empty? ? nil : "Afterwards, always: #{rules.join("; ")}."
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# "Records Topping added."
|
|
80
|
+
def records(command, index)
|
|
81
|
+
events = command.emits.map { |event| index.link(:event, event.to_s.split(/::|\./).last) }
|
|
82
|
+
events.empty? ? nil : "Records #{Naming.to_sentence_list(events)}."
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require_relative "../../plain_english"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Glossary
|
|
6
|
+
module Sentences
|
|
7
|
+
# Speaks a dotted field path the way a reader names it. A one-field value object is
|
|
8
|
+
# named by its own name, so `pizza.price_cents.cents` is "price" and
|
|
9
|
+
# `customer_name.value` is "customer name".
|
|
10
|
+
class Paths
|
|
11
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the construct the path starts on
|
|
12
|
+
# @param value_objects [Array<Bluebook::ValueObject>] the value objects types resolve to
|
|
13
|
+
def initialize(holder, value_objects)
|
|
14
|
+
@holder = holder
|
|
15
|
+
@by_name = value_objects.to_h { |value_object| [value_object.hecks_name, value_object] }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @param path [String, Symbol] a dotted field path
|
|
19
|
+
# @return [Boolean] whether it is the holder's lifecycle field
|
|
20
|
+
def lifecycle?(path)
|
|
21
|
+
lifecycle = @holder.respond_to?(:lifecycle) && @holder.lifecycle
|
|
22
|
+
lifecycle ? lifecycle.field.to_s == path.to_s : false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @param path [String, Symbol] a dotted field path
|
|
26
|
+
# @return [String] how a reader names the field
|
|
27
|
+
def label(path)
|
|
28
|
+
hops = path.to_s.split("/")
|
|
29
|
+
return hops.map { |hop| PlainEnglish.noun(hop) }.join("'s ") if hops.size > 1
|
|
30
|
+
|
|
31
|
+
walk(path).first
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @param path [String, Symbol] a dotted field path
|
|
35
|
+
# @return [String, nil] the type the path ends on, such as "String", when it resolves
|
|
36
|
+
def type(path) = walk(path).last
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# Follows each segment through the attributes and value objects it names. Inside a
|
|
41
|
+
# one-field value object the label already said is kept.
|
|
42
|
+
def walk(path)
|
|
43
|
+
segments = path.to_s.split(".")
|
|
44
|
+
state = { label: PlainEnglish.noun(segments.last), type: nil, attributes: @holder.attributes, named: false }
|
|
45
|
+
segments.each do |segment|
|
|
46
|
+
field = state[:attributes].find { |attribute| attribute.name.to_s == segment }
|
|
47
|
+
return [state[:label], nil] unless field
|
|
48
|
+
|
|
49
|
+
state = step(field, state)
|
|
50
|
+
end
|
|
51
|
+
[state[:label], leaf_type(state)]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The primitive a path ends on: a value object compares by its one number, when it has
|
|
55
|
+
# exactly one, as the query runtime does.
|
|
56
|
+
def leaf_type(state)
|
|
57
|
+
attributes = state[:attributes]
|
|
58
|
+
return attributes.first.type.to_s if state[:named]
|
|
59
|
+
|
|
60
|
+
numbers = attributes.select { |attribute| %w[Integer Float].include?(attribute.type.to_s) }
|
|
61
|
+
numbers.one? ? numbers.first.type.to_s : state[:type]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The state after one segment: its label, type, and where the next segment is looked up.
|
|
65
|
+
def step(field, state)
|
|
66
|
+
value_object = @by_name[field.type.to_s]
|
|
67
|
+
label = state[:named] ? state[:label] : PlainEnglish.noun(field.name)
|
|
68
|
+
single = value_object && value_object.attributes.size == 1
|
|
69
|
+
label = PlainEnglish.noun(value_object.hecks_name) if single
|
|
70
|
+
{ label: label, type: field.type.to_s, attributes: value_object ? value_object.attributes : [],
|
|
71
|
+
named: single || false }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require_relative "../../plain_english"
|
|
2
|
+
require_relative "paths"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Projections
|
|
6
|
+
module Glossary
|
|
7
|
+
module Sentences
|
|
8
|
+
# The sentence a list's term carries, read from its filters, ordering and limit:
|
|
9
|
+
# "Orders that are available, sorted by pizza name, A to Z."
|
|
10
|
+
module Queries
|
|
11
|
+
# How each comparison is said between a field and its value.
|
|
12
|
+
OPERATORS = {
|
|
13
|
+
"eq" => "is", "ne" => "is not", "lt" => "is less than", "lte" => "is at most",
|
|
14
|
+
"gt" => "is more than", "gte" => "is at least", "in" => "is", "contains" => "includes"
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# @param query [Bluebook::Query] the query
|
|
20
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the construct it lists
|
|
21
|
+
# @param value_objects [Array<Bluebook::ValueObject>] the aggregate's value objects
|
|
22
|
+
# @return [Array<String>] the author's description, when there is one, then the
|
|
23
|
+
# derived sentence
|
|
24
|
+
def paragraphs(query, holder, value_objects)
|
|
25
|
+
paths = Paths.new(holder, value_objects)
|
|
26
|
+
[query.description, sentence(query, holder, paths), limit(query)].compact
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def sentence(query, holder, paths)
|
|
30
|
+
things = PlainEnglish.plural(PlainEnglish.noun(holder.hecks_name))
|
|
31
|
+
filters = query.wheres.map { |clause| filter(clause, paths) }
|
|
32
|
+
subject = filters.empty? ? "All #{things}" : "#{PlainEnglish.upper(things)} #{filters.join(" and ")}"
|
|
33
|
+
"#{subject}#{order(query, things, paths)}."
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# "that are available" on the lifecycle field, otherwise "whose price is less than ...".
|
|
37
|
+
def filter(clause, paths)
|
|
38
|
+
operator = clause.op.to_s
|
|
39
|
+
value = value_words(clause.value, operator)
|
|
40
|
+
return "that are #{"not " if operator == "ne"}#{value}" if paths.lifecycle?(clause.field)
|
|
41
|
+
return "that have none #{value}" if operator == "none_in_state"
|
|
42
|
+
|
|
43
|
+
"whose #{paths.label(clause.field)} #{OPERATORS.fetch(operator, operator.tr("_", " "))} #{value}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# A parameter is "the ceiling you give"; a list of states is "open or frozen".
|
|
47
|
+
def value_words(value, operator)
|
|
48
|
+
return "the #{PlainEnglish.noun(value)} you give" if value.is_a?(Symbol)
|
|
49
|
+
return PlainEnglish.states(value.to_s.split(",").map(&:strip)) if operator == "in"
|
|
50
|
+
|
|
51
|
+
PlainEnglish.state(value)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# ", sorted by price, lowest first", and where unset values go when that is declared.
|
|
55
|
+
def order(query, things, paths)
|
|
56
|
+
order_by = query.order_by
|
|
57
|
+
return "" unless order_by
|
|
58
|
+
|
|
59
|
+
label = paths.label(order_by.field)
|
|
60
|
+
words = direction(paths.type(order_by.field), order_by.direction.to_s == "desc")
|
|
61
|
+
", sorted by #{label}#{words}#{nulls(query, things, label)}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def direction(type, descending)
|
|
65
|
+
case type
|
|
66
|
+
when "String" then descending ? ", Z to A" : ", A to Z"
|
|
67
|
+
when "Integer", "Float" then descending ? ", highest first" : ", lowest first"
|
|
68
|
+
else descending ? ", in reverse order" : ""
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def nulls(query, things, label)
|
|
73
|
+
mode = query.null_semantics&.mode.to_s
|
|
74
|
+
return "" unless %w[first last].include?(mode)
|
|
75
|
+
|
|
76
|
+
", with #{things} that have no #{label} #{mode}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# "Shows at most 50." when the list is capped.
|
|
80
|
+
def limit(query)
|
|
81
|
+
value = query.limit&.value
|
|
82
|
+
return nil unless value
|
|
83
|
+
|
|
84
|
+
value.is_a?(Symbol) ? "Shows at most the number you give." : "Shows at most #{value}."
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require_relative "../../../naming"
|
|
2
2
|
require_relative "../../statements"
|
|
3
3
|
require_relative "../sensitivity"
|
|
4
|
+
require_relative "../../../bluebook/named_pattern"
|
|
4
5
|
|
|
5
6
|
module Hecks
|
|
6
7
|
module Projections
|
|
@@ -14,6 +15,13 @@ module Hecks
|
|
|
14
15
|
"Boolean" => "yes or no", "TrueClass" => "yes or no", "FalseClass" => "yes or no"
|
|
15
16
|
}.freeze
|
|
16
17
|
|
|
18
|
+
# What each named pattern asks of a value, in words.
|
|
19
|
+
PATTERN_WORDS = {
|
|
20
|
+
present: "must not be blank", not_empty: "must not be empty", no_whitespace: "must have no spaces",
|
|
21
|
+
snake_name: "must be lowercase words joined by underscores", git_sha: "must be a commit id",
|
|
22
|
+
digits: "must be digits only"
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
17
25
|
module_function
|
|
18
26
|
|
|
19
27
|
def paragraphs(value_object, index, within, sensitive = {})
|
|
@@ -28,7 +36,7 @@ module Hecks
|
|
|
28
36
|
attributes = value_object.attributes
|
|
29
37
|
return "A marker with no details of its own." if attributes.empty?
|
|
30
38
|
|
|
31
|
-
return
|
|
39
|
+
return bare_sentence(attributes.first, index, within) if bare_value?(attributes)
|
|
32
40
|
|
|
33
41
|
fields = attributes.map { |field| field_phrase(field, index, within, sensitive[field.name.to_s]) }
|
|
34
42
|
"Made up of #{Naming.to_sentence_list(fields)}."
|
|
@@ -36,18 +44,37 @@ module Hecks
|
|
|
36
44
|
|
|
37
45
|
def bare_value?(attributes) = attributes.size == 1 && attributes.first.name.to_s == "value"
|
|
38
46
|
|
|
39
|
-
# "
|
|
47
|
+
# "Text that must not be blank.", or just "A whole number." when nothing constrains it.
|
|
48
|
+
def bare_sentence(field, index, within)
|
|
49
|
+
rule = constraint(field)
|
|
50
|
+
"#{Sentences.upper_first(type_words(field, index, within))}#{" that #{rule}" if rule}."
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# "amount (a whole number)", "name (text, must not be blank)", or, when marked
|
|
54
|
+
# sensitive, "medications (text, PHI)".
|
|
40
55
|
def field_phrase(field, index, within, marking = nil)
|
|
41
|
-
detail = [type_words(field, index, within), (
|
|
56
|
+
detail = [type_words(field, index, within), constraint(field), ("optional" if field.optional?),
|
|
57
|
+
(Sensitivity.tag(marking) if marking)].compact.join(", ")
|
|
42
58
|
"#{Naming.words(field.name).downcase} (#{detail})"
|
|
43
59
|
end
|
|
44
60
|
|
|
61
|
+
# A plain type in words, a reference as a link to what it points at, otherwise a link
|
|
62
|
+
# to the value's own term.
|
|
45
63
|
def type_words(field, index, within)
|
|
46
|
-
|
|
47
|
-
|
|
64
|
+
inner = if field.reference? then index.link(:aggregate, field.type.target_name)
|
|
65
|
+
else TYPE_WORDS[field.type.to_s] || index.link(:value_object, field.type.to_s, within: within)
|
|
66
|
+
end
|
|
48
67
|
field.list? ? "a list of #{inner}" : inner
|
|
49
68
|
end
|
|
50
69
|
|
|
70
|
+
# What a field's pattern asks of its value, or nil when it has none.
|
|
71
|
+
def constraint(field)
|
|
72
|
+
return nil unless field.pattern
|
|
73
|
+
|
|
74
|
+
name = Bluebook::NamedPattern::TABLE.key(field.pattern)
|
|
75
|
+
name ? PATTERN_WORDS[name] : "must follow a set format"
|
|
76
|
+
end
|
|
77
|
+
|
|
51
78
|
# A multi-field row leads with its first field and keeps the rest beside it,
|
|
52
79
|
# so no row loses what makes it distinct.
|
|
53
80
|
def closed_set_sentence(members)
|
|
@@ -2,6 +2,8 @@ require_relative "../../naming"
|
|
|
2
2
|
require_relative "../statements"
|
|
3
3
|
require_relative "sensitivity"
|
|
4
4
|
require_relative "sentences/value_objects"
|
|
5
|
+
require_relative "sentences/actions"
|
|
6
|
+
require_relative "sentences/queries"
|
|
5
7
|
|
|
6
8
|
module Hecks
|
|
7
9
|
module Projections
|
|
@@ -12,8 +14,6 @@ module Hecks
|
|
|
12
14
|
module Sentences
|
|
13
15
|
# The one sentence each kind of term other than an entity or value object carries.
|
|
14
16
|
SINGLES = {
|
|
15
|
-
command: ->(facts, _index) { command_sentence(facts[:command]) },
|
|
16
|
-
query: ->(facts, _index) { facts[:query].description },
|
|
17
17
|
event: ->(facts, index) { event_sentence(facts, index) },
|
|
18
18
|
policy: ->(facts, index) { policy_sentence(facts[:policy], index) },
|
|
19
19
|
saga: ->(facts, index) { saga_sentence(facts[:saga], index) },
|
|
@@ -28,6 +28,8 @@ module Hecks
|
|
|
28
28
|
case entry.kind
|
|
29
29
|
when :entity then holder_paragraphs(facts[:entity])
|
|
30
30
|
when :value_object then value_object_paragraphs(entry, index)
|
|
31
|
+
when :command then Actions.paragraphs(facts, index)
|
|
32
|
+
when :query then Queries.paragraphs(facts[:query], facts[:holder], facts[:value_objects])
|
|
31
33
|
else [SINGLES.fetch(entry.kind).call(facts, index)]
|
|
32
34
|
end.compact
|
|
33
35
|
end
|
|
@@ -67,7 +69,7 @@ module Hecks
|
|
|
67
69
|
# A cross-domain trigger (`across "Compliance"`) is spoken as words with the
|
|
68
70
|
# domain named, since there is nothing here to link to.
|
|
69
71
|
def policy_sentence(policy, index)
|
|
70
|
-
sentence = "When #{
|
|
72
|
+
sentence = "When #{trigger_phrase(bare(policy.on_event), index)}, #{asked_phrase(policy, index)}"
|
|
71
73
|
if policy.for_each
|
|
72
74
|
query_holder, query = split_trigger(policy.for_each)
|
|
73
75
|
sentence += ", once for each row of #{index.link(:query, query, within: query_holder)}"
|
|
@@ -75,6 +77,14 @@ module Hecks
|
|
|
75
77
|
"#{sentence}."
|
|
76
78
|
end
|
|
77
79
|
|
|
80
|
+
# "Topping added happens", or, for an event nothing here records, its name in quotes
|
|
81
|
+
# and where it comes from.
|
|
82
|
+
def trigger_phrase(event, index)
|
|
83
|
+
return "#{index.link(:event, event)} happens" if index[:event, event]
|
|
84
|
+
|
|
85
|
+
"\"#{Naming.words(event)}\" arrives from outside"
|
|
86
|
+
end
|
|
87
|
+
|
|
78
88
|
# What the policy asks of whom, linked unless the trigger crosses into another domain.
|
|
79
89
|
def asked_phrase(policy, index)
|
|
80
90
|
holder, command = split_trigger(policy.trigger_command)
|
|
@@ -14,7 +14,10 @@ module Hecks
|
|
|
14
14
|
take.call(Markdown.title(bluebook))
|
|
15
15
|
sections.each do |section|
|
|
16
16
|
section.slug = take.call(section.title)
|
|
17
|
-
section.
|
|
17
|
+
Groups.walk(section).each do |kind, item|
|
|
18
|
+
slug = take.call(kind == :group ? item : item.headword)
|
|
19
|
+
item.slug = slug unless kind == :group
|
|
20
|
+
end
|
|
18
21
|
end
|
|
19
22
|
end
|
|
20
23
|
|
|
@@ -2,6 +2,7 @@ require_relative "../projector"
|
|
|
2
2
|
require_relative "../naming"
|
|
3
3
|
require_relative "statements"
|
|
4
4
|
require_relative "glossary/sections"
|
|
5
|
+
require_relative "glossary/groups"
|
|
5
6
|
require_relative "glossary/sensitivity"
|
|
6
7
|
require_relative "glossary/sentences"
|
|
7
8
|
require_relative "glossary/mermaid"
|
|
@@ -77,7 +78,7 @@ module Hecks
|
|
|
77
78
|
def with_headwords(entries, section_name)
|
|
78
79
|
entries.each { |entry| entry.headword = Naming.words(entry.name) }
|
|
79
80
|
entries.group_by(&:headword).each_value { |group| qualify_repeats(group, section_name) }
|
|
80
|
-
entries.sort_by { |entry| [entry.headword.downcase, entry.kind.to_s] }
|
|
81
|
+
entries.sort_by { |entry| [Groups.rank(entry), entry.headword.downcase, entry.kind.to_s] }
|
|
81
82
|
end
|
|
82
83
|
|
|
83
84
|
def qualify_repeats(group, section_name)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require_relative "../naming"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
# The few pieces of English grammar the statements and the glossary share: how a construct
|
|
6
|
+
# name is spoken as a noun, and how a command is spoken as something being done.
|
|
7
|
+
#
|
|
8
|
+
# PlainEnglish.doing("AddTopping", "Order") # => "adding a topping"
|
|
9
|
+
# PlainEnglish.doing("Purchase", "Order") # => "purchasing an order"
|
|
10
|
+
module PlainEnglish
|
|
11
|
+
# Nouns that take no article: "accruing interest", never "accruing an interest".
|
|
12
|
+
UNCOUNTED = %w[interest money cash information data feedback equipment evidence].freeze
|
|
13
|
+
|
|
14
|
+
# Two-syllable verbs stressed on the last syllable, which double their final consonant.
|
|
15
|
+
DOUBLED = %w[submit commit admit permit omit refer prefer transfer occur control patrol compel
|
|
16
|
+
expel regret equip rebut forget begin cancel].freeze
|
|
17
|
+
|
|
18
|
+
# One-word verbs that act on their holder through a preposition: "withdrawing from a
|
|
19
|
+
# card", never "withdrawing a card".
|
|
20
|
+
THROUGH = { "withdraw" => "from", "deposit" => "into" }.freeze
|
|
21
|
+
|
|
22
|
+
# Verbs written as one word whose "-ing" form splits back into a verb and a particle.
|
|
23
|
+
PHRASAL = {
|
|
24
|
+
"chargeback" => "charging back", "rollback" => "rolling back", "backup" => "backing up",
|
|
25
|
+
"setup" => "setting up", "signup" => "signing up", "checkout" => "checking out",
|
|
26
|
+
"login" => "logging in", "logout" => "logging out", "payout" => "paying out"
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# A construct name as a lowercase noun phrase; acronyms keep their capitals.
|
|
32
|
+
#
|
|
33
|
+
# @param name [String, Symbol] a construct or field name, such as `ATMCard` or `daily_limit`
|
|
34
|
+
# @return [String] the spoken noun, such as "ATM card" or "daily limit"
|
|
35
|
+
def noun(name)
|
|
36
|
+
Naming.words(name).split.map { |word| word.match?(/\A[A-Z]{2,}\z/) ? word : word.downcase }.join(" ")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @param phrase [String] a spoken noun phrase
|
|
40
|
+
# @return [String] the phrase with its indefinite article, such as "an order"
|
|
41
|
+
def a(phrase) = "#{Naming.a_or_an(phrase)} #{phrase}"
|
|
42
|
+
|
|
43
|
+
# @param phrase [String] a spoken noun phrase
|
|
44
|
+
# @return [String] the phrase with its last word pluralised, such as "ATM cards"
|
|
45
|
+
def plural(phrase)
|
|
46
|
+
*head, last = phrase.split
|
|
47
|
+
[*head, Naming.plural(last)].join(" ")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# A state name as it is said: `charged_back` -> "charged back".
|
|
51
|
+
def state(name) = name.to_s.tr("_", " ")
|
|
52
|
+
|
|
53
|
+
# @param states [Array<String>] state names
|
|
54
|
+
# @return [String] the spoken states joined with "or"
|
|
55
|
+
def states(states) = Naming.to_sentence_list(states.map { |name| state(name) }, conj: "or")
|
|
56
|
+
|
|
57
|
+
# @param text [String] a sentence fragment
|
|
58
|
+
# @return [String] the fragment with its first letter capitalised
|
|
59
|
+
def upper(text) = text.sub(/\A[[:lower:]]/, &:upcase)
|
|
60
|
+
|
|
61
|
+
# A command as the act of doing it. A one-word command takes its holder as the object;
|
|
62
|
+
# a command named as a past state ("Debited") reads as marking the holder so.
|
|
63
|
+
#
|
|
64
|
+
# @param command_name [String] the command's name, such as `AddTopping`
|
|
65
|
+
# @param holder_name [String] the aggregate or entity the command is declared on
|
|
66
|
+
# @return [String] the lowercase phrase, such as "adding a topping"
|
|
67
|
+
def doing(command_name, holder_name)
|
|
68
|
+
verb, *rest = noun(command_name).split
|
|
69
|
+
holder = a(noun(holder_name))
|
|
70
|
+
return "marking #{holder} #{[verb, *rest].join(" ")}" if verb.end_with?("ed")
|
|
71
|
+
return [gerund(verb), THROUGH[verb], holder].compact.join(" ") if rest.empty?
|
|
72
|
+
|
|
73
|
+
"#{gerund(verb)} #{object(rest.join(" "))}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @param phrase [String] the words after a command's verb
|
|
77
|
+
# @return [String] the phrase with an article, unless it names something uncounted
|
|
78
|
+
def object(phrase) = UNCOUNTED.include?(phrase.split.last) ? phrase : a(phrase)
|
|
79
|
+
|
|
80
|
+
# The "-ing" form of a plain verb.
|
|
81
|
+
#
|
|
82
|
+
# @param verb [String] a lowercase verb, such as "purchase" or "log"
|
|
83
|
+
# @return [String] its "-ing" form, such as "purchasing" or "logging"
|
|
84
|
+
def gerund(verb)
|
|
85
|
+
return PHRASAL[verb] if PHRASAL.key?(verb)
|
|
86
|
+
return "#{verb[0..-3]}ying" if verb.end_with?("ie")
|
|
87
|
+
return "#{verb[0..-2]}ing" if verb.match?(/[^aeiouy]e\z/) || verb.end_with?("ue")
|
|
88
|
+
return "#{verb}#{verb[-1]}ing" if doubles?(verb)
|
|
89
|
+
|
|
90
|
+
"#{verb}ing"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# One vowel then one plain consonant ("log", "set") doubles; so do the stressed `DOUBLED`.
|
|
94
|
+
def doubles?(verb) = DOUBLED.include?(verb) || verb.match?(/\A[^aeiou]*[aeiou][bdgklmnprt]\z/)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -163,8 +163,8 @@ module Hecks
|
|
|
163
163
|
def local_name(argument) = "#{argument}_text"
|
|
164
164
|
|
|
165
165
|
# @param argument [String] an argument's name
|
|
166
|
-
# @return [String] the Rust field,
|
|
167
|
-
def rust_field(argument) = RustVocabulary
|
|
166
|
+
# @return [String] the Rust field, escaped when the name is a keyword
|
|
167
|
+
def rust_field(argument) = RustVocabulary.field_name(argument)
|
|
168
168
|
end
|
|
169
169
|
end
|
|
170
170
|
end
|
|
@@ -10,7 +10,7 @@ module Hecks
|
|
|
10
10
|
" /// order given. A template is read, never evaluated. Private: call",
|
|
11
11
|
" /// sites go through a site's typed `<Variant>Args::render_args`,",
|
|
12
12
|
" /// which formats and supplies every declared argument.",
|
|
13
|
-
" fn render(
|
|
13
|
+
" fn render(self, values: &[(&str, &str)]) -> String {",
|
|
14
14
|
" let mut text = self.template().to_string();",
|
|
15
15
|
" for (key, value) in values {",
|
|
16
16
|
" text = text.replace(&format!(\"{{{key}}}\"), value);",
|
|
@@ -28,40 +28,38 @@ module Hecks
|
|
|
28
28
|
def impl_lines(spec, variants, rows)
|
|
29
29
|
enum = spec[:enum]
|
|
30
30
|
["impl #{enum} {",
|
|
31
|
-
constants(
|
|
32
|
-
rows.first.keys.map { |field| accessor(
|
|
33
|
-
from_name(
|
|
34
|
-
extras(
|
|
31
|
+
constants(variants, spec[:kind]),
|
|
32
|
+
rows.first.keys.map { |field| accessor(field, variants, rows) },
|
|
33
|
+
from_name(spec[:kind]),
|
|
34
|
+
extras(variants, spec[:kind]),
|
|
35
35
|
"}",
|
|
36
36
|
""]
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
# @param enum [String] the enum's name
|
|
40
39
|
# @param variants [Array<String>] the variant names
|
|
41
40
|
# @param kind [Symbol] `:order`, `:templates` or `:set`
|
|
42
41
|
# @return [Array<String>] the `ORDER` or `ALL` constant, as Rust lines
|
|
43
|
-
def constants(
|
|
44
|
-
list = variants.map { |variant| "
|
|
42
|
+
def constants(variants, kind)
|
|
43
|
+
list = variants.map { |variant| " Self::#{variant}," }
|
|
45
44
|
if kind == :order
|
|
46
45
|
[" /// The declared step order, first to last.",
|
|
47
|
-
" pub const ORDER: [
|
|
46
|
+
" pub const ORDER: [Self; #{variants.size}] = [", list, " ];", ""]
|
|
48
47
|
else
|
|
49
48
|
[" /// Every row, in declared order.",
|
|
50
|
-
" pub const ALL: &'static [
|
|
49
|
+
" pub const ALL: &'static [Self] = &[", list, " ];", ""]
|
|
51
50
|
end
|
|
52
51
|
end
|
|
53
52
|
|
|
54
|
-
# @param enum [String] the enum's name
|
|
55
53
|
# @param field [String] the row field the accessor reads
|
|
56
54
|
# @param variants [Array<String>] the variant names
|
|
57
55
|
# @param rows [Array<Hash>] the table's rows
|
|
58
56
|
# @return [Array] the accessor, as Rust lines
|
|
59
|
-
def accessor(
|
|
57
|
+
def accessor(field, variants, rows)
|
|
60
58
|
arms = variants.zip(rows).map do |variant, row|
|
|
61
|
-
"
|
|
59
|
+
" Self::#{variant} => #{RustVocabulary.rust_string(row.fetch(field))},"
|
|
62
60
|
end
|
|
63
61
|
[" /// The row's declared `#{field}`.",
|
|
64
|
-
" pub fn #{RustVocabulary.accessor_name(field)}(&self) -> &'static str {",
|
|
62
|
+
" pub const fn #{RustVocabulary.accessor_name(field)}(&self) -> &'static str {",
|
|
65
63
|
" match self {",
|
|
66
64
|
arms,
|
|
67
65
|
" }",
|
|
@@ -71,38 +69,35 @@ module Hecks
|
|
|
71
69
|
|
|
72
70
|
# Built on the enum's own list and name accessor, not a string match, so no wildcard arm.
|
|
73
71
|
#
|
|
74
|
-
# @param enum [String] the enum's name
|
|
75
72
|
# @param kind [Symbol] `:order`, `:templates` or `:set`
|
|
76
73
|
# @return [Array<String>] `from_name`, as Rust lines
|
|
77
|
-
def from_name(
|
|
74
|
+
def from_name(kind)
|
|
78
75
|
list = kind == :order ? "ORDER" : "ALL"
|
|
79
76
|
field = kind == :templates ? "site" : RustVocabulary.name_field(kind)
|
|
80
77
|
[" /// The row whose `#{field}` is `name`, if any.",
|
|
81
|
-
" pub fn from_name(name: &str) -> Option
|
|
82
|
-
"
|
|
78
|
+
" pub fn from_name(name: &str) -> Option<Self> {",
|
|
79
|
+
" Self::#{list}.iter().copied().find(|row| row.#{RustVocabulary.accessor_name(field)}() == name)",
|
|
83
80
|
" }",
|
|
84
81
|
""]
|
|
85
82
|
end
|
|
86
83
|
|
|
87
|
-
# @param enum [String] the enum's name
|
|
88
84
|
# @param variants [Array<String>] the variant names
|
|
89
85
|
# @param kind [Symbol] `:order`, `:templates` or `:set`
|
|
90
86
|
# @return [Array<String>] the methods only this kind of table has, as Rust lines
|
|
91
|
-
def extras(
|
|
87
|
+
def extras(variants, kind)
|
|
92
88
|
case kind
|
|
93
|
-
when :order then position_fn(
|
|
89
|
+
when :order then position_fn(variants)
|
|
94
90
|
when :templates then RENDER_FN
|
|
95
91
|
else []
|
|
96
92
|
end
|
|
97
93
|
end
|
|
98
94
|
|
|
99
|
-
# @param enum [String] the enum's name
|
|
100
95
|
# @param variants [Array<String>] the variant names
|
|
101
96
|
# @return [Array] `position`, as Rust lines
|
|
102
|
-
def position_fn(
|
|
103
|
-
arms = variants.each_with_index.map { |variant, index| "
|
|
97
|
+
def position_fn(variants)
|
|
98
|
+
arms = variants.each_with_index.map { |variant, index| " Self::#{variant} => #{index}," }
|
|
104
99
|
[" /// This step's index in `ORDER`.",
|
|
105
|
-
" pub fn position(&self) -> usize {",
|
|
100
|
+
" pub const fn position(&self) -> usize {",
|
|
106
101
|
" match self {",
|
|
107
102
|
arms,
|
|
108
103
|
" }",
|
|
@@ -59,11 +59,11 @@ module Hecks
|
|
|
59
59
|
|
|
60
60
|
# @return [Array<String>] the test that every row is found by its own name
|
|
61
61
|
def name_test(enum, kind)
|
|
62
|
-
list = kind == :order ? "ORDER" : "ALL"
|
|
62
|
+
list = kind == :order ? "&#{enum}::ORDER" : "#{enum}::ALL"
|
|
63
63
|
name = RustVocabulary.accessor_name(RustVocabulary.name_field(kind))
|
|
64
64
|
[" #[test]",
|
|
65
65
|
" fn every_row_is_found_by_its_own_name() {",
|
|
66
|
-
" for row in #{
|
|
66
|
+
" for row in #{list} {",
|
|
67
67
|
" assert_eq!(#{enum}::from_name(row.#{name}()), Some(*row));",
|
|
68
68
|
" }",
|
|
69
69
|
" }",
|