I would like to propse a new versionator.eclass for consideration (attached).

This version, I believe, is more readable and maintainable then the one 
currently in portage. It also uses a lot less code and has the bonus of being 
pure sh.

It has not been tested in any ebuilds, but it does pass the self test from 
portage as it stands. No doubt the nit pickers can invent new tests where 
this one fails and the current one does not - I shall address this when they 
do.

Comments are welcome.

Thanks

Roy
# Copyright 2007 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

# sh version of versionator.eclass

# get_last_version_component_index implementation is not implemented because
# 1) The name encourages the use of bash arrarys
# 2) User could do $(($(get_version_component_count) - 1)) to achieve the
#    same result

_get_all_version_components() {
        local dosep=$1 ver=${2:-${PV}} x= last= this=

        while [ -n "${ver}" ]; do
                x=$(printf "%c" "${ver}")
                case "${x}" in
                        [0-9]) this="digit";;
                        -|.|_) this="sep";;
                        *)     this="alpha";;
                esac
                if ${dosep} || [ "${this}" != "sep" ]; then
                        [ -n "${last}" -a "${this}" != "${last}" ] && printf " "
                        printf "%c" "${x}"
                fi
                last=${this}
                ver=${ver#${x}}
        done
}

# Split up a version string into its component parts. If no parameter is
# supplied, defaults to $PV.
#     0.8.3       ->  0 . 8 . 3
#     7c          ->  7 c
#     3.0_p2      ->  3 . 0 _ p2
#     20040905    ->  20040905
#     3.0c-r1     ->  3 . 0 c - r1
get_all_version_components() {
        _get_all_version_components true "$@"
}

# Get the important version components, excluding '.', '-' and '_'. Defaults to
# $PV if no parameter is supplied.
#     0.8.3       ->  0 8 3
#     7c          ->  7 c
#     3.0_p2      ->  3 0 p2
#     20040905    ->  20040905
#     3.0c-r1     ->  3 0 c r1
get_version_components() {
        _get_all_version_components false "$@"
}

# Get the major version of a value. Defaults to $PV if no parameter is supplied.
#     0.8.3       ->  0
#     7c          ->  7
#     3.0_p2      ->  3
#     20040905    ->  20040905
#     3.0c-r1     ->  3
get_major_version() {
        set -- $(get_version_components "$@")
        printf "%s" "$1"
}

# Get everything after the major version and its separator (if present) of a
# value. Defaults to $PV if no parameter is supplied.
#     0.8.3       ->  8.3
#     7c          ->  c
#     3.0_p2      ->  0_p2
#     20040905    ->  (empty string)
#     3.0c-r1     ->  0c-r1
get_after_major_version() {
        printf "%s" "[EMAIL PROTECTED](get_major_version "$@")}"
}

_replace_version_separator_n() {
        local n=$1 sep=$2 i=0

        set -- $(get_all_version_components "$3")
        while [ -n "$1" ]; do
                case "$1" in
                        -|.|_)
                                i=$((${i} + 1))
                                if [ "${i}" = "${n}" ]; then
                                        printf "%s" "${sep}"
                                else
                                        printf "%s" "$1"
                                fi
                                ;;
                        *)
                                printf "%s" "$1"
                                ;;
                esac
                shift
        done
}

_replace_version_separator_a() {
        local n=$1 sep=$2 i=0

        set -- $(get_all_version_components "$3")
        while [ -n "$1" ]; do
                if [ "${n}" = "$1" ]; then
                        printf "%s" "${sep}"
                        n=
                else
                        printf "%s" "$1"
                fi
                shift
        done
}

# Replace the $1th separator with $2 in $3 (defaults to $PV if $3 is not
# supplied). If there are fewer than $1 separators, don't change anything.
#     1 '_' 1.2.3       -> 1_2.3
#     2 '_' 1.2.3       -> 1.2_3
#     1 '_' 1b-2.3      -> 1b_2.3
# Rather than being a number, $1 can be a separator character such as '-', '.'
# or '_'. In this case, the first separator of this kind is selected.
replace_version_separator() {
        case "$1" in
                [0-9]*) _replace_version_separator_n "$@";;
                *)      _replace_version_separator_a "$@";;
        esac
}

# Replace all version separators in $2 (defaults to $PV) with $1.
#     '_' 1b.2.3        -> 1b_2_3
replace_all_version_separators() {
        local sep=$1

        set -- $(get_all_version_components "$2")
        while [ -n "$1" ]; do
                case "$1" in
                        -|.|_) printf "%s" "${sep}";;
                        *)     printf "%s" "$1";;
                esac
                shift
        done
}

