Add utility modules: encryption, file_utils, git_utils, path_utils
This commit is contained in:
105
confsync/utils/file_utils.py
Normal file
105
confsync/utils/file_utils.py
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
"""File utility functions for ConfSync."""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_file_hash(content: str) -> str:
|
||||||
|
"""Calculate SHA256 hash of content."""
|
||||||
|
return hashlib.sha256(content.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def read_file_safe(path: str, encoding: str = 'utf-8') -> Optional[str]:
|
||||||
|
"""Safely read a file, returning None on error."""
|
||||||
|
try:
|
||||||
|
with open(path, 'r', encoding=encoding) as f:
|
||||||
|
return f.read()
|
||||||
|
except (IOError, OSError, UnicodeDecodeError) as e:
|
||||||
|
print(f"Warning: Could not read file {path}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def write_file_safe(content: str, path: str, encoding: str = 'utf-8') -> bool:
|
||||||
|
"""Safely write to a file, returning success status."""
|
||||||
|
try:
|
||||||
|
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with open(path, 'w', encoding=encoding) as f:
|
||||||
|
f.write(content)
|
||||||
|
return True
|
||||||
|
except (IOError, OSError) as e:
|
||||||
|
print(f"Error: Could not write to file {path}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def find_files_matching(
|
||||||
|
base_path: str,
|
||||||
|
patterns: List[str],
|
||||||
|
recursive: bool = False
|
||||||
|
) -> List[str]:
|
||||||
|
"""Find files matching any of the given patterns."""
|
||||||
|
base = Path(base_path)
|
||||||
|
matches: List[str] = []
|
||||||
|
|
||||||
|
for pattern in patterns:
|
||||||
|
if recursive:
|
||||||
|
matches.extend(str(m) for m in base.rglob(pattern))
|
||||||
|
else:
|
||||||
|
matches.extend(str(m) for m in base.glob(pattern))
|
||||||
|
|
||||||
|
return list(set(matches))
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_size(path: str) -> int:
|
||||||
|
"""Get file size in bytes."""
|
||||||
|
try:
|
||||||
|
return os.path.getsize(path)
|
||||||
|
except OSError:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_mtime(path: str) -> float:
|
||||||
|
"""Get file modification time."""
|
||||||
|
try:
|
||||||
|
return os.path.getmtime(path)
|
||||||
|
except OSError:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_directory(path: str) -> bool:
|
||||||
|
"""Ensure directory exists, creating if necessary."""
|
||||||
|
try:
|
||||||
|
Path(path).mkdir(parents=True, exist_ok=True)
|
||||||
|
return True
|
||||||
|
except OSError as e:
|
||||||
|
print(f"Error creating directory {path}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_line_endings(content: str, style: str = 'unix') -> str:
|
||||||
|
"""Normalize line endings to Unix (LF) or Windows (CRLF) style."""
|
||||||
|
if style == 'windows':
|
||||||
|
return content.replace('\n', '\r\n')
|
||||||
|
return content.replace('\r\n', '\n')
|
||||||
|
|
||||||
|
|
||||||
|
def is_binary_content(content: bytes) -> bool:
|
||||||
|
"""Check if content appears to be binary."""
|
||||||
|
text_chars = bytes([7, 8, 9, 10, 12, 13, 27]) + bytes(range(0x20, 0x100))
|
||||||
|
return bool(content.translate(None, text_chars))
|
||||||
|
|
||||||
|
|
||||||
|
def split_into_lines(content: str, max_line_length: int = 1000) -> List[str]:
|
||||||
|
"""Split content into lines, handling various formats."""
|
||||||
|
return content.splitlines()
|
||||||
|
|
||||||
|
|
||||||
|
def read_binary_file(path: str) -> Optional[bytes]:
|
||||||
|
"""Read a binary file, returning None on error."""
|
||||||
|
try:
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
return f.read()
|
||||||
|
except (IOError, OSError) as e:
|
||||||
|
print(f"Warning: Could not read binary file {path}: {e}")
|
||||||
|
return None
|
||||||
Reference in New Issue
Block a user