#!/bin/bash
# *******************************************************************
# **  @file eisman_tree.sh - resolve requirement tree
# **
# **  Creation:     2013-01-13 dv
# **  Last update:  $Id: eisman_tree.sh 42826 2016-08-13 19:44:23Z hbfl $
# **
# **  Copyright (c) 2013-2016 the eisfair team, team(at)eisfair(dot)org
# **
# **  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 2 of the License, or
# **  (at your option) any later version.
# *******************************************************************

. /var/install/include/eislib
. /var/install/include/eismanlib

# debug file
# DEBUG_FILE="/tmp/eisman_tree.log"

# global search mode
SEARCH_MODE="stable"
SEARCH_URL=""

# query filters
STATE_FILTER="s"
URL_FILTER=".*"

# global tree index
PACKIDX="0"

# constant for unlimited number of results
UNLIMITED="9999"

# setup global database path for testing
if [ -n "${EISMAN_TEST_DB}" ]
then
    EISMAN_PATH="${EISMAN_TEST_DB}"
fi

# setup global binary path for testing
if [ -n "${EISMAN_TEST_BIN}" ]
then
    EISMAN_BIN_PATH="${EISMAN_TEST_BIN}"
fi


# *******************************************************************
# **  print current package list to debug file
# **  @param  $1  optional text
# **  @return n/a
# *******************************************************************

print_package_list()
{
    {
        echo "   -----------------------------------------------------"
        echo "    $1 "
        echo "   -----------------------------------------------------"
        # output results to stdout
        # list found packages in reverse order
        local idx=$PACKIDX
        echo "   idx | level | version  | status     | name           "
        echo "   ----+-------+----------+------------+----------------"
        while [ "$idx" -gt 0 ]
        do
            eval local name='$PACKAGE_'$idx'_NAME'
            eval local version='$PACKAGE_'$idx'_VERSION'
            eval local status='$PACKAGE_'$idx'_STATUS'
            eval local level='$PACKAGE_'$idx'_LEVEL'

            printf "   %-3s | %-5s | %-8s | %-10s | %-17s\n" \
                "$idx" "$level" "$version" "$status" "$name"
            idx=$(($idx - 1))
        done
        echo "   ------------------------------------------------------"
    } >> ${DEBUG_FILE}
}


# *******************************************************************
# **  use grep to perform a query on the package.db file. Note that
# **  the global variable $STATE_FILTER is used to decide if the search
# **  covers only stable or all packages.
# **  @param  $1  package
# **  @param  $2  version (can be an empty string!)
# **  @param  $3  the info field to be returned
# **  @param  $4  max number of results
# **  @return (on stdout) data found in packages.db meeting the
# **          search criteria
# *******************************************************************

query_package_db()
{
    local package="$1"
    local version="$2"
    local field="$3"
    local maxresults="$4"
    local result
    local resline
    local idx="1"
    local header=$((${FIELDHEADER} + 1))

    if [ -z "$version" ]
    then
        version='.*'
    fi

    if [ "${maxresults}" -eq "${UNLIMITED}" ]
    then
        grep -E "^\[.*\]\[$package\]\[$version\]\[$STATE_FILTER\]\[$URL_FILTER\]${field} " \
             "${EISMAN_PATH}/packages.db" |
        cut  -d ']' -f 6                  |
        cut  -c "${header}-"
    else
        grep -E "^\[.*\]\[$package\]\[$version\]\[$STATE_FILTER\]\[$URL_FILTER\]${field} " \
             "${EISMAN_PATH}/packages.db" |
        cut  -d ']' -f 6                  |
        cut  -c "${header}-"              |
        head -n "${maxresults}"
    fi
}


# *******************************************************************
# **  use grep to perform a query on the outdated.db file.
# **  @param  $1  package
# **  @param  $2  field
# **  @return (on stdout) data found in outdated.db meeting the
# **          search criteria
# *******************************************************************

query_outdated_db()
{
    local package="$1"
    local field="$2"
    local header=$((${FIELDHEADER} + 1))

    grep -E "^\[outdated\]\[${package}\]\[.*\]\[.*\]\[.*\]${field} " \
        "${EISMAN_PATH}/outdated.db" |
    cut  -d ']' -f 6                 |
    cut  -c "${header}-"
}