# Delete the $1th separator in $2 (defaults to $PV if $2 is not supplied). If
# there are fewer than $1 separators, don't change anything.
#     1 1.2.3       -> 12.3
#     2 1.2.3       -> 1.23
#     1 1b-2.3      -> 1b2.3
# Rather than being a number, $1 can be a separator character such as '-', '.'
# or '_'. In this case, the first separator of this kind is deleted.
delete_version_separator() {
        replace_version_separator "$1" "" "$2"
}

# Delete all version separators in $1 (defaults to $PV).
#     1b.2.3        -> 1b23
delete_all_version_separators() {
        replace_all_version_separators "" "$@"
}

# How many version components are there in $1 (defaults to $PV)?
#     1.0.1       ->  3
#     3.0c-r1     ->  4
get_version_component_count() {
        set -- $(get_version_components "$@")
        printf "%s" "$#"
}

# Get a particular component or range of components from the version. If no
# version parameter is supplied, defaults to $PV.
#    1      1.2.3       -> 1
#    1-2    1.2.3       -> 1.2
#    2-     1.2.3       -> 2.3
get_version_component_range() {
        [ -z "$1" ] && return 1

        local range=$(get_all_version_components "$1")
        shift
        local vers=$(get_all_version_components "$@")

        set -- ${range}
        local one=$1 two=$2 three=$3

        set -- ${vers}
        local i=1
        while [ ${i} -lt ${one} ]; do
                shift; shift
                i=$((${i} + 1))
        done

        printf "%s" "$1"
        [ "${two}" != "-" ] && return
        shift

        [ -z "${three}" ] && three=$(get_version_component_count "${vers}")
        while [ ${i} -lt ${three} ]; do
                printf "%s" "$1$2"
                shift; shift
                i=$((${i} + 1))
        done
}

_version_getn() {
        local v=$1
        case "$1" in
                0*)    v=${1##*0}; v=${v:-0};;
                -|.|_) v=0;;
        esac
        printf "%s" "${v}"
}

_version_is_prefix() {
        case "$1" in
                alpha|beta|pre|rc) return 0;;
        esac
        return 1
}

# Takes two parameters (a, b) which are versions. If a is an earlier version
# than b, returns 1. If a is identical to b, return 2. If b is later than a,
# return 3. You probably want version_is_at_least rather than this function.
# May not be very reliable. Test carefully before using this.
version_compare() {
        # Don't beat around the bush
        [ "$1" = "$2" ] && return 2

        local ver1=$(get_all_version_components "$1") ver11= ver11n=
        local ver2=$(get_all_version_components "$2") ver21= ver21n=

        while [ -n "${ver1}" -o -n "${ver2}" ]; do
                # Grab the components and trim leading 0's
                set -- ${ver1}
                ver11=$(_version_getn "$@")
                shift
                ver1="$@"
                set -- ${ver2}
                ver21=$(_version_getn "$@")
                shift
                ver2="$@"

                if _version_is_prefix "${ver11}"; then
                        _version_is_prefix "${ver21}" || return 1
                else
                        _version_is_prefix "${ver21}" && return 3
                fi

                [ -z "${ver11}" ] && ver11=0
                [ -z "${ver21}" ] && ver21=0

                [ "${ver11}" = "${ver21}" ] && continue

                case "${ver11}" in
                        [0-9]*) ver11n=true;;
                        *)      ver11n=false;;
                esac

                case "${ver21}" in
                        [0-9]*) ver21n=true;;
                        *)      ver21n=false;;
                esac

                if ${ver11n} && ${ver21n}; then
                        # Both are numbers
                        [ "${ver11}" -lt "${ver21}" ] && return 1 
                        [ "${ver11}" -gt "${ver21}" ] && return 3
                fi

                # Either is not a number, so lexical comparison
                [ "${ver11}" "<" "${ver21}" ] && return 1 
                [ "${ver11}" ">" "${ver21}" ] && return 3
        done

        # All equal then
        return 2 
}

# Is $2 (defaults to $PVR) at least version $1? Intended for use in eclasses
# only. May not be reliable, be sure to do very careful testing before actually
# using this. Prod ciaranm if you find something it can't handle.
version_is_at_least() {
        version_compare "$1" "${2:-${PVR}}"
        case $? in
                1|2) return 0;;
                3)   return 1;;
                *)   die "versionator compare bug";;
        esac
}

