hecks 3.10.0 → 3.12.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 +158 -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 +70 -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/newcomer.rb +50 -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/read_models.rb +1 -1
- 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 +4 -15
- data/lib/hecks/bluebook/assembly/scalars.rb +39 -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 +24 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +6 -3
- 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 +2 -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 +34 -4
- 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 +33 -7
- 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 +133 -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 +13 -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.behaviors +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.behaviors +19 -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 +63 -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/projection_files/expression_tables.rb +7 -2
- data/lib/hecks/projections/deploy/box/compose.rb +6 -4
- data/lib/hecks/projections/deploy/box/edge.rb +62 -0
- data/lib/hecks/projections/deploy/box/logging.rb +99 -0
- data/lib/hecks/projections/deploy/box/roll_scripts.rb +16 -8
- data/lib/hecks/projections/deploy/box/settings/cdn.rb +40 -0
- data/lib/hecks/projections/deploy/box/settings/logs.rb +40 -0
- data/lib/hecks/projections/deploy/box/settings/patterns.rb +11 -0
- data/lib/hecks/projections/deploy/box/settings/types.rb +16 -1
- data/lib/hecks/projections/deploy/box/settings.rb +6 -2
- data/lib/hecks/projections/deploy/box/stack_yaml.rb +5 -2
- data/lib/hecks/projections/deploy/box/templates/Makefile.tmpl +16 -0
- data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +8 -4
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +32 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/box/templates/edge.mk.tmpl +17 -0
- data/lib/hecks/projections/deploy/box/templates/edge.yaml.tmpl +65 -0
- data/lib/hecks/projections/deploy/box/templates/log-group.yaml.tmpl +12 -0
- data/lib/hecks/projections/deploy/box/templates/log-policy.yaml.tmpl +7 -0
- data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +5 -4
- data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +5 -4
- data/lib/hecks/projections/deploy/box.rb +15 -3
- 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/projections/site/site_root/workflow.rb +5 -0
- data/lib/hecks/projector/cli_audience.rb +22 -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/cli_projector/usage.rb +15 -4
- 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/host.rb +11 -4
- data/lib/hecks/rust_build/prebuilt_binary.rb +101 -0
- 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/ci_gate_decision.rb +35 -0
- data/lib/hecks/tools/ci_gates.rb +20 -1
- 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/files.rb +8 -0
- data/lib/hecks/tools/lanes/prebuilt_workflow.rb +81 -0
- data/lib/hecks/tools/lanes/rows.rb +6 -0
- data/lib/hecks/tools/lanes.rb +1 -0
- data/lib/hecks/tools/oidc_manifests.rb +4 -5
- data/lib/hecks/tools/regeneration_run.rb +11 -4
- data/lib/hecks/tools/release_only.rb +111 -0
- 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 +19 -5
- 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/9ee54e2f0d4d6d3fe046827779b221e02de665fba911a048ae5dd8b8fbfb7d90.marshal +0 -0
- data/prebuilt/d74a00e996222c1d8b676d5959092effc46b4dc59e0555f3bf9554ac58e3094a.marshal +0 -0
- data/prebuilt/verdicts-df19f35868d3284a70f55e42dcef87ef4e65e6a678e5135f72e1f6cff4c1357b.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/extension.rs +13 -1
- data/rust/host/src/lib.rs +1 -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/platform_signin/attack_tests.rs +1100 -0
- data/rust/host/src/platform_signin/config.rs +512 -0
- data/rust/host/src/platform_signin/cookie.rs +237 -0
- data/rust/host/src/platform_signin/extension.rs +190 -0
- data/rust/host/src/platform_signin/fetcher.rs +322 -0
- data/rust/host/src/platform_signin/handler.rs +251 -0
- data/rust/host/src/platform_signin/handler_tests.rs +554 -0
- data/rust/host/src/platform_signin/jti.rs +196 -0
- data/rust/host/src/platform_signin/jwks.rs +542 -0
- data/rust/host/src/platform_signin/jws.rs +249 -0
- data/rust/host/src/platform_signin/known_answer_tests.rs +237 -0
- data/rust/host/src/platform_signin/mapping_tests.rs +254 -0
- data/rust/host/src/platform_signin/mod.rs +75 -0
- data/rust/host/src/platform_signin/mode_tests.rs +382 -0
- data/rust/host/src/platform_signin/pages.rs +204 -0
- data/rust/host/src/platform_signin/query.rs +233 -0
- data/rust/host/src/platform_signin/session.rs +151 -0
- data/rust/host/src/platform_signin/settings.rs +385 -0
- data/rust/host/src/platform_signin/strict_json.rs +351 -0
- data/rust/host/src/platform_signin/test_support.rs +235 -0
- data/rust/host/src/platform_signin/ttl_tests.rs +94 -0
- data/rust/host/src/platform_signin/verify.rs +604 -0
- data/rust/host/src/platform_signin/wiring_support.rs +319 -0
- 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 +53 -9
- 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 +34 -17
- 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 +178 -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
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Site
|
|
6
|
+
module CmsEditor
|
|
7
|
+
# What a holder's rules say about how much an attribute may hold: a list's least and most
|
|
8
|
+
# rows (`chapters.size >= 1`, `parts.size <= 12`) and a text's most characters
|
|
9
|
+
# (`summary.to_s.size <= 600`). The rules are the domain's own, so the editor shows the
|
|
10
|
+
# limit it will be held to, and a text that may be long is entered in a writing box.
|
|
11
|
+
#
|
|
12
|
+
# The holder is a value object (its invariants), an aggregate (its invariants) or a command
|
|
13
|
+
# (its `given`s). A rule is read only when it is the whole of the invariant, or the whole
|
|
14
|
+
# of it after an `x.unset? ||` guard on the same part.
|
|
15
|
+
module Limits
|
|
16
|
+
# A list's size compared with a number.
|
|
17
|
+
LIST = /\A(\w+)\.size (<=|>=) (\d+)\z/
|
|
18
|
+
|
|
19
|
+
# A text's length bounded above (`to_s.size`, `size` or `length`), maybe after a guard.
|
|
20
|
+
TEXT = /\A(?:(\w+)\.unset\? \|\| )?(\w+)\.(?:to_s\.)?(?:size|length) <= (\d+)\z/
|
|
21
|
+
|
|
22
|
+
# A text that may be longer than this many characters is entered in a writing box.
|
|
23
|
+
MULTILINE_FROM = 200
|
|
24
|
+
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
# @param attributes [Array<Hash{String => Object}>] the holder's attributes as `Schema`
|
|
28
|
+
# shapes them
|
|
29
|
+
# @param rules [Array<#canonical>] the holder's invariants or `given`s
|
|
30
|
+
# @return [Array<Hash{String => Object}>] the attributes, each with `min` and `max` when
|
|
31
|
+
# it is a list a rule bounds, and `maxLength` (with `multiline` above `MULTILINE_FROM`)
|
|
32
|
+
# when it is a text a rule bounds
|
|
33
|
+
def apply(attributes, rules)
|
|
34
|
+
texts = rules.map { |rule| rule.canonical.to_s }
|
|
35
|
+
lists = sizes(texts)
|
|
36
|
+
bound = lengths(texts)
|
|
37
|
+
attributes.map { |attr| attr.merge(listed(attr, lists), texted(attr, bound)) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [Hash{String => Hash{String => Integer}}] each list's `min` and `max`; where a
|
|
41
|
+
# holder says several, the tightest wins
|
|
42
|
+
def sizes(texts)
|
|
43
|
+
texts.filter_map { |text| LIST.match(text) }.each_with_object({}) do |found, bounds|
|
|
44
|
+
held = (bounds[found[1]] ||= {})
|
|
45
|
+
kind = found[2] == "<=" ? "max" : "min"
|
|
46
|
+
held[kind] = [held[kind], found[3].to_i].compact.public_send(kind == "max" ? :min : :max)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [Hash{String => Integer}] each text's most characters
|
|
51
|
+
def lengths(texts)
|
|
52
|
+
texts.filter_map { |text| TEXT.match(text) }
|
|
53
|
+
.select { |found| found[1].nil? || found[1] == found[2] }
|
|
54
|
+
.to_h { |found| [found[2], found[3].to_i] }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def listed(attr, lists)
|
|
58
|
+
found = attr["list"] ? lists[attr["name"]] : nil
|
|
59
|
+
found ? found.reject { |kind, size| kind == "min" && size < 1 } : {}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def texted(attr, bound)
|
|
63
|
+
most = attr["type"] == "String" && !attr["list"] ? bound[attr["name"]] : nil
|
|
64
|
+
return {} unless most
|
|
65
|
+
|
|
66
|
+
{ "maxLength" => most, **(most > MULTILINE_FROM ? { "multiline" => true } : {}) }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -75,7 +75,7 @@ module Hecks
|
|
|
75
75
|
# @return [Hash{String => Object}] the picture aggregate as the editor's schema names it
|
|
76
76
|
def describe(agg, command, found)
|
|
77
77
|
listing = agg["queries"].find { |query| query["attributes"].empty? }
|
|
78
|
-
{ "aggregate" => agg["name"], "command" => command["name"], "fields" => found,
|
|
78
|
+
{ "aggregate" => agg["name"], **agg.slice("chapter"), "command" => command["name"], "fields" => found,
|
|
79
79
|
"listing" => listing && listing["name"] }
|
|
80
80
|
end
|
|
81
81
|
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,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
|