Refactor project structure: update .gitignore, and add base and utils modules for improved functionality
This commit is contained in:
parent
df67db7c00
commit
09b585d5f6
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +1,6 @@
|
|||||||
venv/
|
venv/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
build/
|
build/
|
||||||
nut.egg-info/
|
nut.egg-info/
|
||||||
|
*.zip
|
||||||
|
*.nut
|
||||||
@ -1,3 +0,0 @@
|
|||||||
"""nut package."""
|
|
||||||
|
|
||||||
__version__ = "0.1.0"
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import sys
|
import sys
|
||||||
|
from .utils import zip_dir
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
for arg in sys.argv[1:]:
|
for arg in sys.argv[1:]:
|
||||||
|
|||||||
58
nut/builtins/base.py
Normal file
58
nut/builtins/base.py
Normal file
@ -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)
|
||||||
96
nut/utils.py
Normal file
96
nut/utils.py
Normal file
@ -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
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user