import os from abc import abstractmethod from typing import Generator, List from pydantic import BaseModel from ..libs.util.map_path import map_path from .main import Position, Range class FileSystemEdit(BaseModel): @abstractmethod def next_edit(self) -> Generator["FileSystemEdit", None, None]: raise NotImplementedError @abstractmethod def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": raise NotImplementedError class AtomicFileSystemEdit(FileSystemEdit): def next_edit(self) -> Generator["FileSystemEdit", None, None]: yield self class FileEdit(AtomicFileSystemEdit): filepath: str range: Range replacement: str def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": return FileEdit( map_path(self.filepath, orig_root, copy_root), self.range, self.replacement ) @staticmethod def from_deletion(filepath: str, range: Range) -> "FileEdit": return FileEdit(filepath=filepath, range=range, replacement="") @staticmethod def from_insertion(filepath: str, position: Position, content: str) -> "FileEdit": return FileEdit( filepath=filepath, range=Range.from_shorthand( position.line, position.character, position.line, position.character ), replacement=content, ) @staticmethod def from_append( filepath: str, previous_content: str, appended_content: str ) -> "FileEdit": return FileEdit( filepath=filepath, range=Range.from_position(Position.from_end_of_file(previous_content)), replacement=appended_content, ) class FileEditWithFullContents(BaseModel): fileEdit: FileEdit fileContents: str class AddFile(AtomicFileSystemEdit): filepath: str content: str def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": return AddFile( self, map_path(self.filepath, orig_root, copy_root), self.content ) class DeleteFile(AtomicFileSystemEdit): filepath: str def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": return DeleteFile(map_path(self.filepath, orig_root, copy_root)) class RenameFile(AtomicFileSystemEdit): filepath: str new_filepath: str def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": return RenameFile( map_path(self.filepath, orig_root, copy_root), map_path(self.new_filepath, orig_root, copy_root), ) class AddDirectory(AtomicFileSystemEdit): path: str def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": return AddDirectory(map_path(self.path, orig_root, copy_root)) class DeleteDirectory(AtomicFileSystemEdit): path: str def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": return DeleteDirectory(map_path(self.path, orig_root, copy_root)) class RenameDirectory(AtomicFileSystemEdit): path: str new_path: str def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": return RenameDirectory( map_path(self.filepath, orig_root, copy_root), map_path(self.new_path, orig_root, copy_root), ) class DeleteDirectoryRecursive(FileSystemEdit): path: str def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": return DeleteDirectoryRecursive(map_path(self.path, orig_root, copy_root)) def next_edit(self) -> Generator[FileSystemEdit, None, None]: yield DeleteDirectory(path=self.path) for child in os.listdir(self.path): child_path = os.path.join(self.path, child) if os.path.isdir(child_path): yield DeleteDirectoryRecursive(path=child_path) else: yield DeleteFile(filepath=child_path) class SequentialFileSystemEdit(FileSystemEdit): edits: List[FileSystemEdit] def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": return SequentialFileSystemEdit( [edit.with_mapped_paths(orig_root, copy_root) for edit in self.edits] ) def next_edit(self) -> Generator["FileSystemEdit", None, None]: for edit in self.edits: yield from edit.next_edit() class EditDiff(BaseModel): """A reversible edit that can be applied to a file.""" forward: FileSystemEdit backward: FileSystemEdit @classmethod def from_sequence(cls, diffs: List["EditDiff"]) -> "EditDiff": forwards = [] backwards = [] for diff in diffs: forwards.append(diff.forward) backwards.insert(0, diff.backward) return cls( forward=SequentialFileSystemEdit(edits=forwards), backward=SequentialFileSystemEdit(edits=backwards), )