Prompt user whether to replace file contents

This commit is contained in:
Joscha 2025-11-03 02:23:05 +01:00
parent 8c12e26951
commit 208ccb0de2
6 changed files with 67 additions and 21 deletions

View file

@ -1,10 +1,10 @@
from . import cmd, file, modules from . import file, modules, util
from .orchestrator import Module, Orchestrator from .orchestrator import Module, Orchestrator
__all__: list[str] = [ __all__: list[str] = [
"Module", "Module",
"Orchestrator", "Orchestrator",
"cmd",
"file", "file",
"modules", "modules",
"util",
] ]

View file

@ -1,16 +0,0 @@
import shlex
import subprocess
from rich import print
from rich.markup import escape
def run_execute(*cmd: str) -> None:
print(f"[bright_black]$ {escape(shlex.join(cmd))}")
subprocess.run(cmd, check=True)
def run_capture(*cmd: str) -> str:
print(f"[bright_black]$ {escape(shlex.join(cmd))}")
result = subprocess.run(cmd, check=True, capture_output=True, encoding="utf-8")
return result.stdout

View file

@ -1,5 +1,5 @@
from pasch.cmd import run_execute
from pasch.orchestrator import Module, Orchestrator from pasch.orchestrator import Module, Orchestrator
from pasch.util import run_execute
class Echo(Module): class Echo(Module):

View file

@ -4,10 +4,12 @@ import random
import string import string
from pathlib import Path from pathlib import Path
from rich.console import Console
from rich.markup import escape from rich.markup import escape
from pasch.file.file import File from pasch.file.file import File
from pasch.orchestrator import Module, Orchestrator from pasch.orchestrator import Module, Orchestrator
from pasch.util import fmt_diff, prompt
def random_tmp_path(path: Path) -> Path: def random_tmp_path(path: Path) -> Path:
@ -42,6 +44,21 @@ def path_to_str(path: Path) -> str:
return str(path.resolve()) return str(path.resolve())
def diff_and_prompt(c: Console, path: Path, new_content_bytes: bytes) -> bool:
try:
new_content = new_content_bytes.decode("utf-8")
except:
return False
try:
old_content = path.read_text(encoding="utf-8")
except:
return False
c.print(fmt_diff(old_content, new_content))
return prompt("Replace file contents?", default=False)
class FileDb: class FileDb:
def __init__(self, path: Path) -> None: def __init__(self, path: Path) -> None:
self._path = path self._path = path
@ -128,6 +145,7 @@ class Files(Module):
if reason := self._file_db.verify_hash(path, cur_hash): if reason := self._file_db.verify_hash(path, cur_hash):
self.c.print(f"[red]Error:[/] {escape(reason)}") self.c.print(f"[red]Error:[/] {escape(reason)}")
if not diff_and_prompt(self.c, path, content):
return return
# We want to avoid scenarios where we fail to remember a file we've # We want to avoid scenarios where we fail to remember a file we've

View file

@ -3,8 +3,8 @@ from subprocess import CalledProcessError
from rich.markup import escape from rich.markup import escape
from pasch.cmd import run_capture, run_execute
from pasch.orchestrator import Module, Orchestrator from pasch.orchestrator import Module, Orchestrator
from pasch.util import run_capture, run_execute
@dataclass @dataclass

44
pasch/util.py Normal file
View file

@ -0,0 +1,44 @@
import difflib
import shlex
import subprocess
from rich import print
from rich.markup import escape
from rich.syntax import Syntax
def run_execute(*cmd: str) -> None:
print(f"[bright_black]$ {escape(shlex.join(cmd))}")
subprocess.run(cmd, check=True)
def run_capture(*cmd: str) -> str:
print(f"[bright_black]$ {escape(shlex.join(cmd))}")
result = subprocess.run(cmd, check=True, capture_output=True, encoding="utf-8")
return result.stdout
def fmt_diff(old: str, new: str, old_name="old", new_name="new") -> Syntax:
diff_text = "".join(
difflib.unified_diff(
old.splitlines(keepends=True),
new.splitlines(keepends=True),
fromfile=old_name,
tofile=new_name,
lineterm="\n",
)
)
return Syntax(diff_text, "diff", line_numbers=False)
def prompt(question: str, default: bool | None = None) -> bool:
default_str = {True: "[Y/n]", False: "[y/N]", None: "[y/n]"}[default]
while True:
reply = input(f"{question} {default_str} ").strip().lower()
if not reply and default is not None:
return default
if reply in {"y", "yes"}:
return True
if reply in {"n", "no"}:
return False
print("Please enter y/yes or n/no.")