# *******************************************************************
# **  add a package to the list stored in
# **     PACKAGE_%_NAME
# **     PACKAGE_%_VERSION
# **     PACKAGE_%_SPACE
# **     PACKAGE_%_STATUS
# **     PACKAGE_%_URL
# **  @param  $1  package
# **  @param  $2  version
# **  @return n/a
# *******************************************************************

add_package()
{
    local package="$1"
    local version="$2"
    local level="$3"

    local packspace=$(query_package_db "$package" "$version" 'space' '1')
    local packstatus=$(query_package_db "$package" "$version" 'status' '1')
    local packurl=$(query_package_db "$package" "$version" 'url' '1')

    if [ -z "$packspace" ]
    then
        packspace='1'
    fi

    PACKIDX=$(($PACKIDX + 1))
    eval 'PACKAGE_'$PACKIDX'_NAME'='$package'
    eval 'PACKAGE_'$PACKIDX'_VERSION'='$version'
    eval 'PACKAGE_'$PACKIDX'_SPACE'='$packspace'
    eval 'PACKAGE_'$PACKIDX'_STATUS'='$packstatus'
    eval 'PACKAGE_'$PACKIDX'_URL'='$packurl'
    eval 'PACKAGE_'$PACKIDX'_LEVEL'='$level'

    if [ -n "${DEBUG_FILE}" ]
    then
        print_package_list "Add package  $package ($version) $level"
    fi
}


# *******************************************************************
# **  move single package to top of the list and set new index
# **  @param  $1  index of entry to move to top
# **  @param  $2  new iteration level of the entry
# **  @return n/a
# *******************************************************************

single_package_to_top()
{
    local index="$1"
    local level="$2"
    local next
    local name
    local version
    local status
    local space
    local url

    if [ "${index}" -lt "${PACKIDX}" ]
    then
        eval name='$PACKAGE_'$index'_NAME'
        eval version='$PACKAGE_'$index'_VERSION'
        eval status='$PACKAGE_'$index'_STATUS'
        eval space='$PACKAGE_'$index'_SPACE'
        eval url='$PACKAGE_'$index'_URL'

        while [ ${index} -lt ${PACKIDX} ]
        do
            next=$(($index + 1))
            eval 'PACKAGE_'$index'_NAME'='$PACKAGE_'$next'_NAME'
            eval 'PACKAGE_'$index'_VERSION'='$PACKAGE_'$next'_VERSION'
            eval 'PACKAGE_'$index'_SPACE'='$PACKAGE_'$next'_SPACE'
            eval 'PACKAGE_'$index'_STATUS'='$PACKAGE_'$next'_STATUS'
            eval 'PACKAGE_'$index'_URL'='$PACKAGE_'$next'_URL'
            eval 'PACKAGE_'$index'_LEVEL'='$PACKAGE_'$next'_LEVEL'
            index=$next
        done

        eval 'PACKAGE_'$PACKIDX'_NAME'=$name
        eval 'PACKAGE_'$PACKIDX'_VERSION'=$version
        eval 'PACKAGE_'$PACKIDX'_SPACE'=$space
        eval 'PACKAGE_'$PACKIDX'_STATUS'=$status
        eval 'PACKAGE_'$PACKIDX'_URL'=$url
        eval 'PACKAGE_'$PACKIDX'_LEVEL'=$level
    fi

    if [ -n "${DEBUG_FILE}" ]
    then
        print_package_list "Single Package To Top  index=$1 level=$2"
    fi
}


# *******************************************************************
# **  remove single package from list
# **  @param  $1  index of entry to remove
# **  @return n/a
# *******************************************************************

remove_single_package()
{
    local index="$1"
    local next
    local name
    local version
    local status
    local space
    local url

    while [ ${index} -lt ${PACKIDX} ]
    do
        next=$(($index + 1))
        eval 'PACKAGE_'$index'_NAME'='$PACKAGE_'$next'_NAME'
        eval 'PACKAGE_'$index'_VERSION'='$PACKAGE_'$next'_VERSION'
        eval 'PACKAGE_'$index'_SPACE'='$PACKAGE_'$next'_SPACE'
        eval 'PACKAGE_'$index'_STATUS'='$PACKAGE_'$next'_STATUS'
        eval 'PACKAGE_'$index'_URL'='$PACKAGE_'$next'_URL'
        eval 'PACKAGE_'$index'_LEVEL'='$PACKAGE_'$next'_LEVEL'
        index=$next
    done

    PACKIDX=$((${PACKIDX} - 1))

    if [ -n "${DEBUG_FILE}" ]
    then
        print_package_list "  Remove Single Package  index=$1"
    fi
}


