mirror of
https://github.com/th30d4y/ExecuTrace.git
synced 2026-05-26 19:36:32 +00:00
first commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""Utility helpers for ExecuTrace."""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
|
||||
|
||||
BLOCK_SIZE = 1024 * 64
|
||||
|
||||
|
||||
def sha256_bytes(data: bytes) -> str:
|
||||
digest = hashlib.sha256()
|
||||
digest.update(data)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def sha256_file(path: str) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with open(path, "rb") as f:
|
||||
while True:
|
||||
block = f.read(BLOCK_SIZE)
|
||||
if not block:
|
||||
break
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
@@ -0,0 +1,69 @@
|
||||
"""Interactive CLI utilities for user input and selection."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
|
||||
def prompt_storage_location(default: str | None = None) -> str:
|
||||
"""Prompt user for storage location with optional default."""
|
||||
if default is None:
|
||||
default = str(Path.home() / ".exectrace" / "workflows")
|
||||
|
||||
prompt_text = f"Enter storage path (press Enter for default: {default}): "
|
||||
user_input = input(prompt_text).strip()
|
||||
|
||||
if not user_input:
|
||||
return default
|
||||
|
||||
# Expand home directory
|
||||
path = Path(user_input).expanduser()
|
||||
|
||||
# Create directory if it doesn't exist
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
return str(path)
|
||||
|
||||
|
||||
def prompt_file_format() -> Literal["json", "xml"]:
|
||||
"""Prompt user to choose file format (JSON or XML)."""
|
||||
while True:
|
||||
print("Choose file format:")
|
||||
print(" 1) JSON (default)")
|
||||
print(" 2) XML")
|
||||
|
||||
choice = input("Enter choice (1 or 2, default: 1): ").strip().lower()
|
||||
|
||||
if not choice or choice == "1":
|
||||
return "json"
|
||||
elif choice == "2":
|
||||
return "xml"
|
||||
else:
|
||||
print("Invalid choice. Please enter 1 or 2.")
|
||||
|
||||
|
||||
def prompt_confirmation(message: str) -> bool:
|
||||
"""Prompt user for yes/no confirmation."""
|
||||
while True:
|
||||
response = input(f"{message} (y/n): ").strip().lower()
|
||||
if response in ("y", "yes"):
|
||||
return True
|
||||
elif response in ("n", "no"):
|
||||
return False
|
||||
else:
|
||||
print("Please enter 'y' or 'n'.")
|
||||
|
||||
|
||||
def list_directories(base_path: str | None = None) -> list[str]:
|
||||
"""List subdirectories in a path for selection (future feature)."""
|
||||
if base_path is None:
|
||||
base_path = str(Path.home())
|
||||
|
||||
try:
|
||||
base = Path(base_path)
|
||||
dirs = sorted([d.name for d in base.iterdir() if d.is_dir()])
|
||||
return dirs
|
||||
except (OSError, PermissionError):
|
||||
return []
|
||||
@@ -0,0 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
def get_logger(name: str = "exectrace", level: int = logging.INFO) -> logging.Logger:
|
||||
logger = logging.getLogger(name)
|
||||
if not logger.handlers:
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s")
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
logger.setLevel(level)
|
||||
return logger
|
||||
@@ -0,0 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Iterable
|
||||
|
||||
DEFAULT_PATTERNS = [
|
||||
re.compile(r"(?i)(api[_-]?key\s*[=:]\s*)([^\s]+)"),
|
||||
re.compile(r"(?i)(token\s*[=:]\s*)([^\s]+)"),
|
||||
re.compile(r"(?i)(password\s*[=:]\s*)([^\s]+)"),
|
||||
re.compile(r"(?i)(secret\s*[=:]\s*)([^\s]+)"),
|
||||
re.compile(r"(?i)(Authorization:\s*Bearer\s+)([^\s]+)"),
|
||||
]
|
||||
|
||||
|
||||
def redact_text(text: str, patterns: Iterable[re.Pattern] | None = None) -> str:
|
||||
"""Redact common secrets from captured command text."""
|
||||
active_patterns = list(patterns) if patterns is not None else DEFAULT_PATTERNS
|
||||
result = text
|
||||
for pattern in active_patterns:
|
||||
result = pattern.sub(r"\1<REDACTED>", result)
|
||||
return result
|
||||
@@ -0,0 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
ISO_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
|
||||
|
||||
|
||||
def utc_now_iso() -> str:
|
||||
"""Return a UTC timestamp in ISO-8601 format."""
|
||||
return datetime.now(tz=timezone.utc).strftime(ISO_FORMAT)
|
||||
Reference in New Issue
Block a user