[Asterisk-code-review] version parser: Add a module for parsing Asterisk versions (repotools[master])
Matt Jordan
asteriskteam at digium.com
Fri May 8 15:56:03 CDT 2015
Matt Jordan has submitted this change and it was merged.
Change subject: version_parser: Add a module for parsing Asterisk versions
......................................................................
version_parser: Add a module for parsing Asterisk versions
This patch adds a module for parsing Asterisk versions. While it is
similar to the version.py module in the Asterisk Test Suite, it differs
in a few important ways:
(1) It only parses actual Asterisk tagged versions, and not any
arbitrary source control derived version string. As such, portions
of the logic are substantially simpler.
(2) The primary purpose of the Test Suite version.py module is to
compare relative versions. On the other hand, most scripts in
repotools care more about taking one particular version and
calculating various versions related to it in some fashion. This
patch deals more with this latter purpose.
The implemented class can handle versions:
* Prefixed with a '1' (pre-10) or non-prefixed (10+)
* With any arbitrary modifiers, e.g., 'beta', 'rc', etc., in a
descending priority order
* From non-mainline branches, prefixed with a tag modifier, e.g.,
'certified/'
In addition, an instance of the class can provide the previous logical
tag, which is useful for other scripts in repotools.
REP-15
Change-Id: I73661d43819388bda383ef4ba1c6ecdeee761fa9
---
A version_parser.py
1 file changed, 498 insertions(+), 0 deletions(-)
Approvals:
Samuel Galarneau: Looks good to me, but someone else must approve
Matt Jordan: Looks good to me, approved; Verified
diff --git a/version_parser.py b/version_parser.py
new file mode 100644
index 0000000..bb4e88f
--- /dev/null
+++ b/version_parser.py
@@ -0,0 +1,498 @@
+#!/usr/bin/env python
+"""Asterisk version parser
+
+This module provides a class that parses an Asterisk version string into its
+constituent parts.
+
+Copyright (C) 2015, Digium, Inc.
+Matt Jordan <mjordan at digium.com>
+"""
+
+import unittest
+import sys
+import re
+
+class AsteriskVersion(object):
+ """Representation of an Asterisk Version
+
+ Attributes:
+ raw - The raw version string
+ prefix - A tag prefix, such as 'certified'
+ leading_one - Whether the major version was preceeded by a '1'. This is
+ for compatibility with older versions of Asterisk, such
+ as 1.8.
+ major - The semantic major version
+ minor - The semantic minor version
+ patch - The semantic patch version. -1 if there is no patch version.
+ modifiers - Version modifiers. These are alphabetic suffixes to a
+ version string, and themselves contain an additional version
+ suffix, i.e., 'rc3'. Any number may be included, delineated
+ by a '-'.
+ """
+
+ @classmethod
+ def create_from_string(cls, raw):
+ """Create an AsteriskVersion instance from a string
+
+ Keyword Arguments:
+ raw - The raw string representation of the version
+ """
+ prefix = None
+ leading_one = None
+ modifiers = []
+ ver_modifiers = []
+
+ # Strip any prefix off the front, such as 'certified/'
+ if '/' in raw:
+ prefix = raw[:raw.index('/')]
+ raw = raw[raw.index('/') + 1:]
+ version_parts = raw.split('.')
+ if len(version_parts) == 0 or len(version_parts) == 1:
+ raise ValueError("Version '{0}' does not contain a major/"
+ "minor part".format(raw))
+
+ # Split out any modifiers
+ if '-' in version_parts[len(version_parts) - 1]:
+ ver_modifiers = version_parts[len(version_parts) - 1].split('-')
+ version_parts[len(version_parts) - 1] = ver_modifiers.pop(0)
+
+ # Parse out the major/minor digits, handling the case where we have
+ # a leading '1' digit
+ start_index = 0
+ if version_parts[0] == '1':
+ leading_one = True
+ start_index = 1
+
+ major = int(version_parts[start_index])
+ minor = int(version_parts[start_index + 1])
+ if len(version_parts) > 2:
+ patch = int(version_parts[start_index + 2])
+ else:
+ patch = -1
+
+ # Build out modifier tuples, consisting of the modifier type plus
+ # its digit
+ for modifier in ver_modifiers:
+ digit_pos = re.search('\d', modifier)
+ if not digit_pos:
+ raise ValueError("No digit found in modifier '{0}' in "
+ "version '{1}'".format(modifier, raw))
+ modifiers.append((modifier[:digit_pos.start()],
+ int(modifier[digit_pos.start():])))
+
+ ver = AsteriskVersion(major, minor, patch=patch, modifiers=modifiers,
+ prefix=prefix, leading_one=leading_one)
+ ver.raw = raw
+ return ver
+
+ def __init__(self, major, minor, patch=-1, modifiers=None, prefix=None,
+ leading_one=False):
+ """Constructor
+
+ Keyword Arguments:
+ major - The semantic major version
+ minor - The semantic minor version
+ patch - The semantic patch version. -1 if there is no patch
+ version.
+ modifiers - Version modifiers. These are alphabetic suffixes to a
+ version string, and themselves contain an additional
+ version suffix, i.e., 'rc3'. Any number may be included,
+ delineated by a '-'.
+ prefix - A tag prefix, such as 'certified'
+ leading_one - Whether the major version was preceeded by a '1'. This
+ is for compatibility with older versions of Asterisk,
+ such as 1.8.
+ """
+ self.major = major
+ self.minor = minor
+ self.patch = patch
+ self.modifiers = modifiers or []
+ self.prefix = prefix
+ self.leading_one = leading_one
+
+ def get_previous_version(self):
+ """Return the logical previous version of this version
+
+ The previous version works as follows:
+ (1) Starting with the modifiers, start with the least significant.
+ If the previous version would be 0, move to the next modifier.
+ If all modifiers are 1, continue on.
+ (2) Starting with the patch, decrement by 1. If the value is negative,
+ move to the next number.
+ """
+
+ previous = AsteriskVersion(self.major, self.minor,
+ patch=self.patch, modifiers=list(self.modifiers),
+ prefix=self.prefix, leading_one=self.leading_one)
+
+ for i, modifier in enumerate(reversed(previous.modifiers)):
+ prefix, num = modifier
+ if num != 1:
+ num -= 1
+ mod_len = len(previous.modifiers)
+ previous.modifiers[mod_len - 1 - i] = (prefix, num)
+ previous.modifiers = previous.modifiers[:mod_len - i]
+ return previous
+
+ # If all of the modifiers were the first of their kind,
+ # then the previous version took place before any modifiers.
+ # Starting with the least significant digit, return the previous
+ # version closest to the current.
+ previous.modifiers = []
+ if previous.patch > 0:
+ previous.patch -= 1
+ return previous
+
+ previous.patch = 0
+ if previous.minor > 0:
+ previous.minor -= 1
+ return previous
+
+ previous.major -= 1
+ return previous
+
+ def __repr__(self):
+ """Return a representation of the version"""
+ ver = ''
+
+ if self.prefix:
+ ver = '{0}/'.format(self.prefix)
+
+ if self.leading_one:
+ ver = '{0}1.'.format(ver)
+
+ ver = '{0}{1}.{2}'.format(ver, self.major, self.minor)
+
+ if self.patch > -1:
+ ver = '{0}.{1}'.format(ver, self.patch)
+
+ for mod, num in self.modifiers:
+ ver = '{0}-{1}{2}'.format(ver, mod, num)
+
+ return ver
+
+
+class AsteriskVersionTests(unittest.TestCase):
+ """Unit tests for the AsteriskVersion class"""
+
+ def test_parse_18(self):
+ """Test parsing a 1.8 (leading one) version string"""
+ ver = AsteriskVersion.create_from_string('1.8.23.0')
+ self.assertTrue(ver.leading_one)
+ self.assertEqual(ver.major, 8)
+ self.assertEqual(ver.minor, 23)
+ self.assertEqual(ver.patch, 0)
+ self.assertEqual(ver.prefix, None)
+ self.assertEqual(len(ver.modifiers), 0)
+
+ def test_parse_18_patch(self):
+ """Test parsing a 1.8 version with a patch number"""
+ ver = AsteriskVersion.create_from_string('1.8.23.2')
+ self.assertTrue(ver.leading_one)
+ self.assertEqual(ver.major, 8)
+ self.assertEqual(ver.minor, 23)
+ self.assertEqual(ver.patch, 2)
+ self.assertEqual(ver.prefix, None)
+ self.assertEqual(len(ver.modifiers), 0)
+
+ def test_parse_11(self):
+ """Test parsing an 11 (non-leading one) version string"""
+ ver = AsteriskVersion.create_from_string('11.17.0')
+ self.assertFalse(ver.leading_one)
+ self.assertEqual(ver.major, 11)
+ self.assertEqual(ver.minor, 17)
+ self.assertEqual(ver.patch, 0)
+ self.assertEqual(ver.prefix, None)
+ self.assertEqual(len(ver.modifiers), 0)
+
+ def test_parse_11_patch(self):
+ """Test parsing an 11 version string with patch"""
+ ver = AsteriskVersion.create_from_string('11.17.11')
+ self.assertEqual(ver.prefix, None)
+ self.assertFalse(ver.leading_one)
+ self.assertEqual(ver.major, 11)
+ self.assertEqual(ver.minor, 17)
+ self.assertEqual(ver.patch, 11)
+ self.assertEqual(ver.prefix, None)
+ self.assertEqual(len(ver.modifiers), 0)
+
+ def test_parse_18_rc1(self):
+ """Test parsing a 1.8 version with an rc1"""
+ ver = AsteriskVersion.create_from_string('1.8.23.0-rc1')
+ self.assertEqual(ver.prefix, None)
+ self.assertTrue(ver.leading_one)
+ self.assertEqual(ver.major, 8)
+ self.assertEqual(ver.minor, 23)
+ self.assertEqual(ver.patch, 0)
+ self.assertEqual(len(ver.modifiers), 1)
+ mod, num = ver.modifiers[0]
+ self.assertEqual(mod, 'rc')
+ self.assertEqual(num, 1)
+
+ def test_parse_11_rc(self):
+ """Test parsing a 11 version with a non-rc1"""
+ ver = AsteriskVersion.create_from_string('11.17.0-rc3')
+ self.assertEqual(ver.prefix, None)
+ self.assertFalse(ver.leading_one)
+ self.assertEqual(ver.major, 11)
+ self.assertEqual(ver.minor, 17)
+ self.assertEqual(ver.patch, 0)
+ self.assertEqual(len(ver.modifiers), 1)
+ mod, num = ver.modifiers[0]
+ self.assertEqual(mod, 'rc')
+ self.assertEqual(num, 3)
+
+ def test_parse_18_beta(self):
+ """Test parsing a 1.8 non-beta1 version"""
+ ver = AsteriskVersion.create_from_string('1.8.23.0-beta2')
+ self.assertEqual(ver.prefix, None)
+ self.assertTrue(ver.leading_one)
+ self.assertEqual(ver.major, 8)
+ self.assertEqual(ver.minor, 23)
+ self.assertEqual(ver.patch, 0)
+ self.assertEqual(len(ver.modifiers), 1)
+ mod, num = ver.modifiers[0]
+ self.assertEqual(mod, 'beta')
+ self.assertEqual(num, 2)
+
+ def test_parse_11_beta1(self):
+ """Test parsing an 11 beta1 version"""
+ ver = AsteriskVersion.create_from_string('11.17.0-beta1')
+ self.assertEqual(ver.prefix, None)
+ self.assertFalse(ver.leading_one)
+ self.assertEqual(ver.major, 11)
+ self.assertEqual(ver.minor, 17)
+ self.assertEqual(ver.patch, 0)
+ self.assertEqual(len(ver.modifiers), 1)
+ mod, num = ver.modifiers[0]
+ self.assertEqual(mod, 'beta')
+ self.assertEqual(num, 1)
+
+ def test_parse_18_beta1_rc(self):
+ """Test parsing a 1.8 beta1 rc"""
+ ver = AsteriskVersion.create_from_string('1.8.0.0-beta1-rc3')
+ self.assertEqual(ver.prefix, None)
+ self.assertTrue(ver.leading_one)
+ self.assertEqual(ver.major, 8)
+ self.assertEqual(ver.minor, 0)
+ self.assertEqual(ver.patch, 0)
+ self.assertEqual(len(ver.modifiers), 2)
+ mod, num = ver.modifiers[0]
+ self.assertEqual(mod, 'beta')
+ self.assertEqual(num, 1)
+ mod, num = ver.modifiers[1]
+ self.assertEqual(mod, 'rc')
+ self.assertEqual(num, 3)
+
+ def test_parse_11_beta_rc1(self):
+ """Test parsing an 11 beta rc1"""
+ ver = AsteriskVersion.create_from_string('11.0.0-beta3-rc1')
+ self.assertEqual(ver.prefix, None)
+ self.assertFalse(ver.leading_one)
+ self.assertEqual(ver.major, 11)
+ self.assertEqual(ver.minor, 0)
+ self.assertEqual(ver.patch, 0)
+ self.assertEqual(len(ver.modifiers), 2)
+ mod, num = ver.modifiers[0]
+ self.assertEqual(mod, 'beta')
+ self.assertEqual(num, 3)
+ mod, num = ver.modifiers[1]
+ self.assertEqual(mod, 'rc')
+ self.assertEqual(num, 1)
+
+ def test_cert_13(self):
+ """Test parsing 13.1-cert1"""
+ ver = AsteriskVersion.create_from_string('certified/13.1-cert1')
+ self.assertFalse(ver.leading_one)
+ self.assertEqual(ver.prefix, 'certified')
+ self.assertEqual(ver.major, 13)
+ self.assertEqual(ver.minor, 1)
+ self.assertEqual(ver.patch, -1)
+ self.assertEqual(len(ver.modifiers), 1)
+ mod, num = ver.modifiers[0]
+ self.assertEqual(mod, 'cert')
+ self.assertEqual(num, 1)
+
+ def test_cert_13_rc(self):
+ """Test parsing 13.1-cert with a high rc"""
+ ver = AsteriskVersion.create_from_string('certified/13.1-cert12-rc10')
+ self.assertFalse(ver.leading_one)
+ self.assertEqual(ver.prefix, 'certified')
+ self.assertEqual(ver.major, 13)
+ self.assertEqual(ver.minor, 1)
+ self.assertEqual(ver.patch, -1)
+ self.assertEqual(len(ver.modifiers), 2)
+ mod, num = ver.modifiers[0]
+ self.assertEqual(mod, 'cert')
+ self.assertEqual(num, 12)
+ mod, num = ver.modifiers[1]
+ self.assertEqual(mod, 'rc')
+ self.assertEqual(num, 10)
+
+ def test_1300_prev(self):
+ """Test getting the prev version from 13.0.0 (prev major release)"""
+ ver = AsteriskVersion.create_from_string('13.0.0')
+ prev = ver.get_previous_version()
+ self.assertEqual(prev.prefix, None)
+ self.assertEqual(prev.major, 12)
+ self.assertEqual(prev.minor, 0)
+ self.assertEqual(prev.patch, 0)
+
+ def test_1320_prev(self):
+ """Test getting the prev version from 13.2.0 (prev minor)"""
+ ver = AsteriskVersion.create_from_string('13.2.0')
+ prev = ver.get_previous_version()
+ self.assertEqual(prev.prefix, None)
+ self.assertEqual(prev.major, 13)
+ self.assertEqual(prev.minor, 1)
+ self.assertEqual(prev.patch, 0)
+
+ def test_1323_prev(self):
+ """Test getting the prev version from 13.2.3 (prev patch)"""
+ ver = AsteriskVersion.create_from_string('13.2.3')
+ prev = ver.get_previous_version()
+ self.assertEqual(prev.prefix, None)
+ self.assertEqual(prev.major, 13)
+ self.assertEqual(prev.minor, 2)
+ self.assertEqual(prev.patch, 2)
+
+ def test_18193_prev(self):
+ """Test getting the prev version from 1.8.19.3 (prev patch)"""
+ ver = AsteriskVersion.create_from_string('1.8.19.3')
+ prev = ver.get_previous_version()
+ self.assertEqual(prev.prefix, None)
+ self.assertTrue(prev.leading_one)
+ self.assertEqual(prev.major, 8)
+ self.assertEqual(prev.minor, 19)
+ self.assertEqual(prev.patch, 2)
+
+ def test_1300rc1_prev(self):
+ """Test getting the prev feature release starting from rc"""
+ ver = AsteriskVersion.create_from_string('13.0.0-rc1')
+ prev = ver.get_previous_version()
+ self.assertEqual(prev.prefix, None)
+ self.assertEqual(prev.major, 12)
+ self.assertEqual(prev.minor, 0)
+ self.assertEqual(prev.patch, 0)
+ self.assertEqual(len(prev.modifiers), 0)
+
+ def test_1300rc2_prev(self):
+ """Test getting the prev rc release"""
+ ver = AsteriskVersion.create_from_string('13.0.0-rc2')
+ prev = ver.get_previous_version()
+ self.assertEqual(prev.prefix, None)
+ self.assertEqual(prev.major, 13)
+ self.assertEqual(prev.minor, 0)
+ self.assertEqual(prev.patch, 0)
+ self.assertEqual(len(prev.modifiers), 1)
+ mod, num = prev.modifiers[0]
+ self.assertEqual(mod, 'rc')
+ self.assertEqual(num, 1)
+
+ def test_1300beta1rc2_prev(self):
+ """Test getting the prev feature release (rc, beta)"""
+ ver = AsteriskVersion.create_from_string('13.0.0-beta1-rc1')
+ prev = ver.get_previous_version()
+ self.assertEqual(prev.prefix, None)
+ self.assertEqual(prev.major, 12)
+ self.assertEqual(prev.minor, 0)
+ self.assertEqual(prev.patch, 0)
+ self.assertEqual(len(prev.modifiers), 0)
+
+ def test_1300beta1rc2_prev(self):
+ """Test getting the prev rc release with beta tag"""
+ ver = AsteriskVersion.create_from_string('13.0.0-beta1-rc2')
+ prev = ver.get_previous_version()
+ self.assertEqual(prev.prefix, None)
+ self.assertEqual(prev.major, 13)
+ self.assertEqual(prev.minor, 0)
+ self.assertEqual(prev.patch, 0)
+ self.assertEqual(len(prev.modifiers), 2)
+ mod, num = prev.modifiers[0]
+ self.assertEqual(mod, 'beta')
+ self.assertEqual(num, 1)
+ mod, num = prev.modifiers[1]
+ self.assertEqual(mod, 'rc')
+ self.assertEqual(num, 1)
+
+ def test_1300beta2rc1_prev(self):
+ """Test getting the prev beta release with rc tag"""
+ ver = AsteriskVersion.create_from_string('13.0.0-beta2-rc1')
+ prev = ver.get_previous_version()
+ self.assertEqual(prev.prefix, None)
+ self.assertEqual(prev.major, 13)
+ self.assertEqual(prev.minor, 0)
+ self.assertEqual(prev.patch, 0)
+ self.assertEqual(len(prev.modifiers), 1)
+ mod, num = prev.modifiers[0]
+ self.assertEqual(mod, 'beta')
+ self.assertEqual(num, 1)
+
+ def test_131_cert1_prev(self):
+ """Test getting previous mainline release from cert release"""
+ ver = AsteriskVersion.create_from_string('certified/13.1-cert1')
+ prev = ver.get_previous_version()
+ self.assertEqual(prev.prefix, 'certified')
+ self.assertEqual(prev.major, 13)
+ self.assertEqual(prev.minor, 0)
+ self.assertEqual(prev.patch, 0)
+
+ def test_131_cert1_rc1_prev(self):
+ """Test getting previous mainline release from cert/rc release"""
+ ver = AsteriskVersion.create_from_string('certified/13.1-cert1-rc1')
+ prev = ver.get_previous_version()
+ self.assertEqual(prev.prefix, 'certified')
+ self.assertEqual(prev.major, 13)
+ self.assertEqual(prev.minor, 0)
+ self.assertEqual(prev.patch, 0)
+
+ def test_131_cert2_prev(self):
+ """Test getting the previous cert release"""
+ ver = AsteriskVersion.create_from_string('certified/13.1-cert2')
+ prev = ver.get_previous_version()
+ self.assertEqual(prev.prefix, 'certified')
+ self.assertEqual(prev.major, 13)
+ self.assertEqual(prev.minor, 1)
+ self.assertEqual(prev.patch, -1)
+ self.assertEqual(len(prev.modifiers), 1)
+ mod, num = prev.modifiers[0]
+ self.assertEqual(mod, 'cert')
+ self.assertEqual(num, 1)
+
+ def test_131_cert2_rc1_prev(self):
+ """Test getting the previous cert release with rc1"""
+ ver = AsteriskVersion.create_from_string('certified/13.1-cert2-rc1')
+ prev = ver.get_previous_version()
+ self.assertEqual(prev.prefix, 'certified')
+ self.assertEqual(prev.major, 13)
+ self.assertEqual(prev.minor, 1)
+ self.assertEqual(prev.patch, -1)
+ self.assertEqual(len(prev.modifiers), 1)
+ mod, num = prev.modifiers[0]
+ self.assertEqual(mod, 'cert')
+ self.assertEqual(num, 1)
+
+ def test_131_cert2_rc2_prev(self):
+ """Test getting the previous cert release with rc"""
+ ver = AsteriskVersion.create_from_string('certified/13.1-cert2-rc2')
+ prev = ver.get_previous_version()
+ self.assertEqual(prev.prefix, 'certified')
+ self.assertEqual(prev.major, 13)
+ self.assertEqual(prev.minor, 1)
+ self.assertEqual(prev.patch, -1)
+ self.assertEqual(len(prev.modifiers), 2)
+ mod, num = prev.modifiers[0]
+ self.assertEqual(mod, 'cert')
+ self.assertEqual(num, 2)
+ mod, num = prev.modifiers[1]
+ self.assertEqual(mod, 'rc')
+ self.assertEqual(num, 1)
+
+
+def main():
+ unittest.main()
+ return
+
+
+if __name__ == '__main__':
+ sys.exit(main() or 0)
--
To view, visit https://gerrit.asterisk.org/379
To unsubscribe, visit https://gerrit.asterisk.org/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I73661d43819388bda383ef4ba1c6ecdeee761fa9
Gerrit-PatchSet: 3
Gerrit-Project: repotools
Gerrit-Branch: master
Gerrit-Owner: Matt Jordan <mjordan at digium.com>
Gerrit-Reviewer: Matt Jordan <mjordan at digium.com>
Gerrit-Reviewer: Samuel Galarneau <sgalarneau at digium.com>
More information about the asterisk-code-review
mailing list