81 lines
2.3 KiB
Python
81 lines
2.3 KiB
Python
"""Path utility functions for ConfSync."""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
from typing import List
|
|
|
|
|
|
def expand_path(path: str) -> str:
|
|
"""Expand user home directory and environment variables in path."""
|
|
return os.path.expandvars(os.path.expanduser(path))
|
|
|
|
|
|
def normalize_path(path: str) -> str:
|
|
"""Normalize a path to absolute form."""
|
|
return str(Path(expand_path(path)).resolve())
|
|
|
|
|
|
def get_home_directory() -> str:
|
|
"""Get the user's home directory."""
|
|
return str(Path.home())
|
|
|
|
|
|
def get_xdg_config_dir() -> str:
|
|
"""Get the XDG config directory, falling back to ~/.config."""
|
|
xdg_config = os.environ.get('XDG_CONFIG_HOME')
|
|
if xdg_config:
|
|
return xdg_config
|
|
return os.path.join(get_home_directory(), '.config')
|
|
|
|
|
|
def get_xdg_data_dir() -> str:
|
|
"""Get the XDG data directory, falling back to ~/.local/share."""
|
|
xdg_data = os.environ.get('XDG_DATA_HOME')
|
|
if xdg_data:
|
|
return xdg_data
|
|
return os.path.join(get_home_directory(), '.local', 'share')
|
|
|
|
|
|
def list_directories(path: str) -> List[str]:
|
|
"""List all directories in a path."""
|
|
try:
|
|
return [d for d in os.listdir(path) if os.path.isdir(os.path.join(path, d))]
|
|
except OSError:
|
|
return []
|
|
|
|
|
|
def path_components(path: str) -> List[str]:
|
|
"""Get all components of a path."""
|
|
return list(Path(path).parts)
|
|
|
|
|
|
def get_relative_path(path: str, base: str) -> str:
|
|
"""Get the relative path from base to path."""
|
|
try:
|
|
return str(Path(path).relative_to(base))
|
|
except ValueError:
|
|
return path
|
|
|
|
|
|
def is_subpath(path: str, parent: str) -> bool:
|
|
"""Check if path is a subpath of parent."""
|
|
try:
|
|
path_resolved = Path(expand_path(path)).resolve()
|
|
parent_resolved = Path(expand_path(parent)).resolve()
|
|
return path_resolved == parent_resolved or parent_resolved in path_resolved.parents
|
|
except (OSError, ValueError):
|
|
return False
|
|
|
|
|
|
def safe_join(base: str, *parts: str) -> str:
|
|
"""Safely join paths, preventing directory traversal."""
|
|
base_path = Path(expand_path(base)).resolve()
|
|
|
|
for part in parts:
|
|
if part.startswith('/') or part.startswith('..'):
|
|
if '..' in part:
|
|
part = part.replace('..', '')
|
|
base_path /= part
|
|
|
|
return str(base_path)
|