confz

git mirror of https://ccx.te2000.cz/bzr/confz
git clone https://ccx.te2000.cz/git/confz
Log | Files | Refs

confz (7567B)


      1 #!/bin/zsh
      2 # vim: ft=zsh noet ts=4 sts=4 sw=4
      3 
      4 autoload -Uz colors; colors
      5 
      6 setopt extended_glob warn_create_global no_unset
      7 zmodload zsh/zutil || exit $?
      8 zmodload zsh/parameter || exit $?
      9 
     10 typeset -gA vars vars_prev
     11 typeset -ga do_command actions_performed
     12 typeset -g confz_indent check_only verbose fail_reason quiet
     13 
     14 : ${check_only:=0}
     15 : ${verbose:=0}
     16 : ${quiet:=0}
     17 
     18 # helper that prints out error message and exits
     19 die() {
     20 	print -r - "$fg_bold[red]Fatal$reset_color error occurend in:" >&2
     21 	local n
     22 	for n in {${#funcfiletrace}..1}; do
     23 		printf '%d> %s (%s)\n' $n "$funcfiletrace[$n]" "$functrace[$n]"
     24 	done
     25 	print -r - "$fg_bold[red]*$reset_color $@" >&2
     26 	exit 1
     27 }
     28 
     29 confz_print_usage() {
     30 	echo >&2 "usage: $0:h [-f <fpath>] <requirement> [<args>] [; <requirement> ...]"
     31 	exit 2
     32 }
     33 
     34 # option parsing
     35 typeset -ga confz_opt_f
     36 zparseopts -D - f+:=confz_opt_f || die "Unable to parse options"
     37 () {
     38 	local opt val
     39 	for opt val in "$confz_opt_f[@]"; do
     40 		[[ $opt == "-"* ]] || die "Something went wrong"
     41 		fpath=( $val "$fpath[@]" )
     42 	done
     43 }
     44 
     45 # trace-printing helpers
     46 confz_check_start() {
     47 	(($quiet)) || print -r - "${confz_indent}checking $fg_bold[default]$1$reset_color ${(@q)argv[2,-1]} ${reset_color}[" >&2
     48 	confz_indent+=" "
     49 }
     50 
     51 confz_check_ok() {
     52 	confz_indent=${confz_indent% }
     53 	(($quiet)) || print -r - "$confz_indent] $fg[green]$1$reset_color OK" >&2
     54 }
     55 
     56 confz_check_fail() {
     57 	confz_indent=${confz_indent% }
     58 	if (($check_only)); then
     59 		print -P "$confz_indent] $fg[red]$1$reset_color FAIL" >&2
     60 	else
     61 		(($quiet)) || print -P "$confz_indent] $fg[yellow]$1$reset_color NEED" >&2
     62 	fi
     63 }
     64 
     65 confz_do() {
     66 	(($quiet)) || print -r - "$confz_indent$fg[yellow]*$fg[default] ${(q)@}" >&2
     67 	actions_performed+=( "$*" )
     68 	"$@" || die "command failed with error $?: ${(q)@}"
     69 }
     70 
     71 # trace-printing helper for setting $vars values
     72 setvar() {
     73 	if (($verbose)); then
     74 		print -r - "$confz_indent$fg[cyan]$1$fg[default]=${(qqq)2}" >&2
     75 	fi
     76 	vars[$1]=$2
     77 }
     78 
     79 # set $vars parameter if it's empty (ie. set default value)
     80 defvar() {
     81 	if ! (($+vars[$1])); then
     82 		setvar "$1" "$2"
     83 	fi
     84 }
     85 
     86 # check if variables are nonempty
     87 checkvars() {
     88 	local var
     89 	local -a empty
     90 	for var in "$@"; do
     91 		if ! (($+vars[$var])); then
     92 			empty+=( $var )
     93 		fi
     94 	done
     95 	(( $#empty )) && die "required parameters are empty: ${(@q)empty}"
     96 }
     97 
     98 # check if given arguments (as name value pairs) match current $vars
     99 matchvars() {
    100 	local name value
    101 	for name value in "$@"; do
    102 		if (($+vars[$name])); then
    103 			[[ $value == $vars[$name] ]] || return 1
    104 		fi
    105 	done
    106 	return 0
    107 }
    108 
    109 # unify given arguments (as name value pairs) with current $vars
    110 unify() {
    111 	local name value
    112 	for name value in "$@"; do
    113 		if (($+vars[$name])); then
    114 			[[ $value == $vars[$name] ]] \
    115 				|| die "Unified variable $name has value ${(qqq)vars[$name]} not matching ${(qqq)value}"
    116 		else
    117 			vars[$name]=$value
    118 		fi
    119 	done
    120 }
    121 
    122 # autoload all relevant functions and run confz_*_init
    123 confz_load() {
    124 	local func
    125 	local -a match mbegin mend confz_functions func_files
    126 
    127 	func_files=( $^fpath/confz_*(N) )
    128 
    129 	for func in $func_files ; do
    130 		if ! [[ -r "$func" ]]; then
    131 			print -r >&2 - "$func is not readable"
    132 			continue
    133 		fi
    134 		autoload -Uz $func:t
    135 		confz_functions+=$func:t
    136 	done
    137 
    138 	for func in ${(o)confz_functions}; do
    139 		if [[ $func == *_init ]]; then
    140 			$func || die "Init function failed: $func"
    141 		fi
    142 	done
    143 }
    144 
    145 # check & run & check a dependency
    146 require() {
    147 	# usage: require <name> [<variables>] [-- <arguments>]
    148 	# where:
    149 	#   <name> is a dependency name as defined by function confz_<name>_check
    150 	#   <variables> are variable assignments in the form of:
    151 	#       foo=bar  -- sets ${vars[foo]} in calee to value "bar"
    152 	#       :foo  -- passes ${vars[foo]} from caller to calee
    153 	#       ?foo=bar -- sets ${vars[foo]} in calee to value of ${vars[bar]}
    154 	#                   iff it is defined in the caller
    155 	#       ?foo -- passes ${vars[foo]} from caller to calee iff it is defined
    156 	#               in the caller
    157 	#       %foo  -- passes ${vars[foo]} from callee to caller
    158 	#       %foo=bar  -- passes ${vars[foo]} from callee
    159 	#                    to variable ${vars[bar]} of caller
    160 	#   <arguments> are arguments passed to the dependency function
    161 
    162 	local name outer inner indent_prev check_only_prev check_ret
    163 	local prev_name new_name
    164 	local -a do_command_prev
    165 	local -A vars_local lift
    166 
    167 	name=$1
    168 	shift
    169 
    170 	# store and clear $vars
    171 	vars_prev=( "${(@kv)vars}" )
    172 	vars_local=( "${(@kv)vars}" )
    173 	vars=( )
    174 
    175 	# print check start
    176 	confz_check_start $name "$@"
    177 
    178 	# parse variable assignments
    179 	while (( $# )); do
    180 		case $1 in
    181 			(:*)   ((${+vars_prev[${1#:}]})) || \
    182 				die "variable ${(qqq)1#:} not set, passed as :argument"
    183 				setvar ${1#:} "${vars_prev[${1#:}]}";;
    184 			(\?*=*)
    185 				prev_name=${${1#\?}#*=}
    186 				new_name=${${1#\?}%%=*}
    187 				(($+vars_prev[$prev_name])) && \
    188 					setvar $new_name "$vars_prev[$prev_name]";;
    189 			(\?*)  ((${+vars_prev[${1#\?}]})) && \
    190 				setvar ${1#\?} "${vars_prev[${1#\?}]}";;
    191 			(%*=*) lift[${${1#%}%%=*}]=${${1#%}#*=};;
    192 			(%*)   lift[${1#%}]=${1#%};;
    193 			(*=*)  setvar "${1%%=*}" "${1#*=}";;
    194 			(--)   shift; break;;
    195 			(*)    die "$name: unrecognised argument: ${(qqq)1}";;
    196 		esac
    197 		shift
    198 	done
    199 
    200 	# store old $do_command
    201 	do_command_prev=( "${do_command[@]}" )
    202 	do_command=( confz_${name}_do )
    203 
    204 	# clear fail_reason
    205 	fail_reason=''
    206 
    207 	if ! (($+functions[confz_${name}_check])); then
    208 		die "$name: Function confz_${name}_check not defined."
    209 	fi
    210 	# perform check - run - check
    211 	if confz_${name}_check "$@"; then
    212 		confz_check_ok $name
    213 	else
    214 		check_ret=$?
    215 		confz_check_fail $name
    216 
    217 		if (($check_only)); then
    218 			# if (($verbose)); then
    219 			# 	print "re-running check with xtrace"
    220 			# 	set +x
    221 			# 	confz_${name}_check
    222 			# 	set -x
    223 			# fi
    224 			die "$name: check failed: ${fail_reason:-error $check_ret}"
    225 		else
    226 			if (($verbose)); then
    227 				print -r - "$confz_indent$fg[cyan]*$fg[default] reason: ${fail_reason:-error $check_ret}"
    228 				(($+functions[confz_${name}_do])) && \
    229 					typeset -f -t confz_${name}_do
    230 			fi
    231 			confz_do "${do_command[@]}" "$@"
    232 
    233 			# print check start
    234 			confz_check_start $name "$@" again...
    235 
    236 			# clear fail_reason
    237 			fail_reason=''
    238 
    239 			# perform check once again
    240 			check_only_prev=$check_only
    241 			check_only=1
    242 			confz_${name}_check "$@" || \
    243 				die "$name: check failed: ${fail_reason:-error $?}"
    244 			confz_check_ok $name
    245 			check_only=$check_only_prev
    246 		fi
    247 	fi
    248 
    249 	# clear fail_reason
    250 	fail_reason=''
    251 
    252 	# restore $do_command
    253 	do_command=( "${do_command_prev[@]}" )
    254 
    255 	# restore old $vars and put calee's $vars into $vars_prev
    256 	vars_prev=( "${(@kv)vars}" )
    257 	vars=( "${(@kv)vars_local}" )
    258 
    259 	# lift %variables from calee to caller
    260 	for inner outer in ${(kv)lift}; do
    261 		((${+vars_prev[$inner]})) || \
    262 			die "variable ${(qqq)inner} not set, was requested by caller as ${(qqq)outer}"
    263 		setvar $outer ${vars_prev[$inner]}
    264 	done
    265 }
    266 
    267 # "dependency" that represents toplevel and gets it's deps out of argv
    268 confz_main_check() {
    269 	local arg
    270 	local -a args
    271 	local -a printout
    272 	for arg in "$@"; do
    273 		if [[ $arg == ';' ]]; then
    274 			require $args
    275 			for arg in $printout; do
    276 				printf "%s=%s\n" $arg ${(qqq)vars[$arg]}
    277 			done
    278 			args=()
    279 			printout=()
    280 		else
    281 			if [[ $arg == %* ]]; then
    282 				printout+=( ${${arg#%}##*=} )
    283 			fi
    284 			args+=( $arg )
    285 		fi
    286 	done
    287 	(( $#args )) && require $args
    288 			for arg in $printout; do
    289 				printf "%s=%s\n" $arg ${(qqq)vars[$arg]}
    290 			done
    291 
    292 	(($check_only)) && return 0
    293 	fail_reason="performed $#actions_performed actions, recheck"
    294 	return $#actions_performed
    295 }
    296 
    297 # noop for the toplevel, functionality is in the dependencies
    298 confz_main_do() {
    299 	true
    300 }
    301 
    302 # run the toplevel
    303 confz_load
    304 require main -- "$@"