#!/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
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" "prettybat" "$ARG"
exit 1
fi
}
setargs "$@"
_ARGV_ORIGINAL=("$@")
hook_version(){
SHIFTOPT_HOOKS+=("__shiftopt_hook__version")
__shiftopt_hook__version(){
if [[ $OPT == "--version" ]];then
printf "%s %s\n\n%s\n%s\n" \
"prettybat" \
"2024.08.24" \
"Copyright (C) 2019-2021 eth-p | MIT License" \
"https://github.com/eth-p/bat-extras"
exit 0
fi
return 1
}
}
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" "prettybat" "${@:2}" 1>&2
}
print_error(){
printc "%{RED}[%s error]%{CLEAR}: $1%{CLEAR}\n" "prettybat" "${@: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
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"
}
check_exists(){
[[ -e $1 ]]&&return 0
print_error "%s: No such file or directory" "$1"
return 1
}
check_is_file(){
[[ -f $1 ]]&&return 0
print_error "%s: Not a file" "$1"
return 1
}
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"
}
hook_version
FORMATTERS=(
"yq" "prettier" "rustfmt" "shfmt" "clangformat"
"black" "mix_format" "column")
formatter_prettier_supports(){
case "$1" in
.js|.jsx|\
.ts|.tsx|\
.css|.scss|.sass|\
.graphql|.gql|\
.html|.svg|\
.json|\
.md|\
.yml)return 0
esac
return 1
}
formatter_prettier_process(){
local file="$1"
local fext="$(extname "$file")"
case "$fext" in
.svg)file="$(basename -- "$file" "$fext").html"
esac
prettier --stdin --stdin-filepath "$file" 2>/dev/null
return $?
}
formatter_clangformat_supports(){
case "$1" in
.c|.cpp|.cxx|\
.h|.hpp|\
.m)return 0
esac
return 1
}
formatter_clangformat_process(){
clang-format "$1" 2>/dev/null
return $?
}
formatter_rustfmt_supports(){
[[ $1 == ".rs" ]]
return $?
}
formatter_rustfmt_process(){
rustfmt
return $?
}
formatter_shfmt_supports(){
[[ $1 == ".sh" ]]
return $?
}
formatter_shfmt_process(){
shfmt
return $?
}
formatter_black_supports(){
case "$1" in
.py|\
.py3|\
.pyw|\
.pyi)return 0
esac
return 1
}
formatter_black_process(){
black --code "$(cat -)"
return $?
}
formatter_mix_format_supports(){
case "$1" in
.ex|\
.exs|\
.eex|\
.heex)return 0
esac
return 1
}
formatter_mix_format_process(){
mix format
return $?
}
formatter_column_supports(){
case "$1" in
.tsv)return 0
esac
return 1
}
formatter_column_process(){
local needs_newline=true
local args=(
-t
-s $'\t'
-c "$TERMINAL_WIDTH")
if column --help &>/dev/null;then
args+=(--keep-empty-lines)
needs_newline=false
fi
{ { "$needs_newline"&&sed 's/$/\n/';}||cat;}|column "${args[@]}"
}
formatter_yq_supports__version_ok(){
local yq_version
yq_version=$(yq --version|sed 's/^.* version v//')
if version_compare "$yq_version" -lt 4.0;then
formatter_yq_supports(){ return 1;}
formatter_yq_supports__version_ok(){ return 1;}
return 1
fi
formatter_yq_supports__version_ok(){ return 0;}
return 0
}
formatter_yq_supports(){
case "$1" in
.yaml|yml|\
.json)formatter_yq_supports__version_ok
return $?
esac
return 1
}
formatter_yq_process(){
local args=()
case "$1" in
*.json)args+=(--output-format json)
esac
yq --prettyPrint --indent 4 "${args[@]}"|sed -e ':l' -e 's/^\(\t*\)    /\1\t/g; t l'
return $?
}
map_language_to_extension(){
local ext=".txt"
case "$1" in
sh|bash)ext=".sh";;
js|es6|es)ext=".js";;
jsx)ext=".jsx";;
ts)ext=".ts";;
tsx)ext=".tsx";;
css)ext=".css";;
scss)ext=".scss";;
sass)ext=".sass";;
svg)ext=".svg";;
html|htm|shtml|xhtml)ext=".html";;
json)ext=".json";;
md|mdown|markdown)ext=".md";;
yaml|yml)ext=".yml";;
rust|rs)ext=".rs";;
graphql|gql)ext=".graphql";;
python|py)ext=".py";;
elixir|ex)ext=".ex";;
exs)ext=".exs";;
tsv)ext=".tsv"
esac
echo "$ext"
}
map_extension_to_formatter(){
local formatter
for formatter in "${FORMATTERS[@]}";do
if "formatter_${formatter}_supports" "$1";then
echo "$formatter"
return 0
fi
done
echo "none"
return 0
}
extname(){
local file="$1"
echo ".${file##*.}"
}
print_file(){
if [[ ${#PRINT_ARGS[@]} -eq 0 ]];then
"bat" "$@"
return $?
else
"bat" "${PRINT_ARGS[@]}" "$@"
return $?
fi
}
process_file(){
PRINT_ARGS=("${BAT_ARGS[@]}")
local file="$1"
local ext="$2"
local fext="$ext"
local lang="${ext:1}"
local formatter
if [[ $file != "-" ]];then
check_exists "$file"||return 1
check_is_file "$file"||return 1
fi
if [[ -n $OPT_LANGUAGE ]];then
lang="$OPT_LANGUAGE"
fext="$(map_language_to_extension "$lang")"
fi
if [[ $ext != "-" ]];then
formatter="$(map_extension_to_formatter "$fext")"
fi
if "$DEBUG_PRINT_FORMATTER";then
printc "%{CYAN}%s%{CLEAR}: %s\n" "$file" "$formatter"
return 0
fi
forward_file_name "$file"
if [[ $formatter == "none" ]];then
if [[ -z $OPT_LANGUAGE ]];then
print_file "$file"
else
print_file --language="$OPT_LANGUAGE" "$file"
fi
return $?
fi
local data_raw
local data_formatted
if [[ $file == "-" ]];then
data_raw="$(cat -)"
data_formatted="$("formatter_${formatter}_process" "STDIN$fext" 2>/dev/null <<<"$data_raw")"
if [[ $? -ne 0 ]];then
print_warning "'STDIN': Unable to format with '%s'" "$formatter"
print_file --language="$lang" - <<<"$data_raw"
return 1
fi
else
data_formatted="$("formatter_${formatter}_process" "$file" <"$file")"
if [[ $? -ne 0 ]];then
print_warning "'%s': Unable to format with '%s'" "$file" "$formatter"
print_file --language="$lang" "$file"
return 1
fi
fi
print_file --language="$lang" - <<<"$data_formatted"
return $?
}
BAT_VERSION="$(bat_version)"
forward_file_name(){ :;}
if version_compare "$BAT_VERSION" -ge "0.14";then
forward_file_name(){
PRINT_ARGS+=("--file-name" "$1")
}
fi
BAT_ARGS=()
OPT_LANGUAGE=
FILES=()
DEBUG_PRINT_FORMATTER=false
TERMINAL_WIDTH="$(term_width)"
while shiftopt;do
case "$OPT" in
-l)shiftval
OPT_LANGUAGE="$OPT_VAL"
;;
-l*)OPT_LANGUAGE="${OPT:2}";;
--language)shiftval
OPT_LANGUAGE="$OPT_VAL"
;;
--debug:formatter)DEBUG_PRINT_FORMATTER=true;;
-)FILES+=("-");;
-*){
if [[ -n $OPT_VAL ]];then
BAT_ARGS+=("$OPT=$OPT_VAL")
else
BAT_ARGS+=("$OPT")
fi
};;
*){
FILES+=("$OPT")
}
esac
done
if [[ ${#FILES[@]} -eq 0 ]];then
FILES=("-")
fi
FAIL=0
for file in "${FILES[@]}";do
if ! process_file "$file" "$(tolower "$(extname "$file")")";then
FAIL=1
fi
done
exit "$FAIL"
