hecks 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +65 -0
- data/lib/hecks/adapters/driven/folder.rb +73 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +25 -4
- data/lib/hecks/adapters/driven/governance_authorization.rb +57 -6
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +53 -7
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +93 -2
- data/lib/hecks/adapters/driven/identity_registry.rb +10 -0
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +22 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +35 -7
- data/lib/hecks/adapters/driven/lambda.rb +43 -2
- data/lib/hecks/adapters/driven/local_storage.rb +67 -1
- data/lib/hecks/adapters/driven/memory.rb +13 -13
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +20 -0
- data/lib/hecks/adapters/driven/prism.rb +46 -7
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +2 -0
- data/lib/hecks/adapters/driving/github_webhook.rb +14 -1
- data/lib/hecks/behaviors/dsl.rb +58 -0
- data/lib/hecks/behaviors/expectations.rb +168 -7
- data/lib/hecks/behaviors/ir.rb +11 -0
- data/lib/hecks/behaviors/rspec.rb +8 -0
- data/lib/hecks/behaviors/runner.rb +19 -0
- data/lib/hecks/behaviors.rb +8 -0
- data/lib/hecks/bluebook/aggregate.rb +30 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +7 -0
- data/lib/hecks/bluebook/assembly/build.rb +25 -4
- data/lib/hecks/bluebook/assembly/contract.rb +66 -14
- data/lib/hecks/bluebook/assembly/contracts.rb +24 -19
- data/lib/hecks/bluebook/assembly/marks.rb +143 -14
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -0
- data/lib/hecks/bluebook/assembly.rb +25 -7
- data/lib/hecks/bluebook/attribute.rb +17 -3
- data/lib/hecks/bluebook/behaviour/aggregate.rb +32 -0
- data/lib/hecks/bluebook/behaviour/attribute.rb +13 -0
- data/lib/hecks/bluebook/behaviour/chapter.rb +55 -2
- data/lib/hecks/bluebook/behaviour/command.rb +33 -3
- data/lib/hecks/bluebook/behaviour/domain_port.rb +20 -0
- data/lib/hecks/bluebook/behaviour/entity.rb +12 -0
- data/lib/hecks/bluebook/behaviour/hexagon.rb +26 -0
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +23 -2
- data/lib/hecks/bluebook/behaviour/policy.rb +34 -9
- data/lib/hecks/bluebook/behaviour/process_manager.rb +32 -1
- data/lib/hecks/bluebook/behaviour/query.rb +5 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +21 -0
- data/lib/hecks/bluebook/behaviour/traits.rb +36 -0
- data/lib/hecks/bluebook/behaviour/value_object.rb +17 -5
- data/lib/hecks/bluebook/chapter.rb +23 -0
- data/lib/hecks/bluebook/command.rb +53 -8
- data/lib/hecks/bluebook/domain_port.rb +25 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +196 -37
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +79 -42
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +59 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +385 -53
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +135 -21
- data/lib/hecks/bluebook/dsl/command_builder.rb +189 -27
- data/lib/hecks/bluebook/dsl/entity_builder.rb +139 -9
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +41 -0
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +23 -6
- data/lib/hecks/bluebook/dsl/policy_builder.rb +60 -19
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +79 -15
- data/lib/hecks/bluebook/dsl/query_builder.rb +33 -4
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +102 -27
- data/lib/hecks/bluebook/dsl/rule_reference.rb +60 -8
- data/lib/hecks/bluebook/dsl/translation_builder.rb +146 -40
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +55 -7
- data/lib/hecks/bluebook/entity.rb +29 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +115 -23
- data/lib/hecks/bluebook/expression/ast_reader.rb +29 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +48 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +207 -29
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +36 -0
- data/lib/hecks/bluebook/expression/resolver.rb +320 -77
- data/lib/hecks/bluebook/hexagon.rb +34 -0
- data/lib/hecks/bluebook/lifecycle.rb +11 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +30 -26
- data/lib/hecks/bluebook/meta_validator/plan.rb +43 -8
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +157 -7
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -4
- data/lib/hecks/bluebook/meta_validator/shapes.rb +141 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +149 -23
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +5 -4
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +1 -0
- data/lib/hecks/bluebook/meta_validator.rb +180 -84
- data/lib/hecks/bluebook/model_check.rb +268 -24
- data/lib/hecks/bluebook/pattern_subset.rb +23 -1
- data/lib/hecks/bluebook/process_manager.rb +13 -0
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +44 -0
- data/lib/hecks/bluebook/query.rb +27 -0
- data/lib/hecks/bluebook/read_model.rb +21 -1
- data/lib/hecks/bluebook/reference.rb +21 -8
- data/lib/hecks/bluebook/smoke_test.rb +29 -6
- data/lib/hecks/bluebook/synthesizer.rb +34 -0
- data/lib/hecks/bluebook/translation.rb +30 -1
- data/lib/hecks/bluebook/value_object.rb +23 -5
- data/lib/hecks/bluebook.rb +3 -4
- data/lib/hecks/codemod.rb +107 -20
- data/lib/hecks/construct.rb +15 -1
- data/lib/hecks/corpus.rb +146 -25
- data/lib/hecks/deploy/bluebook/deploy.bluebook +105 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/deploy/oidc.json +5 -0
- data/lib/hecks/doc/reference.rb +185 -16
- data/lib/hecks/embryonaut_bluebook.rb +32 -9
- data/lib/hecks/facade/handle.rb +76 -3
- data/lib/hecks/facade/surface/aggregate_door.rb +8 -0
- data/lib/hecks/forms/field_shape.rb +3 -0
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/port_argument.rb +12 -0
- data/lib/hecks/forms/query_form_renderer.rb +63 -0
- data/lib/hecks/forms/record_renderer.rb +58 -0
- data/lib/hecks/forms/record_table.rb +27 -0
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +10 -0
- data/lib/hecks/fqn.rb +58 -0
- data/lib/hecks/framework/bluebook/compliance.bluebook +221 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +43 -20
- data/lib/hecks/freezer.rb +17 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +159 -24
- data/lib/hecks/fuzzing/combination_miner.rb +59 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +109 -8
- data/lib/hecks/fuzzing/coverage_campaign.rb +56 -13
- data/lib/hecks/fuzzing/differential.rb +34 -0
- data/lib/hecks/fuzzing/domain_generator.rb +188 -11
- data/lib/hecks/fuzzing/era_boundary.rb +45 -15
- data/lib/hecks/fuzzing/form_census.rb +86 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +76 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +39 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +79 -22
- data/lib/hecks/fuzzing/nondeterministic.rb +13 -1
- data/lib/hecks/fuzzing/persistence_parity.rb +95 -3
- data/lib/hecks/fuzzing/properties/corrections.rb +25 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +158 -14
- data/lib/hecks/fuzzing/properties/guards.rb +44 -0
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +48 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +18 -0
- data/lib/hecks/fuzzing/properties/outbox.rb +49 -11
- data/lib/hecks/fuzzing/properties/querying.rb +68 -14
- data/lib/hecks/fuzzing/properties.rb +24 -15
- data/lib/hecks/fuzzing/qa_settings.rb +12 -0
- data/lib/hecks/fuzzing/replay.rb +137 -29
- data/lib/hecks/fuzzing/rotation_priority.rb +41 -21
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +46 -20
- data/lib/hecks/fuzzing/self_consistency.rb +189 -40
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +12 -6
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +1 -1
- data/lib/hecks/fuzzing/sequence_generator.rb +47 -0
- data/lib/hecks/fuzzing/shrinker.rb +114 -2
- data/lib/hecks/fuzzing/structural_skips.rb +18 -4
- data/lib/hecks/fuzzing/sweep_depth.rb +8 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +61 -14
- data/lib/hecks/fuzzing/value_generator.rb +98 -10
- data/lib/hecks/grammar/evolve.rb +178 -2
- data/lib/hecks/grammar.rb +46 -0
- data/lib/hecks/ir.rb +38 -7
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/literal.rb +32 -0
- data/lib/hecks/naming.rb +88 -7
- data/lib/hecks/ports/access_control.rb +5 -10
- data/lib/hecks/ports/authorization.rb +3 -6
- data/lib/hecks/ports/identity_assignment.rb +1 -2
- data/lib/hecks/ports/identity_resolution.rb +1 -2
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +85 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +6 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +38 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +6 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +40 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +92 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +8 -0
- data/lib/hecks/ports/query/in_memory.rb +39 -6
- data/lib/hecks/ports/query/ordering.rb +15 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +43 -8
- data/lib/hecks/projections/deploy/fargate.rb +666 -0
- data/lib/hecks/projections/deploy/lambda.rb +2423 -0
- data/lib/hecks/projections/deploy/shared.rb +624 -0
- data/lib/hecks/projections/diagrams.rb +243 -7
- data/lib/hecks/projections/glossary/html.rb +88 -0
- data/lib/hecks/projections/glossary/markdown.rb +47 -0
- data/lib/hecks/projections/glossary/mermaid.rb +48 -0
- data/lib/hecks/projections/glossary/sentences.rb +105 -0
- data/lib/hecks/projections/glossary.rb +161 -19
- data/lib/hecks/projections/model/deviations.rb +44 -0
- data/lib/hecks/projections/model.rb +51 -1
- data/lib/hecks/projections/oidc.rb +40 -3
- data/lib/hecks/projections/parser_table.rb +49 -8
- data/lib/hecks/projections/reference.rb +12 -0
- data/lib/hecks/projections/rust_vocabulary.rb +219 -16
- data/lib/hecks/projections/shape.rb +8 -0
- data/lib/hecks/projections/statements.rb +63 -16
- data/lib/hecks/projections/vocabulary.rb +17 -0
- data/lib/hecks/projections.rb +3 -0
- data/lib/hecks/projector/cli_projector.rb +218 -10
- data/lib/hecks/projector/docs_projector.rb +145 -19
- data/lib/hecks/projector/exporter.rb +65 -11
- data/lib/hecks/projector/ir_projector.rb +6 -0
- data/lib/hecks/projector/narrate_projector.rb +136 -15
- data/lib/hecks/projector/target.rb +47 -10
- data/lib/hecks/projector.rb +101 -19
- data/lib/hecks/query_ir.rb +47 -0
- data/lib/hecks/query_specification/common/null_policy.rb +5 -3
- data/lib/hecks/rendering.rb +6 -0
- data/lib/hecks/router/namespace_installer.rb +13 -0
- data/lib/hecks/router.rb +55 -0
- data/lib/hecks/runtime/aggregate_lock.rb +9 -0
- data/lib/hecks/runtime/boot_gates.rb +18 -0
- data/lib/hecks/runtime/caller.rb +32 -0
- data/lib/hecks/runtime/capability_graph.rb +11 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +23 -21
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +14 -15
- data/lib/hecks/runtime/command_interpreter.rb +42 -17
- data/lib/hecks/runtime/command_rules/admissibility.rb +165 -14
- data/lib/hecks/runtime/command_rules/arithmetic.rb +17 -5
- data/lib/hecks/runtime/command_rules/references.rb +118 -28
- data/lib/hecks/runtime/dependency_planning.rb +45 -0
- data/lib/hecks/runtime/dispatcher.rb +28 -50
- data/lib/hecks/runtime/entity_element.rb +161 -8
- data/lib/hecks/runtime/entity_interpreter.rb +44 -9
- data/lib/hecks/runtime/errors.rb +18 -4
- data/lib/hecks/runtime/event.rb +10 -5
- data/lib/hecks/runtime/identity.rb +71 -3
- data/lib/hecks/runtime/instance.rb +67 -7
- data/lib/hecks/runtime/interpreting.rb +13 -5
- data/lib/hecks/runtime/invocation.rb +118 -36
- data/lib/hecks/runtime/loader.rb +94 -8
- data/lib/hecks/runtime/outbox.rb +145 -7
- data/lib/hecks/runtime/policy_interpreter.rb +22 -9
- data/lib/hecks/runtime/port_operation_interpreter.rb +20 -0
- data/lib/hecks/runtime/query_interpreter.rb +40 -12
- data/lib/hecks/runtime/reaction_invocation.rb +53 -8
- data/lib/hecks/runtime/read_model_interpreter.rb +23 -7
- data/lib/hecks/runtime/rebuild_sweep.rb +28 -0
- data/lib/hecks/runtime/reference_hop.rb +42 -0
- data/lib/hecks/runtime/refusal_wording.rb +50 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +11 -0
- data/lib/hecks/runtime/registry/verification.rb +119 -4
- data/lib/hecks/runtime/registry.rb +157 -4
- data/lib/hecks/runtime/remote_dispatcher.rb +92 -6
- data/lib/hecks/runtime/routing.rb +27 -2
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +10 -12
- data/lib/hecks/runtime/saga_interpreter.rb +27 -13
- data/lib/hecks/runtime/tenant_check.rb +26 -6
- data/lib/hecks/runtime/tenant_scope.rb +18 -0
- data/lib/hecks/runtime/value/coercion.rb +255 -33
- data/lib/hecks/runtime/value/entity_list_coercion.rb +102 -30
- data/lib/hecks/runtime/value.rb +50 -0
- data/lib/hecks/runtime.rb +32 -0
- data/lib/hecks/storehouse.rb +305 -9
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks.rb +79 -1
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -0
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +12 -2
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +29 -5
- metadata +14 -5
- data/lib/hecks/codemod/legacy_dispatch_args.rb +0 -299
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +0 -186
- data/lib/hecks/deprecation.rb +0 -95
- data/lib/hecks/framework/bluebook/compliance.bluebook +0 -1
|
@@ -10,27 +10,30 @@ module Hecks
|
|
|
10
10
|
# a sanity check that the domain still says what someone thinks it
|
|
11
11
|
# says).
|
|
12
12
|
#
|
|
13
|
-
#
|
|
14
|
-
# discipline, held here too: a relationship's sentence is built
|
|
15
|
-
# mechanically from its own declared shape (holder, target,
|
|
16
|
-
# relationship kind — the same facts `Projections::Diagrams`'s own
|
|
17
|
-
# relationship_edge already reads), and an invariant's sentence is
|
|
18
|
-
# the domain author's own `description`, capitalized and
|
|
19
|
-
# punctuated, never paraphrased. If a fact has no author-written
|
|
20
|
-
# description and no unambiguous mechanical phrasing, it doesn't
|
|
21
|
-
# get a sentence here — a wrong sentence is worse than a missing
|
|
22
|
-
# one.
|
|
13
|
+
# ## Never invents a sentence from nothing
|
|
23
14
|
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# `
|
|
15
|
+
# `DocsProjector`'s own discipline, held here too: a relationship's
|
|
16
|
+
# sentence is built mechanically from its own declared shape (holder,
|
|
17
|
+
# target, relationship kind — the same facts `Projections::Diagrams`'s
|
|
18
|
+
# own relationship_edge already reads), and an invariant's sentence is
|
|
19
|
+
# the domain author's own `description`, capitalized and punctuated,
|
|
20
|
+
# never paraphrased. If a fact has no author-written description and
|
|
21
|
+
# no unambiguous mechanical phrasing, it doesn't get a sentence here —
|
|
22
|
+
# a wrong sentence is worse than a missing one.
|
|
23
|
+
#
|
|
24
|
+
# ## Reachable like any projection
|
|
25
|
+
#
|
|
26
|
+
# No new facade wiring needed: `Pizzas.project(Projections::Statements)`
|
|
27
|
+
# — `Facade::Surface::Chapter#project`'s own comment already settled
|
|
27
28
|
# this ("anything genuinely needing the graph is a projector, and a
|
|
28
29
|
# projector is given it"). `bin/statements` is a thin, optional
|
|
29
30
|
# convenience for reaching the same call from a shell.
|
|
30
31
|
#
|
|
31
|
-
# MVP scope
|
|
32
|
-
#
|
|
33
|
-
#
|
|
32
|
+
# ## MVP scope
|
|
33
|
+
#
|
|
34
|
+
# "has many"/"has a"/"belongs to"/"references" sentences for every
|
|
35
|
+
# list or relationship attribute, and every invariant's own
|
|
36
|
+
# description (aggregate-level and every nested value object's),
|
|
34
37
|
# verbatim. Lifecycle transitions and command given/ensures read the
|
|
35
38
|
# same way and are the natural next sentences — not built yet.
|
|
36
39
|
module Statements
|
|
@@ -40,12 +43,30 @@ module Hecks
|
|
|
40
43
|
|
|
41
44
|
module_function
|
|
42
45
|
|
|
46
|
+
# Projects every reachable fact in the chapter into its flat sentence list.
|
|
47
|
+
#
|
|
48
|
+
# @param bluebook [Bluebook::Chapter] the chapter being projected
|
|
49
|
+
# @param options [Hash{Symbol => Object}] ignored; present to satisfy the
|
|
50
|
+
# `Projector::Target` calling convention
|
|
51
|
+
# @return [Array<String>] one sentence per attribute relationship and invariant,
|
|
52
|
+
# in holder order
|
|
43
53
|
def call(bluebook:, options: {})
|
|
44
54
|
holders(bluebook).flat_map { |holder| statements_for(holder) }
|
|
45
55
|
end
|
|
46
56
|
|
|
57
|
+
# Every construct that can carry its own attributes and invariants — an
|
|
58
|
+
# aggregate and each of its entities.
|
|
59
|
+
#
|
|
60
|
+
# @param bluebook [Bluebook::Chapter] the chapter being walked
|
|
61
|
+
# @return [Array<Bluebook::Aggregate, Bluebook::Entity>] every aggregate followed
|
|
62
|
+
# by its own entities, in declaration order
|
|
47
63
|
def holders(bluebook) = bluebook.aggregates.flat_map { |aggregate| [aggregate, *aggregate.entities] }
|
|
48
64
|
|
|
65
|
+
# All of one holder's sentences: its attribute relationships, then its invariants.
|
|
66
|
+
#
|
|
67
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the construct being
|
|
68
|
+
# projected
|
|
69
|
+
# @return [Array<String>] the holder's sentences
|
|
49
70
|
def statements_for(holder)
|
|
50
71
|
attribute_statements(holder) + invariant_statements(holder)
|
|
51
72
|
end
|
|
@@ -60,10 +81,24 @@ module Hecks
|
|
|
60
81
|
# name, because the field's own name is what the domain author
|
|
61
82
|
# actually chose to call the collection — "toppings", not
|
|
62
83
|
# "Topping".
|
|
84
|
+
# Projects every one of a holder's attributes that says something in plain
|
|
85
|
+
# English — a list, or a named relationship.
|
|
86
|
+
#
|
|
87
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the construct whose
|
|
88
|
+
# attributes are being read
|
|
89
|
+
# @return [Array<String>] one sentence per list or relationship attribute; a
|
|
90
|
+
# plain scalar attribute contributes none
|
|
63
91
|
def attribute_statements(holder)
|
|
64
92
|
holder.attributes.filter_map { |attribute| attribute_statement(holder, attribute) }
|
|
65
93
|
end
|
|
66
94
|
|
|
95
|
+
# Projects one attribute's relationship, if it has one worth stating.
|
|
96
|
+
#
|
|
97
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the attribute's owning
|
|
98
|
+
# construct, named as the sentence's subject
|
|
99
|
+
# @param attribute [Bluebook::Attribute] the attribute being projected
|
|
100
|
+
# @return [String, nil] the sentence, or `nil` if `attribute` is neither a list
|
|
101
|
+
# nor a `has_one`/`belongs_to`/`reference_to` relationship
|
|
67
102
|
def attribute_statement(holder, attribute)
|
|
68
103
|
subject = "#{article(holder.hecks_name)} #{holder.hecks_name}"
|
|
69
104
|
return "#{subject} has many #{attribute.name}." if attribute.list?
|
|
@@ -85,12 +120,20 @@ module Hecks
|
|
|
85
120
|
# "university") because a construct name is always spelled as a
|
|
86
121
|
# plain word, never an abbreviation read letter-by-letter or a
|
|
87
122
|
# word starting with a consonant letter but a vowel sound.
|
|
123
|
+
#
|
|
124
|
+
# @param word [String] a construct name to prefix
|
|
125
|
+
# @return [String] `"An"` if `word` starts with a vowel letter, `"A"` otherwise
|
|
88
126
|
def article(word) = word.to_s.match?(/\A[AEIOUaeiou]/) ? "An" : "A"
|
|
89
127
|
|
|
90
128
|
# Invariants live in two places — directly on the holder (an
|
|
91
129
|
# aggregate-level rule, checked after every command) and on every
|
|
92
130
|
# value object nested inside it — `DocsProjector#rules_of`'s own
|
|
93
131
|
# `value_object_for` lookup is the precedent for walking both.
|
|
132
|
+
#
|
|
133
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the construct whose
|
|
134
|
+
# invariants, and whose value objects' invariants, are being read
|
|
135
|
+
# @return [Array<String>] one sentence per invariant, the holder's own first,
|
|
136
|
+
# then each nested value object's
|
|
94
137
|
def invariant_statements(holder)
|
|
95
138
|
own = Array(holder.respond_to?(:invariants) ? holder.invariants : [])
|
|
96
139
|
nested = Array(holder.respond_to?(:value_objects) ? holder.value_objects : []).flat_map(&:invariants)
|
|
@@ -100,6 +143,10 @@ module Hecks
|
|
|
100
143
|
# The domain author's own words, capitalized and punctuated —
|
|
101
144
|
# nothing else. `invariant("a pizza is named")` already reads as
|
|
102
145
|
# a sentence; this is the entire transformation.
|
|
146
|
+
#
|
|
147
|
+
# @param invariant [Bluebook::Invariant] the invariant being projected
|
|
148
|
+
# @return [String] the invariant's `description`, capitalized, ending with `.`,
|
|
149
|
+
# `!` or `?`
|
|
103
150
|
def invariant_statement(invariant)
|
|
104
151
|
text = invariant.description.to_s.strip
|
|
105
152
|
text = "#{text[0].upcase}#{text[1..]}" if text[0]
|
|
@@ -40,6 +40,11 @@ module Hecks
|
|
|
40
40
|
|
|
41
41
|
# The projector protocol. `options` is unused: a vocabulary table
|
|
42
42
|
# has nothing to vary.
|
|
43
|
+
#
|
|
44
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter declaring the
|
|
45
|
+
# Vocabulary aggregate to project
|
|
46
|
+
# @param options [Hash] unused; accepted to satisfy the registry's call shape
|
|
47
|
+
# @return [String] the rendered `lib/hecks/vocabulary.rb` source
|
|
43
48
|
def call(bluebook:, options: {}) = render(bluebook)
|
|
44
49
|
|
|
45
50
|
# Full rows, not just the first field of each.
|
|
@@ -50,12 +55,24 @@ module Hecks
|
|
|
50
55
|
# Taking the first field of those produced a list of thirty-nine
|
|
51
56
|
# duplicated error names — well-formed and meaningless. So rows are
|
|
52
57
|
# carried whole, and the terms are derived from them.
|
|
58
|
+
#
|
|
59
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter declaring the
|
|
60
|
+
# Vocabulary aggregate to read
|
|
61
|
+
# @return [Hash{String => Array<Hash{String => String}>}] each closed set's name,
|
|
62
|
+
# mapped to its member rows with every field stringified
|
|
53
63
|
def tables(bluebook)
|
|
54
64
|
bluebook.aggregate("Vocabulary").value_objects.to_h do |vo|
|
|
55
65
|
[vo.hecks_name, vo.members.map { |row| row.to_h.transform_keys(&:to_s).transform_values(&:to_s) }]
|
|
56
66
|
end
|
|
57
67
|
end
|
|
58
68
|
|
|
69
|
+
# Renders `lib/hecks/vocabulary.rb`'s full source: the frozen `TABLES`
|
|
70
|
+
# and `TERMS` constants and the `fetch`/`rows`/`symbols`/`names` reader
|
|
71
|
+
# methods, built from `bluebook`'s declared closed sets.
|
|
72
|
+
#
|
|
73
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter declaring the
|
|
74
|
+
# Vocabulary aggregate to render
|
|
75
|
+
# @return [String] the generated Ruby source, ready to write to disk
|
|
59
76
|
def render(bluebook)
|
|
60
77
|
rows = tables(bluebook).sort_by(&:first).map do |name, members|
|
|
61
78
|
" #{name.inspect} => [\n#{members.map { |row| " #{row.inspect}.freeze" }.join(",\n")}\n ].freeze"
|
data/lib/hecks/projections.rb
CHANGED
|
@@ -33,3 +33,6 @@ require_relative "projections/model"
|
|
|
33
33
|
require_relative "projections/diagrams"
|
|
34
34
|
require_relative "projections/statements"
|
|
35
35
|
require_relative "projections/glossary"
|
|
36
|
+
require_relative "projections/deploy/shared"
|
|
37
|
+
require_relative "projections/deploy/lambda"
|
|
38
|
+
require_relative "projections/deploy/fargate"
|
|
@@ -10,10 +10,14 @@ module Hecks
|
|
|
10
10
|
# then drifts — and the first thing to drift is the help text, which is the
|
|
11
11
|
# only part anybody reads.
|
|
12
12
|
#
|
|
13
|
-
# What is projected
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
13
|
+
# ## What is projected
|
|
14
|
+
#
|
|
15
|
+
# This answers the surface only — the verb tree, the argument spec, the
|
|
16
|
+
# usage text — and nothing executes here. One small generic runner
|
|
17
|
+
# (`bin/run`) boots a domain, asks for this, parses against it and
|
|
18
|
+
# dispatches.
|
|
19
|
+
#
|
|
20
|
+
# ## Why project rather than generate
|
|
17
21
|
#
|
|
18
22
|
# The alternative was generating an executable per domain, which is what
|
|
19
23
|
# `bin/project_rust` does for a whole runtime and would be the more
|
|
@@ -23,12 +27,13 @@ module Hecks
|
|
|
23
27
|
# reads. Projecting the surface keeps one dispatcher and a help text that
|
|
24
28
|
# cannot be stale, because it is computed at the moment it is printed.
|
|
25
29
|
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
30
|
+
# ## The typing is the point
|
|
31
|
+
#
|
|
32
|
+
# A CLI hands everything over as a String. `sequence.value=99` has to
|
|
33
|
+
# become the Integer 99 or the runtime refuses it, and the only honest
|
|
34
|
+
# place to learn that is the value object's own declared field type. A CLI
|
|
35
|
+
# that guessed — "it looks like a number" — would send 99 for a version
|
|
36
|
+
# string of "99" and be wrong in a way nobody could see.
|
|
32
37
|
module CliProjector
|
|
33
38
|
module_function
|
|
34
39
|
|
|
@@ -45,6 +50,17 @@ module Hecks
|
|
|
45
50
|
# shell-safe where a `?` suffix would be eaten by globbing, and it makes
|
|
46
51
|
# the collision impossible rather than detected. It also reads as what it
|
|
47
52
|
# is: everything under `ask` changes nothing.
|
|
53
|
+
#
|
|
54
|
+
# @param bluebook [Bluebook::Chapter] the booted domain to project
|
|
55
|
+
# @param options [Hash{Symbol => Object}] `:program` (String, defaults to
|
|
56
|
+
# `"bin/run"`) is echoed into the usage text; `:verb` (String) and `:ask`
|
|
57
|
+
# (Boolean) select one verb's `--help` text instead of the full usage
|
|
58
|
+
# @return [Hash{Symbol => Object}] `:verbs` and `:questions` map each projected
|
|
59
|
+
# name to its spec hash; `:names` maps `:command`/`:question` to an alias table
|
|
60
|
+
# (short and full spelling both keying the full name); `:usage` is the
|
|
61
|
+
# pre-rendered help text
|
|
62
|
+
# @raise [Bluebook::DSL::Malformed] if two verbs project to the same command-line
|
|
63
|
+
# name
|
|
48
64
|
def call(bluebook:, options: {})
|
|
49
65
|
verbs = {}
|
|
50
66
|
questions = {}
|
|
@@ -114,6 +130,10 @@ module Hecks
|
|
|
114
130
|
# aggregates declaring `Close` keep `customer.close` and `account.close`,
|
|
115
131
|
# which is the honest answer — a CLI that picked one would be choosing
|
|
116
132
|
# for the caller.
|
|
133
|
+
#
|
|
134
|
+
# @param specs [Hash{String => Hash}] the verb or question map being projected,
|
|
135
|
+
# mutated in place: each spec hash gains a `:short` key
|
|
136
|
+
# @return [void]
|
|
117
137
|
def shorten(specs)
|
|
118
138
|
tails = specs.keys.group_by { |name| name.split(".").last }
|
|
119
139
|
specs.each do |name, spec|
|
|
@@ -122,6 +142,12 @@ module Hecks
|
|
|
122
142
|
end
|
|
123
143
|
end
|
|
124
144
|
|
|
145
|
+
# Builds the alias table `CliRunner` resolves a typed name against.
|
|
146
|
+
#
|
|
147
|
+
# @param specs [Hash{String => Hash}] a verb or question map whose specs already
|
|
148
|
+
# carry `:short` (set by `shorten`)
|
|
149
|
+
# @return [Hash{String => String}] every accepted spelling — the full name and
|
|
150
|
+
# its `:short` form — mapped to the full name
|
|
125
151
|
def aliases(specs)
|
|
126
152
|
specs.each_with_object({}) do |(name, spec), map|
|
|
127
153
|
map[name] = name
|
|
@@ -134,6 +160,13 @@ module Hecks
|
|
|
134
160
|
# subcommands. Refused here rather than silently resolving to whichever
|
|
135
161
|
# was walked first, which is how `Ticket.Filed` (a command) and
|
|
136
162
|
# `Ticket.Filed` (a query) sat undetected until something flattened them.
|
|
163
|
+
#
|
|
164
|
+
# @param verbs [Hash{String => Hash}] the verb or question map being built,
|
|
165
|
+
# mutated in place
|
|
166
|
+
# @param name [String] the command-line name this spec claims
|
|
167
|
+
# @param spec [Hash{Symbol => Object}] the spec hash to store under `name`
|
|
168
|
+
# @return [void]
|
|
169
|
+
# @raise [Bluebook::DSL::Malformed] if `name` is already claimed by another verb
|
|
137
170
|
def claim(verbs, name, spec)
|
|
138
171
|
if verbs.key?(name)
|
|
139
172
|
raise Bluebook::DSL::Malformed,
|
|
@@ -144,6 +177,15 @@ module Hecks
|
|
|
144
177
|
verbs[name] = spec
|
|
145
178
|
end
|
|
146
179
|
|
|
180
|
+
# Builds the dotted command-line name for one verb: `aggregate[.entity].verb`,
|
|
181
|
+
# snake-cased.
|
|
182
|
+
#
|
|
183
|
+
# @param aggregate [Bluebook::Aggregate] the verb's owning aggregate
|
|
184
|
+
# @param verb [Bluebook::Command, Bluebook::Query, Bluebook::PortOperation]
|
|
185
|
+
# the command, query or port operation being named
|
|
186
|
+
# @param entity [Bluebook::Entity, nil] the entity the verb is declared on, or `nil`
|
|
187
|
+
# for an aggregate-level verb
|
|
188
|
+
# @return [String] the dotted, snake-cased command-line name
|
|
147
189
|
def name_for(aggregate, verb, entity = nil)
|
|
148
190
|
parts = [Naming.snake(aggregate.hecks_name)]
|
|
149
191
|
parts << Naming.snake(entity.hecks_name) if entity
|
|
@@ -151,6 +193,16 @@ module Hecks
|
|
|
151
193
|
parts.join(".")
|
|
152
194
|
end
|
|
153
195
|
|
|
196
|
+
# Builds the verb's fully-qualified language name, `Bluebook::Aggregate[.Entity].Verb`,
|
|
197
|
+
# for display in help text.
|
|
198
|
+
#
|
|
199
|
+
# @param bluebook [Bluebook::Chapter] the verb's owning chapter
|
|
200
|
+
# @param aggregate [Bluebook::Aggregate] the verb's owning aggregate
|
|
201
|
+
# @param verb [Bluebook::Command, Bluebook::Query, Bluebook::PortOperation]
|
|
202
|
+
# the command, query or port operation being named
|
|
203
|
+
# @param entity [Bluebook::Entity, nil] the entity the verb is declared on, or `nil`
|
|
204
|
+
# for an aggregate-level verb
|
|
205
|
+
# @return [String] the fully-qualified name, as the language spells it
|
|
154
206
|
def fqn(bluebook, aggregate, verb, entity = nil)
|
|
155
207
|
[bluebook.name, "::", aggregate.hecks_name, ".",
|
|
156
208
|
entity ? "#{entity.hecks_name}." : "", verb.hecks_name].join
|
|
@@ -166,6 +218,14 @@ module Hecks
|
|
|
166
218
|
#
|
|
167
219
|
# `nil` — a creating command — takes none: there is no existing record
|
|
168
220
|
# yet for `to=` to name.
|
|
221
|
+
#
|
|
222
|
+
# @param receiver [Symbol, nil] `:entity`, `:aggregate`, or `nil` for a creating
|
|
223
|
+
# command that names no existing record
|
|
224
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate the record belongs to
|
|
225
|
+
# @param entity [Bluebook::Entity, nil] the entity to name, required when
|
|
226
|
+
# `receiver` is `:entity`
|
|
227
|
+
# @return [Array<Hash{Symbol => Object}>] the `to`/`to.aggregate`+`to.entity` option
|
|
228
|
+
# specs this receiver needs, or `[]` for `nil`
|
|
169
229
|
def receiver_options(receiver, aggregate, entity)
|
|
170
230
|
case receiver
|
|
171
231
|
when :entity
|
|
@@ -185,6 +245,18 @@ module Hecks
|
|
|
185
245
|
|
|
186
246
|
# ── one verb ──────────────────────────────────────────────────────
|
|
187
247
|
|
|
248
|
+
# Projects one command into the spec `verb_help` and `CliRunner` both read.
|
|
249
|
+
#
|
|
250
|
+
# @param bluebook [Bluebook::Chapter] the command's owning chapter
|
|
251
|
+
# @param aggregate [Bluebook::Aggregate] the command's owning aggregate
|
|
252
|
+
# @param entity [Bluebook::Entity, nil] the entity the command is declared on, or
|
|
253
|
+
# `nil` for an aggregate-level command
|
|
254
|
+
# @param command [Bluebook::Command] the command being projected
|
|
255
|
+
# @return [Hash{Symbol => Object}] `:verb` (fully-qualified name), `:kind` (`:command`),
|
|
256
|
+
# `:summary`, `:role` (String, nil if the command declares none), `:role_gated`
|
|
257
|
+
# (Boolean), `:creates` (Boolean), `:receiver` (`:entity`, `:aggregate` or `nil`),
|
|
258
|
+
# `:legacy_receiver` (`:id` or `nil`), `:legacy_arguments`, `:refusals`
|
|
259
|
+
# (see `refusals`) and `:arguments` (see `options_for`)
|
|
188
260
|
def command_spec(bluebook, aggregate, entity, command)
|
|
189
261
|
holder = entity || aggregate
|
|
190
262
|
arguments = command.attributes.flat_map { |a| options_for(a, holder, aggregate) }
|
|
@@ -229,6 +301,16 @@ module Hecks
|
|
|
229
301
|
# suite is red, and refuse only when rspec could not run. Somebody
|
|
230
302
|
# reading `--help` should not have to open the hecksagon to find that
|
|
231
303
|
# out.
|
|
304
|
+
#
|
|
305
|
+
# @param bluebook [Bluebook::Chapter] the port's owning chapter
|
|
306
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate the port is declared on
|
|
307
|
+
# @param port [Bluebook::DomainPort] the port the operation is declared on
|
|
308
|
+
# @param operation [Bluebook::PortOperation] the operation being projected
|
|
309
|
+
# @return [Hash{Symbol => Object}] `:verb` (the `Aggregate.port.operation` name),
|
|
310
|
+
# `:kind` (`:command`), `:creates` (`false`), `:receiver` (`:aggregate`),
|
|
311
|
+
# `:refusals` (`[]`), `:role` (String, who calls whom through the port),
|
|
312
|
+
# `:role_gated` (`false`), `:summary` (see `port_summary`) and `:arguments`
|
|
313
|
+
# (see `options_for`)
|
|
232
314
|
def port_spec(bluebook, aggregate, port, operation)
|
|
233
315
|
arguments = receiver_options(:aggregate, aggregate, nil) +
|
|
234
316
|
operation.attributes.flat_map { |a| options_for(a, aggregate, aggregate) }
|
|
@@ -260,6 +342,12 @@ module Hecks
|
|
|
260
342
|
summary: port_summary(port, operation), arguments: arguments }
|
|
261
343
|
end
|
|
262
344
|
|
|
345
|
+
# Names both endings a port operation can have: what an inbound `tells` records,
|
|
346
|
+
# or what an outbound `asks` came back with and what it said instead.
|
|
347
|
+
#
|
|
348
|
+
# @param port [Bluebook::DomainPort] the operation's owning port
|
|
349
|
+
# @param operation [Bluebook::PortOperation] the operation being summarized
|
|
350
|
+
# @return [String] one line, worded for `--help`
|
|
263
351
|
def port_summary(port, operation)
|
|
264
352
|
return "#{port.name} reports it; emits #{operation.emits.join(', ')}" unless operation.outbound?
|
|
265
353
|
|
|
@@ -268,6 +356,12 @@ module Hecks
|
|
|
268
356
|
|
|
269
357
|
# A rootless report takes nothing; a rooted one takes the id of the
|
|
270
358
|
# record it is a view of, under the name the model gave that reference.
|
|
359
|
+
#
|
|
360
|
+
# @param bluebook [Bluebook::Chapter] the report's owning chapter
|
|
361
|
+
# @param model [Bluebook::ReadModel] the report being projected
|
|
362
|
+
# @return [Hash{Symbol => Object}] `:verb` (`Chapter.ReportName`), `:kind`
|
|
363
|
+
# (`:query`), `:summary` and `:arguments` (`[]`, or one required id option
|
|
364
|
+
# named after the model's `reference_name` when it declares a `reference_target`)
|
|
271
365
|
def report_spec(bluebook, model)
|
|
272
366
|
arguments =
|
|
273
367
|
if model.reference_target
|
|
@@ -281,6 +375,15 @@ module Hecks
|
|
|
281
375
|
summary: model.description, arguments: arguments }
|
|
282
376
|
end
|
|
283
377
|
|
|
378
|
+
# Projects one query into the spec `verb_help` and `CliRunner` both read.
|
|
379
|
+
#
|
|
380
|
+
# @param bluebook [Bluebook::Chapter] the query's owning chapter
|
|
381
|
+
# @param aggregate [Bluebook::Aggregate] the query's owning aggregate
|
|
382
|
+
# @param entity [Bluebook::Entity, nil] the entity the query is declared on, or
|
|
383
|
+
# `nil` for an aggregate-level query
|
|
384
|
+
# @param query [Bluebook::Query] the query being projected
|
|
385
|
+
# @return [Hash{Symbol => Object}] `:verb` (fully-qualified name), `:kind`
|
|
386
|
+
# (`:query`), `:summary` and `:arguments` (see `options_for`)
|
|
284
387
|
def query_spec(bluebook, aggregate, entity, query)
|
|
285
388
|
arguments = Array(query.to_h[:attributes]).flat_map do |declared|
|
|
286
389
|
attribute = query.attributes.find { |a| a.name.to_s == declared[:name].to_s }
|
|
@@ -308,6 +411,18 @@ module Hecks
|
|
|
308
411
|
# one-level CLI is not merely inconvenient, it is a machine for writing
|
|
309
412
|
# malformed records into a real store, which is what it did on its first
|
|
310
413
|
# run against the pizzas database.
|
|
414
|
+
#
|
|
415
|
+
# @param attribute [Bluebook::Attribute] the field being projected
|
|
416
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity, Class] the construct
|
|
417
|
+
# `attribute` is declared on; a `Bluebook::ValueObject` subclass on a recursive call
|
|
418
|
+
# @param aggregate [Bluebook::Aggregate] the top-level aggregate, kept across recursion
|
|
419
|
+
# so a nested value object can still be found
|
|
420
|
+
# @param prefix [String, nil] the dotted path built so far, `nil` at the top level
|
|
421
|
+
# @param optional [Boolean, nil] whether an enclosing field already makes this one
|
|
422
|
+
# optional; `nil` defers to `attribute.optional?`
|
|
423
|
+
# @return [Array<Hash{Symbol => Object}>] one option spec per leaf field, each with
|
|
424
|
+
# `:path`, `:type`, `:required`, and optionally `:note`, `:enum`, `:pattern`,
|
|
425
|
+
# `:default`, `:list`
|
|
311
426
|
def options_for(attribute, holder, aggregate, prefix = nil, optional = nil)
|
|
312
427
|
path = [prefix, attribute.name].compact.join(".")
|
|
313
428
|
optional ||= attribute.optional?
|
|
@@ -342,11 +457,27 @@ module Hecks
|
|
|
342
457
|
fields.map { |option| option.merge(list: true, note: [option[:note], "repeatable"].compact.join("; ")) }
|
|
343
458
|
end
|
|
344
459
|
|
|
460
|
+
# Projects a `reference_to` attribute as the id of the record it points at.
|
|
461
|
+
#
|
|
462
|
+
# @param attribute [Bluebook::Attribute] a `reference?` attribute
|
|
463
|
+
# @return [Hash{Symbol => Object}] `:path`, `:type` (`"String"`), `:required` and
|
|
464
|
+
# `:note`
|
|
345
465
|
def reference_option(attribute)
|
|
346
466
|
{ path: attribute.name.to_s, type: "String", required: !attribute.optional?,
|
|
347
467
|
note: "id of a #{attribute.type.target_name}" }
|
|
348
468
|
end
|
|
349
469
|
|
|
470
|
+
# Projects one leaf field — a plain attribute, or one field of a flattened
|
|
471
|
+
# value object.
|
|
472
|
+
#
|
|
473
|
+
# @param path [String] the dotted option path this field is reached at
|
|
474
|
+
# @param field [Bluebook::Attribute] the field being projected
|
|
475
|
+
# @param optional [Boolean] whether this option may be omitted
|
|
476
|
+
# @param enum [Array<Object>] the closed set of values this field admits, `[]` for
|
|
477
|
+
# an open field
|
|
478
|
+
# @return [Hash{Symbol => Object}] `:path`, `:type`, `:required`, plus `:enum` when
|
|
479
|
+
# `enum` is non-empty, `:pattern` when `field` declares one, and `:default` when
|
|
480
|
+
# `field` declares a non-nil one
|
|
350
481
|
def scalar_option(path, field, optional, enum: [])
|
|
351
482
|
option = { path: path, type: field.type.to_s, required: !optional }
|
|
352
483
|
option[:enum] = enum unless enum.empty?
|
|
@@ -355,12 +486,30 @@ module Hecks
|
|
|
355
486
|
option
|
|
356
487
|
end
|
|
357
488
|
|
|
489
|
+
# The values a `one_of` value object's field is closed to, for the option's
|
|
490
|
+
# `:enum`.
|
|
491
|
+
#
|
|
492
|
+
# @param value_object [Class] a `Bluebook::ValueObject` subclass
|
|
493
|
+
# @param field [Bluebook::Attribute] the value object's field being projected
|
|
494
|
+
# @return [Array<Object>] the distinct values every declared member gives this
|
|
495
|
+
# field, or `[]` when the value object declares no closed set
|
|
358
496
|
def closed_members(value_object, field)
|
|
359
497
|
return [] unless value_object.closed_set?
|
|
360
498
|
|
|
361
499
|
value_object.members.filter_map { |member| member[field.name] }.uniq
|
|
362
500
|
end
|
|
363
501
|
|
|
502
|
+
# Finds the value object type an attribute declares, if any — the fork between
|
|
503
|
+
# `scalar_option` (one option) and recursing into `options_for` (one option per
|
|
504
|
+
# field).
|
|
505
|
+
#
|
|
506
|
+
# @param attribute [Bluebook::Attribute] the field whose type is being resolved
|
|
507
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity, Class, nil] the construct
|
|
508
|
+
# `attribute` is declared on, searched first
|
|
509
|
+
# @param aggregate [Bluebook::Aggregate, nil] the top-level aggregate, searched when
|
|
510
|
+
# `holder` does not declare the value object itself
|
|
511
|
+
# @return [Class, nil] the matching `Bluebook::ValueObject` subclass, or `nil` if
|
|
512
|
+
# `attribute`'s type names no value object in scope
|
|
364
513
|
def value_object_for(attribute, holder, aggregate)
|
|
365
514
|
[holder, aggregate].compact.each do |scope|
|
|
366
515
|
next unless scope.respond_to?(:value_objects)
|
|
@@ -373,6 +522,13 @@ module Hecks
|
|
|
373
522
|
|
|
374
523
|
# Every way this verb can say no, in the chapter's own words — printed
|
|
375
524
|
# by `--help` before the caller spends a dispatch finding out.
|
|
525
|
+
#
|
|
526
|
+
# @param command [Bluebook::Command] the command whose refusals are being projected
|
|
527
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the aggregate or entity the
|
|
528
|
+
# command's lifecycle guard, if any, is checked against
|
|
529
|
+
# @return [Array<String>] one sentence per way the command can refuse: a lifecycle
|
|
530
|
+
# state mismatch, a missing referenced record per `reference_to` attribute, and
|
|
531
|
+
# each declared `given`'s description, in that order
|
|
376
532
|
def refusals(command, holder)
|
|
377
533
|
out = []
|
|
378
534
|
lifecycle = holder.lifecycle
|
|
@@ -386,6 +542,16 @@ module Hecks
|
|
|
386
542
|
|
|
387
543
|
# ── the help ──────────────────────────────────────────────────────
|
|
388
544
|
|
|
545
|
+
# Renders the full verb/question table, or one verb's `--help` text when
|
|
546
|
+
# `options[:verb]` names one.
|
|
547
|
+
#
|
|
548
|
+
# @param bluebook [Bluebook::Chapter] the projected chapter, for its name and vision
|
|
549
|
+
# line
|
|
550
|
+
# @param verbs [Hash{String => Hash}] the projected, `shorten`ed command map
|
|
551
|
+
# @param questions [Hash{String => Hash}] the projected, `shorten`ed query map
|
|
552
|
+
# @param options [Hash{Symbol => Object}] `:program` (String), `:verb` (String, nil)
|
|
553
|
+
# and `:ask` (Boolean) — see `call`
|
|
554
|
+
# @return [String] the rendered help text
|
|
389
555
|
def usage(bluebook, verbs, questions, options)
|
|
390
556
|
program = options[:program] || "bin/run"
|
|
391
557
|
only = options[:verb]
|
|
@@ -416,6 +582,12 @@ module Hecks
|
|
|
416
582
|
out.join("\n")
|
|
417
583
|
end
|
|
418
584
|
|
|
585
|
+
# Picks one verb whose full spelling differs from its short one, to show the
|
|
586
|
+
# caller both forms exist.
|
|
587
|
+
#
|
|
588
|
+
# @param verbs [Hash{String => Hash}] the projected, `shorten`ed command map
|
|
589
|
+
# @return [String] `"<short> is also <full>"`, or `""` if every verb's short form
|
|
590
|
+
# is its full name (a one-aggregate domain, where no name ever collides)
|
|
419
591
|
def example_qualified(verbs)
|
|
420
592
|
name, spec = verbs.find { |key, value| key != value[:short] } || verbs.first
|
|
421
593
|
name ? "#{spec[:short]} is also #{name}" : ""
|
|
@@ -424,6 +596,9 @@ module Hecks
|
|
|
424
596
|
# A query's `description` is written as a paragraph — it argues for why
|
|
425
597
|
# the list is worth reading. A verb table wants the first sentence of
|
|
426
598
|
# that argument; `--help` still prints the whole thing.
|
|
599
|
+
#
|
|
600
|
+
# @param text [String, nil] the full description, or `nil`
|
|
601
|
+
# @return [String] the text up to and including its first `.`, or `""` for `nil`
|
|
427
602
|
def first_sentence(text)
|
|
428
603
|
text.to_s.split(/(?<=\.)\s/).first.to_s
|
|
429
604
|
end
|
|
@@ -434,6 +609,13 @@ module Hecks
|
|
|
434
609
|
# below), so each is its own method returning the lines it
|
|
435
610
|
# contributes — `[]` when it contributes none — concatenated in the
|
|
436
611
|
# same order the original inline version built them in.
|
|
612
|
+
#
|
|
613
|
+
# @param program [String] how the caller was invoked, echoed in the invocation line
|
|
614
|
+
# @param name [String] the verb's short display name
|
|
615
|
+
# @param spec [Hash{Symbol => Object}] the verb or question's projected spec (see
|
|
616
|
+
# `command_spec`, `port_spec`, `report_spec` or `query_spec`)
|
|
617
|
+
# @param ask [Boolean] whether this is a question, reached through `ask`
|
|
618
|
+
# @return [String] the verb's full `--help` text
|
|
437
619
|
def verb_help(program, name, spec, ask: false)
|
|
438
620
|
out = verb_help_meta_lines(name, spec)
|
|
439
621
|
out.concat(verb_help_invocation_lines(program, name, spec, ask))
|
|
@@ -442,6 +624,12 @@ module Hecks
|
|
|
442
624
|
out.join("\n")
|
|
443
625
|
end
|
|
444
626
|
|
|
627
|
+
# Renders the name/summary heading, plus the dispatched or read verb and issuing
|
|
628
|
+
# role when the spec declares them.
|
|
629
|
+
#
|
|
630
|
+
# @param name [String] the verb's short display name
|
|
631
|
+
# @param spec [Hash{Symbol => Object}] the verb or question's projected spec
|
|
632
|
+
# @return [Array<String>] the meta block's lines
|
|
445
633
|
def verb_help_meta_lines(name, spec)
|
|
446
634
|
out = ["#{name} — #{spec[:summary]}", ""]
|
|
447
635
|
out << "dispatches #{spec[:verb]}" if spec[:kind] == :command
|
|
@@ -450,11 +638,26 @@ module Hecks
|
|
|
450
638
|
out
|
|
451
639
|
end
|
|
452
640
|
|
|
641
|
+
# Renders the one-line example invocation, with every argument's path as a
|
|
642
|
+
# placeholder.
|
|
643
|
+
#
|
|
644
|
+
# @param program [String] how the caller was invoked
|
|
645
|
+
# @param name [String] the verb's short display name
|
|
646
|
+
# @param spec [Hash{Symbol => Object}] the verb or question's projected spec, read
|
|
647
|
+
# for `:arguments`
|
|
648
|
+
# @param ask [Boolean] whether to show the `ask` form of the invocation
|
|
649
|
+
# @return [Array<String>] the invocation block's lines
|
|
453
650
|
def verb_help_invocation_lines(program, name, spec, ask)
|
|
454
651
|
invocation = ask ? "#{program} ask #{name}" : "#{program} #{name}"
|
|
455
652
|
["", " #{invocation}#{spec[:arguments].map { |a| " #{a[:path]}=…" }.join}", ""]
|
|
456
653
|
end
|
|
457
654
|
|
|
655
|
+
# Renders one line per declared argument: its type, admitted values, pattern,
|
|
656
|
+
# default and whether it is optional.
|
|
657
|
+
#
|
|
658
|
+
# @param spec [Hash{Symbol => Object}] the verb or question's projected spec, read
|
|
659
|
+
# for `:arguments`
|
|
660
|
+
# @return [Array<String>] the argument block's lines, `[]` if the verb takes none
|
|
458
661
|
def verb_help_argument_lines(spec)
|
|
459
662
|
return [] if spec[:arguments].empty?
|
|
460
663
|
|
|
@@ -472,6 +675,11 @@ module Hecks
|
|
|
472
675
|
lines << ""
|
|
473
676
|
end
|
|
474
677
|
|
|
678
|
+
# Renders the "refused when:" block, one line per way the verb can say no.
|
|
679
|
+
#
|
|
680
|
+
# @param spec [Hash{Symbol => Object}] the verb or question's projected spec, read
|
|
681
|
+
# for `:refusals`
|
|
682
|
+
# @return [Array<String>] the refusal block's lines, `[]` if the verb declares none
|
|
475
683
|
def verb_help_refusal_lines(spec)
|
|
476
684
|
return [] if Array(spec[:refusals]).empty?
|
|
477
685
|
|