hecks 3.13.0 → 4.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.bluebook +4 -4
- data/examples/pizzas/bluebook/pizzas.hecksagon +5 -3
- data/examples/pizzas/config.ru +22 -0
- data/lib/hecks/adapters/driven/prism.rb +0 -2
- data/lib/hecks/adapters/driven.rb +0 -2
- data/lib/hecks/adapters/driving/json.rb +1 -7
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +15 -5
- 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/named_pattern.rb +33 -0
- 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 +3 -0
- data/lib/hecks/chapters.rb +3 -3
- data/lib/hecks/cli/domain_stub.rb +1 -1
- data/lib/hecks/cli/interview_aggregate.rb +4 -4
- 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/deploy/bluebook/deploy.bluebook +34 -34
- 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/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/framework/bluebook/change.bluebook +2 -2
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -3
- data/lib/hecks/fuzzing/domain_generator/block_rendering.rb +1 -1
- 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/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 +58 -37
- 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 +15 -92
- data/lib/hecks/hecks/hecks.bluebook +2 -2
- data/lib/hecks/hecks/hecks.hecksagon +2 -2
- data/lib/hecks/hecks/hecks.world +1 -1
- data/lib/hecks/hecks/rehearsal.bluebook +10 -16
- data/lib/hecks/hecks/tooling.bluebook +22 -22
- data/lib/hecks/kernel_capabilities/templates.rb +4 -4
- data/lib/hecks/kernel_capabilities.rb +1 -1
- data/lib/hecks/language/bluebook/aggregate.bluebook +1 -0
- data/lib/hecks/language/bluebook/command.bluebook +1 -0
- data/lib/hecks/language/bluebook/entity.bluebook +1 -0
- data/lib/hecks/language/bluebook/query.bluebook +1 -0
- data/lib/hecks/language/bluebook/shape.bluebook +8 -7
- data/lib/hecks/language/bluebook/vocabulary.bluebook +2 -3
- data/lib/hecks/language/hecksagon/port_operation.bluebook +1 -0
- 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/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/quality_control/quality_control.bluebook +51 -51
- 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.rb +13 -0
- 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/site/bluebook/site.bluebook +5 -5
- data/lib/hecks/sme/bluebook/sme.bluebook +12 -12
- data/lib/hecks/tickets/bluebook/tickets.bluebook +3 -3
- 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/{58208399bada10b0db99928c48e38789288fc58f7b71a72b21bfa386680e32ed.marshal → 02c09c73b272c1fb5a9ae6d87bb5e5e529208e00f6b4b597dc2c0fd70a9c0964.marshal} +0 -0
- data/prebuilt/{2b59737fbdbd5f44edda0fddce2611f45d76087b3a7c782d1f1a17ea58c77948.marshal → bb84a835cf6a15136bad5ca03aa5bf93a5b92f5f72444618d3ef2412b987fb32.marshal} +0 -0
- data/prebuilt/verdicts-f1f10da14c8e8f01b4c9b7e3e2d74be8cd2bf4676364ed17fa16975d2a076d75.json +1 -0
- 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 +300 -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 +145 -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/queries.rs +601 -226
- data/rust/codegen/src/reactions.rs +268 -74
- data/rust/codegen/src/read_models.rs +606 -205
- 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 +990 -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 +191 -0
- data/rust/host/src/payments/tests.rs +1445 -275
- data/rust/host/src/payments.rs +409 -121
- 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 +483 -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 -0
- data/rust/parser/src/build/named_pattern.rs +26 -0
- 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 -510
- 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 +43 -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 +229 -112
- 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 +230 -148
- data/rust/src/kernel/mod.rs +46 -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/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/prebuilt/verdicts-48318ca172edd21443b1ad02801c697fff100b746e828f09db987647641a5df2.json +0 -1
- data/rust/host/src/approval.rs +0 -652
|
@@ -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
|
" }",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
require_relative "../naming"
|
|
1
2
|
require_relative "../projector"
|
|
2
3
|
require_relative "../runtime/refusal_wording"
|
|
4
|
+
require_relative "../vocabulary"
|
|
3
5
|
require_relative "vocabulary"
|
|
4
6
|
require_relative "rust_vocabulary/enum_body"
|
|
5
7
|
require_relative "rust_vocabulary/arguments"
|
|
@@ -33,12 +35,20 @@ module Hecks
|
|
|
33
35
|
kind: :order }
|
|
34
36
|
}.freeze
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
while abstract become box do final macro override priv typeof unsized virtual yield try].freeze
|
|
38
|
+
# Keywords Rust refuses as a raw identifier, so they cannot take the `r#` escape.
|
|
39
|
+
NOT_RAW = %w[crate self Self super].freeze
|
|
39
40
|
|
|
40
41
|
module_function
|
|
41
42
|
|
|
43
|
+
# @param name [String] a field's name
|
|
44
|
+
# @return [String] the Rust field: raw-escaped when a keyword, with a trailing underscore
|
|
45
|
+
# when the keyword cannot be raw
|
|
46
|
+
def field_name(name)
|
|
47
|
+
return "#{name}_" if NOT_RAW.include?(name)
|
|
48
|
+
|
|
49
|
+
Hecks::Vocabulary.fetch("RustReservedWord").include?(name) ? "r##{name}" : name
|
|
50
|
+
end
|
|
51
|
+
|
|
42
52
|
def call(bluebook:, options: {}) = render(bluebook)
|
|
43
53
|
|
|
44
54
|
def render(bluebook)
|
|
@@ -121,14 +131,12 @@ module Hecks
|
|
|
121
131
|
def accessor_name(field) = field == "refusal" ? "refusal_class" : field
|
|
122
132
|
|
|
123
133
|
def variant_name(row, fields)
|
|
124
|
-
name = fields.map { |field| pascal(row.fetch(field)) }.join
|
|
134
|
+
name = fields.map { |field| Naming.pascal(row.fetch(field)) }.join
|
|
125
135
|
raise ArgumentError, "#{name.inspect} is not a Rust variant name" unless name.match?(/\A[A-Z][A-Za-z0-9]*\z/)
|
|
126
136
|
|
|
127
137
|
name
|
|
128
138
|
end
|
|
129
139
|
|
|
130
|
-
def pascal(text) = text.to_s.split("_").map { |part| part[0].to_s.upcase + part[1..].to_s }.join
|
|
131
|
-
|
|
132
140
|
# Only `\`, `"` and newlines need escaping in the text a vocabulary row holds.
|
|
133
141
|
def rust_string(text)
|
|
134
142
|
escaped = text.to_s.gsub("\\") { "\\\\" }.gsub('"') { "\\\"" }.gsub("\n") { "\\n" }
|
|
@@ -97,12 +97,6 @@ module Hecks
|
|
|
97
97
|
# @return [Boolean] whether `value` reaches `ratio` on every background
|
|
98
98
|
def reads?(value, against, ratio) = against.all? { |back| contrast(value, back) >= ratio }
|
|
99
99
|
|
|
100
|
-
# @param background [String] a fill
|
|
101
|
-
# @param light [String] a light foreground to try
|
|
102
|
-
# @param dark [String] a dark foreground to try
|
|
103
|
-
# @return [String] whichever reads better on `background`
|
|
104
|
-
def foreground(background, light, dark) = contrast(light, background) >= contrast(dark, background) ? light : dark
|
|
105
|
-
|
|
106
100
|
# @return [Float] the hue, in degrees, of a colour given by its channels
|
|
107
101
|
def angle(red, green, blue, high, spread)
|
|
108
102
|
degrees = if high == red then ((green - blue) / spread) % 6
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require_relative "../plain_english"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Statements
|
|
6
|
+
# The statements a holder's lifecycle and commands carry: where it starts, what moves it
|
|
7
|
+
# between states, who does each command, and every rule a command checks before and after.
|
|
8
|
+
# Rule text is the author's own; only the command's name is turned into words around it.
|
|
9
|
+
#
|
|
10
|
+
# Actions.rules(order) # => ["Adding a topping requires: at most 10 toppings.", ...]
|
|
11
|
+
module Actions
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the construct with a lifecycle
|
|
15
|
+
# @return [Array<String>] where it starts, then one sentence per move
|
|
16
|
+
def lifecycle(holder)
|
|
17
|
+
lifecycle = holder.respond_to?(:lifecycle) && holder.lifecycle
|
|
18
|
+
return [] unless lifecycle
|
|
19
|
+
|
|
20
|
+
start = "#{PlainEnglish.upper(PlainEnglish.a(PlainEnglish.noun(holder.hecks_name)))} " \
|
|
21
|
+
"starts out #{PlainEnglish.state(lifecycle.default)}."
|
|
22
|
+
[start, *moves(lifecycle).map { |command, move| move_sentence(holder, command, move) }]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Each command's moves, as `[command, { from: [...], to: state }]`, merged across the
|
|
26
|
+
# transitions a command declares from more than one state.
|
|
27
|
+
def moves(lifecycle)
|
|
28
|
+
merged = lifecycle.transitions.each_with_object({}) do |(command, transition), found|
|
|
29
|
+
key = [command.to_s, transition.target]
|
|
30
|
+
found[key] ||= { from: [], to: transition.target }
|
|
31
|
+
found[key][:from] |= Array(transition.from)
|
|
32
|
+
end
|
|
33
|
+
merged.map { |(command, _target), move| [command, move] }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# "Purchasing an order moves it from available to sold."
|
|
37
|
+
def move_sentence(holder, command, move)
|
|
38
|
+
doing = PlainEnglish.doing(command, holder.hecks_name)
|
|
39
|
+
noun = PlainEnglish.noun(holder.hecks_name)
|
|
40
|
+
object = doing.match?(/\b#{Regexp.escape(noun)}\b/) ? "it" : "the #{noun}"
|
|
41
|
+
"#{PlainEnglish.upper(doing)} #{move_phrase(move, object)}."
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# "moves it from available to sold", "keeps it failed", or "moves it to closed".
|
|
45
|
+
#
|
|
46
|
+
# @param move [Hash] `{ from: [...], to: state }`, as `moves` answers it
|
|
47
|
+
# @param object [String] what is moved, such as "it" or "the order"
|
|
48
|
+
# @return [String] the verb phrase
|
|
49
|
+
def move_phrase(move, object)
|
|
50
|
+
to = PlainEnglish.state(move[:to])
|
|
51
|
+
return "keeps #{object} #{to}" if move[:from] == [move[:to]]
|
|
52
|
+
return "moves #{object} to #{to}" if move[:from].empty?
|
|
53
|
+
|
|
54
|
+
"moves #{object} from #{PlainEnglish.states(move[:from])} to #{to}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the construct the commands are on
|
|
58
|
+
# @return [Array<String>] each command's role, its rules, and what it guarantees after
|
|
59
|
+
def rules(holder)
|
|
60
|
+
holder.commands.flat_map { |command| command_rules(holder, command) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def command_rules(holder, command)
|
|
64
|
+
doing = PlainEnglish.doing(command.hecks_name, holder.hecks_name)
|
|
65
|
+
role = command.role && "#{PlainEnglish.upper(doing)} is done by the #{PlainEnglish.noun(command.role)}."
|
|
66
|
+
[role, *checks(holder, command).map { |rule| "#{PlainEnglish.upper(doing)} requires: #{rule}." },
|
|
67
|
+
*command.ensures.map { |rule| "After #{doing}: #{text(rule)}." }].compact
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# What a command checks first: the state it starts from, then each `given` in the
|
|
71
|
+
# author's words. The state is left out when a move sentence already says it.
|
|
72
|
+
#
|
|
73
|
+
# @param said_by_move [Boolean] whether a move sentence states the starting state
|
|
74
|
+
# @return [Array<String>] the rules, without a closing period
|
|
75
|
+
def checks(holder, command, said_by_move: true)
|
|
76
|
+
[state_check(holder, command, said_by_move), *command.givens.map { |rule| text(rule) }].compact
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# "the order is available", for a command declared `from:` a state.
|
|
80
|
+
def state_check(holder, command, said_by_move)
|
|
81
|
+
return nil unless command.from
|
|
82
|
+
return nil if said_by_move && moved_by?(holder, command)
|
|
83
|
+
|
|
84
|
+
"the #{PlainEnglish.noun(holder.hecks_name)} is #{PlainEnglish.states(Array(command.from))}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def moved_by?(holder, command)
|
|
88
|
+
lifecycle = holder.respond_to?(:lifecycle) && holder.lifecycle
|
|
89
|
+
return false unless lifecycle
|
|
90
|
+
|
|
91
|
+
lifecycle.transitions.any? { |name, _transition| name.to_s == command.hecks_name }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# A rule's description as written, without a trailing period.
|
|
95
|
+
def text(rule) = rule.description.to_s.strip.delete_suffix(".")
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|