# *******************************************************************
# **  search the list of packages stored in
# **     PACKAGE_%_NAME
# **     PACKAGE_%_VERSION
# **  for a specific entry
# **  @param  $1  package
# **  @return (on stdout) index of entry or "" if not found
# *******************************************************************

find_package()
{
    local package="$1"
    local idx=1
    while [ "$idx" -le "$PACKIDX" ]
    do
        eval local name='$PACKAGE_'$idx'_NAME'
        if [ "$name" = "$package" ]
        then
            echo "$idx"
            return
        fi

        idx=$(($idx + 1))
    done
}


# *******************************************************************
# **  search for the best version of a package that is newer than
# **  a given version (newest update). In case of a requirement
# **  that can't be met by a already installed package, this is used
# **  to get the best version available.
# **  If -unstable switch has been specified the best version may have
# **  an unstable state, otherwise only stable packages are searched.
# **  @param  $1  package
# **  @param  $2  min. version (may be empty or 0.0.0 if not specified)
# **  @return (on stdout) best version found
# *******************************************************************

find_best_match()
{
    local package="$1"
    local version="$2"
    local resline=""
    local bestver=""
    local beststatus=""
    local result=$(query_package_db "$package" "" "(version|status)" "${UNLIMITED}")
    local state

    if [ -z $version ]
    then
        version="0.0.0"
    fi

    local IFS_BAK=$IFS
    IFS="
"

    local foundver=""
    local foundstatus=""
    local idx=0
    for resline in $result
    do
        if [ "${idx}" -eq 0 ]
        then
            foundver="${resline}"
            idx=1
        else
            foundstatus="${resline}"
            idx=0

            local res=$(diff_version ${foundver} ${version})
            if [ "${res}" = "new" ] || [ "${res}" = "installed" ]
            then
                if [ -z "${bestver}" ]
                then
                    # if version has not been set yet, take the first match
                    # even if it is unstable
                    bestver="${foundver}"
                    beststatus="${foundstatus}"
                else
                    if [ "${beststatus}" != "stable" ] && [ "${foundstatus}" = "stable" ]
                    then
                        # always prefer the stable package even if the unstable package is
                        # more recent
                        bestver="${foundver}"
                        beststatus="${foundstatus}"

                    elif [ "${beststatus}" != "stable" ] || [ "${foundstatus}" = "stable" ]
                    then
                        # only if the previous result is unstable or the newly found package is
                        # (also) stable, we proceed (never go from stable to unstable!):
                        # compare the version of both packages and take the one that is more recent
                        state=$(diff_version "${foundver}" $bestver)
                        if [ "$state" = "new" ]
                        then
                            bestver="${foundver}"
                            beststatus="${foundstatus}"
                        fi
                    fi
                fi
            fi
        fi
    done

    IFS=$IFS_BAK

    echo "$bestver"
}


# *******************************************************************
# **  given is a certain requirement of a package. Check if it is already
# **  met by an installed package. If not search for the best
# **  available version and add the found package to the list
# **  of needed packages (if not already there).
# **  If the package has been added, it also may have requirements
# **  that have to be considered. Therefore the routine calls
# **  "find_requirements" or "find_linked_packages" and therefore enters
# **  a new recursion loop.
# **  Note: if a requirement cannot be resolved, an error is written
# **  to stdout and the whole script is terminated with exit code 1
# **  @param  $1  required package
# **  @param  $2  required version
# **  @param  $2  current iteration level
# **  @return n/a
# *******************************************************************

