################################################################################
# AMiRo-Apps is a collection of applications and configurations for the #
# Autonomous Mini Robot (AMiRo). #
# Copyright (C) 2018..2020 Thomas Schöpping et al. #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see . #
# #
# This research/work was supported by the Cluster of Excellence Cognitive #
# Interaction Technology 'CITEC' (EXC 277) at Bielefeld University, which is #
# funded by the German Research Foundation (DFG). #
################################################################################
#!/bin/bash
# load library
source "$(dirname ${BASH_SOURCE[0]})/../../bash/setuplib.sh"
### print welcome text #########################################################
# Prints a welcome message to standard out.
#
# usage: printWelcomeText
# arguments: n/a
# return: n/a
#
function printWelcomeText {
printf "######################################################################\n"
printf "# #\n"
printf "# Welcome to the QtCreator setup! #\n"
printf "# #\n"
printf "######################################################################\n"
printf "# #\n"
printf "# Copyright (c) 2018..2020 Thomas Schöpping #\n"
printf "# #\n"
printf "# This is free software; see the source for copying conditions. #\n"
printf "# There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR #\n"
printf "# A PARTICULAR PURPOSE. The development of this software was #\n"
printf "# supported by the Excellence Cluster EXC 227 Cognitive Interaction #\n"
printf "# Technology. The Excellence Cluster EXC 227 is a grant of the #\n"
printf "# Deutsche Forschungsgemeinschaft (DFG) in the context of the German #\n"
printf "# Excellence Initiative. #\n"
printf "# #\n"
printf "######################################################################\n"
}
### print help #################################################################
# Prints a help text to standard out.
#
# usage: printHelp
# arguments: n/a
# return: n/a
#
function printHelp {
printInfo "printing help text\n"
printf "usage: $(basename ${BASH_SOURCE[0]}) [-h|--help] [-p|--project=] [-a|--all] [-c|--clean] [-w|--wipe] [-q|--quit] [--log=]\n"
printf "\n"
printf "options: -h, --help\n"
printf " Print this help text.\n"
printf " -p, --project \n"
printf " Create projects for a single configuration.\n"
printf " -a, --all\n"
printf " Create projects for all configurations.\n"
printf " -c, --clean\n"
printf " Delete project files.\n"
printf " -w, --wipe\n"
printf " Delete project and .user files.\n"
printf " -q, --quit\n"
printf " Quit the script.\n"
printf " --log=\n"
printf " Specify a log file.\n"
}
### read directory where to create/delete projects #############################
# Read the directory where to create/delete project files from user.
#
# usage: getProjectsDir
# arguments:
# Variable to store the selected path to.
# return: n/a
#
function getProjectsDir {
printLog "reading path for project files from user...\n"
local amiroappsdir=$(realpath $(dirname $(realpath ${BASH_SOURCE[0]}))/../../../)
local input=""
read -p "Path where to create/delete project files: " -i $amiroappsdir -e input
printLog "user selected path $(realpath $input)\n"
eval $1="$(realpath $input)"
}
### retrieves the ARM-NONE-EABI-GCC include directory ##########################
# Retrieves the include directory of the currently set arm-none-eabi-gcc.
#
# usage: retrieveGccIncludeDir
# arguments:
# Variable to store the path to.
# return: 0
# No error or warning occurred.
# -1
# Error: Command 'arm-none-eabi-gcc' not found.
# -2
# Error: include directory could not be resolved.
#
function retrieveGccIncludeDir {
# retrieve binary path or link
local binpath=$(which arm-none-eabi-gcc)
local gccincpath=""
if [ -z "$binpath" ]; then
printError "command 'arm-none-eabi-gcc' not found\n"
return -1
else
# traverse any links
while [ -L "$binpath" ]; do
binpath=$(realpath $(dirname $binpath)/$(readlink $binpath))
done
printInfo "gcc-arm-none-eabi detected: $binpath\n"
# return include path
gccincpath=$(realpath $(dirname ${binpath})/../arm-none-eabi/include/)
if [ ! -d "$gccincpath" ]; then
printWarning "$gccincpath does not exist\n"
return -2
else
eval $1="$gccincpath"
return 0
fi
fi
}
### detect available configurations ############################################
# Detect all avalable configurations.
#
# usage: detectConfigurations
# arguments:
# Array variable to store all detected configurations to.
# return: n/a
#
function detectConfigurations {
local configsdir=$(realpath $(dirname $(realpath ${BASH_SOURCE[0]}))/../../../configurations)
local configs_detected=()
# detect all available modules (via directories)
for dir in $(ls -d ${configsdir}/*/); do
configs_detected[${#configs_detected[@]}]=$(basename $dir)
done
# set the output variable
eval "$1=(${configs_detected[*]})"
}
### create project files for a single configuration ############################
# Create project files for all modules of a configuration.
#
# usage: createConfigProjects [-c|--config=] [-p|--path=] [--gcc=] [-o|--out=] [--gccout=]
# arguments:
# Array containing all configurations available.
# -c, --config
# Name (folder name) of the configuration for which project files shall be generated.
# -p, --path
# Path where to create the project files.
# --gcc=
# Path to the GCC include directory.
# -o, --out
# Variable to store the path to.
# --gccout=
# Variable to store the path to the GCC include directory to.
# If this optional arguments is absent, ths function will ask for user input.
# return: 0
# No error or warning occurred.
# 1
# Aborted by user.
# 2
# The selected configuration does not contain any modules.
# -1
# No configurations available.
# -2
# The specified could not be found.
# -3
# Parsing the project for the specified configuration failed.
# -4
# Missing dependencies.
#
function createConfigProjects {
local userdir=$(pwd)
local configsdir=$(realpath $(dirname $(realpath ${BASH_SOURCE[0]}))/../../../configurations)
local configs=("${!1}")
local config=""
local configidx=""
local projectsdir=""
local gccincludedir=""
local outvar=""
local gccoutvar=""
# check dependencies
checkCommands make
if [ $? -ne 0 ]; then
printError "Missing dependencies detected.\n"
return -4
fi
# parse arguments
local otherargs=()
while [ $# -gt 0 ]; do
if ( parseIsOption $1 ); then
case "$1" in
-c=*|--config=*)
config="${1#*=}"; shift 1;;
-c|--config)
config="$2"; shift 2;;
-p=*|--path=*)
projectsdir=$(realpath "${1#*=}"); shift 1;;
-p|--path)
projectsdir=$(realpath "$2"); shift 2;;
--gcc=*)
gccincludedir=$(realpath "${1#*=}"); shift 1;;
--gcc)
gccincludedir=$(realpath "$2"); shift 2;;
-o=*|--out=*)
outvar=${1#*=}; shift 1;;
-o|--out)
outvar=$2; shift 2;;
--gccout=*)
gccoutvar=$(realpath "${1#*=}"); shift 1;;
--gccout)
gccoutvar=$(realpath "$2"); shift 2;;
*)
printError "invalid option: $1\n"; shift 1;;
esac
else
otherargs+=("$1")
shift 1
fi
done
# sanity check for the configs variable
if [ -z "${configs[*]}" ]; then
printError "no configurations available\n"
return -1
fi
# select configuration
if [ -z $config ]; then
# list all available configurations
printInfo "choose a configuration or type 'A' to abort:\n"
for (( idx=0; idx<${#configs[@]}; ++idx )); do
printf "%4u: %s\n" $(($idx + 1)) "${configs[$idx]}"
done
# read user input
printLog "read user selection\n"
local userinput=""
while [[ ! "$userinput" =~ ^[0-9]+$ ]] || [ ! "$userinput" -gt 0 ] || [ ! "$userinput" -le ${#configs[@]} ] && [[ ! "$userinput" =~ ^[Aa]$ ]]; do
read -p "your selection: " -e userinput
printLog "user selection: $userinput\n"
if [[ ! "$userinput" =~ ^[0-9]+$ ]] || [ ! "$userinput" -gt 0 ] || [ ! "$userinput" -le ${#configs[@]} ] && [[ ! "$userinput" =~ ^[Aa]$ ]]; then
printWarning "Please enter an integer between 1 and ${#configs[@]} or 'A' to abort.\n"
fi
done
if [[ "$userinput" =~ ^[Aa]$ ]]; then
printWarning "aborted by user\n"
return 1
fi
# store selection
configidx=$(($userinput - 1))
config="${configs[$configidx]}"
printf "\n"
else
# search all configurations for the selected one
for (( idx=0; idx<${#configs[@]}; ++idx )); do
if [ "${configs[$idx]}" = "$config" ]; then
configidx=$idx
break
fi
done
# error if the configurations could not be found
if [ -z $configidx ]; then
printError "configuration ($config) not available\n"
return -2
fi
fi
# retrieve modules in the configuration
local modules=()
if [ -d ${configsdir}/${config}/modules ]; then
for dir in $(ls -d ${configsdir}/${config}/modules/*/); do
modules[${#modules[@]}]=$(basename $dir)
done
if [ ${#modules[@]} -eq 0 ]; then
printWarning "configuration ${config} does not contain any modules\n"
return 2
fi
else
printWarning "'module/' folder does not exist in configuration ${config}\n"
return 2
fi
# read absolute project directory if required
if [ -z "$projectsdir" ]; then
getProjectsDir projectsdir
printf "\n"
fi
# check for existing project files
local projectfiles=""
for module in ${modules[@]}; do
projectfiles+="$(find ${projectsdir} -maxdepth 1 -type f | grep -E "${config}_${module}\.(includes|files|config|creator)$")"
done
IFS=$'\n'; projectfiles=($projectfiles); unset IFS
if [ ! -z "${projectfiles[*]}" ]; then
printWarning "The following files will be overwritten:\n"
for pfile in ${projectfiles[@]}; do
printWarning "\t$(basename $pfile)\n"
done
local userinput=""
printInfo "Continue and overwrite? [y/n]\n"
readUserInput "YyNn" userinput
case "$userinput" in
Y|y)
;;
N|n)
printWarning "Project generation for ${config} configuration aborted by user\n"
return 1
;;
*)
printError "unexpected input: ${userinput}\n"; return -999;;
esac
printf "\n"
fi
# print message
printInfo "generating QtCreator project files for the $config configuration...\n"
# retrieve absolute GCC include path
if [ -z "$gccincludedir" ]; then
retrieveGccIncludeDir gccincludedir
fi
# change to project directory
cd "$projectsdir"
# create project files for each module of the selected configuration
for module in ${modules[@]}; do
# run make, but only run the GCC preprocessor and produce no binaries
local sourcefiles=()
local sourcefile=""
local parse_state="WAIT_FOR_INCLUDE_COMPILE_MAKE"
local makedirs=()
# capture all output from make and GCC and append the return value of make as last line
printInfo "processing module ${module} (this may take a while)...\n"
local rawout=$(make --directory ${configsdir}/${config}/modules/${module} --always-make USE_OPT="-v -E -H" USE_VERBOSE_COMPILE="no" OUTFILES="" 2>&1 && echo $?)
# check whether the make call was successfull
if [[ $(echo "${rawout}" | tail -n 1) != "0" ]]; then
printError "executing 'make' in configuration directory failed\n"
cd "$userdir"
return -3
else
# cleanup
make --directory ${configsdir}/${config}/modules/${module} clean &>/dev/null
fi
# extract file names from raw output
IFS=$'\n'; rawout=($rawout); unset IFS
for line in "${rawout[@]}"; do
case $parse_state in
WAIT_FOR_INCLUDE_COMPILE_MAKE)
# lines stating include files look like:
# ... <../relative/path/to/file>
if [[ "$line" =~ ^\.+[[:blank:]].+\..+$ ]]; then
sourcefile=${line##* }
if [[ ! "$sourcefile" =~ ^/ ]]; then
sourcefile=$(realpath ${makedirs[-1]}/${sourcefile})
fi
sourcefiles[${#sourcefiles[@]}]="$sourcefile"
# whenever the next source file is processed, a message appears like:
# Compiling
elif [[ "$line" =~ ^Compiling[[:blank:]](.+\..+)$ ]]; then
printf "."
sourcefile=${BASH_REMATCH[1]}
parse_state="WAIT_FOR_COMPILERCALL"
# if make is called again in another directory, a message appears like:
# make[1]: Entering directory 'directory'
elif [[ "$line" =~ ^make(\[[0-9]+\])?:\ Entering\ directory\ \'.+\'$ ]]; then
makedirs+=($(echo "$line" | (cut -d "'" -f 2)))
# if make is leaving a directory, a message appears like:
# make[1]: Leaving directory 'directory'
elif [[ "$line" =~ ^make(\[[0-9]+\])?:\ Leaving\ directory\ \'.+\'$ ]]; then
unset makedirs[-1]
fi;;
WAIT_FOR_COMPILERCALL)
# wait for the actual call of the compiler to retrieve the full path to the source file
if [[ "$line" == *${sourcefile}* ]]; then
line="${line%%${sourcefile}*}${sourcefile}"
line="${line##* }"
if [[ "$line" =~ ^/ ]]; then
# aboslute path
sourcefile=$line
else
# relative path
sourcefile=$(realpath ${makedirs[-1]}/${line##* })
fi
sourcefiles[${#sourcefiles[@]}]="$sourcefile"
parse_state="WAIT_FOR_INCLUDE_COMPILE_MAKE"
fi;;
esac
done
unset rawout
printf "\n"
# sort and remove duplicates
IFS=$'\n'; sourcefiles=($(sort --unique <<< "${sourcefiles[*]}")); unset IFS
# extract include paths
local includes=()
for source in ${sourcefiles[*]}; do
includes[${#includes[@]}]="$(dirname ${source})"
done
# sort and remove duplicates
IFS=$'\n'; includes=($(sort --unique <<< "${includes[*]}")); unset IFS
# generate the .incldues file, containing all include paths
echo "" > ${projectsdir}/${config}_${module}.includes
for inc in ${includes[*]}; do
echo "$inc" >> ${projectsdir}/${config}_${module}.includes
done
# generate the .files file, containing all source files
echo "" > ${projectsdir}/${config}_${module}.files
for source in ${sourcefiles[*]}; do
# skip GCC files
if [[ ! "$source" =~ .*/gcc.* ]]; then
echo "$source" >> ${projectsdir}/${config}_${module}.files
fi
done
# generate a default project configuration file if it doesn't exits yet
if [ ! -f ${projectsdir}/${config}_${module}.config ]; then
echo "// Add predefined macros for your project here. For example:" > ${projectsdir}/${config}_${module}.config
echo "// #define YOUR_CONFIGURATION belongs here" >> ${projectsdir}/${config}_${module}.config
fi
# generate a default .creator file if it doesn't exist yet
if [ ! -f ${projectsdir}/${config}_${module}.creator ]; then
echo "[general]" > ${projectsdir}/${config}_${module}.creator
fi
done
# go back to user directory
cd $userdir
# fill the output variables
if [ ! -z "$outvar" ]; then
eval $outvar="$projectsdir"
fi
if [ ! -z "$gccoutvar" ]; then
eval $gccoutvar="$gccincludedir"
fi
return 0
}
### create project files for all configurations ################################
# Create project files for all configurations.
#
# usage: createAllProjects [-p|--path=] [--gcc=] [-o|--out=] [--gccout=]
# arguments:
# Array containing all configurations available.
# -p, --path
# Path where to create the project files.
# --gcc=
# Path to the GCC include directory.
# -o, --out
# Variable to store the path to.
# --gccout=
# Variable to store the path to the GCC include directory to.
# If this optional arguments is absent, ths function will ask for user input.
# return: 0
# No error or warning occurred.
# 1
# Aborted by user.
# -1
# No configurations available.
#
function createAllProjects {
local configs=("${!1}")
local projectsdir=""
local gccincludedir=""
local outvar=""
local gccoutvar=""
# parse arguments
local otherargs=()
while [ $# -gt 0 ]; do
if ( parseIsOption $1 ); then
case "$1" in
-p=*|--path=*)
projectsdir=$(realpath "${1#*=}"); shift 1;;
-p|--path)
projectsdir=$(realpath "$2"); shift 2;;
--gcc=*)
gccincludedir=$(realpath "${1#*=}"); shift 1;;
--gcc)
gccincludedir=$(realpath "$2"); shift 2;;
-o=*|--out=*)
outvar=${1#*=}; shift 1;;
-o|--out)
outvar=$2; shift 2;;
--gccout=*)
gccoutvar=$(realpath "${1#*=}"); shift 1;;
--gccout)
gccoutvar=$(realpath "$2"); shift 2;;
*)
printError "invalid option: $1\n"; shift 1;;
esac
else
otherargs+=("$1")
shift 1
fi
done
# sanity check for the configurations variable
if [ -z "${configs[*]}" ]; then
printError "no configurations available\n"
return -1
fi
# read absolute project directory if required
if [ -z "$projectsdir" ]; then
getProjectsDir projectsdir
fi
# check for existing project files
local projectfiles=""
for config in ${configs[@]}; do
for module in ${modules[@]}; do
projectfiles+="$(find ${projectsdir} -maxdepth 1 -type f | grep -E "${config}_${module}\.(includes|files|config|creator)$")"
done
done
IFS=$'\n'; projectfiles=($projectfiles); unset IFS
if [ ! -z "${projectfiles[*]}" ]; then
printWarning "The following files will be removed:\n"
for pfile in ${projectfiles[@]}; do
printWarning "\t$(basename $pfile)\n"
done
local userinput=""
printInfo "Continue and overwrite? [y/n]\n"
readUserInput "YyNn" userinput
case "${userinput}" in
Y|y)
for pfile in ${projectfiles[*]}; do
rm "$pfile"
done
;;
N|n)
printWarning "Project generation aborted by user\n"
return 1
;;
*)
printError "unexpected input: ${userinput}\n"
return 999
;;
esac
fi
# print message
printf "\n"
printInfo "generating QtCreator project files for all configurations...\n"
# retrieve absolute GCC include path
if [ -z "$gccincludedir" ]; then
retrieveGccIncludeDir gccincludedir
fi
# iterate over all configurations
local retval=1
for config in ${configs[@]}; do
if [ $retval != 0 ]; then
printf "\n"
fi
createConfigProjects configs[@] --config="$config" --path="$projectsdir" --gcc="$gccincludedir"
retval=$?
done
return 0
}
### delete project files #######################################################
# Deletes all project files and optionally .user files, too.
#
# usage: deleteProjects [-p|--path=] [-c|--config=] [-o|--out=] [-w|-wipe]
# arguments: -p, --path
# Path where to delete the project files.
# -c, --config
# Configuration name for which the project files shall be deleted.
# -o, --out
# Variable to store the path to.
# -w, --wipe
# Delete .user files as well.
# return:
# - 0: no error
#
function deleteProjects {
local config=""
local projectsdir=""
local outvar=""
local wipe=false
local files=""
# parse arguments
local otherargs=()
while [ $# -gt 0 ]; do
if ( parseIsOption $1 ); then
case "$1" in
-p=*|--path=*)
projectsdir=$(realpath "${1#*=}"); shift 1;;
-p|--path)
projectsdir=$(realpath "$2"); shift 2;;
-c=*|--config=*)
config="${1#*=}"; shift 1;;
-c|--config)
config="${2}"; shift 2;;
-o=*|--out=*)
outvar=${1#*=}; shift 1;;
-o|--out)
outvar=$2; shift 2;;
-w|--wipe)
wipe=true; shift 1;;
*)
printError "invalid option: $1\n"; shift 1;;
esac
else
otherargs+=("$1")
shift 1
fi
done
# read absolute project directory if required
if [ -z "$projectsdir" ]; then
getProjectsDir projectsdir
fi
# list all files to be deleted
if [ -z "$config" ]; then
if [ $wipe != true ]; then
files=$(find "${projectsdir}" -maxdepth 1 -type f | grep -E "^.+\.(includes|files|config|creator|cflags|cxxflags)$")
else
files=$(find "${projectsdir}" -maxdepth 1 -type f | grep -E "^.+\.(includes|files|config|creator|cflags|cxxflags|creator(\.user(\..+)?)?)$")
fi
else
if [ $wipe != true ]; then
files=$(find "${projectsdir}" -maxdepth 1 -type f | grep -E "^${config}_.+\.(includes|files|config|creator|cflags|cxxflags)$")
else
files=$(find "${projectsdir}" -maxdepth 1 -type f | grep -E "^${config}_.+\.(includes|files|config|creator|cflags|cxxflags|creator(\.user(\..+)?)?)$")
fi
fi
if [ ! -z "$files" ]; then
printInfo "Deleting the following files:\n"
while read line; do
printInfo "\t$(basename ${line})\n"
rm ${line} 2>&1 | tee -a $LOG_FILE
done <<< "${files}"
else
printInfo "No project files found\n"
fi
# store the path to the output variable, if required
if [ ! -z "$outvar" ]; then
eval $outvar="$projectsdir"
fi
return 0
}
### main function of this script ###############################################
# Creates, deletes and wipes QtCreator project files.
#
# usage: see function printHelp
# arguments: see function printHelp
# return: 0
# No error or warning ocurred.
#
function main {
# print welcome/info text if not suppressed
if [[ $@ != *"--noinfo"* ]]; then
printWelcomeText
else
printf "######################################################################\n"
fi
printf "\n"
# if --help or -h was specified, print the help text and exit
if [[ $@ == *"--help"* || $@ == *"-h"* ]]; then
printHelp
printf "\n"
quitScript
fi
# set log file if specified
if [[ $@ == *"--log"* ]] || [[ $@ == *"--LOG"* ]]; then
# get the parameter (file name)
local cmdidx=1
while [[ ! "${!cmdidx}" = "--log"* ]] && [[ ! "${!cmdidx}" = "--LOG"* ]]; do
cmdidx=$[cmdidx + 1]
done
local cmd="${!cmdidx}"
local logfile=""
if [[ "$cmd" = "--log="* ]] || [[ "$cmd" = "--LOG="* ]]; then
logfile=${cmd#*=}
else
local filenameidx=$((cmdidx + 1))
logfile="${!filenameidx}"
fi
# optionally force silent appending
if [[ "$cmd" = "--LOG"* ]]; then
setLogFile --option=c --quiet "$logfile" LOG_FILE
else
setLogFile "$logfile" LOG_FILE
printf "\n"
fi
fi
# log script name
printLog "this is $(realpath ${BASH_SOURCE[0]})\n"
# detect available configurations and inform user
local configurations=()
detectConfigurations configurations
case "${#configurations[@]}" in
0)
printInfo "no configuration has been detected\n";;
1)
printInfo "1 configuration has been detected:\n";;
*)
printInfo "${#configurations[@]} configurations have been detected:\n";;
esac
for (( idx=0; idx<${#configurations[@]}; ++idx )); do
printInfo " - ${configurations[$idx]}\n"
done
printf "\n"
# parse arguments
local otherargs=()
while [ $# -gt 0 ]; do
if ( parseIsOption $1 ); then
case "$1" in
-h|--help) # already handled; ignore
shift 1;;
-p=*|--project=*)
createConfigProjects configurations[@] --configuration="${1#*=}"; printf "\n"; shift 1;;
-p|--project)
createConfigProjects configurations[@] --configuration="${2}"; printf "\n"; shift 2;;
-a|--all)
createAllProjects configurations[@]; shift 1;;
-c|--clean)
deleteProjects; printf "\n"; shift 1;;
-w|--wipe)
deleteProjects --wipe; printf "\n"; shift 1;;
-q|--quit)
quitScript; shift 1;;
--log=*|--LOG=*) # already handled; ignore
shift 1;;
--log|--LOG) # already handled; ignore
shift 2;;
--noinfo) # already handled; ignore
shift 1;;
*)
printError "invalid option: $1\n"; shift 1;;
esac
else
otherargs+=("$1")
shift 1
fi
done
# interactive menu
while ( true ); do
# main menu info prompt and selection
printInfo "QtCreator setup main menu\n"
printf "Please select one of the following actions:\n"
printf " [P] - create projects for a single configuration\n"
printf " [A] - create projects for all configurations\n"
printf " [C] - clean project files\n"
printf " [W] - wipe project and .user files\n"
printf " [Q] - quit this setup\n"
local userinput=""
readUserInput "PpAaCcWwQq" userinput
printf "\n"
# evaluate user selection
case "$userinput" in
P|p)
createConfigProjects configurations[@]; printf "\n";;
A|a)
createAllProjects configurations[@]; printf "\n";;
C|c)
deleteProjects; printf "\n";;
W|w)
deleteProjects --wipe; printf "\n";;
Q|q)
quitScript;;
*) # sanity check (exit with error)
printError "unexpected argument: $userinput\n";;
esac
done
exit 0
}
################################################################################
# SCRIPT ENTRY POINT #
################################################################################
main "$@"