Restructure project directory, python files are now in their own folder

This commit is contained in:
Eric Torres
2022-10-01 11:16:15 -07:00
parent 243b5207bf
commit a8972362fc
22 changed files with 783 additions and 783 deletions

View File

View 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]

View File

@@ -0,0 +1,20 @@
"""
.. 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
E_FILE_EXISTS = 6
# ----- Messages -----
NO_FILE_SELECTED_MESSAGE = "No file was selected."
# ========== Functions ==========

View File

@@ -0,0 +1,66 @@
"""
.. moduleauthor:: Eric Torres
.. module:: files-scripts.fzf
:synopsis: Helper functions for interacting with fzf
"""
# ========== Imports ==========
import shutil
import subprocess
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):
"""Custom error class for any errors that occur when fzf is run.
**Attributes**
* exit_code: exit code when fzf exited
* message: stderr of fzf's process when the error occured
"""
def __init__(self, exit_code, message):
"""
:type exit_code: int
"""
self.exit_code = exit_code
self.message = message
def __repr__(self):
return f"FZFError({self.exit_code}, {self.message})"
def __str__(self):
return str(self.__repr__())
# ========== 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: path of selected file
:rtype: path-like object
:raises: FZFError if there was an error with fzf or no file was selected
"""
try:
# if we use capture_output, it captures file selection dialog
# use subprocess.PIPE instead to prevent this
output = subprocess.run(
(FZF_CMD, *FZF_OPTS), input=files, stdout=subprocess.PIPE, check=True
)
except subprocess.CalledProcessError as e:
raise FZFError(e.returncode, e.stderr) from e
else:
return Path(output.stdout.decode(LOCALE).strip("\x00"))

View File

@@ -0,0 +1,110 @@
"""
.. moduleauthor:: Eric Torres
.. module:: package.module
:synopsis: Short and succinct module description
"""
# ========== Imports ==========
import shutil
import subprocess
from multiprocessing import cpu_count
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",
"--threads",
str(cpu_count()),
]
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
"""
# Sample of a command that gets passed to subprocess:
# ['/usr/bin/find', '--hidden', '--', '<pattern>', 'path/to/directory']
cmd = [bin_override if bin_override is not None else FIND_CMD, *opts]
cmd.append("--")
""" Options Matrix
pattern T T F F
directory T F T F
1 2 3 4
1. Pattern and directory were given: pass both
2. Only pattern was given: pass pattern and give . as directory
3. Only directory was given: pass . as pattern and give directory
4. Neither pattern nor directory were given: do nothing
"""
if pattern is not None and directory is not None:
cmd.extend([pattern, directory])
elif pattern is not None and directory is None:
cmd.extend([pattern, "."])
elif pattern is None and directory is not None:
cmd.extend([".", directory])
elif pattern is None and directory is None:
pass
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