# -*- mode: sh; sh-shell: bash -*-
# vim: set ft=bash:
# shellcheck shell=bash
require --opt tty_lib
### Library of general functions which are always present
# TODO: add [-s|--state "extrastate"]
# FIXME: all is_foo must be in foo module with require foo_lib as dependency
function memo ## [-c|-d] [cmd args..].. - memoize the result/stdout/stderr of commands, will always return the same result again
{
## Memoizes for the current run only. See memodb for persistent memoization.
## Memoized functions must be pure and only depend on their arguments and the current working directory.
## Can not handle null bytes in stdout/stderr, use memodb when that is required.
if [[ $1 == "-c" ]]; then
unset MEMO_RC MEMO_STDOUT MEMO_STDERR
shift
fi
local delete=false
if [[ $1 == "-d" ]]; then
delete=true
shift
fi
declare -gAi MEMO_RC
declare -gA MEMO_STDOUT MEMO_STDERR
# shellcheck disable=SC2155
local key="$(hash_args "$$ $PWD $*")"
if [[ $delete = true && -v MEMO_RC["$key"] ]]; then
unset "MEMO_RC[$key]" "MEMO_STDOUT[$key]" "MEMO_STDERR[$key]"
fi
if [[ -v MEMO_RC["$key"] ]]; then
echo -n "${MEMO_STDOUT[$key]}"
echo -n "${MEMO_STDERR[$key]}" 1>&2
return ${MEMO_RC["$key"]}
elif [[ $# -ge 1 ]]; then
touch "$TMPDIR/$key.stdout" "$TMPDIR/$key.stderr"
local rc=0
eval "$*" > >(tee "$TMPDIR/$key.stdout") 2> >(tee "$TMPDIR/$key.stderr" 1>&2) || rc=$?
MEMO_RC["$key"]=$rc
MEMO_STDOUT["$key"]=$(<"$TMPDIR/$key.stdout")
MEMO_STDERR["$key"]=$(<"$TMPDIR/$key.stderr")
rm "$TMPDIR/$key.stdout" "$TMPDIR/$key.stderr"
return "$rc"
fi
}
function memofn ## <functionnames..> - rewrites a function into a function that uses 'memo'
{
## This renames the given functions to 'nomemo_<functionname>' and creates a wrapper
## function with the original name that memoizes the result.
for fn in "$@"; do
# rename the original function as nomemo_*
eval "nomemo_$(declare -f "$fn")"
# create a new function that calls memo with the original function
eval "function $fn () { memo nomemo_$fn \"\$@\" ; }"
done
}
function is_scalar ## <name> - Checks that a variable 'name' is a scalar (non array) variable.
{
# shellcheck disable=2155
local type="$(declare -p "$1")"
[[ $type == 'declare --'* || $type == 'declare -i'* || $type == 'declare -x'* ]]
}
function called_as ## [pattern..] - Checks whenever bar is called as a symlink matching the given patterns.
{
## This is usable as a rule dependency.
local pat
for pat in "$@"; do
# shellcheck disable=2053
[[ "$BAR_CALLED_AS" = $pat ]] && return 0
done
return 1
}
function progress # [message..] - elusive progress notice, will be overwritten by the next output.
{
[[ "${TERM:-dumb}" = "dumb" ]] && return 0
if (( BAR_CHECK_LEVEL > 0 )); then
(( "${BAR_VERBOSITY_LEVEL:-5}" > 2 )) && {
tty_newline
tty_echo "${TTYERR[K_B]:-} RUN: $*${TTYERR[n]:-}"
}
else
(( "${BAR_VERBOSITY_LEVEL:-5}" > 1 )) && {
tty_newline
tty_echo -n "${TTYERR[K_B]:-} RUN: $*${TTYERR[ce]:-}${TTYERR[cr]:-}${TTYERR[n]:-}"
}
fi >&2
}
function success # [message..] - Message for success.
{
if (( BAR_CHECK_LEVEL > 0 )); then
(( "${BAR_VERBOSITY_LEVEL:-5}" > 2 )) && {
tty_newline
echo -e "${TTYERR[g_B]:-}CHECK:${TTYERR[n]:-} $*"
}
else
(( "${BAR_VERBOSITY_LEVEL:-5}" > 1 )) && {
tty_newline
echo -e "${TTYERR[g_B]:-} OK:${TTYERR[n]:-} $*"
}
fi >&2
}
function failure # [message..] - Message for failure.
{
if (( BAR_CHECK_LEVEL > 0 )); then
(( "${BAR_VERBOSITY_LEVEL:-5}" > 2 )) && {
tty_newline
echo -e "${TTYERR[K_B]:-}CHECK: ${TTYERR[__S]:-}$*${TTYERR[n]:-}"
}
else
(( "${BAR_VERBOSITY_LEVEL:-5}" > 1 )) && {
tty_newline
echo -e "${TTYERR[r_B]:-} FAIL:${TTYERR[n]:-} $*"
}
fi >&2
}
function uncond_call # <rc> [message..] - Message for unconditional dependency calls
{
declare -i rc="$1"
shift
if (( "${BAR_VERBOSITY_LEVEL:-5}" > 2 )); then
tty_newline
if (( rc == 0 )); then
echo -e "${TTYERR[g_B]:-} EVAL:${TTYERR[n]:-} $*"
else
echo -e "${TTYERR[K_B]:-} EVAL:${TTYERR[n]:-} $*"
fi
fi >&2
}
function rule_autoload # <name> - module autoloader
{
[[ "$1" = -* ]] && return 0
trace "$*"
local modname="${1#always_}"
modname="${modname#is_}"
modname="${modname#has_}"
modname="${modname#try_}"
modname="${modname%%_*}"
require --opt "$modname" "${modname}_lib"
# when $name exists as command or function then create a rule for that
if ! rule_exists "$1" && command -v "$1" >/dev/null ; then
rule "$1:"
fi
rule_exists "$1" || die "no rule, command or function '$1' defined"
}
function is_command_installed ## <command> - Checks whenever a function exists or a shell command is in PATH.
{
if command -v "$1" >/dev/null; then
return 0
else
info "$1 not installed"
return 1
fi
}
memofn is_command_installed
function bar_now ## Returns the current timestamp in microseconds since epoch.
{
echo "${EPOCHREALTIME//[^0-9]/}"
}
function hash_args ## <arguments..> - returns the sha1hash of all supplied arguments
{
# shellcheck disable=SC2155
local hash="$(sha1sum <<<"$*")"
echo "${hash:0:40}"
}
function iset ## <target> [values].. - indirect assign values to a variable given by name, assigns to an array when more values are given.
{
## When target is empty or '[*]' (missing name) then this is a no-op. Otherwise target
## must be a declared variable When no values are given then target is cleared.
[[ -z "${1:-}" || "$1" = "["*"]" ]] && return 0
local target="$1"
shift
declare -p "${target%\[*}" &>/dev/null || {
error "$target is not defined"
return 1
}
if (( $# == 0 )); then
eval "$target=()"
elif (( $# == 1 )); then
printf -v "$target" '%s' "${1:-}"
else
eval "$target=( \"\$@\" )"
fi
}