#!/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
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" "bat-modules" "${@:2}" 1>&2
}
print_error(){
printc "%{RED}[%s error]%{CLEAR}: $1%{CLEAR}\n" "bat-modules" "${@: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
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" "bat-modules" "$ARG"
exit 1
fi
}
setargs "$@"
_ARGV_ORIGINAL=("$@")
hook_color(){
SHIFTOPT_HOOKS+=("__shiftopt_hook__color")
__shiftopt_hook__color(){
case "$OPT" in
--no-color)OPT_COLOR=false;;
--color){
case "$OPT_VAL" in
"")OPT_COLOR=true;;
always|true)OPT_COLOR=true;;
never|false)OPT_COLOR=false;;
auto)return 0;;
*)printc "%{RED}%s: '--color' expects value of 'auto', 'always', or 'never'%{CLEAR}\n" "bat-modules"
exit 1
esac
};;
*)return 1
esac
printc_init "$OPT_COLOR"
return 0
}
if [[ -z $OPT_COLOR ]];then
if [[ -t 1 ]];then
OPT_COLOR=true
else
OPT_COLOR=false
fi
printc_init "$OPT_COLOR"
fi
}
hook_version(){
SHIFTOPT_HOOKS+=("__shiftopt_hook__version")
__shiftopt_hook__version(){
if [[ $OPT == "--version" ]];then
printf "%s %s\n\n%s\n%s\n" \
"bat-modules" \
"2024.08.24" \
"Copyright (C) 2019-2021 eth-p | MIT License" \
"https://github.com/eth-p/bat-extras"
exit 0
fi
return 1
}
}
dsl_parse_file(){
dsl_parse <"$1"
return $?
}
dsl_parse(){
local line
local line_raw
local line_fields
local indent
local command
DSL_LINE_NUMBER=0
DSL_COMMAND=''
while IFS='' read -r line_raw;do
((DSL_LINE_NUMBER++))||true
[[ $line_raw =~ ^(	|[[:space:]]{2,}) ]]||true
indent="${BASH_REMATCH[1]}"
line="${line_raw:${#indent}}"
if [[ -n $line ]]&&! [[ $line =~ ^# ]];then
eval "$(dsl_parse_line <<<"$line")"
if [[ ${#indent} -eq 0 ]];then
if [[ -n $DSL_COMMAND ]];then
dsl_on_command_commit
fi
DSL_COMMAND="${line_fields[0]}"
dsl_on_command "${line_fields[@]}"
else
dsl_on_option "${line_fields[@]}"
fi
fi
dsl_on_raw "$indent" "$line"
done
if [[ -n $DSL_COMMAND ]];then
dsl_on_command_commit
fi
return 0
}
dsl_parse_line(){
awk '
		{
			print "line_fields=()"
			n=0
			buffer=""
			quoted=0
			while ($0 != "") {
				quoted_once=0
				while ($0 != "") {
					# Match " ", "\", or quote.
					if (!match($0, /[\t \\"]/)) {
						buffer=sprintf("%s%s", buffer, $0)
						$0=""
						break
					}

					# Extract the character and previous literal string.
					buffer=sprintf("%s%s", buffer, substr($0, 0, RSTART - 1))
					chr=substr($0, RSTART, RLENGTH)
					$0=substr($0, RSTART + RLENGTH)

					# Handle the matched character.
					if (chr == "\\") {
						buffer=sprintf("%s%s", buffer, substr($0, 0, 1))
						$0=substr($0, 2)
						continue
					}

					if (chr == "\"") {
						quoted=!quoted
						quoted_once=1
						continue
					}

					if ((chr == " " || chr == "\t") && quoted) {
						buffer=sprintf("%s ", buffer)
						continue
					}

					break
				}

				# If the buffer is empty and it is not intentionally empty,
				# it should not be considered a separate field.
				if (buffer == "" && !quoted_once) {
					continue
				}

				# Escape the parsed value.
				sub(/"/, "\\\"", buffer)
				sub(/\$/, "\\$", buffer)

				# Print the parsed value.
				print sprintf("line_fields[%s]=\"%s\"", n, buffer)
				buffer=""
				n=n+1
			}
		}
	'
}
dsl_on_raw(){
:
}
tolower(){
tr "[:upper:]" "[:lower:]" <<<"$1"
}
toupper(){
tr "[:lower:]" "[:upper:]" <<<"$1"
}
hook_color
hook_version
COMMON_URL_GITHUB="https://github.com/%s.git"
COMMON_URL_GITLAB="https://gitlab.com/%s.git"
CONFIG_DIR="$(bat --config-dir)"
SYNTAX_DIR="$CONFIG_DIR/syntaxes"
THEME_DIR="$CONFIG_DIR/themes"
MODULES_FILE="$CONFIG_DIR/modules.txt"
ACTION="help"
while shiftopt;do
case "$OPT" in
--help)ACTION="help";;
--update)ACTION="update";;
--clear)ACTION="clear";;
--setup)ACTION="setup";;
--modules-file)ACTION="show_file";;
-*){
printc "%{RED}%s: unknown option '%s'%{CLEAR}\n" "bat-modules" "$OPT" 1>&2
exit 1
}
esac
done
ensure_setup(){
if ! [[ -f $MODULES_FILE ]];then
printc "%{YELLOW}The bat-modules modules file wasn't found.%{CLEAR}\n"
printc "%{YELLOW}Use %{CLEAR}%s --setup%{YELLOW} to set up bat-modules, or%{CLEAR}\n" "bat-modules"
printc "%{YELLOW}read the documentation at %{CLEAR}%s%{YELLOW} for more info.%{CLEAR}\n" "https://github.com/eth-p/bat-extras"
exit 1
fi
mkdir -p "$SYNTAX_DIR" &>/dev/null||true
mkdir -p "$THEME_DIR" &>/dev/null||true
}
fail_parsing(){
print_warning "Failed to parse bat-modules file."
print_warning "Line %s: %s" "$DSL_LINE" "$1"
exit 1
}
dsl_on_command(){
BM_TYPE="$(tolower "$1")"
BM_SOURCE="$(parse_source "$2")"
BM_OPT_CHECKOUT="master"
case "$BM_TYPE" in
"syntax"|"theme"):;;
*)fail "unknown module type '$BM_TYPE'"
esac
}
dsl_on_option(){
case "$(tolower "$1")" in
checkout)BM_OPT_CHECKOUT="$2"
return 0
esac
case "$BM_TYPE" in
"syntax")on_option_for_syntax "$@"&&return 0;;
"theme")on_option_for_theme "$@"&&return 0
esac
fail "unknown %s option '%s'" "$BM_TYPE" "$*"
}
on_option_for_syntax(){
:
}
on_option_for_theme(){
:
}
parse_source(){
local source="$1"
case "$source" in
"github:"*|"gh:"*)source="$(printf "$COMMON_URL_GITHUB" "$(cut -d':' -f2- <<<"$source")")"
;;
"gitlab:"*|"gl:"*)source="$(printf "$COMMON_URL_GITLAB" "$(cut -d':' -f2- <<<"$source")")"
;;
*)if
[[ $1 =~ ^([A-Za-z0-9-])+/([A-Za-z0-9-])+$ ]]
then
parse_source "github:$1" "${@:2}"
return $?
fi
esac
echo "$source"
}
parse_source_name(){
basename "$1" .git
}
action:show_file(){
printf "%s\n" "$MODULES_FILE"
}
action:setup(){
if ! [[ -f $MODULES_FILE ]];then
cat >"$MODULES_FILE" <<-EOF
# bat-modules example file.
# See https://github.com/eth-p/bat-extras for documentation and help.

# syntax example/syntax

# theme https://github.com/example/theme.git
#     checkout abcdef1

EOF
fi
"${EDITOR:-vi}" "$MODULES_FILE"
}
action:help(){
{
printc "%{YELLOW}%s help:%{CLEAR}\n" "bat-modules"
printc "  --clear         -- Clear the cached themes and syntaxes.\n"
printc "  --update        -- Update themes and syntaxes.\n"
printc "  --setup         -- Edit the bat-modules modules.txt file.\n"
printc "  --modules-file  -- Show the bat-modules modules.txt file.\n"
} 1>&2
}
action:clear(){
printc "%{YELLOW}Clearing bat syntax and theme cache...%{CLEAR}\n"
"bat" cache --clear
}
action:update(){
CHANGES=false
dsl_on_command_commit(){
case "$BM_TYPE" in
syntax)cd "$SYNTAX_DIR";;
theme)cd "$THEME_DIR"
esac
local hash
local name="$(parse_source_name "$BM_SOURCE")"
printc "%{BLUE}----- %s: %s -----%{CLEAR}\n" "$BM_TYPE" "$name"
if ! [[ -d $name ]];then
printc "%{YELLOW}Cloning...%{CLEAR}\n"
"git" clone "$BM_SOURCE" "$name"
CHANGES=true
fi
printc "%{YELLOW}Updating...%{CLEAR}\n"
cd "$name"
hash="$("git" rev-parse HEAD)"
"git" fetch origin --quiet
"git" checkout "$BM_OPT_CHECKOUT" --quiet
hash_new="$("git" rev-parse HEAD)"
if [[ $hash != "$hash_new" ]];then
printc "%{YELLOW}Updated to %s.%{CLEAR}\n" "$hash_new"
CHANGES=true
fi
}
ensure_setup
dsl_parse_file "$MODULES_FILE"
printc "%{BLUE}----- bat-modules -----%{CLEAR}\n" "$BM_TYPE" "$name"
printc "%{YELLOW}Done.%{CLEAR}\n"
if "$CHANGES";then
printc "%{YELLOW}Rebuilding cache...%{CLEAR}\n"
"bat" cache --build
fi
}
action:"$ACTION"
exit $?
