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