hecks 1.4.0 → 1.5.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/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.hecksagon +19 -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/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 +58 -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/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/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 +29 -5
- data/lib/hecks/projector.rb +74 -6
- 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 +11 -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
|
@@ -30,6 +30,10 @@ module Hecks
|
|
|
30
30
|
|
|
31
31
|
module_function
|
|
32
32
|
|
|
33
|
+
# Renders `glossary.md`'s Markdown source as a self-contained HTML page.
|
|
34
|
+
#
|
|
35
|
+
# @param markdown [String] `glossary.md`'s own rendered Markdown source
|
|
36
|
+
# @return [String] the complete `html/index.html` source
|
|
33
37
|
def render(markdown)
|
|
34
38
|
blocks = parse(markdown)
|
|
35
39
|
slugs = heading_slugs(blocks)
|
|
@@ -40,6 +44,12 @@ module Hecks
|
|
|
40
44
|
|
|
41
45
|
# ── reading the Markdown ─────────────────────────────────────────
|
|
42
46
|
|
|
47
|
+
# Parses the subset of Markdown the glossary projector emits into
|
|
48
|
+
# structured blocks.
|
|
49
|
+
#
|
|
50
|
+
# @param markdown [String] the Markdown source to parse
|
|
51
|
+
# @return [Array<Block>] one block per heading, quote, list, mermaid
|
|
52
|
+
# fence, or paragraph, in document order
|
|
43
53
|
def parse(markdown)
|
|
44
54
|
lines = markdown.lines.map(&:chomp)
|
|
45
55
|
blocks = []
|
|
@@ -70,6 +80,11 @@ module Hecks
|
|
|
70
80
|
|
|
71
81
|
# The mermaid source between the opening fence at `index` and its
|
|
72
82
|
# closing one, and the index just past that.
|
|
83
|
+
#
|
|
84
|
+
# @param lines [Array<String>] the document's lines
|
|
85
|
+
# @param index [Integer] the opening ` ```mermaid ` fence's line index
|
|
86
|
+
# @return [Array(Block, Integer)] the parsed `:mermaid` block, and the line
|
|
87
|
+
# index just past its closing fence
|
|
73
88
|
def fence(lines, index)
|
|
74
89
|
close = ((index + 1)...lines.size).find { |at| lines[at] == "```" } || lines.size
|
|
75
90
|
[Block.new(type: :mermaid, text: lines[(index + 1)...close].join("\n")), close + 1]
|
|
@@ -77,6 +92,13 @@ module Hecks
|
|
|
77
92
|
|
|
78
93
|
# The consecutive lines from `index` that satisfy the block, and
|
|
79
94
|
# the index just past them.
|
|
95
|
+
#
|
|
96
|
+
# @param lines [Array<String>] the document's lines
|
|
97
|
+
# @param index [Integer] the line index to start scanning from
|
|
98
|
+
# @yieldparam text [String] one candidate line
|
|
99
|
+
# @yieldreturn [Boolean] whether that line belongs to the run
|
|
100
|
+
# @return [Array(Array<String>, Integer)] the consecutive matching lines, and
|
|
101
|
+
# the line index just past them
|
|
80
102
|
def run(lines, index)
|
|
81
103
|
taken = []
|
|
82
104
|
while index < lines.size && yield(lines[index])
|
|
@@ -93,6 +115,10 @@ module Hecks
|
|
|
93
115
|
# members, so two "### Open" headings in different sections would
|
|
94
116
|
# otherwise be one key, and the first would answer with the
|
|
95
117
|
# second's "-1" slug, leaving `#open` with nothing to land on.
|
|
118
|
+
#
|
|
119
|
+
# @param blocks [Array<Block>] every parsed block, in document order
|
|
120
|
+
# @return [Hash{Block => String}] each heading block, mapped to its GitHub-style
|
|
121
|
+
# slug, keyed by object identity
|
|
96
122
|
def heading_slugs(blocks)
|
|
97
123
|
seen = Hash.new(0)
|
|
98
124
|
slugs = {}.compare_by_identity
|
|
@@ -106,6 +132,11 @@ module Hecks
|
|
|
106
132
|
|
|
107
133
|
# Everything before the first `##` is the front matter; each `##`
|
|
108
134
|
# opens a section that runs to the next.
|
|
135
|
+
#
|
|
136
|
+
# @param blocks [Array<Block>] every parsed block, in document order
|
|
137
|
+
# @return [Array<Hash{Symbol => Object}>] one Hash per section: `:heading`
|
|
138
|
+
# (the `Block`, or nil for the leading front matter) and `:blocks` (its
|
|
139
|
+
# own `Array<Block>`)
|
|
109
140
|
def split_sections(blocks)
|
|
110
141
|
sections = [{ heading: nil, blocks: [] }]
|
|
111
142
|
blocks.each do |block|
|
|
@@ -117,6 +148,13 @@ module Hecks
|
|
|
117
148
|
|
|
118
149
|
# ── writing the page ─────────────────────────────────────────────
|
|
119
150
|
|
|
151
|
+
# Renders the full HTML page shell around the navigation rail and content.
|
|
152
|
+
#
|
|
153
|
+
# @param title [String] the document's `#` heading text
|
|
154
|
+
# @param sections [Array<Hash{Symbol => Object}>] every `##` section, as
|
|
155
|
+
# `split_sections` builds (the front matter excluded by the caller)
|
|
156
|
+
# @param slugs [Hash{Block => String}] every heading block's own slug
|
|
157
|
+
# @return [String] the complete HTML page source
|
|
120
158
|
def page(title, sections, slugs)
|
|
121
159
|
front, *rest = sections
|
|
122
160
|
<<~HTML
|
|
@@ -148,6 +186,14 @@ module Hecks
|
|
|
148
186
|
HTML
|
|
149
187
|
end
|
|
150
188
|
|
|
189
|
+
# Renders the navigation rail: the domain name, a search box, and one
|
|
190
|
+
# link per section.
|
|
191
|
+
#
|
|
192
|
+
# @param title [String] the document's `#` heading text, `"Domain — Glossary"`
|
|
193
|
+
# @param sections [Array<Hash{Symbol => Object}>] every `##` section (the
|
|
194
|
+
# front matter excluded)
|
|
195
|
+
# @param slugs [Hash{Block => String}] every heading block's own slug
|
|
196
|
+
# @return [String] the rail's HTML source
|
|
151
197
|
def rail(title, sections, slugs)
|
|
152
198
|
domain = title.split(" — ").first
|
|
153
199
|
items = sections.map do |section|
|
|
@@ -167,6 +213,10 @@ module Hecks
|
|
|
167
213
|
HTML
|
|
168
214
|
end
|
|
169
215
|
|
|
216
|
+
# Renders the page's `<header>`: the title, vision, lede, and overview diagram.
|
|
217
|
+
#
|
|
218
|
+
# @param blocks [Array<Block>] the front matter's own blocks
|
|
219
|
+
# @return [String] the header's HTML source
|
|
170
220
|
def front_matter(blocks)
|
|
171
221
|
parts = ["<header>"]
|
|
172
222
|
blocks.each do |block|
|
|
@@ -185,6 +235,11 @@ module Hecks
|
|
|
185
235
|
# rules list), then its terms — each `###` opens an article that
|
|
186
236
|
# runs to the next. A bold-only paragraph is the caption of
|
|
187
237
|
# whatever figure or list follows it.
|
|
238
|
+
#
|
|
239
|
+
# @param section [Hash{Symbol => Object}] one `##` section, as `split_sections`
|
|
240
|
+
# builds
|
|
241
|
+
# @param slugs [Hash{Block => String}] every heading block's own slug
|
|
242
|
+
# @return [String] the section's `<section>` HTML source
|
|
188
243
|
def section_html(section, slugs)
|
|
189
244
|
heading = section[:heading]
|
|
190
245
|
state = { parts: [%(<section id="#{slugs[heading]}">), "<h2>#{inline(heading.text)}</h2>"],
|
|
@@ -195,6 +250,15 @@ module Hecks
|
|
|
195
250
|
state[:parts].join("\n")
|
|
196
251
|
end
|
|
197
252
|
|
|
253
|
+
# Renders one block into `state[:parts]`, mutating `state` as it goes.
|
|
254
|
+
#
|
|
255
|
+
# @param block [Block] the block to render
|
|
256
|
+
# @param state [Hash{Symbol => Object}] the section's own render state:
|
|
257
|
+
# `:parts` (the growing `Array<String>` of HTML), `:caption` (a pending
|
|
258
|
+
# bold-only paragraph's text, or nil), `:in_terms` (whether a `###` term
|
|
259
|
+
# article is currently open)
|
|
260
|
+
# @param slugs [Hash{Block => String}] every heading block's own slug
|
|
261
|
+
# @return [void]
|
|
198
262
|
def section_block(block, state, slugs)
|
|
199
263
|
parts = state[:parts]
|
|
200
264
|
case block.type
|
|
@@ -214,6 +278,13 @@ module Hecks
|
|
|
214
278
|
end
|
|
215
279
|
end
|
|
216
280
|
|
|
281
|
+
# Renders one paragraph, either capturing it as a pending caption (a
|
|
282
|
+
# bold-only line) or appending it to `state[:parts]`.
|
|
283
|
+
#
|
|
284
|
+
# @param text [String] the paragraph's own text
|
|
285
|
+
# @param state [Hash{Symbol => Object}] the section's own render state (see
|
|
286
|
+
# `section_block`); `:caption` and `:parts` may be mutated
|
|
287
|
+
# @return [void]
|
|
217
288
|
def paragraph(text, state)
|
|
218
289
|
if text.match?(/\A\*\*[^*]+\*\*\z/)
|
|
219
290
|
state[:caption] = text.delete("*")
|
|
@@ -223,6 +294,11 @@ module Hecks
|
|
|
223
294
|
end
|
|
224
295
|
end
|
|
225
296
|
|
|
297
|
+
# Renders a mermaid block as a `<figure>`, with an optional caption.
|
|
298
|
+
#
|
|
299
|
+
# @param block [Block] the `:mermaid` block to render
|
|
300
|
+
# @param caption [String, nil] the figure's caption text, or nil for none
|
|
301
|
+
# @return [String] the `<figure>`'s HTML source
|
|
226
302
|
def figure(block, caption)
|
|
227
303
|
parts = ["<figure>"]
|
|
228
304
|
parts << "<figcaption>#{escape(caption)}</figcaption>" if caption
|
|
@@ -232,10 +308,18 @@ module Hecks
|
|
|
232
308
|
|
|
233
309
|
# ── inline text ──────────────────────────────────────────────────
|
|
234
310
|
|
|
311
|
+
# Escapes text for HTML.
|
|
312
|
+
#
|
|
313
|
+
# @param text [Object, nil] the value to escape, rendered with `to_s`
|
|
314
|
+
# @return [String] `text`, HTML-escaped
|
|
235
315
|
def escape(text) = Forms::Escape.html(text)
|
|
236
316
|
|
|
237
317
|
# Escape first, so nothing in the prose ever becomes a tag; then
|
|
238
318
|
# the three inline forms the Markdown uses, on the escaped text.
|
|
319
|
+
#
|
|
320
|
+
# @param text [String] the inline text to render
|
|
321
|
+
# @return [String] `text`, HTML-escaped, with `[x](#y)` links, `**bold**` and
|
|
322
|
+
# `*italic*` spans converted to their HTML equivalents
|
|
239
323
|
def inline(text)
|
|
240
324
|
escape(text)
|
|
241
325
|
.gsub(/\[([^\]]+)\]\(#([^)]+)\)/) { %(<a href="##{Regexp.last_match(2)}">#{Regexp.last_match(1)}</a>) }
|
|
@@ -243,6 +327,10 @@ module Hecks
|
|
|
243
327
|
.gsub(/\*(.+?)\*/) { "<em>#{Regexp.last_match(1)}</em>" }
|
|
244
328
|
end
|
|
245
329
|
|
|
330
|
+
# Reads one static asset file from beside this file.
|
|
331
|
+
#
|
|
332
|
+
# @param name [String] the asset's filename, such as `"page.css"`
|
|
333
|
+
# @return [String] the asset file's contents, read from beside this file
|
|
246
334
|
def asset(name) = File.read(File.join(__dir__, name))
|
|
247
335
|
end
|
|
248
336
|
end
|
|
@@ -39,14 +39,27 @@ module Hecks
|
|
|
39
39
|
|
|
40
40
|
module_function
|
|
41
41
|
|
|
42
|
+
# Titles the document after its chapter.
|
|
43
|
+
#
|
|
44
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to title after
|
|
45
|
+
# @return [String] the document's `#` heading text
|
|
42
46
|
def title(bluebook) = "#{bluebook.name} — Glossary"
|
|
43
47
|
|
|
48
|
+
# Renders the full `glossary.md` document.
|
|
49
|
+
#
|
|
50
|
+
# @param document [Glossary::Document] the assembled document to render
|
|
51
|
+
# @return [String] the complete Markdown source, ending in a newline
|
|
44
52
|
def render(document)
|
|
45
53
|
parts = [header(document.bluebook)]
|
|
46
54
|
parts += document.sections.map { |section| section_text(section, document) }
|
|
47
55
|
"#{parts.join("\n\n")}\n"
|
|
48
56
|
end
|
|
49
57
|
|
|
58
|
+
# Renders the document's title, vision blockquote, lede, and overview map.
|
|
59
|
+
#
|
|
60
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to render a
|
|
61
|
+
# header for
|
|
62
|
+
# @return [String] the header's Markdown source
|
|
50
63
|
def header(bluebook)
|
|
51
64
|
parts = ["# #{title(bluebook)}"]
|
|
52
65
|
parts << "> #{bluebook.vision}" if bluebook.vision
|
|
@@ -55,6 +68,13 @@ module Hecks
|
|
|
55
68
|
parts.join("\n\n")
|
|
56
69
|
end
|
|
57
70
|
|
|
71
|
+
# Renders one `##` section: its opening (an aggregate's, or a standing
|
|
72
|
+
# group's blurb), followed by every term inside it.
|
|
73
|
+
#
|
|
74
|
+
# @param section [Glossary::Section] the section to render
|
|
75
|
+
# @param document [Glossary::Document] the document `section` belongs to,
|
|
76
|
+
# for its bluebook and link index
|
|
77
|
+
# @return [String] the section's Markdown source
|
|
58
78
|
def section_text(section, document)
|
|
59
79
|
parts = ["## #{section.title}"]
|
|
60
80
|
parts += section.aggregate ? opening(section.aggregate, document.bluebook) : ["> #{STANDING[section.name]}"]
|
|
@@ -64,6 +84,12 @@ module Hecks
|
|
|
64
84
|
|
|
65
85
|
# What an aggregate is, what it can be, how it fits and moves,
|
|
66
86
|
# and what is always true of it — before a single term.
|
|
87
|
+
#
|
|
88
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate to render an opening for
|
|
89
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter `aggregate` belongs
|
|
90
|
+
# to, for drawing its context diagram
|
|
91
|
+
# @return [Array<String>] the opening's Markdown blocks, one per paragraph or
|
|
92
|
+
# diagram, not yet joined
|
|
67
93
|
def opening(aggregate, bluebook)
|
|
68
94
|
parts = []
|
|
69
95
|
parts << "> #{aggregate.description}" if aggregate.description
|
|
@@ -82,6 +108,12 @@ module Hecks
|
|
|
82
108
|
# one thing this page never shows as an identifier — so each is
|
|
83
109
|
# replaced with how it is said, whole-word, and nothing else in
|
|
84
110
|
# the sentence is touched.
|
|
111
|
+
#
|
|
112
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate to gather rules for
|
|
113
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to read every
|
|
114
|
+
# construct name from, for de-identifying relationship sentences
|
|
115
|
+
# @return [Array<String>] `aggregate`'s (and its entities') attribute and
|
|
116
|
+
# invariant statements, spoken and deduplicated
|
|
85
117
|
def always_true(aggregate, bluebook)
|
|
86
118
|
names = bluebook.aggregates.flat_map { |other| [other.hecks_name, *other.entities.map(&:hecks_name)] }
|
|
87
119
|
[aggregate, *aggregate.entities].flat_map do |holder|
|
|
@@ -90,14 +122,29 @@ module Hecks
|
|
|
90
122
|
end.uniq
|
|
91
123
|
end
|
|
92
124
|
|
|
125
|
+
# Replaces every whole-word construct name in `sentence` with how it is said.
|
|
126
|
+
#
|
|
127
|
+
# @param sentence [String] the sentence to de-identify
|
|
128
|
+
# @param names [Array<String>] every construct name to replace, whole-word
|
|
129
|
+
# @return [String] `sentence` with each name in `names` replaced by
|
|
130
|
+
# `Naming.words(name)`
|
|
93
131
|
def spoken(sentence, names)
|
|
94
132
|
names.reduce(sentence) { |text, name| text.gsub(/\b#{Regexp.escape(name)}\b/, Naming.words(name)) }
|
|
95
133
|
end
|
|
96
134
|
|
|
135
|
+
# Renders one term's `###` heading and its paragraphs.
|
|
136
|
+
#
|
|
137
|
+
# @param entry [Glossary::Entry] the term to render
|
|
138
|
+
# @param index [Glossary::Index] the document's link index
|
|
139
|
+
# @return [String] the term's Markdown source
|
|
97
140
|
def term_text(entry, index)
|
|
98
141
|
["### #{entry.headword}", *Sentences.paragraphs(entry, index)].join("\n\n")
|
|
99
142
|
end
|
|
100
143
|
|
|
144
|
+
# Wraps a Mermaid diagram's source in a fenced code block.
|
|
145
|
+
#
|
|
146
|
+
# @param source [String] a Mermaid diagram's own source
|
|
147
|
+
# @return [String] `source` wrapped in a ` ```mermaid ` fence
|
|
101
148
|
def fence(source) = "```mermaid\n#{source}\n```"
|
|
102
149
|
end
|
|
103
150
|
end
|
|
@@ -25,6 +25,10 @@ module Hecks
|
|
|
25
25
|
module Mermaid
|
|
26
26
|
module_function
|
|
27
27
|
|
|
28
|
+
# Draws the whole chapter's aggregates and which points at which.
|
|
29
|
+
#
|
|
30
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to draw
|
|
31
|
+
# @return [String] a Mermaid `flowchart LR` source
|
|
28
32
|
def map(bluebook)
|
|
29
33
|
names = bluebook.aggregates.map(&:hecks_name)
|
|
30
34
|
lines = ["flowchart LR"]
|
|
@@ -39,6 +43,12 @@ module Hecks
|
|
|
39
43
|
lines.join("\n")
|
|
40
44
|
end
|
|
41
45
|
|
|
46
|
+
# Draws one aggregate, its entities, and its neighbours.
|
|
47
|
+
#
|
|
48
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate to focus on
|
|
49
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to find
|
|
50
|
+
# neighbouring aggregates in
|
|
51
|
+
# @return [String] a Mermaid `flowchart LR` source
|
|
42
52
|
def context(aggregate, bluebook)
|
|
43
53
|
focus = aggregate.hecks_name
|
|
44
54
|
lines = ["flowchart LR", node(focus, focus: true)]
|
|
@@ -58,6 +68,11 @@ module Hecks
|
|
|
58
68
|
lines.uniq.join("\n")
|
|
59
69
|
end
|
|
60
70
|
|
|
71
|
+
# Draws the states a holder can be in and what moves it between them.
|
|
72
|
+
#
|
|
73
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the holder whose
|
|
74
|
+
# lifecycle to draw
|
|
75
|
+
# @return [String] a Mermaid `stateDiagram-v2` source
|
|
61
76
|
def lifecycle(holder)
|
|
62
77
|
lifecycle = holder.lifecycle
|
|
63
78
|
lines = ["stateDiagram-v2"]
|
|
@@ -74,15 +89,33 @@ module Hecks
|
|
|
74
89
|
|
|
75
90
|
# ── the facts drawn ──────────────────────────────────────────────
|
|
76
91
|
|
|
92
|
+
# Names a holder's own reference attributes.
|
|
93
|
+
#
|
|
94
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the holder to
|
|
95
|
+
# read reference attributes from
|
|
96
|
+
# @return [Array<Bluebook::Attribute>] `holder`'s own reference attributes
|
|
77
97
|
def references(holder) = holder.attributes.select(&:reference?)
|
|
78
98
|
|
|
79
99
|
# An entity is held as `list_of(LedgerEntry)` — the attribute
|
|
80
100
|
# whose element type names one of the aggregate's own entities.
|
|
101
|
+
#
|
|
102
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate to read entity
|
|
103
|
+
# attributes from
|
|
104
|
+
# @return [Array<Bluebook::Attribute>] attributes whose element type is one
|
|
105
|
+
# of `aggregate`'s own entities
|
|
81
106
|
def entities(aggregate)
|
|
82
107
|
names = aggregate.entities.map(&:hecks_name)
|
|
83
108
|
aggregate.attributes.select { |attribute| !attribute.reference? && names.include?(attribute.type.to_s) }
|
|
84
109
|
end
|
|
85
110
|
|
|
111
|
+
# Finds every other aggregate that references `aggregate`.
|
|
112
|
+
#
|
|
113
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate to find pointers to
|
|
114
|
+
# @param bluebook [Bluebook::Behaviour::Chapter] the chapter to search other
|
|
115
|
+
# aggregates in
|
|
116
|
+
# @return [Array<Array(Bluebook::Aggregate, Bluebook::Attribute)>] every other
|
|
117
|
+
# aggregate's own reference attribute that targets `aggregate`, paired
|
|
118
|
+
# with its holder
|
|
86
119
|
def pointing_at(aggregate, bluebook)
|
|
87
120
|
bluebook.aggregates.reject { |other| other.equal?(aggregate) }.flat_map do |other|
|
|
88
121
|
references(other).select { |attribute| attribute.type.target_name == aggregate.hecks_name }
|
|
@@ -94,13 +127,28 @@ module Hecks
|
|
|
94
127
|
|
|
95
128
|
# A node id the page source can carry without an identifier in
|
|
96
129
|
# it — `n_atm_card`, never `n_ATMCard`.
|
|
130
|
+
#
|
|
131
|
+
# @param name [String, Symbol] the construct name to derive an id from
|
|
132
|
+
# @return [String] a Mermaid-safe node id, such as `"n_atm_card"`
|
|
97
133
|
def id(name) = "n_#{Naming.snake(name).gsub(/[^a-z0-9_]/, '_')}"
|
|
98
134
|
|
|
135
|
+
# Renders one Mermaid node declaration.
|
|
136
|
+
#
|
|
137
|
+
# @param name [String, Symbol] the node's construct name
|
|
138
|
+
# @param focus [Boolean] whether to mark this node with the `focus` CSS class
|
|
139
|
+
# @return [String] a Mermaid node declaration line
|
|
99
140
|
def node(name, focus: false)
|
|
100
141
|
line = " #{id(name)}[\"#{Naming.words(name)}\"]"
|
|
101
142
|
focus ? "#{line}:::focus" : line
|
|
102
143
|
end
|
|
103
144
|
|
|
145
|
+
# Renders one Mermaid edge declaration.
|
|
146
|
+
#
|
|
147
|
+
# @param from [String, Symbol] the source node's construct name
|
|
148
|
+
# @param attribute [Bluebook::Attribute] the attribute the edge is labelled after
|
|
149
|
+
# @param to [String, Symbol] the target node's construct name
|
|
150
|
+
# @return [String] a Mermaid edge declaration line, labelled with the
|
|
151
|
+
# attribute's spoken name
|
|
104
152
|
def edge(from, attribute, to)
|
|
105
153
|
" #{id(from)} -->|\"#{Naming.words(attribute.name).downcase}\"| #{id(to)}"
|
|
106
154
|
end
|
|
@@ -30,6 +30,11 @@ module Hecks
|
|
|
30
30
|
|
|
31
31
|
# The paragraphs under a term's headword — the definition first,
|
|
32
32
|
# then, when the term carries rules, one "Always true: …" line.
|
|
33
|
+
#
|
|
34
|
+
# @param entry [Glossary::Entry] the term to render
|
|
35
|
+
# @param index [Glossary::Index] the document's link index
|
|
36
|
+
# @return [Array<String>] one or more paragraphs, compacted of any that
|
|
37
|
+
# have nothing to say (such as an entity with no lifecycle)
|
|
33
38
|
def paragraphs(entry, index)
|
|
34
39
|
facts = entry.facts
|
|
35
40
|
case entry.kind
|
|
@@ -47,10 +52,18 @@ module Hecks
|
|
|
47
52
|
|
|
48
53
|
# An aggregate's lede or an entity's entry: what it is, then the
|
|
49
54
|
# states it can be in.
|
|
55
|
+
#
|
|
56
|
+
# @param holder [Bluebook::Aggregate, Bluebook::Entity] the holder to describe
|
|
57
|
+
# @return [Array<String>] `holder`'s description, then its lifecycle sentence;
|
|
58
|
+
# either or both may be absent
|
|
50
59
|
def holder_paragraphs(holder)
|
|
51
60
|
[holder.description, holder.lifecycle && lifecycle_sentence(holder.lifecycle)].compact
|
|
52
61
|
end
|
|
53
62
|
|
|
63
|
+
# Describes a lifecycle's starting state and every state it can reach.
|
|
64
|
+
#
|
|
65
|
+
# @param lifecycle [Bluebook::Lifecycle] the state machine to describe
|
|
66
|
+
# @return [String] what state it starts in and every state it can reach
|
|
54
67
|
def lifecycle_sentence(lifecycle)
|
|
55
68
|
states = ([lifecycle.default] + lifecycle.transitions.map { |_name, transition| transition.target }).uniq
|
|
56
69
|
"Starts out #{spoken(lifecycle.default)}. " \
|
|
@@ -58,8 +71,19 @@ module Hecks
|
|
|
58
71
|
end
|
|
59
72
|
|
|
60
73
|
# A state name is spelled `awaiting_credit`; said, it is "awaiting credit".
|
|
74
|
+
#
|
|
75
|
+
# @param state [String, Symbol] a state name
|
|
76
|
+
# @return [String] `state` with underscores turned to spaces
|
|
61
77
|
def spoken(state) = state.to_s.tr("_", " ")
|
|
62
78
|
|
|
79
|
+
# Renders a value object's definition, and its rules if it has any.
|
|
80
|
+
#
|
|
81
|
+
# @param value_object [Bluebook::ValueObject] the value object to describe
|
|
82
|
+
# @param index [Glossary::Index] the document's link index
|
|
83
|
+
# @param within [String, nil] the value object's own holder's name, for
|
|
84
|
+
# resolving a nested value object's own type link
|
|
85
|
+
# @return [Array<String>] the value object's definition sentence, then its
|
|
86
|
+
# "Always true: …" rules line; the rules line is absent when it has none
|
|
63
87
|
def value_object_paragraphs(value_object, index, within)
|
|
64
88
|
rules = value_object.invariants.map { |invariant| Statements.invariant_statement(invariant) }
|
|
65
89
|
[value_object_sentence(value_object, index, within), rules_line(rules)].compact
|
|
@@ -67,6 +91,11 @@ module Hecks
|
|
|
67
91
|
|
|
68
92
|
# A one-field object whose field is just "value" is its type —
|
|
69
93
|
# "Text.", "A whole number." — the field name would add nothing.
|
|
94
|
+
#
|
|
95
|
+
# @param value_object [Bluebook::ValueObject] the value object to describe
|
|
96
|
+
# @param index [Glossary::Index] the document's link index
|
|
97
|
+
# @param within [String, nil] the value object's own holder's name
|
|
98
|
+
# @return [String] the value object's definition sentence
|
|
70
99
|
def value_object_sentence(value_object, index, within)
|
|
71
100
|
return closed_set_sentence(value_object.members) if value_object.closed_set?
|
|
72
101
|
return "A marker with no details of its own." if value_object.attributes.empty?
|
|
@@ -82,10 +111,22 @@ module Hecks
|
|
|
82
111
|
|
|
83
112
|
# "amount (a whole number)" — the field as the author named it,
|
|
84
113
|
# then what kind of thing goes in it.
|
|
114
|
+
#
|
|
115
|
+
# @param field [Bluebook::Attribute] the field to describe
|
|
116
|
+
# @param index [Glossary::Index] the document's link index
|
|
117
|
+
# @param within [String, nil] the field's own holder's name
|
|
118
|
+
# @return [String] the field's name, spoken, with its type in parentheses
|
|
85
119
|
def field_phrase(field, index, within)
|
|
86
120
|
"#{Naming.words(field.name).downcase} (#{type_words(field, index, within)})"
|
|
87
121
|
end
|
|
88
122
|
|
|
123
|
+
# Describes one field's type in plain words.
|
|
124
|
+
#
|
|
125
|
+
# @param field [Bluebook::Attribute] the field to describe
|
|
126
|
+
# @param index [Glossary::Index] the document's link index
|
|
127
|
+
# @param within [String, nil] the field's own holder's name
|
|
128
|
+
# @return [String] the field's type in plain words, or a link to its value
|
|
129
|
+
# object's own headword; "a list of …" when the field is a list
|
|
89
130
|
def type_words(field, index, within)
|
|
90
131
|
type = field.type.to_s
|
|
91
132
|
inner = TYPE_WORDS[type] || index.link(:value_object, type, within: within)
|
|
@@ -96,6 +137,9 @@ module Hecks
|
|
|
96
137
|
# whose rows carry more (StatementFrequency's cadence plus a
|
|
97
138
|
# retention and a fee) leads with the first field and keeps the
|
|
98
139
|
# rest beside it, so no row loses what makes it distinct.
|
|
140
|
+
#
|
|
141
|
+
# @param members [Array<Hash{Symbol => Object}>] the closed set's member rows
|
|
142
|
+
# @return [String] "One of: …" (multi-field rows) or "One of …" (single-field)
|
|
99
143
|
def closed_set_sentence(members)
|
|
100
144
|
if members.first && members.first.size > 1
|
|
101
145
|
rows = members.map do |row|
|
|
@@ -112,6 +156,10 @@ module Hecks
|
|
|
112
156
|
# code." — the rules as their author wrote them, kept out of the
|
|
113
157
|
# definition sentence (a rule hidden inside a definition is a rule
|
|
114
158
|
# a reader misses).
|
|
159
|
+
#
|
|
160
|
+
# @param rules [Array<String>] rendered invariant sentences
|
|
161
|
+
# @return [String, nil] "Always true: …" joining every rule, or nil if
|
|
162
|
+
# `rules` is empty
|
|
115
163
|
def rules_line(rules)
|
|
116
164
|
return nil if rules.empty?
|
|
117
165
|
|
|
@@ -119,6 +167,11 @@ module Hecks
|
|
|
119
167
|
"Always true: #{clauses.join('; ')}."
|
|
120
168
|
end
|
|
121
169
|
|
|
170
|
+
# Describes a command's goal and who does it.
|
|
171
|
+
#
|
|
172
|
+
# @param command [Bluebook::Command] the command to describe
|
|
173
|
+
# @return [String, nil] the command's goal and/or its role sentence, or nil
|
|
174
|
+
# if it declares neither
|
|
122
175
|
def command_sentence(command)
|
|
123
176
|
parts = []
|
|
124
177
|
parts << with_period(command.goal) if command.goal
|
|
@@ -126,6 +179,14 @@ module Hecks
|
|
|
126
179
|
parts.empty? ? nil : parts.join(" ")
|
|
127
180
|
end
|
|
128
181
|
|
|
182
|
+
# Describes when an event is recorded and what it prompts.
|
|
183
|
+
#
|
|
184
|
+
# @param facts [Hash{Symbol => Object}] an `:event`-kind entry's facts:
|
|
185
|
+
# `:raised_by` (`Array<Array(Bluebook::Aggregate, Bluebook::Command)>`) and
|
|
186
|
+
# `:policies` (`Array<Bluebook::Policy>`)
|
|
187
|
+
# @param index [Glossary::Index] the document's link index
|
|
188
|
+
# @return [String] which commands record the event, and, when any policy
|
|
189
|
+
# reacts to it, which ones it prompts
|
|
129
190
|
def event_sentence(facts, index)
|
|
130
191
|
raisers = command_links(facts[:raised_by], index)
|
|
131
192
|
sentence = "Recorded after #{Naming.to_sentence_list(raisers, conj: 'or')}."
|
|
@@ -138,6 +199,10 @@ module Hecks
|
|
|
138
199
|
# account, once for each row of Open for customer." — a
|
|
139
200
|
# cross-domain trigger (`across "Compliance"`) is spoken as words
|
|
140
201
|
# with the domain named, since nothing here to link to exists.
|
|
202
|
+
#
|
|
203
|
+
# @param policy [Bluebook::Policy] the policy to describe
|
|
204
|
+
# @param index [Glossary::Index] the document's link index
|
|
205
|
+
# @return [String] when the policy fires and what it asks for
|
|
141
206
|
def policy_sentence(policy, index)
|
|
142
207
|
holder, command = split_trigger(policy.trigger_command)
|
|
143
208
|
asked = if policy.target_domain
|
|
@@ -154,6 +219,13 @@ module Hecks
|
|
|
154
219
|
"#{sentence}."
|
|
155
220
|
end
|
|
156
221
|
|
|
222
|
+
# Describes when a saga begins and ends, and its states along the way.
|
|
223
|
+
#
|
|
224
|
+
# @param shape [Hash{Symbol => Object}] a process manager's `to_h`, read for
|
|
225
|
+
# `:starts_on`, `:ends_on`, and `:states`
|
|
226
|
+
# @param index [Glossary::Index] the document's link index
|
|
227
|
+
# @return [String] when the saga begins and ends, and, when it declares any,
|
|
228
|
+
# the states it can be in along the way
|
|
157
229
|
def saga_sentence(shape, index)
|
|
158
230
|
sentence = "Begins when #{index.link(:event, bare(shape[:starts_on]))} happens " \
|
|
159
231
|
"and ends when #{index.link(:event, bare(shape[:ends_on]))} happens."
|
|
@@ -166,6 +238,11 @@ module Hecks
|
|
|
166
238
|
# never "Can credit and debit": banking's System role raises
|
|
167
239
|
# `Debited` and `Credited`, and "can … debited" is a wrong
|
|
168
240
|
# sentence. The headwords are already what people say.
|
|
241
|
+
#
|
|
242
|
+
# @param issues [Array<Array(Bluebook::Aggregate, Bluebook::Command)>] the
|
|
243
|
+
# role's own `[holder, command]` pairs
|
|
244
|
+
# @param index [Glossary::Index] the document's link index
|
|
245
|
+
# @return [String] "Responsible for …", naming every command
|
|
169
246
|
def role_sentence(issues, index)
|
|
170
247
|
"Responsible for #{Naming.to_sentence_list(command_links(issues, index))}."
|
|
171
248
|
end
|
|
@@ -174,6 +251,12 @@ module Hecks
|
|
|
174
251
|
# — a role responsible for CardPayment's Reverse and Transfer's
|
|
175
252
|
# Reverse is responsible for "Reverse (card payment)" and
|
|
176
253
|
# "Reverse (transfer)", not for "Reverse" twice.
|
|
254
|
+
#
|
|
255
|
+
# @param issues [Array<Array(Bluebook::Aggregate, Bluebook::Command)>] each
|
|
256
|
+
# `[holder, command]` pair to link
|
|
257
|
+
# @param index [Glossary::Index] the document's link index
|
|
258
|
+
# @return [Array<String>] one Markdown link per distinct `[holder, command]`
|
|
259
|
+
# pair, qualified with the holder's name when the command name repeats
|
|
177
260
|
def command_links(issues, index)
|
|
178
261
|
issues = issues.uniq { |holder, command| [holder.hecks_name, command.hecks_name] }
|
|
179
262
|
repeated = issues.map { |_holder, command| command.hecks_name }.tally.select { |_name, count| count > 1 }
|
|
@@ -187,17 +270,39 @@ module Hecks
|
|
|
187
270
|
|
|
188
271
|
# ── small carpentry ─────────────────────────────────────────────
|
|
189
272
|
|
|
273
|
+
# Splits a dotted `Holder.command` trigger into its two parts.
|
|
274
|
+
#
|
|
275
|
+
# @param dotted [String, Symbol] a `"Holder.command"` reference
|
|
276
|
+
# @return [Array(String, String)] `[holder, command]`
|
|
190
277
|
def split_trigger(dotted)
|
|
191
278
|
holder, _dot, command = dotted.to_s.rpartition(".")
|
|
192
279
|
[holder, command]
|
|
193
280
|
end
|
|
194
281
|
|
|
282
|
+
# Strips a dotted name down to its last segment.
|
|
283
|
+
#
|
|
284
|
+
# @param qualified [String, Symbol] a dotted or bare name, such as
|
|
285
|
+
# `"Handler.Dispatch"` or `"Freeze"`
|
|
286
|
+
# @return [String] the name after the last `.`, or the whole name if it has none
|
|
195
287
|
def bare(qualified) = qualified.to_s.split(".").last
|
|
196
288
|
|
|
289
|
+
# Ensures text ends in sentence-ending punctuation.
|
|
290
|
+
#
|
|
291
|
+
# @param text [String, Symbol, nil] the text to punctuate
|
|
292
|
+
# @return [String] `text`, stripped, with a trailing `.` added if it has no
|
|
293
|
+
# sentence-ending punctuation already
|
|
197
294
|
def with_period(text) = text.to_s.strip.end_with?(".", "!", "?") ? text.to_s.strip : "#{text.to_s.strip}."
|
|
198
295
|
|
|
296
|
+
# Lowercases a leading capital letter.
|
|
297
|
+
#
|
|
298
|
+
# @param text [String] the text to adjust
|
|
299
|
+
# @return [String] `text` with a leading uppercase letter lowercased
|
|
199
300
|
def lower_first(text) = text.sub(/\A[[:upper:]]/, &:downcase)
|
|
200
301
|
|
|
302
|
+
# Uppercases a leading lowercase letter.
|
|
303
|
+
#
|
|
304
|
+
# @param text [String] the text to adjust
|
|
305
|
+
# @return [String] `text` with a leading lowercase letter uppercased
|
|
201
306
|
def upper_first(text) = text.sub(/\A[[:lower:]]/, &:upcase)
|
|
202
307
|
end
|
|
203
308
|
end
|