diff --git a/pasch/__init__.py b/pasch/__init__.py index 1d2a64a..489cf58 100644 --- a/pasch/__init__.py +++ b/pasch/__init__.py @@ -1,10 +1,10 @@ -from . import cmd, file, modules +from . import file, modules, util from .orchestrator import Module, Orchestrator __all__: list[str] = [ "Module", "Orchestrator", - "cmd", "file", "modules", + "util", ] diff --git a/pasch/cmd.py b/pasch/cmd.py index e981cd5..e69de29 100644 --- a/pasch/cmd.py +++ b/pasch/cmd.py @@ -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 diff --git a/pasch/modules/echo.py b/pasch/modules/echo.py index bdf1b6b..6d96427 100644 --- a/pasch/modules/echo.py +++ b/pasch/modules/echo.py @@ -1,5 +1,5 @@ -from pasch.cmd import run_execute from pasch.orchestrator import Module, Orchestrator +from pasch.util import run_execute class Echo(Module): diff --git a/pasch/modules/files.py b/pasch/modules/files.py index d53aa95..18fe9e8 100644 --- a/pasch/modules/files.py +++ b/pasch/modules/files.py @@ -4,10 +4,12 @@ import random import string from pathlib import Path +from rich.console import Console from rich.markup import escape from pasch.file.file import File from pasch.orchestrator import Module, Orchestrator +from pasch.util import fmt_diff, prompt def random_tmp_path(path: Path) -> Path: @@ -42,6 +44,21 @@ def path_to_str(path: Path) -> str: 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: def __init__(self, path: Path) -> None: self._path = path @@ -128,7 +145,8 @@ class Files(Module): if reason := self._file_db.verify_hash(path, cur_hash): self.c.print(f"[red]Error:[/] {escape(reason)}") - return + if not diff_and_prompt(self.c, path, content): + return # We want to avoid scenarios where we fail to remember a file we've # written. It is better to remember a file with an incorrect hash than diff --git a/pasch/modules/pacman.py b/pasch/modules/pacman.py index bdfc768..9f9fe43 100644 --- a/pasch/modules/pacman.py +++ b/pasch/modules/pacman.py @@ -3,8 +3,8 @@ from subprocess import CalledProcessError from rich.markup import escape -from pasch.cmd import run_capture, run_execute from pasch.orchestrator import Module, Orchestrator +from pasch.util import run_capture, run_execute @dataclass diff --git a/pasch/util.py b/pasch/util.py new file mode 100644 index 0000000..163a926 --- /dev/null +++ b/pasch/util.py @@ -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.")