Skip to content

Commit

Permalink
feat: the WF engine should resolves escaped variables
Browse files Browse the repository at this point in the history
* feat: handle escape variable in exchange protocol
* feat: handle escape variable in auto-completion
* feat: state support dot variable
  • Loading branch information
FabienArcellier committed Sep 23, 2024
1 parent 6833960 commit 6ce8c5a
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 17 deletions.
15 changes: 12 additions & 3 deletions src/ui/src/builder/BuilderTemplateInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ const handleComplete = (selectedText) => {
const full = getPath(text);
if (full === null) return;
const keyword = full.at(-1);
const replaced = text.replace(new RegExp(keyword + "$"), selectedText);
const regexKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "$"; // escape the keyword to handle properly on a regex
const replaced = text.replace(new RegExp(regexKeyword), selectedText);
newValue = replaced + newValue.slice(selectionEnd);
emit("input", { target: { value: newValue } });
emit("update:value", newValue);
Expand Down Expand Up @@ -138,6 +140,13 @@ const getPath = (text) => {
return raw.split(".");
};
/**
* Escape a key to support the "." and "\" in a state variable
*/
const escapeVariable = (key) => {
return key.replace("\\", "\\\\").replace(".", "\\.");
};
const handleInput = (ev) => {
emit("input", ev);
emit("update:value", ev.target.value);
Expand All @@ -162,7 +171,7 @@ const showAutocomplete = () => {
const allOptions = Object.entries(_get(ss.getUserState(), path) ?? {}).map(
([key, val]) => ({
text: key,
text: escapeVariable(key),
type: typeToString(val),
}),
);
Expand All @@ -186,7 +195,7 @@ const showAutocomplete = () => {
const handleBlur = () => {
setTimeout(() => {
autocompleteOptions.value = [];
}, 100);
}, 300);
};
</script>

Expand Down
12 changes: 10 additions & 2 deletions src/ui/src/renderer/useEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@ export function useEvaluator(wf: Core) {
s = "";
let level = 0;

for (let i = 0; i < expr.length; i++) {
let i = 0
while (i < expr.length) {
const c = expr.charAt(i);
if (c == ".") {
if (c == "\\") {
if (i + 1 < expr.length) {
s += expr.charAt(i + 1);
i++;
}
} else if (c == ".") {
if (level == 0) {
accessors.push(s);
s = "";
Expand All @@ -44,6 +50,8 @@ export function useEvaluator(wf: Core) {
} else {
s += c;
}

i++
}

if (s) {
Expand Down
26 changes: 17 additions & 9 deletions src/writer/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -872,7 +872,7 @@ def calculated_property(self,

for p in path_list:
state_proxy = self._state_proxy
path_parts = p.split(".")
path_parts = parse_state_variable_expression(p)
for i, path_part in enumerate(path_parts):
if i == len(path_parts) - 1:
local_mutation = MutationSubscription('property', p, handler, self, property_name)
Expand Down Expand Up @@ -1491,28 +1491,36 @@ def parse_expression(self, expr: str, instance_path: Optional[InstancePath] = No
s = ""
level = 0

for c in expr:
if c == ".":
i = 0
while i < len(expr):
character = expr[i]
if character == "\\":
if i + 1 < len(expr):
s += expr[i + 1]
i += 1
elif character == ".":
if level == 0:
accessors.append(s)
s = ""
else:
s += c
elif c == "[":
s += character
elif character == "[":
if level == 0:
accessors.append(s)
s = ""
else:
s += c
s += character
level += 1
elif c == "]":
elif character == "]":
level -= 1
if level == 0:
s = str(self.evaluate_expression(s, instance_path))
else:
s += c
s += character
else:
s += c
s += character

i += 1

if s:
accessors.append(s)
Expand Down
8 changes: 5 additions & 3 deletions tests/backend/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
"counter": 4,
"_private": 3,
# Used as an example of something unserialisable yet pickable
"_private_unserialisable": np.array([[1+2j, 2, 3+3j]])
"_private_unserialisable": np.array([[1+2j, 2, 3+3j]]),
"a.b": 3
}

simple_dict = {"items": {
Expand Down Expand Up @@ -157,7 +158,8 @@ def test_apply_mutation_marker(self) -> None:
'+interests': ['lamps', 'cars'],
'+name': 'Robert',
'+state\\.with\\.dots': None,
'+utfࠀ': 23
'+utfࠀ': 23,
'+a\.b': 3
}

self.sp_simple_dict.apply_mutation_marker()
Expand Down Expand Up @@ -1135,6 +1137,7 @@ def test_evaluate_expression(self) -> None:
assert e.evaluate_expression("features.eyes", instance_path) == "green"
assert e.evaluate_expression("best_feature", instance_path) == "eyes"
assert e.evaluate_expression("features[best_feature]", instance_path) == "green"
assert e.evaluate_expression("a\.b", instance_path) == 3

def test_get_context_data_should_return_the_target_of_event(self) -> None:
"""
Expand Down Expand Up @@ -1186,7 +1189,6 @@ def test_get_context_data_should_return_the_repeater_position_and_the_target_ins
assert context.get("item") == "b"
assert context.get("value") == "B"


class TestSessionManager:

sm = SessionManager()
Expand Down

0 comments on commit 6ce8c5a

Please sign in to comment.