scope_resolver.py

Code Hygiene Score: 100

Keine Issues gefunden.

Dependencies 4

Klassen 1

Code

"""Scope Resolver - Pfad-Aufloesung und Glob-Pattern-Matching."""

import os
import glob as globlib
from pathlib import Path
from typing import Optional


class ScopeResolver:
    """Loest Pfade und Glob-Patterns auf."""

    def __init__(self, base_path: str = "/var/www/dev.campus.systemische-tools.de"):
        self.base_path = base_path

    def resolve_paths(
        self,
        scope: dict,
        target_path: Optional[str] = None,
        legacy_applicability: Optional[dict] = None,
    ) -> list[str]:
        """
        Ermittelt zu pruefende Pfade aus Scope.

        Args:
            scope: Contract scope dict mit paths/includes/excludes
            target_path: Optional spezifischer Pfad
            legacy_applicability: Legacy applicability dict

        Returns:
            Liste der aufgeloesten Pfade (dedupliziert)
        """
        if target_path:
            return [target_path]

        paths_list = self._get_paths_list(scope, legacy_applicability)
        excludes = scope.get("excludes", [])

        check_paths = []
        for pattern in paths_list:
            matched = self._expand_glob(pattern)
            for path in matched:
                if not self._is_excluded(path, excludes):
                    check_paths.append(path)

        return list(set(check_paths))

    def _get_paths_list(
        self, scope: dict, legacy_applicability: Optional[dict] = None
    ) -> list[str]:
        """Extrahiert Pfade aus verschiedenen Contract-Formaten."""
        # Neues Standard-Format
        paths_list = scope.get("paths", [])

        # Legacy-Formate als Fallback
        if not paths_list:
            paths_list = scope.get("includes", [])
        if not paths_list:
            paths_list = scope.get("applies_to_paths", [])
        if not paths_list and legacy_applicability:
            paths_list = legacy_applicability.get("scope", [])
            if isinstance(paths_list, str):
                paths_list = [paths_list]

        return paths_list

    def _expand_glob(self, pattern: str) -> list[str]:
        """Expandiert Glob-Pattern zu konkreten Pfaden."""
        full_pattern = os.path.join(self.base_path, pattern.lstrip("/"))
        matched = globlib.glob(full_pattern, recursive=True)

        if matched:
            return matched

        # Fallback: Basisverzeichnis wenn kein Match
        pattern_path = pattern.replace("**", "").replace("*", "").rstrip("/")
        full_path = os.path.join(self.base_path, pattern_path.lstrip("/"))
        if os.path.exists(full_path):
            return [full_path]

        return []

    def _is_excluded(self, path: str, excludes: list[str]) -> bool:
        """Prueft ob Pfad durch Exclude-Pattern ausgeschlossen wird."""
        for excl in excludes:
            excl_pattern = os.path.join(self.base_path, excl.lstrip("/"))
            if globlib.fnmatch.fnmatch(path, excl_pattern):
                return True
        return False

    def file_matches_pattern(self, file_path: str, pattern: str) -> bool:
        """Prueft ob Datei zum Glob-Pattern passt."""
        rel_path = file_path.replace(self.base_path, "").lstrip("/")

        if "**" in pattern:
            parts = pattern.split("**")
            if len(parts) == 2:
                prefix = parts[0].rstrip("/")
                suffix = parts[1].lstrip("/")
                if prefix and not rel_path.startswith(prefix.lstrip("/")):
                    return False
                if suffix and not globlib.fnmatch.fnmatch(rel_path, f"*{suffix}"):
                    return False
                return True
        elif "*" in pattern:
            return globlib.fnmatch.fnmatch(rel_path, pattern)
        else:
            return pattern in rel_path

        return True
← Übersicht Graph