__global_bake_version='1.11.1'
if [ "$BAKE_INTERNAL_ONLY_VERSION" = 'yes' ]; then
BAKE_INTERNAL_ONLY_VERSION_SUCCESS='yes'
return 0
fi
if [ "$0" != "${BASH_SOURCE[0]}" ] && [ "$BAKE_INTERNAL_CAN_SOURCE" != 'yes' ]; then
printf '%s\n' 'Error: This file should not be sourced' >&2
return 1
fi
bake.die() {
if [ -n "$1" ]; then
__bake_error "$1. Exiting"
else
__bake_error 'Exiting'
fi
__bake_print_big --show-time '<- ERROR'
__bake_print_stacktrace
exit 1
}
bake.warn() {
if __bake_is_color; then
printf "\033[1;33m%s:\033[0m %s\n" 'Warn' "$1"
else
printf '%s: %s\n' 'Warn' "$1"
fi
} >&2
bake.info() {
if __bake_is_color; then
printf "\033[0;34m%s:\033[0m %s\n" 'Info' "$1"
else
printf '%s: %s\n' 'Info' "$1"
fi
}
bake.assert_nonempty() {
__bake_internal_warn "Function 'bake.assert_nonempty' is deprecated. Please use 'bake.assert_not_empty' instead"
bake.assert_not_empty "$@"
}
bake.assert_not_empty() {
local variable_name=
for variable_name; do
local -n ____variable="$variable_name"
if [ -z "$____variable" ]; then
bake.die "Failed because variable '$variable_name' is empty"
fi
done; unset -v variable_name
}
bake.assert_cmd() {
local cmd=$1
if [ -z "$cmd" ]; then
bake.die "Argument must not be empty"
fi
if ! command -v "$cmd" &>/dev/null; then
bake.die "Failed to find command '$cmd'. Please install it before continuing"
fi
}
bake.has_flag() {
local flag_name="$1"
if [ -z "$flag_name" ]; then
bake.die "Argument must not be empty"
fi
if ! shift; then
bake.die 'Failed to shift'
fi
local -a flags=("$@")
if ((${#flags[@]} == 0)); then
flags=("${__bake_args_userflags[@]}")
fi
local arg=
for arg in "${flags[@]}"; do
if [ "$arg" = "$flag_name" ]; then
return 0
fi
done; unset -v arg
return 1
}
bake.cfg() {
local cfg="$1"
local value="$2"
case $cfg in
stacktrace)
case $value in
yes) __bake_internal_warn "Passing either 'yes' or 'no' as a value for 'bake.cfg stacktrace' is deprecated. Instead, use either 'on' or 'off'"; __bake_cfg_stacktrace='on' ;;
no) __bake_internal_warn "Passing either 'yes' or 'no' as a value for 'bake.cfg stacktrace' is deprecated. Instead, use either 'on' or 'off'"; __bake_cfg_stacktrace='off' ;;
on|off) __bake_cfg_stacktrace=$value ;;
*) __bake_internal_bigdie "Config property '$cfg' accepts only either 'on' or 'off'" ;;
esac
;;
big-print)
case $value in
yes|no|on|off) __bake_internal_warn "Passing any once-valid value to 'bake.cfg big-print' is deprecated. Instead, use function comments" ;;
*) __bake_internal_bigdie "Config property '$cfg' accepts only either 'on' or 'off'" ;;
esac
;;
pedantic-task-cd)
case $value in
yes) __bake_internal_warn "Passing either 'yes' or 'no' as a value for 'bake.cfg pedantic-task-cd' is deprecated. Instead, use either 'on' or 'off'"; trap '__bake_trap_debug' 'DEBUG' ;;
no) __bake_internal_warn "Passing either 'yes' or 'no' as a value for 'bake.cfg pedantic-task-cd' is deprecated. Instead, use either 'on' or 'off'"; trap - 'DEBUG' ;;
on) trap '__bake_trap_debug' 'DEBUG' ;;
off) trap - 'DEBUG' ;;
*) __bake_internal_bigdie "Config property '$cfg' accepts only either 'on' or 'off'" ;;
esac
;;
*)
__bake_internal_bigdie "No config property matched '$cfg'"
;;
esac
}
__bake_print_stacktrace() {
if [ "$__bake_cfg_stacktrace" = 'on' ]; then
if __bake_is_color; then
printf '\033[4m%s\033[0m\n' 'Stacktrace:'
else
printf '%s\n' 'Stacktrace:'
fi
local i=
for ((i=0; i<${#FUNCNAME[@]}-1; ++i)); do
local __bash_source="${BASH_SOURCE[$i]}"; __bash_source=${__bash_source##*/}
printf '%s\n' " in ${FUNCNAME[$i]} ($__bash_source:${BASH_LINENO[$i-1]})"
done; unset -v i __bash_source
fi
} >&2
__bake_trap_err() {
local error_code=$?
__bake_print_big --show-time '<- ERROR'
__bake_internal_error "Your Bakefile did not exit successfully (exit code $error_code)"
__bake_print_stacktrace
exit $error_code
} >&2
__global_bake_trap_debug_current_function=
__bake_trap_debug() {
local current_function="${FUNCNAME[1]}"
if [[ $current_function != "$__global_bake_trap_debug_current_function" \
&& $current_function == task.* ]]; then
if ! cd -- "$BAKE_ROOT"; then
__bake_internal_die "Failed to cd to \$BAKE_ROOT"
fi
fi
__global_bake_trap_debug_current_function=$current_function
} >&2
__bake_is_color() {
local fd="1"
if [ ${NO_COLOR+x} ]; then
return 1
fi
if [[ $FORCE_COLOR == @(1|2|3) ]]; then
return 0
elif [[ $FORCE_COLOR == '0' ]]; then
return 1
fi
if [ "$TERM" = 'dumb' ]; then
return 1
fi
if [ -t "$fd" ]; then
return 0
fi
return 1
}
__bake_internal_die() {
__bake_internal_error "$1. Exiting"
exit 1
}
__bake_internal_bigdie() {
__bake_print_big '<- ERROR'
__bake_internal_error "$1. Exiting"
exit 1
}
__bake_internal_error() {
if __bake_is_color; then
printf "\033[0;31m%s:\033[0m %s\n" "Error (bake)" "$1"
else
printf '%s: %s\n' 'Error (bake)' "$1"
fi
} >&2
__bake_internal_warn() {
if __bake_is_color; then
printf "\033[0;33m%s:\033[0m %s\n" "Warn (bake)" "$1"
else
printf '%s: %s\n' 'Warn (bake)' "$1"
fi
} >&2
__bake_error() {
if __bake_is_color; then
printf "\033[0;31m%s:\033[0m %s\n" 'Error' "$1"
else
printf '%s: %s\n' 'Error' "$1"
fi
} >&2
__bake_should_replace_bakescript() {
local dir="$BAKE_ROOT"
while [ ! -d "$dir/.git" ] && [[ -n "$dir" ]]; do
dir=${dir%/*}
done
if [ -d "$dir/.git" ]; then
local file=
for file in {rebase-apply/applying,rebase-apply/rebasing,rebase-apply,rebase-merge/interactive,rebase-merge,CHERRY_PICK_HEAD,MERGE_HEAD,BISECT_LOG,REVERT_HEAD}; do
if [ -f "$dir/.git/$file" ]; then
return 1
fi
done; unset -v file
fi
return 0
}
__bake_time_prepare() {
if ((BASH_VERSINFO[0] >= 5)); then
__bake_global_timestart=$EPOCHSECONDS
fi
}
__bake_time_get_total_pretty() {
unset -v REPLY; REPLY=
if ((BASH_VERSINFO[0] >= 5)); then
local timediff=$((EPOCHSECONDS - __bake_global_timestart))
if ((timediff < 1)); then
return
fi
local seconds=$((timediff % 60))
local minutes=$((timediff / 60 % 60))
local hours=$((timediff / 3600 % 60))
REPLY="${seconds}s"
if ((minutes > 0)); then
REPLY="${minutes}m $REPLY"
fi
if ((hours > 0)); then
REPLY="${hours}h $REPLY"
fi
fi
}
__bake_parse_task_comments() {
local task_name="$1"
local tmp_docstring=
local -a tmp_watch_args=()
local -A tmp_cfg_map=()
local line=
while IFS= read -r line || [ -n "$line" ]; do
if [[ $line =~ ^[[:space:]]*#[[:space:]](doc|watch|config):[[:space:]]*(.*?)$ ]]; then
local comment_category="${BASH_REMATCH[1]}"
local comment_content="${BASH_REMATCH[2]}"
if [ "$comment_category" = 'doc' ]; then
tmp_docstring=$comment_content
elif [ "$comment_category" = 'watch' ]; then
readarray -td' ' tmp_watch_args <<< "$comment_content"
tmp_watch_args[-1]=${tmp_watch_args[-1]::-1}
elif [ "$comment_category" = 'config' ]; then
local -a pairs=()
readarray -td' ' pairs <<< "$comment_content"
pairs[-1]=${pairs[-1]::-1}
local pair= key= value=
for pair in "${pairs[@]}"; do
IFS='=' read -r key value <<< "$pair"
tmp_cfg_map[$key]=${value:-on}
done; unset -v pair
fi
fi
if [[ $line =~ ^([[:space:]]*function[[:space:]]*)?(.*?)[[:space:]]*\(\)[[:space:]]*\{ ]]; then
local function_name="${BASH_REMATCH[2]}"
if [ "$function_name" == task."$task_name" ]; then
__bake_config_docstring=$tmp_docstring
__bake_config_watchexec_args+=("${tmp_watch_args[@]}")
local key=
for key in "${!tmp_cfg_map[@]}"; do
__bake_config_map[$key]=${tmp_cfg_map[$key]}
done; unset -v key
break
elif [ "$function_name" == 'init' ]; then
__bake_config_watchexec_args+=("${tmp_watch_args[@]}")
local key=
for key in "${!tmp_cfg_map[@]}"; do
__bake_config_map[$key]=${tmp_cfg_map[$key]}
done; unset -v key
fi
tmp_docstring=
tmp_watch_args=()
tmp_cfg_map=()
fi
done < "$BAKE_FILE"; unset -v line
}
__bake_print_tasks() {
local str=$'Tasks:\n'
local -a task_flags=()
local line= task_docstring=
while IFS= read -r line || [ -n "$line" ]; do
if [[ $line =~ ^[[:space:]]*#[[:space:]]doc:[[:space:]](.*?) ]]; then
task_docstring=${BASH_REMATCH[1]}
fi
if [[ $line =~ bake\.has_flag[[:space:]][\'\"]?([[:alnum:]]+) ]]; then
task_flags+=("[--${BASH_REMATCH[1]}]")
fi
if [[ $line =~ ^([[:space:]]*function[[:space:]]*)?task\.(.*?)\(\)[[:space:]]*\{[[:space:]]*(#[[:space:]]*(.*))? ]]; then
local matched_function_name="${BASH_REMATCH[2]}"
local matched_comment="${BASH_REMATCH[4]}"
if ((${#task_flags[@]} > 0)); then
str+=" ${task_flags[*]}"$'\n'
fi
task_flags=()
str+=" -> $matched_function_name"
if [[ -n "$matched_comment" || -n "$task_docstring" ]]; then
if [ -n "$matched_comment" ]; then
__bake_internal_warn "Adjacent documentation comments are deprecated. Instead, write a comment above 'task.$matched_function_name()' like so: '# doc: $matched_comment'"
task_docstring=$matched_comment
fi
if __bake_is_color; then
str+=$' \033[3m'"($task_docstring)"$'\033[0m'
else
str+=" ($task_docstring)"
fi
fi
str+=$'\n'
task_docstring=
fi
done < "$BAKE_FILE"; unset -v line
if [ -z "$str" ]; then
if __bake_is_color; then
str=$' \033[3mNo tasks\033[0m\n'
else
str=$' No tasks\n'
fi
fi
printf '%s' "$str"
} >&2
__bake_print_big() {
if [ "${__bake_config_map[big-print]}" = 'off' ]; then
return
fi
if [ "$1" = '--show-time' ]; then
local flag_show_time='yes'
local print_text="$2"
else
local flag_show_time='no'
local print_text="$1"
fi
__bake_time_get_total_pretty
local time_str="${REPLY:+ ($REPLY) }"
local _stty_height= _stty_width=
read -r _stty_height _stty_width < <(
if stty size &>/dev/null; then
stty size
else
if [ -n "$COLUMNS" ]; then
printf '%s\n' "20 $COLUMNS"
else
printf '%s\n' '20 80'
fi
fi
)
local separator_text=
printf -v separator_text '%*s' $((_stty_width - ${#print_text} - 1))
printf -v separator_text '%s' "${separator_text// /=}"
if [[ "$flag_show_time" == 'yes' && -n "$time_str" ]]; then
separator_text="${separator_text::5}${time_str}${separator_text:5+${#time_str}:${#separator_text}}"
fi
if __bake_is_color; then
printf '\033[1m%s %s\033[0m\n' "$print_text" "$separator_text"
else
printf '%s %s\n' "$print_text" "$separator_text"
fi
} >&2
__bake_parse_args() {
unset -v REPLY; REPLY=
local -i total_shifts=0
local arg=
for arg; do case $arg in
-f)
BAKE_FILE=$2
if [ -z "$BAKE_FILE" ]; then
__bake_internal_die "A value was not specified for for flag '-f'"
fi
((total_shifts += 2))
if ! shift 2; then
__bake_internal_die 'Failed to shift'
fi
if [ ! -e "$BAKE_FILE" ]; then
__bake_internal_die "Specified file '$BAKE_FILE' does not exist"
fi
if [ ! -f "$BAKE_FILE" ]; then
__bake_internal_die "Specified file '$BAKE_FILE' is not actually a file"
fi
;;
-w)
((total_shifts += 1))
if ! shift; then
__bake_internal_die 'Failed to shift'
fi
if [[ ! -v 'BAKE_INTERNAL_NO_WATCH_OVERRIDE' ]]; then
BAKE_FLAG_WATCH='yes'
fi
;;
-v)
printf '%s\n' "Version: $__global_bake_version"
exit 0
;;
-h)
local flag_help='yes'
if ! shift; then
__bake_internal_die 'Failed to shift'
fi
;;
*)
break
;;
esac done; unset -v arg
if [ -n "$BAKE_FILE" ]; then
BAKE_ROOT=$(
CDPATH= cd -- "${BAKE_FILE%/*}"
printf '%s\n' "$PWD"
)
BAKE_FILE="$BAKE_ROOT/${BAKE_FILE##*/}"
else
if ! BAKE_ROOT=$(
while [ ! -f './Bakefile.sh' ] && [ "$PWD" != / ]; do
if ! cd ..; then
exit 1
fi
done
if [ "$PWD" = / ]; then
exit 1
fi
printf '%s' "$PWD"
); then
__bake_internal_die "Failed to find 'Bakefile.sh'"
fi
BAKE_FILE="$BAKE_ROOT/Bakefile.sh"
fi
if [ "$flag_help" = 'yes' ]; then
cat <<-"EOF"
Usage: bake [-h|-v] [-w] [-f <Bakefile>] [var=value ...] <task> [args ...]
EOF
__bake_print_tasks
exit
fi
REPLY=$total_shifts
}
__bake_main() {
set -ETeo pipefail
shopt -s dotglob extglob globasciiranges globstar lastpipe shift_verbose
export LANG='C' LC_CTYPE='C' LC_NUMERIC='C' LC_TIME='C' LC_COLLATE='C' \
LC_MONETARY='C' LC_MESSAGES='C' LC_PAPER='C' LC_NAME='C' LC_ADDRESS='C' \
LC_TELEPHONE='C' LC_MEASUREMENT='C' LC_IDENTIFICATION='C' LC_ALL='C'
trap '__bake_trap_err' 'ERR'
trap ':' 'INT'
declare -ga __bake_args_original=("$@")
BAKE_ROOT=; BAKE_FILE=; BAKE_FLAG_WATCH=
__bake_parse_args "$@"
if ! shift $REPLY; then
__bake_internal_die 'Failed to shift'
fi
local __bake_key= __bake_value= __bake_arg=
for __bake_arg; do case $__bake_arg in
*=*)
IFS='=' read -r __bake_key __bake_value <<< "$__bake_arg"
declare -g "$__bake_key"
local -n __bake_variable="$__bake_key"
__bake_variable="$__bake_value"
declare -g "var_$__bake_key"
local -n __bake_variable="var_$__bake_key"
__bake_variable="$__bake_value"
if ! shift; then
__bake_internal_die 'Failed to shift'
fi
;;
*)
break
;;
esac done; unset -v __bake_arg
unset -v __bake_key __bake_value
unset -vn __bake_variable
local __bake_task="$1"
if [ -z "$__bake_task" ]; then
__bake_internal_error 'No valid task supplied'
__bake_print_tasks
exit 1
fi
if ! shift; then
__bake_internal_die 'Failed to shift'
fi
declare -ga __bake_args_userflags=("$@")
declare -g __bake_config_docstring=
declare -ga __bake_config_watchexec_args=()
declare -gA __bake_config_map=(
[stacktrace]='off'
[big-print]='on'
[pedantic-cd]='off'
)
if [ "$BAKE_FLAG_WATCH" = 'yes' ]; then
if ! command -v watchexec &>/dev/null; then
__bake_internal_die "Executable not found: 'watchexec'"
fi
__bake_parse_task_comments "$__bake_task"
BAKE_INTERNAL_NO_WATCH_OVERRIDE= exec watchexec "${__bake_config_watchexec_args[@]}" "$BAKE_ROOT/bake" -- "${__bake_args_original[@]}"
else
if ! cd -- "$BAKE_ROOT"; then
__bake_internal_die "Failed to cd"
fi
__bake_task= source "$BAKE_FILE"
if declare -f task."$__bake_task" >/dev/null 2>&1; then
__bake_parse_task_comments "$__bake_task"
__bake_print_big "-> RUNNING TASK '$__bake_task'"
if declare -f init >/dev/null 2>&1; then
init "$__bake_task"
fi
__bake_time_prepare
task."$__bake_task" "${__bake_args_userflags[@]}"
__bake_print_big --show-time "<- DONE"
else
__bake_internal_error "Task '$__bake_task' not found"
__bake_print_tasks
exit 1
fi
fi
}
__bake_main "$@"