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 }