Rewrite the lunabuild toolchain with enhanced feature (#60)
[lunaix-os.git] / lunaix-os / scripts / build-tools / shared / shconfig / common.py
diff --git a/lunaix-os/scripts/build-tools/shared/shconfig/common.py b/lunaix-os/scripts/build-tools/shared/shconfig/common.py
new file mode 100644 (file)
index 0000000..c9825e9
--- /dev/null
@@ -0,0 +1,106 @@
+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