mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-02 14:16:56 -06:00
Fix #3965: a malformed or unknown id is refused under the command's name, never replaced by the selection
This commit is contained in:
committed by
bflatastic
parent
8c5714a260
commit
c40113b755
@@ -586,7 +586,9 @@ fn set_gradient_geom(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let stroke = edits_stroke(s, p, item, !s.fill_active, C)?;
|
||||
let item = item.of_kind(s, !stroke);
|
||||
let targets = match item {
|
||||
ItemTarget::Top => leaf_targets(s, &super::given_objects(p).map_or_else(|| selected_roots(s), |ids| Ok(outermost(&s.doc()?.doc, ids)))?)?,
|
||||
ItemTarget::Top => {
|
||||
leaf_targets(s, &super::given_objects(s, p, C)?.map_or_else(|| selected_roots(s), |ids| Ok(outermost(&s.doc()?.doc, ids)))?)?
|
||||
}
|
||||
_ => item.targets(s, p)?,
|
||||
};
|
||||
if targets.is_empty() {
|
||||
|
||||
@@ -367,12 +367,20 @@ pub(crate) fn id_param(p: &Value, key: &str) -> Option<NodeId> {
|
||||
pub(crate) fn ids_param(p: &Value, key: &str) -> Option<Vec<NodeId>> {
|
||||
p.get(key).and_then(Value::as_array).map(|a| a.iter().filter_map(Value::as_u64).map(NodeId).collect())
|
||||
}
|
||||
/// The objects `p` gives: its `ids`, else its `id` as `ids` of one (as [`targets`] reads them);
|
||||
/// `None` when it gives neither.
|
||||
pub(crate) fn given_objects(p: &Value) -> Option<Vec<NodeId>> {
|
||||
match p.get("ids").filter(|v| !v.is_null()) {
|
||||
Some(_) => ids_param(p, "ids"),
|
||||
None => id_param(p, "id").map(|id| vec![id]),
|
||||
/// The objects `p` gives `command`: its `ids`, else its `id` as `ids` of one (as [`targets`] reads
|
||||
/// them), each checked ([`checked_id`]): a malformed one, or one naming no object, is an error,
|
||||
/// never the selection. `None` when it gives neither (or gives them as `null`).
|
||||
pub(crate) fn given_objects(s: &Session, p: &Value, command: &str) -> Result<Option<Vec<NodeId>>> {
|
||||
if p.get("ids").is_some_and(|v| !v.is_null()) {
|
||||
return checked_ids_param(s, p, "ids", command).map(Some);
|
||||
}
|
||||
match p.get("id").filter(|v| !v.is_null()) {
|
||||
None => Ok(None),
|
||||
// A step reference a batch couldn't resolve, as `targets` reports it.
|
||||
Some(Value::String(r)) if r.starts_with('$') => {
|
||||
Err(EngineError::Other(format!("`id` is the step reference {r:?}, which only `run` and `command_batch` resolve")))
|
||||
}
|
||||
Some(v) => Ok(Some(vec![checked_id(s, v, command)?])),
|
||||
}
|
||||
}
|
||||
/// `v` as an index, a count or a byte offset: past `usize::MAX` reads as `usize::MAX`, past the end of any list.
|
||||
|
||||
@@ -684,7 +684,7 @@ fn default_paint(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
/// active item of the proxy's kind is recoloured in each selected object's own stack instead.
|
||||
fn proxy_recolor(s: &mut Session, p: &Value, cmd: &str, label: &str, f: &dyn Fn(Color) -> Color) -> Result<Value> {
|
||||
let stroke = stroke_param(s, p, cmd)?;
|
||||
let ids = match super::given_objects(p) {
|
||||
let ids = match super::given_objects(s, p, cmd)? {
|
||||
Some(ids) => ids,
|
||||
None => super::edit::selected_roots(s)?,
|
||||
};
|
||||
|
||||
@@ -161,6 +161,32 @@ pub(crate) fn takes_key(doc: &'static str, key: &str) -> bool {
|
||||
doc_keys(doc).is_ok_and(|parsed| parsed.keys.contains(&key)) || accepted(doc, 0).is_some_and(|keys| keys.contains(&key))
|
||||
}
|
||||
|
||||
/// Does the description `doc` take `id` as `ids` of one object (`{ids?|id?}`, `{id?, ids?: …
|
||||
/// (instead of id)}`, `{id} or {ids: […]}`): it names both keys, and no params object of it names
|
||||
/// `ids` beside a required `id`, which is something else (`plugin.run {id, ids?}`: the plug-in's)?
|
||||
pub(crate) fn id_stands_for_ids(doc: &'static str) -> bool {
|
||||
fn head(alternative: &str) -> &str {
|
||||
alternative.split([':', '=', '(']).next().unwrap_or_default().trim()
|
||||
}
|
||||
if !takes_key(doc, "ids") || !takes_key(doc, "id") {
|
||||
return false;
|
||||
}
|
||||
let mut rest = doc;
|
||||
while let Ok((body, after)) = object_body(rest) {
|
||||
let entries = split_top(body, ',');
|
||||
let required_id = entries.iter().any(|e| split_top(e, '|').len() == 1 && head(e) == "id");
|
||||
let names_ids = entries.iter().any(|e| split_top(e, '|').iter().any(|a| head(a).trim_end_matches('?').trim_end() == "ids"));
|
||||
if required_id && names_ids {
|
||||
return false;
|
||||
}
|
||||
match after.trim_start().strip_prefix("or ").or_else(|| after.trim_start().strip_prefix("| ")).filter(|r| r.starts_with('{')) {
|
||||
Some(next) => rest = next,
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// The edits (insert, delete, replace, swap of neighbours) that turn `a` into `b`, ignoring case.
|
||||
fn distance(a: &str, b: &str) -> usize {
|
||||
let (a, b): (Vec<char>, Vec<char>) = (a.chars().map(|c| c.to_ascii_lowercase()).collect(), b.chars().map(|c| c.to_ascii_lowercase()).collect());
|
||||
|
||||
Reference in New Issue
Block a user