# Returns its parameters sorted, highest version last.
version_sort() {
        local sorted= left="$@" item=

        while [ -n "${left}" ]; do
                set -- ${left}
                item=$1
                shift
                left="$@"

                set -- ${sorted}
                sorted=
                local inserted=false
                while [ -n "$1" ]; do
                        version_compare "${item}" "$1"
                        if [ "$?" = "1" ]; then
                                sorted="${sorted}${sorted:+ }${item} $*"
                                continue 2
                        fi
                        sorted="${sorted}${sorted:+ }$1"
                        shift
                done

                sorted="${sorted}${sorted:+ }${item}"
        done

        printf "%s" "${sorted}"
}

__versionator__test_version_compare() {
        local lt=1 eq=2 gt=3 p= q=

        __versionator__test_version_compare_t() {
                version_compare "$1" "$3"
                local r=$?
                [ "${r}" != "$2" ] && echo "FAIL: [EMAIL PROTECTED] (got ${r} 
exp $2)"
        }

        echo "
                0             $lt 1
                1             $lt 2
                2             $gt 1
                2             $eq 2
                0             $eq 0
                10            $lt 20
                68            $eq 068
                068           $gt 67
                068           $lt 69

                1.0           $lt 2.0
                2.0           $eq 2.0
                2.0           $gt 1.0

                1.0           $gt 0.0
                0.0           $eq 0.0
                0.0           $lt 1.0

                0.1           $lt 0.2
                0.2           $eq 0.2
                0.3           $gt 0.2

                1.2           $lt 2.1
                2.1           $gt 1.2

                1.2.3         $lt 1.2.4
                1.2.4         $gt 1.2.3

                1.2.0         $eq 1.2
                1.2.1         $gt 1.2
                1.2           $lt 1.2.1

                1.2b          $eq 1.2b
                1.2b          $lt 1.2c
                1.2b          $gt 1.2a
                1.2b          $gt 1.2
                1.2           $lt 1.2a

                1.3           $gt 1.2a
                1.3           $lt 1.3a

                1.0_alpha7    $lt 1.0_beta7
                1.0_beta      $lt 1.0_pre
                1.0_pre5      $lt 1.0_rc2
                1.0_rc2       $lt 1.0

                1.0_p1        $gt 1.0
                1.0_p1-r1     $gt 1.0_p1

                1.0_alpha6-r1 $gt 1.0_alpha6
                1.0_beta6-r1  $gt 1.0_alpha6-r2

                1.0_pre1      $lt 1.0-p1

                1.0p          $gt 1.0_p1
                1.0r          $gt 1.0-r1
                1.6.15        $gt 1.6.10-r2
                1.6.10-r2     $lt 1.6.15

        " | while read a b c ; do
                [ -z "${a}${b}${c}" ] && continue;
                __versionator__test_version_compare_t "${a}" "${b}" "${c}"
        done


        for q in "alpha beta pre rc=${lt};${gt}" "p r=${gt};${lt}" ; do
                for p in ${q%%=*} ; do
                        local c=${q##*=}
                        local alt=${c%%;*} agt=${c##*;}
                        __versionator__test_version_compare_t "1.0" $agt 
"1.0_${p}"
                        __versionator__test_version_compare_t "1.0" $agt 
"1.0_${p}1"
                        __versionator__test_version_compare_t "1.0" $agt 
"1.0_${p}068"

                        __versionator__test_version_compare_t "2.0_${p}"    
$alt "2.0"
                        __versionator__test_version_compare_t "2.0_${p}1"   
$alt "2.0"
                        __versionator__test_version_compare_t "2.0_${p}068" 
$alt "2.0"

                        __versionator__test_version_compare_t "1.0_${p}"  $eq 
"1.0_${p}"
                        __versionator__test_version_compare_t "0.0_${p}"  $lt 
"0.0_${p}1"
                        __versionator__test_version_compare_t "666_${p}3" $gt 
"666_${p}"

                        __versionator__test_version_compare_t "1_${p}7"  $lt 
"1_${p}8"
                        __versionator__test_version_compare_t "1_${p}7"  $eq 
"1_${p}7"
                        __versionator__test_version_compare_t "1_${p}7"  $gt 
"1_${p}6"
                        __versionator__test_version_compare_t "1_${p}09" $eq 
"1_${p}9"
                done
        done

        for p in "-r" "_p" ; do
                __versionator__test_version_compare_t "7.2${p}1" $lt "7.2${p}2"
                __versionator__test_version_compare_t "7.2${p}2" $gt "7.2${p}1"
                __versionator__test_version_compare_t "7.2${p}3" $gt "7.2${p}2"
                __versionator__test_version_compare_t "7.2${p}2" $lt "7.2${p}3"
        done
}

Reply via email to