#!/bin/bash APP_SVNID='$HeadURL$ $LastChangedRevision$' . $(ade-config ade_share_prefix)/include/ade.sh || { echo "${0##*/}: INTERNAL ERROR: failed to load ade.sh" >&2; exit 3; } WRA_DEFINED_ERRORS=( "KEY=WRA_ERR_MISC; FMT=\"%s\"" ) # Other globals MAX_VOLUME=100 WRA_SHARE_PREFIX=$(wra-config wra_share_prefix) INIT_WRARC=$WRA_SHARE_PREFIX/wrarc FADEIN_STEP_INTERVAL=5 HELPER_DIRS=( $HOME/.config/${ADE_APP_PROGNAME}/helpers $WRA_SHARE_PREFIX/helpers ) declare -A STATION_HASH wra() { local ERRSTACK_REF="$1"; shift local OPTVAL RC USERSEL_STATION local -a DOLLARAT ########################################################################### # # SET ADE OPTIONS # ########################################################################### # Register application-specific errors ade_err_registerdefderrs WRA_DEFINED_ERRORS ########################################################################### # # PROCESS OPTIONS # ########################################################################### # Defaults for options OPT_MODE=normal # There's no nice way to determine the .config dir. Stephen Kitt on # https://unix.stackexchange.com/questions/726717 suggests to use # ${XDG_CONFIG_HOME:-$HOME/.config}. OPT_RC_FILE=${XDG_CONFIG_HOME:-$HOME/.config}/$ADE_APP_PROGNAME/${ADE_APP_PROGNAME}rc # Register wra's options ade_opt_register "$ERRSTACK_REF" -o fl --longoptions=config-file:,list-stations --callback-template="wra_opt_handler_%s" || return $? ade_msg_register "$ERRSTACK_REF" wra_usage wra_version wra_listpaths || return $? # Process options ade_opt_process "$ERRSTACK_REF" NEW_DOLLAR_AT "$@" || return $? set -- "${NEW_DOLLAR_AT[@]}" # Process arguments # (later) # Sanity checks and derivations ade_err_resetstack "$ERRSTACK_REF" writers="_ade_msg_writer_stderr _ade_msg_writer_logfile" logfile=/tmp/wra.$$.log # Create rc file if none exists. # The rc file used to be a file, then it was a symlink to a package-provided # rc file, but now, in order to support personal helper scripts, we need it # to be a directory. All these possibilties require some careful handling. if [ -h ~/.wrarc ] && [[ $(readlink ~/.wrarc) =~ .*/usr/share/wra/wrarc$ ]] && [ ! -h $OPT_RC_FILE -a ! -e $OPT_RC_FILE ]; then # ~/.wrarc is a symlink, points to the package provided-file, so it can be recreated in ~/.config/wra/ rm ~/.wrarc mkdir -p ${OPT_RC_FILE%/*} ln -sr $INIT_WRARC $OPT_RC_FILE elif [ -h ~/.wrarc ]; then ade_err_error "$ERRSTACK_REF" WRA_ERR_MISC "~/.wrarc is a non-standard symlink (hint: resolve this yourself)" return $ADE_ERR_FAIL elif [ -f ~/.wrarc ] && [ ! -h $OPT_RC_FILE -a ! -e $OPT_RC_FILE ]; then mkdir -p ${OPT_RC_FILE%/*} mv ~/.wrarc $OPT_RC_FILE elif [ -f ~/.wrarc ]; then ade_err_error "$ERRSTACK_REF" WRA_ERR_MISC "~/.wrarc is a file but new location already exists (hint: resolve this yourself)" return $ADE_ERR_FAIL elif [ -e ~/.wrarc ]; then ade_err_error "$ERRSTACK_REF" WRA_ERR_MISC "~/.wrarc exists but is not a file or a symlink (hint: resolve this yourself)" return $ADE_ERR_FAIL elif [ ! -h $OPT_RC_FILE -a ! -e $OPT_RC_FILE ]; then mkdir -p ${OPT_RC_FILE%/*} ln -sr $INIT_WRARC $OPT_RC_FILE elif [ -h $OPT_RC_FILE ] && [[ $(readlink $OPT_RC_FILE) =~ .*/usr/share/wra/wrarc$ ]]; then : else ade_err_warning "$ERRSTACK_REF" WRA_ERR_MISC "custom wrarc" fi # Load configuration. ade_err_debug "$ERRSTACK_REF" 10 "wra: checking $OPT_RC_FILE is loadable ..." sh -c "station() { :; }; helper() { :; }; . $OPT_RC_FILE > /dev/null 2>&1" || { ade_err_error "$ERRSTACK_REF" WRA_ERR_MISC "$OPT_RC_FILE: not loadable (hint: check quoting, ...)" return $ADE_ERR_FAIL } ade_err_debug "$ERRSTACK_REF" 10 "wra: loading $OPT_RC_FILE ..." # See comments below in station() function regarding STATION_AGGREGATED_RC. STATION_AGGREGATED_RC=$ADE_ERR_OK . $OPT_RC_FILE if [ $STATION_AGGREGATED_RC != $ADE_ERR_OK ]; then return $STATION_AGGREGATED_RC fi # Guts mode_$OPT_MODE "$@" || return $? return $ADE_ERR_OK } mode_list() { local ERRSTACK_REF="$1"; shift local STATION DEFAULT DESCRIPTION IS_DEFAULT_STATION_FLAG STATIONS # 'sort -u' is because there are multiple entries in the hash for each station, # namely each of the second keys ('description', 'is_default', etc). STATIONS=( $(echo ${!STATION_HASH[*]} | xargs -n 1 echo | sed 's/,[^ ]*//' | sort -u) ) # Get longest station name LONGEST_STATION_LENGTH=0 for STATION in ${STATIONS[*]}; do if [ ${#STATION} -gt $LONGEST_STATION_LENGTH ]; then LONGEST_STATION_LENGTH=${#STATION} fi done # Get longest station description LONGEST_DESCRIPTION_LENGTH=0 for STATION in ${STATIONS[*]}; do read_multi_key_hash_entry "$ERRSTACK_REF" $STATION description DESCRIPTION if [ ${#DESCRIPTION} -gt $LONGEST_DESCRIPTION_LENGTH ]; then LONGEST_DESCRIPTION_LENGTH=${#DESCRIPTION} fi done if [ -t 1 ]; then # Get screen width eval `resize -u` # How many chars to display each station? Add '2' for ': ' and '4' is inter-column spacing RECORD_LENGTH=$(( LONGEST_STATION_LENGTH + LONGEST_DESCRIPTION_LENGTH + 2 + 4)) # To calculate the number of columns we can display *and the number of inter-column # 4-spaces is much eased if we add 4 to the width of the screen and then divide # by the record length. RECORDS_PER_LINE_COUNT=$(( (COLUMNS+4) / RECORD_LENGTH )) else RECORDS_PER_LINE_COUNT=1 fi I=0 for STATION in ${STATIONS[*]}; do read_multi_key_hash_entry "$ERRSTACK_REF" $STATION is_default IS_DEFAULT_STATION_FLAG read_multi_key_hash_entry "$ERRSTACK_REF" $STATION description DESCRIPTION # The printfs below work fine except for diacriticals, which are get wrongly # padded (bash's print things that 'รก' is 2 chars long, so adds extra spaces # than after 'a' for example). Accordingly, we need to add an adjustment to # the padding. Most of time time this adjustment is zero, because most of # the time there are no diacriticals. The adjustment is the difference between # 'wc -c' and 'wc -m'. See https://unix.stackexchange.com/questions/609125/padding-unicode-strings-with-bashs-printf. ADJUSTMENT=$(( $(wc -m <<<"$DESCRIPTION") - $(wc -c <<<"$DESCRIPTION") )) if $IS_DEFAULT_STATION_FLAG; then BOLD_ON_OR_EMPTY=$(tput smso) BOLD_OFF_OR_EMPTY=$(tput rmso) else BOLD_ON_OR_EMPTY= BOLD_OFF_OR_EMPTY= fi # +1 is because the ':' is displayed as part of station name to avoid mis-padding. printf -v RECORD "%-$((LONGEST_STATION_LENGTH+1))s %-$((LONGEST_DESCRIPTION_LENGTH-ADJUSTMENT))s" "$STATION$ASTERISK_OR_NOTHING:" "$DESCRIPTION" # If we're displaying the last record on the line then don't add inter-column spacing but do add newline. if ((I % RECORDS_PER_LINE_COUNT == RECORDS_PER_LINE_COUNT - 1)); then echo "$BOLD_ON_OR_EMPTY$RECORD$BOLD_OFF_OR_EMPTY" # If we're (not displaying the last record on the line and) we're displaying the final record add newline. elif ((I == ${#STATIONS[*]} - 1)); then echo "$BOLD_ON_OR_EMPTY$RECORD$BOLD_OFF_OR_EMPTY" # If we're (not displaying the last record on the line and it's not the final record and) we're displaying # non-last-record-on-line then do add inter-column padding but don't add newline. else echo -n "$BOLD_ON_OR_EMPTY$RECORD$BOLD_OFF_OR_EMPTY " fi ((I++)) done return $ADE_ERR_OK } mode_normal() { local CHECK EXPECT # Process arguments twice. Once as a check and once for real. for CHECK in true false; do # STOP_ALL_HELPERS_FLAG is only used if CHECK is false. It is used # to tell start_station() or stop_station() that they don't know # which helpers (if any) are active, so they should stop all helpers # before starting a station of stopping a helper. STOP_ALL_HELPERS_FLAG=true EXPECT=onorofforstation for ARG in "$@"; do ade_err_debug "$ERRSTACK_REF" 10 "wra: CHECK=$CHECK, EXPECT=$EXPECT, ARG=$ARG" if [ $EXPECT = on -a $ARG = on ]; then EXPECT=ontime elif [ $EXPECT = on ]; then ade_msg_usage "$ERRSTACK_REF" elif [ $EXPECT = off -a $ARG = off ]; then EXPECT=offtime elif [ $EXPECT = off ]; then ade_msg_usage "$ERRSTACK_REF" elif [ $EXPECT = onoroff -a $ARG = on ]; then EXPECT=ontime elif [ $EXPECT = onoroff -a $ARG = off ]; then EXPECT=offtime elif [ $EXPECT = onoroff ]; then ade_msg_usage "$ERRSTACK_REF" elif [ $EXPECT = onorofforstation -a $ARG = on ]; then EXPECT=ontime elif [ $EXPECT = onorofforstation -a $ARG = off ]; then EXPECT=offtime elif [ $EXPECT = onorofforstation ]; then validate_station "$ERRSTACK_REF" "$ARG" || return $? $CHECK || process_station "$ERRSTACK_REF" $ARG || return $? EXPECT=onoroff elif [ $EXPECT = onorstation -a $ARG = on ]; then EXPECT=ontime elif [ $EXPECT = onorstation ]; then validate_station "$ERRSTACK_REF" "$ARG" || return $? $CHECK || process_station "$ERRSTACK_REF" $ARG || return $? EXPECT=on elif [ $EXPECT = offorstation -a $ARG = off ]; then EXPECT=offtime elif [ $EXPECT = offorstation ]; then validate_station "$ERRSTACK_REF" "$ARG" || return $? $CHECK || process_station "$ERRSTACK_REF" $ARG || return $? EXPECT=off elif [ $EXPECT = ontime ]; then validate_time "$ERRSTACK_REF" "$ARG" || return $? $CHECK || process_ontime "$ERRSTACK_REF" $ARG || return $? EXPECT=offorstation elif [ $EXPECT = offtime ]; then validate_time "$ERRSTACK_REF" "$ARG" || return $? $CHECK || process_offtime "$ERRSTACK_REF" $ARG || return $? EXPECT=onorstation else ade_msg_usage "$ERRSTACK_REF" fi done # Check last argument left state engine in an acceptable state [ $EXPECT = onorstation -o $EXPECT = offorstation ] || ade_msg_usage "$ERRSTACK_REF" done return $ADE_ERR_OK } wra_opt_handler_l() { wra_opt_handler_list_stations "$@" } wra_opt_handler_list_stations() { local ERRSTACK_REF="$1"; shift OPT_MODE=list return $ADE_ERR_OK } wra_opt_handler_f() { wra_opt_handler_config_file "$@" } wra_opt_handler_config_file() { local ERRSTACK_REF="$1"; shift OPT_RC_FILE="$1" return $ADE_ERR_OK } wra_usage() { local ERRSTACK_REF="$1"; shift local USAGETEXT_REF="$1"; shift local PASSNO=$1; shift if [ $PASSNO = 1 ]; then eval "$USAGETEXT_REF=\"{ on