check_and_add_requirement()
{
    local packname="${1}"
    local packversion="${2}"
    local level="${3}"

    local idx=$(find_package ${packname})
    if [ -z "$idx" ]
    then
        # package is new!
        # add package to list
        add_package       "${packname}" "${packversion}" "${level}"

        # find linked packages to this link (recursively)
        find_linked_packages "${packname}" "${packversion}" "$(($level + 1))"

        # find requirements (recursively)
        find_requirements "${packname}" "${packversion}" "$(($level + 1))"
    else
        # package already in the list, check if it's version is sufficient
        # for us. If not, remove the old package and (re)add the new one
        eval local listver='$PACKAGE_'$idx'_VERSION'

        local res=$(diff_version ${listver} ${packversion})
        if [ "${res}" = "old" ]
        then
            # listed package is old! This means we have to replace it with
            # the new one and have to deal with the new requirements.
            # This is a quite seldom and unusual situation and only happens, when
            # a stable package has to be replaced by an unstable one due to some
            # obscure compination of package requirements.
            # We only remove the single package and redo search for linked packages
            # and requirements. It may happen, that some requirements of the
            # original package remain in the list if the new version of the package
            # no longer requires them. But that is not really harmful! Therefore
            # we currently have to live with this behaviour

            # remove previous version from list
            remove_single_package "${idx}"

            # add package to list
            add_package       "${packname}" "${packversion}" "${level}"

            # find linked packages to this link (recursively)
            find_linked_packages "${packname}" "${packversion}" "$(($level + 1))"

            # find requirements (recursively)
            find_requirements "${packname}" "${packversion}" "$(($level + 1))"
        else
            # listed package is sufficient for us! We only have to move it
            # to the top of the list and redo the requirement search to bring
            # up all required packages too. But we have no need to search
            # for linked packages at this point, since they already are in
            # the list.

            # move package to the top of the list
            single_package_to_top "${idx}" "${level}"

            # find requirements (recursively)
            find_requirements "${packname}" "${packversion}" "$(($level + 1))"
        fi
    fi
}


# *******************************************************************
# **  get requirements of a package and check if they are already
# **  met by an installed package. If not search for the best
# **  available version and call "check_and_add_requirement"
# **  to add the new requirement to the list
# **  @param  $1  package
# **  @param  $2  version
# **  @return n/a
# *******************************************************************

find_requirements()
{
    local package="$1"
    local version="$2"
    local level="$3"
    local resline
    local result=$(query_package_db "$package" "$version" "require" "${UNLIMITED}")

    if [ -n "${DEBUG_FILE}" ]
    then
        {
            echo "${level} -------------------------------------------------------"
            echo "${level} BEGIN Find Requirements ${package} (${version})"
            echo "${level} -------------------------------------------------------"
        } >> ${DEBUG_FILE}
    fi

    local IFS_BAK=$IFS
    IFS="
"

    for resline in $result
    do
        local packname="${resline%% *}"
        local packver="${resline#* }"

        if [ -z "${packver}" -o "${packver}" = "${packname}" ]
        then
            packver="0.0.0"
        fi

        if [ -n "${DEBUG_FILE}" ]
        then
            {
                echo "${level} --> Found ${packname} (${packver})"
            } >> ${DEBUG_FILE}
        fi

        # special handling of kernel requirements, since it must be trans-
        # lated into it's installed variant
        if [ "${packname}" = "eiskernel" ]
        then
            packname=${packname}-$(uname -r |
                awk -F'-' '{ print tolower($NF) }')
        fi

        # check if package is outdated
        local outdated=$(query_outdated_db "${packname}" "substitude")
        if [ -n "${outdated}" ]
        then
            if [ "${outdated}" = "dummy" ]
            then
                outdated=$(query_outdated_db "${packname}" "hint")
                echo "error: package \"${packname}\" required from \"${package}\" is outdated!" >&2
                echo "error: ${outdated}" >&2
                exit 1
            else
                packname=$(echo ${outdated} | cut -d ' ' -f 1)
                packver=$(echo ${outdated} | cut -d ' ' -f 2)

                # check if version has been supplied
                if [ "${packname}" = "${packver}" ]
                then
                    version="0.0.0"
                fi
            fi
        fi

        # if packname is still valid...
        if [ -n "${packname}" ]
        then
            local state=$(${EISMAN_BIN_PATH}/eisman_check_version.sh $packname $packver)
            if [ "$state" = "new" -o "$state" = "not-installed" ]
            then
                local bestver=$(find_best_match $packname $packver)

                if [ -z "$bestver" ]
                then
                    echo "error: requirement $packname >= $packver not found!" >&2
                    exit 1
                fi

                # check if requirement is needed and recursively add the package
                # and all of it's requirements
                check_and_add_requirement "${packname}" "${bestver}" "${level}"
            fi
        fi
    done

    IFS=$IFS_BAK

    if [ -n "${DEBUG_FILE}" ]
    then
        {
            echo "${level} -------------------------------------------------------"
            echo "${level} END Find Requirements ${package} (${version})"
            echo "${level} -------------------------------------------------------"
            echo ""
        } >> ${DEBUG_FILE}
    fi
}


