diff --git a/bash/bin/cptemplate.sh b/bash/bin/cptemplate.sh deleted file mode 100755 index a5396a5..0000000 --- a/bash/bin/cptemplate.sh +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env bash - -# Cleanup -set -e -trap 'exit 1' SIGINT - -# Source library -LIBDIR="/usr/share/file-scripts/" - -for f in "$LIBDIR"/*.sh; do - source "${f}" -done - -DEFAULT_TEMPLATE_DIR="$HOME/Templates" - -# Helper functions -function help() { - cat <&2 - exit 1 - ;; - *) - break - ;; - esac -done - -# If directory wasn't overridden -if [[ -z "$DIR" ]]; then - DIR="$DEFAULT_TEMPLATE_DIR" -fi - -# If no target specified -if [[ -z "$1" ]]; then - printf '%s\n' 'Please specify target name' - exit 1 -fi - -# Check if default template directory exists -if ! [[ -d "$DIR" ]]; then - printf '%s\n' "Template directory doesn't exist, exiting." - exit 2 -fi - -files="$(find_files "$DIR")" -selected_file="$(run_fzf "$files")" - -# Check if target exists -if [[ -f "$1" && -z "$FORCE_OVERWRITE" ]]; then - printf '%s\n' 'File already exists, exiting' - exit 1 -elif [[ -f "$1" && -n "$FORCE_OVERWRITE" ]]; then - cp --verbose --force -- "$selected_file" "$1" -else - cp --verbose -- "$selected_file" "$1" -fi diff --git a/bash/bin/fedit.sh b/bash/bin/fedit.sh deleted file mode 100755 index c0c4050..0000000 --- a/bash/bin/fedit.sh +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env bash - -# Cleanup -set -e -trap 'exit 1' SIGINT - -# Source library -LIBDIR="/usr/share/file-scripts/" - -for f in "$LIBDIR"/*.sh; do - source "${f}" -done - -BOOT_DIR='/boot' -ETC_DIR='/etc' - -# Helper functions -function help() { - cat <&2 - exit 1 - ;; - *) - break - ;; - esac -done - -# Handle -b and -E, they are mutually exclusive -if [[ -n $EDIT_BOOT && -z $EDIT_ETC ]]; then - DIR="$BOOT_DIR" -elif [[ -z $EDIT_BOOT && -n $EDIT_ETC ]]; then - DIR="$ETC_DIR" -elif [[ -n $EDIT_BOOT && -n $EDIT_ETC ]]; then - printf '%s\n' 'Select either --boot or --etc, not both' - exit 1 -elif [[ -z $DIR ]]; then - DIR='.' -fi - -# Handle extra options -declare -a extra_opts - -if [[ -n $NO_IGNORE ]]; then - extra_opts+=('--no-ignore') -elif [[ -n $NO_IGNORE_VCS ]]; then - extra_opts+=('--no-ignore-vcs') -fi - -files="$(find_files $DIR "${extra_opts[@]}")" -selected_file="$(run_fzf "$files")" - -if [[ -w "${selected_file}" ]]; then - "$EDITOR" "$selected_file" -else - sudo --edit "$selected_file" -fi diff --git a/bash/bin/quickdel.sh b/bash/bin/quickdel.sh deleted file mode 100755 index 35e3a55..0000000 --- a/bash/bin/quickdel.sh +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/env bash - -# Cleanup -set -e -trap 'exit 1' SIGINT - -# ========== Source library ========== -LIBDIR="/usr/share/file-scripts/" - -for f in "$LIBDIR"/*.sh; do - source "${f}" -done - -# ========== Constants ========== -RED=$'\e[1;31m' -GREEN=$'\e[1;32m' -BLUE=$'\e[1;34m' -YELLOW=$'\e[1;33m' -WHITE_BOLD=$'\e[1;37m' -RESET=$'\e[0;0m' - -declare -a extra_fd_opts -declare -a typeopts - -# ========== Helper functions ========== -function help() { - cat << HELPMESSAGE -$(basename "$0") $MAJOR_VERSION.$MINOR_VERSION.$PATCH_VERSION - -Usage: $(basename "$0") [-h] [-d] [-e] [-E ext] [-f] [-F] [-I] [-i] [-l] patterns [patterns ...] - -positional arguments: - patterns file matching patterns - -options: - -h, --help show this help message and exit - -d, --directories-only - filter results to directories - -e, --empty-only filter results to empty files and directories - -E ext, --extension ext - file extension - -f, --files-only filter results to files - -F, --force-directory-delete - do not ignore non-empty directories, delete anyways - -I, --no-ignore-vcs do not ignore .gitignore - -i, --no-ignore do not ignore .gitignore and .fdignore - -l, --links-only filter results to symlinks -HELPMESSAGE -} - -# $1 is the output string, $2 is the color -function color_output() { - case "$2" in - 'red') - printf "$RED%s\n" "$1" - ;; - 'green') - printf "$GREEN%s\n" "$1" - ;; - 'blue') - printf "$BLUE%s\n" "$1" - ;; - 'yellow') - printf "$YELLOW%s\n" "$1" - ;; - 'white') - printf "$WHITE_BOLD%s\n" "$1" - ;; - 'reset') - printf "$RESET%s\n" "$1" - ;; - esac -} - -# Color files blue and directories green -function color_path() { - if [[ -f "$1" ]]; then - color_output "$1" 'blue' - elif [[ -d "$1" ]]; then - color_output "$1" 'green' - fi -} - -# Delete path using correct command -# Parameters: -# - $1: path -# - $2: force delete boolean flag -function delete() { - exit 1 -} - -while true; do - case "${1}" in - '-d' | '--directories-only') - typeopts+=(--type directory) - shift - continue - ;; - '-e' | '--empty-only') - typeopts+=(--type empty) - shift - continue - ;; - '-E' | '--extension') - EXT="${2}" - case "${EXT}" in - "") - exit 1 - ;; - -*) - exit 1 - ;; - esac - extra_fd_opts+=('--extension' "$EXT") - shift 2 - continue - ;; - --extension=*) - EXT="${1#*=}" - case "${EXT}" in - "") - exit 1 - ;; - -*) - exit 1 - ;; - esac - extra_fd_opts+=('--extension' "$EXT") - shift - continue - ;; - '-f' | '--files-only') - typeopts+=(--type file) - shift - continue - ;; - '-F' | '--force-directory-delete') - rm_force='--force' - shift - continue - ;; - '-i' | '--no-ignore-vcs') - extra_fd_opts+=('--no-ignore-vcs') - shift - continue - ;; - '-I' | '--no-ignore') - extra_fd_opts+=('--no-ignore') - shift - continue - ;; - '-l' | '--links-only') - typeopts+=(--type symlink) - shift - continue - ;; - '-h' | '--help') - help - exit - ;; - --) - shift - break - ;; - -*) - printf '%s\n' "Unknown option: ${1}" >&2 - exit 1 - ;; - *) - break - ;; - esac -done - -# Interpret options -if [[ -z "$*" ]]; then - help -fi - -declare -a files pattern_results - -for pattern in "$@"; do - readarray -d $'\n' -t pattern_results <<< "$(fd "${DEFAULT_FD_OPTS[@]}" "${extra_fd_opts[@]}" "${typeopts[@]}" "$pattern")" - files+=("${pattern_results[@]}") -done - -# If nothing was found -if [[ -z "${files[*]}" ]]; then - color_output 'No files found, exiting' 'yellow' - exit 1 -fi - -# Sort list of paths before printing -readarray -t paths < <(printf '%s\n' "${files[@]}" | sort --unique) - -# Print list of files found matching criteria -i=1 -for p in "${paths[@]}"; do - printf '%s. %s\n' "$(color_output $i 'white')" "$(color_path "$p")" - i=$((i + 1)) -done - -# Padding between files and prompt -#color_output '' reset - -read -r -n 1 -p 'Would you like to delete these files? [y/N]: ' user_response -# Padding between prompt and output -echo '' - -if [[ "$user_response" =~ (y|Y) ]]; then - for p in "${paths[@]}"; do - if [[ -d "$p" ]]; then - rm --recursive "$rm_force" --verbose -- "$p" || printf '%s %s\n' "$(color_output "Unable to remove path:" 'red')" "$(color_path "$p")" - else - rm "$rm_force" --verbose -- "$p" || printf '%s %s\n' "$(color_output "Unable to remove path:" 'red')" "$(color_path "$p")" - fi - done -fi diff --git a/bin/cptemplate b/bin/cptemplate index e102b2f..a5396a5 100755 --- a/bin/cptemplate +++ b/bin/cptemplate @@ -1,86 +1,113 @@ -#!/usr/bin/python3 -""" -cptemplate - copy a template file from a specified template directory +#!/usr/bin/env bash -Dependencies -============ -* fd -* fzf -""" +# Cleanup +set -e +trap 'exit 1' SIGINT -# Module Imports -import argparse +# Source library +LIBDIR="/usr/share/file-scripts/" -import file_scripts.fzf as fzf -import file_scripts.search as search -import file_scripts.error as error +for f in "$LIBDIR"/*.sh; do + source "${f}" +done -from pathlib import Path -from sys import platform +DEFAULT_TEMPLATE_DIR="$HOME/Templates" +# Helper functions +function help() { + cat <&2 + exit 1 + ;; + *) + break + ;; + esac +done - parser = argparse.ArgumentParser() - parser.add_argument( - "-d", - "--template-dir", - dest="dir", - type=str, - help="choose a template directory (default: ~/Templates)", - ) - parser.add_argument( - "-f", - "--force", - dest="force_overwrite", - action="store_true", - help="overwrite dest if it exists", - ) - parser.add_argument("dest", type=str) +# If directory wasn't overridden +if [[ -z "$DIR" ]]; then + DIR="$DEFAULT_TEMPLATE_DIR" +fi - args = parser.parse_args() +# If no target specified +if [[ -z "$1" ]]; then + printf '%s\n' 'Please specify target name' + exit 1 +fi - template_dir = DEFAULT_TEMPLATE_DIR if args.dir is None else Path(args.dir) +# Check if default template directory exists +if ! [[ -d "$DIR" ]]; then + printf '%s\n' "Template directory doesn't exist, exiting." + exit 2 +fi - # Check if default template directory is non-existent - if not template_dir.exists(): - print(TEMPLATE_DIR_DOESNT_EXIST) - exit(E_TEMP_DIR_NON_EXIST) +files="$(find_files "$DIR")" +selected_file="$(run_fzf "$files")" - files = search.find_files(directory=template_dir) - - try: - selected_file = fzf.select_file_with_fzf(files) - except KeyboardInterrupt: - exit(error.E_INTERRUPT) - except fzf.FZFError as f: - print(f) - exit(f.exit_code) - - dest_file = Path(args.dest) - - try: - dest_file.touch(mode=0o600, exist_ok=False) - except FileExistsError as e: - if args.force_overwrite: - dest_file.touch(mode=0o600, exist_ok=True) - dest_file.write_bytes(selected_file.read_bytes()) - else: - print(e) - exit(error.E_FILE_EXISTS) - else: - dest_file.write_bytes(selected_file.read_bytes()) +# Check if target exists +if [[ -f "$1" && -z "$FORCE_OVERWRITE" ]]; then + printf '%s\n' 'File already exists, exiting' + exit 1 +elif [[ -f "$1" && -n "$FORCE_OVERWRITE" ]]; then + cp --verbose --force -- "$selected_file" "$1" +else + cp --verbose -- "$selected_file" "$1" +fi diff --git a/bin/fedit b/bin/fedit index 7735545..c0c4050 100755 --- a/bin/fedit +++ b/bin/fedit @@ -1,107 +1,130 @@ -#!/usr/bin/env python3 -""" -Fuzzy-find a file and edit it. +#!/usr/bin/env bash -Dependencies -============ -* fd -* fzf -""" +# Cleanup +set -e +trap 'exit 1' SIGINT -import argparse -import subprocess -from sys import platform +# Source library +LIBDIR="/usr/share/file-scripts/" -import file_scripts.fzf as fzf -import file_scripts.editor as editor -import file_scripts.search as search -import file_scripts.error as error +for f in "$LIBDIR"/*.sh; do + source "${f}" +done -# ========== Constants ========== -# ----- Paths ----- -BOOT_DIR = "/boot" -ETC_DIR = "/etc" +BOOT_DIR='/boot' +ETC_DIR='/etc' -# ========== Functions ========== +# Helper functions +function help() { + cat <&2 + exit 1 + ;; + *) + break + ;; + esac +done - user_opts = [] if args.extra_find_opts is None else args.extra_find_opts - user_opts.extend(search.DEFAULT_FIND_OPTS) +# Handle -b and -E, they are mutually exclusive +if [[ -n $EDIT_BOOT && -z $EDIT_ETC ]]; then + DIR="$BOOT_DIR" +elif [[ -z $EDIT_BOOT && -n $EDIT_ETC ]]; then + DIR="$ETC_DIR" +elif [[ -n $EDIT_BOOT && -n $EDIT_ETC ]]; then + printf '%s\n' 'Select either --boot or --etc, not both' + exit 1 +elif [[ -z $DIR ]]; then + DIR='.' +fi - selected_editor = None +# Handle extra options +declare -a extra_opts - try: - selected_editor = editor.select_editor(args.editor) - except FileNotFoundError as e: - print(e) - exit(error.E_NOEDITORFOUND) +if [[ -n $NO_IGNORE ]]; then + extra_opts+=('--no-ignore') +elif [[ -n $NO_IGNORE_VCS ]]; then + extra_opts+=('--no-ignore-vcs') +fi - # If patterns were passed, use locate - # Otherwise check for -d and use fd - files = ( - search.find_files(opts=user_opts, directory=args.dir) - if not args.patterns - else search.locate_files(args.patterns) - ) +files="$(find_files $DIR "${extra_opts[@]}")" +selected_file="$(run_fzf "$files")" - try: - selected_file = fzf.select_file_with_fzf(files) - except KeyboardInterrupt: - exit() - except fzf.FZFError as e: - exit(e.exit_code) - - if selected_file != "": - cmd = editor.gen_editor_cmd(selected_editor, selected_file) - subprocess.run(cmd) - else: - exit(error.E_NOFILESELECTED) +if [[ -w "${selected_file}" ]]; then + "$EDITOR" "$selected_file" +else + sudo --edit "$selected_file" +fi diff --git a/bin/quickdel b/bin/quickdel index 6d48921..35e3a55 100755 --- a/bin/quickdel +++ b/bin/quickdel @@ -1,179 +1,218 @@ -#!/usr/bin/env python3 -""" -quickdel - delete any file matching a query +#!/usr/bin/env bash -Dependencies -============ -* fd -* python-termcolor +# Cleanup +set -e +trap 'exit 1' SIGINT -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 -""" +# ========== Source library ========== +LIBDIR="/usr/share/file-scripts/" -import argparse -import os -import os.path -import re -import shutil -import file_scripts.error as error -import file_scripts.search as search - -from termcolor import colored +for f in "$LIBDIR"/*.sh; do + source "${f}" +done # ========== Constants ========== -fd_opts = ["--hidden"] -# Matches 'y' or 'yes' only, ignoring case -USER_RESPONSE_YES = r"^[Yy]{1}([Ee]{1}[Ss]{1})?$" +RED=$'\e[1;31m' +GREEN=$'\e[1;32m' +BLUE=$'\e[1;34m' +YELLOW=$'\e[1;33m' +WHITE_BOLD=$'\e[1;37m' +RESET=$'\e[0;0m' +declare -a extra_fd_opts +declare -a typeopts -# ========== Functions ========== -def color_file(filename): - """Return correct color code for filetype of filename. +# ========== Helper functions ========== +function help() { + cat << HELPMESSAGE +$(basename "$0") $MAJOR_VERSION.$MINOR_VERSION.$PATCH_VERSION - Example - ------- - >>> color_file('Test File', 'red') - '\x1b[31mTest String\x1b[0m' +Usage: $(basename "$0") [-h] [-d] [-e] [-E ext] [-f] [-F] [-I] [-i] [-l] patterns [patterns ...] - :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 +positional arguments: + patterns file matching patterns +options: + -h, --help show this help message and exit + -d, --directories-only + filter results to directories + -e, --empty-only filter results to empty files and directories + -E ext, --extension ext + file extension + -f, --files-only filter results to files + -F, --force-directory-delete + do not ignore non-empty directories, delete anyways + -I, --no-ignore-vcs do not ignore .gitignore + -i, --no-ignore do not ignore .gitignore and .fdignore + -l, --links-only filter results to symlinks +HELPMESSAGE +} -# ========== 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") +# $1 is the output string, $2 is the color +function color_output() { + case "$2" in + 'red') + printf "$RED%s\n" "$1" + ;; + 'green') + printf "$GREEN%s\n" "$1" + ;; + 'blue') + printf "$BLUE%s\n" "$1" + ;; + 'yellow') + printf "$YELLOW%s\n" "$1" + ;; + 'white') + printf "$WHITE_BOLD%s\n" "$1" + ;; + 'reset') + printf "$RESET%s\n" "$1" + ;; + esac +} - args = parser.parse_args() +# Color files blue and directories green +function color_path() { + if [[ -f "$1" ]]; then + color_output "$1" 'blue' + elif [[ -d "$1" ]]; then + color_output "$1" 'green' + fi +} - 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]) +# Delete path using correct command +# Parameters: +# - $1: path +# - $2: force delete boolean flag +function delete() { + exit 1 +} - files = set() - for p in args.patterns: - files.update( - search.find_files(opts=fd_opts, capture_text=True, pattern=p).splitlines() - ) - files = sorted(files) +while true; do + case "${1}" in + '-d' | '--directories-only') + typeopts+=(--type directory) + shift + continue + ;; + '-e' | '--empty-only') + typeopts+=(--type empty) + shift + continue + ;; + '-E' | '--extension') + EXT="${2}" + case "${EXT}" in + "") + exit 1 + ;; + -*) + exit 1 + ;; + esac + extra_fd_opts+=('--extension' "$EXT") + shift 2 + continue + ;; + --extension=*) + EXT="${1#*=}" + case "${EXT}" in + "") + exit 1 + ;; + -*) + exit 1 + ;; + esac + extra_fd_opts+=('--extension' "$EXT") + shift + continue + ;; + '-f' | '--files-only') + typeopts+=(--type file) + shift + continue + ;; + '-F' | '--force-directory-delete') + rm_force='--force' + shift + continue + ;; + '-i' | '--no-ignore-vcs') + extra_fd_opts+=('--no-ignore-vcs') + shift + continue + ;; + '-I' | '--no-ignore') + extra_fd_opts+=('--no-ignore') + shift + continue + ;; + '-l' | '--links-only') + typeopts+=(--type symlink) + shift + continue + ;; + '-h' | '--help') + help + exit + ;; + --) + shift + break + ;; + -*) + printf '%s\n' "Unknown option: ${1}" >&2 + exit 1 + ;; + *) + break + ;; + esac +done - if files == []: - print("No results found, exiting") - exit(error.E_NO_RESULTS) +# Interpret options +if [[ -z "$*" ]]; then + help +fi - # Pretty print all filenames - for index, filename in enumerate([color_file(f) for f in files], 1): - print(f"{index}. {filename}") - # Padding line - print() +declare -a files pattern_results - try: - user_response = input("Would you like to delete these files? [y/N]: ") - except KeyboardInterrupt: - exit(error.E_INTERRUPT) +for pattern in "$@"; do + readarray -d $'\n' -t pattern_results <<< "$(fd "${DEFAULT_FD_OPTS[@]}" "${extra_fd_opts[@]}" "${typeopts[@]}" "$pattern")" + files+=("${pattern_results[@]}") +done - if re.match(USER_RESPONSE_YES, user_response) is None: - print("Operation cancelled") - exit(error.E_USER_RESPONSE_NO) +# If nothing was found +if [[ -z "${files[*]}" ]]; then + color_output 'No files found, exiting' 'yellow' + exit 1 +fi - # Remove files first - for f in [fi for fi in files if os.path.isfile(fi)]: - os.remove(f) +# Sort list of paths before printing +readarray -t paths < <(printf '%s\n' "${files[@]}" | sort --unique) - # Check -f, --force-directory-delete option - # shutil.rmtree forcibly removes directories - # os.rmdir removes directories only if they are empty - rmdir_func = shutil.rmtree if args.force_directory_delete else os.rmdir +# Print list of files found matching criteria +i=1 +for p in "${paths[@]}"; do + printf '%s. %s\n' "$(color_output $i 'white')" "$(color_path "$p")" + i=$((i + 1)) +done - 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" - ) +# Padding between files and prompt +#color_output '' reset - print(colored("\nDeletions complete", "green")) +read -r -n 1 -p 'Would you like to delete these files? [y/N]: ' user_response +# Padding between prompt and output +echo '' + +if [[ "$user_response" =~ (y|Y) ]]; then + for p in "${paths[@]}"; do + if [[ -d "$p" ]]; then + rm --recursive "$rm_force" --verbose -- "$p" || printf '%s %s\n' "$(color_output "Unable to remove path:" 'red')" "$(color_path "$p")" + else + rm "$rm_force" --verbose -- "$p" || printf '%s %s\n' "$(color_output "Unable to remove path:" 'red')" "$(color_path "$p")" + fi + done +fi diff --git a/bash/fzf.sh b/lib/fzf.sh similarity index 100% rename from bash/fzf.sh rename to lib/fzf.sh diff --git a/bash/search.sh b/lib/search.sh similarity index 100% rename from bash/search.sh rename to lib/search.sh diff --git a/bash/version.sh b/lib/version.sh similarity index 100% rename from bash/version.sh rename to lib/version.sh diff --git a/python/bin/cptemplate b/python/bin/cptemplate new file mode 100755 index 0000000..e102b2f --- /dev/null +++ b/python/bin/cptemplate @@ -0,0 +1,86 @@ +#!/usr/bin/python3 +""" +cptemplate - copy a template file from a specified template directory + +Dependencies +============ +* fd +* fzf +""" + +# Module Imports +import argparse + +import file_scripts.fzf as fzf +import file_scripts.search as search +import file_scripts.error as error + +from pathlib import Path +from sys import platform + + +# ========== Constants ========== +# ----- Paths ----- +DEFAULT_TEMPLATE_DIR = Path.home() / "Templates" + +# ----- Error Messages ----- +TEMPLATE_DIR_DOESNT_EXIST = "Warning: template dir does not exist, exiting." + +# ----- Exit Codes ----- +E_TEMP_DIR_NON_EXIST = 1 + +# ========== Functions========== +# ========== Main Script ========== +if __name__ == "__main__": + if platform == "win32": + exit(error.E_INTERRUPT) + + parser = argparse.ArgumentParser() + parser.add_argument( + "-d", + "--template-dir", + dest="dir", + type=str, + help="choose a template directory (default: ~/Templates)", + ) + parser.add_argument( + "-f", + "--force", + dest="force_overwrite", + action="store_true", + help="overwrite dest if it exists", + ) + parser.add_argument("dest", type=str) + + args = parser.parse_args() + + template_dir = DEFAULT_TEMPLATE_DIR if args.dir is None else Path(args.dir) + + # Check if default template directory is non-existent + if not template_dir.exists(): + print(TEMPLATE_DIR_DOESNT_EXIST) + exit(E_TEMP_DIR_NON_EXIST) + + files = search.find_files(directory=template_dir) + + try: + selected_file = fzf.select_file_with_fzf(files) + except KeyboardInterrupt: + exit(error.E_INTERRUPT) + except fzf.FZFError as f: + print(f) + exit(f.exit_code) + + dest_file = Path(args.dest) + + try: + dest_file.touch(mode=0o600, exist_ok=False) + except FileExistsError as e: + if args.force_overwrite: + dest_file.touch(mode=0o600, exist_ok=True) + dest_file.write_bytes(selected_file.read_bytes()) + else: + print(e) + exit(error.E_FILE_EXISTS) + else: + dest_file.write_bytes(selected_file.read_bytes()) diff --git a/python/bin/fedit b/python/bin/fedit new file mode 100755 index 0000000..7735545 --- /dev/null +++ b/python/bin/fedit @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +""" +Fuzzy-find a file and edit it. + +Dependencies +============ +* fd +* fzf +""" + +import argparse +import subprocess +from sys import platform + +import file_scripts.fzf as fzf +import file_scripts.editor as editor +import file_scripts.search as search +import file_scripts.error as error + +# ========== Constants ========== +# ----- Paths ----- +BOOT_DIR = "/boot" +ETC_DIR = "/etc" + +# ========== Functions ========== + + +# ========== Main Script ========== +if __name__ == "__main__": + # This script doesn't support Windows + if platform == "win32": + exit(error.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=search.EXTRA_FIND_OPTS["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=search.EXTRA_FIND_OPTS["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(search.DEFAULT_FIND_OPTS) + + selected_editor = None + + try: + selected_editor = editor.select_editor(args.editor) + except FileNotFoundError as e: + print(e) + exit(error.E_NOEDITORFOUND) + + # If patterns were passed, use locate + # Otherwise check for -d and use fd + files = ( + search.find_files(opts=user_opts, directory=args.dir) + if not args.patterns + else search.locate_files(args.patterns) + ) + + try: + selected_file = fzf.select_file_with_fzf(files) + except KeyboardInterrupt: + exit() + except fzf.FZFError as e: + exit(e.exit_code) + + if selected_file != "": + cmd = editor.gen_editor_cmd(selected_editor, selected_file) + subprocess.run(cmd) + else: + exit(error.E_NOFILESELECTED) diff --git a/python/bin/quickdel b/python/bin/quickdel new file mode 100755 index 0000000..6d48921 --- /dev/null +++ b/python/bin/quickdel @@ -0,0 +1,179 @@ +#!/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 file_scripts.error as error +import file_scripts.search as search + +from termcolor import colored + +# ========== Constants ========== +fd_opts = ["--hidden"] +# Matches 'y' or 'yes' only, ignoring case +USER_RESPONSE_YES = r"^[Yy]{1}([Ee]{1}[Ss]{1})?$" + + +# ========== 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 p in args.patterns: + files.update( + search.find_files(opts=fd_opts, capture_text=True, pattern=p).splitlines() + ) + files = sorted(files) + + if files == []: + print("No results found, exiting") + exit(error.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? [y/N]: ") + except KeyboardInterrupt: + exit(error.E_INTERRUPT) + + if re.match(USER_RESPONSE_YES, user_response) is None: + print("Operation cancelled") + exit(error.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 + # shutil.rmtree forcibly removes directories + # os.rmdir removes directories only if they are empty + 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/file_scripts/__init__.py b/python/file_scripts/__init__.py similarity index 100% rename from file_scripts/__init__.py rename to python/file_scripts/__init__.py diff --git a/file_scripts/editor.py b/python/file_scripts/editor.py similarity index 100% rename from file_scripts/editor.py rename to python/file_scripts/editor.py diff --git a/file_scripts/error.py b/python/file_scripts/error.py similarity index 100% rename from file_scripts/error.py rename to python/file_scripts/error.py diff --git a/file_scripts/fzf.py b/python/file_scripts/fzf.py similarity index 100% rename from file_scripts/fzf.py rename to python/file_scripts/fzf.py diff --git a/file_scripts/search.py b/python/file_scripts/search.py similarity index 100% rename from file_scripts/search.py rename to python/file_scripts/search.py diff --git a/setup.py b/python/setup.py similarity index 100% rename from setup.py rename to python/setup.py diff --git a/tests/__init__.py b/python/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to python/tests/__init__.py diff --git a/tests/test_editor.py b/python/tests/test_editor.py similarity index 100% rename from tests/test_editor.py rename to python/tests/test_editor.py diff --git a/tests/test_fzf.py b/python/tests/test_fzf.py similarity index 100% rename from tests/test_fzf.py rename to python/tests/test_fzf.py diff --git a/tests/test_search.py b/python/tests/test_search.py similarity index 100% rename from tests/test_search.py rename to python/tests/test_search.py