diff --git a/cptemplate.sh b/cptemplate.sh deleted file mode 100755 index ce56530..0000000 --- a/cptemplate.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bash -# Copy a file from ~/Templates to a given name -# -# Dependencies: -# - fd (soft) -# - fzf - -printHelp() { -cat << EOF -Usage: cptemplate [-h,--help] [options] [filename] - -Options: - -h, --help print this help page -EOF -} - -while true; do - case "${1}" in - '-h'|'--help') - printHelp - exit - ;; - --) - shift - break - ;; - -*) - printf '%s\n' "Unknown option: ${1}" - exit 1 - ;; - *) - break - ;; - esac -done - -# check for existence of fd and fzf binaries -if ! $(which fzf > /dev/null); then - printf '%s\n' 'fzf is not installed on the system' - exit 1 -fi - -declare -a find_opts -template_dir="${HOME}/Templates" - -if [[ -x $(which fd) ]]; then - find_bin=$(which fd) - find_opts+=('--print0') - find_opts+=('--type' 'f') - find_opts+=('--' '.' "${template_dir}") -else - find_bin=$(which find) - find_opts+=("${template_dir}") - find_opts+=('-mindepth' '0') - find_opts+=('-type' 'f') - find_opts+=('-print0') -fi - -template_file="$("${find_bin}" "${find_opts[@]}" | fzf --read0 --select-1 --exit-0 --no-mouse)" -[[ -z "${template_file}" ]] && exit 1 - -cp --interactive --verbose "${template_file}" "${1:-.}" diff --git a/fedit.py b/fedit.py deleted file mode 100755 index a80eb45..0000000 --- a/fedit.py +++ /dev/null @@ -1,230 +0,0 @@ -#!/usr/bin/env python3 -""" -Fuzzy-find a file and edit it. - -Dependencies -============ -* fd -* fzf -""" - -import argparse -import os -import shutil -import subprocess - -from sys import platform - -# ========== Constants ========== -# ----- Paths ----- -BOOT_DIR = "/boot" -ETC_DIR = "/etc" - -# ----- Exit Codes ----- -E_INTERRUPT = 1 -E_NOEDITORFOUND = 2 -E_NOFILESELECTED = 3 - -# ----- Commands ----- - -# Options: show hidden files, null terminator, files only -# Optional arguments: show vcs files, show every file -FIND_CMD = shutil.which("fd") -FIND_OPTS = ["--hidden", "--print0", "--type", "f"] -EXTRA_FIND_OPTS = {"no_ignore_vcs": "--no-ignore", "no_ignore": "--no-ignore-vcs"} - -# Options: null terminator, ignore case, print names matching all non-option arguments -LOCATE_CMD = shutil.which("locate") - -# Options: read null terminator, auto-select if one option, exit if no options, print null terminator -FZF_CMD = shutil.which("fzf") -FZF_OPTS = ["--read0", "--select-1", "--exit-0", "--print0"] - -# Platform-specific options -# macOS doesn't support GNU-style long options -if platform == "linux": - LOCATE_OPTS = ["--all", "--ignore-case", "--null"] -elif platform == "darwin": - LOCATE_OPTS = ["-0", "-i"] - -# ----- Misc. ----- -LOCALE = "utf-8" - - -# ========== Functions ========== -def select_editor(override=None): - """Return a possible canonical path to an editor. - Select an editor from one of: - * -e, --editor - * $EDITOR - * Default of vim - - In this order - - If an editor cannot be resolved, then an Error is raised instead. - - :param override: argument to override an editor - :returns: path to one of these editors - :rtype: str - :raises: FileNotFoundError if an editor could not be resolved - """ - editor = None - - if override is not None: - editor = shutil.which(override) - elif "EDITOR" in os.environ: - editor = shutil.which(os.environ.get("EDITOR")) - elif shutil.which("vim") is not None: - editor = shutil.which("vim") - - if editor is None: - raise FileNotFoundError("An editor could not be resolved") - - return editor - - -def gen_editor_cmd(filename): - """Generate a command line to run for editing a file based on - permissions. - - This command does not pass extra options to the editor, hence - there are no arguments to pass for options. - - :param filename: name of file to edit - :type filename: str or path-like object - :returns: command to execute to edit file - :rtype: list - """ - # Possible for a race condition to occur here - # What happens if the file or its metadata changes? - if os.access(filename, os.W_OK): - return [editor, filename] - else: - return ["sudo", "--edit", filename] - - -def run_fzf(files): - """Run fzf on a stream of searched files for the user to select. - - :param files: stream of null-terminated files to read - :type files: bytes stream (stdout of a completed process) - :returns: selected file - :rtype: str - """ - selected_file = subprocess.run( - [FZF_CMD, *FZF_OPTS], input=files, stdout=subprocess.PIPE - ).stdout - - return selected_file.decode(LOCALE).strip("\x00") - - -def find_files(opts, directory=None): - """Use a find-based program to locate files, then pass to fzf. - - :param opts: options to pass to the find program - :type opts: list of str - :param directory: directory to search for files - :type directory: str - :returns: path of user-selected file - :rtype: bytes - """ - cmd = [FIND_CMD, *opts] - - if directory is not None: - cmd.extend(["--", ".", directory]) - - return subprocess.run(cmd, capture_output=True).stdout - - -def locate_files(patterns): - """Use a locate-based program to locate files, then pass to fzf. - - :param patterns: patterns to pass to locate - :type patterns: list - :returns: path of user-selected file - :rtype: bytes - """ - cmd = [LOCATE_CMD] + LOCATE_OPTS - cmd.extend(patterns) - - return subprocess.run(cmd, capture_output=True).stdout - - -# ========== Main Script ========== -if __name__ == "__main__": - # This script doesn't support Windows - if platform == "windows": - sys.exit(E_INTERRUPT) - - parser = argparse.ArgumentParser() - parser.add_argument( - "-b", - "--boot", - action="store_const", - const=BOOT_DIR, - dest="dir", - help="edit a file in /boot", - ) - parser.add_argument( - "-d", "--dir", dest="dir", type=str, help="edit a file in a given directory" - ) - parser.add_argument( - "-E", - "--etc", - action="store_const", - const=ETC_DIR, - dest="dir", - help="edit a file in /etc", - ) - parser.add_argument( - "-I", - "--no-ignore", - action="append_const", - const="--no-ignore", - dest="extra_find_opts", - help="do not respect .(git|fd)ignore files", - ) - parser.add_argument( - "-i", - "--no-ignore-vcs", - action="append_const", - const="--no-ignore-vcs", - dest="extra_find_opts", - help="do not respect .gitignore files", - ) - parser.add_argument("-e", "--editor", help="use a given editor") - parser.add_argument( - "patterns", type=str, nargs="*", help="patterns to pass to locate" - ) - - args = parser.parse_args() - - user_opts = [] if args.extra_find_opts is None else args.extra_find_opts - user_opts.extend(FIND_OPTS) - - editor = "" - - try: - editor = select_editor(args.editor) - except FileNotFoundError as e: - print(e) - exit(E_NOEDITORFOUND) - - # If patterns were passed, use locate - # Otherwise check for -d and use fd - files = ( - find_files(user_opts, args.dir) - if not args.patterns - else locate_files(args.patterns) - ) - - try: - selected_file = run_fzf(files) - except KeyboardInterrupt: - exit(E_INTERRUPT) - - if selected_file != "": - cmd = gen_editor_cmd(selected_file) - subprocess.run(cmd) - else: - exit(E_NOFILESELECTED) diff --git a/misc/quickdel.sh b/misc/quickdel.sh deleted file mode 100755 index 4d0c09a..0000000 --- a/misc/quickdel.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/bin/bash -## quickdel - delete any file matching a query -## Dependencies: -## * bash -## * fd - -printHelp() { -cat << EOF -Fuzzy find and delete files matching patterns - -Usage: quickdel [-h] [-i] [-I] [patterns] - -Options: - -d, --directories-only only delete directories - -h, --help print this help page - -i, --no-ignore do not ignore .gitignore and .fdignore - -I, --no--ignore-vcs do not ignore .gitignore -EOF -} - -# Pre-run correctness checks -unset files -unset fd_opts -ans= - -declare -a files -declare -a fd_opts -declare -r blue='\033[0;34m' -declare -r nocolor='\033[0;0m' - -[[ -z "$(which fd)" ]] && echo 'fd is not present, cancelling' >&2 && exit 1 - -while true; do - case "${1}" in - '-d'|'--directories-only') - fd_opts+=('--type' 'd') - shift - continue - ;; - '-h'|'--help') - printHelp - exit - ;; - '-i'|'--no-ignore') - fd_opts+=('--no-ignore') - shift - continue - ;; - '-I'|'--no-ignore-vcs') - fd_opts+=('--no-ignore-vcs') - shift - continue - ;; - --) - shift - break - ;; - -*) - printf '%s\n' "Unknown option: ${1}" >&2 - exit 1 - ;; - *) - break - ;; - esac -done - -# Prevent fd from selecting everything -[[ -z "${@}" ]] && printf '%s\n' "No queries entered, cancelling" >&2 && exit 1 - -for pattern in "${@}"; do - while IFS= read -r -d '' file; do - files+=("${file}") - done < <(fd --hidden --print0 "${fd_opts[@]}" -- "${pattern}") -done - -[[ -z "${files[*]}" ]] && printf '%s\n' "No results found" >&2 && exit 1 - -# List all filenames, pretty print them -for filename in "${files[@]}"; do - if [[ -f "${filename}" ]]; then - printf '%s\n' "${filename}" - elif [[ -d "${filename}" ]]; then - printf '%b%s%b\n' "${blue}" "${filename}" "${nocolor}" - fi -done - -printf '%s' "Would you like to delete these files? [y/n] " -read -r -n 1 ans - -if [[ "${ans:-n}" =~ (Y|y) ]]; then - rm --recursive --force -- "${files[@]}" -else - printf '\n%s\n' "Operation cancelled" >&2 - exit 1 -fi diff --git a/quickdel.py b/quickdel.py deleted file mode 100755 index 3610d2b..0000000 --- a/quickdel.py +++ /dev/null @@ -1,182 +0,0 @@ -#!/usr/bin/env python3 -""" -quickdel - delete any file matching a query - -Dependencies -============ -* fd -* python-termcolor - -Command-Line Arguments -====================== -* -d, --directories-only -* -D, --directory TODO: implement this -* -e, --empty-only -* -E, --extension -* -f, --files-only -* -F, --force-directory-delete -* -i, --no-ignore -* -I, --no-ignore-vcs -* -l, --links-only -""" - -import argparse -import os -import os.path -import re -import shutil -import subprocess - -from termcolor import colored - -# ========== Constants ========== -FD_BIN = shutil.which("fd") -FD_OPTS = ["--hidden"] -# Matches 'y' or 'yes' only, ignoring case -USER_RESPONSE_YES = r"^[Yy]{1}([Ee]{1}[Ss]{1})?$" - -E_NO_RESULTS = 1 -E_USER_RESPONSE_NO = 2 -E_INPUT_INTERRUPTED = 3 - - -# ========== Functions ========== -def color_file(filename): - """Return correct color code for filetype of filename. - - Example - ------- - >>> color_file('Test File', 'red') - '\x1b[31mTest String\x1b[0m' - - :param filename: file to determine color output for - :type filename: str - :return: filename with ANSII escape codes for color - :rtype: str - """ - if os.path.isdir(filename): - return colored(filename, "blue") - elif os.path.islink(filename): - return colored(filename, "green") - else: - return filename - - -# ========== Main Script ========== -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument( - "-d", - "--directories-only", - action="store_const", - const=["--type", "directory"], - dest="fd_extra_opts", - help="filter results to directories", - ) - parser.add_argument( - "-e", - "--empty-only", - action="store_const", - const=["--type", "empty"], - dest="fd_extra_opts", - help="filter results to empty files and directories", - ) - parser.add_argument( - "-E", - "--extension", - action="append", - dest="extensions", - help="file extension", - metavar="ext", - ) - parser.add_argument( - "-f", - "--files-only", - action="store_const", - const=["--type", "file"], - dest="fd_extra_opts", - help="filter results to files", - ) - parser.add_argument( - "-F", - "--force-directory-delete", - action="store_true", - help="do not ignore non-empty directories, delete anyways", - ) - parser.add_argument( - "-I", - "--no-ignore-vcs", - action="store_const", - const="--no-ignore-vcs", - dest="fd_extra_opts", - help="do not ignore .gitignore", - ) - parser.add_argument( - "-i", - "--no-ignore", - action="store_const", - const="--no-ignore", - dest="fd_extra_opts", - help="do not ignore .gitignore and .fdignore", - ) - parser.add_argument( - "-l", - "--links-only", - action="store_const", - const=["--type", "symlink"], - dest="fd_extra_opts", - help="filter results to symlinks", - ) - parser.add_argument("patterns", nargs="+", help="file matching patterns") - - args = parser.parse_args() - - if args.fd_extra_opts is not None: - FD_OPTS.extend(args.fd_extra_opts) - if args.extensions is not None: - for ext in args.extensions: - FD_OPTS.extend(["--extension", ext]) - - files = set() - for pattern in args.patterns: - cmd = [FD_BIN, *FD_OPTS, pattern] - files.update( - subprocess.run(cmd, capture_output=True, text=True).stdout.splitlines() - ) - files = sorted(files) - - if files == []: - print(f"No results found, exiting") - exit(E_NO_RESULTS) - - # Pretty print all filenames - for index, filename in enumerate([color_file(f) for f in files], 1): - print(f"{index}. {filename}") - # Padding line - print() - - try: - user_response = input("Would you like to delete these files? ") - except KeyboardInterrupt: - exit(E_INPUT_INTERRUPTED) - - if re.match(USER_RESPONSE_YES, user_response) is None: - print("Operation cancelled") - exit(E_USER_RESPONSE_NO) - - # Remove files first - for f in [fi for fi in files if os.path.isfile(fi)]: - os.remove(f) - - # Check -f, --force-directory-delete option - rmdir_func = shutil.rmtree if args.force_directory_delete else os.rmdir - - for d in filter(os.path.isdir, files): - try: - rmdir_func(d) - except OSError: - print( - f"{colored('Warning', 'yellow')}: {colored(d, 'blue')} is not empty, not deleting directory" - ) - - print(colored("\nDeletions complete", "green")) diff --git a/zsh/completions/_fedit b/zsh/completions/_fedit deleted file mode 100644 index 70c462a..0000000 --- a/zsh/completions/_fedit +++ /dev/null @@ -1,15 +0,0 @@ -#compdef fedit -local arguments - -arguments=( - {-h,--help}'[show this help message and exit]' - {-b,--boot}'[edit a file in /boot]' - {-d,--dir}'[edit a file in a given directory]' - {-E,--etc}'[edit a file in /etc]' - {-e,--editor}'[use a given editor]' - {-I,--no-ignore}'[do not respect .(git|fd)ignore files]' - {-i,--no-ignore-vcs}'[do not respect .gitignore files]' - '*:filename:_files' -) - -_arguments -s $arguments diff --git a/zsh/completions/_quickdel b/zsh/completions/_quickdel deleted file mode 100644 index 566613a..0000000 --- a/zsh/completions/_quickdel +++ /dev/null @@ -1,20 +0,0 @@ -#compdef quickdel - -# ========== Completions ========== -local arguments - -arguments=( - {-d,--directories-only}'[filter results to directories]' - {-e,--empty-only}'[filter results to empty files and directories]' - {-f,--files-only}'[filter results to files]' - {-F,--force-directory-delete}'[do not ignore non-empty directories, delete anyways]' - {-E,--extension}'[file extension]' - {-h,--help}'[print this help page]' - {-i,--no-ignore}'[do not ignore .gitignore and .fdignore]' - {-I,--no-ignore-vcs}'[do not ignore .gitignore]' - {-l,--links-only}'[filter results to symlinks]' - '*:filename:_files' -) - - -_arguments -s $arguments