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
data/lib/hecks/codemod.rb
CHANGED
|
@@ -2,20 +2,26 @@ require_relative "bluebook/meta_validator"
|
|
|
2
2
|
require_relative "corpus"
|
|
3
3
|
|
|
4
4
|
module Hecks
|
|
5
|
-
# Shared machinery for a codemod that migrates real `.bluebook` source
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
5
|
+
# Shared machinery for a codemod that migrates real `.bluebook` source.
|
|
6
|
+
#
|
|
7
|
+
# ## Why this module exists
|
|
8
|
+
#
|
|
9
|
+
# For migrating source once a DSL builder change turns a required
|
|
10
|
+
# declaration into an optional, redundant one — pulled out of
|
|
11
|
+
# `bin/codemod_implicit_append_fields` (the first one built), which
|
|
12
|
+
# needed three real, hard-won fixes before it could be trusted: a
|
|
13
|
+
# process-lifetime AST cache with no invalidation, a batch-revert
|
|
14
|
+
# granularity that let one unsafe candidate sink every other safe one
|
|
15
|
+
# sharing its boot, and (in the spine the codemod migrates for, not
|
|
16
|
+
# here) an append-at-end insertion that only round-tripped correctly
|
|
17
|
+
# for whichever field happened to be last. None of those are guessable
|
|
18
|
+
# in advance; they only surface by actually running a real edit
|
|
19
|
+
# against real self-hosted code. This module is that lesson, kept —
|
|
20
|
+
# the next codemod plugs in two rule-specific procs (`find_candidates`,
|
|
21
|
+
# `apply_candidate`) and inherits the boot/safety-net machinery rather
|
|
22
|
+
# than rediscovering it.
|
|
23
|
+
#
|
|
24
|
+
# ## The three-step contract
|
|
19
25
|
#
|
|
20
26
|
# **A codemod is not pattern-matching alone**. Deciding "is this line safe
|
|
21
27
|
# to delete" means knowing what the runtime would resolve it to — so
|
|
@@ -33,9 +39,11 @@ module Hecks
|
|
|
33
39
|
# can surface as a runtime refusal, not just a differing export)
|
|
34
40
|
# -> revert and report skipped, never silently guessed past.
|
|
35
41
|
#
|
|
36
|
-
# Design-time checklist for
|
|
37
|
-
#
|
|
38
|
-
#
|
|
42
|
+
# ## Design-time checklist for a future codemod
|
|
43
|
+
#
|
|
44
|
+
# For the spine change a future codemod migrates corpus text for —
|
|
45
|
+
# both items below are real bugs this module's own first use found,
|
|
46
|
+
# not hypothetical:
|
|
39
47
|
# - Does the resolved value get inserted into an order-sensitive
|
|
40
48
|
# list (the exported IR is array-order-sensitive throughout)? If
|
|
41
49
|
# so, the spine's own insertion must preserve the original
|
|
@@ -70,6 +78,10 @@ module Hecks
|
|
|
70
78
|
MEMORY_ADAPTER = File.join(ROOT, "lib/hecks/adapters/driven/memory.adapter")
|
|
71
79
|
PRISM_ADAPTER = File.join(ROOT, "lib/hecks/adapters/driven/prism.adapter")
|
|
72
80
|
|
|
81
|
+
# Exports a booted registry's canonical IR as JSON, for a before/after diff.
|
|
82
|
+
#
|
|
83
|
+
# @param registry [Runtime::Registry] the booted registry to export
|
|
84
|
+
# @return [String] the pretty-printed JSON IR export
|
|
73
85
|
def self.export_json(registry) = Hecks::Projector::Exporter.json(registry)
|
|
74
86
|
|
|
75
87
|
# `Hecks::Adapters::Prism` caches a file's parsed AST for the
|
|
@@ -83,6 +95,10 @@ module Hecks
|
|
|
83
95
|
# file. `Prism.forget` is the real invalidation API this module's
|
|
84
96
|
# own first use motivated (found here, fixed at the source rather
|
|
85
97
|
# than left as a private `TREES.clear` poke from outside).
|
|
98
|
+
#
|
|
99
|
+
# @param path [String, Array<String>] a single `.bluebook` file, a directory
|
|
100
|
+
# to glob every `.bluebook` file from, or an explicit list of file paths
|
|
101
|
+
# @return [Runtime::Registry] a fresh registry with every file loaded and judged
|
|
86
102
|
def self.load_bluebook(path)
|
|
87
103
|
paths = if path.is_a?(Array)
|
|
88
104
|
path
|
|
@@ -109,12 +125,17 @@ module Hecks
|
|
|
109
125
|
# files (`MetaValidator::GRAMMAR_FILES`) merged into one registry,
|
|
110
126
|
# and a caller here (the codemod runner) doesn't generally know in
|
|
111
127
|
# advance which one it just edited.
|
|
128
|
+
# @return [String] the freshly re-derived meta-domain's canonical IR, as JSON
|
|
112
129
|
def self.boot_meta
|
|
113
130
|
Hecks::Adapters::Prism.forget_all
|
|
114
131
|
Hecks::Bluebook::MetaValidator.instance_variable_set(:@grammar_registry, nil)
|
|
115
132
|
export_json(Hecks::Bluebook::MetaValidator.grammar_registry)
|
|
116
133
|
end
|
|
117
134
|
|
|
135
|
+
# Forces the self-hosted meta-domain to re-derive from its current source,
|
|
136
|
+
# discarding any cached parse trees or memoized registry.
|
|
137
|
+
#
|
|
138
|
+
# @return [Runtime::Registry] the freshly re-derived meta-domain's registry
|
|
118
139
|
def self.meta_registry
|
|
119
140
|
Hecks::Adapters::Prism.forget_all
|
|
120
141
|
Hecks::Bluebook::MetaValidator.instance_variable_set(:@grammar_registry, nil)
|
|
@@ -128,6 +149,14 @@ module Hecks
|
|
|
128
149
|
# `AggregateBuilder#command` vs `EntityBuilder#command` already
|
|
129
150
|
# draws. Generic enough for any rule that needs to walk real
|
|
130
151
|
# commands, not specific to the attribute-redundancy rule.
|
|
152
|
+
#
|
|
153
|
+
# @param registry [Runtime::Registry] a booted registry
|
|
154
|
+
# @yield [construct, command] every command in the registry, once per aggregate
|
|
155
|
+
# and once per nested entity
|
|
156
|
+
# @yieldparam construct [Bluebook::Aggregate, Bluebook::Entity] whichever
|
|
157
|
+
# aggregate or entity actually owns `command`
|
|
158
|
+
# @yieldparam command [Bluebook::Command] the command
|
|
159
|
+
# @return [Hash{String => Bluebook::Chapter}] `registry.bluebooks`, unchanged
|
|
131
160
|
def self.each_command(registry)
|
|
132
161
|
registry.bluebooks.each_value do |chapter|
|
|
133
162
|
chapter.aggregates.each do |aggregate|
|
|
@@ -140,6 +169,12 @@ module Hecks
|
|
|
140
169
|
end
|
|
141
170
|
end
|
|
142
171
|
|
|
172
|
+
# Finds one construct's own attribute by name.
|
|
173
|
+
#
|
|
174
|
+
# @param construct [Bluebook::Aggregate, Bluebook::Entity] the construct to search
|
|
175
|
+
# @param name [String, Symbol, #to_s] the attribute's declared name
|
|
176
|
+
# @return [Bluebook::Attribute, nil] the matching attribute, or nil if `construct`
|
|
177
|
+
# declares no attribute named `name`
|
|
143
178
|
def self.owner_attribute(construct, name)
|
|
144
179
|
construct.attributes.find { |attr| attr.name.to_s == name.to_s }
|
|
145
180
|
end
|
|
@@ -149,18 +184,48 @@ module Hecks
|
|
|
149
184
|
# `AttributeCollector#resolve_identity_field!` already does. Shared
|
|
150
185
|
# because "what does this list actually hold" is a question any
|
|
151
186
|
# append-shaped rule needs answered, not just this one.
|
|
187
|
+
#
|
|
188
|
+
# Nothing in the DSL stops a value object and an entity nested under
|
|
189
|
+
# the same aggregate from sharing a `hecks_name` — they are declared
|
|
190
|
+
# through two separate collections with no cross-kind uniqueness
|
|
191
|
+
# check between them. A silent value-objects-before-entities pick
|
|
192
|
+
# would resolve such a pair the same way every time regardless of
|
|
193
|
+
# which one `list_field` actually names, so a genuine collision is
|
|
194
|
+
# refused instead, the same "never first-wins silently" call
|
|
195
|
+
# `EntityBuilder#install_closed_sets!` already makes for a same-named
|
|
196
|
+
# closed set.
|
|
197
|
+
#
|
|
198
|
+
# @param construct [Bluebook::Aggregate, Bluebook::Entity] the construct
|
|
199
|
+
# declaring `list_field`
|
|
200
|
+
# @param list_field [String, Symbol, #to_s] the name of the `list_of(...)` attribute
|
|
201
|
+
# @return [Bluebook::Entity, Bluebook::ValueObject, nil] the construct
|
|
202
|
+
# `list_field` holds a list of, or nil if `list_field` names no attribute,
|
|
203
|
+
# isn't a list, or names no known value object or entity
|
|
204
|
+
# @raise [RuntimeError] if a value object and an entity both match `list_field`'s type
|
|
152
205
|
def self.element_construct_for(construct, list_field)
|
|
153
206
|
list_attr = owner_attribute(construct, list_field)
|
|
154
207
|
return nil unless list_attr&.list?
|
|
155
208
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
209
|
+
value_objects = construct.respond_to?(:value_objects) ? construct.value_objects : []
|
|
210
|
+
entities = construct.respond_to?(:entities) ? construct.entities : []
|
|
211
|
+
matches = (value_objects + entities).select { |c| c.hecks_name.to_s == list_attr.type.to_s }
|
|
212
|
+
|
|
213
|
+
if matches.size > 1
|
|
214
|
+
raise "#{construct.hecks_name}##{list_field} names #{list_attr.type}, held by both a value " \
|
|
215
|
+
"object and an entity — ambiguous, cannot resolve which one the list holds"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
matches.first
|
|
159
219
|
end
|
|
160
220
|
|
|
161
221
|
# Either a raised exception or a differing export counts as unsafe
|
|
162
222
|
# — see the module header on why the meta-domain specifically can
|
|
163
223
|
# raise. Returns [value_or_nil, error_message_or_nil].
|
|
224
|
+
#
|
|
225
|
+
# @yield the risky boot/export step to run
|
|
226
|
+
# @return [Array(Object, nil), Array(nil, String)] `[the block's result, nil]`
|
|
227
|
+
# on success, or `[nil, "ExceptionClass: message"]` if the block raises
|
|
228
|
+
# any StandardError
|
|
164
229
|
def self.safely
|
|
165
230
|
[yield, nil]
|
|
166
231
|
rescue StandardError => e
|
|
@@ -186,12 +251,29 @@ module Hecks
|
|
|
186
251
|
# label: ->(candidate) { "..." }
|
|
187
252
|
# One-line description for the results report.
|
|
188
253
|
class Runner
|
|
254
|
+
# @param find_candidates [Proc] `->(registry) { [...] }` — given a booted
|
|
255
|
+
# registry, returns every candidate this rule could migrate; a candidate's
|
|
256
|
+
# shape is the caller's own, read back only by `apply_candidate`
|
|
257
|
+
# @param apply_candidate [Proc] `->(text, candidate) { [new_text, changed_bool] }`
|
|
258
|
+
# — given one file's current text and one candidate, returns the edited text
|
|
259
|
+
# and whether a match was found; `false` means the candidate doesn't apply to
|
|
260
|
+
# this file
|
|
261
|
+
# @param label [Proc] `->(candidate) { "..." }` — a one-line description of a
|
|
262
|
+
# candidate, for the results report
|
|
189
263
|
def initialize(find_candidates:, apply_candidate:, label:)
|
|
190
264
|
@find_candidates = find_candidates
|
|
191
265
|
@apply_candidate = apply_candidate
|
|
192
266
|
@label = label
|
|
193
267
|
end
|
|
194
268
|
|
|
269
|
+
# Runs this rule's codemod across every example domain and the meta-domain.
|
|
270
|
+
#
|
|
271
|
+
# @param dry_run [Boolean] when true, every safe edit is still written and
|
|
272
|
+
# reverified, then reverted rather than kept
|
|
273
|
+
# @return [Hash{Symbol => Array}] `:applied` (`Array<Hash{file: String,
|
|
274
|
+
# candidates: Array<String>}>`), `:skipped` (`Array<Hash{file: String,
|
|
275
|
+
# reason: String, candidates: Array<String>}>`), and `:clean`
|
|
276
|
+
# (`Array<String>` of domain directories or `"meta-domain"` with no candidates)
|
|
195
277
|
def run(dry_run: false)
|
|
196
278
|
results = { applied: [], skipped: [], clean: [] }
|
|
197
279
|
run_example_domains(results, dry_run)
|
|
@@ -199,6 +281,11 @@ module Hecks
|
|
|
199
281
|
results
|
|
200
282
|
end
|
|
201
283
|
|
|
284
|
+
# Prints `run`'s results to stdout.
|
|
285
|
+
#
|
|
286
|
+
# @param results [Hash{Symbol => Array}] a `run` result
|
|
287
|
+
# @param dry_run [Boolean] whether this was a dry run, for the report's own heading
|
|
288
|
+
# @return [void]
|
|
202
289
|
def report(results, dry_run:)
|
|
203
290
|
puts "== results (#{dry_run ? 'DRY RUN — nothing written' : 'applied'}) =="
|
|
204
291
|
puts "clean (no candidates): #{results[:clean].join(', ')}" unless results[:clean].empty?
|
data/lib/hecks/construct.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
# The invisible field a built construct carries.
|
|
3
3
|
#
|
|
4
|
+
# ## Identity
|
|
5
|
+
#
|
|
4
6
|
# A construct is a record with an owner chain — the chapter (Bluebook)
|
|
5
7
|
# owns its aggregates, an aggregate owns everything declared on it — and the
|
|
6
8
|
# bluebook identity is carried in its own field, under a `hecks_` prefix that
|
|
@@ -20,7 +22,7 @@ module Hecks
|
|
|
20
22
|
# record of that construct carry the same identity, and there is no
|
|
21
23
|
# translation table between them to be quietly wrong in.
|
|
22
24
|
#
|
|
23
|
-
# Usage
|
|
25
|
+
# ## Usage
|
|
24
26
|
#
|
|
25
27
|
# price = Class.new(ValueObject) # a declaration holder
|
|
26
28
|
# price.hecks_name = "Price"
|
|
@@ -41,14 +43,21 @@ module Hecks
|
|
|
41
43
|
# rather than a top — see hecks_fqn.
|
|
42
44
|
attr_writer :hecks_root
|
|
43
45
|
|
|
46
|
+
# Whether this construct is the top of its own owner chain.
|
|
47
|
+
#
|
|
48
|
+
# @return [Boolean] true for a chapter, which sets `hecks_root`; false otherwise
|
|
44
49
|
def hecks_root? = @hecks_root ? true : false
|
|
45
50
|
|
|
46
51
|
# The name as the bluebook declares it, never the constant path.
|
|
52
|
+
#
|
|
53
|
+
# @return [String] the construct's own name, without any owner prefix
|
|
47
54
|
def hecks_name = @hecks_name
|
|
48
55
|
|
|
49
56
|
# How this construct joins its owner. An aggregate is a member of its
|
|
50
57
|
# chapter's namespace (`::`) ; everything else is declared on its owner
|
|
51
58
|
# (`.`). Overridden by Aggregate, defaulted here for every other construct.
|
|
59
|
+
#
|
|
60
|
+
# @return [String] `"."`, the separator this construct uses in `hecks_fqn`
|
|
52
61
|
def hecks_separator = "."
|
|
53
62
|
|
|
54
63
|
# Refuses rather than guesses. A construct with no owner and no claim to be a
|
|
@@ -56,6 +65,11 @@ module Hecks
|
|
|
56
65
|
# while entities are still IR objects — and answering the bare name would be a
|
|
57
66
|
# plausible half-truth that no test would notice. That shape of falsehood is
|
|
58
67
|
# what this repo keeps finding, so it goes red instead.
|
|
68
|
+
#
|
|
69
|
+
# @return [String] the fully-qualified name, joining every owner from the
|
|
70
|
+
# chapter down to this construct
|
|
71
|
+
# @raise [Construct::Unowned] if this construct has no owner and is not itself
|
|
72
|
+
# a chapter (root)
|
|
59
73
|
def hecks_fqn
|
|
60
74
|
return hecks_name.to_s if hecks_root?
|
|
61
75
|
|
data/lib/hecks/corpus.rb
CHANGED
|
@@ -4,14 +4,13 @@ module Hecks
|
|
|
4
4
|
# **The corpus, discovered** — every place in this repo that holds a real
|
|
5
5
|
# domain, named once.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# spec/parser_parity_spec.rb, bin/fuzz, Fuzzing::CombinationMiner
|
|
10
|
-
# the copies had already drifted: only the model checker saw
|
|
7
|
+
# One table here, rather than each consumer spelling out its own copy —
|
|
8
|
+
# spec/corpus_spec.rb, spec/model_check_spec.rb, bin/model_check,
|
|
9
|
+
# spec/parser_parity_spec.rb, bin/fuzz, Fuzzing::CombinationMiner all did,
|
|
10
|
+
# and the copies had already drifted: only the model checker saw
|
|
11
11
|
# `qa/bluebook`, only parser parity saw `spec/fixtures`, and nothing but
|
|
12
|
-
# bin/fuzz's own sweep ever saw `qa/stress_domains`.
|
|
13
|
-
#
|
|
14
|
-
# those kinds live.
|
|
12
|
+
# bin/fuzz's own sweep ever saw `qa/stress_domains`. Each consumer names
|
|
13
|
+
# the `KINDS` it walks rather than re-deriving where those kinds live.
|
|
15
14
|
#
|
|
16
15
|
# Plain Dir/File only — bin/ scripts require this before (or without)
|
|
17
16
|
# booting anything.
|
|
@@ -25,7 +24,19 @@ module Hecks
|
|
|
25
24
|
DIRECTORY_KINDS = {
|
|
26
25
|
example: "examples/*",
|
|
27
26
|
stress: "qa/stress_domains/*",
|
|
28
|
-
semantics: "spec/corpus/semantics/domains/*"
|
|
27
|
+
semantics: "spec/corpus/semantics/domains/*",
|
|
28
|
+
# A PACKAGE VENDORED VIA `uses_embryonaut_bluebook` — same real
|
|
29
|
+
# mechanism `uses_framework` is (`lib/hecks/embryonaut_bluebook.rb`'s
|
|
30
|
+
# own header: "same shape as Framework"), one level further out: not
|
|
31
|
+
# a chapter shipped inside THIS gem's own `lib/hecks/framework/
|
|
32
|
+
# bluebook/` (the `:framework` FILE_KIND, below), but a directory
|
|
33
|
+
# nested inside the CONSUMING example's own checkout
|
|
34
|
+
# (`<domain>/vendor/embryonaut_bluebooks/<name>/bluebook/`,
|
|
35
|
+
# EmbryonautBluebook.load!'s own resolution path) — hence its own
|
|
36
|
+
# DIRECTORY_KIND rather than reuse of `:framework`'s shape. First
|
|
37
|
+
# real member: docs/decisions/0058's own
|
|
38
|
+
# examples/embryonaut_vendoring_demo/vendor/embryonaut_bluebooks/widgets.
|
|
39
|
+
vendored: "examples/*/vendor/embryonaut_bluebooks/*"
|
|
29
40
|
}.freeze
|
|
30
41
|
|
|
31
42
|
# **One chapter per file**. Stemmed by the path below the glob's fixed
|
|
@@ -37,6 +48,7 @@ module Hecks
|
|
|
37
48
|
qa: "qa/bluebook/*.bluebook",
|
|
38
49
|
language: "lib/hecks/language/**/*.bluebook",
|
|
39
50
|
deploy: "lib/hecks/deploy/bluebook/*.bluebook",
|
|
51
|
+
tenancy: "lib/hecks/tenancy/bluebook/*.bluebook",
|
|
40
52
|
fixture: "spec/fixtures/**/*.bluebook"
|
|
41
53
|
}.freeze
|
|
42
54
|
|
|
@@ -88,6 +100,13 @@ module Hecks
|
|
|
88
100
|
|
|
89
101
|
module_function
|
|
90
102
|
|
|
103
|
+
# Lists corpus members of the given kinds (every kind, by default).
|
|
104
|
+
#
|
|
105
|
+
# @param kinds [Array<Symbol>] corpus kinds to include, from `KINDS`; every kind
|
|
106
|
+
# when empty
|
|
107
|
+
# @param root [String] repository root to search under
|
|
108
|
+
# @return [Array<Member>] matching members
|
|
109
|
+
# @raise [ArgumentError] if `kinds` names a kind not in `KINDS`
|
|
91
110
|
def members(*kinds, root: ROOT)
|
|
92
111
|
kinds = KINDS if kinds.empty?
|
|
93
112
|
kinds.flat_map do |kind|
|
|
@@ -107,6 +126,10 @@ module Hecks
|
|
|
107
126
|
# What a boot loads for a member: a directory kind's bluebook
|
|
108
127
|
# directory, a file kind's own file. `nil` for a directory holding no
|
|
109
128
|
# bluebook at all.
|
|
129
|
+
#
|
|
130
|
+
# @param member [Member] the corpus member
|
|
131
|
+
# @return [String, nil] the path to boot, or nil when a directory member holds
|
|
132
|
+
# no bluebook
|
|
110
133
|
def source_of(member)
|
|
111
134
|
DIRECTORY_KINDS.key?(member.kind) ? bluebook_dir(member.path) : member.path
|
|
112
135
|
end
|
|
@@ -114,6 +137,9 @@ module Hecks
|
|
|
114
137
|
# Where a domain path keeps its bluebooks — `<domain>/bluebook/*.bluebook`
|
|
115
138
|
# (every example and stress domain), or the directory itself
|
|
116
139
|
# (`qa/bluebook`). `nil` when neither holds a bluebook.
|
|
140
|
+
#
|
|
141
|
+
# @param domain_path [String] path to a domain directory
|
|
142
|
+
# @return [Array<String>, nil] `.bluebook` file paths found, or nil when none
|
|
117
143
|
def bluebook_files(domain_path)
|
|
118
144
|
[File.join(domain_path, "bluebook"), domain_path].each do |dir|
|
|
119
145
|
files = Dir[File.join(dir, "*.bluebook")]
|
|
@@ -122,6 +148,11 @@ module Hecks
|
|
|
122
148
|
nil
|
|
123
149
|
end
|
|
124
150
|
|
|
151
|
+
# The directory holding `domain_path`'s bluebooks.
|
|
152
|
+
#
|
|
153
|
+
# @param domain_path [String] path to a domain directory
|
|
154
|
+
# @return [String, nil] directory of the first `.bluebook` file found, or nil
|
|
155
|
+
# when `domain_path` holds none
|
|
125
156
|
def bluebook_dir(domain_path)
|
|
126
157
|
files = bluebook_files(domain_path)
|
|
127
158
|
files && File.dirname(files.first)
|
|
@@ -129,6 +160,9 @@ module Hecks
|
|
|
129
160
|
|
|
130
161
|
# The route a repo-relative path takes instead of the sweep — the
|
|
131
162
|
# first one it matches — or `nil` when the sweep boots it.
|
|
163
|
+
#
|
|
164
|
+
# @param relative_path [String] a corpus member's path, relative to the repo root
|
|
165
|
+
# @return [Route, nil] the first matching route, or nil when the sweep boots it
|
|
132
166
|
def route_for(relative_path)
|
|
133
167
|
ROUTES.find { |route| route.pattern.match?(relative_path) }
|
|
134
168
|
end
|
|
@@ -139,8 +173,12 @@ module Hecks
|
|
|
139
173
|
# member a route already sends to a destination of its own: the
|
|
140
174
|
# broken-on-purpose model_check fixtures must produce their findings
|
|
141
175
|
# there, so a clean-corpus gate here would be the wrong check for them.
|
|
142
|
-
MODEL_CHECK_KINDS = %i[example grammar framework qa stress fixture].freeze
|
|
176
|
+
MODEL_CHECK_KINDS = %i[example grammar framework vendored qa stress fixture].freeze
|
|
143
177
|
|
|
178
|
+
# Every corpus member `bin/model_check` and `spec/model_check_spec.rb` walk.
|
|
179
|
+
#
|
|
180
|
+
# @param root [String] repository root to search under
|
|
181
|
+
# @return [Array<Member>] members of `MODEL_CHECK_KINDS`, less any routed elsewhere
|
|
144
182
|
def model_check_members(root: ROOT)
|
|
145
183
|
members(*MODEL_CHECK_KINDS, root: root).reject { |member| route_for(member.path.delete_prefix("#{root}/")) }
|
|
146
184
|
end
|
|
@@ -162,6 +200,10 @@ module Hecks
|
|
|
162
200
|
# puts a domain in the rotation and nothing tied that list to the
|
|
163
201
|
# corpus. Promotion only ever printed the `target.identify` line for a
|
|
164
202
|
# human to run.
|
|
203
|
+
#
|
|
204
|
+
# @param root [String] repository root to search under
|
|
205
|
+
# @return [Hash{String => String}] each rotation member's stem/reference mapped
|
|
206
|
+
# to its repo-relative path
|
|
165
207
|
def rotation_targets(root: ROOT)
|
|
166
208
|
members(:example, :stress, root: root)
|
|
167
209
|
.to_h { |member| [member.stem, member.path.delete_prefix("#{root}/")] }
|
|
@@ -171,6 +213,9 @@ module Hecks
|
|
|
171
213
|
# Every bootable domain in the project, not a hand-kept list — any
|
|
172
214
|
# directory holding a `.bluebook` no route sends elsewhere, a
|
|
173
215
|
# `bluebook/` folder standing for the domain directory around it.
|
|
216
|
+
#
|
|
217
|
+
# @param root [String] repository root to search under
|
|
218
|
+
# @return [Array<String>] absolute paths of every sweepable domain directory
|
|
174
219
|
def sweepable_domains(root = ROOT)
|
|
175
220
|
Dir.chdir(root) do
|
|
176
221
|
Dir.glob("**/*.bluebook")
|
|
@@ -184,6 +229,9 @@ module Hecks
|
|
|
184
229
|
|
|
185
230
|
# The domain directory a member stands for, spelled the way
|
|
186
231
|
# `sweepable_domains` spells it: a `bluebook/` folder is its parent.
|
|
232
|
+
#
|
|
233
|
+
# @param member [Member] the corpus member
|
|
234
|
+
# @return [String] the member's owning domain directory path
|
|
187
235
|
def domain_dir_of(member)
|
|
188
236
|
dir = File.directory?(member.path) ? member.path : File.dirname(member.path)
|
|
189
237
|
File.basename(dir) == "bluebook" ? File.dirname(dir) : dir
|
|
@@ -192,10 +240,10 @@ module Hecks
|
|
|
192
240
|
# ── The Rust-facing corpus ─────────────────────────────────────────
|
|
193
241
|
#
|
|
194
242
|
# Every Rust-facing list (the fuzz bridge, the codegen drift check,
|
|
195
|
-
# rust coverage, codegen parity)
|
|
196
|
-
#
|
|
197
|
-
#
|
|
198
|
-
# `rust/src/generated/` modules split into buckets, and
|
|
243
|
+
# rust coverage, codegen parity) reads `rust/Cargo.toml`'s `[features]`
|
|
244
|
+
# as its one source, rather than being typed out by hand per consumer
|
|
245
|
+
# and drifting from it — hand-typed, fuzzing once saw only 8 of 20
|
|
246
|
+
# features. The `rust/src/generated/` modules split into buckets, and
|
|
199
247
|
# spec/corpus_rust_spec.rb proves every Cargo feature and every
|
|
200
248
|
# generated module lands in exactly one of them:
|
|
201
249
|
#
|
|
@@ -232,15 +280,27 @@ module Hecks
|
|
|
232
280
|
# it is deleted here.
|
|
233
281
|
RUST_COVERAGE_PENDING = {}.freeze
|
|
234
282
|
|
|
283
|
+
# The Cargo `[features]` table's raw text.
|
|
284
|
+
#
|
|
285
|
+
# @param root [String] repository root to search under
|
|
286
|
+
# @return [String] the table's text, or `""` when `rust/Cargo.toml` has none
|
|
235
287
|
def cargo_features_table(root: ROOT)
|
|
236
288
|
File.read(File.join(root, "rust/Cargo.toml"))[Fuzzing::TargetCapabilities::FEATURES_TABLE] || ""
|
|
237
289
|
end
|
|
238
290
|
|
|
291
|
+
# Every feature name Cargo declares with no dependency array of its own.
|
|
292
|
+
#
|
|
293
|
+
# @param root [String] repository root to search under
|
|
294
|
+
# @return [Array<String>] feature names, in file order
|
|
239
295
|
def cargo_features(root: ROOT)
|
|
240
296
|
cargo_features_table(root: root).scan(/^(\w+)\s*=\s*\[\]/).flatten
|
|
241
297
|
end
|
|
242
298
|
|
|
243
299
|
# The feature `bin/project_rust` last wrote as Cargo's `default`.
|
|
300
|
+
#
|
|
301
|
+
# @param root [String] repository root to search under
|
|
302
|
+
# @return [String, nil] the default feature's name, or nil when Cargo.toml
|
|
303
|
+
# declares none
|
|
244
304
|
def cargo_default(root: ROOT)
|
|
245
305
|
cargo_features_table(root: root)[/^default\s*=\s*\["(\w+)"\]/, 1]
|
|
246
306
|
end
|
|
@@ -250,6 +310,9 @@ module Hecks
|
|
|
250
310
|
# decides which directory it came from — a directory name alone is not
|
|
251
311
|
# enough: spec/fixtures/qa_discover_external_domains vendors a second
|
|
252
312
|
# `examples/pizzas` that no Cargo feature was ever generated from.
|
|
313
|
+
#
|
|
314
|
+
# @param root [String] repository root to search under
|
|
315
|
+
# @return [Array<RustDomain>] each Rust-facing domain, sorted by directory path
|
|
253
316
|
def rust_domains(root: ROOT)
|
|
254
317
|
features = cargo_features(root: root)
|
|
255
318
|
members(*RUST_DOMAIN_KINDS, root: root)
|
|
@@ -260,6 +323,12 @@ module Hecks
|
|
|
260
323
|
.map { |dir, kind| RustDomain.new(File.basename(dir).downcase, dir, kind) }
|
|
261
324
|
end
|
|
262
325
|
|
|
326
|
+
# Tells whether `dir` is the real source directory for its Cargo feature.
|
|
327
|
+
#
|
|
328
|
+
# @param dir [String] a candidate domain directory
|
|
329
|
+
# @param features [Array<String>] known Cargo feature names
|
|
330
|
+
# @param root [String] repository root `dir` is rooted under
|
|
331
|
+
# @return [Boolean]
|
|
263
332
|
def rust_domain_dir?(dir, features, root)
|
|
264
333
|
feature = File.basename(dir).downcase
|
|
265
334
|
source = generated_source(feature, root: root)
|
|
@@ -269,36 +338,83 @@ module Hecks
|
|
|
269
338
|
# Where a generated module came from, read off the stamp bin/project_rust
|
|
270
339
|
# writes into its metadata.rs — `examples/pizzas`, `/abs/path/embryonaut`,
|
|
271
340
|
# `the self-hosted language (lib/hecks/language/bluebook)`, with any
|
|
272
|
-
# ` (uses_framework "X")`
|
|
273
|
-
|
|
274
|
-
|
|
341
|
+
# ` (uses_framework "X")` or ` (uses_embryonaut_bluebook "X")` suffix
|
|
342
|
+
# dropped. `nil` when not generated.
|
|
343
|
+
SOURCE_STAMP = %r{
|
|
344
|
+
GENERATED\ by\ bin/project_rust\ —\ (.+?)
|
|
345
|
+
(?:\ \((?:uses_framework|uses_embryonaut_bluebook)\ "\w+"\))?
|
|
346
|
+
's\ own\ canonical\ IR,
|
|
347
|
+
}x
|
|
348
|
+
|
|
349
|
+
# Where a generated module came from, read off the stamp `bin/project_rust`
|
|
350
|
+
# writes into its metadata.rs.
|
|
351
|
+
#
|
|
352
|
+
# @param module_name [String] a generated module's name
|
|
353
|
+
# @param root [String] repository root to search under
|
|
354
|
+
# @return [String, nil] the source path the stamp names, or nil when the module
|
|
355
|
+
# is not generated
|
|
275
356
|
def generated_source(module_name, root: ROOT)
|
|
276
357
|
metadata = File.join(root, GENERATED_DIR, module_name, "metadata.rs")
|
|
277
358
|
File.file?(metadata) ? File.read(metadata)[SOURCE_STAMP, 1] : nil
|
|
278
359
|
end
|
|
279
360
|
|
|
361
|
+
# Every module directory under `rust/src/generated`.
|
|
362
|
+
#
|
|
363
|
+
# @param root [String] repository root to search under
|
|
364
|
+
# @return [Array<String>] generated module names, sorted
|
|
280
365
|
def generated_modules(root: ROOT)
|
|
281
366
|
Dir.children(File.join(root, GENERATED_DIR)).select { |name| File.directory?(File.join(root, GENERATED_DIR, name)) }.sort
|
|
282
367
|
end
|
|
283
368
|
|
|
369
|
+
# Tells whether `feature` has already been generated (has a merged.rs).
|
|
370
|
+
#
|
|
371
|
+
# @param feature [String] a Cargo feature / domain name
|
|
372
|
+
# @param root [String] repository root to search under
|
|
373
|
+
# @return [Boolean]
|
|
284
374
|
def generated?(feature, root: ROOT)
|
|
285
375
|
File.file?(File.join(root, GENERATED_DIR, feature, "merged.rs"))
|
|
286
376
|
end
|
|
287
377
|
|
|
288
|
-
#
|
|
289
|
-
#
|
|
290
|
-
|
|
378
|
+
# Chapters attached via `kind` (`:framework` or `:vendored`) that
|
|
379
|
+
# `bin/project_rust` writes a module for as a side effect of the
|
|
380
|
+
# attaching domain — a module, no merged.rs of its own. Both kinds
|
|
381
|
+
# share this shape: `bin/project_rust`'s own generated module name is
|
|
382
|
+
# always `chapter_name.downcase` (rust/src/generated/mod.rs's own
|
|
383
|
+
# convention), and for either kind that downcased name is always the
|
|
384
|
+
# original lowercase directory stem again — confirmed live for a
|
|
385
|
+
# framework member and for a vendored package alike (a vendored
|
|
386
|
+
# package's own bluebook stem doesn't have to equal its declared
|
|
387
|
+
# chapter name the way a framework member's does — `EmbryonautBluebook.
|
|
388
|
+
# load!` resolves it as `Naming.pascal(dir_name)`, e.g. "widgets" ->
|
|
389
|
+
# "Widgets" — but downcasing a `Naming.pascal`-derived name always
|
|
390
|
+
# returns the original lowercase stem again).
|
|
391
|
+
#
|
|
392
|
+
# @param kind [Symbol] :framework or :vendored
|
|
393
|
+
# @param root [String] repository root to search under
|
|
394
|
+
# @return [Array<String>] stems with a generated module but no merged.rs
|
|
395
|
+
# of their own
|
|
396
|
+
def rust_side_chapters(kind, root: ROOT)
|
|
291
397
|
modules = generated_modules(root: root)
|
|
292
|
-
members(
|
|
293
|
-
|
|
398
|
+
members(kind, root: root).map(&:stem)
|
|
399
|
+
.select { |stem| modules.include?(stem) && !generated?(stem, root: root) }
|
|
294
400
|
end
|
|
295
401
|
|
|
296
|
-
|
|
402
|
+
def rust_framework_chapters(root: ROOT)
|
|
403
|
+
rust_side_chapters(:framework, root: root)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def rust_vendored_chapters(root: ROOT)
|
|
407
|
+
rust_side_chapters(:vendored, root: root)
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# THE REGENERATION ORDER the drift check runs. Sorted by path, so
|
|
297
411
|
# which domain runs last — and so wins Cargo's `default`, mod.rs's cfg
|
|
298
412
|
# comments and the shared framework modules' attribution stamp — is a
|
|
299
|
-
# fact of the sorted list, not a hand-picked order
|
|
300
|
-
#
|
|
301
|
-
#
|
|
413
|
+
# fact of the sorted list, not a hand-picked order: today that is
|
|
414
|
+
# `has_many_fixture`. spec/corpus_rust_spec.rb pins last == default.
|
|
415
|
+
#
|
|
416
|
+
# @param root [String] repository root to search under
|
|
417
|
+
# @return [Array<RustDomain>] already-generated Rust domains, in regeneration order
|
|
302
418
|
def rust_regen_order(root: ROOT)
|
|
303
419
|
rust_domains(root: root).select { |domain| generated?(domain.feature, root: root) }
|
|
304
420
|
end
|
|
@@ -308,6 +424,11 @@ module Hecks
|
|
|
308
424
|
# file is named after its role, `aggregate.bluebook`, while its
|
|
309
425
|
# chapter is always "Bluebook"). Scans the whole file: a framework
|
|
310
426
|
# member's header comment can run past any fixed line cap.
|
|
427
|
+
#
|
|
428
|
+
# @param bluebook_path [String, Array<String>] a `.bluebook` file path, or an
|
|
429
|
+
# array whose first element is used
|
|
430
|
+
# @return [String, nil] the declared chapter name, or nil when the file never
|
|
431
|
+
# declares one
|
|
311
432
|
def chapter_name_of(bluebook_path)
|
|
312
433
|
bluebook_path = Array(bluebook_path).first
|
|
313
434
|
header = File.foreach(bluebook_path).find { |line| line =~ /\A\s*Hecks\.bluebook\s+"([^"]+)"/ }
|
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
# The Deploy domain, bound so it boots standing alone.
|
|
2
2
|
Hecks.hecksagon "Deploy" do
|
|
3
3
|
persisted_by "Memory"
|
|
4
|
+
|
|
5
|
+
# THE IMPERATIVE HALF, BEHIND A REAL PORT — `bin/project_tenant`
|
|
6
|
+
# dispatches this `asks` once `Tenant.Declare` has already validated
|
|
7
|
+
# the request; `TenantProvisioner` (lib/hecks/adapters/driven/
|
|
8
|
+
# tenant_provisioner.rb) writes the overlay file and answers or
|
|
9
|
+
# refuses. `database`/`adapter`/`directory` are declared here, not on
|
|
10
|
+
# `Tenant` itself — CLI-only facts this validated request never
|
|
11
|
+
# needed to hold as its own state. `directory` is the real
|
|
12
|
+
# filesystem path being provisioned — NOT re-derivable from `Tenant`'s
|
|
13
|
+
# own `domain` (its declared NAME, e.g. "Scratch", never a path).
|
|
14
|
+
Deploy::Tenant.port "TenantProvisioning" do
|
|
15
|
+
asks "Provision", to: Tenant do
|
|
16
|
+
attribute :database, DatabaseName
|
|
17
|
+
attribute :adapter, AdapterName
|
|
18
|
+
attribute :directory, DirectoryPath
|
|
19
|
+
answers "TenantProvisioned"
|
|
20
|
+
refuses "ProvisioningRefused"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
4
23
|
end
|