batpipe: Add batpipe script

This commit is contained in:
Ethan P 2021-03-24 18:35:56 -07:00
parent dd07f98367
commit b85c7c43e3
No known key found for this signature in database
GPG Key ID: 6963FD04F6CF35EA
4 changed files with 342 additions and 0 deletions

18
lib/dirs.sh Normal file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2021 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
# Gets a configuration directory for a command-line program.
# Arguments:
# 1 -- The program name.
config_dir() {
if [[ -n "${XDG_CONFIG_HOME+x}" ]]; then
echo "${XDG_CONFIG_HOME}/$1"
else
echo "${HOME}/.config/$1"
fi
}

26
lib/path.sh Normal file
View File

@ -0,0 +1,26 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2021 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
# Gets the file extension from a file path.
# Arguments:
# 1 -- The file path.
extname() {
local file="$1"
echo ".${file##*.}"
}
# Strips trailing slashes from a file path.
# Arguments:
# 1 -- The file path.
strip_trailing_slashes() {
local file="$1"
while [[ -n "$file" && "${file: -1}" = "/" ]]; do
file="${file:0:$((${#file}-1))}"
done
echo "$file"
}

67
lib/proc.sh Normal file
View File

@ -0,0 +1,67 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2021 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
# Gets the path to the parent executable file.
# Arguments:
# 1 -- The target pid. If not provided, the script's parent is used.
parent_executable() {
local target_pid="${1:-$PPID}"
ps -f -p "$target_pid" | tail -n 1 | awk '{for(i=8;i<=NF;i++) printf $i" "; printf "\n"}'
}
# Gets the PID of the parent executable file.
# Arguments:
# 1 -- The target pid. If not provided, the script's parent is used.
parent_executable_pid() {
local target_pid="${1:-$PPID}"
ps -f -p "$target_pid" | tail -n 1 | awk '{print $3}'
}
# Gets the path to the parent login shell.
# Arguments:
# 1 -- The target pid. If not provided, the script's parent is used.
parent_shell() {
local target_pid="${1:-$PPID}"
local target_name
while true; do
{
read -r target_pid
read -r target_name
# If the parent process starts with a "-", it's a login shell.
if [[ "${target_name:0:1}" = "-" ]]; then
target_name="$(cut -f1 -d' ' <<< "${target_name:1}")"
break
fi
# If the parent process has "*sh " followed by "-l", it's probably a login shell.
if [[ "$target_name" =~ sh\ .*-l ]]; then
target_name="$(cut -f1 -d' ' <<< "${target_name}")"
break
fi
# If the parent process is pid 0 (init), then we haven't found a parent shell.
# At this point, it's best to assume the shell is whatever is defined in $SHELL.
if [[ "$target_pid" -eq 0 ]]; then
target_name="$SHELL"
break
fi
} < <({
ps -f -p "$target_pid" \
| tail -n 1 \
| awk '{print $3; for(i=8;i<=NF;i++) printf $i" "; printf "\n"}'
})
done
# Ensure that the detected shell is an executable path.
if [[ -f "$target_name" ]]; then
echo "$target_name"
elif ! command -v "$target_name" 2>/dev/null; then
echo "$target_name" # It's not, but we have nothing else we can do here.
fi
}

231
src/batpipe.sh Executable file
View File

