Initial commit
This commit is contained in:
0
file_scripts/__init__.py
Normal file
0
file_scripts/__init__.py
Normal file
76
file_scripts/editor.py
Normal file
76
file_scripts/editor.py
Normal file
@ -0,0 +1,76 @@
|
||||
"""
|
||||
.. moduleauthor:: Eric Torres
|
||||
.. module:: file_scripts.editor
|
||||
:synopsis: Helper functions for interacting with system editors
|
||||
"""
|
||||
# ========== Imports ==========
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# ========== Constants ==========
|
||||
SUDO_COMMAND = "sudo"
|
||||
|
||||
# ========== Functions ==========
|
||||
def select_editor(override=None):
|
||||
"""Return a 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 a FileNotFoundError 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(editor, filename):
|
||||
"""Generate a command line to run for editing a file based on
|
||||
permissions. This command line is suitable for passing to
|
||||
subprocess.run().
|
||||
|
||||
This command does not pass extra options to the editor, hence
|
||||
there are no arguments to pass for options.
|
||||
|
||||
Conditions
|
||||
----------
|
||||
* Path exists: use normal command
|
||||
* Path does not exist: use normal command
|
||||
* Path exists but is not readable by current process: use SUDO_COMMAND
|
||||
|
||||
|
||||
:param editor: path of editor
|
||||
:type editor: str or path-like object
|
||||
:param filename: path/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?
|
||||
path = Path(filename)
|
||||
if path.exists() and not os.access(path, os.W_OK):
|
||||
return [SUDO_COMMAND, "--edit", filename]
|
||||
else:
|
||||
return [editor, filename]
|
19
file_scripts/error.py
Normal file
19
file_scripts/error.py
Normal file
@ -0,0 +1,19 @@
|
||||
"""
|
||||
.. moduleauthor:: Eric Torres
|
||||
.. module:: file_scripts:error
|
||||
:synopsis: Exit codes and error handling.
|
||||
"""
|
||||
# ========== Imports ==========
|
||||
|
||||
# ========== Constants ==========
|
||||
# ----- Exit Codes -----
|
||||
E_INTERRUPT = 1
|
||||
E_NOFILESELECTED = 2
|
||||
E_NOEDITORFOUND = 3
|
||||
E_NO_RESULTS = 4
|
||||
E_USER_RESPONSE_NO = 5
|
||||
|
||||
# ----- Messages -----
|
||||
NO_FILE_SELECTED_MESSAGE = "No file was selected."
|
||||
|
||||
# ========== Functions ==========
|
45
file_scripts/fzf.py
Normal file
45
file_scripts/fzf.py
Normal file
@ -0,0 +1,45 @@
|
||||
"""
|
||||
.. moduleauthor:: Eric Torres
|
||||
.. module:: files-scripts.fzf
|
||||
:synopsis: Helper functions for interacting with fzf
|
||||
"""
|
||||
# ========== Imports ==========
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import file_scripts.error as error
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# ========== Constants ==========
|
||||
# ----- Commands -----
|
||||
# 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"]
|
||||
|
||||
# ----- Misc. -----
|
||||
LOCALE = "utf-8"
|
||||
|
||||
|
||||
class FZFError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# ========== Functions ==========
|
||||
def select_file_with_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: path-like object
|
||||
:raises: FZFError if there was an error with fzf or no file was selected
|
||||
"""
|
||||
output = subprocess.run([FZF_CMD, *FZF_OPTS], input=files, stdout=subprocess.PIPE)
|
||||
|
||||
try:
|
||||
output.check_returncode()
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise FZFError(error.NO_FILE_SELECTED_MESSAGE) from e
|
||||
else:
|
||||
return Path(selected_file.stdout.decode(LOCALE).strip("\x00"))
|
80
file_scripts/search.py
Normal file
80
file_scripts/search.py
Normal file
@ -0,0 +1,80 @@
|
||||
"""
|
||||
.. moduleauthor:: Eric Torres
|
||||
.. module:: package.module
|
||||
:synopsis: Short and succinct module description
|
||||
"""
|
||||
# ========== Imports ==========
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
from sys import platform
|
||||
|
||||
# ========== Constants ==========
|
||||
# ----- Commands -----
|
||||
# Options: show hidden files, null terminator, files only
|
||||
# Optional arguments: show vcs files, show every file
|
||||
FIND_CMD = shutil.which("fd")
|
||||
DEFAULT_FIND_OPTS = ["--hidden", "--print0", "--type", "f", "--type", "l"]
|
||||
EXTRA_FIND_OPTS = {"no_ignore": "--no-ignore", "no_ignore_vcs": "--no-ignore-vcs"}
|
||||
|
||||
# Options: null terminator, ignore case, print names matching all non-option arguments
|
||||
LOCATE_CMD = shutil.which("locate")
|
||||
|
||||
# Platform-specific options
|
||||
# macOS doesn't support GNU-style long options
|
||||
LOCATE_OPTS = []
|
||||
if platform == "linux":
|
||||
LOCATE_OPTS = ["--all", "--ignore-case", "--null"]
|
||||
elif platform == "darwin":
|
||||
LOCATE_OPTS = ["-0", "-i"]
|
||||
|
||||
# ========== Functions ==========
|
||||
def find_files(
|
||||
opts=DEFAULT_FIND_OPTS,
|
||||
directory=None,
|
||||
capture_text=None,
|
||||
bin_override=None,
|
||||
pattern=None,
|
||||
):
|
||||
"""Use a find-based program to locate files. The returned data is
|
||||
the stdout of the completed process.
|
||||
|
||||
:param opts: options to pass to the find program
|
||||
:type opts: list of str
|
||||
:param directory: directory to search for files
|
||||
:type directory: str
|
||||
:param capture_text: capture output as text
|
||||
:type capture_text: bool
|
||||
:param bin_override: override find binary
|
||||
:type bin_override: str
|
||||
:param pattern: patterns to pass to fd
|
||||
:type pattern: str
|
||||
:returns: path of user-selected file
|
||||
:rtype: bytes, str if capture_text was initialized from None
|
||||
"""
|
||||
cmd = [bin_override if bin_override is not None else FIND_CMD, *opts]
|
||||
|
||||
cmd.append("--")
|
||||
if pattern is not None:
|
||||
cmd.append(pattern)
|
||||
cmd.append(directory if directory is not None else ".")
|
||||
|
||||
return subprocess.run(cmd, capture_output=True, text=capture_text).stdout
|
||||
|
||||
|
||||
def locate_files(patterns, bin_override=None, capture_text=None):
|
||||
"""Use a locate-based program to locate files, then pass to fzf.
|
||||
|
||||
:param patterns: patterns to pass to locate
|
||||
:type patterns: list
|
||||
:param bin_override: override find binary
|
||||
:type bin_override: str
|
||||
:param capture_text: capture output as text
|
||||
:type capture_text: bool
|
||||
:returns: path of user-selected file
|
||||
:rtype: bytes, str if capture_text was initialized from None
|
||||
"""
|
||||
cmd = [bin_override if bin_override is not None else LOCATE_CMD, *LOCATE_OPTS]
|
||||
cmd.extend(patterns)
|
||||
|
||||
return subprocess.run(cmd, capture_output=True, text=capture_text).stdout
|
Reference in New Issue
Block a user