8 from lcfg.common import LConfigEnvironment
11 from lib.utils import join_path
12 from pathlib import Path
15 class ShellException(Exception):
16 def __init__(self, *args: object) -> None:
17 super().__init__(*args)
20 def __init__(self, label, node) -> None:
24 def expand(self, sctx):
30 def write(self, sctx, val):
33 def get_help(self, sctx):
34 return self.node.help_prompt()
36 def get_type(self, sctx):
39 def draw(self, sctx, canvas):
43 class SubviewLink(ViewElement):
44 def __init__(self, label, node, cb) -> None:
45 super().__init__(label, node)
48 def expand(self, sctx):
53 def draw(self, sctx, canvas):
54 print(f" [{self.label}]")
56 def get_type(self, sctx):
57 return f"Collection: {self.label}"
59 class RootWrapper(ViewElement):
60 def __init__(self, root) -> None:
62 super().__init__("root", None)
64 def expand(self, sctx):
66 self.__root.render(sctx)
69 def draw(self, sctx, canvas):
72 def get_type(self, sctx):
75 class FieldView(ViewElement):
76 def __init__(self, label, node) -> None:
77 super().__init__(label, node)
80 return self.node.get_value()
82 def write(self, sctx, val):
83 if self.node.read_only():
86 self.node.set_value(val)
89 def get_type(self, sctx):
90 return f"Config term: {self.label}\n{self.node.get_type()}"
92 def draw(self, sctx, canvas):
94 if self.node.read_only():
96 print(f" {self.label}{suffix}")
98 class ShellContext(RenderContext):
99 def __init__(self) -> None:
106 def add_expandable(self, label, node, on_expand_cb):
107 name = node.get_name()
108 self._view[name] = SubviewLink(name, node, on_expand_cb)
109 self._subviews.append(name)
111 def add_field(self, label, node):
112 name = node.get_name()
113 self._view[name] = FieldView(name, node)
114 self._field.append(name)
116 def clear_view(self):
118 self._subviews.clear()
122 for v in self._subviews + self._field:
123 self._view[v].draw(self, None)
125 def get_view(self, label):
126 if label in self._view:
127 return self._view[label]
130 class InteractiveShell(InteractiveRenderer):
131 def __init__(self, root: Renderable) -> None:
133 self.__levels = [RootWrapper(root)]
134 self.__aborted = True
135 self.__sctx = ShellContext()
138 "list all config node under current collection",
143 "print help prompt for given name of node",
145 self.print_help(*args)
148 "print the type of a config node",
150 self.print_type(*args)
153 "navigate to a collection node given a unix-like path",
158 "print out the usage",
165 l = [level.label for level in self.__levels[1:]]
166 return f"/{'/'.join(l)}"
168 def resolve(self, relative):
170 p = join_path(self.get_path(), relative)
171 ps = Path(p).resolve().parts
175 node = self.__levels[0]
178 if not node.expand(ctx):
179 raise ShellException(f"node is not a collection: {part}")
181 node = ctx.get_view(part)
183 raise ShellException(f"no such node: {part}")
187 return (node, levels)
189 def print_usage(self):
190 for cmd, (desc, _) in self.__cmd.items():
197 def print_help(self, node_name):
198 view, _ = self.resolve(node_name)
200 print(view.get_help(self.__sctx))
202 def print_type(self, node_name):
203 view, _ = self.resolve(node_name)
205 print(view.get_type(self.__sctx))
207 def do_read(self, node_name):
208 view, _ = self.resolve(node_name)
209 rd_val = view.read(self.__sctx)
211 raise ShellException(f"config node {view.label} is not readable")
215 def do_write(self, node_name, val):
216 view, _ = self.resolve(node_name)
217 wr = view.write(self.__sctx, val)
219 raise ShellException(f"config node {view.label} is read only")
221 print(f"write: {val}")
224 def get_in(self, node_name):
225 view, lvl = self.resolve(node_name)
227 if not view.expand(self.__sctx):
228 print(f"{node_name} is not a collection")
235 curr = self.__levels[-1]
236 curr.expand(self.__sctx)
239 prefix = "config: %s> "%(self.__levels[-1].label)
242 args = shlex.split(line)
247 if cmd in self.__cmd:
248 self.__cmd[cmd][1](*args[1:])
252 self.__aborted = False
258 node = self.resolve(cmd)
260 raise ShellException(f"unrecognised command {line}")
262 if len(args) == 3 and args[1] == '=':
263 self.do_write(args[0], args[2])
267 self.do_read(args[0])
270 print(f"unrecognised command {line}")
274 def render_loop(self):
277 "Interactive LConfig Shell",
278 " type 'usage' to find out how to use",
279 " type 'exit' to end (with saving)",
280 " type 'abort' to abort (without saving)",
281 " type node name directly to read the value",
282 " type 'node = val' to set 'val' to 'node'",
287 if not self.__loop():
289 except KeyboardInterrupt:
291 except ConfigTypeCheckError as e:
293 except ShellException as e:
296 return not self.__aborted