Introduce tod - Template Open Deploy
diff --git a/util/util.sh b/util/util.sh
new file mode 100755
index 0000000..6bd5289
--- /dev/null
+++ b/util/util.sh
@@ -0,0 +1,699 @@
+#!/bin/bash
+#
+# util.sh
+#
+# Copyright 2019 Luigi Santivetti <luigi.santivetti@gmail.com>
+
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice (including the next
+# paragraph) shall be included in all copies or substantial portions of the
+# Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# Set log line max width
+if [ -n "$COLUMNS" ]; then
+ declare -ir prn_wmax="$COLUMNS"
+elif [ -n "$(tput cols 2>/dev/null)" ]; then
+ declare -ir prn_wmax="$(tput cols)"
+else
+ declare -ir prn_wmax="135" # my size :)
+fi
+
+# Set width ration for description
+if [ "${LOG_D:-0}" -eq "1" ]; then
+ declare -ir prn_wdesc_ratio="35"
+ # Set log description max width
+ declare -ir prn_wdesc="$prn_wmax / 100 * $prn_wdesc_ratio"
+else
+ declare -ir prn_wdesc="0"
+fi
+
+# Set log tags
+declare -Ar prn_tag=( \
+ ["W"]="WARNING" ["E"]="ERROR" ["D"]="DEBUG" \
+ ["I"]="INFO" ["A"]="ASKING" ["X"]="PIPE" \
+)
+
+# Set log tag max width
+declare -ir prn_wtag="$(for t in ${prn_tag[@]}; do \
+ echo -ne $t | wc -m; done | sort -r | head -1)"
+
+# Set log offset for symbols and whitespaces
+declare -ir prn_wofs="8"
+
+# Set log message max width
+declare -ir prn_wmsg="$prn_wmax - $prn_wdesc - $prn_wtag - $prn_wofs"
+
+# Some color
+declare -rA c=( \
+ [LGREEN]="\033[1;32m" [LGREY]="\033[0;37m" [LCYAN]="\033[96m" \
+ [YELLOW]="\033[1;33m" [LRED]="\033[1;31m" [LBLUE]="\033[1;34m" \
+ [NONE]="\033[0m" \
+)
+
+if which fold &>/dev/null; then
+ function prn_line
+ {
+ echo -en "$1\n" | fold -s -w "$prn_wmsg"
+ }
+else
+ function prn_line
+ {
+ echo -e "$1\n"
+ }
+fi
+
+# @return : stdout, absolute path to the main script, assume it is one level up '..'
+function get_script_dir
+{
+ pushd \
+ "$(dirname "$(readlink -f ${BASH_SOURCE[0]} 2>/dev/null)" 2>/dev/null)" \
+ &>/dev/null && {
+ echo "$(realpath "$(pwd)"/..)"
+ popd &>/dev/null
+ }
+}
+
+# @return : stdout, runtime timestamp at lauching time
+function get_script_now
+{
+ local now="$(date +'%d%m%Y%H%M%S')" && echo "$now"
+}
+
+# @desc : main routine for handling basic stdout messages
+function prn
+{
+ local nl colr ltag lmsg desc line fdesc ftag fmt_ltag fmt
+
+ fdesc="^"
+ ftag="^"
+
+ # Order here should match the invocation order in log()
+ while [ ${#} -gt 0 ]; do
+ case "$1" in
+ -n) nl="\n"; shift ;;
+ -d) desc="$2"; shift 2 ;;
+ -t) ltag="$2"; shift 2 ;;
+ -c) colr="$2"; shift 2 ;;
+ -x) ftag="$2"; line="$2\n"; shift 2 ;;
+ -l) line="$(prn_line "$line$2\n")"; shift 2 ;;
+ *) shift ;;
+ esac
+ done
+
+ # Build the format output string
+ fmt_ltag="$colr%*s${c[NONE]}"
+ fmt="%*.*s $fmt_ltag %.*s$nl"
+
+ # Process stdout
+ while read -r lmsg; do
+ printf "$fmt" $prn_wdesc $prn_wdesc $desc \
+ $prn_wtag ${ltag:0:$prn_wtag} $prn_wmsg "$lmsg"
+
+ # If printing folded lines, remove func and tag
+ desc=$fdesc && ltag=$ftag
+ done < <(cat <<EOF
+$line
+EOF
+ )
+}
+
+# @1 : text to be sent to stdout via prn
+# @return : Success if user enters y, error if n, otherwise it keeps
+# : asking for input.
+function ask
+{
+ local desc="${FUNCNAME[2]}:${BASH_LINENO[1]}"
+ local ans=""
+
+ [ "$answer" = 'y' ] && return $s_ok
+ [ "$answer" = 'n' ] && return $s_err
+
+ prn -d $desc -t ${prn_tag[A]} -c ${c[LBLUE]} -l "${*} [y/n]" 1>&2
+ while [ "$ans" != 'y' ] && [ "$ans" != 'n' ]; do read -s -n 1 ans; done
+
+ printf " $ans\n" 1>&2; [ "$ans" = "y" ] && return $s_ok || return $s_err
+}
+
+# @ : list of options and arguments (-x, -w, -d, -e, -i and --phew). For
+# : -d, debug and -i, info, --phew overrides the color to green.
+# @desc : main routine for logging facilities. Note, all logs are redirected
+# : to stderr and logging error (-e) returns an error (return 1)
+function log
+{
+ local -i do_print=0
+ local -a msg=( )
+ local desc=""
+ local ltag=""
+ local colr=""
+ local xlog=""
+
+ [ $LOG_D -eq 1 ] && desc="-d ${FUNCNAME[2]}:${BASH_LINENO[1]}" || desc="-d ''"
+
+ case "${1}" in
+ -x) [ $LOG_X -eq 1 ] && {
+ shift
+ [ -n "$1" ] && xlog="-x $1"
+ shift
+ colr="-c ${c[LCYAN]}"
+ do_print=1
+ msg=( "${*:-$(</dev/stdin)}" )
+ ltag=${prn_tag[X]}
+ } ;;
+ -w) [ $LOG_W -eq 1 ] && {
+ shift
+ colr="-c ${c[YELLOW]}"
+ do_print=1
+ msg=( "${*}" )
+ ltag=${prn_tag[W]}
+ } ;;
+ -d) [ $LOG_D -eq 1 ] && {
+ shift
+ [ "$1" = "--phew" ] && {
+ shift
+ colr="-c ${c[LGREEN]}"
+ } || colr="-c ${c[LGREY]}"
+ do_print=1
+ msg=( "${*}" )
+ ltag=${prn_tag[D]}
+ } ;;
+ -i) [ $LOG_I -eq 1 ] && {
+ shift
+ [ "$1" = "--phew" ] && {
+ shift
+ colr="-c ${c[LGREEN]}"
+ } || colr="-c ${c[NONE]}"
+ do_print=1
+ msg=( "${*}" )
+ ltag=${prn_tag[I]}
+ } ;;
+ -e) # Always print errors
+ shift
+ prn -n $desc -t ${prn_tag[E]} -c ${c[LRED]} -l "${*}" 1>&2
+ return $s_err ;;
+ esac
+
+ [ $do_print -eq 1 ] && {
+ prn -n $desc -t $ltag $colr $xlog -l "${msg[*]}" 1>&2
+ }; return $s_ok
+}
+
+function is_set
+{
+ [ "$(set -o | grep $1 | grep -oE "(on|off)")" = "on" ]
+}
+
+# desc : util handler
+function lets
+{
+ is_set "xtrace" && local xtrace="set -x" && set +x
+ local -i ret
+
+ case "$1" in
+ -l|--log) shift && log ${@}; ret=$? ;;
+ -a|--ask) shift && ask ${@}; ret=$? ;;
+ -d|--die) shift && die ${@}; ret=$? ;;
+ *) eval ${xtrace}; return $s_err ;;
+ esac
+
+ eval ${xtrace}; return $ret
+}
+
+# @1 : string compliant with unix file system node addressing
+# @desc : using ${*} to get it to fail if passed in with multiple words
+function is_unix_path
+{
+ [ ! -z "${*}" ] && pathchk -P -- "${*}" &>/dev/null
+}
+
+# @1 : absolute path to file
+# @2 : absolute path to directory
+# @return : success if @2 exists and @1 is a valid unix path and @1 is a node in @2,
+# : error otherwise.
+# @desc : useful when @1 is a prospective file, but not a file just yet
+function is_in_tree
+{
+ [ -d "${2}" ] && is_unix_path "${1}" || return 1
+
+ case "${1}" in "${2}"*) return 0 ;; *) return 1 ;; esac
+}
+
+# @1 : absolute path to a prospective file
+# @return : success if the named file in @1 is or will be in the $instance_d tree
+function is_in_tree_instance_d
+{
+ is_in_tree "${1}" "${instance_d}"
+}
+
+# @1 : file mode permission
+# @2 : file owner and group mode permission
+# @3 : (optional) recursive flag -R
+# @ : list of files and/or directories
+# @return : Success if it could chmod and chown a valid file
+function set_mode
+{
+ local mode=$1; shift
+ local own=$1; shift
+ local t
+
+ if [ "$1" = "-R" ]; then local flags=$1; shift; fi
+
+ for t in ${@}; do
+ if [ -f "$t" ] || [ -d "$t" ]; then
+ sudo chmod $flags $mode "$t" && sudo chown $own:$own "$t" || return 1
+ else
+ lets -l -e "invalid: $t"
+ fi
+ done
+}
+
+# @1 : list of packages
+function util_install_dependency
+{
+ local pkg
+
+ for pkg in ${@}; do
+ sudo apt-get install -y --fix-missing $pkg 2>&1 | lets -l -x "apt-get"
+ [ ${PIPESTATUS[0]} -eq 0 ] || {
+ lets -l -e "failed to install $pkg"; return 1
+ }
+ done
+}
+
+# @1 : absolute path
+# @desc : Validate a prospective directory path
+function is_valid_new_dir
+{
+ is_unix_path "${*}" && [ ! -e "${*}" ]
+}
+
+# @desc : remove trailing and leading whitespaces
+function get_wtrim
+{
+ echo "${*}"
+}
+
+# @1 : absolute path to a generic file
+# @return : basename without extension
+# @desc : convert an absolute path to a lhs suitable variable name. This
+# : means stripping anything after the dot
+function get_stripname
+{
+ local stripped
+
+ stripped="$(basename $1)"; stripped="${stripped%%.*}"
+ echo "$stripped"
+}
+
+# @1 : array variable name
+# @desc : check if the vriable named in @1 is set and is an array
+function is_array_set
+{
+ declare -p -- "${*}" | grep -q "declare \-a"
+}
+
+# @1 : function name
+# @desc : check if the vriable named in @1 is set
+function is_function_set
+{
+ declare -F "${*}" &>/dev/null
+}
+
+function is_system_ready
+{
+ case "$BASH_VERSION" in
+ ''|[123].*|4.[0123]) echo "ERROR: Bash 4.4 required" >&2; return 1 ;;
+ esac
+}
+
+# @1 : asoblute path to output file
+# @2 : fd variable name reference
+# @3 : char representing file mode operation - r,w,rw
+# @desc : in preparation to add support for dedicated fds for logs and output
+function fdopen
+{
+ local -n out_fd=$2
+ local -r file=$1
+
+ [ -z "$out_fd" ] || { lets -l -e "invalid out fd"; return 1; }
+
+ case "$3" in
+ rw|wr) [ -n "$file" ] && [ ! -f "$file" ] && \
+ is_unix_path $file && exec {fd}<> $file ;;
+ w ) [ ! -f "$file" ] && \
+ is_unix_path $file && exec {fd}> $file ;;
+ r ) [ -r "$file" ] && exec {fd}< $file ;;
+ esac
+
+ # Posix bash dynamic fd allocation is from 9 on
+ [ -f "$file" ] && [ $fd -gt 9 ] && out_fd=$fd && return
+
+ lets -l -e "failed to open fd=$1"
+ return 1
+}
+
+# @1 : fd int
+# @2 : char representing file mode operation - r,w,rw
+# @desc : in preparation to add support for dedicated fds for logs and output
+function fdclose
+{
+ local -ri fd=$1
+
+ [ $fd -gt 9 ] || { lets -l -e "invalid file fd"; return 1; }
+
+ case "$2" in
+ rw|wr) exec {fd}>&- && { lets -l -d "$fd closed"; return; }
+ exec {fd}<&- && { lets -l -d "$fd closed"; return; } ;;
+ w ) exec {fd}>&- && { lets -l -d "$fd closed"; return; } ;;
+ r ) exec {fd}<&- && { lets -l -d "$fd closed"; return; } ;;
+ esac
+
+ lets -l -e "failed to close fd=$2"
+ return 1
+}
+
+function get_hunk_indexes
+{
+ local begin="$rex_legal_manifest_hook_begin"
+ local ended="$rex_legal_manifest_hook_ended"
+ local -a idx
+
+ idx=( $(grep -n -e "$begin" -e "$ended" $manifest_f | cut -f1 -d':') )
+ local -i mod="${#idx[@]} % 2"
+
+ [ $mod -eq 0 ] && echo ${idx[@]} || lets -l -e "invalid $manifest_f"
+}
+
+function get_hunk_pair
+{
+ local -i head; local -i tail; local -a pair
+ local -i from=$1; shift
+
+ pair=( ${@:$from:2} )
+ [ ${#pair[@]} -eq 2 ] || { [ ${#pair[@]} -eq 0 ] && return $s_ok; } || \
+ return $s_err
+
+ tail=${pair[0]}; head=${pair[1]};
+ tail="$head - $tail + 1"
+
+ echo "$head $tail"
+}
+
+function get_hunk
+{
+ cat $manifest_f | head -n $1 | tail -n $2
+}
+
+# @1 : remote address
+# @2 : branch name
+# @3 : mirror name
+function get_new_sha_upstream
+{
+ local remote_sha
+
+ remote_sha="$(git ls-remote $1 $2 | head -1 | cut -f 1)"
+
+ if [ -d "$3" ]; then
+ [ -f "$3/$__version" ] || {
+ lets -l -e "${3:-uknown mirror} not versioned"
+ return $s_err2
+ }
+ else
+ mkdir -p "$3" || { lets -l -e "mkdir failed: $3"; return $s_err2; }
+ echo $remote_sha; return $s_ok
+ fi
+
+ [ "$(< "$3/$__version")" != "$remote_sha" ] && {
+ lets -l -d "$3: $remote_sha"
+ rm -rf $3; echo $remote_sha; return $s_ok
+ } || {
+ lets -l -i "$3 is up to date";
+ }
+
+ return $s_err
+}
+
+function __git_archive
+{
+ local mirror="$1"
+ local remote="$4"
+ local staging_d="$(dirname $2)"
+ local flags="--prefix=$mirror/" # need trailing slash
+ flags+=" --output=$2"
+ flags+=" --format=$3"
+ flags+=" --remote=$remote"
+# flags+=" --verbose"
+ local branch="$5"
+ local remote_sha
+
+ remote_sha="$(get_new_sha_upstream $remote $branch $staging_d/$mirror)"
+ case "$?" in
+ $s_err2 ) return $s_err ;; # no new sha, an error occurred
+ $s_err ) return $s_ok ;; # no new sha, no need to update
+ esac
+
+ git archive $flags $branch 2>&1 | lets -l -x "git"
+
+ [ ${PIPESTATUS[0]} -eq $s_ok ] && {
+ tar -xf $2 -C "${2%/*}" 2>&1 | lets -l -x "tar"
+ [ ${PIPESTATUS[0]} -eq $s_ok ] && rm -rf $2 || {
+ lets -l -w "tar failed: $2. Clean up needed"
+ return $s_err
+ }
+ echo $remote_sha > "${2%/*}/$mirror/$__version"
+ }
+
+ return ${PIPESTATUS[0]}
+}
+
+function __git_clone
+{
+ local mirror_d="$3"
+ local branch="$1"
+ local remote="$2"
+ local flags="--single-branch"
+ flags+=" --depth=1"
+ flags+=" --branch=$branch"
+ flags+=" --verbose"
+ local remote_sha
+
+ remote_sha="$(get_new_sha_upstream $remote $branch $mirror_d)"
+ case "$?" in
+ $s_err2 ) return $s_err ;; # no new sha, an error occurred
+ $s_err ) return $s_ok ;; # no new sha, no need to update
+ esac
+
+ git clone $flags $remote $3 2>&1 | lets -l -x "git"
+ [ ${PIPESTATUS[0]} -eq $s_ok ] || return $s_err
+
+ echo $remote_sha > "$mirror_d/$__version"
+}
+
+function __wget_file
+{
+ local output="-O $1"
+ local remote="$2"
+ local flags="-nv"
+
+ if [ ! -f "$1" ]; then
+ wget $flags $output $remote 2>&1 | lets -l -x "wget"
+ [ ${PIPESTATUS[0]} -eq 0 ] || {
+ lets -l -d "failed wget $(basename $1)"
+ return $s_err
+ }
+ else
+ lets -l -i "$1 up to date"
+ fi; [ -f "$1" ]
+}
+
+# @1 : module name
+# @2 : output dir
+function process_manifest
+{
+ local -i from=1 # $@ is 1 indexed
+ local -a hunks
+ local -a pair
+
+ [ -z "$1" ] && { lets -l -e "invalid module name"; return $s_err; }
+
+ hunks=( $(get_hunk_indexes) )
+ case "$?" in $s_err ) return $s_err ;; esac
+
+ pair=( $(get_hunk_pair $from ${hunks[@]}) )
+ case "$?" in $s_err ) return $s_err ;; esac
+
+ while [ -n "$pair" ]; do
+ (
+ # Clean up manifest fields before sourcing
+ modname=""; mirror=""; protocol=""; branch=""; host=""; user="";
+ method=""; format=""; file=""
+
+ set -e
+ source <(get_hunk ${pair[@]}) && set +e || { set +e; return $s_err3; }
+
+ [ "$1" = "$modname" ] || return $s_ok # no error, try again
+
+ case "$method" in
+ git-archive )
+ [ -n "$user" ] && user="$user@"
+ [ -n "$protocol" ] && protocol="$protocol://"
+ [ -n "$format" ] || return $s_err4
+
+ local remote="${protocol:-}${user:-}$host/$mirror"
+ local tarball="$2/$mirror.$format"
+
+ __git_archive \
+ $mirror $tarball $format $remote $branch || return $s_err5 ;;
+ git-clone )
+ [ -n "$user" ] && user="$user@"
+ [ -n "$protocol" ] && protocol="$protocol://"
+
+ local remote="${protocol:-}${user:-}$host/$mirror"
+
+ __git_clone $branch $remote $2/$mirror || return $s_err6 ;;
+ wget-file )
+ [ -n "$file" ] || return $s_err7
+ [ -n "$format" ] && file="$file.$format"
+
+ __wget_file $2/$file $host/$file || return $s_err8 ;;
+ *) return $s_err9 ;;
+ esac
+ ) || {
+ case "$?" in
+ $s_err3 ) lets -l -e "$1: failed to source manifest" ;;
+ $s_err4 ) lets -l -e "$1: manifest missing format" ;;
+ $s_err5 ) lets -l -e "$1: failed git archive" ;;
+ $s_err6 ) lets -l -e "$1: failed git clone" ;;
+ $s_err7 ) lets -l -e "$1: manifest missing file" ;;
+ $s_err8 ) lets -l -e "$1: failed wget file" ;;
+ $s_err9 ) lets -l -e "$1: invalid manifest method" ;;
+ esac; return $s_err
+ }
+
+ from="$from + 2"; pair=( $(get_hunk_pair $from ${hunks[@]}) )
+ [ $? -eq 0 ] || return $s_err
+ done; return $s_ok
+}
+
+function get_all_modules
+{
+ ls -Ad $module_d/*/ | xargs -n1 basename
+}
+
+function get_modules_optarg
+{
+ local modname; local -a mods=( ); local -a imods=( )
+
+ if [ -z "${*}" ]; then
+ imods=( $(get_all_modules) )
+ else
+ imods=( "${@}" )
+ fi
+
+ for modname in ${imods[@]}; do
+ [ -n "$modname" ] && [ -d "$module_d/$modname" ] || {
+ lets -l -w "$modname not found"
+ continue
+ }
+
+ __is_in_array "$modname" "${BLMODULES[@]}" && [ $modall -eq 0 ] && {
+ lets -l -d "$modname is blacklisted, set MODALL=1 to force"
+ continue
+ }
+
+ mods+=( $modname )
+ done
+
+ [ -n "${mods[*]}" ] && echo "${mods[@]}"
+}
+
+function is_opt
+{
+ local k
+
+ for k in ${!OPTIONS[@]}; do
+ [ ${OPTIONS[$k]} -eq 1 ] && \
+ case "${*}" in *"$k"* ) return $s_ok ;; esac
+ done
+
+ return $s_err
+}
+
+function get_opt
+{
+ local k
+
+ for k in ${!OPTIONS[@]}; do
+ [ ${OPTIONS[$k]} -eq 1 ] && echo $k
+ done
+}
+
+function __is_in_array
+{
+ printf "%s\n" "${@:2}" | grep -qFx -- "$1"
+}
+
+# @ : list of items to be merged
+# @desc : very inefficient way for ensuring uniqueness without sorting
+function __merge_array
+{
+ cat -n <(printf "%s\n" "${@}") | sort -t$'\t' -k2,2 -u | \
+ sort -t$'\t' -k1,1 | cut -d$'\t' -f 2
+}
+
+function __append_item
+{
+ # emulating `+=` which adds items to the end of the list
+ local -ar __tmp_a=( "${@:2}" "$1" )
+ __merge_array "${__tmp_a[@]}"
+}
+
+function is_in_options
+{
+ __is_in_array "$1" ${!OPTIONS[@]}
+}
+
+function is_in_modules
+{
+ __is_in_array "$1" ${MODULES[@]}
+}
+
+function are_in_modules
+{
+ local i
+ for i in "${@}"; do __is_in_array "$i" "${MODULES[@]}" || return $s_err; done
+}
+
+function is_in_optarg
+{
+ __is_in_array "$1" ${OPTARG[@]}
+}
+
+function module_enable
+{
+ MODULES=( $(__append_item "$1" "${MODULES[@]}") )
+}
+
+function getflag
+{
+ local -ar flags=( ${2//:/ } )
+
+ __is_in_array "$1" ${flags[@]} && echo "true" || echo "false"
+}
+
+function setflag
+{
+ echo "${*// /:}"
+}