hecks 3.9.1 → 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 +124 -2
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +8 -0
- data/lib/hecks/deploy/oidc.json +16 -3
- 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/clearing.rb +51 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -0
- 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/site_toolchain/role_prober.rb +78 -0
- data/lib/hecks/hecks/adapters/site_toolchain.rb +29 -4
- data/lib/hecks/hecks/adapters/terminal.rb +13 -11
- data/lib/hecks/hecks/adapters/world_clearing.rb +65 -0
- 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 +61 -18
- data/lib/hecks/hecks/hecks.world +11 -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/fargate/settings/tuning.rb +15 -0
- data/lib/hecks/projections/deploy/fargate/settings.rb +3 -1
- data/lib/hecks/projections/deploy/fargate/stack/readers.rb +1 -0
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/shared.rb +5 -2
- data/lib/hecks/projections/deploy/templates/fargate/Makefile.tmpl +7 -4
- 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 +95 -0
- data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
- data/lib/hecks/projections/site/cms_editor/checks.rb +77 -0
- 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 +85 -0
- 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/pretty.rb +40 -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 +161 -0
- data/lib/hecks/projections/site/cms_editor/setting.rb +130 -0
- data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +370 -0
- 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 +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/session.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/sso.ts.tmpl +20 -0
- 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 +133 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +29 -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 +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +177 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/multipart.ts.tmpl +53 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/sniff.ts.tmpl +94 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/storage.ts.tmpl +50 -0
- 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 +214 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/server.ts.tmpl +65 -0
- 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 +123 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_parse.js.tmpl +320 -0
- 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 +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +61 -0
- 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 +92 -0
- 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 +63 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +213 -0
- 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 +17 -0
- 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 +133 -0
- data/lib/hecks/projections/site/role_probe.rb +74 -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 +29 -1
- data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -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/cli.rb +1 -1
- data/lib/hecks/tools/site_routes/editor_files.rb +45 -0
- data/lib/hecks/tools/site_routes.rb +14 -7
- 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 +9 -1
- 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/bridging.rs +1 -1
- data/rust/codegen/src/commands.rs +13 -8
- data/rust/codegen/src/constraints.rs +25 -0
- data/rust/codegen/src/domain_generator.rs +11 -9
- data/rust/codegen/src/expr_emitter.rs +13 -0
- data/rust/codegen/src/json_codec.rs +54 -15
- data/rust/codegen/src/main.rs +2 -1
- data/rust/codegen/src/mutations.rs +174 -4
- data/rust/codegen/src/naming.rs +30 -0
- 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 +73 -4
- data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
- data/rust/src/kernel/expression_operators/presence.rs +41 -3
- 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 +174 -36
- 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/prebuilt/0ee7f83cba59ce7d1c45c65ba166b071779f70143449f584f33ff7989f64eccb.marshal +0 -0
- data/prebuilt/6e4a6bcf0aa880e2e6b056e1846b70e5a768090b9dc998bbadeaf5d6bcfd3db8.marshal +0 -0
- data/prebuilt/verdicts-91aaf93ce8981bd9a754948f075d95650dc2ec6435015376f5c60992b36a24b1.json +0 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Site
|
|
6
|
+
module CmsEditor
|
|
7
|
+
# The aggregate that registers pictures, found by shape in the schema the editor reads.
|
|
8
|
+
#
|
|
9
|
+
# It is an aggregate with a creating command whose attributes are the aggregate's identity
|
|
10
|
+
# (the key), an alt text and a mime type, and optionally a width and a height; every other
|
|
11
|
+
# attribute of that command must be optional, because an upload cannot fill it. Neither the
|
|
12
|
+
# aggregate's name nor the command's decides it; the attribute names are the vocabulary, the
|
|
13
|
+
# way `blocks`, `kind` and `spans` are for a document body. An attribute is a plain value or
|
|
14
|
+
# a value object whose first part is a plain value; the editor sends a value the same way.
|
|
15
|
+
#
|
|
16
|
+
# The domain keeps the picture's record, never its bytes.
|
|
17
|
+
module Media
|
|
18
|
+
# The attribute names that carry each part of a picture's record besides its key.
|
|
19
|
+
ROLES = { "alt" => %w[alt alt_text], "mime" => %w[mime_type mime content_type],
|
|
20
|
+
"width" => %w[width], "height" => %w[height] }.freeze
|
|
21
|
+
|
|
22
|
+
# The parts every picture's registration must take.
|
|
23
|
+
REQUIRED = %w[key alt mime].freeze
|
|
24
|
+
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
# @param aggregates [Array<Hash{String => Object}>] the aggregates, as `Schema` reads them
|
|
28
|
+
# @return [Hash{String => Object}, nil] the aggregate and command that register a
|
|
29
|
+
# picture, each part's attribute and the query that lists the pictures; nil when no
|
|
30
|
+
# aggregate has the shape
|
|
31
|
+
def read(aggregates)
|
|
32
|
+
aggregates.each do |agg|
|
|
33
|
+
agg["commands"].select { |command| command["creates"] }.each do |command|
|
|
34
|
+
found = fields(agg, command)
|
|
35
|
+
return describe(agg, command, found) if found
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Hash{String => Hash}, nil] each part's attribute and how its value is sent, or
|
|
42
|
+
# nil when the command is not a picture's registration
|
|
43
|
+
def fields(agg, command)
|
|
44
|
+
chosen = parts(agg, command["attributes"])
|
|
45
|
+
return nil unless REQUIRED.all? { |role| chosen.key?(role) } && others_optional?(command, chosen)
|
|
46
|
+
|
|
47
|
+
wrapped = chosen.transform_values { |attr| wrap(agg, attr) }
|
|
48
|
+
return nil if wrapped.value?(false)
|
|
49
|
+
|
|
50
|
+
chosen.to_h { |role, attr| [role, { "name" => attr["name"], "wrap" => wrapped.fetch(role) }] }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @return [Boolean] whether every attribute that is not a part of the record is optional
|
|
54
|
+
def others_optional?(command, chosen)
|
|
55
|
+
(command["attributes"] - chosen.values).all? { |attr| attr["optional"] }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @return [Hash{String => Hash}] each part of the record to the attribute that carries it
|
|
59
|
+
def parts(agg, attrs)
|
|
60
|
+
found = { "key" => attrs.find { |attr| attr["name"] == agg["identity"] } }
|
|
61
|
+
ROLES.each { |role, names| found[role] = attrs.find { |attr| names.include?(attr["name"]) } }
|
|
62
|
+
found.compact
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [String, nil, false] the part a value object's value is sent in, nil for a
|
|
66
|
+
# plain value, false for an attribute an upload cannot fill
|
|
67
|
+
def wrap(agg, attr)
|
|
68
|
+
return false if attr["list"] || attr["kind"] == "reference"
|
|
69
|
+
return nil unless attr["kind"] == "object"
|
|
70
|
+
|
|
71
|
+
first = agg["valueObjects"].fetch(attr["type"], []).first
|
|
72
|
+
first && first["kind"] != "object" && !first["list"] ? first["name"] : false
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @return [Hash{String => Object}] the picture aggregate as the editor's schema names it
|
|
76
|
+
def describe(agg, command, found)
|
|
77
|
+
listing = agg["queries"].find { |query| query["attributes"].empty? }
|
|
78
|
+
{ "aggregate" => agg["name"], **agg.slice("chapter"), "command" => command["name"], "fields" => found,
|
|
79
|
+
"listing" => listing && listing["name"] }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Site
|
|
6
|
+
module CmsEditor
|
|
7
|
+
# Which integer attributes are moments in time, read from the names the bluebook gave them.
|
|
8
|
+
#
|
|
9
|
+
# An integer is a moment when one of the names it goes by (the attribute's, its value
|
|
10
|
+
# object's, the value object's part's) ends in `_at` or `At` (a date and a time), ends in
|
|
11
|
+
# `_on` or `On` (a date), or has `epoch` in it (seconds since 1970, so a date and a time).
|
|
12
|
+
# The value is always whole seconds since 1970 UTC; the editor shows it in the viewer's own
|
|
13
|
+
# time zone. A name is the only evidence the bluebook gives, so the rule is this narrow:
|
|
14
|
+
# an integer named `duration` or `Season` is never a date.
|
|
15
|
+
module Moment
|
|
16
|
+
# Names that end in `at` or `on` as a word, so `created_at` and `CreatedAt` match and
|
|
17
|
+
# `format`, `Season` and `Combat` do not.
|
|
18
|
+
AT = /(?:_at|[a-z0-9]At)\z/
|
|
19
|
+
ON = /(?:_on|[a-z0-9]On)\z/
|
|
20
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
# @param names [Array<String>] the names the integer goes by
|
|
24
|
+
# @return [String, nil] `"datetime"`, `"date"`, or nil when the integer is not a moment
|
|
25
|
+
def widget(names)
|
|
26
|
+
return "datetime" if names.any? { |name| AT.match?(name) || name.match?(/epoch/i) }
|
|
27
|
+
|
|
28
|
+
"date" if names.any? { |name| ON.match?(name) }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "color"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Projections
|
|
7
|
+
module Site
|
|
8
|
+
module CmsEditor
|
|
9
|
+
# The editor's colours, light and dark, derived from one accent colour, as the custom
|
|
10
|
+
# properties of a daisyUI theme (`--color-base-100`, `--color-primary`, ...) and the few
|
|
11
|
+
# extras the pages use (`--ed-muted`, `--ed-edge`). Every text and fill pair is moved
|
|
12
|
+
# until it reaches the accessibility standard's level for it (4.5:1 for text, 3:1 for the
|
|
13
|
+
# edge of a control), so a brand colour that would fail cannot make the editor unreadable.
|
|
14
|
+
#
|
|
15
|
+
# The neutrals (ground, page, ink, muted ink, rule) are fixed when the row names no
|
|
16
|
+
# accent, and take the accent's hue at low saturation when it does, so an accent retints
|
|
17
|
+
# the whole theme. Primary buttons are filled with ink (daisyUI's `neutral`); the accent
|
|
18
|
+
# (`primary`) marks selection, focus, links, the active navigation item and the saved or
|
|
19
|
+
# published state.
|
|
20
|
+
class Palette
|
|
21
|
+
# The accent a row that names none gets: a deep verdigris.
|
|
22
|
+
DEFAULT_ACCENT = "#1f7a6d"
|
|
23
|
+
|
|
24
|
+
# The neutrals a row that names no accent gets, per theme.
|
|
25
|
+
FIXED = {
|
|
26
|
+
light: { ground: "#f4f6f8", surface: "#ffffff", ink: "#1a222c", muted: "#5a6675", rule: "#dce1e7" },
|
|
27
|
+
dark: { ground: "#11161b", surface: "#181f26", ink: "#e7ebef", muted: "#9aa6b3", rule: "#28313b" }
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
# Hues of the status colours, in degrees.
|
|
31
|
+
TONES = { ok: 150.0, warn: 38.0, danger: 4.0 }.freeze
|
|
32
|
+
|
|
33
|
+
# The daisyUI variable each status tone fills.
|
|
34
|
+
STATUS = { ok: "success", warn: "warning", danger: "error" }.freeze
|
|
35
|
+
|
|
36
|
+
# @param accent [String, nil] a six-digit hex colour, or nil for the default
|
|
37
|
+
def initialize(accent = nil)
|
|
38
|
+
@custom = !accent.to_s.empty?
|
|
39
|
+
@accent = (accent.to_s.empty? ? DEFAULT_ACCENT : accent).downcase
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @return [Hash{Symbol => Hash{String => String}}] each theme's properties, name to value
|
|
43
|
+
def themes = { light: properties(:light), dark: properties(:dark) }
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def properties(theme)
|
|
48
|
+
base = neutrals(theme)
|
|
49
|
+
found = base.merge(accents(theme, base)).merge(tones(theme, base))
|
|
50
|
+
{ **daisy(found), **extras(found) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def lighter(theme) = theme == :light ? :darker : :lighter
|
|
54
|
+
|
|
55
|
+
def neutrals(theme)
|
|
56
|
+
fixed = @custom ? tinted(FIXED.fetch(theme)) : FIXED.fetch(theme)
|
|
57
|
+
backs = [fixed[:surface], fixed[:ground]]
|
|
58
|
+
edge = Color.reach(Color.mix(fixed[:rule], fixed[:muted], 0.5), backs, 3.0, lighter(theme))
|
|
59
|
+
{ **fixed, muted: Color.reach(fixed[:muted], backs, 4.5, lighter(theme)), edge: edge }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The fixed neutrals with the accent's hue at the saturation each already has.
|
|
63
|
+
def tinted(fixed)
|
|
64
|
+
hue = Color.hsl(@accent).first
|
|
65
|
+
fixed.transform_values do |hex|
|
|
66
|
+
_, sat, light = Color.hsl(hex)
|
|
67
|
+
hex == "#ffffff" ? hex : Color.from_hsl(hue, sat, light)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def accents(theme, base)
|
|
72
|
+
backs = [base[:surface], base[:ground]]
|
|
73
|
+
fill = theme == :light ? @accent : Color.reach(@accent, backs, 4.5, :lighter)
|
|
74
|
+
soft = Color.mix(base[:surface], fill, theme == :light ? 0.1 : 0.18)
|
|
75
|
+
{ primary: Color.reach(fill, [*backs, soft], 4.5, lighter(theme)), accent_soft: soft }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def tones(theme, base)
|
|
79
|
+
TONES.each_with_object({}) do |(name, hue), found|
|
|
80
|
+
back = theme == :light ? Color.from_hsl(hue, 0.5, 0.94) : Color.from_hsl(hue, 0.28, 0.18)
|
|
81
|
+
start = Color.from_hsl(hue, 0.55, theme == :light ? 0.3 : 0.72)
|
|
82
|
+
found[:"#{name}_bg"] = back
|
|
83
|
+
found[:"#{name}_fg"] = Color.reach(start, [back, base[:surface]], 4.5, lighter(theme))
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The first of white, a near-black and black that reads on `fill`.
|
|
88
|
+
def on(fill) = ["#ffffff", "#0f151a", "#000000"].find { |hex| Color.contrast(hex, fill) >= 4.5 }
|
|
89
|
+
|
|
90
|
+
def daisy(found)
|
|
91
|
+
colours = surfaces(found).merge(fills(found))
|
|
92
|
+
named = colours.slice("primary", "secondary", "accent", "info", *STATUS.values)
|
|
93
|
+
contents = named.to_h { |name, value| ["#{name}-content", on(value)] }
|
|
94
|
+
colours.merge(contents).transform_keys { |name| "--color-#{name}" }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def surfaces(found)
|
|
98
|
+
{ "base-100" => found[:surface], "base-200" => found[:ground], "base-300" => found[:rule],
|
|
99
|
+
"base-content" => found[:ink], "neutral" => found[:ink], "neutral-content" => found[:surface] }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def fills(found)
|
|
103
|
+
{ "primary" => found[:primary], "secondary" => found[:muted], "accent" => found[:primary], "info" => found[:primary],
|
|
104
|
+
**STATUS.to_h { |tone, name| [name, found[:"#{tone}_fg"]] } }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def extras(found)
|
|
108
|
+
{ "--ed-muted" => found[:muted], "--ed-edge" => found[:edge], "--ed-soft" => found[:accent_soft],
|
|
109
|
+
"--ed-ok-bg" => found[:ok_bg], "--ed-warn-bg" => found[:warn_bg], "--ed-danger-bg" => found[:danger_bg] }
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "targets"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Projections
|
|
7
|
+
module Site
|
|
8
|
+
module CmsEditor
|
|
9
|
+
# Which attributes name another aggregate's instance, found by the bluebook's own
|
|
10
|
+
# declarations, so the editor offers the instances in place of a text box. In this order:
|
|
11
|
+
# a `<kind>:<slug>` key by its pattern (see `KeyKinds`), each kind that is an aggregate's
|
|
12
|
+
# snake-case name offering that aggregate; a `reference_to` attribute whose target is an
|
|
13
|
+
# editor aggregate; a value object of one text part named `<Stem>Ref`, where `<Stem>` is
|
|
14
|
+
# the type of an editor aggregate's identity or its name; and `<Image|Picture|Photo|Media>
|
|
15
|
+
# Ref` with a picture aggregate, which suggests pictures and accepts any other text. A
|
|
16
|
+
# target is found once: the same chapter's, else the only one of its name; else none.
|
|
17
|
+
module Pickers
|
|
18
|
+
# The stems that name a picture, for the editor's picture aggregate.
|
|
19
|
+
PICTURE_STEMS = %w[Image Picture Photo Media].freeze
|
|
20
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
# @param aggregates [Array<Hash{String => Object}>] the aggregates as `Schema` shapes them
|
|
24
|
+
# @param media [Hash{String => Object}, nil] the picture aggregate, as `Media` says
|
|
25
|
+
# @return [Array<Hash{String => Object}>] the aggregates, each attribute that names an
|
|
26
|
+
# instance carrying its `picker`
|
|
27
|
+
def apply(aggregates, media = nil)
|
|
28
|
+
targets = Targets.new(aggregates, media)
|
|
29
|
+
aggregates.map { |agg| annotate(agg, targets) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [Hash{String => Object}] `agg` with a picker on each attribute that has one
|
|
33
|
+
def annotate(agg, targets)
|
|
34
|
+
mark = ->(list) { list.map { |attr| with_picker(attr, agg, targets) } }
|
|
35
|
+
agg.merge("attributes" => mark.call(agg["attributes"]), "valueObjects" => objects(agg, mark),
|
|
36
|
+
"commands" => marked(agg["commands"], mark), "queries" => marked(agg["queries"], mark))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [Array<Hash>] the commands or queries, the attributes of each marked
|
|
40
|
+
def marked(items, mark) = items.map { |item| item.merge("attributes" => mark.call(item["attributes"])) }
|
|
41
|
+
|
|
42
|
+
# @return [Hash{String => Array}] the value objects, the parts of one of several parts
|
|
43
|
+
# marked; a value object of one part is entered as the attribute that names it
|
|
44
|
+
def objects(agg, mark)
|
|
45
|
+
agg["valueObjects"].transform_values do |parts|
|
|
46
|
+
parts.size == 1 ? parts.map { |part| part.except("keys") } : mark.call(parts)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [Hash{String => Object}] `attr` with its `picker`, or as it was
|
|
51
|
+
def with_picker(attr, agg, targets)
|
|
52
|
+
picker = picker_for(attr, agg, targets)
|
|
53
|
+
picker ? attr.except("keys").merge("picker" => picker) : attr
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def picker_for(attr, agg, targets)
|
|
57
|
+
return kinds(attr["keys"], agg, targets) if attr["keys"]
|
|
58
|
+
return targets.named(attr["target"], agg) if attr["kind"] == "reference"
|
|
59
|
+
|
|
60
|
+
stem = reference_stem(attr, agg)
|
|
61
|
+
stem && (targets.keyed_by(stem, agg) || (targets.pictures if PICTURE_STEMS.include?(stem)))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @return [Hash{String => Array}] one entry per kind, with the aggregate it names if any
|
|
65
|
+
def kinds(words, agg, targets)
|
|
66
|
+
{ "kinds" => words.map { |word| { "kind" => word, **(targets.kind(word, agg) || {}) } } }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [String, nil] `Stem` of a `StemRef` value object that has one plain text part
|
|
70
|
+
def reference_stem(attr, agg)
|
|
71
|
+
return nil unless plain_text_object?(attr, agg["valueObjects"][attr["type"]])
|
|
72
|
+
|
|
73
|
+
stem = attr["type"].delete_suffix("Ref")
|
|
74
|
+
stem unless stem.empty? || stem == attr["type"]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @return [Boolean] whether `attr` is a value object whose one part is a single text
|
|
78
|
+
def plain_text_object?(attr, parts)
|
|
79
|
+
attr["kind"] == "object" && parts&.size == 1 && parts.first["kind"] == "text" && !parts.first["list"]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Projections
|
|
7
|
+
module Site
|
|
8
|
+
module CmsEditor
|
|
9
|
+
# JSON indented two spaces, with an empty list or object as `[]` or `{}`. It does not use
|
|
10
|
+
# `JSON.pretty_generate`, whose spelling of an empty list depends on which JSON extensions
|
|
11
|
+
# are loaded, so the projection's text is the same wherever it runs.
|
|
12
|
+
module Pretty
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# @param value [Object] a hash, array, string, number, boolean or nil
|
|
16
|
+
# @param depth [Integer] the indentation level of the line the value starts on
|
|
17
|
+
# @return [String] the value as indented JSON
|
|
18
|
+
def generate(value, depth = 0)
|
|
19
|
+
case value
|
|
20
|
+
when Hash then block("{", "}", value.map { |key, item| member(key, item, depth) }, depth)
|
|
21
|
+
when Array then block("[", "]", value.map { |item| generate(item, depth + 1) }, depth)
|
|
22
|
+
else JSON.generate(value)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [String] one `"key": value` line of an object
|
|
27
|
+
def member(key, item, depth) = "#{JSON.generate(key.to_s)}: #{generate(item, depth + 1)}"
|
|
28
|
+
|
|
29
|
+
# @return [String] the lines between `open` and `close`, each indented under `depth`
|
|
30
|
+
def block(open, close, lines, depth)
|
|
31
|
+
return "#{open}#{close}" if lines.empty?
|
|
32
|
+
|
|
33
|
+
pad = " " * (depth + 1)
|
|
34
|
+
"#{open}\n#{lines.map { |line| "#{pad}#{line}" }.join(",\n")}\n#{" " * depth}#{close}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Projections
|
|
7
|
+
module Site
|
|
8
|
+
module CmsEditor
|
|
9
|
+
# The `Editor` row's `preview`: where a record is shown on the site, as a URL template the
|
|
10
|
+
# editor frames in a side drawer.
|
|
11
|
+
#
|
|
12
|
+
# A template is an `http://` or `https://` address, or a path that starts with one slash,
|
|
13
|
+
# with the placeholders `{key}`, `{kind}`, `{slug}` and `{id}` filled from a record's
|
|
14
|
+
# identity when the page is built. Anything else is refused, so a template cannot name a
|
|
15
|
+
# scheme that runs script (`javascript:`), a host written with credentials, or a
|
|
16
|
+
# placeholder the editor would leave unfilled. A placeholder may not stand in the origin
|
|
17
|
+
# (scheme, host and port), because the content security policy names that origin once, at
|
|
18
|
+
# build time, and allows no other frame.
|
|
19
|
+
module Preview
|
|
20
|
+
# The words a template may put in braces.
|
|
21
|
+
PLACEHOLDERS = %w[key kind slug id].freeze
|
|
22
|
+
|
|
23
|
+
# An absolute address: the origin, then whatever follows it.
|
|
24
|
+
ABSOLUTE = %r{\A(?<origin>https?://[^/?#\s]+)(?<rest>.*)\z}m
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
# @param template [String] the row's `preview`, which may be empty
|
|
29
|
+
# @return [Array<String>] every reason the template is refused; empty when it is usable
|
|
30
|
+
def problems(template)
|
|
31
|
+
return [] if template.empty?
|
|
32
|
+
|
|
33
|
+
found = []
|
|
34
|
+
found << "must not hold whitespace" if template.match?(/\s/)
|
|
35
|
+
found.concat(shape_problems(template))
|
|
36
|
+
found.concat(placeholder_problems(template))
|
|
37
|
+
found.map { |problem| "preview #{template.inspect} #{problem}" }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [String, nil] what the content security policy's `frame-src` names for the
|
|
41
|
+
# template: its origin, `'self'` for a path, or nil for no preview
|
|
42
|
+
def frame_source(template)
|
|
43
|
+
return nil if template.empty?
|
|
44
|
+
|
|
45
|
+
found = ABSOLUTE.match(template)
|
|
46
|
+
found ? found[:origin] : "'self'"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [Array<String>] the problems with the scheme, the host and the path
|
|
50
|
+
def shape_problems(template)
|
|
51
|
+
found = ABSOLUTE.match(template)
|
|
52
|
+
return path_problems(template) unless found
|
|
53
|
+
|
|
54
|
+
origin_problems(found[:origin])
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [Array<String>] the problems with a path template
|
|
58
|
+
def path_problems(template)
|
|
59
|
+
return ["must be an http(s) address or a path that starts with one slash"] unless template.start_with?("/")
|
|
60
|
+
return ["must not start with two slashes, which names another host"] if template.start_with?("//")
|
|
61
|
+
|
|
62
|
+
[]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [Array<String>] the problems with the origin of an absolute template
|
|
66
|
+
def origin_problems(origin)
|
|
67
|
+
found = []
|
|
68
|
+
found << "must not name credentials in the address" if origin.include?("@")
|
|
69
|
+
found << "must not hold a placeholder in the scheme, host or port" if origin.include?("{")
|
|
70
|
+
found << "names no host" if !origin.include?("{") && URI.parse(origin).host.to_s.empty?
|
|
71
|
+
found
|
|
72
|
+
rescue URI::InvalidURIError
|
|
73
|
+
["is not a valid address"]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @return [Array<String>] a problem for each unknown placeholder and each stray brace
|
|
77
|
+
def placeholder_problems(template)
|
|
78
|
+
unknown = template.scan(/\{([^{}]*)\}/).flatten.uniq - PLACEHOLDERS
|
|
79
|
+
found = unknown.map do |name|
|
|
80
|
+
"names the placeholder {#{name}}; the placeholders are #{PLACEHOLDERS.map do |word|
|
|
81
|
+
"{#{word}}"
|
|
82
|
+
end.join(", ")}"
|
|
83
|
+
end
|
|
84
|
+
found << "has a brace that is not part of a placeholder" if template.gsub(/\{[^{}]*\}/, "").match?(/[{}]/)
|
|
85
|
+
found
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Site
|
|
6
|
+
module CmsEditor
|
|
7
|
+
# The records that belong to one record, found by the keys they carry. Aggregate `B` is
|
|
8
|
+
# related to `A` when `B`'s identity is a `<kind>:<slug>` key (see `KeyKinds`) whose
|
|
9
|
+
# kinds include `A`'s snake-case name, so `post:first-light` keys the `B` of the `Post`
|
|
10
|
+
# `first-light`.
|
|
11
|
+
#
|
|
12
|
+
# The rule reads the declared pattern and what the kind resolves to (see `Pickers`),
|
|
13
|
+
# never a name. A `B` is shown by its shape: a **document** (a rich-text body), a
|
|
14
|
+
# **gallery** (a list that names pictures) or **metadata** (anything else). Its creating
|
|
15
|
+
# command is the first that takes the identity; its editing command is the draft-saving
|
|
16
|
+
# command of a document that keeps drafts (see `Drafts`), else the first command on an
|
|
17
|
+
# existing record that is neither a lifecycle move nor destructive and takes the body or
|
|
18
|
+
# the pictures.
|
|
19
|
+
module Relations
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# @param aggregates [Array<Hash{String => Object}>] the aggregates as `Pickers` leaves
|
|
23
|
+
# them
|
|
24
|
+
# @param media [Hash{String => Object}, nil] the picture aggregate, as `Media` says
|
|
25
|
+
# @return [Array<Hash{String => Object}>] each aggregate, carrying `related` when other
|
|
26
|
+
# aggregates are keyed by it
|
|
27
|
+
def apply(aggregates, media = nil)
|
|
28
|
+
aggregates.map do |agg|
|
|
29
|
+
found = aggregates.reject { |other| other.equal?(agg) }.filter_map { |other| relation(other, agg, media) }
|
|
30
|
+
found.empty? ? agg : agg.merge("related" => found)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @return [Hash{String => Object}, nil] how `other` is related to `agg`, or nil when it
|
|
35
|
+
# is not
|
|
36
|
+
def relation(other, agg, media)
|
|
37
|
+
key = other["attributes"].find { |attr| attr["name"] == other["identity"] }
|
|
38
|
+
return nil unless key && keyed_by?(key, agg)
|
|
39
|
+
|
|
40
|
+
shape, field = shape_of(other, media)
|
|
41
|
+
{ "aggregate" => other["name"], "chapter" => other["chapter"], "field" => key["name"], "shape" => shape,
|
|
42
|
+
"create" => creating(other, key), "edit" => editing(other, field) }.compact
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [Boolean] whether `key` is declared a key of `<agg's snake name>:<slug>`
|
|
46
|
+
def keyed_by?(key, agg)
|
|
47
|
+
(key.dig("picker", "kinds") || []).any? { |kind| kind["target"] == agg["name"] && kind["chapter"] == agg["chapter"] }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [Array(String, String)] the shape, and the attribute that gives it
|
|
51
|
+
def shape_of(other, media)
|
|
52
|
+
body = other["attributes"].find { |attr| attr["widget"] == "body" && !attr["list"] }
|
|
53
|
+
return ["document", body["name"]] if body
|
|
54
|
+
|
|
55
|
+
pictures = pictures_of(other, media)
|
|
56
|
+
pictures ? ["gallery", pictures["name"]] : ["metadata", nil]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @return [Hash{String => Object}, nil] the list attribute that names pictures
|
|
60
|
+
def pictures_of(other, media)
|
|
61
|
+
return nil unless media
|
|
62
|
+
|
|
63
|
+
other["attributes"].find { |attr| attr["list"] && attr.dig("picker", "target") == media["aggregate"] }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @return [String, nil] the first creating command that takes the identity
|
|
67
|
+
def creating(other, key)
|
|
68
|
+
other["commands"].find do |command|
|
|
69
|
+
command["creates"] && command["attributes"].any? do |attr|
|
|
70
|
+
attr["name"] == key["name"]
|
|
71
|
+
end
|
|
72
|
+
end&.fetch("name")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @return [String, nil] the command that changes the record's body, pictures or fields
|
|
76
|
+
def editing(other, field)
|
|
77
|
+
return other.dig("drafts", "save") if other["drafts"] && field == other.dig("drafts", "live")
|
|
78
|
+
|
|
79
|
+
moves = (other.dig("lifecycle", "transitions") || []).map { |move| move["verb"] }
|
|
80
|
+
other["commands"].find { |command| edits?(command, field, moves) }&.fetch("name")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def edits?(command, field, moves)
|
|
84
|
+
names = command["attributes"].map { |attr| attr["name"] }
|
|
85
|
+
!command["creates"] && !command["destructive"] && !moves.include?(command["name"]) &&
|
|
86
|
+
!names.empty? && (field.nil? || names.include?(field))
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Site
|
|
6
|
+
module CmsEditor
|
|
7
|
+
# What the `Editor` row's settings for many chapters and long lists must satisfy: chapters
|
|
8
|
+
# named once each, roles given for a chapter the editor edits, and a page size a list can
|
|
9
|
+
# hold. A chapter's name is part of an address, so it cannot be spelled as one of the
|
|
10
|
+
# editor's own paths.
|
|
11
|
+
module ScaleChecks
|
|
12
|
+
# The most instances a list may show to a page.
|
|
13
|
+
MAX_PAGE = 1000
|
|
14
|
+
|
|
15
|
+
# The first segments of the editor's own addresses, which no chapter may be named.
|
|
16
|
+
RESERVED = %w[assets media logout api scheduled].freeze
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# @param row [Hash{Symbol => String}] the row, defaults filled
|
|
21
|
+
# @return [Array<String>] every problem with `chapter`, `chapter_roles` and `page_size`
|
|
22
|
+
def problems(row)
|
|
23
|
+
chapters = row[:chapter].split(",").map(&:strip)
|
|
24
|
+
[*chapter_problems(chapters), *role_problems(row[:chapter_roles], chapters), *page_size_problems(row[:page_size])]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Array<String>] the problems with an empty, repeated or reserved chapter name
|
|
28
|
+
def chapter_problems(chapters)
|
|
29
|
+
found = []
|
|
30
|
+
found << "chapter names no chapter" if chapters.reject(&:empty?).empty?
|
|
31
|
+
repeated = chapters.find { |name| chapters.count(name) > 1 }
|
|
32
|
+
found << "chapter names #{repeated.inspect} twice" if repeated
|
|
33
|
+
[*found, *reserved_problems(chapters)]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [Array<String>] a problem for each chapter spelled as an address of the editor's
|
|
37
|
+
def reserved_problems(chapters)
|
|
38
|
+
return [] if chapters.size < 2
|
|
39
|
+
|
|
40
|
+
(chapters & RESERVED).map do |name|
|
|
41
|
+
"chapter #{name.inspect} is the first part of an address the editor serves; name it another way"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [Array<String>] the problems with `Chapter=Role,Role;Chapter=Role` entries
|
|
46
|
+
def role_problems(text, chapters)
|
|
47
|
+
text.split(";").map(&:strip).reject(&:empty?).flat_map do |entry|
|
|
48
|
+
name, roles = entry.split("=", 2)
|
|
49
|
+
next ["chapter_roles #{entry.inspect} must read Chapter=Role,Role"] if roles.to_s.strip.empty?
|
|
50
|
+
next [] if chapters.include?(name.strip)
|
|
51
|
+
|
|
52
|
+
["chapter_roles names #{name.strip.inspect}, which is not a chapter of this editor"]
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @return [Array<String>] the problem with a page size that is not a whole 1 to 1000
|
|
57
|
+
def page_size_problems(size)
|
|
58
|
+
return [] if size.match?(/\A[1-9]\d{0,3}\z/) && size.to_i <= MAX_PAGE
|
|
59
|
+
|
|
60
|
+
["page_size #{size.inspect} must be a whole number from 1 to #{MAX_PAGE}"]
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|