hecks 3.10.0 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
- data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
- data/exe/hecks +4 -4
- data/lib/hecks/adapters/driven/d1.rb +2 -10
- data/lib/hecks/adapters/driven/heki.adapter +1 -1
- data/lib/hecks/adapters/driven/heki.rb +2 -12
- data/lib/hecks/adapters/driven/lambda.adapter +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +3 -18
- data/lib/hecks/adapters/driven/postgres.rb +4 -11
- data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
- data/lib/hecks/adapters/driven/sqlite.rb +2 -10
- data/lib/hecks/adapters/driving/admission.rb +46 -0
- data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
- data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
- data/lib/hecks/adapters/driving/cli.rb +127 -0
- data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +155 -0
- data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
- data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
- data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +69 -0
- data/lib/hecks/adapters/driving/command_request.rb +121 -0
- data/lib/hecks/adapters/driving/handle.rb +214 -0
- data/lib/hecks/adapters/driving/json.rb +136 -0
- data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
- data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
- data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
- data/lib/hecks/adapters/driving/mcp.rb +159 -0
- data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
- data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
- data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
- data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
- data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
- data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
- data/lib/hecks/adapters/driving/ruby.rb +83 -0
- data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
- data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
- data/lib/hecks/behaviors/expectations/boot.rb +1 -1
- data/lib/hecks/bench/ruby_runner.rb +1 -1
- data/lib/hecks/bluebook/ask_resolution.rb +130 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
- data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
- data/lib/hecks/bluebook/assembly/marks.rb +1 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/command.rb +13 -8
- data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
- data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
- data/lib/hecks/bluebook/capabilities.rb +91 -9
- data/lib/hecks/bluebook/domain_port.rb +16 -4
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
- data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
- data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
- data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
- data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
- data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
- data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
- data/lib/hecks/bluebook/expression/projection.json +36 -0
- data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
- data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
- data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
- data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
- data/lib/hecks/bluebook/expression/resolver.rb +20 -1
- data/lib/hecks/bluebook/hecksagon.rb +23 -3
- data/lib/hecks/bluebook/lifecycle.rb +22 -3
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
- data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/readings.rb +18 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
- data/lib/hecks/bluebook/meta_validator.rb +12 -12
- data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
- data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
- data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
- data/lib/hecks/bluebook/model_check.rb +8 -2
- data/lib/hecks/bluebook/policy.rb +4 -2
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- data/lib/hecks/bluebook/synthesizer.rb +2 -0
- data/lib/hecks/bluebook.rb +1 -0
- data/lib/hecks/cli/command_table.rb +1 -1
- data/lib/hecks/cli/console.rb +2 -1
- data/lib/hecks/cli/document.rb +1 -1
- data/lib/hecks/cli/launcher_words.rb +4 -4
- data/lib/hecks/cli/mcp.rb +4 -4
- data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
- data/lib/hecks/cli/project_cli/templates.rb +5 -5
- data/lib/hecks/cli/project_cli.rb +9 -6
- data/lib/hecks/cli/run.rb +2 -2
- data/lib/hecks/cli/smoke_http/checks.rb +4 -1
- data/lib/hecks/cli/smoke_http/transport.rb +3 -3
- data/lib/hecks/cli/smoke_http.rb +3 -0
- data/lib/hecks/codemod.rb +5 -2
- data/lib/hecks/corpus/routes.rb +7 -0
- data/lib/hecks/corpus/rust.rb +3 -2
- data/lib/hecks/deploy/bluebook/deploy.bluebook +18 -2
- data/lib/hecks/doc/reference/rendering.rb +6 -3
- data/lib/hecks/forms/app/commands.rb +8 -7
- data/lib/hecks/forms/app/queries.rb +2 -1
- data/lib/hecks/forms/app.rb +1 -1
- data/lib/hecks/forms/record_renderer.rb +1 -1
- data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
- data/lib/hecks/fuzzing/failure_triage.rb +2 -1
- data/lib/hecks/fuzzing/mutation/run.rb +11 -4
- data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
- data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
- data/lib/hecks/fuzzing/properties/guards.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency.rb +6 -2
- data/lib/hecks/fuzzing/shrinker.rb +3 -1
- data/lib/hecks/fuzzing/value_generator.rb +19 -5
- data/lib/hecks/gate/stages.yml +4 -10
- data/lib/hecks/grammar/evolve.rb +1 -1
- data/lib/hecks/grammar/expression_operators.json +372 -0
- data/lib/hecks/grammar/operators.rb +3 -1
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
- data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
- data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
- data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
- data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
- data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
- data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
- data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
- data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
- data/lib/hecks/hecks/adapters/local_files.rb +2 -2
- data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
- data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
- data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
- data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +129 -0
- data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
- data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
- data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
- data/lib/hecks/hecks/adapters/terminal.rb +13 -11
- data/lib/hecks/hecks/codebase.behaviors +13 -1
- data/lib/hecks/hecks/codebase.bluebook +239 -5
- data/lib/hecks/hecks/custodian.behaviors +24 -6
- data/lib/hecks/hecks/custodian.bluebook +6 -1227
- data/lib/hecks/hecks/deploy.behaviors +1 -1
- data/lib/hecks/hecks/deploy.world +1 -3
- data/lib/hecks/hecks/hecks.behaviors +1 -1
- data/lib/hecks/hecks/hecks.hecksagon +53 -18
- data/lib/hecks/hecks/hecks.world +10 -10
- data/lib/hecks/hecks/rehearsal.bluebook +477 -0
- data/lib/hecks/hecks/site.behaviors +1 -1
- data/lib/hecks/hecks/site.world +0 -1
- data/lib/hecks/hecks/tooling.bluebook +1238 -0
- data/lib/hecks/indifferent_key.rb +20 -0
- data/lib/hecks/language/adapter.bluebook +13 -3
- data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
- data/lib/hecks/language/bluebook/entity.bluebook +18 -0
- data/lib/hecks/language/bluebook/policy.bluebook +9 -1
- data/lib/hecks/language/bluebook/query.bluebook +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
- data/lib/hecks/language/oidc.json +10 -0
- data/lib/hecks/language/port.bluebook +4 -4
- data/lib/hecks/literal.rb +12 -3
- data/lib/hecks/ports/key_vault.rb +7 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
- data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/shared.rb +5 -2
- data/lib/hecks/projections/model/deviations.rb +2 -2
- data/lib/hecks/projections/model.rb +2 -2
- data/lib/hecks/projections/oidc.rb +13 -1
- data/lib/hecks/projections/shape.rb +5 -2
- data/lib/hecks/projections/site/cms_editor/attributes.rb +26 -6
- data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
- data/lib/hecks/projections/site/cms_editor/checks.rb +14 -1
- data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
- data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
- data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
- data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
- data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
- data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
- data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
- data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
- data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
- data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
- data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
- data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
- data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
- data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
- data/lib/hecks/projections/site/cms_editor/schema.rb +96 -19
- data/lib/hecks/projections/site/cms_editor/setting.rb +52 -9
- data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +18 -1
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +251 -57
- data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +32 -24
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +78 -24
- data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +6 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +19 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +31 -8
- data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +131 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +8 -5
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +229 -34
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +40 -81
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +29 -12
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +53 -17
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +178 -88
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
- data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +2 -1
- data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
- data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
- data/lib/hecks/projections/site/cms_editor.rb +49 -23
- data/lib/hecks/projections/site/role_probe.rb +2 -0
- data/lib/hecks/projector/cli_audience.rb +2 -1
- data/lib/hecks/projector/cli_projector/listing.rb +1 -1
- data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
- data/lib/hecks/projector/docs_projector/closing.rb +1 -1
- data/lib/hecks/projector/docs_projector.rb +1 -1
- data/lib/hecks/projector/exporter/bindings.rb +40 -10
- data/lib/hecks/projector/exporter/capability_values.rb +55 -0
- data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
- data/lib/hecks/projector/target.rb +11 -0
- data/lib/hecks/projector.rb +32 -5
- data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
- data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
- data/lib/hecks/quality_control/quality_control.bluebook +6 -2
- data/lib/hecks/release/approval.rb +64 -0
- data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
- data/lib/hecks/router/namespace_installer.rb +16 -16
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/best_effort.rb +22 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
- data/lib/hecks/runtime/command_interpreter.rb +3 -2
- data/lib/hecks/runtime/command_rules/emission.rb +1 -1
- data/lib/hecks/runtime/dispatcher.rb +4 -4
- data/lib/hecks/runtime/entity_element/location.rb +1 -1
- data/lib/hecks/runtime/event.rb +5 -0
- data/lib/hecks/runtime/identity.rb +7 -6
- data/lib/hecks/runtime/interpreting.rb +2 -1
- data/lib/hecks/runtime/invocation_builder.rb +1 -1
- data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
- data/lib/hecks/runtime/loader.rb +10 -10
- data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
- data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
- data/lib/hecks/runtime/policy_interpreter.rb +6 -6
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
- data/lib/hecks/runtime/reaction_invocation.rb +1 -1
- data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
- data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
- data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
- data/lib/hecks/runtime/registry/merging.rb +1 -1
- data/lib/hecks/runtime/registry/providers.rb +16 -0
- data/lib/hecks/runtime/registry/read_repository.rb +6 -4
- data/lib/hecks/runtime/registry/verification.rb +4 -1
- data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
- data/lib/hecks/runtime/registry.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
- data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter.rb +3 -3
- data/lib/hecks/runtime/tenant_check.rb +5 -4
- data/lib/hecks/runtime/value/coercion.rb +2 -2
- data/lib/hecks/runtime/value/validation.rb +16 -4
- data/lib/hecks/runtime.rb +9 -9
- data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
- data/lib/hecks/rust_build/wasm.rb +1 -1
- data/lib/hecks/rust_build/wasm_browser.rb +1 -1
- data/lib/hecks/rust_build.rb +11 -0
- data/lib/hecks/served_options.rb +53 -0
- data/lib/hecks/site/bluebook/site.bluebook +1 -0
- data/lib/hecks/storehouse/audit.rb +1 -1
- data/lib/hecks/storehouse/dispatching.rb +8 -6
- data/lib/hecks/storehouse/introspection.rb +2 -2
- data/lib/hecks/storehouse/querying.rb +6 -4
- data/lib/hecks/storehouse.rb +4 -4
- data/lib/hecks/tools/comment_style/source_file.rb +4 -1
- data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
- data/lib/hecks/tools/lanes/rows.rb +6 -0
- data/lib/hecks/tools/oidc_manifests.rb +4 -5
- data/lib/hecks/tools/regeneration_run.rb +11 -4
- data/lib/hecks/tools/site_routes/editor_files.rb +12 -5
- data/lib/hecks/tools/tenant_provisioning.rb +2 -2
- data/lib/hecks/tools/tools_doc.rb +4 -4
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +4 -4
- data/lib/hecks.rb +15 -15
- data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
- data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
- data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
- data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
- data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
- data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
- data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
- data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
- data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
- data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
- data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
- data/rust/.gitignore +6 -0
- data/rust/Cargo.toml +6 -0
- data/rust/build/src/binding_pass.rs +103 -0
- data/rust/build/src/cargo_sync.rs +6 -0
- data/rust/build/src/lineage_pass.rs +3 -3
- data/rust/build/src/main.rs +17 -5
- data/rust/build/src/pipeline.rs +48 -5
- data/rust/build/src/seams_pass.rs +123 -0
- data/rust/build/src/translation_pass.rs +167 -0
- data/rust/codegen/src/asks.rs +103 -0
- data/rust/codegen/src/commands.rs +13 -8
- data/rust/codegen/src/constraints.rs +25 -0
- data/rust/codegen/src/domain_generator.rs +2 -0
- data/rust/codegen/src/expr_emitter.rs +13 -0
- data/rust/codegen/src/json_codec.rs +54 -15
- data/rust/codegen/src/main.rs +1 -0
- data/rust/codegen/src/mutations.rs +128 -3
- data/rust/codegen/src/queries.rs +11 -2
- data/rust/codegen/src/reactions.rs +1 -1
- data/rust/codegen/src/types.rs +166 -8
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +4 -4
- data/rust/host/src/bin/validate_harness.rs +43 -0
- data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
- data/rust/host/src/boundary_fuzz/ir.rs +1 -0
- data/rust/host/src/boundary_fuzz/server.rs +1 -1
- data/rust/host/src/checkout.rs +35 -19
- data/rust/host/src/commerce_ir.rs +408 -2
- data/rust/host/src/expr_json.rs +118 -0
- data/rust/host/src/mint.rs +1 -1
- data/rust/host/src/payments/tests.rs +27 -1
- data/rust/host/src/payments.rs +12 -4
- data/rust/host/src/query_step.rs +22 -4
- data/rust/host/src/reference_validate.rs +653 -108
- data/rust/host/src/resend.rs +2 -0
- data/rust/host/src/server.rs +19 -6
- data/rust/host/src/ui_schema.rs +132 -5
- data/rust/host/src/web/newsletter.rs +7 -8
- data/rust/host/src/web/newsletter_send.rs +38 -13
- data/rust/host/src/web/registrations.rs +229 -26
- data/rust/host/src/web.rs +3 -3
- data/rust/parser/src/emit.rs +38 -6
- data/rust/parser/src/expr/ast_json.rs +7 -1
- data/rust/parser/src/expr/resolver.rs +171 -6
- data/rust/parser/src/ir.rs +7 -0
- data/rust/parser/src/keywords.rs +24 -0
- data/rust/parser/src/main.rs +3 -0
- data/rust/parser/src/parse/aggregate.rs +2 -1
- data/rust/parser/src/parse/chapter.rs +2 -2
- data/rust/parser/src/parse/command.rs +2 -0
- data/rust/parser/src/parse/hecksagon.rs +5 -2
- data/rust/parser/src/parse/lifecycle.rs +81 -0
- data/rust/parser/src/parse/mod.rs +15 -1
- data/rust/parser/src/parse/policy.rs +13 -2
- data/rust/parser/src/parse/value_object.rs +13 -8
- data/rust/parser/src/ruby_value.rs +1 -1
- data/rust/src/exemplar/commands.rs +4 -4
- data/rust/src/exemplar/constraints.rs +1 -1
- data/rust/src/exemplar/json.rs +43 -1
- data/rust/src/kernel/cli.rs +7 -5
- data/rust/src/kernel/dispatch.rs +2 -2
- data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
- data/rust/src/kernel/expr.rs +66 -3
- data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
- data/rust/src/kernel/expression_operators/text.rs +26 -2
- data/rust/src/kernel/json.rs +15 -0
- data/rust/src/kernel/mod.rs +1 -1
- data/rust/src/kernel/orchestrate.rs +54 -5
- data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
- data/rust/src/kernel/pattern.rs +60 -0
- data/rust/src/kernel/reference_lookup.rs +90 -7
- metadata +140 -38
- data/lib/hecks/doors/cli_door/nesting.rb +0 -34
- data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
- data/lib/hecks/doors/cli_door.rb +0 -124
- data/lib/hecks/doors/cli_runner/answers.rb +0 -138
- data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
- data/lib/hecks/doors/cli_runner/settling.rb +0 -115
- data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
- data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
- data/lib/hecks/doors/cli_runner.rb +0 -66
- data/lib/hecks/doors/command_request.rb +0 -119
- data/lib/hecks/doors/handle.rb +0 -208
- data/lib/hecks/doors/json_door.rb +0 -132
- data/lib/hecks/doors/launcher_options.rb +0 -247
- data/lib/hecks/doors/mcp_door/properties.rb +0 -47
- data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
- data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
- data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
- data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
- data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
- data/lib/hecks/doors/mcp_door.rb +0 -156
- data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
- data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
- data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
- data/lib/hecks/doors/mcp_door_scope.rb +0 -201
- data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
- data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
- data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
- data/lib/hecks/doors/ruby_door.rb +0 -80
- data/lib/hecks/doors/usage_cache.rb +0 -139
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_widget.js.tmpl +0 -282
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/media_picker.js.tmpl +0 -109
- data/prebuilt/c28d6d9db14eea3e17161e9d9c262efee30b256cf97693a7cfd552bce24b2f7f.marshal +0 -0
- data/prebuilt/cb375cba61ca0dbe8ddb9739c306510fe05e5f4b7a9b6955caff8224c63ea0e0.marshal +0 -0
- data/prebuilt/verdicts-d6d584ad43b5f3cebf88aafbbdbf024c38574acf18e06adffbc555f7786b1c2f.json +0 -1
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
# 40 the box stack has no instance
|
|
9
9
|
# 41 the roll did not succeed on the box
|
|
10
10
|
# 42 the roll succeeded but the box is not healthy after it
|
|
11
|
+
# 43 no restore anchor: the database's backups cannot be read, or are kept fewer days than
|
|
12
|
+
# MIN_BACKUP_RETENTION_DAYS (default 3)
|
|
13
|
+
#
|
|
14
|
+
# Before anything changes, the roll names the point the database can be restored to: the instance, the
|
|
15
|
+
# moment this roll starts, and how long its continuous backups are kept. A roll that mints a new era is
|
|
16
|
+
# a one-way door, so this is the rollback anchor, and it costs nothing (a point-in-time restore uses the
|
|
17
|
+
# backups the instance already keeps, where a manual snapshot would be stored until someone deleted it).
|
|
18
|
+
# SKIP_RESTORE_ANCHOR=1 skips it.
|
|
11
19
|
#
|
|
12
20
|
# Before the roll replaces a container, the box saves that container's log to
|
|
13
21
|
# /var/log/hecks-captures/<container>-<UTC timestamp>.log and keeps the newest 14 per container
|
|
@@ -30,6 +38,27 @@ DB_HOST=$(out "$RDS_STACK" DbEndpoint)
|
|
|
30
38
|
[ -n "$BOX" ] && [ "$BOX" != None ] || { echo "no instance in stack $BOX_STACK" >&2; exit 40; }
|
|
31
39
|
ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
|
|
32
40
|
|
|
41
|
+
if [ "${SKIP_RESTORE_ANCHOR:-}" = 1 ]; then
|
|
42
|
+
echo "==> skipping the restore anchor (SKIP_RESTORE_ANCHOR=1)"
|
|
43
|
+
else
|
|
44
|
+
DB_ID=${DB_HOST%%.*}
|
|
45
|
+
BACKUPS=$(aws rds describe-db-instances --db-instance-identifier "$DB_ID" \
|
|
46
|
+
--query 'DBInstances[0].[BackupRetentionPeriod,LatestRestorableTime]' --output text) || BACKUPS=
|
|
47
|
+
read -r KEPT_DAYS RESTORABLE <<< "$BACKUPS"
|
|
48
|
+
case "${KEPT_DAYS:-}" in ''|*[!0-9]*)
|
|
49
|
+
echo "==> no restore anchor: could not read the backups of $DB_ID (SKIP_RESTORE_ANCHOR=1 rolls without one)" >&2
|
|
50
|
+
exit 43 ;;
|
|
51
|
+
esac
|
|
52
|
+
if [ "$KEPT_DAYS" -lt "${MIN_BACKUP_RETENTION_DAYS:-3}" ]; then
|
|
53
|
+
echo "==> no restore anchor: $DB_ID keeps backups $KEPT_DAYS day(s), fewer than ${MIN_BACKUP_RETENTION_DAYS:-3}" >&2
|
|
54
|
+
exit 43
|
|
55
|
+
fi
|
|
56
|
+
ANCHOR_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
57
|
+
echo "==> restore anchor: $DB_ID restorable to $ANCHOR_AT (latest $RESTORABLE, backups kept $KEPT_DAYS days)"
|
|
58
|
+
echo " to return: aws rds restore-db-instance-to-point-in-time --source-db-instance-identifier $DB_ID \\"
|
|
59
|
+
echo " --target-db-instance-identifier $DB_ID-restore --restore-time $ANCHOR_AT"
|
|
60
|
+
fi
|
|
61
|
+
|
|
33
62
|
WORK=$(mktemp -d); trap 'rm -rf "$WORK"' EXIT
|
|
34
63
|
|
|
35
64
|
# SMOKE_LISTENER=1 (a rehearsal box only) mounts a loopback HTTPS site on 127.0.0.1:8443 that adds the
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
# 32 the box's Compose file is unreadable 33 the box stack has no instance
|
|
25
25
|
# 34 the stack has no parameter for the container 35 the stack update failed or did not settle
|
|
26
26
|
# 36 a parameter other than the container's changed 37 the task definition lacks the pushed image
|
|
27
|
-
# 38 the task definition has no such container 40-
|
|
27
|
+
# 38 the task definition has no such container 40-43 deploy-box.sh's own
|
|
28
28
|
#
|
|
29
29
|
# Environment:
|
|
30
30
|
# LOCAL_IMAGE local image to push, default <ecr repository>:latest
|
|
@@ -365,7 +365,7 @@ signup_admin() {
|
|
|
365
365
|
# Prints what to paste into the browser console to sign in as the preview's first
|
|
366
366
|
# admin: a session for that email signed with THIS preview's session secret (the
|
|
367
367
|
# secret itself is never printed). Anyone with AWS access to the account could
|
|
368
|
-
# mint the same, which is the point: it is the deployer's
|
|
368
|
+
# mint the same, which is the point: it is the deployer's entry, not a public one.
|
|
369
369
|
cmd_login() {
|
|
370
370
|
[ -n "$(stack_status)" ] || die "no preview for branch '$BRANCH' - run 'preview.sh deploy'"
|
|
371
371
|
admin_identity
|
|
@@ -7,6 +7,9 @@ module Hecks
|
|
|
7
7
|
# VPC, RDS/Aurora, bastion and cross-domain invoke plumbing shared by the
|
|
8
8
|
# `Lambda` and `Fargate` deploy targets. Plain module functions, not a Target.
|
|
9
9
|
module Shared
|
|
10
|
+
# Raised when a bastion parameter names a stack output that is not declared.
|
|
11
|
+
class Error < RuntimeError; end
|
|
12
|
+
|
|
10
13
|
# The names a domain's own VPC and database are rendered by.
|
|
11
14
|
#
|
|
12
15
|
# @!attribute [r] shared [Boolean] whether this domain borrows another domain's RDS instance
|
|
@@ -66,11 +69,11 @@ module Hecks
|
|
|
66
69
|
|
|
67
70
|
# Generation-time check that every bastion parameter maps to a stack output.
|
|
68
71
|
#
|
|
69
|
-
# @raise [
|
|
72
|
+
# @raise [Error] if a parameter names a `from_output` absent from `stack_outputs`
|
|
70
73
|
def check_bastion_parameters!(bastion_parameters, stack_outputs)
|
|
71
74
|
bastion_parameters.each do |param|
|
|
72
75
|
stack_outputs.any? { |o| o[:key] == param[:from_output] } or
|
|
73
|
-
raise "bastion parameter #{param[:name]} references undeclared stack output #{param[:from_output]}"
|
|
76
|
+
raise Error, "bastion parameter #{param[:name]} references undeclared stack output #{param[:from_output]}"
|
|
74
77
|
end
|
|
75
78
|
end
|
|
76
79
|
|
|
@@ -17,8 +17,8 @@ module Hecks
|
|
|
17
17
|
|
|
18
18
|
# One model field gathered from several declared ones.
|
|
19
19
|
FOLDED = {
|
|
20
|
-
"Aggregate" => { lifecycle: %i[state_field state_start transitions] },
|
|
21
|
-
"Entity" => { lifecycle: %i[state_field state_start transitions] },
|
|
20
|
+
"Aggregate" => { lifecycle: %i[state_field state_start transitions marks] },
|
|
21
|
+
"Entity" => { lifecycle: %i[state_field state_start transitions marks] },
|
|
22
22
|
"Query" => { order_by: %i[order_field order_way] }
|
|
23
23
|
}.freeze
|
|
24
24
|
|
|
@@ -16,10 +16,10 @@ module Hecks
|
|
|
16
16
|
construct: "Policy",
|
|
17
17
|
file: "policy.rb",
|
|
18
18
|
behaviour: "Behaviour::Policy",
|
|
19
|
-
readers: %i[name on_event trigger_command target_domain expect_undelivered where for_each with_spec],
|
|
19
|
+
readers: %i[name on_event trigger_command ask target_domain expect_undelivered where for_each with_spec],
|
|
20
20
|
accessors: %i[aggregate],
|
|
21
21
|
defaults: { name: nil, on_event: "nil", trigger_command: "nil",
|
|
22
|
-
target_domain: "nil", expect_undelivered: "false", where: "nil", for_each: "nil",
|
|
22
|
+
ask: "nil", target_domain: "nil", expect_undelivered: "false", where: "nil", for_each: "nil",
|
|
23
23
|
with_spec: "[]", aggregate: "nil" },
|
|
24
24
|
# The language holds a flag as "true" and the builder as `true`; both become a boolean.
|
|
25
25
|
coerce: { name: ".to_s", aggregate: "&.to_s", expect_undelivered: ".to_s == \"true\"" },
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require "json"
|
|
1
2
|
require_relative "../naming"
|
|
2
3
|
require_relative "../projector"
|
|
3
4
|
|
|
@@ -35,6 +36,17 @@ module Hecks
|
|
|
35
36
|
}
|
|
36
37
|
end
|
|
37
38
|
|
|
39
|
+
# The manifest as the text `oidc.json` holds: indented JSON ending in a newline, with an empty
|
|
40
|
+
# list as `[]`. `JSON.pretty_generate` spells an empty list `[\n\n]` in some json releases
|
|
41
|
+
# (2.7.2 among them) and `[]` in others, so the file is normalised here and a committed
|
|
42
|
+
# manifest compares equal whichever json a machine has.
|
|
43
|
+
#
|
|
44
|
+
# @param manifest [Hash{String => Object}] what `call` returns
|
|
45
|
+
# @return [String] the text of the manifest file
|
|
46
|
+
def render(manifest)
|
|
47
|
+
"#{JSON.pretty_generate(manifest).gsub(/\[\n\s*\]/, "[]")}\n"
|
|
48
|
+
end
|
|
49
|
+
|
|
38
50
|
# Every command's scope entry, entity commands included (they dispatch as `Entity.Command`).
|
|
39
51
|
# Sorted by scope so two manifest versions diff cleanly.
|
|
40
52
|
#
|
|
@@ -79,7 +91,7 @@ module Hecks
|
|
|
79
91
|
end
|
|
80
92
|
|
|
81
93
|
# One scope entry per command, spelled like `banking:account.open`; snake-cased through
|
|
82
|
-
# `Naming.snake`, as the facade names its
|
|
94
|
+
# `Naming.snake`, as the facade names its module methods.
|
|
83
95
|
#
|
|
84
96
|
# @param commands [Array<Bluebook::Command>] the commands to project
|
|
85
97
|
# @param verb_prefix [String] the owning aggregate or entity's fully-qualified
|
|
@@ -6,6 +6,9 @@ module Hecks
|
|
|
6
6
|
# The storage shape of one bluebook, the form `StorageShape.mint_hash` hashes to name an era.
|
|
7
7
|
# Diffing two of these answers whether a change bumps the era.
|
|
8
8
|
module Shape
|
|
9
|
+
# Raised when the shape projection is asked for without the era plugin loaded.
|
|
10
|
+
class Error < RuntimeError; end
|
|
11
|
+
|
|
9
12
|
extend Projector::Target
|
|
10
13
|
|
|
11
14
|
projects_as :shape
|
|
@@ -15,8 +18,8 @@ module Hecks
|
|
|
15
18
|
# Registered even when the era plugin is unloaded; the call then refuses clearly.
|
|
16
19
|
def call(bluebook:, options: {})
|
|
17
20
|
unless Ports::Persistence.plugin?(:era)
|
|
18
|
-
raise "the :shape projection needs the era persistence plugin loaded " \
|
|
19
|
-
|
|
21
|
+
raise Error, "the :shape projection needs the era persistence plugin loaded " \
|
|
22
|
+
"(require \"hecks/ports/persistence/plugins/era\")"
|
|
20
23
|
end
|
|
21
24
|
|
|
22
25
|
Runtime::StorageShape.project(bluebook)
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "blocks"
|
|
4
|
+
require_relative "hints"
|
|
5
|
+
|
|
3
6
|
module Hecks
|
|
4
7
|
module Projections
|
|
5
8
|
module Site
|
|
@@ -9,11 +12,8 @@ module Hecks
|
|
|
9
12
|
#
|
|
10
13
|
# A type that names one of the aggregate's value objects is an `object`; a reference is a
|
|
11
14
|
# `reference`; the primitives are `integer`, `number`, `boolean` and `text`, and any other
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# An attribute whose value object has the shape of a structured document, a `blocks`
|
|
15
|
-
# list of a value object that has `kind` and `spans`, carries `widget: "body"` and is
|
|
16
|
-
# edited with the rich-text widget. The shape decides it, never a name.
|
|
15
|
+
# is text. The shape decides what more it is, never a name: a body (`widget: "body"`), a
|
|
16
|
+
# block list (see `Blocks`), a moment, a `<kind>:` key (see `Hints`) or a picture.
|
|
17
17
|
class Attributes
|
|
18
18
|
# The parts a value object needs for its list of blocks to be a document body's blocks.
|
|
19
19
|
BLOCK_PARTS = %w[kind spans].freeze
|
|
@@ -26,6 +26,7 @@ module Hecks
|
|
|
26
26
|
def initialize(objects)
|
|
27
27
|
@by_name = objects.to_h { |object| [object.hecks_name, object] }
|
|
28
28
|
@objects = @by_name.keys
|
|
29
|
+
@hints = Hints.new(@by_name)
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
# @param attribute [Bluebook::Attribute] a declared attribute
|
|
@@ -35,11 +36,30 @@ module Hecks
|
|
|
35
36
|
return reference(attribute, type.target_name) if type.is_a?(Bluebook::Reference)
|
|
36
37
|
|
|
37
38
|
entry = base(attribute, type.to_s, kind(type.to_s))
|
|
38
|
-
|
|
39
|
+
return entry.merge("widget" => "body") if body?(attribute)
|
|
40
|
+
|
|
41
|
+
entry.merge(@hints.of(attribute), listing(attribute), pictured(attribute))
|
|
39
42
|
end
|
|
40
43
|
|
|
41
44
|
private
|
|
42
45
|
|
|
46
|
+
# @return [Hash{String => Object}] `blockList` when the attribute is a list of blocks
|
|
47
|
+
def listing(attribute)
|
|
48
|
+
found = Blocks.of(attribute, @by_name)
|
|
49
|
+
found ? { "blockList" => found } : {}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [Hash{String => Object}] `picture` (the parts that name the file, its
|
|
53
|
+
# description and its caption) when the value object is a picture: it has a media key
|
|
54
|
+
# and an `alt` and is not a block of a body or a list of mixed blocks
|
|
55
|
+
def pictured(attribute)
|
|
56
|
+
object = @by_name[attribute.type.to_s]
|
|
57
|
+
return {} unless object && !Blocks.body_block?(object) && !listing(attribute).key?("blockList")
|
|
58
|
+
|
|
59
|
+
found = Blocks.picture(object)
|
|
60
|
+
found&.key?("key") && found.key?("alt") ? { "picture" => found } : {}
|
|
61
|
+
end
|
|
62
|
+
|
|
43
63
|
# A single (not listed) attribute whose value object holds a list of document blocks.
|
|
44
64
|
def body?(attribute)
|
|
45
65
|
return false if attribute.list?
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Site
|
|
6
|
+
module CmsEditor
|
|
7
|
+
# A list of mixed blocks, found by shape: a `list_of` value object that has a discriminator,
|
|
8
|
+
# a part that is one of a closed set of kinds. The editor draws it as ordered cards.
|
|
9
|
+
#
|
|
10
|
+
# The discriminator is a part whose type is a closed set (`one_of`) of one plain value, or,
|
|
11
|
+
# failing that, a text part that an invariant restricts to a literal list
|
|
12
|
+
# (`["a", "b"].include?(kind)`). A value object that holds `spans` is a body's block, which
|
|
13
|
+
# the rich-text widget edits, and is never a block list.
|
|
14
|
+
#
|
|
15
|
+
# Which slots each kind uses is read from a declared table: a closed set of rows with the
|
|
16
|
+
# parts `kind`, `requires` and `uses` whose kinds are among the discriminator's. With no
|
|
17
|
+
# such table every slot is shown, and the invariants that name a kind only hint which slots
|
|
18
|
+
# it reads. A limit is read from an invariant or given of the form `<list>.size <= <n>`.
|
|
19
|
+
module Blocks
|
|
20
|
+
# The parts a table of rules must have.
|
|
21
|
+
RULE_PARTS = %w[kind requires uses].freeze
|
|
22
|
+
|
|
23
|
+
# The parts that make a value object's picture, in the vocabulary of a body's image block.
|
|
24
|
+
PICTURE = { "key" => "media_ref", "alt" => "alt", "caption" => "caption" }.freeze
|
|
25
|
+
|
|
26
|
+
# An invariant that restricts a text part to a literal list of kinds.
|
|
27
|
+
LITERALS = /\A\[((?:"[^"]+"(?:, )?)+)\]\.include\?\((\w+)\)\z/
|
|
28
|
+
|
|
29
|
+
# An invariant that holds unless the kind is the named one: what the rest of it reads.
|
|
30
|
+
GUARD = /\A(\w+)(?:\.value)? != "([^"]+)" \|\| (.*)\z/m
|
|
31
|
+
|
|
32
|
+
# A limit written as a size comparison.
|
|
33
|
+
LIMIT = /\A(\w+)\.size <= (\d+)\z/
|
|
34
|
+
|
|
35
|
+
module_function
|
|
36
|
+
|
|
37
|
+
# @param attribute [Bluebook::Attribute] a declared attribute
|
|
38
|
+
# @param objects [Hash{String => Bluebook::ValueObject}] the aggregate's value objects
|
|
39
|
+
# @return [Hash{String => Object}, nil] the block list the attribute is, or nil
|
|
40
|
+
def of(attribute, objects)
|
|
41
|
+
item = objects[attribute.type.to_s]
|
|
42
|
+
return nil unless attribute.list? && item && !body_block?(item)
|
|
43
|
+
|
|
44
|
+
found = discriminator(item, objects)
|
|
45
|
+
found && describe(item, objects, *found)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [Boolean] whether the value object holds `spans`, as a body's block does
|
|
49
|
+
def body_block?(item) = item.attributes.any? { |part| part.name.to_s == "spans" }
|
|
50
|
+
|
|
51
|
+
# @return [Array(String, Array<String>), nil] the part and the kinds it is one of
|
|
52
|
+
def discriminator(item, objects)
|
|
53
|
+
closed(item, objects) || literal(item)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @return [Array(String, Array<String>), nil] a part typed as a closed set of plain values
|
|
57
|
+
def closed(item, objects)
|
|
58
|
+
item.attributes.each do |part|
|
|
59
|
+
kinds = members_of(objects[part.type.to_s]) unless part.list?
|
|
60
|
+
return [part.name.to_s, kinds] if kinds && !kinds.empty?
|
|
61
|
+
end
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [Array<String>, nil] the values of a closed set of one plain part
|
|
66
|
+
def members_of(set)
|
|
67
|
+
return nil unless set&.closed_set? && set.attributes.size == 1
|
|
68
|
+
|
|
69
|
+
set.members.filter_map { |member| member[set.attributes.first.name]&.to_s }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @return [Array(String, Array<String>), nil] a text part restricted by a literal list
|
|
73
|
+
def literal(item)
|
|
74
|
+
found = literals(item).find { |match| item.attributes.any? { |part| part.name.to_s == match[2] && !part.list? } }
|
|
75
|
+
found && [found[2], found[1].scan(/"([^"]+)"/).flatten]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @return [Array<MatchData>] the invariants that restrict a part to a literal list
|
|
79
|
+
def literals(item) = item.invariants.filter_map { |rule| LITERALS.match(rule.canonical.to_s) }
|
|
80
|
+
|
|
81
|
+
def describe(item, objects, name, kinds)
|
|
82
|
+
table = rules(objects, kinds)
|
|
83
|
+
found = { "discriminator" => name, "kinds" => kinds, "limits" => limits(item), "pictures" => pictures(item, objects) }
|
|
84
|
+
found["rules"] = table if table
|
|
85
|
+
found["mentions"] = mentions(item, name, kinds) unless table
|
|
86
|
+
found.reject { |_, value| value.respond_to?(:empty?) && value.empty? }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @return [Hash{String => Hash}, nil] each kind's `requires` and `uses`, from the table
|
|
90
|
+
def rules(objects, kinds)
|
|
91
|
+
table = objects.values.find { |object| ruled?(object, kinds) }
|
|
92
|
+
return nil unless table
|
|
93
|
+
|
|
94
|
+
table.members.to_h { |row| [row[:kind].to_s, { "requires" => words(row[:requires]), "uses" => words(row[:uses]) }] }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def ruled?(object, kinds)
|
|
98
|
+
object.closed_set? && (RULE_PARTS - object.attributes.map { |part| part.name.to_s }).empty? &&
|
|
99
|
+
object.members.all? { |row| kinds.include?(row[:kind].to_s) }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def words(text) = text.to_s.split(",").map(&:strip).reject(&:empty?)
|
|
103
|
+
|
|
104
|
+
# @return [Hash{String => Array<String>}] the slots each kind's guarded invariants read
|
|
105
|
+
def mentions(item, name, kinds)
|
|
106
|
+
parts = item.attributes.map { |part| part.name.to_s } - [name]
|
|
107
|
+
guarded(item, name).each_with_object({}) do |(kind, rest), found|
|
|
108
|
+
next unless kinds.include?(kind)
|
|
109
|
+
|
|
110
|
+
found[kind] = ((found[kind] || []) | parts.select { |part| rest.match?(/\b#{Regexp.escape(part)}\b/) })
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def guarded(item, name)
|
|
115
|
+
item.invariants.filter_map do |rule|
|
|
116
|
+
match = GUARD.match(rule.canonical.to_s)
|
|
117
|
+
[match[2], match[3]] if match && match[1] == name
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @return [Hash{String => Integer}] each list part of the item and its declared maximum
|
|
122
|
+
def limits(item)
|
|
123
|
+
item.invariants.filter_map do |rule|
|
|
124
|
+
match = LIMIT.match(rule.canonical.to_s)
|
|
125
|
+
[match[1], match[2].to_i] if match
|
|
126
|
+
end.to_h
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @param commands [Array<Bluebook::Command>] the aggregate's commands
|
|
130
|
+
# @param name [String] a list attribute's name
|
|
131
|
+
# @return [Integer, nil] the most the list may hold, from a command's `given`
|
|
132
|
+
def maximum(commands, name)
|
|
133
|
+
sizes = commands.flat_map(&:givens).filter_map do |rule|
|
|
134
|
+
match = LIMIT.match(rule.canonical.to_s)
|
|
135
|
+
match[2].to_i if match && match[1] == name
|
|
136
|
+
end
|
|
137
|
+
sizes.min
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# @return [Hash{String => Hash}] the value object and each value object its lists hold,
|
|
141
|
+
# to the parts that name a picture, for those that have the parts
|
|
142
|
+
def pictures(item, objects)
|
|
143
|
+
held = item.attributes.select(&:list?).filter_map { |part| objects[part.type.to_s] }
|
|
144
|
+
[item, *held].filter_map { |object| (found = picture(object)) && [object.hecks_name, found] }.to_h
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def picture(object)
|
|
148
|
+
names = object.attributes.reject(&:list?).map { |part| part.name.to_s }
|
|
149
|
+
return nil unless names.include?(PICTURE["key"]) && names.include?(PICTURE["alt"])
|
|
150
|
+
|
|
151
|
+
PICTURE.select { |_, part| names.include?(part) }
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "preview"
|
|
4
|
+
require_relative "scale_checks"
|
|
5
|
+
require_relative "theme_checks"
|
|
6
|
+
|
|
3
7
|
module Hecks
|
|
4
8
|
module Projections
|
|
5
9
|
module Site
|
|
@@ -23,7 +27,8 @@ module Hecks
|
|
|
23
27
|
def problems(row, roles, table)
|
|
24
28
|
base = row[:base_path]
|
|
25
29
|
[*path_problems(row, base), *("Editor roles name no role" if roles.empty?), *login_problems(row[:login], table),
|
|
26
|
-
*media_problems(row)
|
|
30
|
+
*media_problems(row), *media_chapter_problems(row), *ThemeChecks.problems(row), *ScaleChecks.problems(row),
|
|
31
|
+
*Preview.problems(row[:preview].strip)]
|
|
27
32
|
end
|
|
28
33
|
|
|
29
34
|
# @return [Array<String>] the problems with the base path and the sign-in path
|
|
@@ -45,6 +50,14 @@ module Hecks
|
|
|
45
50
|
found
|
|
46
51
|
end
|
|
47
52
|
|
|
53
|
+
# @return [Array<String>] the problem with a `media` chapter that is one the editor edits
|
|
54
|
+
def media_chapter_problems(row)
|
|
55
|
+
named = row[:media].strip
|
|
56
|
+
return [] unless row[:chapter].split(",").map(&:strip).include?(named)
|
|
57
|
+
|
|
58
|
+
["media #{named.inspect} is this editor's own chapter; name another chapter"]
|
|
59
|
+
end
|
|
60
|
+
|
|
48
61
|
# The login page is a public row: a visitor with no session must reach it.
|
|
49
62
|
#
|
|
50
63
|
# @return [Array<String>] the problems with the login page
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Site
|
|
6
|
+
module CmsEditor
|
|
7
|
+
# A command argument that only clears: the bluebook's way of setting an optional field back
|
|
8
|
+
# to nothing is an optional argument the command never reads, named as the source of a
|
|
9
|
+
# `sets <field>, to: :<argument>` mutation (`attribute :nothing, Body, optional: true`,
|
|
10
|
+
# `sets :draft_body, to: :nothing`). Nobody types into it, so the editor offers no field
|
|
11
|
+
# for it and the server sends its empty value itself: an empty list, or nothing at all.
|
|
12
|
+
#
|
|
13
|
+
# The shape decides it, never a name. The argument is optional, is no attribute of the
|
|
14
|
+
# aggregate, feeds only plain `sets` mutations of other fields that can be empty (optional
|
|
15
|
+
# or a list), and no `given` or `ensures` of the command reads it.
|
|
16
|
+
module Clearing
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# @param command [Bluebook::Command] the command
|
|
20
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate it belongs to
|
|
21
|
+
# @return [Array<Bluebook::Attribute>] the command's arguments that only clear
|
|
22
|
+
def of(command, aggregate)
|
|
23
|
+
command.attributes.select { |argument| only_clears?(argument, command, aggregate) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param shaped [Hash{String => Object}] the command as the editor reads it
|
|
27
|
+
# @param only [Array<Bluebook::Attribute>] its arguments that only clear
|
|
28
|
+
# @return [Hash{String => Object}] `shaped` without those arguments, and naming the ones
|
|
29
|
+
# that are lists as `empty`, for the editor to send as empty lists
|
|
30
|
+
def apply(shaped, only)
|
|
31
|
+
return shaped if only.empty?
|
|
32
|
+
|
|
33
|
+
names = only.map { |argument| argument.name.to_s }
|
|
34
|
+
lists = only.select(&:list?).map { |argument| argument.name.to_s }
|
|
35
|
+
kept = shaped["attributes"].reject { |attr| names.include?(attr["name"]) }
|
|
36
|
+
shaped.merge("attributes" => kept, **(lists.empty? ? {} : { "empty" => lists }))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [Boolean] whether `argument` is a clearing argument of `command`
|
|
40
|
+
def only_clears?(argument, command, aggregate)
|
|
41
|
+
return false if !argument.optional? || named?(aggregate.attributes, argument.name)
|
|
42
|
+
|
|
43
|
+
fed = fed_by(argument, command)
|
|
44
|
+
!fed.empty? && fed.all? { |mutation| clears?(mutation, aggregate) } && !read?(argument, command)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [Array<Bluebook::Mutation>] the mutations that take their value from `argument`
|
|
48
|
+
def fed_by(argument, command)
|
|
49
|
+
command.mutations.select { |mutation| mutation.source.is_a?(Symbol) && named?([argument], mutation.source) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [Boolean] whether any of `attributes` is called `name`
|
|
53
|
+
def named?(attributes, name) = attributes.any? { |held| held.name.to_s == name.to_s }
|
|
54
|
+
|
|
55
|
+
# @return [Boolean] whether the mutation is a plain set of a field that can be empty
|
|
56
|
+
def clears?(mutation, aggregate)
|
|
57
|
+
target = aggregate.attributes.find { |held| held.name.to_s == mutation.target.to_s }
|
|
58
|
+
mutation.op == :set && !target.nil? && (target.optional? || target.list?)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @return [Boolean] whether a `given` or `ensures` of the command names the argument
|
|
62
|
+
def read?(argument, command)
|
|
63
|
+
word = /\b#{Regexp.escape(argument.name.to_s)}\b/
|
|
64
|
+
[*command.givens, *command.ensures].any? { |rule| word.match?(rule.canonical.to_s) }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Site
|
|
6
|
+
module CmsEditor
|
|
7
|
+
# Colour arithmetic for the editor's theme: parsing and writing six-digit hex, moving
|
|
8
|
+
# between red-green-blue and hue-saturation-lightness, mixing, and the contrast ratio the
|
|
9
|
+
# web accessibility guidelines define. Pure functions of their inputs, so the theme the
|
|
10
|
+
# editor is generated with is the same on every run.
|
|
11
|
+
module Color
|
|
12
|
+
# A six-digit hex colour with its leading hash.
|
|
13
|
+
HEX = /\A#[0-9a-fA-F]{6}\z/
|
|
14
|
+
|
|
15
|
+
# The step by which `reach` moves a colour's lightness while it searches.
|
|
16
|
+
STEP = 0.01
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# @param value [String] a colour such as `"#1f7a6d"`
|
|
21
|
+
# @return [Array<Float>] its red, green and blue, each from 0.0 to 1.0
|
|
22
|
+
def rgb(value) = value.delete_prefix("#").scan(/../).map { |pair| pair.hex / 255.0 }
|
|
23
|
+
|
|
24
|
+
# @param channels [Array<Float>] red, green and blue, each from 0.0 to 1.0
|
|
25
|
+
# @return [String] the colour as lower-case hex
|
|
26
|
+
def hex(channels)
|
|
27
|
+
channels.each_with_object(+"#") { |c, text| text << format("%02x", (c.clamp(0.0, 1.0) * 255).round) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param value [String] a colour
|
|
31
|
+
# @return [Array<Float>] hue in degrees, then saturation and lightness from 0.0 to 1.0
|
|
32
|
+
def hsl(value)
|
|
33
|
+
red, green, blue = rgb(value)
|
|
34
|
+
high = [red, green, blue].max
|
|
35
|
+
low = [red, green, blue].min
|
|
36
|
+
light = (high + low) / 2.0
|
|
37
|
+
return [0.0, 0.0, light] if high == low
|
|
38
|
+
|
|
39
|
+
spread = high - low
|
|
40
|
+
sat = light > 0.5 ? spread / (2.0 - high - low) : spread / (high + low)
|
|
41
|
+
[angle(red, green, blue, high, spread), sat, light]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @param hue [Float] degrees
|
|
45
|
+
# @param sat [Float] saturation from 0.0 to 1.0
|
|
46
|
+
# @param light [Float] lightness from 0.0 to 1.0
|
|
47
|
+
# @return [String] the colour as hex
|
|
48
|
+
def from_hsl(hue, sat, light)
|
|
49
|
+
chroma = (1 - ((2 * light) - 1).abs) * sat
|
|
50
|
+
shift = light - (chroma / 2)
|
|
51
|
+
hex(sector(hue % 360, chroma, second(hue % 360, chroma)).map { |channel| channel + shift })
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [Float] the second-largest channel of a colour of the given hue and chroma
|
|
55
|
+
def second(hue, chroma) = chroma * (1 - (((hue / 60.0) % 2) - 1).abs)
|
|
56
|
+
|
|
57
|
+
# @param one [String] a colour
|
|
58
|
+
# @param other [String] another colour
|
|
59
|
+
# @param share [Float] how much of `other` goes in, from 0.0 to 1.0
|
|
60
|
+
# @return [String] the colours mixed channel by channel
|
|
61
|
+
def mix(one, other, share) = hex(rgb(one).zip(rgb(other)).map { |a, b| (a * (1 - share)) + (b * share) })
|
|
62
|
+
|
|
63
|
+
# @param value [String] a colour
|
|
64
|
+
# @return [Float] its relative luminance as the accessibility guidelines define it
|
|
65
|
+
def luminance(value)
|
|
66
|
+
red, green, blue = rgb(value).map { |c| c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055)**2.4 }
|
|
67
|
+
(0.2126 * red) + (0.7152 * green) + (0.0722 * blue)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @param one [String] a colour
|
|
71
|
+
# @param other [String] another colour
|
|
72
|
+
# @return [Float] the contrast ratio, from 1.0 to 21.0
|
|
73
|
+
def contrast(one, other)
|
|
74
|
+
high = [luminance(one), luminance(other)].max
|
|
75
|
+
low = [luminance(one), luminance(other)].min
|
|
76
|
+
(high + 0.05) / (low + 0.05)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Moves a colour's lightness, a little at a time, until it reads against every background.
|
|
80
|
+
#
|
|
81
|
+
# @param value [String] where to start
|
|
82
|
+
# @param against [Array<String>] backgrounds the colour must read on
|
|
83
|
+
# @param ratio [Float] the least contrast to reach
|
|
84
|
+
# @param toward [Symbol] `:darker` or `:lighter`
|
|
85
|
+
# @return [String] `value` itself when it already reads, else the nearest such colour
|
|
86
|
+
def reach(value, against, ratio, toward)
|
|
87
|
+
hue, sat, light = hsl(value)
|
|
88
|
+
sign = toward == :darker ? -STEP : STEP
|
|
89
|
+
light += sign until light.round(2).clamp(0.0,
|
|
90
|
+
1.0) != light.round(2) || reads?(from_hsl(hue, sat, light), against, ratio)
|
|
91
|
+
from_hsl(hue, sat, light.clamp(0.0, 1.0))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @param value [String] a colour
|
|
95
|
+
# @param against [Array<String>] backgrounds
|
|
96
|
+
# @param ratio [Float] the least contrast
|
|
97
|
+
# @return [Boolean] whether `value` reaches `ratio` on every background
|
|
98
|
+
def reads?(value, against, ratio) = against.all? { |back| contrast(value, back) >= ratio }
|
|
99
|
+
|
|
100
|
+
# @param background [String] a fill
|
|
101
|
+
# @param light [String] a light foreground to try
|
|
102
|
+
# @param dark [String] a dark foreground to try
|
|
103
|
+
# @return [String] whichever reads better on `background`
|
|
104
|
+
def foreground(background, light, dark) = contrast(light, background) >= contrast(dark, background) ? light : dark
|
|
105
|
+
|
|
106
|
+
# @return [Float] the hue, in degrees, of a colour given by its channels
|
|
107
|
+
def angle(red, green, blue, high, spread)
|
|
108
|
+
degrees = if high == red then ((green - blue) / spread) % 6
|
|
109
|
+
elsif high == green then ((blue - red) / spread) + 2
|
|
110
|
+
else ((red - green) / spread) + 4
|
|
111
|
+
end
|
|
112
|
+
degrees * 60.0
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @return [Array<Float>] the red, green and blue before lightness is added back
|
|
116
|
+
def sector(hue, chroma, mid)
|
|
117
|
+
[[chroma, mid, 0], [mid, chroma, 0], [0, chroma, mid], [0, mid, chroma], [mid, 0, chroma],
|
|
118
|
+
[chroma, 0, mid]][(hue / 60).floor % 6]
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Site
|
|
6
|
+
module CmsEditor
|
|
7
|
+
# Several chapters as one editor: each aggregate keeps the name its chapter gave it and is
|
|
8
|
+
# stamped with the chapter that declares it, so two aggregates of one name in different
|
|
9
|
+
# chapters are two aggregates. The stamps exist only when the editor spans more than one
|
|
10
|
+
# chapter (`chapter`), or when a chapter's roles are limited (`roles`), so an editor of one
|
|
11
|
+
# open chapter reads exactly as a chapter alone does.
|
|
12
|
+
module Composition
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# @param chapters [Array<Bluebook::Chapter>] the chapters, in the order the row names them
|
|
16
|
+
# @param skip [Array<String>] `Name` (in every chapter) or `Chapter::Name` to leave out
|
|
17
|
+
# @param roles [Hash{String => Array<String>}] the roles each chapter is limited to
|
|
18
|
+
# @yieldparam aggregate [Bluebook::Aggregate] one aggregate to shape
|
|
19
|
+
# @yieldreturn [Hash{String => Object}] the aggregate as the editor reads it
|
|
20
|
+
# @return [Array<Hash{String => Object}>] the shaped aggregates, chapter by chapter
|
|
21
|
+
def aggregates(chapters, skip:, roles:)
|
|
22
|
+
many = chapters.size > 1
|
|
23
|
+
chapters.flat_map do |chapter|
|
|
24
|
+
kept(chapter, skip).map { |agg| stamp(yield(agg), many ? chapter.name : nil, roles[chapter.name]) }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [Array<Bluebook::Aggregate>] the chapter's aggregates that are not skipped
|
|
29
|
+
def kept(chapter, skip)
|
|
30
|
+
chapter.aggregates.reject { |agg| skip.intersect?([agg.hecks_name, "#{chapter.name}::#{agg.hecks_name}"]) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Hash{String => Object}] `shaped` with its chapter and roles after its name
|
|
34
|
+
def stamp(shaped, named, roles)
|
|
35
|
+
extra = { "chapter" => named, "roles" => roles }.compact
|
|
36
|
+
{ "name" => shaped["name"], **extra, **shaped.except("name") }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|