Skip to content

Instantly share code, notes, and snippets.

@code-yeongyu
Created September 13, 2023 08:54
Show Gist options
  • Save code-yeongyu/6e770ff679790157ff83e5880a2d4b8f to your computer and use it in GitHub Desktop.
Save code-yeongyu/6e770ff679790157ff83e5880a2d4b8f to your computer and use it in GitHub Desktop.
Safely delete with recovery in a sleek CLI interface. Fully compatible with `rm`. Based on `rip`.
#!/usr/bin/env python3
import os
import shutil
import subprocess
try:
from rich.console import Console
from rich.style import Style
except ImportError:
import pip
pip.main(["install", "rich"])
from rich.console import Console
from rich.style import Style
try:
import typer
except ImportError:
import pip
pip.main(["install", "typer"])
import typer
app = typer.Typer()
def is_unix_like() -> bool:
"""Check if the current operating system is Unix-like."""
return os.name == "posix"
def is_rip_available() -> bool:
"""Check if rip is available in the system's PATH."""
return shutil.which("rip") is not None
def rm_remove(
filenames: list[str],
overwrite: bool,
undelete: bool,
same_fs: bool,
directory: bool,
verbose: bool,
interactive: bool,
force: bool,
recursive: bool,
):
Console().print(
"Warning: Your Command calls 'rm'. Deleted files can't be recovered.",
style=Style(color="YELLOW"),
)
cmd = ["/bin/rm"]
if overwrite:
cmd.append("-P")
if undelete:
cmd.append("-W")
if same_fs:
cmd.append("-x")
if directory:
cmd.append("-d")
if verbose:
cmd.append("-v")
if interactive:
cmd.append("-i")
if force:
cmd.append("-f")
if recursive:
cmd.append("-r")
cmd.extend(filenames)
subprocess.run(cmd, check=True)
def rip_remove(
filenames: list[str],
verbose: bool,
force: bool,
interactive: bool,
):
"""Remove files using rip,
but support with verbose, force, and interactive options."""
cmd = ["rip"]
if interactive:
for filename in filenames:
if not os.path.exists(filename):
if force:
continue
Console(stderr=True).print(
f"Error: [bold]{filename}[/bold] does not exist.",
style=Style(color="RED"),
)
raise typer.Exit(code=1)
if interactive:
typer.confirm(
f"Are you sure you want to delete '{typer.style(filename, bold=True)}'?",
abort=True,
)
subprocess.run(cmd + [filename], check=True)
if verbose:
typer.echo(f"Removed {filename}")
else:
# check every filenames are exist
filenames_to_remove = []
for filename in filenames:
if not os.path.exists(filename) and not force:
Console(stderr=True).print(
f"Error: [bold]{filename}[/bold] does not exist.",
style=Style(color="RED"),
)
raise typer.Exit(code=1)
filenames_to_remove.append(filename)
cmd.extend(filenames)
subprocess.run(cmd, check=True)
if verbose:
for filename in filenames_to_remove:
typer.echo(f"Removed {filename}")
console = Console()
console.print("Done! ✨", style=Style(bold=True))
console.print(
"Tip: You can use 'rip -u' command to recover deleted files.", style=Style(color="YELLOW", italic=True)
)
@app.command()
def modern_rm(
filenames: list[str] = typer.Argument(
...,
help="Names of the files or directories to remove",
),
force: bool = typer.Option(
False,
"-f",
"--force",
help="Ignore nonexistent files and arguments, never prompt",
),
interactive: bool = typer.Option(
False,
"-i",
"--interactive",
help="Prompt before every removal",
),
once: bool = typer.Option(
False,
"-I",
"--once",
help=f"Prompt once before removing more than three files, or when removing recursively {typer.style('[calls original rm]', fg=typer.colors.RED)}",
),
directory: bool = typer.Option(
False,
"-d",
"--directory",
help=f"Remove directories {typer.style('[calls original rm]', fg=typer.colors.RED)}",
),
recursive: bool = typer.Option(
False,
"-r",
"--recursive",
help="Remove directories and their contents recursively",
),
overwrite: bool = typer.Option(
False,
"-P",
help=f"Overwrite regular files before deleting them {typer.style('[calls original rm]', fg=typer.colors.RED)}",
),
undelete: bool = typer.Option(
False,
"-W",
help=f"Attempt to undelete the named files {typer.style('[calls original rm]', fg=typer.colors.RED)}",
),
same_fs: bool = typer.Option(
False,
"-x",
help=f"Stay on the same filesystem {typer.style('[calls original rm]', fg=typer.colors.RED)}",
),
verbose: bool = typer.Option(
False,
"-v",
"--verbose",
help="Display detailed information about the deletion process.",
),
):
"""Safely delete with recovery in a sleek CLI interface. Fully compatible with 'rm'. Based on 'rip'.
Written by YeonGyu Kim ([email protected])"""
if not is_unix_like():
console = Console(stderr=True)
console.print(
"Error: This command is only available on Unix-like systems.",
style=Style(color="RED"),
)
raise typer.Exit(code=1)
if not is_rip_available():
console = Console(stderr=True)
console.print(
"Error: 'rip' command is not available. Please install it and try again.",
style=Style(color="RED"),
)
console.print(
"You can install it at 'https://github.com/nivekuil/rip'.",
style=Style(color="YELLOW"),
)
raise typer.Exit(code=1)
if once and len(filenames) > 3:
typer.confirm("Are you sure you want to delete these files?", abort=True)
if force:
interactive = False
is_rm_required = any((overwrite, undelete, same_fs, directory))
if is_rm_required:
rm_remove(
filenames=filenames,
overwrite=overwrite,
undelete=undelete,
same_fs=same_fs,
directory=directory,
verbose=verbose,
interactive=interactive,
force=force,
recursive=recursive,
)
return
rip_remove(
filenames=filenames,
verbose=verbose,
force=force,
interactive=interactive,
)
if __name__ == "__main__":
app()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment