Skip to content

Commit 67954d3

Browse files
committed
Add action installation/resolving features to NodeTreeRoot
1 parent 43a9c43 commit 67954d3

File tree

3 files changed

+106
-3
lines changed

3 files changed

+106
-3
lines changed

contextshell/Action.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55

66
class Action(ABC):
7+
def __init__(self, name: NodePath):
8+
assert name.is_relative
9+
self.name = name
10+
711
@abstractmethod
812
def __call__(self, target: NodePath, action: NodePath, arguments: Dict[Union[NodePath, int], Any]):
913
raise NotImplementedError()

contextshell/NodeTreeRoot.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from contextshell.Node import Node
22
from contextshell.NodePath import NodePath
33
from contextshell.TreeRoot import TreeRoot
4-
from typing import Callable, List
4+
from contextshell.Action import Action
5+
from typing import Callable, List, Optional
56

67

7-
# TODO: check how implement TemporaryTreeRoot (based on NodeTreeRoot)
8+
# CHECK: how to implement TemporaryTreeRoot (based on NodeTreeRoot)
89
class NodeTreeRoot(TreeRoot):
910
"""Frontend to the (passive) node-based data storage"""
1011
def __init__(self):
@@ -64,6 +65,39 @@ def remove(tree: NodeTreeRoot, target: NodePath, action: NodePath):
6465

6566
self.action_finder.install_action(".", "remove", remove)
6667

68+
def install_global_action(self, action: Action):
69+
self.install_action(NodePath('.'), action)
70+
71+
def install_action(self, target: NodePath, action: Action):
72+
self.create(NodePath.join(target, '@actions', action.name), action)
73+
74+
def find_action(self, target: NodePath, action: NodePath) -> Optional[Action]:
75+
possible_locations = (
76+
target,
77+
NodePath.join(target, '@type'),
78+
NodePath('.')
79+
)
80+
for candidate_path in possible_locations:
81+
action_implementation = self._find_action_in(candidate_path, action)
82+
if action_implementation is not None:
83+
return action_implementation
84+
return None
85+
86+
def _find_action_in(self, target: NodePath, action: NodePath) -> Optional[Action]:
87+
action_node = self._resolve_optional_path(NodePath.join(target, '@actions', action))
88+
if action_node is None:
89+
return None
90+
action_implementation = action_node.get()
91+
if not self._is_action_implementation(action_implementation):
92+
return None
93+
return action_implementation
94+
95+
def _is_action_implementation(self, node_value) -> bool:
96+
return isinstance(node_value, Callable)
97+
98+
# def install_type(self, type: NodeType):
99+
# raise NotImplementedError()
100+
67101
def is_attribute(self, path: NodePath):
68102
return path.startswith('@')
69103

@@ -78,7 +112,7 @@ def list_actions(self, path: NodePath) -> List[NodePath]:
78112

79113
def is_action(self, path: NodePath):
80114
node_value = self.get(path)
81-
return isinstance(node_value, Callable)
115+
return self._is_action_implementation(node_value)
82116

83117
def execute(self, target: NodePath, action: NodePath, *args):
84118
action_impl = self.action_finder.find_action(target, action)

tests/unit/NodeTreeRootTests.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import unittest
2+
from typing import Dict, Union, Any
3+
24
from contextshell.NodePath import NodePath as np
35
from contextshell.NodePath import NodePath
46
from contextshell.TreeRoot import TreeRoot
@@ -280,3 +282,66 @@ def test_node_specific_actions(self):
280282
child_actions = tree.list_actions(child_path)
281283

282284
self.assertSequenceEqual(['test'] + ListActions.default_actions, child_actions)
285+
286+
287+
from contextshell.Action import Action
288+
289+
290+
class FakeAction(Action):
291+
def __init__(self, name: NodePath=np('action')):
292+
super().__init__(name)
293+
294+
def __call__(self, target: NodePath, action: NodePath, arguments: Dict[Union[NodePath, int], Any]):
295+
pass
296+
297+
298+
class FindActionTests(unittest.TestCase):
299+
def test_find_nonexistent_target(self):
300+
tree = create_tree()
301+
global_action = FakeAction(np('action'))
302+
tree.install_global_action(global_action)
303+
304+
found_action = tree.find_action(np('.nonexistent'), np('action'))
305+
306+
self.assertIs(global_action, found_action)
307+
308+
def test_find_nonexistent_action(self):
309+
tree = create_tree()
310+
311+
found_action = tree.find_action(np('.'), np('action'))
312+
313+
self.assertIsNone(found_action)
314+
315+
def test_find_invalid_action(self):
316+
tree = create_tree()
317+
tree.create(np('[email protected]'), 123)
318+
319+
found_action = tree.find_action(np('.'), np('action'))
320+
321+
self.assertIsNone(found_action)
322+
323+
def test_resolve_order_target_before_type(self):
324+
tree = create_tree()
325+
action_name = np('action')
326+
type_action = FakeAction(action_name)
327+
target_action = FakeAction(action_name)
328+
tree.create(np('.target'))
329+
tree.install_action(np('.target.@type'), type_action)
330+
tree.install_action(np('.target'), target_action)
331+
332+
found_action = tree.find_action(np('.target'), action_name)
333+
334+
self.assertIs(target_action, found_action)
335+
336+
def test_resolve_order_type_before_global(self):
337+
tree = create_tree()
338+
tree.create(np('.target.@type'))
339+
action_name = np('action')
340+
global_action = FakeAction(action_name)
341+
type_action = FakeAction(action_name)
342+
tree.install_global_action(global_action)
343+
tree.install_action(np('.target.@type'), type_action)
344+
345+
found_action = tree.find_action(np('.target'), action_name)
346+
347+
self.assertIs(type_action, found_action)

0 commit comments

Comments
 (0)