280 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			280 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| # This file provides:
 | |
| # - a default control flow
 | |
| #   * initializes the environment
 | |
| #   * able to mock "git push" in your script and in all sub scripts
 | |
| #   * call a function in your script based on the arguments
 | |
| # - named argument parsing and automatic generation of the "usage" for your script
 | |
| # - intercepting "git push" in your script and all sub scripts
 | |
| # - utility functions
 | |
| #
 | |
| # Usage:
 | |
| # - define the variable ARGS_DEF (see below) with the arguments for your script
 | |
| # - include this file using `source utils.inc` at the end of your script.
 | |
| #
 | |
| # Default control flow:
 | |
| # 0. Set the current directory to the directory of the script. By this
 | |
| #    the script can be called from anywhere.
 | |
| # 1. Parse the named arguments
 | |
| # 2. If the parameter "git_push_dryrun" is set, all calls to `git push` in this script
 | |
| #    or in child scripts will be intercepted so that the `--dry-run` and `--porcelain` is added
 | |
| #    to show what the push would do but not actually do it.
 | |
| # 3. If the parameter "verbose" is set, the `-x` flag will be set in bash.
 | |
| # 4. The function "init" will be called if it exists
 | |
| # 5. If the parameter "action" is set, it will call the function with the name of that parameter.
 | |
| #    Otherwise the function "run" will be called.
 | |
| #
 | |
| # Named Argument Parsing:
 | |
| # - The variable ARGS_DEF defines the valid command arguments
 | |
| #   * Required args syntax: --paramName=paramRegex
 | |
| #   * Optional args syntax: [--paramName=paramRegex]
 | |
| #   * e.g. ARG_DEFS=("--required_param=(.+)" "[--optional_param=(.+)]")
 | |
| # - Checks that:
 | |
| #   * all arguments match to an entry in ARGS_DEF
 | |
| #   * all required arguments are present
 | |
| #   * all arguments match their regex
 | |
| # - Afterwards, every parameter value will be stored in a variable
 | |
| #   with the name of the parameter in upper case (with dash converted to underscore).
 | |
| #
 | |
| # Special arguments that are always available:
 | |
| # - "--action=.*": This parameter will be used to execute a function with that name when the
 | |
| #   script is started
 | |
| # - "--git_push_dryrun=true": This will intercept all calls to `git push` in this script
 | |
| #   or in child scripts so that the `--dry-run` and `--porcelain` is added
 | |
| #   to show what the push would do but not actually do it.
 | |
| # - "--verbose=true": This will set the `-x` flag in bash so that all calls will be logged
 | |
| #
 | |
| # Utility functions:
 | |
| # - readJsonProp
 | |
| # - replaceJsonProp
 | |
| # - resolveDir
 | |
| # - getVar
 | |
| # - serVar
 | |
| # - isFunction
 | |
| 
 | |
| # always stop on errors
 | |
| set -e
 | |
| 
 | |
| function usage {
 | |
|   echo "Usage: ${0} ${ARG_DEFS[@]}"
 | |
|   exit 1
 | |
| }
 | |
| 
 | |
| 
 | |
