From c9176dbbcec2a8b6d057f16e6247ebb7922582fa Mon Sep 17 00:00:00 2001 From: lordlogo2002 Date: Fri, 3 Apr 2026 14:20:14 +0200 Subject: [PATCH] Implement CLI framework and refactor main logic; add YAML support in utils --- nut/__main__.py | 29 ++++++++++++---- nut/cli.py | 51 ++++++++++++++++++++++++++++ nut/templates/Nutfile | 0 nut/{ => templates}/builtins/base.py | 4 --- nut/utils.py | 25 +++++++++----- 5 files changed, 90 insertions(+), 19 deletions(-) create mode 100644 nut/cli.py create mode 100644 nut/templates/Nutfile rename nut/{ => templates}/builtins/base.py (99%) diff --git a/nut/__main__.py b/nut/__main__.py index c94b6d1..d4c7761 100644 --- a/nut/__main__.py +++ b/nut/__main__.py @@ -1,11 +1,28 @@ -import sys -from .utils import zip_dir +from cli import CLI -def main() -> int: - for arg in sys.argv[1:]: - print(arg) +def main(): + cli = CLI("nut") + + cli.command("run") \ + .flag("debug", "d") \ + .handle(cmd_run) + + cli.command("build") \ + .handle(cmd_build) + + return cli.run() + +def cmd_run(subcommands, flags): + print("RUN") + print("sub:", subcommands) + print("flags:", flags) return 0 +def cmd_build(subcommands, flags): + print("BUILD") + print("sub:", subcommands) + print("flags:", flags) + return 0 if __name__ == "__main__": - raise SystemExit(main()) + raise SystemExit(main()) \ No newline at end of file diff --git a/nut/cli.py b/nut/cli.py new file mode 100644 index 0000000..8bea7d1 --- /dev/null +++ b/nut/cli.py @@ -0,0 +1,51 @@ +import argparse + +class CLI: + def __init__(self, prog="nut"): + self.parser = argparse.ArgumentParser(prog=prog) + self.subparsers = self.parser.add_subparsers(dest="command", required=True) + + def command(self, name): + parser = self.subparsers.add_parser(name) + + cmd = _Command(parser) + return cmd + + def run(self): + args = self.parser.parse_args() + + # extract structured data + sub_args = getattr(args, "_args", []) + flags = { + k: v for k, v in vars(args).items() + if k not in ("command", "func", "_args") + } + + return args.func(sub_args, flags) + +class _Command: + def __init__(self, parser): + self.parser = parser + self.parser.set_defaults(func=self._missing) + + # default catch-all args + self.parser.add_argument("_args", nargs="*") + + def arg(self, name, **kwargs): + self.parser.add_argument(name, **kwargs) + return self + + def flag(self, name, short=None): + flags = [f"--{name}"] + if short: + flags.append(f"-{short}") + + self.parser.add_argument(*flags, action="store_true") + return self + + def handle(self, func): + self.parser.set_defaults(func=func) + return self + + def _missing(self, *_): + raise RuntimeError("No handler defined for command") \ No newline at end of file diff --git a/nut/templates/Nutfile b/nut/templates/Nutfile new file mode 100644 index 0000000..e69de29 diff --git a/nut/builtins/base.py b/nut/templates/builtins/base.py similarity index 99% rename from nut/builtins/base.py rename to nut/templates/builtins/base.py index b3bd262..dcb6725 100644 --- a/nut/builtins/base.py +++ b/nut/templates/builtins/base.py @@ -8,15 +8,12 @@ class _Jump: def __init__(self, target): self.target = target - class _Jmp(_Jump): pass - class _Call(_Jump): RETURN = object() - class _VM: def __init__(self, entry, ctx=None): self.current = entry @@ -31,7 +28,6 @@ class _VM: print(f"Error: {e}") sys.exit(1) - def loop(self): action = self.current(self.ctx) diff --git a/nut/utils.py b/nut/utils.py index b78565f..15ad053 100644 --- a/nut/utils.py +++ b/nut/utils.py @@ -2,9 +2,9 @@ import os import zipfile import json import fnmatch +import yaml - -def _should_ignore(rel_path, patterns): +def _should_ignore(rel_path: str, patterns: list) -> bool: """ Check if a file should be ignored based on patterns. """ @@ -25,8 +25,7 @@ def _should_ignore(rel_path, patterns): return False - -def zip_dir(source_dir, output_zip_path, metadata=None, ignore_patterns=None): +def zip_dir(source_dir: str, output_zip_path: str, metadata: dict = None, ignore_patterns: list = None) -> None: """ Compress a directory into a zip file with ignore support. @@ -63,16 +62,14 @@ def zip_dir(source_dir, output_zip_path, metadata=None, ignore_patterns=None): if metadata: zipf.writestr("_meta.json", json.dumps(metadata, indent=2)) - -def unzip_to_dir(zip_path, output_dir): +def unzip_to_dir(zip_path: str, output_dir: str) -> None: """ Extract zip file to a directory. """ with zipfile.ZipFile(zip_path, 'r') as zipf: zipf.extractall(output_dir) - -def unzip_in_memory(zip_path): +def unzip_in_memory(zip_path: str) -> dict: """ Read zip contents into memory only. @@ -93,4 +90,14 @@ def unzip_in_memory(zip_path): return { "files": data, "metadata": metadata - } \ No newline at end of file + } + +def read_yaml(path: str) -> dict: + """ + :param path: path to yaml file + :return: parsed dict + """ + with open(path, "r", encoding="utf-8") as f: + data = yaml.safe_load(f) + + return data