# *******************************************************************
# **  find all packages that are specified with the linked-package
# **  tag. This packages are added to the requirement list if
# **  they are installed on the machine and the specified version
# **  is newer than the installed on
# **  @param  $1  package
# **  @param  $2  version
# **  @param  $3  level
# **  @return n/a
# *******************************************************************

find_linked_packages()
{
    local package="$1"
    local version="$2"
    local level="$3"
    local resline

    local result=$(query_package_db "$package" "$version" "link" "${UNLIMITED}")

    if [ -n "${DEBUG_FILE}" ]
    then
        {
            echo "${level} -------------------------------------------------------"
            echo "${level} BEGIN Find Linked Packages ${package} (${version})"
            echo "${level} -------------------------------------------------------"
        } >> ${DEBUG_FILE}
    fi

    local FS_BAK=$IFS
    IFS="
"

    for resline in $result
    do
        local packname="${resline%% *}"
        local packver="${resline#* }"
        local state

        if [ -z "${packver}" -o "${packver}" = "${packname}" ]
        then
            packver="$version"
        fi

        if [ -n "${DEBUG_FILE}" ]
        then
            {
                echo "${level} --> Found ${packname} (${packver})"
            } >> ${DEBUG_FILE}
        fi

        # special handling of kernel requirements, since it must be trans-
        # lated into it's installed variant
        if [ "${packname}" = "eiskernel" ]
        then
            packname=${packname}-$(uname -r |
                awk -F'-' '{ print tolower($NF) }')
        fi

        state=$(${EISMAN_BIN_PATH}/eisman_check_version.sh $packname $packver)
        if [ "$state" = "new" -o "$state" = "old" ]
        then
            # check if package exists
            result=$(query_package_db "$packname" "$packver" 'version' '1')
            if [ -z $result ]
            then
                echo "error: link requirement ${packname} (${packver}) not found!" >&2
                exit 1
            fi

            # check if requirement is needed and recursively add the package
            # and all of it's requirements
            check_and_add_requirement "${packname}" "${packver}" "${level}"
        fi
    done

    IFS=$IFS_BAK

    if [ -n "${DEBUG_FILE}" ]
    then
        {
            echo "${level} -------------------------------------------------------"
            echo "${level} END Find Linked Packages ${package} (${version})"
            echo "${level} -------------------------------------------------------"
            echo ""
        } >> ${DEBUG_FILE}
    fi
}


# *******************************************************************
# **  do complete package processing (search for requirements).
# **  in case of an error, it is output to stderr and the script is
# **  terminated with exit code 1
# **  @param  $1  package
# **  @param  $2  version
# **  @return n/a
# *******************************************************************

process_root_package()
{
    local package="$1"
    local version="$2"
    local result

    if [ -n "${DEBUG_FILE}" ]
    then
        {
            echo "0 -------------------------------------------------------"
            echo "0 BEGIN Process Root Package ${package} (${version})"
            echo "0 -------------------------------------------------------"
        } >> ${DEBUG_FILE}
    fi

    # check if package exists
    result=$(query_package_db "${package}" "${version}" 'version' '1')
    if [ -z "$result" ]
    then
        echo "error: package ${package} not found!" >&2
        exit 1
    fi

    # add the root package
    add_package "${package}" "${version}" 0

    # when the root package has been found, any other required package
    # may be from any source. Therefore reset URL filter
    URL_FILTER=".*"

    # even if the global search mode tells us to prefer only stable
    # packages, any required package may also be unstable. Therefore
    # reset state filter
    STATE_FILTER=".*"

    # find linked packages to this package. Note that linked packages
    # normally state a requirement to the current package. Therefore
    # they must be searched before the package is added to the list
    find_linked_packages "${package}" "${version}" 1

    # search for requirements. From here on, no further URL or status
    # filtering is done.
    find_requirements "${package}" "${version}" 1

    if [ -n "${DEBUG_FILE}" ]
    then
        {
            echo "0 -------------------------------------------------------"
            echo "0 END Process Root Package ${package} (${version})"
            echo "0 -------------------------------------------------------"
        } >> ${DEBUG_FILE}
    fi
}