@ -0,0 +1,231 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2021 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
#
# EXTERNAL VIEWERS FOR BATPIPE:
#
# External viewers can be added to batpipe by creating bash scripts
# inside the `~/.config/batpipe/viewers.d/` directory.
#
# CREATING A VIEWER:
#
# Viewers must define two functions and append the viewer's name to the
# `BATPIPE_VIEWERS` array.
#
# - viewer_${viewer}_supports [file_path] [file_basename]
# If this returns 0, the viewer's process function will be used.
#
# - viewer_${viewer}_process [file_path] [inner_file_path]
#
# VIEWER API:
#
# $BATPIPE_VIEWERS -- An array of loaded file viewers.
# $BATPIPE_ENABLE_COLOR -- Whether color is supported. (`true`|`false`)
# $BATPIPE_INSIDE_LESS -- Whether batpipe is inside less. (`true`|`false`)
#
# batpipe_header [pattern] [...] -- Print a viewer header line.
# batpipe_subheader [pattern] [...] -- Print a viewer subheader line.
#
# strip_trailing_slashes [path] -- Strips trailing slashes from a path.
#
# -----------------------------------------------------------------------------
# shellcheck disable=SC1090 disable=SC2155
SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo ".")")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo ".")")/../lib" && pwd)"
source "${LIB}/constants.sh"
source "${LIB}/dirs.sh"
source "${LIB}/str.sh"
source "${LIB}/print.sh"
source "${LIB}/path.sh"
source "${LIB}/proc.sh"
# -----------------------------------------------------------------------------
# Usage/Install:
# -----------------------------------------------------------------------------
if [[ "$#" -eq 0 ]]; then
# If writing to a terminal, display instructions and help.
if [[ -t 1 ]]; then
printc "%{DIM}# %s, %s.\n# %s\n# %s\n# %s\n# \n# %s%{CLEAR}\n" \
"$PROGRAM" \
"a bat-based preprocessor for less and bat" \
"Version: $PROGRAM_VERSION" \
"Homepage: $PROGRAM_HOMEPAGE" \
"$PROGRAM_COPYRIGHT" \
"To use $PROGRAM, eval the output of this command in your shell init script."
fi
# Detect the shell.
#
# This will directly check if the parent is fish, since there's a
# good chance that `bash` or `sh` will be invoking fish.
if [[ "$(basename -- "$(parent_executable | cut -f1 -d' ')")" == "fish" ]]; then
detected_shell="fish"
else
detected_shell="$(parent_shell)"
fi
# Print the commands required to add `batpipe` to the environment variables.
case "$(basename -- "${detected_shell:bash}")" in
fish) # Fish
printc '%{YELLOW}set -x %{CLEAR}LESSOPEN %{CYAN}"|%q %%s"%{CLEAR};\n' "$SELF"
printc '%{YELLOW}set -e %{CLEAR}LESSCLOSE;\n'
;;
*) # Bash-like
printc '%{YELLOW}LESSOPEN=%{CYAN}"|%s %%s"%{CLEAR};\n' "$SELF"
printc '%{YELLOW}export%{CLEAR} LESSOPEN\n' "$SELF"
printc '%{YELLOW}unset%{CLEAR} LESSCLOSE;\n'
;;
esac
exit 0
fi
# -----------------------------------------------------------------------------
# Init:
# -----------------------------------------------------------------------------
if [[ "$(basename -- "$(parent_executable "$(parent_executable_pid)"|cut -f1 -d' ')")" == less ]]; then
BATPIPE_INSIDE_LESS=true
else
BATPIPE_INSIDE_LESS=false
fi
# -----------------------------------------------------------------------------
# Viewers:
# -----------------------------------------------------------------------------
BATPIPE_VIEWERS=("ls" "tar")
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
viewer_ls_supports() {
[[ -d "$1" ]]
return $?
}
viewer_ls_process() {
local dir="$(strip_trailing_slashes "$1")"
batpipe_header "Viewing contents of directory: %{PATH}%s" "$dir"
ls -lA "$1"
return $?
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
viewer_tar_supports() {
command -v "tar" &> /dev/null || return 1
case "$2" in
*.tar | *.tar.*) return 0 ;;
esac
return 1
}
viewer_tar_process() {
if [[ -n "$2" ]]; then
batpipe_header "Archived file: %{SUBPATH}%s%{PATH}/%s" "$1" "$2"
tar -xf "$1" -O "$2" 2>&1
else
batpipe_header "Viewing contents of archive: %{PATH}%s" "$1"
batpipe_subheader "To view files within the archive, add the file path after the archive."
tar -tvf "$1"
return $?
fi
}
# -----------------------------------------------------------------------------
# Functions:
# -----------------------------------------------------------------------------
# Print a header for batpipe messages.
# Arguments:
# 1 -- The printc formatting string.
# ... -- The printc formatting arguments.
batpipe_header() {
local pattern="${1//%{C\}/%{C\}%{HEADER\}}"
printc "%{HEADER}==> $pattern%{C}\n" "${@:2}"
}
# Print a subheader for batpipe messages.
# Arguments:
# 1 -- The printc formatting string.
# ... -- The printc formatting arguments.
batpipe_subheader() {
local pattern="${1//%{C\}/%{C\}%{SUBHEADER\}}"
printc "%{SUBHEADER}==> $pattern%{C}\n" "${@:2}"
}
# -----------------------------------------------------------------------------
# Colors:
# -----------------------------------------------------------------------------
printc_init "[DEFINE]" << END
C \x1B[0m
SUBPATH \x1B[2;35m
PATH \x1B[0;35m
HEADER \x1B[0;36m
SUBHEADER \x1B[2;36m
END
# Enable color output if:
# - Parent is not less OR BATPIPE=color; AND
# - NO_COLOR is not defined.
#
# shellcheck disable=SC2034
if [[ "$BATPIPE_INSIDE_LESS" == "false" || "$BATPIPE" == "color" ]] && [[ -z "${NO_COLOR+x}" ]]; then
BATPIPE_ENABLE_COLOR=true
printc_init true
else
BATPIPE_ENABLE_COLOR=false
printc_init false
fi
# -----------------------------------------------------------------------------
# Main:
# -----------------------------------------------------------------------------
__CONFIG_DIR="$(config_dir batpipe)"
__TARGET_INSIDE=""
__TARGET_FILE="$(strip_trailing_slashes "$1")"
# Determine the target file by walking upwards from the specified path.
# This allows inner paths of archives to be used.
while ! [[ -e "$__TARGET_FILE" ]]; do
__TARGET_INSIDE="$(basename -- "${__TARGET_FILE}")/${__TARGET_INSIDE}"
__TARGET_FILE="$(dirname "${__TARGET_FILE}")"
done
# Remove trailing slash of the inner target path.
__TARGET_INSIDE="$(strip_trailing_slashes "$__TARGET_INSIDE")"
__TARGET_BASENAME="$(basename -- "$__TARGET_FILE")"
# Stop bat from calling this recursively.
unset LESSOPEN
unset LESSCLOSE
# Load external viewers.
if [[ -d "${__CONFIG_DIR}/viewers.d" ]]; then
unset LIB
unset SELF
shopt -o nullglob
for viewer_script in "${__CONFIG_DIR}/viewers.d"/*; do
source "${viewer_script}"
done
shopt -u nullglob
fi
# Try opening the file with the first viewer that supports it.
for viewer in "${BATPIPE_VIEWERS[@]}"; do
if "viewer_${viewer}_supports" "$__TARGET_FILE" "$__TARGET_BASENAME" 1>&2; then
"viewer_${viewer}_process" "$__TARGET_FILE" "$__TARGET_INSIDE"
exit $?
fi
done
# No supported viewer. Just pass it through.
exit 1