first commit

This commit is contained in:
w4nn4d13
2026-04-06 13:37:26 +05:30
commit 065eae9134
52 changed files with 1918 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
"""Core models and replay logic for ExecuTrace."""
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+26
View File
@@ -0,0 +1,26 @@
from __future__ import annotations
from typing import Union
from exectrace.core.models import Workflow
from exectrace.storage.factory import get_storage
from exectrace.storage.json_storage import JsonStorage
from exectrace.storage.xml_storage import XmlStorage
class WorkflowEditor:
"""Load, modify, and save workflows programmatically."""
def __init__(self, storage: Union[JsonStorage, XmlStorage] | None = None) -> None:
self.storage = storage or get_storage("json")
def load(self, name: str) -> Workflow:
return self.storage.load_workflow(name)
def save(self, workflow: Workflow) -> None:
self.storage.save_workflow(workflow)
def remove_action(self, workflow: Workflow, index: int) -> None:
if index < 0 or index >= len(workflow.actions):
raise IndexError("Action index out of range")
workflow.actions.pop(index)
+60
View File
@@ -0,0 +1,60 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Dict, List
from exectrace.utils.time_utils import utc_now_iso
@dataclass
class Action:
action_type: str
timestamp: str
payload: Dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]:
return {
"action_type": self.action_type,
"timestamp": self.timestamp,
"payload": self.payload,
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Action":
return cls(
action_type=data["action_type"],
timestamp=data["timestamp"],
payload=data.get("payload", {}),
)
@dataclass
class Workflow:
name: str
version: str = "1.0"
created_at: str = field(default_factory=utc_now_iso)
updated_at: str = field(default_factory=utc_now_iso)
actions: List[Action] = field(default_factory=list)
def add_action(self, action_type: str, payload: Dict[str, Any]) -> None:
self.actions.append(Action(action_type=action_type, timestamp=utc_now_iso(), payload=payload))
self.updated_at = utc_now_iso()
def to_dict(self) -> Dict[str, Any]:
return {
"name": self.name,
"version": self.version,
"created_at": self.created_at,
"updated_at": self.updated_at,
"actions": [action.to_dict() for action in self.actions],
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Workflow":
return cls(
name=data["name"],
version=data.get("version", "1.0"),
created_at=data.get("created_at", utc_now_iso()),
updated_at=data.get("updated_at", utc_now_iso()),
actions=[Action.from_dict(item) for item in data.get("actions", [])],
)
+103
View File
@@ -0,0 +1,103 @@
from __future__ import annotations
import base64
import hashlib
import os
import shutil
import subprocess
from pathlib import Path
from exectrace.core.models import Action, Workflow
from exectrace.storage.json_storage import JsonStorage
from exectrace.utils.logger import get_logger
logger = get_logger(__name__)
class Replayer:
def __init__(self, storage: JsonStorage | None = None) -> None:
self.storage = storage or JsonStorage()
def _signature(self, action: Action) -> str:
serialized = f"{action.action_type}|{action.payload}"
return hashlib.sha256(serialized.encode("utf-8")).hexdigest()
def replay(
self,
workflow: Workflow,
dry_run: bool = False,
explain: bool = False,
smart: bool = False,
) -> int:
completed = self.storage.load_replay_state(workflow.name) if smart else set()
newly_completed = set(completed)
for idx, action in enumerate(workflow.actions, start=1):
signature = self._signature(action)
if smart and signature in completed:
print(f"[{idx}] SKIP (smart): {action.action_type}")
continue
if explain:
print(self._explain_action(idx, action))
if dry_run:
print(f"[{idx}] DRY-RUN: {action.action_type}")
newly_completed.add(signature)
continue
self._execute_action(idx, action)
newly_completed.add(signature)
if smart:
self.storage.save_replay_state(workflow.name, newly_completed)
return len(workflow.actions)
def _explain_action(self, idx: int, action: Action) -> str:
if action.action_type == "command":
cmd = action.payload.get("command", "")
cwd = action.payload.get("cwd", ".")
return f"[{idx}] Execute command in {cwd}: {cmd}"
path = action.payload.get("path", "")
if action.action_type == "file_create":
return f"[{idx}] Create file: {path}"
if action.action_type == "file_modify":
return f"[{idx}] Modify file: {path}"
if action.action_type == "file_delete":
return f"[{idx}] Delete file: {path}"
return f"[{idx}] Unknown action: {action.action_type}"
def _execute_action(self, idx: int, action: Action) -> None:
if action.action_type == "command":
cmd = str(action.payload.get("command", "")).strip()
cwd = str(action.payload.get("cwd", "."))
if not cmd:
logger.warning("[%d] Empty command skipped", idx)
return
print(f"[{idx}] RUN: {cmd}")
result = subprocess.run(cmd, cwd=cwd, shell=True, check=False)
if result.returncode != 0:
raise RuntimeError(f"Command failed with code {result.returncode}: {cmd}")
return
if action.action_type in {"file_create", "file_modify"}:
path = Path(str(action.payload["path"]))
path.parent.mkdir(parents=True, exist_ok=True)
content_b64 = str(action.payload.get("content_b64", ""))
raw = base64.b64decode(content_b64.encode("ascii"))
path.write_bytes(raw)
print(f"[{idx}] WRITE: {path}")
return
if action.action_type == "file_delete":
path = Path(str(action.payload["path"]))
if path.is_dir():
shutil.rmtree(path, ignore_errors=True)
elif path.exists():
os.remove(path)
print(f"[{idx}] DELETE: {path}")
return
logger.warning("[%d] Unknown action type: %s", idx, action.action_type)