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
}