import logging import os import re from pathlib import Path logger = logging.getLogger(__name__) _ENV_VAR_RE = re.compile(r"\$\{([^}]+)\}") def resolve_env_vars(value: str) -> str: """Replace ${VAR} patterns with environment variable values. Non-recursive: if ${A} expands to a literal ${B}, ${B} is NOT resolved. Unknown variables are replaced with empty string and a warning is logged. """ if not isinstance(value, str): return value def _replacer(match: re.Match) -> str: var_name = match.group(1) env_value = os.environ.get(var_name) if env_value is None: logger.warning("Environment variable %s is not set, using empty string", var_name) return "" return env_value return _ENV_VAR_RE.sub(_replacer, value) def ensure_config_dir(path: Path | None = None) -> Path: """Create ~/.config/redmine-reporter/ with 0o700 permissions. Returns the path to the config directory. No-op if the directory already exists. """ if path is None: path = Path.home() / ".config" / "redmine-reporter" path.mkdir(mode=0o700, parents=True, exist_ok=True) return path def check_file_permissions(path: Path) -> list[str]: """Check that a config file has safe permissions (0o600 or stricter). Returns a list of warning messages. Empty list means no issues. """ warnings: list[str] = [] if not path.exists(): return warnings mode = path.stat().st_mode & 0o777 if mode > 0o600: warnings.append( f"Config file permissions are too open ({mode:04o}). " f"Expected 0600. Fix with: chmod 600 {path}" ) return warnings