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 -- "$@"