diff --git a/.gitignore b/.gitignore index 9b57644..a090e06 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ venv/ __pycache__/ build/ -nut.egg-info/ \ No newline at end of file +nut.egg-info/ +*.zip +*.nut \ No newline at end of file diff --git a/nut/__init__.py b/nut/__init__.py index 834f8f3..e69de29 100644 --- a/nut/__init__.py +++ b/nut/__init__.py @@ -1,3 +0,0 @@ -"""nut package.""" - -__version__ = "0.1.0" diff --git a/nut/__main__.py b/nut/__main__.py index 7e659f2..c94b6d1 100644 --- a/nut/__main__.py +++ b/nut/__main__.py @@ -1,5 +1,5 @@ import sys - +from .utils import zip_dir def main() -> int: for arg in sys.argv[1:]: diff --git a/nut/builtins/base.py b/nut/builtins/base.py new file mode 100644 index 0000000..b3bd262 --- /dev/null +++ b/nut/builtins/base.py @@ -0,0 +1,58 @@ +import sys + +class _Exit: + def __init__(self, code): + self.code = code + +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 + self.stack = [] + self.ctx = ctx if ctx is not None else {} + + def start(self): + while self.current is not None: + try: + self.loop() + except Exception as e: + print(f"Error: {e}") + sys.exit(1) + + + def loop(self): + action = self.current(self.ctx) + + if isinstance(action, _Jmp): + self.current = action.target + return + + if isinstance(action, _Call): + self.stack.append(self.current) + self.current = action.target + return + + if action is _Call.RETURN: + if not self.stack: + self.current = None + return + self.current = self.stack.pop() + return + + if isinstance(action, _Exit): + sys.exit(action.code) + + print(f"Unknown action: {action}") + sys.exit(1) diff --git a/nut/utils.py b/nut/utils.py new file mode 100644 index 0000000..b78565f --- /dev/null +++ b/nut/utils.py @@ -0,0 +1,96 @@ +import os +import zipfile +import json +import fnmatch + + +def _should_ignore(rel_path, patterns): + """ + Check if a file should be ignored based on patterns. + """ + rel_path = rel_path.replace("\\", "/") + + for pattern in patterns: + pattern = pattern.strip() + + if not pattern: + continue + + if pattern.endswith("/"): + if rel_path.startswith(pattern): + return True + + if fnmatch.fnmatch(rel_path, pattern): + return True + + return False + + +def zip_dir(source_dir, output_zip_path, metadata=None, ignore_patterns=None): + """ + Compress a directory into a zip file with ignore support. + + :param source_dir: path to folder + :param output_zip_path: where to save zip + :param metadata: dict -> stored as _meta.json inside zip + :param ignore_patterns: list like gitignore ["*.log", "__pycache__/", "build/*"] + """ + ignore_patterns = ignore_patterns or [] + + with zipfile.ZipFile(output_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: + for root, dirs, files in os.walk(source_dir): + + rel_root = os.path.relpath(root, source_dir).replace("\\", "/") + + # filter dirs in-place (important for performance) + dirs[:] = [ + d for d in dirs + if not _should_ignore( + os.path.join(rel_root, d).replace("\\", "/"), + ignore_patterns + ) + ] + + for file in files: + full_path = os.path.join(root, file) + rel_path = os.path.relpath(full_path, source_dir).replace("\\", "/") + + if _should_ignore(rel_path, ignore_patterns): + continue + + zipf.write(full_path, rel_path) + + if metadata: + zipf.writestr("_meta.json", json.dumps(metadata, indent=2)) + + +def unzip_to_dir(zip_path, output_dir): + """ + Extract zip file to a directory. + """ + with zipfile.ZipFile(zip_path, 'r') as zipf: + zipf.extractall(output_dir) + + +def unzip_in_memory(zip_path): + """ + Read zip contents into memory only. + + :return: dict {filename: bytes}, metadata separately if exists + """ + data = {} + metadata = None + + with zipfile.ZipFile(zip_path, 'r') as zipf: + for name in zipf.namelist(): + content = zipf.read(name) + + if name == "_meta.json": + metadata = json.loads(content.decode()) + else: + data[name] = content + + return { + "files": data, + "metadata": metadata + } \ No newline at end of file