#!/bin/sh # vim: noet:ts=8:sw=8 # jpokorny@redhat.com # NOTE: not ready for arbitrary CWD, from . only # NOTE: this is always to be run from within git repo so things like # "git rev-parse" will work (see do_git_submodule) # NOTE: really stupid, no timestamping logic and similar fancy stuff # TODO: new action "ahead" to check new versions (tags in git, etc.) # # internals # CLR_USR=32 CLR_SCR=36 CLR_BAD=31 CLR_WRN=33 CLR_INF=35 CLR_CMD=34 PRINTED=false GIT_PREFIX=$(git rev-parse --show-prefix) check_nargs () { [ $1 -ge $2 ] ret=$? [ $ret -eq 0 ] || do_announce "action failed (expected $1+ args, got $2)" $CLR_BAD return $ret } check_ret () { [ $1 -ne 0 ] \ && do_announce "action failed with exit status $1" $CLR_BAD return $1 } do_del () { do_announce "about to delete $*" $CLR_WRN do_announce "has to be confirmed (y)..." $CLR_INF rm -rfI -- "$@" 2>/dev/null [ $? -eq 0 ] || do_announce "something to be deleted not present" $CLR_WRN } do_announce () { # usage: see announce ($1, $3) + $2=colour # use colours if available if [ "$3" = "delimit" ] && $PRINTED; then echo else PRINTED=true fi if test -t 1; then echo -en "\\033[${2}m" echo -n "$1" echo -e "\\033[0m" else echo "[[ $1 ]]" fi } do_action () { # usage: $1=action, $2=directory, $3=type-specific hook for get/refresh # sources [ , $4=custom function for hooking-in ] # if two-phased and function for hooking-in is present hook=false if [ $# -ge 4 ] && type -t function_name $4 | grep -q "^function$"; then hook=true fi case "$1" in "get"|"refresh") # shortcut if target dir already exists or target # files were subsequently generated OK [ "$1" = "get" ] && $hook && $4 "probe" && continue $3 "$2" ;; "clean") ret=0 ;; "purge") do_del "$submodule" ret=$? ;; *) do_announce "Unexpected action $1" $CLR_BAD exit 1 ;; esac # repeat if two-phased and function for hooking-in is present $hook && $4 "$1" return $ret } # # kind of API for sourced choices (functions starting with "init_") # init_register () { # usage: $1=choice to register CHOICES="$CHOICES|$1" } init_announce () { # usage: $1=message to output [, $2=start with extra newline if "delimit")] do_announce "$1" $CLR_USR $2 } init_verbosely () { do_announce "$*" $CLR_CMD $@ } init_require () { # usage: $1=what is required [, $2=test (function/command)] tester=$2 if [ -z "$tester" ]; then tester=which fi $tester $1 >/dev/null ret=$? [ $ret -eq 0 ] \ && do_announce "require $1: checked ok" $CLR_INF \ || do_announce "require $1: not met" $CLR_BAD return $ret } init_target () { # usage: $1=action ($1 passed from main or "probe"), $2..target dir # [, $3=getter ] existing=$(find "$2" -mindepth 1 -name dir_info.txt -o -type l -prune \ -o -print 2>/dev/null) case "$1" in "probe"|"get"|"refresh") [ "$(echo "$existing" | wc -w)" -gt 0 ] && return mkdir -p "$2" 2>/dev/null if [ $# -gt 2 ]; then do_announce "about to $1 target files..." $CLR_INF $3 ret=$? if [ $ret -ne 0 ]; then # recursion (should be safe) init_target purge "$2" return $ret fi fi ;; "clean"|"purge") [ "$(echo "$existing" | wc -w)" -eq 0 ] || do_del $existing ;; *) do_announce "Unexpected action $1" $CLR_BAD exit 1 ;; esac } init_git_submodule () { # usage: $1=action ($1 passed from main), $2..$N=submodule(s) # + optionally, if last arg is a function, it is used # for hooking-in get_refresh_hook () { pushd "$(git rev-parse --show-toplevel)" >/dev/null # TODO: --recursive seems to be buggy? do_announce "about to update git submodule ${1}..." $CLR_INF init_verbosely git submodule update --init "${GIT_PREFIX}${1}" 2>&1 ret=$? popd >/dev/null if [ $ret -eq 0 ]; then pushd "$1" >/dev/null git submodule update --init 2>&1 popd >/dev/null fi return $ret } arg_len=$# arg_last=${@:$arg_len} if type -t function_name $arg_last | grep -q "^function$"; then let arg_len-=2 else let arg_len-=1 fi ret=0 for submodule in "${@:2:$arg_len}"; do do_announce "$1 $submodule (git submodule)" $CLR_SCR delimit do_action $1 "$submodule" get_refresh_hook $arg_last ret=$? [ $ret -ne 0 ] && break done check_ret $ret } # TODO: no support for custom function for hooking-in as with git_submodule init_url_wget () { # usage: $1=action ($1 passed from main), $2=DIR, $3..$N=arguments (to wget) check_nargs $# 3 || return $? toplevel="$2" if [ "$(dirname $2)" != "." ]; then toplevel="$(dirname $2)" fi other_params="${@:3}" get_refresh_hook () { wget --no-verbose --no-clobber --execute robots=off \ --directory-prefix "$toplevel" "$other_params" 2>&1 } do_announce "$1 $toplevel (wget)" $CLR_SCR delimit do_action $1 "$toplevel" get_refresh_hook check_ret $? } # TODO: no support for custom function for hooking-in as with git_submodule init_svn () { # usage: $1=action ($1 passed from main), $2=DIR, $3=SVN [, $4=REV] check_nargs $# 3 || return $? toplevel="$2" if [ "$(dirname $2)" != "." ]; then toplevel="$(dirname $2)" fi svn="$3" rev="" if [ $# -ge 4 ]; then rev="--revision $4" fi get_refresh_hook () { svn checkout --force $rev "$svn" "$toplevel" | grep "revision" } do_announce "$1 $toplevel (svn)" $CLR_SCR delimit do_action $1 "$toplevel" get_refresh_hook check_ret $? } # # hook-enablers # usage: $1=action ($1 passed from main), $2..$N command + args # init_post_get () { [ "$1" != "get" ] || "${@:2}"; check_ret $?; } init_post_refresh () { [ "$1" != "refresh" ] || "${@:2}"; check_ret $?; } init_post_clean () { [ "$1" != "clean" ] || "${@:2}"; check_ret $?; } init_post_purge () { [ "$1" != "purge" ] || "${@:2}"; check_ret $?; } # # source the choices (./init-* files) and go # CHOICES="" for choice in $(ls init-*); do [ -f $choice ] && source ./$choice; done CHOICES=$(echo $CHOICES | sed 's/^|//') ACTIONS="get|refresh|clean|purge" help_actions () { cat <<"EOF" quick explanation of actions: - basically, get-refresh and clean-purge are very similar (or even aliasing in some cases) depending on whether the way to get the target files is single-phase (what you fetch is what you use) or two-phased (what you fetch is what you use to generate target files you then use) * get/refresh: get the target files - single-phased (get=refresh): refetch sources (optionally: if needed) - two-phased: - get: if not present, generate the target files from sources, but if sources are missing as well, fetch them first (=refresh) - refresh: remove existing target files, refetch sources (optionally: if needed) and generate fresh target files out of them * clean/purge: remove the files - single-phased: - clean: NOOP (unless something performed via hook) - purge: remove the source/target files directory - two-phased: - clean: remove the target files directory - purge: remove both the target files and source files directories NOTE: you can create composite actions on the commandline, e.g.: ./init.sh purge all && ./init.sh all # completely from scratch EOF } main () { action="get" # mostly, existing target files will be just reused eval "case \"$1\" in $ACTIONS) action=\"$1\" [ $# -gt 1 ] && shift # || fall-through via unrecognized choice ;; esac" while [ -n "$1" ]; do eval "case \"$1\" in $CHOICES) init_announce "--- $1 ---" delimit $1 $action ret=\$? [ \$ret -ne 0 ] && return \$ret shift continue;; all) main $action $(echo $CHOICES | sed 's/|/ /g') break;; help|*) echo \"usage: ./init.sh [$ACTIONS] ($CHOICES|all)+\" shift if [ \"\$1\" != \"actions\" ]; then echo \"see also 'help actions' for details about actions\" else echo help_actions fi break;; esac" done } [ $# -eq 0 ] && main help || main "$@"