| function parseArgs {
 | |
|   local REQUIRED_ARG_NAMES=()
 | |
| 
 | |
|   # -- helper functions
 | |
|   function varName {
 | |
|     # everything to upper case and dash to underscore
 | |
|     echo ${1//-/_} | tr '[:lower:]' '[:upper:]'
 | |
|   }
 | |
| 
 | |
|   function readArgDefs {
 | |
|     local ARG_DEF
 | |
|     local AD_OPTIONAL
 | |
|     local AD_NAME
 | |
|     local AD_RE
 | |
| 
 | |
|     # -- helper functions
 | |
|     function parseArgDef {
 | |
|       local ARG_DEF_REGEX="(\[?)--([^=]+)=(.*)"
 | |
|       if [[ ! $1 =~ $ARG_DEF_REGEX ]]; then
 | |
|         echo "Internal error: arg def has wrong format: $ARG_DEF"
 | |
|         exit 1
 | |
|       fi
 | |
|       AD_OPTIONAL="${BASH_REMATCH[1]}"
 | |
|       AD_NAME="${BASH_REMATCH[2]}"
 | |
|       AD_RE="${BASH_REMATCH[3]}"
 | |
|       if [[ $AD_OPTIONAL ]]; then
 | |
|         # Remove last bracket for optional args.
 | |
|         # Can't put this into the ARG_DEF_REGEX somehow...
 | |
|         AD_RE=${AD_RE%?}
 | |
|       fi
 | |
|     }
 | |
| 
 | |
|     # -- run
 | |
|     for ARG_DEF in "${ARG_DEFS[@]}"
 | |
|     do
 | |
|       parseArgDef $ARG_DEF
 | |
| 
 | |
|       local AD_NAME_UPPER=$(varName $AD_NAME)
 | |
|       setVar "${AD_NAME_UPPER}_OPTIONAL" "$AD_OPTIONAL"
 | |
|       setVar "${AD_NAME_UPPER}_RE" "$AD_RE"
 | |
|       if [[ ! $AD_OPTIONAL ]]; then
 | |
|         REQUIRED_ARG_NAMES+=($AD_NAME)
 | |
|       fi
 | |
|     done
 | |
|   }
 | |
| 
 | |
|   function readAndValidateArgs {
 | |
|     local ARG_NAME
 | |
|     local ARG_VALUE
 | |
|     local ARG_NAME_UPPER
 | |
| 
 | |
|     # -- helper functions
 | |
|     function parseArg {
 | |
|       local ARG_REGEX="--([^=]+)=?(.*)"
 | |
| 
 | |
|       if [[ ! $1 =~ $ARG_REGEX ]]; then
 | |
|         echo "Can't parse argument $i"
 | |
|         usage
 | |
|       fi
 | |
| 
 | |
|       ARG_NAME="${BASH_REMATCH[1]}"
 | |
|       ARG_VALUE="${BASH_REMATCH[2]}"
 | |
|       ARG_NAME_UPPER=$(varName $ARG_NAME)
 | |
|     }
 | |
| 
 | |
|     function validateArg {
 | |
|       local AD_RE=$(getVar ${ARG_NAME_UPPER}_RE)
 | |
| 
 | |
|       if [[ ! $AD_RE ]]; then
 | |
|         echo "Unknown option: $ARG_NAME"
 | |
|         usage
 | |
|       fi
 | |
| 
 | |
|       if [[ ! $ARG_VALUE =~ ^${AD_RE}$ ]]; then
 | |
|         echo "Wrong format: $ARG_NAME"
 | |
|         usage;
 | |
|       fi
 | |
| 
 | |
|       # validate that the "action" option points to a valid function
 | |
|       if [[ $ARG_NAME == "action" ]] && ! isFunction $ARG_VALUE; then
 | |
|         echo "No action $ARG_VALUE defined in this script"
 | |
|         usage;
 | |
|       fi
 | |
|     }
 | |
| 
 | |
|     # -- run
 | |
|     for i in "$@"
 | |
|     do
 | |
|       parseArg $i
 | |
|       validateArg
 | |
|       setVar "${ARG_NAME_UPPER}" "$ARG_VALUE"
 | |
|     done
 | |
|   }
 | |
| 
 | |
|   function checkMissingArgs {
 | |
|     local ARG_NAME
 | |
|     for ARG_NAME in "${REQUIRED_ARG_NAMES[@]}"
 | |
|     do
 | |
|       ARG_VALUE=$(getVar $(varName $ARG_NAME))
 | |
| 
 | |
|       if [[ ! $ARG_VALUE ]]; then
 | |
|         echo "Missing: $ARG_NAME"
 | |
|         usage;
 | |
|       fi
 | |
|     done
 | |
|   }
 | |
| 
 | |
|   # -- run
 | |
|   readArgDefs
 | |
|   readAndValidateArgs "$@"
 | |
|   checkMissingArgs
 | |
| 
 | |
| }
 | |
| 
 | |
| # getVar(varName)
 | |
| function getVar {
 | |
|   echo ${!1}
 | |
| }
 | |
| 
 | |
| # setVar(varName, varValue)
 | |
| function setVar {
 | |
|   eval "$1=\"$2\""
 | |
| }
 | |
| 
 | |
| # isFunction(name)
 | |
| # - to be used in an if, so return 0 if successful and 1 if not!
 | |
| function isFunction {
 | |
|   if [[ $(type -t $1) == "function" ]]; then
 | |
|     return 0
 | |
|   else
 | |
|     return 1
 | |
|   fi
 | |
| }
 | |
| 
 | |
| # readJsonProp(jsonFile, property)
 | |
| # - restriction: property needs to be on a single line!
 | |
| function readJsonProp {
 | |
|   echo $(sed -En 's/.*"'$2'"[ ]*:[ ]*"(.*)".*/\1/p' $1)
 | |
| }
 | |
| 
 | |
| # replaceJsonProp(jsonFile, propertyRegex, valueRegex, replacePattern)
 | |
| # - note: propertyRegex will be automatically placed into a
 | |
| #   capturing group! -> all other groups start at index 2!
 | |
| function replaceJsonProp {
 | |
|   replaceInFile $1 '"('$2')"[ ]*:[ ]*"'$3'"' '"\1": "'$4'"'
 | |
| }
 | |
| 
 | |
| # replaceInFile(file, findPattern, replacePattern)
 | |
| function replaceInFile {
 | |
|   sed -i .tmp -E "s/$2/$3/" $1
 | |
|   rm $1.tmp
 | |
| }
 | |
| 
 | |
| # resolveDir(relativeDir)
 | |
| # - resolves a directory relative to the current script
 | |
| function resolveDir {
 | |
|   echo $(cd $SCRIPT_DIR; cd $1; pwd)
 | |
| }
 | |
| 
 | |
| function git_push_dryrun_proxy {
 | |
|   echo "## git push dryrun proxy enabled!"
 | |
|   export ORIGIN_GIT=$(which git)
 | |
| 
 | |
|   function git {
 | |
| 	  local ARGS=("$@")
 | |
| 	  local RC
 | |
| 	  if [[ $1 == "push" ]]; then
 | |
| 	    ARGS+=("--dry-run" "--porcelain")
 | |
| 	    echo "####### START GIT PUSH DRYRUN #######"
 | |
|       echo "${ARGS[@]}"
 | |
| 	  fi
 | |
| 	  if [[ $1 == "commit" ]]; then
 | |
|       echo "${ARGS[@]}"
 | |
| 	  fi
 | |
| 	  $ORIGIN_GIT "${ARGS[@]}"
 | |
| 	  RC=$?
 | |
| 	  if [[ $1 == "push" ]]; then
 | |
| 	    echo "####### END GIT PUSH DRYRUN #######"
 | |
| 	  fi
 | |
| 	  return $RC
 | |
|   }
 | |
| 
 | |
|   export -f git
 | |
| }
 | |
| 
 | |
| function main {
 | |
|   # normalize the working dir to the directory of the script
 | |
|   cd $(dirname $0);SCRIPT_DIR=$(pwd)
 | |
| 
 | |
|   ARG_DEFS+=("[--git-push-dryrun=(true|false)]" "[--verbose=(true|false)]")
 | |
|   parseArgs "$@"
 | |
| 
 | |
|   # --git_push_dryrun argument
 | |
|   if [[ $GIT_PUSH_DRYRUN == "true" ]]; then
 | |
|     git_push_dryrun_proxy
 | |
|   fi
 | |
| 
 | |
|   # --verbose argument
 | |
|   if [[ $VERBOSE == "true" ]]; then
 | |
|     set -x
 | |
|   fi
 | |
| 
 | |
|   if isFunction init; then
 | |
|     init "$@"
 | |
|   fi
 | |
| 
 | |
|   # jump to the function denoted by the --action argument,
 | |
|   # otherwise call the "run" function
 | |
|   if [[ $ACTION ]]; then
 | |
|     $ACTION "$@"
 | |
|   else
 | |
|     run "$@"
 | |
|   fi
 | |
| }
 | |
| 
 | |
| 
 | |
| main "$@"
 |