hecks 3.2.0 → 3.3.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/bluebook/assembly/contracts.rb +2 -1
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -39
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +3 -3
- data/lib/hecks/bluebook/dsl/need_word.rb +50 -0
- data/lib/hecks/bluebook/dsl/query_builder.rb +6 -1
- data/lib/hecks/bluebook/query.rb +8 -5
- data/lib/hecks/cli/check_era.rb +3 -39
- data/lib/hecks/cli/interview_draft.rb +86 -31
- data/lib/hecks/cli/interview_session.rb +77 -26
- data/lib/hecks/cli/interview_shape.rb +179 -0
- data/lib/hecks/doors/cli_runner.rb +14 -1
- data/lib/hecks/doors/launcher_options.rb +13 -3
- data/lib/hecks/doors/mcp_door.rb +16 -2
- data/lib/hecks/doors/mcp_door_scope.rb +45 -5
- data/lib/hecks/doors/mcp_guide_cache.rb +75 -0
- data/lib/hecks/doors.rb +1 -1
- data/lib/hecks/embryonaut_bluebook/manifest.rb +117 -0
- data/lib/hecks/embryonaut_bluebook/registry.rb +209 -0
- data/lib/hecks/embryonaut_bluebook/vendor.rb +1 -1
- data/lib/hecks/embryonaut_bluebook.rb +2 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +17 -1
- data/lib/hecks/fuzzing/qa_settings.rb +15 -34
- data/lib/hecks/gate/stages.yml +113 -8
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +10 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +10 -2
- data/lib/hecks/hecks/adapters/first_admin.rb +108 -0
- data/lib/hecks/hecks/adapters/git.rb +59 -0
- data/lib/hecks/hecks/adapters/in_process_access.rb +25 -0
- data/lib/hecks/hecks/adapters/in_process_boot.rb +2 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +18 -3
- data/lib/hecks/hecks/adapters/rust_workspace.rb +1 -1
- data/lib/hecks/hecks/adapters/site_toolchain.rb +88 -11
- data/lib/hecks/hecks/codebase.behaviors +6 -0
- data/lib/hecks/hecks/codebase.bluebook +21 -0
- data/lib/hecks/hecks/custodian.behaviors +6 -0
- data/lib/hecks/hecks/custodian.bluebook +217 -0
- data/lib/hecks/hecks/hecks.hecksagon +24 -0
- data/lib/hecks/hecks/hecks.world +8 -3
- data/lib/hecks/hecks/site.world +5 -2
- data/lib/hecks/language/bluebook/query.bluebook +25 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +220 -7
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +1 -1
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -3
- data/lib/hecks/projections/deploy/box/hosting.rb +170 -0
- data/lib/hecks/projections/deploy/box/hosting_settings.rb +123 -0
- data/lib/hecks/projections/deploy/box/settings.rb +40 -6
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +2 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.services-roll.part +33 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +79 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef-roll.part +77 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef.part +12 -0
- data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +24 -0
- data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +17 -0
- data/lib/hecks/projections/deploy/box/templates/smoke-after-deploy.sh.tmpl +271 -0
- data/lib/hecks/projections/deploy/box.rb +28 -4
- data/lib/hecks/projections/deploy/fargate.rb +3 -3
- data/lib/hecks/projections/deploy/lambda.rb +8 -8
- data/lib/hecks/projections/model/deviations.rb +1 -1
- data/lib/hecks/projections/site/edge/behaviours.rb +5 -3
- data/lib/hecks/projections/site/edge/checks.rb +31 -5
- data/lib/hecks/projections/site/edge.rb +25 -11
- data/lib/hecks/projections/site/live_edge.rb +162 -0
- data/lib/hecks/projections/site/routes_ts.rb +50 -27
- data/lib/hecks/projections/site/site_cdn.rb +7 -4
- data/lib/hecks/projections/site/table/checks.rb +53 -7
- data/lib/hecks/projections/site/table/link_builder.rb +64 -0
- data/lib/hecks/projections/site/table/row_builder.rb +21 -6
- data/lib/hecks/projections/site/table.rb +16 -5
- data/lib/hecks/quality_control/adapters/git_pr.rb +0 -14
- data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +32 -16
- data/lib/hecks/quality_control/cli/qa_seed_angles.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_seed_targets.rb +1 -2
- data/lib/hecks/quality_control/quality_control.bluebook +76 -124
- data/lib/hecks/runtime/interpreting.rb +3 -22
- data/lib/hecks/runtime/needs.rb +48 -0
- data/lib/hecks/runtime/query_interpreter.rb +3 -0
- data/lib/hecks/runtime/registry/verification.rb +3 -3
- data/lib/hecks/rust_build/host.rb +147 -0
- data/lib/hecks/rust_build/kernel_input.rb +51 -17
- data/lib/hecks/rust_build/project_rust/codegen_run.rb +0 -18
- data/lib/hecks/rust_build/project_rust.rb +7 -140
- data/lib/hecks/rust_build.rb +1 -0
- data/lib/hecks/site/bluebook/site.bluebook +80 -14
- data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
- data/lib/hecks/sme/bluebook/sme.bluebook +128 -1
- data/lib/hecks/storehouse.rb +35 -1
- data/lib/hecks/tools/comment_style.rb +4 -2
- data/lib/hecks/tools/gate.rb +1 -1
- data/lib/hecks/tools/rust_comment_style.rb +2 -4
- data/lib/hecks/tools/site_routes.rb +69 -28
- data/lib/hecks/tools/tools_doc.rb +204 -0
- data/lib/hecks/tools.rb +1 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +152 -1
- data/lib/hecks.rb +1 -0
- data/qa/settings.yml +1 -2
- data/rust/build/src/lineage_pass.rs +31 -16
- data/rust/build/src/main.rs +4 -2
- data/rust/build/src/pipeline.rs +9 -20
- data/rust/codegen/src/attr.rs +1 -1
- data/rust/codegen/src/bridging.rs +8 -8
- data/rust/codegen/src/commands.rs +1 -1
- data/rust/codegen/src/constraints.rs +1 -1
- data/rust/codegen/src/dependency_planning.rs +1 -1
- data/rust/codegen/src/domain_generator.rs +2 -2
- data/rust/codegen/src/exemplar.rs +1 -1
- data/rust/codegen/src/expr_emitter.rs +1 -1
- data/rust/codegen/src/fielded.rs +1 -1
- data/rust/codegen/src/json.rs +214 -23
- data/rust/codegen/src/json_codec.rs +1 -1
- data/rust/codegen/src/literal.rs +6 -3
- data/rust/codegen/src/main.rs +23 -13
- data/rust/codegen/src/mutations.rs +1 -1
- data/rust/codegen/src/naming.rs +5 -3
- data/rust/codegen/src/optional_pass.rs +226 -0
- data/rust/codegen/src/ports.rs +1 -1
- data/rust/codegen/src/queries.rs +3 -3
- data/rust/codegen/src/reactions.rs +1 -1
- data/rust/codegen/src/read_models.rs +2 -2
- data/rust/codegen/src/reference_specs.rs +1 -1
- data/rust/codegen/src/registry.rs +4 -4
- data/rust/codegen/src/shared.rs +1 -1
- data/rust/{build → codegen}/src/sidecars.rs +9 -5
- data/rust/codegen/src/skip_reason.rs +1 -1
- data/rust/codegen/src/types.rs +4 -4
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +1 -1
- data/rust/host/src/dispatch.rs +5 -0
- data/rust/host/src/needs.rs +193 -29
- data/rust/host/src/resend.rs +4 -4
- data/rust/host/src/ui_schema.rs +1 -1
- data/rust/parser/src/emit.rs +12 -0
- data/rust/parser/src/ir.rs +2 -0
- data/rust/parser/src/keywords.rs +2 -0
- data/rust/parser/src/main.rs +1 -0
- data/rust/parser/src/parse/command.rs +2 -44
- data/rust/parser/src/parse/mod.rs +1 -0
- data/rust/parser/src/parse/needs.rs +60 -0
- data/rust/parser/src/parse/query.rs +3 -0
- data/rust/src/exemplar/commands.rs +1 -1
- data/rust/src/exemplar/constraints.rs +1 -1
- data/rust/src/exemplar/fielded.rs +1 -1
- data/rust/src/exemplar/json.rs +1 -1
- data/rust/src/exemplar/mod.rs +1 -1
- data/rust/src/exemplar/mutations.rs +1 -1
- data/rust/src/exemplar/queries.rs +1 -1
- data/rust/src/exemplar/reactions.rs +1 -1
- data/rust/src/exemplar/read_models.rs +1 -1
- data/rust/src/exemplar/registry.rs +1 -1
- data/rust/src/exemplar/types.rs +1 -1
- data/rust/src/kernel/cli.rs +18 -8
- data/rust/src/kernel/needs.rs +104 -9
- data/rust/src/kernel/read_model.rs +1 -1
- data/rust/src/lib.rs +1 -1
- metadata +24 -30
- data/lib/hecks/rust_build/append_optionals.rb +0 -79
- data/lib/hecks/rust_build/rust_literal.rb +0 -33
- data/lib/hecks/three_zero/forms.yml +0 -127
- data/lib/hecks/three_zero.rb +0 -12
- data/rust/build/src/json.rs +0 -421
- data/rust/build/src/optional_pass.rs +0 -150
- data/rust/project/bridging.rb +0 -257
- data/rust/project/commands.rb +0 -877
- data/rust/project/constraints.rb +0 -86
- data/rust/project/dependency_planning.rb +0 -178
- data/rust/project/domain_generator.rb +0 -983
- data/rust/project/exemplar.rb +0 -148
- data/rust/project/expr_emitter.rb +0 -72
- data/rust/project/fielded.rb +0 -141
- data/rust/project/json_codec.rb +0 -641
- data/rust/project/mutations.rb +0 -695
- data/rust/project/naming.rb +0 -223
- data/rust/project/ports.rb +0 -65
- data/rust/project/queries.rb +0 -493
- data/rust/project/reactions.rb +0 -317
- data/rust/project/read_models.rb +0 -690
- data/rust/project/reference_specs.rb +0 -27
- data/rust/project/registry.rb +0 -396
- data/rust/project/skip_reason.rb +0 -29
- data/rust/project/types.rb +0 -225
- data/rust/project/write_if_changed.rb +0 -7
- data/rust/project.rb +0 -30
- data/rust/project_rust_pipeline.rb +0 -391
data/rust/src/kernel/cli.rs
CHANGED
|
@@ -18,6 +18,8 @@ pub fn run(input: &str) -> String {
|
|
|
18
18
|
|
|
19
19
|
// Optional `"needs"`: which commands need which outside facts (ADR 0081), passed by the host.
|
|
20
20
|
super::needs::install(parsed.get("needs"));
|
|
21
|
+
// Optional `"query_needs"`: which queries need which outside facts, filled the same way.
|
|
22
|
+
super::needs::install_queries(parsed.get("query_needs"));
|
|
21
23
|
// Optional `"defaults"`: each command's declared argument defaults, filled the same way.
|
|
22
24
|
super::needs::install_defaults(parsed.get("defaults"));
|
|
23
25
|
|
|
@@ -86,6 +88,14 @@ pub fn run(input: &str) -> String {
|
|
|
86
88
|
// A query step's `args` sit at the sibling `"args"` key, as for a command step.
|
|
87
89
|
let empty_args = Json::Object(vec![]);
|
|
88
90
|
let args = step.get("args").unwrap_or(&empty_args);
|
|
91
|
+
// What the caller offered is what the log echoes; a fact the query `needs` is answered
|
|
92
|
+
// for the run alone (ADR 0081), so the log holds no value that depends on the clock.
|
|
93
|
+
let offered = args;
|
|
94
|
+
let filled = match step.get("query") {
|
|
95
|
+
Some(Json::Str(question)) => super::needs::enrich_query(question, args, step.get("occurred_at").and_then(Json::as_str)),
|
|
96
|
+
_ => None,
|
|
97
|
+
};
|
|
98
|
+
let args = filled.as_ref().unwrap_or(args);
|
|
89
99
|
|
|
90
100
|
// A `"query"` step carries no verb and takes one of three shapes:
|
|
91
101
|
// - `"Domain::Aggregate.Query"`: a declared aggregate query, looked up in `QUERIES`.
|
|
@@ -106,7 +116,7 @@ pub fn run(input: &str) -> String {
|
|
|
106
116
|
let rows = Json::Array(rows);
|
|
107
117
|
query_results.push(Json::obj(vec![
|
|
108
118
|
("query", Json::Str(question.clone())),
|
|
109
|
-
("args",
|
|
119
|
+
("args", offered.clone()),
|
|
110
120
|
("rows", rows.clone()),
|
|
111
121
|
("reference_rows", rows),
|
|
112
122
|
]));
|
|
@@ -115,7 +125,7 @@ pub fn run(input: &str) -> String {
|
|
|
115
125
|
let message = refusal.to_string();
|
|
116
126
|
query_results.push(Json::obj(vec![
|
|
117
127
|
("query", Json::Str(question.clone())),
|
|
118
|
-
("args",
|
|
128
|
+
("args", offered.clone()),
|
|
119
129
|
("rows", Json::Null),
|
|
120
130
|
("error", Json::Str(message.clone())),
|
|
121
131
|
("reference_rows", Json::Null),
|
|
@@ -131,7 +141,7 @@ pub fn run(input: &str) -> String {
|
|
|
131
141
|
let rows = Json::Array(entries.into_iter().map(|(id, record)| repository::row_json(id, record)).collect());
|
|
132
142
|
query_results.push(Json::obj(vec![
|
|
133
143
|
("query", Json::Str(question.clone())),
|
|
134
|
-
("args",
|
|
144
|
+
("args", offered.clone()),
|
|
135
145
|
("rows", rows.clone()),
|
|
136
146
|
// Same as `rows`: one compiled path (named_query.rs).
|
|
137
147
|
("reference_rows", rows),
|
|
@@ -143,7 +153,7 @@ pub fn run(input: &str) -> String {
|
|
|
143
153
|
let message = refusal.to_string();
|
|
144
154
|
query_results.push(Json::obj(vec![
|
|
145
155
|
("query", Json::Str(question.clone())),
|
|
146
|
-
("args",
|
|
156
|
+
("args", offered.clone()),
|
|
147
157
|
("rows", Json::Null),
|
|
148
158
|
("error", Json::Str(message.clone())),
|
|
149
159
|
("reference_rows", Json::Null),
|
|
@@ -159,7 +169,7 @@ pub fn run(input: &str) -> String {
|
|
|
159
169
|
real declared query whose shape this generator's codegen doesn't cover yet (order_by/limit/\
|
|
160
170
|
cursor/consistency/freshness/authorization/null_semantics/inspection/index_hints, a where \
|
|
161
171
|
clause hopping through a reference, or a literal comparator value whose true JSON type \
|
|
162
|
-
can't be recovered from the exported IR — rust/
|
|
172
|
+
can't be recovered from the exported IR — rust/codegen/src/queries.rs's own header has the \
|
|
163
173
|
full argument); the wheres-only, single-aggregate field-comparator subset and the ad hoc \
|
|
164
174
|
filter shape ({{\"aggregate\",\"field\",\"op\",\"value\"}}) both execute for real"
|
|
165
175
|
)),
|
|
@@ -170,7 +180,7 @@ pub fn run(input: &str) -> String {
|
|
|
170
180
|
Ok(row) => {
|
|
171
181
|
query_results.push(Json::obj(vec![
|
|
172
182
|
("query", Json::Str(question.clone())),
|
|
173
|
-
("args",
|
|
183
|
+
("args", offered.clone()),
|
|
174
184
|
("rows", Json::Array(vec![row])),
|
|
175
185
|
// No `reference_rows` key: Ruby sets it only for questions with a
|
|
176
186
|
// reference twin, and a read model has none.
|
|
@@ -181,7 +191,7 @@ pub fn run(input: &str) -> String {
|
|
|
181
191
|
let message = refusal.to_string();
|
|
182
192
|
query_results.push(Json::obj(vec![
|
|
183
193
|
("query", Json::Str(question.clone())),
|
|
184
|
-
("args",
|
|
194
|
+
("args", offered.clone()),
|
|
185
195
|
("rows", Json::Null),
|
|
186
196
|
("error", Json::Str(message)),
|
|
187
197
|
]));
|
|
@@ -195,7 +205,7 @@ pub fn run(input: &str) -> String {
|
|
|
195
205
|
or a real declared read model whose shape this generator's codegen doesn't cover yet \
|
|
196
206
|
(anything beyond a root aggregate fetched by reference id plus reference-matched sibling \
|
|
197
207
|
heads — where/order_by/limit/offset/cursor/consistency/freshness/authorize(TenantScope)/\
|
|
198
|
-
nulls/inspect_query/use_index — rust/
|
|
208
|
+
nulls/inspect_query/use_index — rust/codegen/src/read_models.rs's own header has the full \
|
|
199
209
|
argument, including why where/order_by/limit specifically can never be recovered from the \
|
|
200
210
|
canonical IR at all); the named/declared AGGREGATE query form (\"Domain::Aggregate.Query\") \
|
|
201
211
|
and the ad hoc filter shape ({{\"aggregate\",\"field\",\"op\",\"value\"}}) both execute for \
|
data/rust/src/kernel/needs.rs
CHANGED
|
@@ -26,15 +26,16 @@ pub struct Need {
|
|
|
26
26
|
thread_local! {
|
|
27
27
|
static TABLE: RefCell<HashMap<String, Vec<Need>>> = RefCell::new(HashMap::new());
|
|
28
28
|
static DEFAULTS: RefCell<HashMap<String, Vec<(String, Json)>>> = RefCell::new(HashMap::new());
|
|
29
|
+
static QUERY_TABLE: RefCell<HashMap<String, Vec<Need>>> = RefCell::new(HashMap::new());
|
|
29
30
|
static FIXED_CLOCK: Cell<Option<i64>> = const { Cell::new(None) };
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
///
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
/// Reads a `{ name: [{fact, type}] }` table (an absent or malformed one reads as empty).
|
|
34
|
+
fn parse_table(table: Option<&Json>) -> HashMap<String, Vec<Need>> {
|
|
35
|
+
match table {
|
|
35
36
|
Some(Json::Object(entries)) => entries
|
|
36
37
|
.iter()
|
|
37
|
-
.map(|(
|
|
38
|
+
.map(|(name, needs)| {
|
|
38
39
|
let needs = needs
|
|
39
40
|
.as_array()
|
|
40
41
|
.unwrap_or(&[])
|
|
@@ -45,12 +46,23 @@ pub fn install(table: Option<&Json>) {
|
|
|
45
46
|
Some(Need { fact, attribute_type })
|
|
46
47
|
})
|
|
47
48
|
.collect();
|
|
48
|
-
(
|
|
49
|
+
(name.clone(), needs)
|
|
49
50
|
})
|
|
50
51
|
.collect(),
|
|
51
52
|
_ => HashMap::new(),
|
|
52
|
-
}
|
|
53
|
-
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// Installs the table the host passed, command verb to its needs (an absent or malformed
|
|
57
|
+
/// `"needs"` installs an empty one).
|
|
58
|
+
pub fn install(table: Option<&Json>) {
|
|
59
|
+
TABLE.with(|t| *t.borrow_mut() = parse_table(table));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Installs the table of queries that need a fact, `"Domain::Aggregate.Query"` to its needs. Kept
|
|
63
|
+
/// apart from the commands': a command and a query may share a qualified name.
|
|
64
|
+
pub fn install_queries(table: Option<&Json>) {
|
|
65
|
+
QUERY_TABLE.with(|t| *t.borrow_mut() = parse_table(table));
|
|
54
66
|
}
|
|
55
67
|
|
|
56
68
|
/// Installs the declared defaults the host passed, `{ verb: { attribute: value } }` (an absent or
|
|
@@ -106,9 +118,11 @@ fn parse_iso8601_utc(text: &str) -> Option<i64> {
|
|
|
106
118
|
|
|
107
119
|
fn answer(need: &Need, occurred_at: Option<&str>) -> Option<Json> {
|
|
108
120
|
match need.fact.as_str() {
|
|
109
|
-
|
|
121
|
+
// `now` is epoch seconds; `today` the day those fall in, whole days since the epoch (UTC).
|
|
122
|
+
"now" | "today" => {
|
|
110
123
|
let secs = now_secs(occurred_at);
|
|
111
|
-
|
|
124
|
+
let answer = if need.fact == "today" { secs.div_euclid(86_400) } else { secs };
|
|
125
|
+
Some(if need.attribute_type == "Integer" { Json::int(answer) } else { Json::obj(vec![("value", Json::int(answer))]) })
|
|
112
126
|
}
|
|
113
127
|
_ => None,
|
|
114
128
|
}
|
|
@@ -151,6 +165,27 @@ pub fn enrich(verb: &str, args: &Json, occurred_at: Option<&str>) -> Option<Json
|
|
|
151
165
|
Some(Json::Object(envelope.iter().map(|(k, v)| if k == "with" { (k.clone(), Json::Object(filled.clone())) } else { (k.clone(), v.clone()) }).collect()))
|
|
152
166
|
}
|
|
153
167
|
|
|
168
|
+
/// `args` with each fact the query `question` needs and the caller left out answered, or `None`
|
|
169
|
+
/// when nothing needed filling (the query needs nothing, or every fact is supplied). A query's
|
|
170
|
+
/// arguments are the flat object of its step.
|
|
171
|
+
pub fn enrich_query(question: &str, args: &Json, occurred_at: Option<&str>) -> Option<Json> {
|
|
172
|
+
let needs = QUERY_TABLE.with(|t| t.borrow().get(question).cloned())?;
|
|
173
|
+
let Json::Object(held) = args else { return None };
|
|
174
|
+
let mut filled = held.clone();
|
|
175
|
+
for need in &needs {
|
|
176
|
+
if held.iter().any(|(k, _)| k == &need.fact) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if let Some(value) = answer(need, occurred_at) {
|
|
180
|
+
filled.push((need.fact.clone(), value));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if filled.len() == held.len() {
|
|
184
|
+
return None;
|
|
185
|
+
}
|
|
186
|
+
Some(Json::Object(filled))
|
|
187
|
+
}
|
|
188
|
+
|
|
154
189
|
#[cfg(test)]
|
|
155
190
|
mod tests {
|
|
156
191
|
use super::*;
|
|
@@ -264,4 +299,64 @@ mod tests {
|
|
|
264
299
|
install_defaults(None);
|
|
265
300
|
assert_eq!(enrich("D::A.Start", &with(vec![]), None), None);
|
|
266
301
|
}
|
|
302
|
+
|
|
303
|
+
fn query_table() -> Json {
|
|
304
|
+
Json::parse(
|
|
305
|
+
r#"{"D::A.Expired": [{"fact": "now", "type": "Integer"}],
|
|
306
|
+
"D::A.Today": [{"fact": "today", "type": "DayNumber"}]}"#,
|
|
307
|
+
)
|
|
308
|
+
.unwrap()
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
#[test]
|
|
312
|
+
fn a_needed_query_fact_the_caller_left_out_is_answered_by_the_clock() {
|
|
313
|
+
install_queries(Some(&query_table()));
|
|
314
|
+
fix_clock(Some(1_700_000_000));
|
|
315
|
+
let filled = enrich_query("D::A.Expired", &Json::obj(vec![]), None).unwrap();
|
|
316
|
+
assert_eq!(filled.get("now"), Some(&Json::int(1_700_000_000)));
|
|
317
|
+
fix_clock(None);
|
|
318
|
+
install_queries(None);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
#[test]
|
|
322
|
+
fn a_query_value_the_caller_supplied_is_kept() {
|
|
323
|
+
install_queries(Some(&query_table()));
|
|
324
|
+
fix_clock(Some(1));
|
|
325
|
+
assert_eq!(enrich_query("D::A.Expired", &Json::obj(vec![("now", Json::int(7))]), None), None);
|
|
326
|
+
fix_clock(None);
|
|
327
|
+
install_queries(None);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
#[test]
|
|
331
|
+
fn a_query_that_needs_nothing_is_untouched() {
|
|
332
|
+
install_queries(Some(&query_table()));
|
|
333
|
+
assert_eq!(enrich_query("D::A.Plain", &Json::obj(vec![]), None), None);
|
|
334
|
+
install_queries(None);
|
|
335
|
+
assert_eq!(enrich_query("D::A.Expired", &Json::obj(vec![]), None), None);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
#[test]
|
|
339
|
+
fn a_command_and_a_query_of_one_name_keep_their_own_needs() {
|
|
340
|
+
install(Some(&table()));
|
|
341
|
+
install_queries(Some(&query_table()));
|
|
342
|
+
fix_clock(Some(5));
|
|
343
|
+
assert_eq!(enrich_query("D::A.Instant", &Json::obj(vec![]), None), None);
|
|
344
|
+
assert!(enrich("D::A.Instant", &with(vec![]), None).is_some());
|
|
345
|
+
fix_clock(None);
|
|
346
|
+
install(None);
|
|
347
|
+
install_queries(None);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
#[test]
|
|
351
|
+
fn today_is_the_day_the_clock_falls_in_whole_days_since_the_epoch() {
|
|
352
|
+
install_queries(Some(&query_table()));
|
|
353
|
+
fix_clock(Some((19_000 * 86_400) + 86_399));
|
|
354
|
+
let filled = enrich_query("D::A.Today", &Json::obj(vec![]), None).unwrap();
|
|
355
|
+
assert_eq!(filled.get("today"), Some(&Json::obj(vec![("value", Json::int(19_000))])));
|
|
356
|
+
fix_clock(Some(19_001 * 86_400));
|
|
357
|
+
let next = enrich_query("D::A.Today", &Json::obj(vec![]), None).unwrap();
|
|
358
|
+
assert_eq!(next.get("today"), Some(&Json::obj(vec![("value", Json::int(19_001))])));
|
|
359
|
+
fix_clock(None);
|
|
360
|
+
install_queries(None);
|
|
361
|
+
}
|
|
267
362
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//! Interprets a compiled `report` block (`ReadModelDef`) against a domain's generated
|
|
2
|
-
//! `READ_MODELS` table — the hand-written counterpart to `
|
|
2
|
+
//! `READ_MODELS` table — the hand-written counterpart to the retired Ruby generator's `read_models.rb`.
|
|
3
3
|
|
|
4
4
|
use super::refusal_wording::{InvariantViolationGroupByCollisionArgs, NotFoundReadModelReferenceMissingArgs, UnauthorizedTenantRequiredArgs};
|
|
5
5
|
use super::{named_query, query_comparators, query_ordering, repository, AggregateScan, Json, QueryCondition, QueryConditionValue, Refusal};
|
data/rust/src/lib.rs
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hecks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Young
|
|
@@ -162,6 +162,7 @@ files:
|
|
|
162
162
|
- lib/hecks/bluebook/dsl/identity_declaration.rb
|
|
163
163
|
- lib/hecks/bluebook/dsl/lifecycle_builder.rb
|
|
164
164
|
- lib/hecks/bluebook/dsl/malformed.rb
|
|
165
|
+
- lib/hecks/bluebook/dsl/need_word.rb
|
|
165
166
|
- lib/hecks/bluebook/dsl/policy_builder.rb
|
|
166
167
|
- lib/hecks/bluebook/dsl/port_builder.rb
|
|
167
168
|
- lib/hecks/bluebook/dsl/port_operation_builder.rb
|
|
@@ -232,6 +233,7 @@ files:
|
|
|
232
233
|
- lib/hecks/cli/interview_draft.rb
|
|
233
234
|
- lib/hecks/cli/interview_run.rb
|
|
234
235
|
- lib/hecks/cli/interview_session.rb
|
|
236
|
+
- lib/hecks/cli/interview_shape.rb
|
|
235
237
|
- lib/hecks/cli/ir.rb
|
|
236
238
|
- lib/hecks/cli/mcp.rb
|
|
237
239
|
- lib/hecks/cli/model_check.rb
|
|
@@ -273,12 +275,15 @@ files:
|
|
|
273
275
|
- lib/hecks/doors/launcher_options.rb
|
|
274
276
|
- lib/hecks/doors/mcp_door.rb
|
|
275
277
|
- lib/hecks/doors/mcp_door_scope.rb
|
|
278
|
+
- lib/hecks/doors/mcp_guide_cache.rb
|
|
276
279
|
- lib/hecks/doors/ruby_door.rb
|
|
277
280
|
- lib/hecks/doors/ruby_door/aggregate_door.rb
|
|
278
281
|
- lib/hecks/doors/ruby_door/chapter.rb
|
|
279
282
|
- lib/hecks/doors/usage_cache.rb
|
|
280
283
|
- lib/hecks/embryonaut_bluebook.rb
|
|
281
284
|
- lib/hecks/embryonaut_bluebook/lock.rb
|
|
285
|
+
- lib/hecks/embryonaut_bluebook/manifest.rb
|
|
286
|
+
- lib/hecks/embryonaut_bluebook/registry.rb
|
|
282
287
|
- lib/hecks/embryonaut_bluebook/shape.rb
|
|
283
288
|
- lib/hecks/embryonaut_bluebook/vendor.rb
|
|
284
289
|
- lib/hecks/embryonaut_bluebook/vendor_cli.rb
|
|
@@ -386,10 +391,12 @@ files:
|
|
|
386
391
|
- lib/hecks/hecks/adapters/deploy_toolchain.rb
|
|
387
392
|
- lib/hecks/hecks/adapters/finding_github.adapter
|
|
388
393
|
- lib/hecks/hecks/adapters/finding_github.rb
|
|
394
|
+
- lib/hecks/hecks/adapters/first_admin.rb
|
|
389
395
|
- lib/hecks/hecks/adapters/git.adapter
|
|
390
396
|
- lib/hecks/hecks/adapters/git.rb
|
|
391
397
|
- lib/hecks/hecks/adapters/host_http.adapter
|
|
392
398
|
- lib/hecks/hecks/adapters/host_http.rb
|
|
399
|
+
- lib/hecks/hecks/adapters/in_process_access.rb
|
|
393
400
|
- lib/hecks/hecks/adapters/in_process_boot.adapter
|
|
394
401
|
- lib/hecks/hecks/adapters/in_process_boot.rb
|
|
395
402
|
- lib/hecks/hecks/adapters/in_process_operations.rb
|
|
@@ -545,14 +552,22 @@ files:
|
|
|
545
552
|
- lib/hecks/projections.rb
|
|
546
553
|
- lib/hecks/projections/bootstrap_table.rb
|
|
547
554
|
- lib/hecks/projections/deploy/box.rb
|
|
555
|
+
- lib/hecks/projections/deploy/box/hosting.rb
|
|
556
|
+
- lib/hecks/projections/deploy/box/hosting_settings.rb
|
|
548
557
|
- lib/hecks/projections/deploy/box/settings.rb
|
|
549
558
|
- lib/hecks/projections/deploy/box/templates/box.yaml.tmpl
|
|
550
559
|
- lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl
|
|
560
|
+
- lib/hecks/projections/deploy/box/templates/deploy-service.services-roll.part
|
|
561
|
+
- lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl
|
|
562
|
+
- lib/hecks/projections/deploy/box/templates/deploy-service.taskdef-roll.part
|
|
563
|
+
- lib/hecks/projections/deploy/box/templates/deploy-service.taskdef.part
|
|
551
564
|
- lib/hecks/projections/deploy/box/templates/fetch-secrets.sh
|
|
565
|
+
- lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl
|
|
552
566
|
- lib/hecks/projections/deploy/box/templates/rds.yaml.tmpl
|
|
553
567
|
- lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl
|
|
554
568
|
- lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl
|
|
555
569
|
- lib/hecks/projections/deploy/box/templates/restore-to-rds.sh.tmpl
|
|
570
|
+
- lib/hecks/projections/deploy/box/templates/smoke-after-deploy.sh.tmpl
|
|
556
571
|
- lib/hecks/projections/deploy/box/templates/verify-copy.sh.tmpl
|
|
557
572
|
- lib/hecks/projections/deploy/fargate.rb
|
|
558
573
|
- lib/hecks/projections/deploy/fargate/assembly.rb
|
|
@@ -611,11 +626,13 @@ files:
|
|
|
611
626
|
- lib/hecks/projections/site/edge/checks.rb
|
|
612
627
|
- lib/hecks/projections/site/edge/pattern.rb
|
|
613
628
|
- lib/hecks/projections/site/edge/rules.rb
|
|
629
|
+
- lib/hecks/projections/site/live_edge.rb
|
|
614
630
|
- lib/hecks/projections/site/regions.rb
|
|
615
631
|
- lib/hecks/projections/site/routes_ts.rb
|
|
616
632
|
- lib/hecks/projections/site/site_cdn.rb
|
|
617
633
|
- lib/hecks/projections/site/table.rb
|
|
618
634
|
- lib/hecks/projections/site/table/checks.rb
|
|
635
|
+
- lib/hecks/projections/site/table/link_builder.rb
|
|
619
636
|
- lib/hecks/projections/site/table/row_builder.rb
|
|
620
637
|
- lib/hecks/projections/statements.rb
|
|
621
638
|
- lib/hecks/projections/vocabulary.rb
|
|
@@ -732,6 +749,7 @@ files:
|
|
|
732
749
|
- lib/hecks/runtime/interpreting.rb
|
|
733
750
|
- lib/hecks/runtime/invocation.rb
|
|
734
751
|
- lib/hecks/runtime/loader.rb
|
|
752
|
+
- lib/hecks/runtime/needs.rb
|
|
735
753
|
- lib/hecks/runtime/outbox.rb
|
|
736
754
|
- lib/hecks/runtime/policy_interpreter.rb
|
|
737
755
|
- lib/hecks/runtime/port_operation_interpreter.rb
|
|
@@ -760,17 +778,16 @@ files:
|
|
|
760
778
|
- lib/hecks/runtime/value/field_checks.rb
|
|
761
779
|
- lib/hecks/runtime/value/invariant_violation.rb
|
|
762
780
|
- lib/hecks/rust_build.rb
|
|
763
|
-
- lib/hecks/rust_build/append_optionals.rb
|
|
764
781
|
- lib/hecks/rust_build/conformance.rb
|
|
765
782
|
- lib/hecks/rust_build/conformance_fuzz.rb
|
|
766
783
|
- lib/hecks/rust_build/coverage.rb
|
|
767
784
|
- lib/hecks/rust_build/domain_name.rb
|
|
785
|
+
- lib/hecks/rust_build/host.rb
|
|
768
786
|
- lib/hecks/rust_build/kernel_input.rb
|
|
769
787
|
- lib/hecks/rust_build/native_build.rb
|
|
770
788
|
- lib/hecks/rust_build/project_rust.rb
|
|
771
789
|
- lib/hecks/rust_build/project_rust/codegen_run.rb
|
|
772
790
|
- lib/hecks/rust_build/project_rust/root_mod.rb
|
|
773
|
-
- lib/hecks/rust_build/rust_literal.rb
|
|
774
791
|
- lib/hecks/rust_build/wasm.rb
|
|
775
792
|
- lib/hecks/rust_build/wasm_browser.rb
|
|
776
793
|
- lib/hecks/rust_build/write_if_changed.rb
|
|
@@ -782,8 +799,6 @@ files:
|
|
|
782
799
|
- lib/hecks/storehouse.rb
|
|
783
800
|
- lib/hecks/tenancy/bluebook/tenancy.bluebook
|
|
784
801
|
- lib/hecks/tenancy/bluebook/tenancy.hecksagon
|
|
785
|
-
- lib/hecks/three_zero.rb
|
|
786
|
-
- lib/hecks/three_zero/forms.yml
|
|
787
802
|
- lib/hecks/tickets/bluebook/tickets.bluebook
|
|
788
803
|
- lib/hecks/tickets/bluebook/tickets.hecksagon
|
|
789
804
|
- lib/hecks/tickets/bluebook/tickets.ports.hecksagon
|
|
@@ -812,6 +827,7 @@ files:
|
|
|
812
827
|
- lib/hecks/tools/site_routes.rb
|
|
813
828
|
- lib/hecks/tools/tail_merge.rb
|
|
814
829
|
- lib/hecks/tools/tenant_provisioning.rb
|
|
830
|
+
- lib/hecks/tools/tools_doc.rb
|
|
815
831
|
- lib/hecks/tools/translation_audit.rb
|
|
816
832
|
- lib/hecks/tools/translation_scaffold.rb
|
|
817
833
|
- lib/hecks/vendoring.rb
|
|
@@ -833,15 +849,12 @@ files:
|
|
|
833
849
|
- rust/build/Cargo.toml
|
|
834
850
|
- rust/build/src/build_artifact.rs
|
|
835
851
|
- rust/build/src/cargo_sync.rs
|
|
836
|
-
- rust/build/src/json.rs
|
|
837
852
|
- rust/build/src/lineage_pass.rs
|
|
838
853
|
- rust/build/src/main.rs
|
|
839
|
-
- rust/build/src/optional_pass.rs
|
|
840
854
|
- rust/build/src/pipeline.rs
|
|
841
855
|
- rust/build/src/reserved_names.rs
|
|
842
856
|
- rust/build/src/resolve.rs
|
|
843
857
|
- rust/build/src/root.rs
|
|
844
|
-
- rust/build/src/sidecars.rs
|
|
845
858
|
- rust/build/src/subprocess.rs
|
|
846
859
|
- rust/build/src/tmp.rs
|
|
847
860
|
- rust/codegen/.cargo/config.toml
|
|
@@ -865,6 +878,7 @@ files:
|
|
|
865
878
|
- rust/codegen/src/manifest.rs
|
|
866
879
|
- rust/codegen/src/mutations.rs
|
|
867
880
|
- rust/codegen/src/naming.rs
|
|
881
|
+
- rust/codegen/src/optional_pass.rs
|
|
868
882
|
- rust/codegen/src/ports.rs
|
|
869
883
|
- rust/codegen/src/prelude.rs
|
|
870
884
|
- rust/codegen/src/queries.rs
|
|
@@ -874,6 +888,7 @@ files:
|
|
|
874
888
|
- rust/codegen/src/registry.rs
|
|
875
889
|
- rust/codegen/src/reserved_names.rs
|
|
876
890
|
- rust/codegen/src/shared.rs
|
|
891
|
+
- rust/codegen/src/sidecars.rs
|
|
877
892
|
- rust/codegen/src/skip_reason.rs
|
|
878
893
|
- rust/codegen/src/types.rs
|
|
879
894
|
- rust/host/.cargo/config.toml
|
|
@@ -967,34 +982,13 @@ files:
|
|
|
967
982
|
- rust/parser/src/parse/hecksagon.rs
|
|
968
983
|
- rust/parser/src/parse/lifecycle.rs
|
|
969
984
|
- rust/parser/src/parse/mod.rs
|
|
985
|
+
- rust/parser/src/parse/needs.rs
|
|
970
986
|
- rust/parser/src/parse/policy.rs
|
|
971
987
|
- rust/parser/src/parse/process_manager.rs
|
|
972
988
|
- rust/parser/src/parse/query.rs
|
|
973
989
|
- rust/parser/src/parse/read_model.rs
|
|
974
990
|
- rust/parser/src/parse/value_object.rs
|
|
975
991
|
- rust/parser/src/ruby_value.rs
|
|
976
|
-
- rust/project.rb
|
|
977
|
-
- rust/project/bridging.rb
|
|
978
|
-
- rust/project/commands.rb
|
|
979
|
-
- rust/project/constraints.rb
|
|
980
|
-
- rust/project/dependency_planning.rb
|
|
981
|
-
- rust/project/domain_generator.rb
|
|
982
|
-
- rust/project/exemplar.rb
|
|
983
|
-
- rust/project/expr_emitter.rb
|
|
984
|
-
- rust/project/fielded.rb
|
|
985
|
-
- rust/project/json_codec.rb
|
|
986
|
-
- rust/project/mutations.rb
|
|
987
|
-
- rust/project/naming.rb
|
|
988
|
-
- rust/project/ports.rb
|
|
989
|
-
- rust/project/queries.rb
|
|
990
|
-
- rust/project/reactions.rb
|
|
991
|
-
- rust/project/read_models.rb
|
|
992
|
-
- rust/project/reference_specs.rb
|
|
993
|
-
- rust/project/registry.rb
|
|
994
|
-
- rust/project/skip_reason.rb
|
|
995
|
-
- rust/project/types.rb
|
|
996
|
-
- rust/project/write_if_changed.rb
|
|
997
|
-
- rust/project_rust_pipeline.rb
|
|
998
992
|
- rust/src/exemplar/commands.rs
|
|
999
993
|
- rust/src/exemplar/constraints.rs
|
|
1000
994
|
- rust/src/exemplar/fielded.rs
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Hecks
|
|
4
|
-
module RustBuild
|
|
5
|
-
# Makes an appended element's field `Option<T>` in the IR when any command sources it from an
|
|
6
|
-
# optional argument, since Ruby stores the omitted nil without complaint. `ir.json` records
|
|
7
|
-
# the marking, so it runs before the IR is written or generated from.
|
|
8
|
-
#
|
|
9
|
-
# `rust/build/src/optional_pass.rs` is the Ruby-free build's port of this; it looks only at an
|
|
10
|
-
# aggregate's own value objects, where this reads the domain-wide set.
|
|
11
|
-
module AppendOptionals
|
|
12
|
-
# Marks every aggregate in the IR, in place.
|
|
13
|
-
#
|
|
14
|
-
# @param tree [Hash] a domain's IR, symbol-keyed
|
|
15
|
-
# @return [Hash] the same IR
|
|
16
|
-
def self.mark(tree)
|
|
17
|
-
domain_wide = by_name(tree[:aggregates].flat_map { |aggregate| aggregate[:value_objects] })
|
|
18
|
-
tree[:aggregates].each do |aggregate|
|
|
19
|
-
mark_aggregate(aggregate, domain_wide.merge(by_name(aggregate[:value_objects])))
|
|
20
|
-
end
|
|
21
|
-
tree
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# Resolves an `append` target to its entity or value object, or nil if it is neither. A
|
|
25
|
-
# local entity wins over a domain-wide value object of the same name: Syntax's local
|
|
26
|
-
# `Argument` entity shares a name with Command's `Argument` value object.
|
|
27
|
-
#
|
|
28
|
-
# @param aggregate [Hash] the aggregate owning the command
|
|
29
|
-
# @param target_type [String] the appended-to attribute's element type
|
|
30
|
-
# @param value_objects_by_name [Hash{String => Hash}] the value objects in scope
|
|
31
|
-
# @return [Hash, nil] the element, or nil
|
|
32
|
-
def self.element(aggregate, target_type, value_objects_by_name)
|
|
33
|
-
aggregate[:entities].find { |entity| entity[:name] == target_type } ||
|
|
34
|
-
value_objects_by_name[target_type]
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# @param aggregate [Hash] one aggregate of the IR
|
|
38
|
-
# @param value_objects_by_name [Hash{String => Hash}] the value objects in scope
|
|
39
|
-
# @return [void]
|
|
40
|
-
def self.mark_aggregate(aggregate, value_objects_by_name)
|
|
41
|
-
aggregate[:attributes].each do |target_attr|
|
|
42
|
-
target = element(aggregate, target_attr[:type], value_objects_by_name)
|
|
43
|
-
next unless target
|
|
44
|
-
|
|
45
|
-
aggregate[:commands].each do |command|
|
|
46
|
-
appends = command[:mutations].select { |m| append_to?(m, target_attr) }
|
|
47
|
-
appends.each { |mutation| mark_fields(mutation, command, target) }
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def self.append_to?(mutation, target_attr)
|
|
53
|
-
mutation[:op].to_s == "append" && mutation[:target].to_s == target_attr[:name].to_s
|
|
54
|
-
end
|
|
55
|
-
private_class_method :append_to?
|
|
56
|
-
|
|
57
|
-
# A Symbol is an argument name, and an optional one is what this pass wants; anything
|
|
58
|
-
# else is a literal.
|
|
59
|
-
def self.mark_fields(mutation, command, target)
|
|
60
|
-
mutation[:fields].each do |field_name, source|
|
|
61
|
-
parsed = Hecks::Bluebook::Assembly::Marks.read(source)
|
|
62
|
-
next unless parsed.is_a?(Symbol)
|
|
63
|
-
|
|
64
|
-
source_attr = command[:attributes].find { |attr| attr[:name].to_s == parsed.to_s }
|
|
65
|
-
next unless source_attr && source_attr[:optional]
|
|
66
|
-
|
|
67
|
-
field_attr = target[:attributes].find { |attr| attr[:name].to_s == field_name.to_s }
|
|
68
|
-
field_attr[:optional] = true if field_attr
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
private_class_method :mark_fields
|
|
72
|
-
|
|
73
|
-
def self.by_name(value_objects)
|
|
74
|
-
value_objects.to_h { |value_object| [value_object[:name], value_object] }
|
|
75
|
-
end
|
|
76
|
-
private_class_method :by_name
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Hecks
|
|
4
|
-
module RustBuild
|
|
5
|
-
# Rust string literals. Ruby's `String#inspect` escapes for Ruby's own read-back (a
|
|
6
|
-
# brace-less `\uXXXX`, for one), which is not valid Rust syntax.
|
|
7
|
-
module RustLiteral
|
|
8
|
-
# Escapes only what Rust's string-literal grammar needs: backslash, double quote and
|
|
9
|
-
# control characters.
|
|
10
|
-
#
|
|
11
|
-
# @param text [#to_s] the text to embed
|
|
12
|
-
# @return [String] the quoted literal
|
|
13
|
-
def self.string(text)
|
|
14
|
-
escaped = text.to_s.each_char.map { |char| escape(char) }.join
|
|
15
|
-
"\"#{escaped}\""
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def self.escape(char)
|
|
19
|
-
case char
|
|
20
|
-
when "\\" then "\\\\"
|
|
21
|
-
when "\"" then "\\\""
|
|
22
|
-
when "\n" then "\\n"
|
|
23
|
-
when "\r" then "\\r"
|
|
24
|
-
when "\t" then "\\t"
|
|
25
|
-
else
|
|
26
|
-
code = char.ord
|
|
27
|
-
code < 0x20 || code == 0x7F ? format("\\u{%x}", code) : char
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
private_class_method :escape
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|