#!/bin/bash

#---------------------------------------------------------------------
## @param string to explode
## @param delimiter
## @param name of array to put it in
##
## @Description
## Turns a string into an array. Each element of the array
## is separated in the string by the delimiter.
##
## Note: The array you want the fields put into must be
## declared before you call this function.
## <pre>
## EXAMPLE
##
## my_array=()
## explode "a_string_to_explode" "_" "my_array"
## echo my_array[*]
##
## Produces "a" "string" "to" "explode".
## </pre>
#---------------------------------------------------------------------
function explode()
{ # $1==string to explode, $2==delimiter, $3==name of array to put it in

  [[ "$3" ]] || return 1
  local l=$1
  local i=0
  while [[ $l ]]; do
    local result=${l%%$2*}
    l=${l#"$result"}
    l=${l#$2}
    eval "$3[$i]=\"$result\""
    ((i++))
  done
  # this adds an empty array element at the end if the line ended in $2
  local lc=${1//\*$2/true}
  if [ "$lc" = "true" ]; then
    eval "$3[$i]=\"\""
  fi
}


#---------------------------------------------------------------------
## @Type API
## @param sed command
## @param file
##
## First argument is a sed command.  Second argument is a file.  
## sedit performs the sed command on the file, modifiying the 
## original file.  For example, 
## <br>sedit "s/foo/bar/g" /tmp/somefile <br>
## will replace all occurances of foo with bar in /tmp/somefile.  
## This function is often used in spells to make changes to source
## files before compiling.  See the sed man page for more information.
##
#---------------------------------------------------------------------
function real_sedit()  {
  sed -i "$1" "$2"
}

#---------------------------------------------------------------------
## @Type API
## @param string to check
##
## Argument is a string to check if the string contains all digits or
## not
#--------------------------------------------------------------------
function isdigit() {
	echo $1 | grep '[[:digit:]]+' >> /dev/null 2>&1 &&
	return 0
}

#--------------------------------------------------------------------
## @Type API
## @param string to check
##
## Argumtemt is a string to check if it contains all chars a-zA-Z
#--------------------------------------------------------------------
function isalpha() {
	echo $1 | grep '[[:alpha:]]+' >> /dev/null 2>&1 &&
	return 0
}

#---------------------------------------------------------------------
## @Type API
## @param question 
## @param default answer
##
## @return 0 on yes
## @return 1 on no
##
## Asks the user a yes/no question.  First argument is the question to
## ask, second argument is the default answer.  If a timeout occurs
## before the question is answered, the given default answer is 
## applied. Pressing spacebar or enter applies the default answer
## immediatelly without waiting for timeout.
## Returns true or false based on the answer given.
##
#---------------------------------------------------------------------
function real_query()  { (

  debug  "libmisc" "Running query() with the following arguments: '$1' and '$2'"
  
  while true ; do
  
    RESPONSE=""

    if  [  -z  "$SILENT"  ];  then

      echo  -e  -n  "${QUERY_COLOR}$1 [$2] ${DEFAULT_COLOR}"
 
      read   -t  $PROMPT_DELAY  -n  1  RESPONSE
      echo
    fi

    RESPONSE=${RESPONSE:=$2}
    case  $RESPONSE  in
      n|N)  return 1  ;;
      y|Y)  return 0  ;;
    esac
  
  done

) }

#---------------------------------------------------------------------
## @param return_var
## @param question
## @param default answer
##
## @return 0 user supplied answer
## @return 1 default answer is used
##
## Asks user for string, with default answer and timeout (like query)
##
#---------------------------------------------------------------------
function real_query_string () {
  
    debug  "libmisc" "Running question() with the following arguments: '$1' and '$2'"
    
    local RESPONSE=""
    local RETURN=0
    
    local DEFAULT=""
    [ -n "$3" ] && DEFAULT=" [$3] "
    
    if [ -z "$SILENT" ]; then
        echo -e -n "${QUERY_COLOR}$2${DEFAULT}${DEFAULT_COLOR}"
        read -t $PROMPT_DELAY RESPONSE
    fi
  
    [ -z "$RESPONSE" ] && RETURN=1 && RESPONSE="$3"
    
    eval $1=\"${RESPONSE}\"
    return $RETURN
}



#---------------------------------------------------------------------
## @Type API
## @param message to echo
## @Stdout message
## echo's the given arguments if SILENT is not set.
#---------------------------------------------------------------------
function real_message()    {
  if  [  !  -n  "$SILENT"  ];  then
    if  [  "$1"  ==  "-n"  ];  then
      shift  1
      echo  -n  -e  "$*"
    else
      echo  -e  "$*"
    fi
  fi

}

#---------------------------------------------------------------------
## @param type
## @param message
##
## Enters a debug message if the type of debug message != 'no'
##
## The 'type' is usually the name of the file the debug statement
## comes from. i.e. DEBUG_liblock, DEBUG_libsorcery, DEBUG_cast etc.
##
#---------------------------------------------------------------------
function debug()  {

  [[ $DEBUG ]] || return 0
  
  local debugVar="DEBUG_${1}"
  local i
  if [[ ${!debugVar} != "no" ]] ; then
    echo -n "$1($$): " >>$DEBUG
    shift
    for i in "$@" ; do
      echo -n " \"$i\"" >>$DEBUG
    done
    echo >>$DEBUG
  fi

  true
}

#---------------------------------------------------------------------
## @Stdout progress spinner
## Displays progress spinner like the one in fsck.
##
#---------------------------------------------------------------------
function progress_spinner()  {
  let  PROGRESS_SPINNER=$PROGRESS_SPINNER+1 
  if ((  PROGRESS_SPINNER > ${#PROGRESS_SPINNER_CHARS}-1  )); then
    PROGRESS_SPINNER=0;
  fi
  echo -en "\b${PROGRESS_SPINNER_CHARS:$PROGRESS_SPINNER:1}"
}


#---------------------------------------------------------------------
## @param opt "dot"
## @param count
## @param total
## @param opt length
## <pre>
## Displays progress bar in the form of [---->   ] 100%
## Or just a dot in the dot format
##</pre>
#---------------------------------------------------------------------
function progress_bar()  {
debug "libmisc" "progress_bar - $*"
  local percent i len num_dash

  if [[ $1 == -dot ]] ; then
    len=0
    shift 1
  else
    len=${3:-${COLUMNS:-70}}
  fi
  
  # Can't make a bar because there's no total, or the length isn't
  #  long enough, or if there is no length 
  if [ $# -lt 2 ] || [ $len -lt 8 ] ; then
    message -n "."
    return 0
  fi
  
  if [ $1 -lt 1 ] || [ $2 -lt 1 ] ; then return 1 ; fi
  
  percent=$((100*$1/$2))
  percent=`printf "%.0f" $percent`
  
  if [[ $LAST_PERCENT == $percent ]] ; then
    progress_spinner
    return 0
  fi

  LAST_PERCENT=$percent


  dash_len=$(($len-8))
  num_dash=$(( $dash_len*$1/$2 ))

  #Format: [--->  ] 100%

  #opening of the bar
  BAR_STRING="["

  #The '=' signs
  for (( i=0 ; i<$num_dash ; i++ )) ; do
    BAR_STRING="${BAR_STRING}="
  done

  #Now a pretty indicator
  [ $num_dash -lt $((dash_len-1)) ] && BAR_STRING="${BAR_STRING}>"

  #Fill the rest of the bar up with blanks
  for (( i++ ; i<$dash_len-1 ; i++ )) ; do
    BAR_STRING="${BAR_STRING} "
  done

  #Put on the closing for the bar and the percent
  BAR_STRING="${BAR_STRING}] ${percent}%"

  #Clear the rest of the space
  for (( i+=3+${#percent} ; i<$len ; i++ )) ; do
    BAR_STRING="${BAR_STRING} "
  done

  clear_line
  message -n "$BAR_STRING"
  progress_spinner

 return 0


}
#---------------------------------------------------------------------
## @Stdout Clear line
## Clears the current line and puts the cursor at the begining of it.
##
#---------------------------------------------------------------------
function clear_line()  {

  echo -en '\r\033[2K'
  return 0

}


#---------------------------------------------------------------------
##
## @Globals SOUND
## Plays a given soundfile if sounds are on and the file exists in 
## the chosen theme.
##
#---------------------------------------------------------------------
function sound()  {

  case  $SOUND  in
    on)  SOUND_FILE=$SOUND_DIRECTORY/$SOUND_THEME/$1
         if  [  -e  $SOUND_FILE  ];  then
           (  cd  /  ;  play  $SOUND_FILE  2>/dev/null  & )
	   debug "libmisc" "Playing $SOUND_FILE"
	 else
	   debug "libmisc" "Error playing $SOUND_FILE: no such file"
         fi
    ;;
  esac

}


#---------------------------------------------------------------------
## @param filename
##
## Runs the editor given by EDITOR on the file given in the first
## argument.  If EDITOR is not set, "nano -w" is used.
##
#---------------------------------------------------------------------
function edit_file()  {

  ${EDITOR:-nano -w} $1

}


#---------------------------------------------------------------------
## @param string
## @Stdout escaped string
##
## Adds escape sequences to strings for special characters.
## Does NOT escape the string ".*".
## Used for putting escape sequences in regexp strings that should
## be treated literaly.
##
#---------------------------------------------------------------------
function esc_str()
{

  local OUT
  if [[ $* == .* ]] ; then
    OUT="$1"
  else
    OUT=`echo "$@" | sed -e 's:\.:\\\.:'g \
                         -e 's:\*:\\\*:'g`
  fi
  echo "$OUT"

}


#---------------------------------------------------------------------
## Properly quotes and backquotes parameters so they can be passed
## through su at the start of many sorcery commands
##
## Expected usage is:
##
##  PARAMS=$(consolidate_params "$@")
##  su -c "$0 $PARAMS" root
#---------------------------------------------------------------------
function consolidate_params() {
  local param
  for param in "$@"; do
    echo -n $param | sed 's/ /\\ /g'
    echo -n " "
  done
}

#---------------------------------------------------------------------
## @Stdin directories.
## @Stdout directories' basenames
## Takes newline separated list of directories
## and outputs their base names.
##
#---------------------------------------------------------------------
function get_basenames() {
  local DIRECTORY=''
  while read DIRECTORY; do
    basename "$DIRECTORY"
  done
}


#---------------------------------------------------------------------
## @Stdin directories.
## @Stdout directories' basenames
## Takes newline separated list of pathnames
## and outputs their directory names.
##
#---------------------------------------------------------------------
function get_dirnames() {
  local PATH_NAME=''
  while read PATH_NAME; do
    dirname "$PATH_NAME"
  done
}

#---------------------------------------------------------------------
##  @param The function to call on each itteration and it's arguments
##  @param The separator string. If there is no $3, only the first char counts
##  @param The string to itterate over. (optional)
##  @Stdin is used when $3 isn't given.
##
##  $3 is optional, when it isn't used, 
##  stdin is used and only the first letter in $2 is used. Note, using stdin
##  can have odd side effects when your function uses read and stdin itself.
##
## Special vars: 
##  BREAK: Use this to break out of the loop prematurely, also causes a return of 1.
##
## If the function returns the value of the last return
## Notes:
##  In stdin mode, if the string does not terminate with the delimiter, the last 
##  token will be ignored.
#----------------------------------------------------------------------
function iterate()
{ # $1=callback+args, $2=separator, $3=(opt)string
#  debug "libmisc" "iterate - $@"
  
  [ $# -lt 2 ] && return 2
  
  local oldIFS="$IFS"
  local token
  local func="$1"
  local returnValue=0
  if [[ $# -gt 2 ]] ; then
    IFS="$2"
    shift 2
    for token in $* ; do
      IFS="$oldIFS"
      
      eval "$func \"$token\""
      [[ $BREAK ]] && break
      
    done
    IFS="$oldIFS"
  else
    while read -r -d "$2" token ; do
      eval "$func \"$token\""
      [[ $BREAK ]] && break
    done
#   debug "leftover token: $token"
  fi
  
  returnValue=$?
  [[ $BREAK ]] && debug "libmisc" "iterate - I was BREAKed."
  unset BREAK
  return $returnValue
}

#---------------------------------------------------------------------
## @param return_var (must not be i, foo, temp, returnvar, or default)
## @param default choice
## @param elements, ..
## 
## gives the user some nice select list and puts the selected
## item in return_var
##
#---------------------------------------------------------------------
function select_list()
{
    local i
    local foo temp number
    local returnvar=$1
    local default=$2
    shift 2
    let i=0
    for foo in "$@"; do
        message "\t$DEFAULT_COLOR($i)  $SPELL_COLOR$foo$DEFAULT_COLOR"
        temp[$i]="$foo"
        let i++
    done

    message -n "\n${QUERY_COLOR}Which one do you want? [$default]$DEFAULT_COLOR "
    read   -t  $PROMPT_DELAY  -n  1  number
    if [[ ! $number ]] ; then number=$default; fi
    while [[ $number != [0-9]* ]] || (( $number >= $i )) ; do
        message -n "\n${QUERY_COLOR}Which one do you want? [$default]$DEFAULT_COLOR "
        read -n 1 number
        if [[ ! $number ]] ; then number=$default; fi
    done

    echo

    eval $returnvar=\"${temp[$number]}\"
}

#---------------------------------------------------------------------
## @param title
## 
## sets the terminal title if TERM=xterm|rxvt or the window title if
## TERM=screen
##
#---------------------------------------------------------------------
function set_term_title()
{
    if [ "$SET_TERM_TITLE" != "on" ]; then return; fi
    case $TERM in
        xterm*|rxvt)  echo -ne "\e]0;$@\007"
                      ;;
             screen)  echo -ne "\ek$@\e\\"
                      ;;
                  *) ;;
    esac
}


#---------------------------------------------------------------------
## @param return_var
## @param elements, ...
##
## Removes from the list string(s). Strings are kept to be unique and
## are separated by spaces
##
#---------------------------------------------------------------------
function real_list_remove () {
    local var=$1
    shift
    
    local i
    
    for i in $@; do
        eval "$var=\`echo \" \$$var \" | sed  \
               -e \"s/[[:space:]]\\+/ /g\"   \
               -e \"s/ $i / /g\"             \
               -e \"s/^ *//\"                \
               -e \"s/ *$//\"\`"
    done
}


#---------------------------------------------------------------------
## @param return_var
## @param elements, ...
##
## Puts in the list string(s). Strings are kept to be unique and are
## separated by spaces
##
#---------------------------------------------------------------------
function real_list_add () {
  local var=$1
  shift
  local i
  for i in $@; do
    local found=0
    function list_add_sub() {
      if [ "$i" == "$1" ]; then
        found=1
      fi
    }
    iterate list_add_sub " " "$(eval "echo \$$var")"
    if [ $found -eq 0 ]; then
      eval "$var=\`echo \"\$$var $i\"\`"
      eval "$var=\"\${$var/# /}\""
    fi
  done
}


#---------------------------------------------------------------------
## @param string
## @param elements, ...
##
## return 0 at least one element is in list
## return 1 none of supplied elements is not in list
##
## Finds if at least one of given elements is in the string. Warning,
## this function takes real string, not variable name as other list_*
## functions
##
#---------------------------------------------------------------------
function real_list_find () {
    local i
    local input="$1"
    shift

    for i in $@; do
        eval "echo \" $input \" | grep -q -e \"[[:space:]]$i[[:space:]]\" && return 0"
    done
    return 1
}

#---------------------------------------------------------------------
## @param spell
## @param variable to read
## @param variable name
##
## finds the persistent var for spell and sets variable name to the value
## of the variable to read
##
#---------------------------------------------------------------------
function real_persistent_read() {
  local exfile pfile tb_dir
  local varin=$2
  local varout=$3
  tablet_find_spell_dir $1 tb_dir  || return 2
  tablet_get_spell_file $tb_dir EXPORTS exfile || return 4
  tablet_get_persistent_config $tb_dir pfile || return 3
  if grep -q "^${varin}$" $exfile
  then
    eval "$3="'$( . "$pfile" && echo "${!varin}" )'
  else
    return 1
  fi &&
  return 0
}

#---------------------------------------------------------------------
## @param variables, ...
##
## Adds variable names to the list of persistent variables
##
#---------------------------------------------------------------------
function real_persistent_add () {
  if [[ $PROTECT_SORCERY ]] ; then
    for each in "$@" ; do
      if is_sorcery_var $each; then
        # complain, but not if we're persistent loading, bug 9416
        if ! [[ $PERSISTENT_LOADING ]] ; then
          complain_sorcery_var $each
        fi
      else
        list_add PERSISTENT_VARIABLES $each
      fi
    done
  else
    list_add PERSISTENT_VARIABLES "$@"
  fi
}


#---------------------------------------------------------------------
## @param variables, ...
##
## Removes variable names from the list of persistent variables
##
#---------------------------------------------------------------------
function real_persistent_remove () {
    list_remove PERSISTENT_VARIABLES "$@"
}


#---------------------------------------------------------------------
## Loads persistent variables stored in file "$SPELL_CONFIG"
##
#---------------------------------------------------------------------
function real_persistent_load () {
    local PERSISTENT_LOADING=yes
    local PERSISTENT_FILE=${1:-$SPELL_CONFIG.p}
    if [ -f "$PERSISTENT_FILE" ]; then
        . "$PERSISTENT_FILE"
        local line
        persistent_add `while read line; do echo ${line%%=*}; done < "$PERSISTENT_FILE"`
    fi
}


#---------------------------------------------------------------------
## Saves variables marked as persistent to file "$SPELL_CONFIG". The
## File is completely overwritten. Also unsets all persistent
## variables
##
#---------------------------------------------------------------------
function real_persistent_save () {
    local VAR
    local TMP
    > "$SPELL_CONFIG.p"
    for VAR in $PERSISTENT_VARIABLES; do
        config_get_option $VAR TMP
        echo "$VAR=\"$TMP\"" >> "$SPELL_CONFIG.p"
        unset "$VAR"
    done
    unset PERSISTENT_VARIABLES
}


#---------------------------------------------------------------------
## Unsets all persistent variables. Mainly usable as replacement of
## persistent_save for functions which can be called by nonroot users
## ( for example from 'gaze what' )
##
#---------------------------------------------------------------------
function real_persistent_clear () {
    local VAR
    for VAR in $PERSISTENT_VARIABLES; do
        unset "$VAR"
    done
    unset PERSISTENT_VARIABLES
}



#---------------------------------------------------------------------
## @param config file variable
## @param return variable (optional)
##
## @return 1 option is not present in the config
## @return 0 option is present in the config (even if the option is
##           empty string "") and it's set.
##
## Retrieves setting from $SPELL_CONFIG file and optionally sets user
## supplied variable. Function is here to make possible changes to
## config system easy, since all other functions are using this
## function and are not working with variables directly
##
#---------------------------------------------------------------------
#function config_get_option {
#    local VAR=$2
#    local REPLY=`grep -E "^$1=" $SPELL_CONFIG | head -n 1 | sed "s/^[^=]*=//"`
#    [ -n "$VAR" ] && eval $VAR=\"$REPLY\"
#    [ -z "$REPLY" ] && return 1
#    return 0
#}
function config_get_option () {
  if [[ $PROTECT_SORCERY ]] ; then
    if is_sorcery_var $1; then
      complain_sorcery_var $1
      return 1
    fi
  fi

  # afrayedknot hates variable leakage
  if real_list_find $1 $PERSISTENT_VARIABLES; then
    # variable is known and valid
    [[ -n $2 ]] && eval "$2=\"\$$1\""
    return 0
  else
    # variable is not known and is invalid
    eval "unset \"$1\""
    return 1
  fi
}

#---------------------------------------------------------------------
## @param Name of variable
## @param return variable
##
## @return 0 if the variable could be found, 1 if no old persistent file
##         exists.
##
## Get value of a persistent variable from the previous cast when
## there is a -r. The persistent data moves to a seperate directory
## when cast -r is run, this makes the new persistent config clean
## but allows us to provide the user defaults from the last cast.
#---------------------------------------------------------------------
function config_get_last_option() {
  if [[ $PROTECT_SORCERY ]] ; then
    if is_sorcery_var $1; then
      complain_sorcery_var $1
      return 1
    fi
  fi

  local pfile
  if test -f $ABANDONED_PERSIST/$SPELL.p; then
    pfile=$ABANDONED_PERSIST/$SPELL.p
  # leave this out for now until theres a way to disable it
  # having it enabled will make it difficult to get back to the true defaults
  #else
  #  local tb_dir
  #  tablet_find_spell_dir $SPELL tb_dir &&
  #  tablet_get_persistent_config $tb_dir pfile || return 1
  fi

  local foo
  if [[ $pfile ]] ; then
    foo=$(
      persistent_load $pfile
      if real_list_find $1 $PERSISTENT_VARIABLES; then
        eval "echo \"\$$1\""
      fi
    )
  else
    return 1
  fi

  eval "$2=\"\$foo\""
  return 0
}


#---------------------------------------------------------------------
## @param config file variable
## @param value
##
## Stores string to given variable and makes the variable persistent
## Function is here to make possible changes to config system easy,
## since all other functions are using this function and are not
## working with variables directly
##
#---------------------------------------------------------------------
#function config_set_option {
#    echo "$1=$2" >> $SPELL_CONFIG
#}
function config_set_option () {
    persistent_add $1
    eval "$1=\"$2\""
}


#---------------------------------------------------------------------
## @param config file variable
## @param question
## @param default answer
##
## @return 0 in all cases
##
## Asks user for string, with default answer and timeout (like query)
## Return variable is also marked as persistent
##
#---------------------------------------------------------------------
function real_config_query () {
    local ANSWER
    if config_get_option "$1" ANSWER; then
        # option allready ANSWERed in config
        echo -e "[[ ${QUERY_COLOR}$2${DEFAULT} -> ${QUERY_COLOR}$ANSWER${DEFAULT} ]]"
  
        # already have a reasonable value...
        [ "$ANSWER" == "y" ] || [ "$ANSWER" == "n" ] && return 0
    fi

    local default
    config_get_last_option "$1" default
    [[ $default ]] || default="$3"

    if query "$2" "$default"; then
        config_set_option "$1" y
    else
        config_set_option "$1" n
    fi
    return 0
}

#---------------------------------------------------------------------
## @param config file variable
## @param question
## @param default answer [y|n]
## @param option_yes - can't be empty string
## @param option_no - can't be empty string
##
## @return 0 in all cases
##
## Asks user for string, with default answer and timeout (like query)
## The string is added to the variable
## If you want to use empty string, place there dummy string and remove
## it later by list_remove function. Also for one config variable, all
## option_yes and option_no have to be different.
##
## Return variable is also marked as persistent
#---------------------------------------------------------------------
function real_config_query_option () {
    local ANSWER key
  
    # If the option exists
    # If the option contains option_yes or option_no
    if config_get_option "$1" ANSWER && list_find "$ANSWER" $4 $5; then
        # Then option allready ANSWERed in config

        # Find out if the option was 'y' or 'n'
        list_find "$ANSWER" $4 && key=y
        list_find "$ANSWER" $5 && key=n

        if [[ "$key" ]]; then
            echo -e "[[ ${QUERY_COLOR}$2${DEFAULT} -> ${QUERY_COLOR}$key${DEFAULT} ]]"
            return 0
        fi
    fi

    local last_answer default=$3
    config_get_last_option "$1" last_answer
    # Find out if the option was 'y' or 'n'
    list_find "$last_answer" $4 && default=y
    list_find "$last_answer" $5 && default=n
  
    if query "$2" "$default"; then
        list_add ANSWER $4
    else
        list_add ANSWER $5
    fi

    config_set_option "$1" "$ANSWER"
    return 0
}


#---------------------------------------------------------------------
## @param config file variable, return variable
## @param question
## @param default answer
##
## @return 0 in all cases
##
## Asks user for string, with default answer and timeout (like query)
## Return variable is also marked as persistent
##
#---------------------------------------------------------------------
function real_config_query_string () {
    local ANSWER
    
    if config_get_option "$1" ANSWER; then
        # option allready answered in config
        echo -e "[[ ${QUERY_COLOR}$2${DEFAULT} -> '${QUERY_COLOR}$ANSWER${DEFAULT}' ]]"
    else
        local default
        config_get_last_option "$1" default
        [[ $default ]] || default="$3"
        query_string ANSWER "$2" "$default"
        config_set_option "$1" "$ANSWER"
    fi
    return 0
}


#---------------------------------------------------------------------
## @param config file variable, return variable
## @param question
## @param elements, ...
##
## @return 0 in all cases
##
## Asks user for string, with numbered possibilities listed
## Return variable is also marked as persistent
##
#---------------------------------------------------------------------
function real_config_query_list () {
  local ANSWER
  local VARIABLE="$1"
  local QUESTION="$2"
  shift
  shift

  if config_get_option "$VARIABLE" ANSWER; then
    # option allready ANSWERed in config
    (
        for foo in "$@"; do
            [ "$foo" == "$ANSWER" ] && exit 0
        done
        echo "!!!! WARNING !!!!"
        echo "!!!! stored option '$ANSWER' in config is not in list of provided options !!!!"
        echo "!!!! WARNING !!!!"
    )
    echo -e "[[ ${QUERY_COLOR}${QUESTION}${DEFAULT} -> '${QUERY_COLOR}$ANSWER${DEFAULT}' ]]"
  else
    # if there was an answer before, find it
    local default default_num=0 foo
    config_get_last_option "$VARIABLE" default
    [[ $default ]] || default="$1"
    let i=0
    for foo in "$@"; do
      if [[ "$foo" == "$default" ]] ; then
        default_num=$i
        break
      fi
      let i++
    done

    # we have to ask user
    message "$QUESTION"
    select_list ANSWER ${default_num} "$@"
    config_set_option "$VARIABLE" "$ANSWER"
  fi

  eval $VARIABLE=\"$ANSWER\"
  return 0
}

#---------------------------------------------------------------------
## Output a list of source numbers associated with the current spell.
## This is the number portion of SOURCE[[:digit:]], eg '', "2", "3", etc.
## A prefix may be given and it will be prepended to each result value.
#---------------------------------------------------------------------
function real_get_source_nums() {
  local foo
  compgen -v SOURCE |
  grep '^SOURCE[[:digit:]]*$'|sort|uniq|
  while read foo; do echo "${1}${foo/SOURCE/}"; done
}

function get_spell_files() {
  for src in $(real_get_source_nums SOURCE); do
    echo ${!src}
  done
}


#---------------------------------------------------------------------
## misc_is_function <function name>
## @param function name
## @return 0 if argument is a valid function
## @return 1 otherwise
## Returns true if input argument is the name of an
## existing function, false otherwise.
##
#---------------------------------------------------------------------
function misc_is_function()  {
  local  FUNCTION_NAME=$1
  [  "`type -t $FUNCTION_NAME`"  =  "function"  ]
}


#---------------------------------------------------------------------
## Remove files listed and any directories which become empty after
## subsequent file removal.
##
## @param Filename to read as input, a pipe may not be used.
## @param Base directory to remove directories up to (usually $INSTALL_ROOT)
#---------------------------------------------------------------------
function remove_files_and_dirs() {
    cat $1 | while read file; do
      test -d $file || rm -f $file
    done

    # remove possibly empty directories, rmdir WILL have error output
    # because some directories wont be empty for one of many reasons,
    # this is OKAY
    cat $1|dirnames|while read dir; do
      until [[ $dir == ${2:-/} ]] ; do
        rmdir $dir &>/dev/null || break
        dir=$(dirname $dir)
      done
    done
}

#---------------------------------------------------------------------
## Safely creates $TMP_DIR and exports the variable so we can use it
## even in subprocesses called through make.
## @param name of the script needing the tmp dir 
#---------------------------------------------------------------------
function mk_tmp_dirs() {
  debug "$FUNCNAME" "Making tmp dirs for $$"
  local SCRIPT_NAME=$1 STATS tmp
  SCRIPT_NAME=${SCRIPT_NAME:-misc}

  local BASE_DIR=${2:-/tmp/sorcery}
  local SCRIPT_DIR=$BASE_DIR/$SCRIPT_NAME
  local FULL_DIR=$SCRIPT_DIR/$$



  tmp=$(ls -ld $BASE_DIR 2>/dev/null|awk '{printf "%s:%s:%s\n" ,$1,$3,$4; }')
  if [[ "$tmp" != "drwxr-xr-x:root:root" ]] ; then
    test -d $BASE_DIR &&
    message "$BASE_DIR has unknown permissions and ownership, replacing it" 2>/dev/null
    rm -rf $BASE_DIR
    install -d -o root -g root -m 755 "$BASE_DIR" &&
    chmod a-s "$BASE_DIR" # work around installer bug where /tmp is suid/sgid
  fi || mk_tmp_dir_error $BASE_DIR

  # double check
  tmp=$(ls -ld $BASE_DIR 2>/dev/null|awk '{printf "%s:%s:%s\n" ,$1,$3,$4; }')
  if [[ "$tmp" != "drwxr-xr-x:root:root" ]] ; then
    mk_tmp_dir_error $BASE_DIR
  fi


  tmp=$(ls -ld $SCRIPT_DIR 2>/dev/null|awk '{printf "%s:%s:%s\n" ,$1,$3,$4; }')
  if [[ "$tmp" != "drwxr-xr-x:root:root" ]] ; then
    rm -rf $SCRIPT_DIR
    install -d -o root -g root -m 755 "$SCRIPT_DIR"
  fi || mk_tmp_dir_error $SCRIPT_DIR

  # double check
  tmp=$(ls -ld $SCRIPT_DIR|awk '{printf "%s:%s:%s\n" ,$1,$3,$4; }')
  if [[ "$tmp" != "drwxr-xr-x:root:root" ]] ; then
    mk_tmp_dir_error $SCRIPT_DIR
  fi


  if [ -e $FULL_DIR ] ; then
    echo "Looks like you had an old $SCRIPT_NAME on PID $$. Cleaning it out..." 2>/dev/null
    rm -rf $FULL_DIR
  fi

  install -d -o root -g root -m 755 "$FULL_DIR" || mk_tmp_dir_error $FULL_DIR
  tmp=$(ls -ld $SCRIPT_DIR|awk '{printf "%s:%s:%s\n" ,$1,$3,$4; }')
  if [[ "$tmp" != "drwxr-xr-x:root:root" ]] ; then
    mk_tmp_dir_error $FULL_DIR
  fi

  # in order for TMP_DIR to make it through make and into pass_three/four
  # we must export
  export TMP_DIR=$FULL_DIR
}

# emergency exit function if we fail to make a TMP_DIR
function mk_tmp_dir_error() {
    message "Failed to make temporary dir, this" \
            "might be due to a security attack,\nplease" \
            "check the permissions of $1. Bailing out..." 2>/dev/null
    exit 1
}

#---------------------------------------------------------------------
##
## 'which' is not in basesystem, heres our own simple version of it
## it should be just as fast as the real one.
##
## @param executable to look for
## @param variable to return path in (pass by reference)
##
## Marches through $PATH looking for the executable specified
##
#---------------------------------------------------------------------
function smgl_which() {
  local target=$1 location
  local BREAK
  function smgl_which_sub() {
    if test -x "$1/$target" ; then
      echo "$1/$target"
      BREAK=yes
    fi
  }
	if [ -z "$1" -o -z "$2" ]; then
		echo "smgl_which: Not enough arguments"
		return 1
	fi
  location=$(iterate smgl_which_sub : "$PATH")
  if [[ "$location" ]]  ; then
    eval "$2=$location"
  else
    echo "which: no $target in ($PATH)"
    return 1
  fi
}

#---------------------------------------------------------------------
##
## Finds the make command and complains if its missing (shouldnt ever
## happen)
##
## @param variable to return path in (pass by reference)
##
#---------------------------------------------------------------------
function find_make() {
  local ___REAL_MAKE
  smgl_which make ___REAL_MAKE
  rc=$?
  if [[ $rc != 0 ]] ; then
    message "Cannot find make command!!"
    message "Bailing out"
    return $rc
  fi
  eval "$1=$___REAL_MAKE"
}


#---------------------------------------------------------------------
## @param tmp_dir, should begin with /tmp/sorcery
#---------------------------------------------------------------------
function cleanup_tmp_dir() {
  local TMP_DIR=$1
  if  [  ${TMP_DIR:0:13} ==  "/tmp/sorcery/"  ];  then
    rm -rf $TMP_DIR
  else
    message "Cowardly refusing to remove $TMP_DIR, this may be a bug," \
            "please alert the sorcery team."
  fi
}

#---------------------------------------------------------------------
## @param Variable to check if its a known sorcery variable
## @return Success if its a sorcery variable, false if not
##
## This function will probably need to be updated as sorcery variables
## come in and out of flux, the variables listed are roughly defined as
## things defined at the point in which a spell file is run, and when it
## is not run in a seperate subshell, note that the build phase of cast
## is seperate from the frontend, so a spell could technically modify
## something like "SPELLS" and not have a major problem. However the
## philosophy is to not discriminate about what sorcery variables
## are technically okay to use in what files and ones that arent,
## instead we'll treat all usages of sorcery variables equally.  The rules
## are of course subject to change without warning and its just easier
## to be consistent, also theres a good chance that if a variable is safe
## in some circumstances and not others, and one uses it where its safe,
## someone will forget and start using it in places that arent safe...
#---------------------------------------------------------------------
function is_sorcery_var() {
  local vars="TOP_LEVEL DISPLAY PATH TMP_DIR SAFE_CAST \
  FAILED_LIST SUCCESS_LIST SPELL SPELLS spells MAKEFILE DEPS_ONLY \
  CAST_PASS download_log IW_LOG OPTS SOLO QUIET INSTALL_QUEUE \
  OVERRIDE_CFLAGS OVERRIDE_CXXFLAGS OVERRIDE_LDFLAGS NO_OPTIMIZATION_FLAGS \
  DOT_PROGRESS VOYEUR_OVERRIDE RECONFIGURE RECAST_DOWN COMPILE RECAST_UP \
  FORCE_DOWNLOAD SOURCE_CACHE SILENT FIX DEBUG SEPARATE BASE_URL \
  CAST_HASH BACK_CAST_HASH CANNOT_CAST_HASH uncommitted_hash NEW_DEPENDS \
  spell_depends DEPENDS_CONFIG UP_DEPENDS SPELL_CONFIG GRIMOIRE_DIR \
  SCRIPT_DIRECTORY SECTION_DIRECTORY GRIMOIRE SPELL_DIRECTORY SECTION \
  BUILD_API VOYEUR_STDOUT VOYEUR_STDERR C_LOG C_FIFO INSTALL_ROOT HOST \
  BUILD INST_LOG MD5_LOG INSTALLWATCHFILE CAST_EXIT_STATUS"

  # the \\< \\> matches word boundaries
  echo $vars|grep -q "\\<$1\\>"
}


#---------------------------------------------------------------------
## @param Variable name used in complaint
## @stdout Complain vehemently that a variable name is used by sorcery
## and the user should file a bug because a spell is using the variable
#---------------------------------------------------------------------
function complain_sorcery_var() {
  message "${PROBLEM_COLOR}WARNING: ATTEMPTING TO USE" \
          "${DEFAULT_COLOR}${SPELL_COLOR}\"$1\"" \
           "${DEFAULT_COLOR}${PROBLEM_COLOR}AS A SPELL VARIABLE."
  message "THIS VARIABLE IS A USED BY SORCERY, THIS IS PROBABLY" \
          "A SPELL BUG\nAND SORCERY MAY BEHAVE IN UNDEFINED WAYS IF" \
          "YOU CONTINUE."
  message "\n\nPLEASE REPORT A BUG IMMEDIATLY!!${DEFAULT_COLOR}\n\n"
}

#-------------------------------------------------------------------
## @param (optional) Architecture to use
## Sets the SPECFILE glocal variable and the SMGL_COMPAT_ARCHS global array
## SPECFILE contains the compiler and other arch specifications
## SMGL_COMPAT_ARCHS is an array that holds architectures which are compatible 
## with the desired architecture. The desired architecture is determined as 
## follows:
## 1) If function is gien an argument, the argument is used, or
## 2) If cross-install is on, the TARGET architecture is used, or
## 3) The local ARCHITECTURE is used
##
## The least specific arch is in SMGL_COMPAT_ARCHS[0], 
## SMGL_COMPAT_ARCHS[1] is more specific, et cetera. For example:
## desired architecture="athlon-xp" might result in:
## SPECFILE=/usr/share/archspecs/ia32/amd/athlon-xp
## SMGL_COMPAT_ARCHS=("ia32" "amd" "athlon-xp")
##
## ARCHITECTURE is also modified to be an array, the reverse of 
## SMGL_COMPAT_ARCHS. The result is an array from most specific arch to least
## specific. $ARCHITECTURE does not change meaning since $A == ${A[0]}.
#-------------------------------------------------------------------
function set_architecture() {

  $STD_DEBUG
  local specdir
  local i j
  unset SPECFILE

  # If given an argument, treat as the architecture to use
  local arch=${1}
  
  # If no arch is specified, see if this is a cross-install, if so, set arch to 
  # the target arch
  if  [[ ! $arch ]] &&
      [[ $CROSS_INSTALL == on ]] &&
      [[ $TARGET ]]
  then
    debug "libmisc" "set_architecture: using cross-install's target arch"
    arch=${arch:-$TARGET}
  fi
  
  # If no arch given and this isn't a cross-install, then default to the ARCHITECTURE var
  if ! [[ $arch ]] ; then
    arch=$ARCHITECTURE
  fi
  
  # Find the specfile to use
  local find_compat=0
  find --version|grep -q 'version 4\.1\(\.\|$\)' && find_compat=1
  for specdir in ${ARCH_SPECS[@]} ; do
    if [[ $find_compat == 1 ]] ; then
      # older find still exists run in slower compatibility mode
      SPECFILE=$(find ${specdir} -perm -400 -type f -name "$arch" -print 2>/dev/null)
    else
      # -L so symlinks work, -type f so it won't match dirs, -quit so it
      # stops searching when it finds the answer
      SPECFILE=$(find -L ${specdir} -perm -400 -type f -name "$arch" -print -quit 2>/dev/null)
    fi
    [ $SPECFILE ] && break
  done
  if [[ ! $SPECFILE ]] ; then 
    message "${PROBLEM_COLOR}Cannot find arch spec for $arch!"
    return 1
  fi
  debug "libmisc" "set_architecture: SPECFILE=$SPECFILE"
  
  # turn the path into an array, but remove $specdir from the start first
  unset SMGL_COMPAT_ARCHS 
  explode "${SPECFILE#$specdir/}" '/' SMGL_COMPAT_ARCHS

    unset ARCHITECTURE
    # Reverse the array so that the most specific arch is first
    j=0
    for(( i=${#SMGL_COMPAT_ARCHS[@]}-1; i>=0; i--)) ; do
      ARCHITECTURE[j++]=${SMGL_COMPAT_ARCHS[i]}
    done
    
    source "$SPECFILE"
}

#---------------------------------------------------------------------
## @param Function body
## @param Function name
## @param (optional) Function name
## @param ...
## Creates functions with identical bodies. It is useful if you need 
## to override a bunch of functions which have already been defined.
#--------------------------------------------------------------------- 
function define_functions() {
  local funcName
  local funcContent="$1"
  shift
  for funcName in $* ; do
    debug "libmisc" "define_functions - redefining $funcName"
    eval "function $funcName () { \
      $funcContent \
    }"
  done
}

#---------------------------------------------------------------------
## Like uniq, but doesnt require a sorted list, implemented in awk.
#---------------------------------------------------------------------
function awkuniq() {
  awk '{ if ( seen[$0] != 1) { seen[$0]=1;print $0;}}'
}

# Standard debug line:
# file "function@line" "all" "args"
STD_DEBUG='eval local _stddbg_file=${BASH_SOURCE[0]} ; 
  _stddbg_file=${_stddbg_file##*/};
  debug "${_stddbg_file}" "${FUNCNAME[0]}@$LINENO" "$@"'


#---------------------------------------------------------------------
## @License
##
## This software 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 2 of the License, or
## (at your option) any later version.
##
## This software 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 software; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
##
#---------------------------------------------------------------------