# *******************************************************************
# **  start package processing
# **  @param  $1  package
# **  @param  $2  version (optional)
# **  @return n/a
# *******************************************************************

process_single_package()
{
    local package=$1
    local version=$2

    # according to the global search mode, the first package must be
    # stable or may also be unstable. Therefore the state filter is
    # set accordingly
    if [ "${SEARCH_MODE}" = "stable" ]
    then
        STATE_FILTER="s"
    else
        STATE_FILTER=".*"
    fi

    # according to the global search URL, this first package must be
    # from a specific source or may be also from other sources. Therefore
    # the URL-filter is set accordingly
    if [ -n "${SEARCH_URL}" ]
    then
        URL_FILTER=$(echo "${SEARCH_URL}.*" | sed 's|\?|\\?|g')
    else
        URL_FILTER=".*"
    fi

    # check if outdated packages db is present
    if [ ! -f "${EISMAN_PATH}/outdated.db" ]
    then
        echo "error: file \"${EISMAN_PATH}/outdated.db\" missing!" >&2
        echo "       reinstall recent base update please." >&2
        exit 1
    fi

    # check if package is outdated. In case of a root package, we
    # issue an error and exit. In case of requirements we try to
    # use the substitude found in outdated db.
    local outdated=$(query_outdated_db "${package}" "substitude")
    if [ -n "${outdated}" ]
    then
        outdated=$(query_outdated_db "${package}" "hint")
        echo "error: package \"${package}\" is outdated!" >&2
        echo "error: ${outdated}" >&2
        exit 1
    fi

    # if version is not specified, search for best available package
    # if not found output error and exit script
    if [ -z "${version}" ]
    then
        version=$(find_best_match ${package} '0.0.0')
        if [ -z "${version}" ]
        then
            echo "error: package ${package} not found!" >&2
            exit 1
        fi
    fi

    # check if package is already in the tree (propably has been a
    # requirement found in a previous run of 'process_single_package')
    local idx=$(find_package ${package})
    if [ -z "$idx" ]
    then
        # process the needed package (root of requirement tree)
        process_root_package "${package}" "${version}"
    else
        # package already in the list, check if it's version is sufficient
        # for us. If not, remove the old package and (re)add the new one
        eval local listver='$PACKAGE_'$idx'_VERSION'
        local res=$(diff_version ${listver} ${version})
        if [ "${res}" = "old" ]
        then
            # remove previous version from list
            remove_single_package "${idx}"

            # process the needed package (root of requirement tree)
            process_root_package "${package}" "${version}"
        fi
    fi
}


# *******************************************************************
# **  main program
# *******************************************************************
main()
{
    if [ -n "${DEBUG_FILE}" ]
    then
        echo "" > ${DEBUG_FILE}
    fi

    # read option switches
    while [ "${1:0:2}" = "--" ]
    do
        case ${1} in
        --unstable)
            SEARCH_MODE="unstable"
            shift
            ;;
        --url)
            shift
            SEARCH_URL="${1}"
            shift
            ;;
        --url=*)
            SEARCH_URL="${1:6}"
            shift
            ;;
        *)
            echo "error: invalid switch \"$1\"!" >&2
            ;;
        esac
    done


    while [ "$#" -gt "0" ]
    do
        name="$(echo ${1} | cut -d '=' -f 1)"
        version="$(echo ${1} | cut -d '=' -f 2)"

        if [ "${version}" = "${name}" ]
        then
            # no version found
            version=""
        fi

        process_single_package \
            "${name}" \
            "${version}"

        shift
    done

    if [ -n "${DEBUG_FILE}" ]
    then
        print_package_list "Final result"
    fi

    # output results to stdout
    # list found packages in reverse order
    idx=$PACKIDX
    total="0"
    while [ "$idx" -gt 0 ]
    do
        eval name='$PACKAGE_'$idx'_NAME'
        eval version='$PACKAGE_'$idx'_VERSION'
        eval status='$PACKAGE_'$idx'_STATUS'
        eval space='$PACKAGE_'$idx'_SPACE'
        eval url='$PACKAGE_'$idx'_URL'
        eval level='$PACKAGE_'$idx'_LEVEL'

        total=$(($total + $space))

        printf "%-8s %-8s %-17s %s\n" "${version}" "${status}" "${name}" "${url%/*}"
        idx=$(($idx - 1))
    done
    echo "space = $total"
}

main "${@}"
