commit:     08d405f72e85125b64f5074e49714e759d85eaf6
Author:     Michał Górny <mgorny <AT> gentoo <DOT> org>
AuthorDate: Mon Feb 19 15:38:28 2018 +0000
Commit:     Michał Górny <mgorny <AT> gentoo <DOT> org>
CommitDate: Sun Mar 11 11:43:41 2018 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=08d405f7

Add EAPI 7 version comparison & manipulation functions

The function code is copied from eapi7-ver.eclass which has been written
by Ulrich Müller and me.

Bug: https://bugs.gentoo.org/482170

 bin/eapi.sh                                   |   4 +
 bin/eapi7-ver-funcs.sh                        | 191 ++++++++++++++++++++
 bin/isolated-functions.sh                     |   4 +
 pym/portage/tests/bin/test_eapi7_ver_funcs.py | 240 ++++++++++++++++++++++++++
 4 files changed, 439 insertions(+)

diff --git a/bin/eapi.sh b/bin/eapi.sh
index fa254485c..1d5ea802d 100644
--- a/bin/eapi.sh
+++ b/bin/eapi.sh
@@ -100,6 +100,10 @@ ___eapi_has_in_iuse() {
        [[ ! ${1-${EAPI-0}} =~ 
^(0|1|2|3|4|4-python|4-slot-abi|5|5-hdepend|5-progress)$ ]]
 }
 
+___eapi_has_version_functions() {
+       [[ ! ${1-${EAPI-0}} =~ ^(0|1|2|3|4|4-python|4-slot-abi|5|5-progress|6)$ 
]]
+}
+
 ___eapi_has_master_repositories() {
        [[ ${1-${EAPI-0}} =~ ^(5-progress)$ ]]
 }

