hecks 3.9.1 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/deploy/bluebook/deploy.bluebook +106 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +8 -0
- data/lib/hecks/deploy/oidc.json +16 -3
- data/lib/hecks/hecks/adapters/deploy_toolchain/clearing.rb +51 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -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/world_clearing.rb +65 -0
- data/lib/hecks/hecks/hecks.hecksagon +8 -0
- data/lib/hecks/hecks/hecks.world +2 -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/templates/fargate/Makefile.tmpl +7 -4
- data/lib/hecks/projections/site/cms_editor/attributes.rb +75 -0
- data/lib/hecks/projections/site/cms_editor/checks.rb +64 -0
- data/lib/hecks/projections/site/cms_editor/media.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/pretty.rb +40 -0
- data/lib/hecks/projections/site/cms_editor/schema.rb +84 -0
- data/lib/hecks/projections/site/cms_editor/setting.rb +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +19 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +176 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +79 -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/commands.ts.tmpl +79 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +23 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +26 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +154 -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/schema.ts.tmpl +85 -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/body_model.js.tmpl +120 -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/body_widget.js.tmpl +282 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +80 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +102 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +75 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/media_picker.js.tmpl +109 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +27 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +123 -0
- data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +16 -0
- data/lib/hecks/projections/site/cms_editor.rb +107 -0
- data/lib/hecks/projections/site/role_probe.rb +72 -0
- data/lib/hecks/site/bluebook/site.bluebook +28 -1
- data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
- data/lib/hecks/tools/site_routes/cli.rb +1 -1
- data/lib/hecks/tools/site_routes/editor_files.rb +38 -0
- data/lib/hecks/tools/site_routes.rb +14 -7
- data/lib/hecks/version.rb +1 -1
- data/prebuilt/{verdicts-91aaf93ce8981bd9a754948f075d95650dc2ec6435015376f5c60992b36a24b1.json → verdicts-d6d584ad43b5f3cebf88aafbbdbf024c38574acf18e06adffbc555f7786b1c2f.json} +1 -1
- data/rust/Cargo.toml +3 -1
- data/rust/codegen/src/bridging.rs +1 -1
- data/rust/codegen/src/domain_generator.rs +9 -9
- data/rust/codegen/src/main.rs +1 -1
- data/rust/codegen/src/mutations.rs +46 -1
- data/rust/codegen/src/naming.rs +30 -0
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/src/kernel/expr.rs +7 -1
- data/rust/src/kernel/expression_operators/presence.rs +41 -3
- metadata +41 -5
- /data/prebuilt/{6e4a6bcf0aa880e2e6b056e1846b70e5a768090b9dc998bbadeaf5d6bcfd3db8.marshal → c28d6d9db14eea3e17161e9d9c262efee30b256cf97693a7cfd552bce24b2f7f.marshal} +0 -0
- /data/prebuilt/{0ee7f83cba59ce7d1c45c65ba166b071779f70143449f584f33ff7989f64eccb.marshal → cb375cba61ca0dbe8ddb9739c306510fe05e5f4b7a9b6955caff8224c63ea0e0.marshal} +0 -0
data/rust/Cargo.toml
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
# domain was generated most recently; any other stays reachable via
|
|
6
6
|
# `--no-default-features --features <name>`.
|
|
7
7
|
[features]
|
|
8
|
-
default = ["
|
|
8
|
+
default = ["reserved_names_fixture"]
|
|
9
|
+
optional_value_object_fixture = []
|
|
10
|
+
reserved_names_fixture = []
|
|
9
11
|
composite_line_ledger = []
|
|
10
12
|
read_model_on_target_fixture = []
|
|
11
13
|
group_by_collision_fixture = []
|
|
@@ -29,7 +29,7 @@ pub fn cross_aggregate_vo_imports(aggregate: &Json, domain_value_object_owner: &
|
|
|
29
29
|
|
|
30
30
|
let mut pairs: Vec<(String, String)> = foreign_types.into_iter().filter_map(|type_name| domain_value_object_owner.get(&type_name).map(|owner| (type_name, owner.clone()))).collect();
|
|
31
31
|
pairs.sort_by(|(a_type, a_owner), (b_type, b_owner)| (a_owner, a_type).cmp(&(b_owner, b_type)));
|
|
32
|
-
pairs.into_iter().map(|(type_name, owner)| format!("use crate::generated::{mod_name}::{}::{};", owner
|
|
32
|
+
pairs.into_iter().map(|(type_name, owner)| format!("use crate::generated::{mod_name}::{}::{};", naming::aggregate_module(&owner), naming::rust_ident(&type_name))).collect()
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/// True when a value object rebuilds into a differently-named one by matching field names.
|
|
@@ -85,8 +85,8 @@ fn reference_checks(
|
|
|
85
85
|
target_mod: target
|
|
86
86
|
.get("name")
|
|
87
87
|
.and_then(Json::as_str)
|
|
88
|
-
.
|
|
89
|
-
.
|
|
88
|
+
.map(crate::naming::aggregate_module)
|
|
89
|
+
.unwrap_or_default(),
|
|
90
90
|
target_name: target
|
|
91
91
|
.get("name")
|
|
92
92
|
.and_then(Json::as_str)
|
|
@@ -161,8 +161,8 @@ fn state_reference_checks(
|
|
|
161
161
|
target_mod: target
|
|
162
162
|
.get("name")
|
|
163
163
|
.and_then(Json::as_str)
|
|
164
|
-
.
|
|
165
|
-
.
|
|
164
|
+
.map(crate::naming::aggregate_module)
|
|
165
|
+
.unwrap_or_default(),
|
|
166
166
|
target_name: target
|
|
167
167
|
.get("name")
|
|
168
168
|
.and_then(Json::as_str)
|
|
@@ -225,7 +225,7 @@ fn tenant_boundary_checks(
|
|
|
225
225
|
|
|
226
226
|
Some(TenantBoundaryCheck {
|
|
227
227
|
reference_field: crate::attr::name(attr).to_string(),
|
|
228
|
-
target_mod: target_decl_name
|
|
228
|
+
target_mod: crate::naming::aggregate_module(&target_decl_name),
|
|
229
229
|
target_name: target_decl_name,
|
|
230
230
|
aggregate_name: aggregate_name.clone(),
|
|
231
231
|
own_tenant_field: own_tenant_field.clone(),
|
|
@@ -1036,13 +1036,13 @@ pub fn generate(
|
|
|
1036
1036
|
}
|
|
1037
1037
|
|
|
1038
1038
|
aggregate_files.push(GeneratedFile {
|
|
1039
|
-
name: format!("{}.rs", agg_name
|
|
1039
|
+
name: format!("{}.rs", crate::naming::aggregate_module(agg_name)),
|
|
1040
1040
|
content: out,
|
|
1041
1041
|
});
|
|
1042
1042
|
|
|
1043
1043
|
registry_aggregates.push(AggregateEntry {
|
|
1044
1044
|
name: agg_name.to_string(),
|
|
1045
|
-
module_name: agg_name
|
|
1045
|
+
module_name: crate::naming::aggregate_module(agg_name),
|
|
1046
1046
|
record: record_name,
|
|
1047
1047
|
commands: registry_commands,
|
|
1048
1048
|
entity_commands,
|
|
@@ -1092,7 +1092,7 @@ pub fn generate(
|
|
|
1092
1092
|
query_defs.push(queries::QueryDef {
|
|
1093
1093
|
verb: format!("{domain_name}::{agg_name}.{query_name}"),
|
|
1094
1094
|
aggregate: format!("{domain_name}::{agg_name}"),
|
|
1095
|
-
arg_checks: queries::query_arg_checks(query, &format!("crate::generated::{mod_name}::{}", agg_name
|
|
1095
|
+
arg_checks: queries::query_arg_checks(query, &format!("crate::generated::{mod_name}::{}", crate::naming::aggregate_module(agg_name)), &value_objects_by_name),
|
|
1096
1096
|
conditions,
|
|
1097
1097
|
reference_hop_conditions,
|
|
1098
1098
|
order_by: query.get("order_by").map(|ob| queries::emit_query_order_by(ob, query.get("null_semantics"))),
|
|
@@ -1273,7 +1273,7 @@ pub fn generate(
|
|
|
1273
1273
|
puts_str(&mut mod_rs, "pub mod metadata;");
|
|
1274
1274
|
puts_str(&mut mod_rs, "pub mod registry;");
|
|
1275
1275
|
for a in ®istry_aggregates {
|
|
1276
|
-
puts_str(&mut mod_rs, &format!("pub mod {};", a.
|
|
1276
|
+
puts_str(&mut mod_rs, &format!("pub mod {};", a.module_name));
|
|
1277
1277
|
}
|
|
1278
1278
|
|
|
1279
1279
|
GeneratedDomain {
|
data/rust/codegen/src/main.rs
CHANGED
|
@@ -73,7 +73,7 @@ fn run_prelude(args: &[String]) -> Result<(), String> {
|
|
|
73
73
|
let name = aggregate.get("name").and_then(Json::as_str).unwrap_or("");
|
|
74
74
|
match prelude::aggregate_prelude(&ex, &ir, aggregate, source_label) {
|
|
75
75
|
Some(text) => {
|
|
76
|
-
let path = format!("{out_dir}/{}.rs", name
|
|
76
|
+
let path = format!("{out_dir}/{}.rs", naming::aggregate_module(name));
|
|
77
77
|
std::fs::write(&path, text).map_err(|e| format!("writing {path}: {e}"))?;
|
|
78
78
|
println!("wrote {path}");
|
|
79
79
|
}
|
|
@@ -455,6 +455,16 @@ pub fn lifecycle_transition_for(command: &Json, aggregate: &Json) -> Option<Tran
|
|
|
455
455
|
Some(Transition { field, to_state, from_states, unconstrained })
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
+
/// Whether a `state(:field)` source reads an `Option` out of the pre-dispatch record: the field
|
|
459
|
+
/// is declared optional, or the record holds every field as an `Option` (`record_optional`).
|
|
460
|
+
/// Such a source already has the target's wrapping, so it must not be wrapped in `Some` again.
|
|
461
|
+
fn state_source_is_option(source: Option<&Json>, aggregate: &Json, record_optional: bool) -> bool {
|
|
462
|
+
let Some(source) = source.filter(|s| s.get("kind").map(Json::to_s).unwrap_or_default() == "state") else { return false };
|
|
463
|
+
let name = source.get("name").map(Json::to_s).unwrap_or_default();
|
|
464
|
+
let attrs = aggregate.get("attributes").map(Json::each).unwrap_or(&[]);
|
|
465
|
+
record_optional || attrs.iter().any(|a| crate::attr::name(a) == name && crate::attr::optional(a))
|
|
466
|
+
}
|
|
467
|
+
|
|
458
468
|
/// Right-hand side for a `set`, coercing the source into the target attribute's declared type.
|
|
459
469
|
///
|
|
460
470
|
/// `target_list` routes list-to-list sets through `list_value_rhs`; `value_rhs` is scalar-only.
|
|
@@ -731,7 +741,8 @@ fn emit_mutation_line_body(
|
|
|
731
741
|
let (guard, effective_rhs) = entity_list_replace_guard(aggregate, target_attr, &target_field, &rhs, value_objects_by_name);
|
|
732
742
|
format!("{guard}{}", exemplar.render("mutation_set_plain", &[("tmpl_field", target_field.to_string()), ("tmpl_rhs_placeholder2()", effective_rhs)]))
|
|
733
743
|
} else {
|
|
734
|
-
let
|
|
744
|
+
let source_is_option = source_attr.map(crate::attr::optional).unwrap_or(false) || state_source_is_option(source, aggregate, optional);
|
|
745
|
+
let wrap = (optional || crate::attr::optional(target_attr)) && !source_is_option;
|
|
735
746
|
if wrap {
|
|
736
747
|
exemplar.render("mutation_set_wrapped", &[("tmpl_field", target_field.to_string()), ("tmpl_rhs_placeholder2()", rhs)])
|
|
737
748
|
} else {
|
|
@@ -851,3 +862,37 @@ mod transition_tests {
|
|
|
851
862
|
assert_eq!(transition_check_arg(Some(&transition)), "None");
|
|
852
863
|
}
|
|
853
864
|
}
|
|
865
|
+
|
|
866
|
+
#[cfg(test)]
|
|
867
|
+
mod state_source_tests {
|
|
868
|
+
use super::*;
|
|
869
|
+
|
|
870
|
+
fn aggregate() -> Json {
|
|
871
|
+
Json::parse(r#"{"name":"Page","attributes":[{"name":"body","type":"Body"},{"name":"draft_body","type":"Body","optional":true}]}"#).unwrap()
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
fn state(name: &str) -> Json {
|
|
875
|
+
Json::parse(&format!(r#"{{"kind":"state","name":"{name}"}}"#)).unwrap()
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
#[test]
|
|
879
|
+
fn a_state_source_in_an_all_optional_record_is_already_an_option() {
|
|
880
|
+
assert!(state_source_is_option(Some(&state("body")), &aggregate(), true));
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
#[test]
|
|
884
|
+
fn a_state_source_reads_an_option_where_it_is_declared_optional() {
|
|
885
|
+
assert!(state_source_is_option(Some(&state("draft_body")), &aggregate(), false));
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
#[test]
|
|
889
|
+
fn a_required_state_source_in_a_plain_record_is_not_an_option() {
|
|
890
|
+
assert!(!state_source_is_option(Some(&state("body")), &aggregate(), false));
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
#[test]
|
|
894
|
+
fn an_argument_source_is_never_a_state_option() {
|
|
895
|
+
let argument = Json::parse(r#"{"kind":"argument","name":"draft_body"}"#).unwrap();
|
|
896
|
+
assert!(!state_source_is_option(Some(&argument), &aggregate(), true));
|
|
897
|
+
}
|
|
898
|
+
}
|
data/rust/codegen/src/naming.rs
CHANGED
|
@@ -70,6 +70,23 @@ pub fn dispatch_fn_name(cmd: &str) -> String {
|
|
|
70
70
|
/// `hecks project_reserved_names` — the same tables `naming.rb` reads.
|
|
71
71
|
pub use crate::reserved_names::{CARGO_RESERVED_DOMAIN_NAMES, RUST_KEYWORDS};
|
|
72
72
|
|
|
73
|
+
/// The modules every generated chapter declares itself, beside its aggregates' modules: the
|
|
74
|
+
/// sidecar `metadata`, the `registry` table, and the `merged` router. An aggregate whose
|
|
75
|
+
/// downcased name is one of these would declare the module twice and overwrite the file.
|
|
76
|
+
pub const CHAPTER_OWNED_MODULES: &[&str] = &["metadata", "registry", "merged"];
|
|
77
|
+
|
|
78
|
+
/// The module (and file stem) one aggregate's generated Rust lives in: its downcased name, or
|
|
79
|
+
/// `<name>_aggregate` when that name is one the chapter declares itself. A downcased name never
|
|
80
|
+
/// holds an underscore, so the suffixed form cannot meet another aggregate's module.
|
|
81
|
+
pub fn aggregate_module(name: &str) -> String {
|
|
82
|
+
let module = name.to_lowercase();
|
|
83
|
+
if CHAPTER_OWNED_MODULES.contains(&module.as_str()) {
|
|
84
|
+
format!("{module}_aggregate")
|
|
85
|
+
} else {
|
|
86
|
+
module
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
73
90
|
/// `/\A[a-z_][a-z0-9_]*\z/` — a plain lowercase Rust identifier.
|
|
74
91
|
fn plain_lower_identifier(name: &str) -> bool {
|
|
75
92
|
let mut chars = name.chars();
|
|
@@ -336,6 +353,19 @@ mod tests {
|
|
|
336
353
|
assert_eq!(scalar_to_value("String", &deref_scalar("String", "v")).as_deref(), Some("Value::Str(v.clone())"));
|
|
337
354
|
}
|
|
338
355
|
|
|
356
|
+
#[test]
|
|
357
|
+
fn an_aggregate_named_for_a_chapter_module_gets_a_suffixed_module() {
|
|
358
|
+
assert_eq!(aggregate_module("Metadata"), "metadata_aggregate");
|
|
359
|
+
assert_eq!(aggregate_module("Registry"), "registry_aggregate");
|
|
360
|
+
assert_eq!(aggregate_module("Merged"), "merged_aggregate");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
#[test]
|
|
364
|
+
fn any_other_aggregate_keeps_its_downcased_module() {
|
|
365
|
+
assert_eq!(aggregate_module("LineItem"), "lineitem");
|
|
366
|
+
assert_eq!(aggregate_module("MetadataEntry"), "metadataentry");
|
|
367
|
+
}
|
|
368
|
+
|
|
339
369
|
#[test]
|
|
340
370
|
fn refuses_an_aggregate_whose_module_name_is_a_rust_keyword() {
|
|
341
371
|
let refusal = reserved_name_refusal("shop", "shop", &["Pizza", "Match", "Type"]).expect("refused");
|
data/rust/host/HECKS_RELEASE
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.10.0
|
data/rust/src/kernel/expr.rs
CHANGED
|
@@ -328,6 +328,12 @@ fn dispatch_operator(category: OperatorCategory, expr: &Expr, ctx: &EvalContext)
|
|
|
328
328
|
/// refuse here — canonical text is validated at boot, so any refusal
|
|
329
329
|
/// past this point is a codegen bug, not a real business refusal.
|
|
330
330
|
fn lookup(path: &str, ctx: &EvalContext) -> Result<Value, Refusal> {
|
|
331
|
+
composite::finish(lookup_field(path, ctx)?, path)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/// `lookup`'s walk, stopped before the last field collapses to a scalar: a path may end on a
|
|
335
|
+
/// nested object, which `.set?` reads as present without needing it to be one value.
|
|
336
|
+
pub(crate) fn lookup_field<'a>(path: &str, ctx: &EvalContext<'a>) -> Result<Field<'a>, Refusal> {
|
|
331
337
|
let mut segments = path.split('.');
|
|
332
338
|
let head = segments.next().unwrap();
|
|
333
339
|
let mut current = ctx
|
|
@@ -340,7 +346,7 @@ fn lookup(path: &str, ctx: &EvalContext) -> Result<Value, Refusal> {
|
|
|
340
346
|
current = composite::step(current, seg, head)?;
|
|
341
347
|
}
|
|
342
348
|
|
|
343
|
-
|
|
349
|
+
Ok(current)
|
|
344
350
|
}
|
|
345
351
|
|
|
346
352
|
/// The list-elements reading of a `Lookup` path — same head resolution
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//! Interprets `.present?`/`.blank?` and `.set?`/`.unset?`.
|
|
2
2
|
//! The two pairs share one operator category and differ in how they treat empty values.
|
|
3
3
|
|
|
4
|
-
use crate::kernel::expr::{interpret as eval, EvalContext, Expr, Value};
|
|
4
|
+
use crate::kernel::expr::{interpret as eval, lookup_field, EvalContext, Expr, Field, Value};
|
|
5
5
|
use crate::kernel::Refusal;
|
|
6
6
|
|
|
7
7
|
pub fn interpret(expr: &Expr, ctx: &EvalContext) -> Result<Value, Refusal> {
|
|
@@ -20,8 +20,14 @@ pub fn interpret_assignment(expr: &Expr, ctx: &EvalContext) -> Result<Value, Ref
|
|
|
20
20
|
return Err(Refusal::TypeMismatch(format!("presence::interpret_assignment called with a non-assignment node {expr:?} — a router bug")));
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
let
|
|
24
|
-
|
|
23
|
+
let set = match &**receiver {
|
|
24
|
+
// A nested object (an optional value-object attribute) is set whenever it is present.
|
|
25
|
+
Expr::Lookup(path) => match lookup_field(path, ctx)? {
|
|
26
|
+
Field::Nested(_) => true,
|
|
27
|
+
Field::Value(v) => !matches!(v, Value::Nil),
|
|
28
|
+
},
|
|
29
|
+
other => !matches!(eval(other, ctx)?, Value::Nil),
|
|
30
|
+
};
|
|
25
31
|
Ok(Value::Bool(if *negated { !set } else { set }))
|
|
26
32
|
}
|
|
27
33
|
|
|
@@ -93,6 +99,38 @@ mod tests {
|
|
|
93
99
|
assert_eq!(set(Expr::Array(vec![]), false), Value::Bool(true));
|
|
94
100
|
}
|
|
95
101
|
|
|
102
|
+
// A record with one value-object field (`body`, holding a nested object or nothing).
|
|
103
|
+
struct Draft {
|
|
104
|
+
body: Option<Body>,
|
|
105
|
+
}
|
|
106
|
+
struct Body;
|
|
107
|
+
impl crate::kernel::expr::Fielded for Body {
|
|
108
|
+
fn field(&self, _name: &str) -> Option<Field<'_>> {
|
|
109
|
+
None
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
impl crate::kernel::expr::Fielded for Draft {
|
|
113
|
+
fn field(&self, name: &str) -> Option<Field<'_>> {
|
|
114
|
+
match (name, &self.body) {
|
|
115
|
+
("body", Some(body)) => Some(Field::Nested(body)),
|
|
116
|
+
("body", None) => Some(Field::Value(Value::Nil)),
|
|
117
|
+
_ => None,
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fn set_on(draft: &Draft, negated: bool) -> Value {
|
|
123
|
+
let expr = Expr::Assignment { receiver: Box::new(Expr::Lookup("body")), negated };
|
|
124
|
+
eval(&expr, &EvalContext { args: &NoFields, instance: draft }).expect("evaluates")
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#[test]
|
|
128
|
+
fn a_nested_object_field_is_set_and_an_absent_one_is_not() {
|
|
129
|
+
assert_eq!(set_on(&Draft { body: Some(Body) }, false), Value::Bool(true));
|
|
130
|
+
assert_eq!(set_on(&Draft { body: None }, false), Value::Bool(false));
|
|
131
|
+
assert_eq!(set_on(&Draft { body: Some(Body) }, true), Value::Bool(false));
|
|
132
|
+
}
|
|
133
|
+
|
|
96
134
|
#[test]
|
|
97
135
|
fn unset_negates_set() {
|
|
98
136
|
assert_eq!(set(Expr::Nil, true), Value::Bool(true));
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hecks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Young
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: prism
|
|
@@ -604,6 +604,7 @@ files:
|
|
|
604
604
|
- lib/hecks/hecks/adapters/deploy_toolchain.adapter
|
|
605
605
|
- lib/hecks/hecks/adapters/deploy_toolchain.rb
|
|
606
606
|
- lib/hecks/hecks/adapters/deploy_toolchain/answers.rb
|
|
607
|
+
- lib/hecks/hecks/adapters/deploy_toolchain/clearing.rb
|
|
607
608
|
- lib/hecks/hecks/adapters/deploy_toolchain/scripts.rb
|
|
608
609
|
- lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb
|
|
609
610
|
- lib/hecks/hecks/adapters/finding_github.adapter
|
|
@@ -645,8 +646,10 @@ files:
|
|
|
645
646
|
- lib/hecks/hecks/adapters/site_toolchain.adapter
|
|
646
647
|
- lib/hecks/hecks/adapters/site_toolchain.rb
|
|
647
648
|
- lib/hecks/hecks/adapters/site_toolchain/prober.rb
|
|
649
|
+
- lib/hecks/hecks/adapters/site_toolchain/role_prober.rb
|
|
648
650
|
- lib/hecks/hecks/adapters/terminal.adapter
|
|
649
651
|
- lib/hecks/hecks/adapters/terminal.rb
|
|
652
|
+
- lib/hecks/hecks/adapters/world_clearing.rb
|
|
650
653
|
- lib/hecks/hecks/codebase.behaviors
|
|
651
654
|
- lib/hecks/hecks/codebase.bluebook
|
|
652
655
|
- lib/hecks/hecks/context_map.hecksagon
|
|
@@ -1043,6 +1046,37 @@ files:
|
|
|
1043
1046
|
- lib/hecks/projections/site/admin_cms/templates/sso.rb
|
|
1044
1047
|
- lib/hecks/projections/site/admin_cms/templates/sso_tail.rb
|
|
1045
1048
|
- lib/hecks/projections/site/admin_cms/templates/users.rb
|
|
1049
|
+
- lib/hecks/projections/site/cms_editor.rb
|
|
1050
|
+
- lib/hecks/projections/site/cms_editor/attributes.rb
|
|
1051
|
+
- lib/hecks/projections/site/cms_editor/checks.rb
|
|
1052
|
+
- lib/hecks/projections/site/cms_editor/media.rb
|
|
1053
|
+
- lib/hecks/projections/site/cms_editor/pretty.rb
|
|
1054
|
+
- lib/hecks/projections/site/cms_editor/schema.rb
|
|
1055
|
+
- lib/hecks/projections/site/cms_editor/setting.rb
|
|
1056
|
+
- lib/hecks/projections/site/cms_editor/templates/package.json.tmpl
|
|
1057
|
+
- lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl
|
|
1058
|
+
- lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl
|
|
1059
|
+
- lib/hecks/projections/site/cms_editor/templates/src/auth/session.ts.tmpl
|
|
1060
|
+
- lib/hecks/projections/site/cms_editor/templates/src/auth/sso.ts.tmpl
|
|
1061
|
+
- lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl
|
|
1062
|
+
- lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl
|
|
1063
|
+
- lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl
|
|
1064
|
+
- lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl
|
|
1065
|
+
- lib/hecks/projections/site/cms_editor/templates/src/media/multipart.ts.tmpl
|
|
1066
|
+
- lib/hecks/projections/site/cms_editor/templates/src/media/sniff.ts.tmpl
|
|
1067
|
+
- lib/hecks/projections/site/cms_editor/templates/src/media/storage.ts.tmpl
|
|
1068
|
+
- lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl
|
|
1069
|
+
- lib/hecks/projections/site/cms_editor/templates/src/server.ts.tmpl
|
|
1070
|
+
- lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl
|
|
1071
|
+
- lib/hecks/projections/site/cms_editor/templates/src/ui/body_parse.js.tmpl
|
|
1072
|
+
- lib/hecks/projections/site/cms_editor/templates/src/ui/body_widget.js.tmpl
|
|
1073
|
+
- lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl
|
|
1074
|
+
- lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl
|
|
1075
|
+
- lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl
|
|
1076
|
+
- lib/hecks/projections/site/cms_editor/templates/src/ui/media_picker.js.tmpl
|
|
1077
|
+
- lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl
|
|
1078
|
+
- lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl
|
|
1079
|
+
- lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl
|
|
1046
1080
|
- lib/hecks/projections/site/edge.rb
|
|
1047
1081
|
- lib/hecks/projections/site/edge/behaviours.rb
|
|
1048
1082
|
- lib/hecks/projections/site/edge/checks.rb
|
|
@@ -1064,6 +1098,7 @@ files:
|
|
|
1064
1098
|
- lib/hecks/projections/site/payload_driver.rb
|
|
1065
1099
|
- lib/hecks/projections/site/probe.rb
|
|
1066
1100
|
- lib/hecks/projections/site/regions.rb
|
|
1101
|
+
- lib/hecks/projections/site/role_probe.rb
|
|
1067
1102
|
- lib/hecks/projections/site/root_rows.rb
|
|
1068
1103
|
- lib/hecks/projections/site/routes_ts.rb
|
|
1069
1104
|
- lib/hecks/projections/site/routes_ts/helpers.rb
|
|
@@ -1455,6 +1490,7 @@ files:
|
|
|
1455
1490
|
- lib/hecks/tools/sequence_script.rb
|
|
1456
1491
|
- lib/hecks/tools/site_routes.rb
|
|
1457
1492
|
- lib/hecks/tools/site_routes/cli.rb
|
|
1493
|
+
- lib/hecks/tools/site_routes/editor_files.rb
|
|
1458
1494
|
- lib/hecks/tools/site_routes/root_files.rb
|
|
1459
1495
|
- lib/hecks/tools/site_routes/templates.rb
|
|
1460
1496
|
- lib/hecks/tools/tail_merge.rb
|
|
@@ -1474,9 +1510,9 @@ files:
|
|
|
1474
1510
|
- lib/rubocop/cop/hecks/fallback_hash_lookup.rb
|
|
1475
1511
|
- lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb
|
|
1476
1512
|
- lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb
|
|
1477
|
-
- prebuilt/
|
|
1478
|
-
- prebuilt/
|
|
1479
|
-
- prebuilt/verdicts-
|
|
1513
|
+
- prebuilt/c28d6d9db14eea3e17161e9d9c262efee30b256cf97693a7cfd552bce24b2f7f.marshal
|
|
1514
|
+
- prebuilt/cb375cba61ca0dbe8ddb9739c306510fe05e5f4b7a9b6955caff8224c63ea0e0.marshal
|
|
1515
|
+
- prebuilt/verdicts-d6d584ad43b5f3cebf88aafbbdbf024c38574acf18e06adffbc555f7786b1c2f.json
|
|
1480
1516
|
- qa/settings.yml
|
|
1481
1517
|
- rust/.cargo/config.toml
|
|
1482
1518
|
- rust/.cargo/mutants.toml
|
|
File without changes
|