#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2020-2024 eth-p and contributors | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues:     https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
#!/usr/bin/env bash
SELF_NC="${BASH_SOURCE:-$0}"
SELF="$(cd "$(dirname "$SELF_NC")"&&cd "$(dirname "$(readlink "$SELF_NC"||echo ".")")"&&pwd)/$(basename "$SELF_NC")"
config_dir(){
if [[ -n ${XDG_CONFIG_HOME+x} ]];then
echo "$XDG_CONFIG_HOME/$1"
else
echo "$HOME/.config/$1"
fi
}
tolower(){
tr "[:upper:]" "[:lower:]" <<<"$1"
}
toupper(){
tr "[:lower:]" "[:upper:]" <<<"$1"
}
printc(){
printf "$(sed "$_PRINTC_PATTERN" <<<"$1")" "${@:2}"
}
printc_init(){
case "$1" in
true)_PRINTC_PATTERN="$_PRINTC_PATTERN_ANSI";;
false)_PRINTC_PATTERN="$_PRINTC_PATTERN_PLAIN";;
"[DEFINE]"){
_PRINTC_PATTERN_ANSI=""
_PRINTC_PATTERN_PLAIN=""
local name
local ansi
while read -r name ansi;do
if [[ -z $name && -z $ansi ]]||[[ ${name:0:1} == "#" ]];then
continue
fi
ansi="${ansi/\\/\\\\}"
_PRINTC_PATTERN_PLAIN="${_PRINTC_PATTERN_PLAIN}s/%{$name}//g;"
_PRINTC_PATTERN_ANSI="${_PRINTC_PATTERN_ANSI}s/%{$name}/$ansi/g;"
done
if [[ -t 1 && -z ${NO_COLOR+x} ]];then
_PRINTC_PATTERN="$_PRINTC_PATTERN_ANSI"
else
_PRINTC_PATTERN="$_PRINTC_PATTERN_PLAIN"
fi
}
esac
}
print_warning(){
printc "%{YELLOW}[%s warning]%{CLEAR}: $1%{CLEAR}\n" "batpipe" "${@:2}" 1>&2
}
print_error(){
printc "%{RED}[%s error]%{CLEAR}: $1%{CLEAR}\n" "batpipe" "${@:2}" 1>&2
}
printc_init "[DEFINE]" <<END
	CLEAR	\x1B[0m
	RED		\x1B[31m
	GREEN	\x1B[32m
	YELLOW	\x1B[33m
	BLUE	\x1B[34m
	MAGENTA	\x1B[35m
	CYAN	\x1B[36m

	DEFAULT \x1B[39m
	DIM		\x1B[2m
END
extname(){
local file="$1"
echo ".${file##*.}"
}
strip_trailing_slashes(){
local file="$1"
while [[ -n $file && ${file: -1} == "/" ]];do
file="${file:0:$((${#file}-1))}"
done
echo "$file"
}
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"}'
}
parent_executable_pid(){
local target_pid="${1:-$PPID}"
ps -f -p "$target_pid"|tail -n 1|awk '{print $3}'
}
parent_shell(){
local target_pid="${1:-$PPID}"
local target_name
while true;do
{
read -r target_pid
read -r target_name
if [[ ${target_name:0:1} == "-" ]];then
target_name="$(cut -f1 -d' ' <<<"${target_name:1}")"
break
fi
if [[ $target_name =~ sh\ .*-l ]];then
target_name="$(cut -f1 -d' ' <<<"$target_name")"
break
fi
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
if [[ -f $target_name ]];then
echo "$target_name"
elif ! command -v "$target_name" 2>/dev/null;then
echo "$target_name"
fi
}
SHIFTOPT_HOOKS=()
SHIFTOPT_SHORT_OPTIONS="VALUE"
setargs(){
_ARGV=("$@")
_ARGV_LAST="$((${#_ARGV[@]}-1))"
_ARGV_INDEX=0
_ARGV_SUBINDEX=1
}
getargs(){
if [[ $1 == "-a" || $1 == "--append" ]];then
if [[ $_ARGV_INDEX -ne "$((_ARGV_LAST+1))" ]];then
eval "$2=(\"\${$2[@]}\" $(printf '%q ' "${_ARGV[@]:_ARGV_INDEX}"))"
fi
else
if [[ $_ARGV_INDEX -ne "$((_ARGV_LAST+1))" ]];then
eval "$1=($(printf '%q ' "${_ARGV[@]:_ARGV_INDEX}"))"
else
eval "$1=()"
fi
fi
}
resetargs(){
setargs "${_ARGV_ORIGINAL[@]}"
}
_shiftopt_next(){
_ARGV_SUBINDEX=1
((_ARGV_INDEX++))||true
}
shiftopt(){
[[ $_ARGV_INDEX -gt $_ARGV_LAST ]]&&return 1
OPT="${_ARGV[$_ARGV_INDEX]}"
unset OPT_VAL
if [[ $OPT =~ ^-[a-zA-Z0-9_-]+=.* ]];then
OPT_VAL="${OPT#*=}"
OPT="${OPT%%=*}"
fi
if [[ $OPT =~ ^-[^-]{2,} ]];then
case "$SHIFTOPT_SHORT_OPTIONS" in
PASS)_shiftopt_next;;
CONV)OPT="-$OPT"
_shiftopt_next
;;
VALUE){
OPT="${_ARGV[$_ARGV_INDEX]}"
OPT_VAL="${OPT:2}"
OPT="${OPT:0:2}"
_shiftopt_next
};;
SPLIT){
OPT="-${OPT:_ARGV_SUBINDEX:1}"
((_ARGV_SUBINDEX++))||true
if [[ $_ARGV_SUBINDEX -gt ${#OPT} ]];then
_shiftopt_next
fi
};;
*)printf "shiftopt: unknown SHIFTOPT_SHORT_OPTIONS mode '%s'" \
"$SHIFTOPT_SHORT_OPTIONS" 1>&2
_shiftopt_next
esac
else
_shiftopt_next
fi
local hook
for hook in "${SHIFTOPT_HOOKS[@]}";do
if "$hook";then
shiftopt
return $?
fi
done
return 0
}
shiftval(){
if [[ -n ${OPT_VAL+x} ]];then
return 0
fi
if [[ $_ARGV_SUBINDEX -gt 1 && $SHIFTOPT_SHORT_OPTIONS == "SPLIT" ]];then
OPT_VAL="${_ARGV[$((_ARGV_INDEX+1))]}"
else
OPT_VAL="${_ARGV[$_ARGV_INDEX]}"
_shiftopt_next
fi
if [[ $OPT_VAL =~ -.* ]];then
printc "%{RED}%s: '%s' requires a value%{CLEAR}\n" "batpipe" "$ARG"
exit 1
fi
}
setargs "$@"
_ARGV_ORIGINAL=("$@")
bat_version(){
if [[ -z $__BAT_VERSION ]];then
__BAT_VERSION="$(command "bat" --version|cut -d ' ' -f 2)"
fi
echo "$__BAT_VERSION"
}
version_compare(){
local version="$1"
local compare="$3"
if ! [[ $version =~ \.$ ]];then
version="$version."
fi
if ! [[ $compare =~ \.$ ]];then
compare="$compare."
fi
version_compare__recurse "$version" "$2" "$compare"
return $?
}
version_compare__recurse(){
local version="$1"
local operator="$2"
local compare="$3"
local v_major="${version%%.*}"
local c_major="${compare%%.*}"
local v_minor="${version#*.}"
local c_minor="${compare#*.}"
if [[ -z $v_minor && -z $c_minor ]];then
[ "$v_major" $operator "$c_major" ]
return $?
fi
if [[ -z $v_minor ]];then
v_minor="0."
fi
if [[ -z $c_minor ]];then
c_minor="0."
fi
case "$operator" in
-eq)[[ $v_major -ne $c_major ]]&&return 1;;
-ne)[[ $v_major -ne $c_major ]]&&return 0;;
-ge|-gt)[[ $v_major -lt $c_major ]]&&return 1
[[ $v_major -gt $c_major ]]&&return 0
;;
-le|-lt)[[ $v_major -gt $c_major ]]&&return 1
[[ $v_major -lt $c_major ]]&&return 0
esac
version_compare__recurse "$v_minor" "$operator" "$c_minor"
}
term_width(){
local width="$({ stty size 2>/dev/null||echo "22 80";}|cut -d ' ' -f2)"
if [[ $width -ne 0 ]];then
echo "$width"
else
echo "80"
fi
return 0
}
term_clear(){
printf "\x1B[3J\x1B[2J\x1B[H"
}
if [[ $# -eq 0 ]];then
if [[ -t 1 ]];then
printc "%{DIM}# %s, %s.\n# %s\n# %s\n# %s\n# \n# %s%{CLEAR}\n" \
"batpipe" \
"a bat-based preprocessor for less and bat" \
"Version: 2024.08.24" \
"Homepage: https://github.com/eth-p/bat-extras" \
"Copyright (C) 2019-2021 eth-p | MIT License" \
"To use batpipe, eval the output of this command in your shell init script."
fi
if [[ "$(basename -- "$(parent_executable|cut -f1 -d' ')")" == "fish" ]];then
detected_shell="fish"
else
detected_shell="$(parent_shell)"
fi
case "$(basename -- "${detected_shell:bash}")" in
fish)printc '%{YELLOW}set -x %{CLEAR}LESSOPEN %{CYAN}"|%q %%s"%{CLEAR};\n' "$SELF"
printc '%{YELLOW}set -e %{CLEAR}LESSCLOSE;\n'
;;
*)printc '%{MAGENTA}LESSOPEN%{CLEAR}=%{CYAN}"|%s %%s"%{CLEAR};\n' "$SELF"
printc '%{YELLOW}export%{CLEAR} LESSOPEN;\n' "$SELF"
printc '%{YELLOW}unset%{CLEAR} LESSCLOSE;\n'
esac
if [[ -t 1 ]];then
printc "\n%{DIM}# The following will enable colors when using batpipe with less:%{CLEAR}\n"
fi
case "$(basename -- "${detected_shell:bash}")" in
fish)printc '%{YELLOW}set -x %{CLEAR}LESS %{CYAN}"%{MAGENTA}$LESS%{CYAN} -R"%{CLEAR};\n' "$SELF"
printc '%{YELLOW}set -x %{CLEAR}BATPIPE %{CYAN}"color"%{CLEAR};\n'
;;
*)printc '%{MAGENTA}LESS%{CLEAR}=%{CYAN}"%{MAGENTA}$LESS%{CYAN} -R"%{CLEAR};\n' "$SELF"
printc '%{MAGENTA}BATPIPE%{CLEAR}=%{CYAN}"color"%{CLEAR};\n' "$SELF"
printc '%{YELLOW}export%{CLEAR} LESS;\n' "$SELF"
printc '%{YELLOW}export%{CLEAR} BATPIPE;\n' "$SELF"
esac
exit 0
fi
BATPIPE_INSIDE_LESS=false
BATPIPE_INSIDE_BAT=false
TERM_WIDTH="$(term_width)"
if [[ -n ${BATPIPE_TERM_WIDTH:-} ]];then
if [[ ${BATPIPE_TERM_WIDTH:0:1} == "-" ]];then
TERM_WIDTH=$((TERM_WIDTH+BATPIPE_TERM_WIDTH))||true
else
TERM_WIDTH="$BATPIPE_TERM_WIDTH"
fi
fi
BATPIPE_PARENT_EXECUTABLE_PID="$PPID"
for i in 1 2 3;do
BATPIPE_PARENT_EXECUTABLE="${BATPIPE_DEBUG_PARENT_EXECUTABLE:-$(parent_executable "$BATPIPE_PARENT_EXECUTABLE_PID")}"
BATPIPE_PARENT_EXECUTABLE_BASENAME="$(basename -- "$BATPIPE_PARENT_EXECUTABLE"|cut -d' ' -f1)"
BATPIPE_PARENT_EXECUTABLE_PID="$(parent_executable_pid "$BATPIPE_PARENT_EXECUTABLE_PID")"
if [[ $BATPIPE_PARENT_EXECUTABLE_BASENAME == "less" ]];then
BATPIPE_INSIDE_LESS=true
break
elif [[ $BATPIPE_PARENT_EXECUTABLE_BASENAME == "$(basename -- "bat")" ]];then
BATPIPE_INSIDE_BAT=true
break
fi
done
if [[ -n ${BATPIPE_DEBUG:-} ]];then
printf "batpipe debug:\n"
printf "  %s: %s\n" \
"BATPIPE_INSIDE_LESS" "$BATPIPE_INSIDE_LESS" \
"BATPIPE_INSIDE_BAT" "$BATPIPE_INSIDE_BAT"
printf "\n"
fi
if ! command -v eza &>/dev/null;then
BATPIPE_VIEWERS=("eza" "ls" "tar" "tar_gz" "unzip" "gunzip" "xz")
else
BATPIPE_VIEWERS=("exa" "ls" "tar" "tar_bz2" "unzip" "gunzip" "xz")
fi
viewer_exa_supports(){
[[ -d $2 ]]||return 1
command -v "exa" &>/dev/null||return 1
return 0
}
viewer_eza_supports(){
[[ -d $2 ]]||return 1
command -v "eza" &>/dev/null||return 1
return 0
}
viewer_exa_process(){
local dir="$(strip_trailing_slashes "$1")"
batpipe_header "Viewing contents of directory: %{PATH}%s" "$dir"
if "$BATPIPE_ENABLE_COLOR";then
exa -la --color=always "$1" 2>&1
else
exa -la --color=never "$1" 2>&1
fi
return $?
}
viewer_eza_process(){
local dir="$(strip_trailing_slashes "$1")"
batpipe_header "Viewing contents of directory: %{PATH}%s" "$dir"
if "$BATPIPE_ENABLE_COLOR";then
eza -la --color=always "$1" 2>&1
else
eza -la --color=never "$1" 2>&1
fi
return $?
}
viewer_ls_supports(){
[[ -d $2 ]]
return $?
}
viewer_ls_process(){
local dir="$(strip_trailing_slashes "$1")"
batpipe_header "Viewing contents of directory: %{PATH}%s" "$dir"
ls -lA "$1" 2>&1
return $?
}
viewer_tar_supports(){
command -v "tar" &>/dev/null||return 1
case "$1" in
*.tar|*.tar.*)return 0
esac
return 1
}
viewer_tar_process(){
if [[ -n $2 ]];then
tar $3 -xf "$1" -O "$2"|bat --file-name="$1/$2"
else
batpipe_archive_header
tar $3 -tvf "$1"
return $?
fi
}
viewer_tar_gz_supports(){
command -v "tar" &>/dev/null||return 1
command -v "gzip" &>/dev/null||return 1
case "$1" in
*.tar.gz|*.tgz)return 0
esac
return 1
}
viewer_tar_gz_process(){
viewer_tar_process "$1" "$2" -z
}
viewer_tar_bz2_supports(){
command -v "tar" &>/dev/null||return 1
command -v "bzip2" &>/dev/null||return 1
case "$1" in
*.tar.bz2|*.tbz)return 0
esac
return 1
}
viewer_tar_bz2_process(){
viewer_tar_process "$1" "$2" -j
}
viewer_unzip_supports(){
command -v "unzip" &>/dev/null||return 1
case "$1" in
*.zip)return 0
esac
return 1
}
viewer_unzip_process(){
if [[ -n $2 ]];then
unzip -p "$1" "$2"|bat --file-name="$1/$2"
else
batpipe_archive_header
unzip -l "$1"
return $?
fi
}
viewer_gunzip_supports(){
command -v "gunzip" &>/dev/null||return 1
[[ -z $3 ]]||return 1
case "$2" in
*.gz)return 0
esac
return 1
}
viewer_gunzip_process(){
gunzip -k -c "$1"|bat --file-name="$1"
return $?
}
viewer_xz_supports(){
command -v "xz" &>/dev/null||return 1
[[ -z $3 ]]||return 1
case "$2" in
*.xz)return 0
esac
return 1
}
viewer_xz_process(){
xz --decompress -k -c "$1"|bat --file-name="$1"
return $?
}
batpipe_header(){
local pattern="${1//%{C\}/%{C\}%{HEADER\}}"
printc "%{HEADER}==> $pattern%{C}\n" "${@:2}"
}
batpipe_subheader(){
local pattern="${1//%{C\}/%{C\}%{SUBHEADER\}}"
printc "%{SUBHEADER}==> $pattern%{C}\n" "${@:2}"
}
batpipe_archive_header(){
batpipe_header "Viewing contents of archive: %{PATH}%s" "$1"
batpipe_subheader "To view files within the archive, add the file path after the archive."
}
bat(){
if [[ -z $__BAT_VERSION ]];then
__BAT_VERSION="$(bat_version)"
__bat_forward_arg_file_name(){ :;}
if version_compare "$__BAT_VERSION" -ge "0.14";then
__bat_forward_arg_file_name(){
__bat_forward_args+=("--file-name" "$1")
}
fi
fi
__bat_batpipe_args=()
__bat_forward_args=()
__bat_files=()
setargs "$@"
while shiftopt;do
case "$OPT" in
--file-name)shiftval
__bat_forward_arg_file_name "$OPT_VAL"
;;
--paging)shiftval;;
--decorations)shiftval;;
--style)shiftval;;
--terminal-width)shiftval;;
--plain|-p|-pp|-ppp):;;
-*){
__bat_forward_args+=("$OPT")
if [[ -n $OPT_VAL ]];then
__bat_forward_args+=("$OPT_VAL")
fi
};;
*){
__bat_forward_args+=("$OPT")
__bat_files=("$OPT")
}
esac
done
if "$BATPIPE_INSIDE_LESS";then
__bat_batpipe_args+=(--decorations=always)
__bat_batpipe_args+=(--terminal-width="$TERM_WIDTH")
if "$BATPIPE_ENABLE_COLOR";then
__bat_batpipe_args+=(--color=always)
else
__bat_batpipe_args+=(--color=never)
fi
fi
if "$BATPIPE_INSIDE_BAT";then
if [[ ${#__bat_files[@]} -eq 0 ]];then
cat
else
cat "${__bat_files[@]}"
fi
fi
command "bat" --paging=never "${__bat_batpipe_args[@]}" "${__bat_forward_args[@]}"
}
printc_init "[DEFINE]" <<END
	C			\x1B[0m
	SUBPATH		\x1B[2;35m
	PATH		\x1B[0;35m
	HEADER		\x1B[0;36m
	SUBHEADER	\x1B[2;36m
END
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
__CONFIG_DIR="$(config_dir batpipe)"
__TARGET_INSIDE=""
__TARGET_FILE="$(strip_trailing_slashes "$1")"
while ! [[ -e $__TARGET_FILE ]];do
__TARGET_INSIDE="$(basename -- "$__TARGET_FILE")/$__TARGET_INSIDE"
__TARGET_FILE="$(dirname -- "$__TARGET_FILE")"
done
if ! [[ -f $__TARGET_FILE ]];then
__TARGET_FILE="$__TARGET_FILE/$__TARGET_INSIDE"
__TARGET_INSIDE=""
fi
if [[ -n $__TARGET_INSIDE ]]||! [[ -d $__TARGET_FILE ]];then
__TARGET_FILE="$(strip_trailing_slashes "$__TARGET_FILE")"
fi
__TARGET_INSIDE="$(strip_trailing_slashes "$__TARGET_INSIDE")"
__TARGET_BASENAME="$(basename -- "$__TARGET_FILE")"
unset LESSOPEN
unset LESSCLOSE
if [[ -d "$__CONFIG_DIR/viewers.d" ]];then
unset LIB
unset SELF
shopt -s nullglob
for viewer_script in "$__CONFIG_DIR/viewers.d"/*;do
source "$viewer_script"
done
shopt -u nullglob
fi
for viewer in "${BATPIPE_VIEWERS[@]}";do
if "viewer_${viewer}_supports" "$__TARGET_BASENAME" "$__TARGET_FILE" "$__TARGET_INSIDE" 1>&2;then
"viewer_${viewer}_process" "$__TARGET_FILE" "$__TARGET_INSIDE"
exit $?
fi
done
if [[ $BATPIPE_INSIDE_BAT == true ]];then
exit 1
fi
if [[ -f $1 ]];then
bat "$1"
fi
