|
| 1 | +import os |
| 2 | +from pathlib import Path |
| 3 | + |
| 4 | + |
| 5 | +class Vfs: |
| 6 | + """ |
| 7 | + Virtual filesystem: write files in-memory, then sync to real fs when done. |
| 8 | + Used to avoid touching files that would not change. |
| 9 | + This avoids invalidating sphinx cache and causing endless rebuild loops w/ |
| 10 | + sphinx-autobuild. |
| 11 | + """ |
| 12 | + def __init__(self): |
| 13 | + self.files: dict[str, VirtualFile] = dict() |
| 14 | + self.files_to_delete: set[Path] = set() |
| 15 | + |
| 16 | + def delete_glob(self, directory: str | Path, glob: str): |
| 17 | + """ |
| 18 | + Glob for all files on disk that were created by the previous build. |
| 19 | + These files should be deleted if this build does not emit them. |
| 20 | + Deletion will not be attempted until this Vfs is synced to disk. |
| 21 | +
|
| 22 | + Doing it this way allows us to leave the files untouched on disk if |
| 23 | + this build would emit an identical file. |
| 24 | + """ |
| 25 | + path = Path(str(directory)) |
| 26 | + for p in path.glob('*.rst'): |
| 27 | + self.files_to_delete.add(p) |
| 28 | + |
| 29 | + def write(self): |
| 30 | + """ |
| 31 | + Sync all files of this Vfs to the real filesystem, and delete any files |
| 32 | + from previous builds. |
| 33 | + """ |
| 34 | + file_paths = [file.path for file in self.files.values()] |
| 35 | + for file in self.files.values(): |
| 36 | + file._write_to_disk() |
| 37 | + for path in self.files_to_delete: |
| 38 | + if not str(path) in file_paths: |
| 39 | + print(f"Deleting {path}") |
| 40 | + os.remove(path) |
| 41 | + |
| 42 | + def exists(self, path: str | Path): |
| 43 | + return str(path) in self.files |
| 44 | + |
| 45 | + def open(self, path: str | Path, mode: str): |
| 46 | + path = str(path) |
| 47 | + modes = set(mode) |
| 48 | + if "b" in modes: |
| 49 | + raise Exception("Binary mode not supported") |
| 50 | + if "r" in modes: |
| 51 | + raise Exception("Reading from VFS not supported.") |
| 52 | + if "a" in modes and path in self.files: |
| 53 | + return self.files[path] |
| 54 | + self.files[path] = file = VirtualFile(path) |
| 55 | + return file |
| 56 | + |
| 57 | + |
| 58 | +class VirtualFile: |
| 59 | + def __init__(self, path: str): |
| 60 | + self.path = path |
| 61 | + self.content = '' |
| 62 | + def write(self, str: str): |
| 63 | + self.content += str |
| 64 | + def close(self): |
| 65 | + pass |
| 66 | + def _write_to_disk(self): |
| 67 | + before = None |
| 68 | + try: |
| 69 | + with open(self.path, "r") as f: |
| 70 | + before = f.read() |
| 71 | + except: |
| 72 | + pass |
| 73 | + if before != self.content: |
| 74 | + print(f"Writing {self.path}") |
| 75 | + with open(self.path, "w") as f: |
| 76 | + f.write(self.content) |
0 commit comments