--- /dev/null
+import inspect
+import textwrap
+
+from typing import Callable, Any
+from lib.utils import Schema
+
+def select(val, _if, _else):
+ return _if if val else _else
+
+def get_config_name(name):
+ return f"CONFIG_{name.upper()}"
+
+def cmd(name, *alias):
+ def __cmd(fn: Callable):
+ fn.__annotations__["__CMD__"] = True
+ fn.__annotations__["__NAME__"] = name
+ fn.__annotations__["__ALIAS__"] = [*alias]
+ return fn
+ return __cmd
+
+def imply_schema(fn: Callable):
+ type_map = inspect.get_annotations(fn)
+ arg_list = []
+
+ for arg in inspect.getargs(fn.__code__).args:
+ if arg == "self":
+ continue
+
+ t = type_map.get(arg)
+ t = t if t else Any
+
+ arg_list.append((arg, t))
+
+ return Schema([x[1] for x in arg_list]), arg_list
+
+class ShconfigException(Exception):
+ def __init__(self, *args):
+ super().__init__(*args)
+
+ def __str__(self):
+ return str(self.args[0])
+
+class Executor:
+ def __init__(self, body: Callable):
+ self.name = body.__annotations__["__NAME__"]
+ self.alias = body.__annotations__["__ALIAS__"]
+ self.help = inspect.getdoc(body)
+ self.help = textwrap.dedent(self.help if self.help else "")
+
+ schema, args = imply_schema(body)
+ self.argstr = ' '.join(
+ [f'<{n.upper()}: {t.__name__}>' for n, t in args])
+
+ self.__fn = body
+ self.__argtype = schema
+
+ def match_name(self, name):
+ return self.name == name or name in self.alias
+
+ def try_invoke(self, *args):
+ t_args = [self.__type_mapper(x) for x in args]
+ if self.__argtype != t_args:
+ raise ShconfigException(
+ f"invalid parameter ({args}), expect: ({self.argstr})")
+
+ self.__fn(*t_args)
+
+ def __type_mapper(self, strtype):
+ if strtype in ['True', 'False']:
+ return bool(strtype)
+ if strtype in ['y', 'n']:
+ return bool(strtype == 'y')
+
+ try: return int(strtype)
+ except: pass
+
+ return strtype
+
+ def __str__(self):
+ return '\n'.join([
+ *[f"{name} {self.argstr}" for name in self.alias + [self.name]],
+ textwrap.indent(self.help, '\t')
+ ])
+
+class CmdTable:
+ def __init__(self):
+ self._cmd_map = []
+
+ fns = inspect.getmembers(self,
+ lambda p: isinstance(p, Callable))
+ for _, fn in fns:
+ if not hasattr(fn, "__annotations__"):
+ continue
+ if "__CMD__" not in fn.__annotations__:
+ continue
+
+ self._cmd_map.append(Executor(fn))
+
+ def call(self, name, *args):
+ for exe in self._cmd_map:
+ if exe.match_name(name):
+ exe.try_invoke(*args)
+ return
+
+ raise ShconfigException(
+ f"command not found {name}")
\ No newline at end of file