#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2019 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues:     https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
# shellcheck disable=SC1090
# --- BEGIN LIBRARY FILE: opt.sh ---
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=("$@")
# --- END LIBRARY FILE ---
# --- BEGIN LIBRARY FILE: opt_hook_version.sh ---
hook_version(){
SHIFTOPT_HOOKS+=("__shiftopt_hook__version")
__shiftopt_hook__version(){
if [[ $OPT == "--version" ]];then
printf "%s %s\n\n%s\n%s\n" \
"prettybat" \
"2021.08.29" \
"Copyright (C) 2019-2021 eth-p | MIT License" \
"https://github.com/eth-p/bat-extras"
exit 0
fi
return 1
}
}
# --- END LIBRARY FILE ---
# --- BEGIN LIBRARY FILE: str.sh ---
tolower(){
tr "[:upper:]" "[:lower:]" <<<"$1"
}
toupper(){
tr "[:lower:]" "[:upper:]" <<<"$1"
}
# --- END LIBRARY FILE ---
# --- BEGIN LIBRARY FILE: print.sh ---
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
# --- END LIBRARY FILE ---
# --- BEGIN LIBRARY FILE: version.sh ---
bat_version(){
if [[ -z $__BAT_VERSION ]];then
__BAT_VERSION="$(command "/usr/bin/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"
}
# --- END LIBRARY FILE ---
# --- BEGIN LIBRARY FILE: check.sh ---
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
}
# --- END LIBRARY FILE ---
# -----------------------------------------------------------------------------
# Init:
# -----------------------------------------------------------------------------
hook_version
# -----------------------------------------------------------------------------
# Formatters:
# -----------------------------------------------------------------------------

FORMATTERS=("prettier" "rustfmt" "shfmt" "clangformat" "black")

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

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() {
	# Rewrite the file extension to hackily support SVG. 
	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 $?
}

# -----------------------------------------------------------------------------
# Functions:
# -----------------------------------------------------------------------------

# This function will map a bat `--language=...` argument into an appropriate
# file extension for the language provided. This should be hardcoded for
# performance reasons.
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" ;;
	esac

	echo "$ext"
}

# This function will map a file extension to a formatter.
# Formatters are defined higher up in the file.
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
		"/usr/bin/bat" "$@"
		return $?
	else
		"/usr/bin/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
	
	# Check that the file exists, and is a file.
	if [[ "$file" != "-" ]]; then
		check_exists  "$file" || return 1
		check_is_file "$file" || return 1
	fi

	# Determine the formatter.
	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

	# Debug: Print the name and formatter.
	if "$DEBUG_PRINT_FORMATTER"; then
		printc "%{CYAN}%s%{CLEAR}: %s\n" "$file" "$formatter"
		return 0
	fi

	# Calculate additional print arguments.
	forward_file_name "$file"

	# Print the formatted file.
	if [[ "$formatter" = "none" ]]; then
		if [[ -z "$OPT_LANGUAGE" ]]; then
			print_file "$file"
		else
			print_file --language="$OPT_LANGUAGE" "$file"
		fi
		return $?
	fi

	# Prettify, then print.
	local data_raw
	local data_formatted

	# shellcheck disable=SC2094 disable=SC2181
	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 $?
}

# -----------------------------------------------------------------------------
# Version-Specific Features:
# -----------------------------------------------------------------------------
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

# -----------------------------------------------------------------------------
# Main:
# -----------------------------------------------------------------------------
BAT_ARGS=()
OPT_LANGUAGE=
FILES=()
DEBUG_PRINT_FORMATTER=false

# Parse arguments.
while shiftopt; do
	case "$OPT" in

	# Language options
	-l)         shiftval; OPT_LANGUAGE="${OPT_VAL}" ;;
	-l*)                  OPT_LANGUAGE="${OPT:2}" ;;
	--language) shiftval; OPT_LANGUAGE="$OPT_VAL" ;;

	# Debug options
	--debug:formatter) DEBUG_PRINT_FORMATTER=true ;;

	# Read from stdin
	-) FILES+=("-") ;;

	# bat options
	-*) {
		BAT_ARGS+=("$OPT=$OPT_VAL")
	} ;;

	# Files
	*) {
		FILES+=("$OPT")
	} ;;

	esac
done

if [[ "${#FILES[@]}" -eq 0 ]]; then
	FILES=("-")
fi

# Handle input files.
FAIL=0
for file in "${FILES[@]}"; do
	if ! process_file "$file" "$(tolower "$(extname "$file")")"; then
		FAIL=1
	fi
done

# Exit.
exit "$FAIL"
