ArgsHelper.sh

#!/bin/bash
#
# source BaseEnvHelper.sh or source ArgsHelper.sh to use
#   used to help other scripts to make their args organized
#   invoke 'SetRequestedArg' / 'SetDescription' / 'SetExample' first
#   then invoke 'VerifyAllArgs "$@"'

set -e
source ${HOME}/env-setup/env-helper/ColorStyleHelper.sh

# create empty arrays to store arguments with corresponding names
# shortNameArray to store shortName and so on
shortNameArray=()
fullNameArray=()
exampleNameArray=() # used to store [$shortName $fullName] together to output when argUsage() called
ifOptionalArray=()
argDescriptionArray=()
ifAcceptValueArray=()
validValueArray=()
ifMultipleArray=()

# it's VERY likely to create an array with non-continuous indices when VerfiyAllArgs() is called
# one or more indices may be 'jumped' over when one or more optional args are NOT passed-in
# so ONLY passed-in args and values are verified and then put into validPassedInValueArray
validPassedInArgArray=()
validPassedInValueArray=()

# call anywhere inside worker script after VerifyAllArgs() was called
# example and description are skipped
PukeMeOut() {
  local size=${#shortNameArray[*]}
  declare -i i=1 # skip [--help HELP_ARG]
  ifAll=$1
  while [ "$size" -gt "$i" ]; do
    if [ "$ifAll" == true ]; then # puke all build-in arrays and items
      if [ -z "${validPassedInValueArray[$i]}" ]; then
        validPassedInArgArray[$i]='None'
        validPassedInValueArray[$i]='None'
      fi
      # exampleName and exampleNameArray are skipped cause they already have styles
      echo -e "${_COLOR_D}\
shortName: [${shortNameArray[$i]}] \
| fullName: [${fullNameArray[$i]}] \
| ifOptional: [${ifOptionalArray[$i]}] \
| argDescription: [${argDescriptionArray[$i]}] \
| ifAcceptValue: [${ifAcceptValueArray[$i]}] \
| validValue: [${validValueArray[$i]}] \
| ifMultiple: [${ifMultipleArray[$i]}] \
| validPassedInArg: [${validPassedInArgArray[$i]}] \
| validPassedInValue: [${validPassedInValueArray[$i]}]\
${_END}"
    else # puke only arrays and itmes related to passed-in args
      if [ -z "${validPassedInValueArray[$i]}" ]; then
        i=$i+1
        continue
      fi
      echo -e "${_COLOR_D}\
validPassedInArg: [${validPassedInArgArray[$i]}] \
| fullName: [${fullNameArray[$i]}] \
| ifOptional: [${ifOptionalArray[$i]}] \
| ifAcceptValue: [${ifAcceptValueArray[$i]}] \
| validValue: [${validValueArray[$i]}] \
| ifMultiple: [${ifMultipleArray[$i]}] \
| validPassedInValue: [${validPassedInValueArray[$i]}]\
${_END}"
    fi
    i=$i+1
  done
}

description= # not an array
exampleArray=()
exampleDescArray=()

# invoke SetRequestedArg() multiple times to setup a list of required args
SetRequestedArg() {
  # 0 at the 1st time, increases by 1 on each invocation
  # so value can be set at correct index of target array
  argNum=${#shortNameArray[*]}

  # two examples:
  # 1. SetRequestedArg "-c" "true" "COMPRESS_FILE" "if compress the output file" "true" "zip tar.gz"
  # valid: '-c zip' or not passed-in, invalid: '-c bzip2'
  #
  # 2. SetRequestedArg "-os" "false" "OS_NAME" "name of operating system" "true" "win7 win10 win2008r2 win2012r2" "true"
  # valid: '-os "win7 win2k8r2"', invalid: '-os "win8 win10"' or not passed-in

  shortName=$1 # arg short name, used when call from command line, like '-c' or '-os'
  ifOptional=$2 # if optional or mendatory, 'true' or 'false'
  fullName=$3 # arg full name, used in script which sources ArgsHelper, like 'COMPRESS_FILE' or 'OS_NAME'
  argDescription=$4 # arg description, describe what is the arg used for
  ifAcceptValue=$5 # if accepta passing value from command line, 'true' or 'false' (false means the arg is like a switch, can be turned on or off)
  validValue=$6 # a list of valid values, used to validate passed-in values, like 'zip tar.gz' or 'win7 win10 win2008r2 win2012r2'
  ifMultiple=$7 # if arg can be passed-in multi-values, multi-values needs double quotes, see examples above

  # 'accepts value' means 'mendatory', so only 'optoinal' arg can accept no value
  if [ "$ifOptional" == "false" -a "$ifAcceptValue" == "false" ]; then
    echo -e "\n${_ERROR}ERROR: only optional arg can be set not to accept value${_END}"
    echo -e "${_ERROR}please re-define arg [$shortName $fullName] and try again${_END}"
    exit 100
  fi

  if [ "$shortName" == "--help" ]; then
    exampleName="${_B}${_COLOR_A}[$shortName]${_END}"
  else
    exampleName="${_B}${_COLOR_A}$shortName${_END} ${_UL}${_COLOR_A}$fullName${_END}"
    [ "$ifOptional" == "true" ] && exampleName="${_COLOR_A}[${_END}${exampleName} ${_COLOR_A}(optional)]${_END}"
    [ "$ifAcceptValue" == "false" ] && exampleName="${exampleName} ${_COLOR_A}(not accepts value)${_END}"
  fi

  shortNameArray[$argNum]=$shortName
  fullNameArray[$argNum]=$fullName
  ifOptionalArray[$argNum]=$ifOptional
  argDescriptionArray[$argNum]=$argDescription
  ifAcceptValueArray[$argNum]=$ifAcceptValue
  exampleNameArray[$argNum]=$exampleName
  validValueArray[$argNum]=$validValue
  ifMultipleArray[$argNum]=$ifMultiple
}

SetDescription() {
  description=$1
}

# example and its description
# if need to use customized format, like
# example:
#   info1
#     info2
#       info3
# just set 'exampleArray' but leave 'exampleDescArray' empty, like
# SetExample "example:\n  info1\n    info2\n      info3" ""
SetExample() {
  argNum=${#exampleArray[*]}
  exampleArray[$argNum]="$1"
  exampleDescArray[$argNum]="$2"
}

# will be invoked on each ERROR in 'shortMode', and '--help' with every details
argUsage() {
  argNum=${#shortNameArray[*]}
  exampleNum=${#exampleArray[*]}

  # create a brief argUsage with script name and arg list
  declare -i i=0
  while [ "$argNum" -gt "$i" ]; do
    argList="$argList ${exampleNameArray[$i]}"
    i=$i+1
  done

  if [ "$1" == "short" ]; then
    echo -e "\n${_COLOR_A}argUsage${_END}:$argList"
    echo -e "${_COLOR_A}run '$villain --help' for more details${_END}"
  else # display every details if not 'shortMode'
    if [ -n "$preLogs" ]; then
      echo -e "${_COLOR_D}----------------------------------------------------------------------------${_END}"
      echo -e "${_COLOR_D}  Functions called before ${FUNCNAME}:${_END}"
      for preLog in $preLogs; do
        echo -e "${_COLOR_D}    ${preLog}${_END}"
      done
      echo -e "${_COLOR_D}----------------------------------------------------------------------------${_END}"
    fi
    echo -e "\n${_4S}${_COLOR_A}argUsage:${_END}$argList"
    echo -e "${_4S}${_COLOR_D}$description${_END}\n"
    declare -i i=0
    while [ "$argNum" -gt "$i" ]; do
      echo -e "${_4S}${exampleNameArray[$i]}" # '[-c COMPRESS_FILE]'
      echo -e "${_4S}${_4S}${_COLOR_B}${argDescriptionArray[$i]}${_END}" # '(optional) if compress the output file'

      if [ "${ifAcceptValueArray[$i]}" == "true" ]; then
        if [ ! -z "${validValueArray[$i]}" ]; then
          if [ "${ifMultipleArray[$i]}" == "true" ]; then # 'Valid values: [win7 win10 win2008r2 win2012r2]'
            echo -e "${_4S}${_4S}${_COLOR_B}valid values: [${validValueArray[$i]}]\
            \n${_4S}${_4S}single('') or double(\"\") quotes required if multi-values passed-in${_END}"
          else
            echo -e "${_4S}${_4S}${_COLOR_B}valid value: [${validValueArray[$i]}]${_END}" # 'Valid value: [zip tar.gz]'
          fi
        else
          echo -e "${_4S}${_4S}${_COLOR_B}accepts any value (validation needs be done in worker script)${_END}" # 'Valid value: [zip tar.gz]'
        fi
      fi
      i=$i+1
    done

    if [ "$exampleNum" -gt 0 ]; then
      echo -e "\n${_4S}${_COLOR_C}Examples:${_END}"
      declare -i i=0
      while [ "$exampleNum" -gt "$i" ]; do
        echo -e "${_4S}${_4S}${_COLOR_D}${exampleArray[$i]}${_END}"
        echo -e "${_2S}${_4S}${_4S}${_COLOR_D}${exampleDescArray[$i]}${_END}\n"
        i=$i+1
      done
    fi
  fi
}

# exceptions=(noArgs), for now only one type of exception
# make an exception when some scripts need NO args to run
MakeException() {
  exception=$1
}

# used to check if required arg(s) are passed-in
# call "CheckArgBinds '-c XYZ' '-a -b'" inside the beginning of a function
# or code block before doing real work
CheckArgBinds() {
  local action=$1
  local binds=$2

  for bind in $binds; do # check each required arg is passed-in
    local shortName=${bind%:*}
    if [[ ! "${validPassedInArgArray[@]}" =~ "$shortName" ]]; then
      echo -e "${_ERROR}ERROR: Arguments Binds validation falied. [$action] requires [${bind/:/ }] to pass-in${_END}"
      exit 11
    fi
  done
}

# useful if want to output log for functions called before argUsage() or VerifyAllArgs()
# cause some values of args need to be generated before worker scripts actually run
preRunningLog=''
SetPreRunningLogs() {
  preRunningLog="${preRunningLog} ${1}"
}

# verify all args passed in, invoked by: 'VerifyAllArgs "$@"'
VerifyAllArgs() {
  if [ "$#" -eq 0 ] && [ "$exception" != 'noArgs' ]; then
    echo -e "\n${_ERROR}ERROR: no arguments passed-in${_END}"
    argUsage short
    exit 11
  fi

  if [ "$1" == "--help" ]; then
    argUsage # display every details if the 1st arg is '--help'
    exit 0
  fi

  echo -e "\n${_COLOR_D}----------------------------------------------------------------------------${_END}"
  if [ -n "$preRunningLog" ]; then
    echo -e "${_COLOR_D}  Functions called before ${FUNCNAME}:${_END}"
    for preLog in $preRunningLog; do
      echo -e "${_COLOR_D}    ${preLog}${_END}"
    done
    echo ""
  fi

  # print info of which worker script runs on which host with what arguments
  # will be useful if called by other script from remote
  echo -e "${_COLOR_D}  On Host [`hostname`] run full invocation as:${_END}"
  echo -e "${_COLOR_D}    $villain $@${_END}"
  echo -e "${_COLOR_D}----------------------------------------------------------------------------${_END}\n"

  ifPassedArray=() # create an array for checking every required arg is passed-in
  argNum=${#shortNameArray[*]}
  declare -i i=0
  while [ "$argNum" -gt "$i" ]; do
    ifPassedArray[$i]="false"
    i=$i+1
  done

  while [ "$#" -gt 0 ]; do
    found="false"
    declare -i i=0
    for short in ${shortNameArray[@]}; do # check if passed-in shortName is valid
      if [ "$short" == "$1" ]; then
        found="true"
        break
      fi
      i=$i+1
    done

    if [ "$found" == "true" ]; then # if shortName is valid
      validPassedInArgArray[$i]="$1"
      ifPassedArray[$i]="true"
      if [ "${ifAcceptValueArray[$i]}" == "true" ]; then # check if accepts values
        validValues="${validValueArray[$i]}"
        if [ -z "$2" ]; then
          echo -e "\n${_ERROR}ERROR: the arg [$1] accepts value but passed-in none${_END}"
          argUsage 'short'
          exit 11
        else # check if passed-in value is valid
          if [ ! -z "${validValues}" ]; then # if accepts value and has a list of valid value
            # iterate all passed-in values, exit on each invalid value
            if [ "${ifMultipleArray[$i]}" == "true" ]; then # if can pass-in multi-values
              for value_i in $2; do # iterate passed-in values
                valid="false"
                for value_j in $validValues; do # iterate the list of valid value
                  if [ "$value_i" == "$value_j" ]; then
                    valid="true"
                    break
                  fi
                done

                if [ "$valid" == "false" ]; then # passed-in value is invalid
                  echo -e "\n${_ERROR}ERROR: arg [$1] does not accept value [$value_i]${_END}"
                  echo -e "${_ERROR}please choose one or more among [${validValues}]${_END}"
                  echo -e "${_ERROR}multiple values need single or double quotes${_END}"
                  argUsage 'short'
                  exit 11
                fi
              done
            else
              # iterate all passed-in values, exit on each invalid value
              valid="false"
              for value in $validValues; do # iterate valid value list
                if [ "$value" == "$2" ]; then
                  valid="true"
                  break
                fi
              done

              if [ "$valid" == "false" ]; then # passed-in value is invalid
                echo -e "\n${_ERROR}ERROR: arg [$1] does not accept value [$2]${_END}"
                echo -e "${_ERROR}please choose one among [${validValues}]${_END}"
                argUsage 'short'
                exit 11
              fi
            fi
          fi

          # the condition of [ -z "${validValueArray[$i]}" ] needes no validation
          # so all passed-in value(s) should be valid till here, export them by 'eval'
          export_cmd="export ${fullNameArray[$i]}=\"$2\"" # make arg can accept values with space, like -x "AA BB"
          validPassedInValueArray[$i]="$2"
          eval "$export_cmd" # export arg and value to parent script
          shift; shift # shift arg and value
        fi
      else
        # simply set the arg to true if not accepts value, only applied to optional arg
        export_cmd="export ${fullNameArray[$i]}=true"
        validPassedInValueArray[$i]=true
        eval "$export_cmd"
        shift; # shift only arg
      fi
    else # if shortName is invalid
      echo -e "\n${_ERROR}ERROR: unknown arg [$1]${_END}"
      argUsage 'short'
      exit 11
    fi
  done

  declare -i i=0
  while [ "$argNum" -gt "$i" ]; do
    # if not optional but without passing value
    if [ "${ifOptionalArray[$i]}" == "false" -a "${ifPassedArray[$i]}" == "false" ]; then
      echo -e "\n${_ERROR}ERROR: missing required arg [${shortNameArray[$i]} ${fullNameArray[$i]}]${_END}"
      argUsage 'short'
      exit 11
    fi
    i=$i+1
  done
}

# add the 1st arg
SetRequestedArg "--help" "true" "HELP_ARG" "displays extended argUsage" "false"

你可能感兴趣的:(ArgsHelper.sh)