diff --git a/bin/eapi7-ver-funcs.sh b/bin/eapi7-ver-funcs.sh
new file mode 100644
index 000000000..b4e98f4e7
--- /dev/null
+++ b/bin/eapi7-ver-funcs.sh
@@ -0,0 +1,191 @@
+#!/bin/bash
+# Copyright 1999-2018 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+__eapi7_ver_parse_range() {
+       local range=${1}
+       local max=${2}
+
+       [[ ${range} == [0-9]* ]] \
+               || die "${FUNCNAME}: range must start with a number"
+       start=${range%-*}
+       [[ ${range} == *-* ]] && end=${range#*-} || end=${start}
+       if [[ ${end} ]]; then
+               [[ ${start} -le ${end} ]] \
+                       || die "${FUNCNAME}: end of range must be >= start"
+               [[ ${end} -le ${max} ]] || end=${max}
+       else
+               end=${max}
+       fi
+}
+
+__eapi7_ver_split() {
+       local v=${1} LC_ALL=C
+
+       comp=()
+
+       # get separators and components
+       local s c
+       while [[ ${v} ]]; do
+               # cut the separator
+               s=${v%%[a-zA-Z0-9]*}
+               v=${v:${#s}}
+               # cut the next component; it can be either digits or letters
+               [[ ${v} == [0-9]* ]] && c=${v%%[^0-9]*} || c=${v%%[^a-zA-Z]*}
+               v=${v:${#c}}
+
+               comp+=( "${s}" "${c}" )
+       done
+}
+
+ver_cut() {
+       local range=${1}
+       local v=${2:-${PV}}
+       local start end
+       local -a comp
+
+       __eapi7_ver_split "${v}"
+       local max=$((${#comp[@]}/2))
+       __eapi7_ver_parse_range "${range}" "${max}"
+
+       local IFS=
+       if [[ ${start} -gt 0 ]]; then
+               start=$(( start*2 - 1 ))
+       fi
+       echo "${comp[*]:start:end*2-start}"
+}
+
+ver_rs() {
+       local v
+       (( ${#} & 1 )) && v=${@: -1} || v=${PV}
+       local start end i
+       local -a comp
+
+       __eapi7_ver_split "${v}"
+       local max=$((${#comp[@]}/2 - 1))
+
+       while [[ ${#} -ge 2 ]]; do
+               __eapi7_ver_parse_range "${1}" "${max}"
+               for (( i = start*2; i <= end*2; i+=2 )); do
+                       [[ ${i} -eq 0 && -z ${comp[i]} ]] && continue
+                       comp[i]=${2}
+               done
+               shift 2
+       done
+
+       local IFS=
+       echo "${comp[*]}"
+}
+
+__eapi7_ver_compare_int() {
+       local a=$1 b=$2 d=$(( ${#1}-${#2} ))
+
+       # Zero-pad to equal length if necessary.
+       if [[ ${d} -gt 0 ]]; then
+               printf -v b "%0${d}d%s" 0 "${b}"
+       elif [[ ${d} -lt 0 ]]; then
+               printf -v a "%0$(( -d ))d%s" 0 "${a}"
+       fi
+
+       [[ ${a} > ${b} ]] && return 3
+       [[ ${a} == "${b}" ]]
+}
+
+__eapi7_ver_compare() {
+       local va=${1} vb=${2} a an al as ar b bn bl bs br re LC_ALL=C
+
+       
re="^([0-9]+(\.[0-9]+)*)([a-z]?)((_(alpha|beta|pre|rc|p)[0-9]*)*)(-r[0-9]+)?$"
+
+       [[ ${va} =~ ${re} ]] || die "${FUNCNAME}: invalid version: ${va}"
+       an=${BASH_REMATCH[1]}
+       al=${BASH_REMATCH[3]}
+       as=${BASH_REMATCH[4]}
+       ar=${BASH_REMATCH[7]}
+
+       [[ ${vb} =~ ${re} ]] || die "${FUNCNAME}: invalid version: ${vb}"
+       bn=${BASH_REMATCH[1]}
+       bl=${BASH_REMATCH[3]}
+       bs=${BASH_REMATCH[4]}
+       br=${BASH_REMATCH[7]}
+
+       # Compare numeric components (PMS algorithm 3.2)
+       # First component
+       __eapi7_ver_compare_int "${an%%.*}" "${bn%%.*}" || return
+
+       while [[ ${an} == *.* && ${bn} == *.* ]]; do
+               # Other components (PMS algorithm 3.3)
+               an=${an#*.}
+               bn=${bn#*.}
+               a=${an%%.*}
+               b=${bn%%.*}
+               if [[ ${a} == 0* || ${b} == 0* ]]; then
+                       # Remove any trailing zeros
+                       [[ ${a} =~ 0+$ ]] && a=${a%"${BASH_REMATCH[0]}"}
+                       [[ ${b} =~ 0+$ ]] && b=${b%"${BASH_REMATCH[0]}"}
+                       [[ ${a} > ${b} ]] && return 3
+                       [[ ${a} < ${b} ]] && return 1
+               else
+                       __eapi7_ver_compare_int "${a}" "${b}" || return
+               fi
+       done
+       [[ ${an} == *.* ]] && return 3
+       [[ ${bn} == *.* ]] && return 1
+
+       # Compare letter components (PMS algorithm 3.4)
+       [[ ${al} > ${bl} ]] && return 3
+       [[ ${al} < ${bl} ]] && return 1
+
+       # Compare suffixes (PMS algorithm 3.5)
+       as=${as#_}${as:+_}
+       bs=${bs#_}${bs:+_}
+       while [[ -n ${as} && -n ${bs} ]]; do
+               # Compare each suffix (PMS algorithm 3.6)
+               a=${as%%_*}
+               b=${bs%%_*}
+               if [[ ${a%%[0-9]*} == "${b%%[0-9]*}" ]]; then
+                       __eapi7_ver_compare_int "${a##*[a-z]}" "${b##*[a-z]}" 
|| return
+               else
+                       # Check for p first
+                       [[ ${a%%[0-9]*} == p ]] && return 3
+                       [[ ${b%%[0-9]*} == p ]] && return 1
+                       # Hack: Use that alpha < beta < pre < rc alphabetically
+                       [[ ${a} > ${b} ]] && return 3 || return 1
+               fi
+               as=${as#*_}
+               bs=${bs#*_}
+       done
+       if [[ -n ${as} ]]; then
+               [[ ${as} == p[_0-9]* ]] && return 3 || return 1
+       elif [[ -n ${bs} ]]; then
+               [[ ${bs} == p[_0-9]* ]] && return 1 || return 3
+       fi
+
+       # Compare revision components (PMS algorithm 3.7)
+       __eapi7_ver_compare_int "${ar#-r}" "${br#-r}" || return
+
+       return 2
+}
+
+ver_test() {
+       local va op vb
+
+       if [[ $# -eq 3 ]]; then
+               va=${1}
+               shift
+       else
+               va=${PVR}
+       fi
+
+       [[ $# -eq 2 ]] || die "${FUNCNAME}: bad number of arguments"
+
+       op=${1}
+       vb=${2}
+
+       case ${op} in
+               -eq|-ne|-lt|-le|-gt|-ge) ;;
+               *) die "${FUNCNAME}: invalid operator: ${op}" ;;
+       esac
+
+       __eapi7_ver_compare "${va}" "${vb}"
+       test $? "${op}" 2
+}

diff --git a/bin/isolated-functions.sh b/bin/isolated-functions.sh
index 0acb81607..28ca94532 100644
--- a/bin/isolated-functions.sh
+++ b/bin/isolated-functions.sh
@@ -4,6 +4,10 @@
 
 source "${PORTAGE_BIN_PATH}/eapi.sh" || exit 1
 
+if ___eapi_has_version_functions; then
+       source "${PORTAGE_BIN_PATH}/eapi7-ver-funcs.sh" || exit 1
+fi
+
 # We need this next line for "die" and "assert". It expands
 # It _must_ preceed all the calls to die and assert.
 shopt -s expand_aliases

diff --git a/pym/portage/tests/bin/test_eapi7_ver_funcs.py 
b/pym/portage/tests/bin/test_eapi7_ver_funcs.py
new file mode 100644
index 000000000..408975298
--- /dev/null
+++ b/pym/portage/tests/bin/test_eapi7_ver_funcs.py
@@ -0,0 +1,240 @@
+# Copyright 2018 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+import subprocess
+import tempfile
+
+from portage.const import PORTAGE_BIN_PATH
+from portage.tests import TestCase
+
+
+class TestEAPI7VerFuncs(TestCase):
+       def _test_output(self, test_cases):
+               """
+               Test that commands in test_cases produce expected output.
+               """
+               with tempfile.NamedTemporaryFile('w') as test_script:
+                       test_script.write('source "%s"/eapi7-ver-funcs.sh\n'
+                                       % (PORTAGE_BIN_PATH,))
+                       for cmd, exp in test_cases:
+                               test_script.write('%s\n' % (cmd,))
+                       test_script.flush()
+
+                       s = subprocess.Popen(['bash', test_script.name],
+                                       stdout=subprocess.PIPE,
+                                       stderr=subprocess.PIPE)
+                       sout, serr = s.communicate()
+                       self.assertEqual(s.returncode, 0)
+
+                       for test_case, result in zip(test_cases, 
sout.decode().splitlines()):
+                               cmd, exp = test_case
+                               self.assertEqual(result, exp,
+                                               '%s -> %s; expected: %s' % 
(cmd, result, exp))
+
+       def _test_return(self, test_cases):
+               """
+               Test that commands in test_cases give appropriate exit codes.
+               """
+               with tempfile.NamedTemporaryFile('w+') as test_script:
+                       test_script.write('source "%s"/eapi7-ver-funcs.sh\n'
+                                       % (PORTAGE_BIN_PATH,))
+                       for cmd, exp in test_cases:
+                               test_script.write('%s; echo $?\n' % (cmd,))
+                       test_script.flush()
+
+                       s = subprocess.Popen(['bash', test_script.name],
+                                       stdout=subprocess.PIPE,
+                                       stderr=subprocess.PIPE)
+                       sout, serr = s.communicate()
+                       self.assertEqual(s.returncode, 0)
+
+                       for test_case, result in zip(test_cases, 
sout.decode().splitlines()):
+                               cmd, exp = test_case
+                               self.assertEqual(result, exp,
+                                               '%s -> %s; expected: %s' % 
(cmd, result, exp))
+
+       def _test_fail(self, test_cases):
+               """
+               Test that commands in test_cases fail.
+               """
+
+               for cmd in test_cases:
+                       test = '''
+source "%s"/eapi7-ver-funcs.sh
+die() { exit 1; }
+%s''' % (PORTAGE_BIN_PATH, cmd)
+
+                       s = subprocess.Popen(['bash', '-c', test],
+                                       stdout=subprocess.PIPE,
+                                       stderr=subprocess.PIPE)
+                       sout, serr = s.communicate()
+                       self.assertEqual(s.returncode, 1,
+                                       '"%s" did not fail; output: %s; %s)'
+                                       % (cmd, sout.decode(), serr.decode()))
+
+       def test_ver_cut(self):
+               test_cases = [
+                       # (command, output)
+                       ('ver_cut 1 1.2.3', '1'),
+                       ('ver_cut 1-1 1.2.3', '1'),
+                       ('ver_cut 1-2 1.2.3', '1.2'),
+                       ('ver_cut 2- 1.2.3', '2.3'),
+                       ('ver_cut 1- 1.2.3', '1.2.3'),
+                       ('ver_cut 3-4 1.2.3b_alpha4', '3b'),
+                       ('ver_cut 5 1.2.3b_alpha4', 'alpha'),
+                       ('ver_cut 1-2 .1.2.3', '1.2'),
+                       ('ver_cut 0-2 .1.2.3', '.1.2'),
+                       ('ver_cut 2-3 1.2.3.', '2.3'),
+                       ('ver_cut 2- 1.2.3.', '2.3.'),
+                       ('ver_cut 2-4 1.2.3.', '2.3.'),
+               ]
+               self._test_output(test_cases)
+
+       def test_ver_rs(self):
+               test_cases = [
+                       # (command, output)
+                       ('ver_rs 1 - 1.2.3', '1-2.3'),
+                       ('ver_rs 2 - 1.2.3', '1.2-3'),
+                       ('ver_rs 1-2 - 1.2.3.4', '1-2-3.4'),
+                       ('ver_rs 2- - 1.2.3.4', '1.2-3-4'),
+                       ('ver_rs 2 . 1.2-3', '1.2.3'),
+                       ('ver_rs 3 . 1.2.3a', '1.2.3.a'),
+                       ('ver_rs 2-3 - 1.2_alpha4', '1.2-alpha-4'),
+                       ('ver_rs 3 - 2 "" 1.2.3b_alpha4', '1.23-b_alpha4'),
+                       ('ver_rs 3-5 _ 4-6 - a1b2c3d4e5', 'a1b_2-c-3-d4e5'),
+                       ('ver_rs 1 - .1.2.3', '.1-2.3'),
+                       ('ver_rs 0 - .1.2.3', '-1.2.3'),
+               ]
+               self._test_output(test_cases)
+
+       def test_truncated_range(self):
+               test_cases = [
+                       # (command, output)
+                       ('ver_cut 0-2 1.2.3', '1.2'),
+                       ('ver_cut 2-5 1.2.3', '2.3'),
+                       ('ver_cut 4 1.2.3', ''),
+                       ('ver_cut 0 1.2.3', ''),
+                       ('ver_cut 4- 1.2.3', ''),
+                       ('ver_rs 0 - 1.2.3', '1.2.3'),
+                       ('ver_rs 3 . 1.2.3', '1.2.3'),
+                       ('ver_rs 3- . 1.2.3', '1.2.3'),
+                       ('ver_rs 3-5 . 1.2.3', '1.2.3'),
+               ]
+               self._test_output(test_cases)
+
+       def test_invalid_range(self):
+               test_cases = [
+                       'ver_cut foo 1.2.3',
+                       'ver_rs -3 _ a1b2c3d4e5',
+                       'ver_rs 5-3 _ a1b2c3d4e5',
+               ]
+               self._test_fail(test_cases)
+
+       def test_ver_test(self):
+               test_cases = [
+                       # Tests from Portage's test_vercmp.py
+                       ('ver_test 6.0 -gt 5.0', '0'),
+                       ('ver_test 5.0 -gt 5', '0'),
+                       ('ver_test 1.0-r1 -gt 1.0-r0', '0'),
+                       ('ver_test 999999999999999999 -gt 999999999999999998', 
'0'),  # 18 digits
+                       ('ver_test 1.0.0 -gt 1.0', '0'),
+                       ('ver_test 1.0.0 -gt 1.0b', '0'),
+                       ('ver_test 1b -gt 1', '0'),
+                       ('ver_test 1b_p1 -gt 1_p1', '0'),
+                       ('ver_test 1.1b -gt 1.1', '0'),
+                       ('ver_test 12.2.5 -gt 12.2b', '0'),
+                       ('ver_test 4.0 -lt 5.0', '0'),
+                       ('ver_test 5 -lt 5.0', '0'),
+                       ('ver_test 1.0_pre2 -lt 1.0_p2', '0'),
+                       ('ver_test 1.0_alpha2 -lt 1.0_p2', '0'),
+                       ('ver_test 1.0_alpha1 -lt 1.0_beta1', '0'),
+                       ('ver_test 1.0_beta3 -lt 1.0_rc3', '0'),
+                       ('ver_test 1.001000000000000001 -lt 
1.001000000000000002', '0'),
+                       ('ver_test 1.00100000000 -lt 1.001000000000000001', 
'0'),
+                       ('ver_test 999999999999999998 -lt 999999999999999999', 
'0'),
+                       ('ver_test 1.01 -lt 1.1', '0'),
+                       ('ver_test 1.0-r0 -lt 1.0-r1', '0'),
+                       ('ver_test 1.0 -lt 1.0-r1', '0'),
+                       ('ver_test 1.0 -lt 1.0.0', '0'),
+                       ('ver_test 1.0b -lt 1.0.0', '0'),
+                       ('ver_test 1_p1 -lt 1b_p1', '0'),
+                       ('ver_test 1 -lt 1b', '0'),
+                       ('ver_test 1.1 -lt 1.1b', '0'),
+                       ('ver_test 12.2b -lt 12.2.5', '0'),
+                       ('ver_test 4.0 -eq 4.0', '0'),
+                       ('ver_test 1.0 -eq 1.0', '0'),
+                       ('ver_test 1.0-r0 -eq 1.0', '0'),
+                       ('ver_test 1.0 -eq 1.0-r0', '0'),
+                       ('ver_test 1.0-r0 -eq 1.0-r0', '0'),
+                       ('ver_test 1.0-r1 -eq 1.0-r1', '0'),
+                       ('ver_test 1 -eq 2', '1'),
+                       ('ver_test 1.0_alpha -eq 1.0_pre', '1'),
+                       ('ver_test 1.0_beta -eq 1.0_alpha', '1'),
+                       ('ver_test 1 -eq 0.0', '1'),
+                       ('ver_test 1.0-r0 -eq 1.0-r1', '1'),
+                       ('ver_test 1.0-r1 -eq 1.0-r0', '1'),
+                       ('ver_test 1.0 -eq 1.0-r1', '1'),
+                       ('ver_test 1.0-r1 -eq 1.0', '1'),
+                       ('ver_test 1.0 -eq 1.0.0', '1'),
+                       ('ver_test 1_p1 -eq 1b_p1', '1'),
+                       ('ver_test 1b -eq 1', '1'),
+                       ('ver_test 1.1b -eq 1.1', '1'),
+                       ('ver_test 12.2b -eq 12.2', '1'),
+
+                       # A subset of tests from Paludis
+                       ('ver_test 1.0_alpha -gt 1_alpha', '0'),
+                       ('ver_test 1.0_alpha -gt 1', '0'),
+                       ('ver_test 1.0_alpha -lt 1.0', '0'),
+                       ('ver_test 1.2.0.0_alpha7-r4 -gt 1.2_alpha7-r4', '0'),
+                       ('ver_test 0001 -eq 1', '0'),
+                       ('ver_test 01 -eq 001', '0'),
+                       ('ver_test 0001.1 -eq 1.1', '0'),
+                       ('ver_test 01.01 -eq 1.01', '0'),
+                       ('ver_test 1.010 -eq 1.01', '0'),
+                       ('ver_test 1.00 -eq 1.0', '0'),
+                       ('ver_test 1.0100 -eq 1.010', '0'),
+                       ('ver_test 1-r00 -eq 1-r0', '0'),
+
+                       # Additional tests
+                       ('ver_test 0_rc99 -lt 0', '0'),
+                       ('ver_test 011 -eq 11', '0'),
+                       ('ver_test 019 -eq 19', '0'),
+                       ('ver_test 1.2 -eq 001.2', '0'),
+                       ('ver_test 1.2 -gt 1.02', '0'),
+                       ('ver_test 1.2a -lt 1.2b', '0'),
+                       ('ver_test 1.2_pre1 -gt 1.2_pre1_beta2', '0'),
+                       ('ver_test 1.2_pre1 -lt 1.2_pre1_p2', '0'),
+                       ('ver_test 1.00 -lt 1.0.0', '0'),
+                       ('ver_test 1.010 -eq 1.01', '0'),
+                       ('ver_test 1.01 -lt 1.1', '0'),
+                       ('ver_test 1.2_pre08-r09 -eq 1.2_pre8-r9', '0'),
+                       ('ver_test 0 -lt 576460752303423488', '0'),  # 2**59
+                       ('ver_test 0 -lt 9223372036854775808', '0'),  # 2**63
+               ]
+               self._test_return(test_cases)
+
+       def test_invalid_test(self):
+               test_cases = [
+                       # Bad number or ordering of arguments
+                       'ver_test 1',
+                       'ver_test 1 -lt 2 3',
+                       'ver_test -lt 1 2',
+
+                       # Bad operators
+                       'ver_test 1 "<" 2',
+                       'ver_test 1 lt 2',
+                       'ver_test 1 -foo 2',
+
+                       # Malformed versions
+                       'ver_test "" -ne 1',
+                       'ver_test 1. -ne 1',
+                       'ver_test 1ab -ne 1',
+                       'ver_test b -ne 1',
+                       'ver_test 1-r1_pre -ne 1',
+                       'ver_test 1-pre1 -ne 1',
+                       'ver_test 1_foo -ne 1',
+                       'ver_test 1_pre1.1 -ne 1',
+                       'ver_test 1-r1.0 -ne 1',
+                       'ver_test cvs.9999 -ne 9999',
+               ]
+               self._test_fail(test_cases)

Reply via email to