mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-29 13:16:50 -06:00
Engine: macro recorder applies the macro policy to the steps of a recorded script run
An Automate script run while recording was replaced by all of its steps except automate.* ones, without the policy check that top-level commands get. For example, a script that runs window.activate or macro.list produced a recorded macro that the runner refused on playback. The steps of a recorded script run, and of an inlined macro run, now go through the same policy as top-level commands; refused ones are left out and listed as skipped.
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
//! - commands a macro may not run (saving, opening files…) and other macros' commands are left
|
||||
//! out; a macro run while recording is replaced by its steps when it has no inputs, no output
|
||||
//! and only plain commands (otherwise it is left out), and an Automate script run while
|
||||
//! recording by the script's steps;
|
||||
//! recording by the script's steps, leaving out the steps a macro may not run;
|
||||
//! - a `sheet.activate` or `selection.set` step is added where the sheet or selection changed
|
||||
//! between commands without one, and runs of `selection.set` collapse to the last;
|
||||
//! - with relative references (the default), the selection when recording started is the example
|
||||
@@ -49,6 +49,7 @@ pub fn convert(name: &str, start: &RecordingStart, steps: &[RecordedStep], wb: &
|
||||
// Commands as they'll be played back: (id, params, sheet before, selection before, whether
|
||||
// they came from another macro and stay as written).
|
||||
let mut cmds: Vec<(String, Json, usize, String, Selection, bool)> = Vec::new();
|
||||
let refused = |id: &str, params: &Json| matches!(macro_policy(id, params, crate::Trust::Local), Policy::Deny(_) | Policy::NeedsPermission(_));
|
||||
for st in steps.iter().filter(|st| st.doc == start.doc) {
|
||||
let id = st.id.as_str();
|
||||
if id == "macro.run" {
|
||||
@@ -58,9 +59,12 @@ pub fn convert(name: &str, start: &RecordingStart, steps: &[RecordedStep], wb: &
|
||||
match inner {
|
||||
Some(m) if m.inputs.is_empty() && m.output.anchor.is_none() && m.steps.iter().all(|x| matches!(x.kind, StepKind::Command { .. })) => {
|
||||
for x in m.steps.iter().filter(|x| x.enabled) {
|
||||
if let StepKind::Command { id: cid, params } = &x.kind {
|
||||
cmds.push((cid.clone(), params.clone(), st.sheet, st.sheet_name.clone(), st.selection.clone(), true));
|
||||
let StepKind::Command { id: cid, params } = &x.kind else { continue };
|
||||
if refused(cid, params) {
|
||||
skipped.push(cid.clone());
|
||||
continue;
|
||||
}
|
||||
cmds.push((cid.clone(), params.clone(), st.sheet, st.sheet_name.clone(), st.selection.clone(), true));
|
||||
}
|
||||
}
|
||||
_ => skipped.push(id.to_string()),
|
||||
@@ -72,6 +76,10 @@ pub fn convert(name: &str, start: &RecordingStart, steps: &[RecordedStep], wb: &
|
||||
match st.params.get("name").and_then(Json::as_str).and_then(|n| crate::cmd::extra::script_steps(wb, n)) {
|
||||
Some(steps) => {
|
||||
for (cid, params) in steps {
|
||||
if refused(&cid, ¶ms) {
|
||||
skipped.push(cid);
|
||||
continue;
|
||||
}
|
||||
cmds.push((cid, params, st.sheet, st.sheet_name.clone(), st.selection.clone(), true));
|
||||
}
|
||||
}
|
||||
@@ -79,7 +87,7 @@ pub fn convert(name: &str, start: &RecordingStart, steps: &[RecordedStep], wb: &
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if matches!(macro_policy(id, &st.params, crate::Trust::Local), Policy::Deny(_) | Policy::NeedsPermission(_)) {
|
||||
if refused(id, &st.params) {
|
||||
skipped.push(id.to_string());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1078,3 +1078,20 @@ fn a_recorded_macro_run_is_inlined_only_when_its_steps_stand_alone() {
|
||||
assert_eq!(r["skipped"], json!(["macro.run", "macro.run"]));
|
||||
assert_eq!(recorded(&mut s, "Rec"), ["cell.set {\"cell\":\"C1\",\"input\":\"plain\"}"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_recorded_script_run_leaves_out_the_steps_a_macro_may_not_run() {
|
||||
let mut s = s();
|
||||
let steps = json!([
|
||||
{"command": "cell.set", "params": {"cell": "B1", "input": "kept"}},
|
||||
{"command": "window.activate", "params": {"index": 0}},
|
||||
{"command": "macro.list", "params": {}}
|
||||
]);
|
||||
s.execute("automate.newScript", json!({"name": "Mixed", "commands": steps})).unwrap();
|
||||
s.execute("macro.record", json!({"relative": false})).unwrap();
|
||||
s.execute("automate.runScript", json!({"name": "Mixed"})).unwrap();
|
||||
let r = s.execute("macro.record", json!({"stop": true, "name": "Rec"})).unwrap();
|
||||
assert_eq!(r["skipped"], json!(["window.activate", "macro.list"]));
|
||||
assert_eq!(recorded(&mut s, "Rec"), ["cell.set {\"cell\":\"B1\",\"input\":\"kept\"}"]);
|
||||
s.execute("macro.run", json!({"name": "Rec", "onConflict": "overwrite"})).unwrap();
|
||||
}
|
||||
|
||||
+4
-3
@@ -125,9 +125,10 @@ references the selection when recording starts is the example input: references
|
||||
on its sheet become tokens (on another sheet `C4` stays that sheet's `C4`), and the input's shape,
|
||||
header and column types are inferred. Toggles such as Bold are recorded with the state they set
|
||||
(`"on": true`), so playing them back sets them rather than flipping them. Commands a macro may not
|
||||
run are left out and listed in the result; a macro run while recording is replaced by its steps
|
||||
when it has no inputs, no output and only plain commands (otherwise it is left out and listed), and
|
||||
an Automate script run by the script's steps.
|
||||
run are left out and listed in the result, also from the steps of a macro or script run while
|
||||
recording; a macro run while recording is replaced by its steps when it has no inputs, no output
|
||||
and only plain commands (otherwise it is left out and listed), and an Automate script run by the
|
||||
script's steps.
|
||||
|
||||
## Other commands and storage
|
||||
|
||||
|
||||
Reference in New Issue
Block a user