diff options
Diffstat (limited to 'circuitpython/tools/python-semver')
-rw-r--r-- | circuitpython/tools/python-semver/.travis.yml | 28 | ||||
-rw-r--r-- | circuitpython/tools/python-semver/CHANGELOG | 308 | ||||
-rw-r--r-- | circuitpython/tools/python-semver/CONTRIBUTORS | 45 | ||||
-rw-r--r-- | circuitpython/tools/python-semver/LICENSE.txt | 27 | ||||
-rw-r--r-- | circuitpython/tools/python-semver/MANIFEST.in | 1 | ||||
-rw-r--r-- | circuitpython/tools/python-semver/Makefile | 5 | ||||
-rw-r--r-- | circuitpython/tools/python-semver/README.rst | 113 | ||||
-rw-r--r-- | circuitpython/tools/python-semver/__pycache__/semver.cpython-310.pyc | bin | 0 -> 10458 bytes | |||
-rw-r--r-- | circuitpython/tools/python-semver/semver.py | 371 | ||||
-rwxr-xr-x | circuitpython/tools/python-semver/setup.py | 107 | ||||
-rw-r--r-- | circuitpython/tools/python-semver/tests.py | 354 | ||||
-rw-r--r-- | circuitpython/tools/python-semver/tox.ini | 16 |
12 files changed, 1375 insertions, 0 deletions
diff --git a/circuitpython/tools/python-semver/.travis.yml b/circuitpython/tools/python-semver/.travis.yml new file mode 100644 index 0000000..5fbc4df --- /dev/null +++ b/circuitpython/tools/python-semver/.travis.yml @@ -0,0 +1,28 @@ +language: python +install: pip install "virtualenv<14.0.0" +script: python setup.py test +matrix: + include: + - python: "2.6" + env: TOXENV=py26 + + - python: "2.7" + env: TOXENV=py27 + + - python: "3.3" + env: TOXENV=py33 + + - python: "3.4" + env: TOXENV=py34 + + - python: "3.4" + env: TOXENV=flake8 + + - python: "3.5" + env: TOXENV=py35 + + - python: "3.6" + env: TOXENV=py36 + + - python: "pypy" + env: TOXENV=pypy diff --git a/circuitpython/tools/python-semver/CHANGELOG b/circuitpython/tools/python-semver/CHANGELOG new file mode 100644 index 0000000..d13b887 --- /dev/null +++ b/circuitpython/tools/python-semver/CHANGELOG @@ -0,0 +1,308 @@ +########## +Change Log +########## + +Python SemVer library +##################### + +All notable changes to this code base will be documented in this file, +in every released version. + +Version 2.7.9 +============= + +:Released: 2017-09-23 +:Maintainer: Kostiantyn Rybnikov <k-bx@k-bx.com> + +* Issue #65 (PR #66). Add finalize_version function + +Version 2.7.8 +============= + +:Released: 2017-08-25 +:Maintainer: Kostiantyn Rybnikov <k-bx@k-bx.com> + +* PR #62. Support custom default names for pre and build + +Version 2.7.7 +============= + +:Released: 2017-05-25 +:Maintainer: Kostiantyn Rybnikov <k-bx@k-bx.com> + +* Issue #54 (PR #55) Add comparision between VersionInfo objects +* PR #56. Add support for Python 3.6 + +Version 2.7.2 +============= + +:Released: 2016-11-08 +:Maintainer: Kostiantyn Rybnikov <k-bx@k-bx.com> + +Additions +--------- + +* Fix issue #37 (Remove trailing zeros from prelease doesn't allow to + parse 0 pre-release version) +* Add ‘parse_version_info’ to parse a version string to a version info + tuple. + +Bug Fixes +--------- + +* Refine parsing to conform more strictly to SemVer 2.0.0. + + SemVer 2.0.0 specification §9 forbids leading zero on identifiers in + the prerelease version. + + +Version 2.6.0 +============= + +:Released: 2016-06-08 +:Maintainer: Kostiantyn Rybnikov <k-bx@k-bx.com> + +Removals +-------- + +* Remove comparison of build component. + + SemVer 2.0.0 specification recommends that build component is + ignored in comparisons. + + +Version 2.5.0 +============= + +:Released: 2016-05-25 +:Maintainer: Kostiantyn Rybnikov <k-bx@k-bx.com> + +Additions +--------- + +* Support matching 'not equal' with “!=”. + +Changes +------- + +* Make separate builds for tests on Travis CI. + + +Version 2.4.2 +============= + +:Released: 2016-05-16 +:Maintainer: Kostiantyn Rybnikov <k-bx@k-bx.com> + +Changes +------- + +* Migrate README document to reStructuredText format. + +* Use Setuptools for distribution management. + +* Migrate test cases to Py.test. + +* Add configuration for Tox test runner. + + +Version 2.4.1 +============= + +:Released: 2016-03-04 +:Maintainer: Kostiantyn Rybnikov <k-bx@k-bx.com> + +Additions +--------- + +* [GitHub issue #23] Compare build component of a version. + + +Version 2.4.0 +============= + +:Released: 2016-02-12 +:Maintainer: Kostiantyn Rybnikov <k-bx@k-bx.com> + +Bug Fixes +--------- + +* [GitHub issue #21] Compare alphanumeric components correctly. + + +Version 2.3.1 +============= + +:Released: 2016-01-30 +:Maintainer: Kostiantyn Rybnikov <k-bx@k-bx.com> + +Additions +--------- + +* Declare granted license name in distribution metadata. + + +Version 2.3.0 +============= + +:Released: 2016-01-29 +:Maintainer: Kostiantyn Rybnikov <k-bx@k-bx.com> + +Additions +--------- + +* Add functions to increment prerelease and build components in a + version. + + +Version 2.2.1 +============= + +:Released: 2015-08-04 +:Maintainer: Kostiantyn Rybnikov <k-bx@k-bx.com> + +Bug Fixes +--------- + +* Correct comparison when any component includes zero. + + +Version 2.2.0 +============= + +:Released: 2015-06-21 +:Maintainer: Kostiantyn Rybnikov <k-bx@k-bx.com> + +Additions +--------- + +* Add functions to determined minimum and maximum version. + +* Add code examples for recently-added functions. + + +Version 2.1.2 +============= + +:Released: 2015-05-23 +:Maintainer: Kostiantyn Rybnikov <k-bx@k-bx.com> + +Bug Fixes +--------- + +* Restore current README document to distribution manifest. + + +Version 2.1.1 +============= + +:Released: 2015-05-23 +:Maintainer: Kostiantyn Rybnikov <k-bx@k-bx.com> + +Bug Fixes +--------- + +* Remove absent document from distribution manifest. + + +Version 2.1.0 +============= + +:Released: 2015-05-22 +:Maintainer: Kostiantyn Rybnikov <k-bx@k-bx.com> + +Additions +--------- + +* Document installation instructions. + +* Document project home page. + +* Add function to format a version string from components. + +* Add functions to increment specific components in a version. + +Changes +------- + +* Migrate README document to Markdown format. + +Bug Fixes +--------- + +* Correct code examples in README document. + + +Version 2.0.2 +============= + +:Released: 2015-04-14 +:Maintainer: Konstantine Rybnikov <k-bx@k-bx.com> + +Additions +--------- + +* Add configuration for Travis continuous integration. + +* Explicitly declare supported Python versions. + + +Version 2.0.1 +============= + +:Released: 2014-09-24 +:Maintainer: Konstantine Rybnikov <k-bx@k-bx.com> + +Bug Fixes +--------- + +* [GitHub issue #9] Correct comparison of equal version strings. + + +Version 2.0.0 +============= + +:Released: 2014-05-24 +:Maintainer: Konstantine Rybnikov <k-bx@k-bx.com> + +Additions +--------- + +* Grant license in this code base under BSD 3-clause license terms. + +Changes +------- + +* Update parser to SemVer standard 2.0.0. + +* Ignore build component for comparison. + + +Version 0.0.2 +============= + +:Released: 2012-05-10 +:Maintainer: Konstantine Rybnikov <k-bx@k-bx.com> + +Changes +------- + +* Use standard library Distutils for distribution management. + + +Version 0.0.1 +============= + +:Released: 2012-04-28 +:Maintainer: Konstantine Rybnikov <kost-bebix@yandex.ru> + +* Initial release. + + +.. + Local variables: + coding: utf-8 + mode: text + mode: rst + End: + vim: fileencoding=utf-8 filetype=rst : diff --git a/circuitpython/tools/python-semver/CONTRIBUTORS b/circuitpython/tools/python-semver/CONTRIBUTORS new file mode 100644 index 0000000..6841099 --- /dev/null +++ b/circuitpython/tools/python-semver/CONTRIBUTORS @@ -0,0 +1,45 @@ +############ +Contributors +############ + +Python SemVer library +##################### + +This document records the primary maintainers and significant +contributors to this code base. + +Thank you to everyone whose work has made this possible. + + +Primary maintainers +=================== + +* Kostiantyn Rybnikov <k-bx@k-bx.com> + + +Significant contributors +======================== + +* Alexander Puzynia <werwolf.by@gmail.com> +* Alexander Shorin <kxepal@gmail.com> +* Anton Talevnin <TalAntR@users.noreply.github.com> +* Ben Finney <ben+python@benfinney.id.au> +* Carles Barrobés <carles@barrobes.com> +* Craig Blaszczyk <masterjakul@gmail.com> +* Jan Pieter Waagmeester <jieter@jieter.nl> +* Jelo Agnasin <jelo@icannhas.com> +* Karol Werner <karol.werner@codete.co> +* Peter Bittner <django@bittner.it> +* robi-wan <robi-wan@suyu.de> +* T. Jameson Little <t.jameson.little@gmail.com> +* Tuure Laurinolli <tuure@laurinolli.net> +* Tyler Cross <tyler@crosscollab.com> +* Zack Lalanne <zack.lalanne@gmail.com> + +.. + Local variables: + coding: utf-8 + mode: text + mode: rst + End: + vim: fileencoding=utf-8 filetype=rst : diff --git a/circuitpython/tools/python-semver/LICENSE.txt b/circuitpython/tools/python-semver/LICENSE.txt new file mode 100644 index 0000000..f98e22b --- /dev/null +++ b/circuitpython/tools/python-semver/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2013, Konstantine Rybnikov +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + Neither the name of the {organization} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/circuitpython/tools/python-semver/MANIFEST.in b/circuitpython/tools/python-semver/MANIFEST.in new file mode 100644 index 0000000..9561fb1 --- /dev/null +++ b/circuitpython/tools/python-semver/MANIFEST.in @@ -0,0 +1 @@ +include README.rst diff --git a/circuitpython/tools/python-semver/Makefile b/circuitpython/tools/python-semver/Makefile new file mode 100644 index 0000000..f1272fa --- /dev/null +++ b/circuitpython/tools/python-semver/Makefile @@ -0,0 +1,5 @@ +sdist: + python setup.py sdist bdist_wheel --universal + +.PHONY sdist + diff --git a/circuitpython/tools/python-semver/README.rst b/circuitpython/tools/python-semver/README.rst new file mode 100644 index 0000000..ecc95e1 --- /dev/null +++ b/circuitpython/tools/python-semver/README.rst @@ -0,0 +1,113 @@ +Semver |latest-version| +======================= + +|build-status| |python-support| |downloads| |license| + +A Python module for `semantic versioning`_. Simplifies comparing versions. + + +.. |latest-version| image:: https://img.shields.io/pypi/v/semver.svg + :alt: Latest version on PyPI + :target: https://pypi.python.org/pypi/semver +.. |build-status| image:: https://travis-ci.org/k-bx/python-semver.svg?branch=master + :alt: Build status + :target: https://travis-ci.org/k-bx/python-semver +.. |python-support| image:: https://img.shields.io/pypi/pyversions/semver.svg + :target: https://pypi.python.org/pypi/semver + :alt: Python versions +.. |downloads| image:: https://img.shields.io/pypi/dm/semver.svg + :alt: Monthly downloads from PyPI + :target: https://pypi.python.org/pypi/semver +.. |license| image:: https://img.shields.io/pypi/l/semver.svg + :alt: Software license + :target: https://github.com/k-bx/python-semver/blob/master/LICENSE.txt +.. _semantic versioning: http://semver.org/ + +Usage +----- + +This module provides just couple of functions, main of which are: + +.. code-block:: python + + >>> import semver + >>> semver.compare("1.0.0", "2.0.0") + -1 + >>> semver.compare("2.0.0", "1.0.0") + 1 + >>> semver.compare("2.0.0", "2.0.0") + 0 + >>> semver.match("2.0.0", ">=1.0.0") + True + >>> semver.match("1.0.0", ">1.0.0") + False + >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') + '3.4.5-pre.2+build.4' + >>> version_parts = semver.parse("3.4.5-pre.2+build.4") + >>> version_parts == { + ... 'major': 3, 'minor': 4, 'patch': 5, + ... 'prerelease': 'pre.2', 'build': 'build.4'} + True + >>> version_info = semver.parse_version_info("3.4.5-pre.2+build.4") + >>> version_info + VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') + >>> version_info.major + 3 + >>> version_info > (1, 0) + True + >>> version_info < (3, 5) + True + >>> semver.bump_major("3.4.5") + '4.0.0' + >>> semver.bump_minor("3.4.5") + '3.5.0' + >>> semver.bump_patch("3.4.5") + '3.4.6' + >>> semver.max_ver("1.0.0", "2.0.0") + '2.0.0' + >>> semver.min_ver("1.0.0", "2.0.0") + '1.0.0' + +Installation +------------ + +For Python 2: + +.. code-block:: bash + + pip install semver + +For Python 3: + +.. code-block:: bash + + pip3 install semver + +How to Contribute +----------------- + +When you make changes to the code please run the tests before pushing your +code to your fork and opening a `pull request`_: + +.. code-block:: bash + + python setup.py test + +We use `py.test`_ and `tox`_ to run tests against all supported Python +versions. All test dependencies are resolved automatically, apart from +virtualenv, which for the moment you still may have to install manually: + +.. code-block:: bash + + pip install "virtualenv<14.0.0" # <14.0.0 needed for Python 3.2 only + +You can use the ``clean`` command to remove build and test files and folders: + +.. code-block:: bash + + python setup.py clean + + +.. _pull request: https://github.com/k-bx/python-semver/pulls +.. _py.test: http://pytest.org/ +.. _tox: http://tox.testrun.org/ diff --git a/circuitpython/tools/python-semver/__pycache__/semver.cpython-310.pyc b/circuitpython/tools/python-semver/__pycache__/semver.cpython-310.pyc Binary files differnew file mode 100644 index 0000000..ad2a468 --- /dev/null +++ b/circuitpython/tools/python-semver/__pycache__/semver.cpython-310.pyc diff --git a/circuitpython/tools/python-semver/semver.py b/circuitpython/tools/python-semver/semver.py new file mode 100644 index 0000000..6ed7887 --- /dev/null +++ b/circuitpython/tools/python-semver/semver.py @@ -0,0 +1,371 @@ +""" +Python helper for Semantic Versioning (http://semver.org/) +""" + +import collections +import re + + +__version__ = '2.7.9' +__author__ = 'Kostiantyn Rybnikov' +__author_email__ = 'k-bx@k-bx.com' + +_REGEX = re.compile( + r""" + ^ + (?P<major>(?:0|[1-9][0-9]*)) + \. + (?P<minor>(?:0|[1-9][0-9]*)) + \. + (?P<patch>(?:0|[1-9][0-9]*)) + (\-(?P<prerelease> + (?:0|[1-9A-Za-z-][0-9A-Za-z-]*) + (\.(?:0|[1-9A-Za-z-][0-9A-Za-z-]*))* + ))? + (\+(?P<build> + [0-9A-Za-z-]+ + (\.[0-9A-Za-z-]+)* + ))? + $ + """, re.VERBOSE) + +_LAST_NUMBER = re.compile(r'(?:[^\d]*(\d+)[^\d]*)+') + +if not hasattr(__builtins__, 'cmp'): + def cmp(a, b): + return (a > b) - (a < b) + + +def parse(version): + """Parse version to major, minor, patch, pre-release, build parts. + + :param version: version string + :return: dictionary with the keys 'build', 'major', 'minor', 'patch', + and 'prerelease'. The prerelease or build keys can be None + if not provided + :rtype: dict + """ + match = _REGEX.match(version) + if match is None: + raise ValueError('%s is not valid SemVer string' % version) + + version_parts = match.groupdict() + + version_parts['major'] = int(version_parts['major']) + version_parts['minor'] = int(version_parts['minor']) + version_parts['patch'] = int(version_parts['patch']) + + return version_parts + + +class VersionInfo(collections.namedtuple( + 'VersionInfo', 'major minor patch prerelease build')): + """ + :param int major: version when you make incompatible API changes. + :param int minor: version when you add functionality in + a backwards-compatible manner. + :param int patch: version when you make backwards-compatible bug fixes. + :param str prerelease: an optional prerelease string + :param str build: an optional build string + + >>> import semver + >>> ver = semver.parse('3.4.5-pre.2+build.4') + >>> ver + {'build': 'build.4', 'major': 3, 'minor': 4, 'patch': 5, + 'prerelease': 'pre.2'} + """ + __slots__ = () + + def __eq__(self, other): + if not isinstance(other, (VersionInfo, dict)): + return NotImplemented + return _compare_by_keys(self._asdict(), _to_dict(other)) == 0 + + def __ne__(self, other): + if not isinstance(other, (VersionInfo, dict)): + return NotImplemented + return _compare_by_keys(self._asdict(), _to_dict(other)) != 0 + + def __lt__(self, other): + if not isinstance(other, (VersionInfo, dict)): + return NotImplemented + return _compare_by_keys(self._asdict(), _to_dict(other)) < 0 + + def __le__(self, other): + if not isinstance(other, (VersionInfo, dict)): + return NotImplemented + return _compare_by_keys(self._asdict(), _to_dict(other)) <= 0 + + def __gt__(self, other): + if not isinstance(other, (VersionInfo, dict)): + return NotImplemented + return _compare_by_keys(self._asdict(), _to_dict(other)) > 0 + + def __ge__(self, other): + if not isinstance(other, (VersionInfo, dict)): + return NotImplemented + return _compare_by_keys(self._asdict(), _to_dict(other)) >= 0 + + +def _to_dict(obj): + if isinstance(obj, VersionInfo): + return obj._asdict() + return obj + + +def parse_version_info(version): + """Parse version string to a VersionInfo instance. + + :param version: version string + :return: a :class:`VersionInfo` instance + :rtype: :class:`VersionInfo` + """ + parts = parse(version) + version_info = VersionInfo( + parts['major'], parts['minor'], parts['patch'], + parts['prerelease'], parts['build']) + + return version_info + + +def _nat_cmp(a, b): + def convert(text): + return int(text) if re.match('^[0-9]+$', text) else text + + def split_key(key): + return [convert(c) for c in key.split('.')] + + def cmp_prerelease_tag(a, b): + if isinstance(a, int) and isinstance(b, int): + return cmp(a, b) + elif isinstance(a, int): + return -1 + elif isinstance(b, int): + return 1 + else: + return cmp(a, b) + + a, b = a or '', b or '' + a_parts, b_parts = split_key(a), split_key(b) + for sub_a, sub_b in zip(a_parts, b_parts): + cmp_result = cmp_prerelease_tag(sub_a, sub_b) + if cmp_result != 0: + return cmp_result + else: + return cmp(len(a), len(b)) + + +def _compare_by_keys(d1, d2): + for key in ['major', 'minor', 'patch']: + v = cmp(d1.get(key), d2.get(key)) + if v: + return v + + rc1, rc2 = d1.get('prerelease'), d2.get('prerelease') + rccmp = _nat_cmp(rc1, rc2) + + if not rccmp: + return 0 + if not rc1: + return 1 + elif not rc2: + return -1 + + return rccmp + + +def compare(ver1, ver2): + """Compare two versions + + :param ver1: version string 1 + :param ver2: version string 2 + :return: The return value is negative if ver1 < ver2, + zero if ver1 == ver2 and strictly positive if ver1 > ver2 + :rtype: int + """ + + v1, v2 = parse(ver1), parse(ver2) + + return _compare_by_keys(v1, v2) + + +def match(version, match_expr): + """Compare two versions through a comparison + + :param str version: a version string + :param str match_expr: operator and version; valid operators are + < smaller than + > greater than + >= greator or equal than + <= smaller or equal than + == equal + != not equal + :return: True if the expression matches the version, otherwise False + :rtype: bool + """ + prefix = match_expr[:2] + if prefix in ('>=', '<=', '==', '!='): + match_version = match_expr[2:] + elif prefix and prefix[0] in ('>', '<'): + prefix = prefix[0] + match_version = match_expr[1:] + else: + raise ValueError("match_expr parameter should be in format <op><ver>, " + "where <op> is one of " + "['<', '>', '==', '<=', '>=', '!=']. " + "You provided: %r" % match_expr) + + possibilities_dict = { + '>': (1,), + '<': (-1,), + '==': (0,), + '!=': (-1, 1), + '>=': (0, 1), + '<=': (-1, 0) + } + + possibilities = possibilities_dict[prefix] + cmp_res = compare(version, match_version) + + return cmp_res in possibilities + + +def max_ver(ver1, ver2): + """Returns the greater version of two versions + + :param ver1: version string 1 + :param ver2: version string 2 + :return: the greater version of the two + :rtype: :class:`VersionInfo` + """ + cmp_res = compare(ver1, ver2) + if cmp_res == 0 or cmp_res == 1: + return ver1 + else: + return ver2 + + +def min_ver(ver1, ver2): + """Returns the smaller version of two versions + + :param ver1: version string 1 + :param ver2: version string 2 + :return: the smaller version of the two + :rtype: :class:`VersionInfo` + """ + cmp_res = compare(ver1, ver2) + if cmp_res == 0 or cmp_res == -1: + return ver1 + else: + return ver2 + + +def format_version(major, minor, patch, prerelease=None, build=None): + """Format a version according to the Semantic Versioning specification + + :param str major: the required major part of a version + :param str minor: the required minor part of a version + :param str patch: the required patch part of a version + :param str prerelease: the optional prerelease part of a version + :param str build: the optional build part of a version + :return: the formatted string + :rtype: str + """ + version = "%d.%d.%d" % (major, minor, patch) + if prerelease is not None: + version = version + "-%s" % prerelease + + if build is not None: + version = version + "+%s" % build + + return version + + +def _increment_string(string): + """ + Look for the last sequence of number(s) in a string and increment, from: + http://code.activestate.com/recipes/442460-increment-numbers-in-a-string/#c1 + """ + match = _LAST_NUMBER.search(string) + if match: + next_ = str(int(match.group(1)) + 1) + start, end = match.span(1) + string = string[:max(end - len(next_), start)] + next_ + string[end:] + return string + + +def bump_major(version): + """Raise the major part of the version + + :param: version string + :return: the raised version string + :rtype: str + """ + verinfo = parse(version) + return format_version(verinfo['major'] + 1, 0, 0) + + +def bump_minor(version): + """Raise the minor part of the version + + :param: version string + :return: the raised version string + :rtype: str + """ + verinfo = parse(version) + return format_version(verinfo['major'], verinfo['minor'] + 1, 0) + + +def bump_patch(version): + """Raise the patch part of the version + + :param: version string + :return: the raised version string + :rtype: str + """ + verinfo = parse(version) + return format_version(verinfo['major'], verinfo['minor'], + verinfo['patch'] + 1) + + +def bump_prerelease(version, token='rc'): + """Raise the prerelease part of the version + + :param version: version string + :param token: defaults to 'rc' + :return: the raised version string + :rtype: str + """ + verinfo = parse(version) + verinfo['prerelease'] = _increment_string( + verinfo['prerelease'] or (token or 'rc') + '.0' + ) + return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'], + verinfo['prerelease']) + + +def bump_build(version, token='build'): + """Raise the build part of the version + + :param version: version string + :param token: defaults to 'build' + :return: the raised version string + :rtype: str + """ + verinfo = parse(version) + verinfo['build'] = _increment_string( + verinfo['build'] or (token or 'build') + '.0' + ) + return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'], + verinfo['prerelease'], verinfo['build']) + + +def finalize_version(version): + """Remove any prerelease and build metadata from the version + + :param version: version string + :return: the finalized version string + :rtype: str + """ + verinfo = parse(version) + return format_version(verinfo['major'], verinfo['minor'], verinfo['patch']) diff --git a/circuitpython/tools/python-semver/setup.py b/circuitpython/tools/python-semver/setup.py new file mode 100755 index 0000000..4b02e18 --- /dev/null +++ b/circuitpython/tools/python-semver/setup.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +import semver as package +from glob import glob +from os import remove +from os.path import dirname, join +from setuptools import setup +from setuptools.command.test import test as TestCommand +from shlex import split +from shutil import rmtree + + +class Tox(TestCommand): + user_options = [('tox-args=', 'a', "Arguments to pass to tox")] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.tox_args = None + + def finalize_options(self): + TestCommand.finalize_options(self) + self.test_args = [] + self.test_suite = True + + def run_tests(self): + from tox import cmdline + args = self.tox_args + if args: + args = split(self.tox_args) + errno = cmdline(args=args) + exit(errno) + + +class Clean(TestCommand): + def run(self): + delete_in_root = [ + 'build', + '.cache', + 'dist', + '.eggs', + '*.egg-info', + '.tox', + ] + delete_everywhere = [ + '__pycache__', + '*.pyc', + ] + for candidate in delete_in_root: + rmtree_glob(candidate) + for visible_dir in glob('[A-Za-z0-9]*'): + for candidate in delete_everywhere: + rmtree_glob(join(visible_dir, candidate)) + rmtree_glob(join(visible_dir, '*', candidate)) + + +def rmtree_glob(file_glob): + for fobj in glob(file_glob): + try: + rmtree(fobj) + print('%s/ removed ...' % fobj) + except OSError: + try: + remove(fobj) + print('%s removed ...' % fobj) + except OSError: + pass + + +def read_file(filename): + with open(join(dirname(__file__), filename)) as f: + return f.read() + + +setup( + name=package.__name__, + version=package.__version__, + description=package.__doc__.strip(), + long_description=read_file('README.rst'), + author=package.__author__, + author_email=package.__author_email__, + url='https://github.com/k-bx/python-semver', + download_url='https://github.com/k-bx/python-semver/downloads', + py_modules=[package.__name__], + include_package_data=True, + license='BSD', + classifiers=[ + 'Environment :: Web Environment', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], + tests_require=['tox', 'virtualenv<14.0.0'], + cmdclass={ + 'clean': Clean, + 'test': Tox, + }, +) diff --git a/circuitpython/tools/python-semver/tests.py b/circuitpython/tools/python-semver/tests.py new file mode 100644 index 0000000..a3e2ab7 --- /dev/null +++ b/circuitpython/tools/python-semver/tests.py @@ -0,0 +1,354 @@ +import pytest # noqa + +from semver import compare +from semver import match +from semver import parse +from semver import format_version +from semver import bump_major +from semver import bump_minor +from semver import bump_patch +from semver import bump_prerelease +from semver import bump_build +from semver import finalize_version +from semver import min_ver +from semver import max_ver +from semver import VersionInfo + + +SEMVERFUNCS = [ + compare, match, parse, format_version, + bump_major, bump_minor, bump_patch, bump_prerelease, bump_build, + max_ver, min_ver, finalize_version +] + + +@pytest.mark.parametrize("func", SEMVERFUNCS, + ids=[func.__name__ for func in SEMVERFUNCS]) +def test_fordocstrings(func): + assert func.__doc__, "Need a docstring for function %r" % func.__name + + +def test_should_parse_version(): + result = parse("1.2.3-alpha.1.2+build.11.e0f985a") + assert result == { + 'major': 1, + 'minor': 2, + 'patch': 3, + 'prerelease': 'alpha.1.2', + 'build': 'build.11.e0f985a', + } + + result = parse("1.2.3-alpha-1+build.11.e0f985a") + assert result == { + 'major': 1, + 'minor': 2, + 'patch': 3, + 'prerelease': 'alpha-1', + 'build': 'build.11.e0f985a', + } + + +def test_should_parse_zero_prerelease(): + result = parse("1.2.3-rc.0+build.0") + + assert result == { + 'major': 1, + 'minor': 2, + 'patch': 3, + 'prerelease': 'rc.0', + 'build': 'build.0', + } + + result = parse("1.2.3-rc.0.0+build.0") + + assert result == { + 'major': 1, + 'minor': 2, + 'patch': 3, + 'prerelease': 'rc.0.0', + 'build': 'build.0', + } + + +def test_should_get_less(): + assert compare("1.0.0", "2.0.0") == -1 + assert compare('1.0.0-alpha', '1.0.0-alpha.1') == -1 + assert compare('1.0.0-alpha.1', '1.0.0-alpha.beta') == -1 + assert compare('1.0.0-alpha.beta', '1.0.0-beta') == -1 + assert compare('1.0.0-beta', '1.0.0-beta.2') == -1 + assert compare('1.0.0-beta.2', '1.0.0-beta.11') == -1 + assert compare('1.0.0-beta.11', '1.0.0-rc.1') == -1 + assert compare('1.0.0-rc.1', '1.0.0') == -1 + + +def test_should_get_greater(): + assert compare("2.0.0", "1.0.0") == 1 + assert compare('1.0.0-alpha.1', '1.0.0-alpha') == 1 + assert compare('1.0.0-alpha.beta', '1.0.0-alpha.1') == 1 + assert compare('1.0.0-beta', '1.0.0-alpha.beta') == 1 + assert compare('1.0.0-beta.2', '1.0.0-beta') == 1 + assert compare('1.0.0-beta.11', '1.0.0-beta.2') == 1 + assert compare('1.0.0-rc.1', '1.0.0-beta.11') == 1 + assert compare('1.0.0', '1.0.0-rc.1') == 1 + + +def test_should_match_simple(): + assert match("2.3.7", ">=2.3.6") is True + + +def test_should_no_match_simple(): + assert match("2.3.7", ">=2.3.8") is False + + +def test_should_match_not_equal(): + assert match("2.3.7", "!=2.3.8") is True + assert match("2.3.7", "!=2.3.6") is True + assert match("2.3.7", "!=2.3.7") is False + + +def test_should_not_raise_value_error_for_expected_match_expression(): + assert match("2.3.7", "<2.4.0") is True + assert match("2.3.7", ">2.3.5") is True + + assert match("2.3.7", "<=2.3.9") is True + assert match("2.3.7", ">=2.3.5") is True + assert match("2.3.7", "==2.3.7") is True + assert match("2.3.7", "!=2.3.7") is False + + +def test_should_raise_value_error_for_unexpected_match_expression(): + with pytest.raises(ValueError): + match("2.3.7", "=2.3.7") + with pytest.raises(ValueError): + match("2.3.7", "~2.3.7") + with pytest.raises(ValueError): + match("2.3.7", "^2.3.7") + + +def test_should_raise_value_error_for_zero_prefixed_versions(): + with pytest.raises(ValueError): + parse("01.2.3") + with pytest.raises(ValueError): + parse("1.02.3") + with pytest.raises(ValueError): + parse("1.2.03") + + +def test_should_raise_value_error_for_invalid_value(): + with pytest.raises(ValueError): + compare('foo', 'bar') + with pytest.raises(ValueError): + compare('1.0', '1.0.0') + with pytest.raises(ValueError): + compare('1.x', '1.0.0') + + +def test_should_raise_value_error_for_invalid_match_expression(): + with pytest.raises(ValueError): + match('1.0.0', '') + with pytest.raises(ValueError): + match('1.0.0', '!') + with pytest.raises(ValueError): + match('1.0.0', '1.0.0') + + +def test_should_follow_specification_comparison(): + """ + produce comparison chain: + 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta.2 < 1.0.0-beta.11 + < 1.0.0-rc.1 < 1.0.0-rc.1+build.1 < 1.0.0 < 1.0.0+0.3.7 < 1.3.7+build + < 1.3.7+build.2.b8f12d7 < 1.3.7+build.11.e0f985a + and in backward too. + """ + chain = [ + '1.0.0-alpha', '1.0.0-alpha.1', '1.0.0-beta.2', '1.0.0-beta.11', + '1.0.0-rc.1', '1.0.0', '1.3.7+build', + ] + versions = zip(chain[:-1], chain[1:]) + for low_version, high_version in versions: + assert compare(low_version, high_version) == -1, \ + '%s should be lesser than %s' % (low_version, high_version) + assert compare(high_version, low_version) == 1, \ + '%s should be higher than %s' % (high_version, low_version) + + +def test_should_compare_rc_builds(): + assert compare('1.0.0-beta.2', '1.0.0-beta.11') == -1 + + +def test_should_compare_release_candidate_with_release(): + assert compare('1.0.0-rc.1', '1.0.0') == -1 + assert compare('1.0.0-rc.1+build.1', '1.0.0') == -1 + + +def test_should_say_equal_versions_are_equal(): + assert compare('2.0.0', '2.0.0') == 0 + assert compare('1.1.9-rc.1', '1.1.9-rc.1') == 0 + assert compare('1.1.9+build.1', '1.1.9+build.1') == 0 + assert compare('1.1.9-rc.1+build.1', '1.1.9-rc.1+build.1') == 0 + + +def test_should_compare_versions_with_build_and_release(): + assert compare('1.1.9-rc.1', '1.1.9-rc.1+build.1') == 0 + assert compare('1.1.9-rc.1', '1.1.9+build.1') == -1 + + +def test_should_ignore_builds_on_compare(): + assert compare('1.0.0+build.1', '1.0.0') == 0 + assert compare('1.0.0-alpha.1+build.1', '1.0.0-alpha.1') == 0 + assert compare('1.0.0+build.1', '1.0.0-alpha.1') == 1 + assert compare('1.0.0+build.1', '1.0.0-alpha.1+build.1') == 1 + + +def test_should_correctly_format_version(): + assert format_version(3, 4, 5) == '3.4.5' + assert format_version(3, 4, 5, 'rc.1') == '3.4.5-rc.1' + assert format_version(3, 4, 5, prerelease='rc.1') == '3.4.5-rc.1' + assert format_version(3, 4, 5, build='build.4') == '3.4.5+build.4' + assert format_version(3, 4, 5, 'rc.1', 'build.4') == '3.4.5-rc.1+build.4' + + +def test_should_bump_major(): + assert bump_major('3.4.5') == '4.0.0' + + +def test_should_bump_minor(): + assert bump_minor('3.4.5') == '3.5.0' + + +def test_should_bump_patch(): + assert bump_patch('3.4.5') == '3.4.6' + + +def test_should_ignore_extensions_for_bump(): + assert bump_patch('3.4.5-rc1+build4') == '3.4.6' + + +def test_should_get_max(): + assert max_ver('3.4.5', '4.0.2') == '4.0.2' + + +def test_should_get_min(): + assert min_ver('3.4.5', '4.0.2') == '3.4.5' + + +def test_should_get_min_same(): + assert min_ver('3.4.5', '3.4.5') == '3.4.5' + + +def test_should_get_more_rc1(): + assert compare("1.0.0-rc1", "1.0.0-rc0") == 1 + + +def test_prerelease_order(): + assert min_ver('1.2.3-rc.2', '1.2.3-rc.10') == '1.2.3-rc.2' + assert min_ver('1.2.3-rc2', '1.2.3-rc10') == '1.2.3-rc10' + # identifiers with letters or hyphens are compared lexically in ASCII sort + # order. + assert min_ver('1.2.3-Rc10', '1.2.3-rc10') == '1.2.3-Rc10' + # Numeric identifiers always have lower precedence than non-numeric + # identifiers. + assert min_ver('1.2.3-2', '1.2.3-rc') == '1.2.3-2' + # A larger set of pre-release fields has a higher precedence than a + # smaller set, if all of the preceding identifiers are equal. + assert min_ver('1.2.3-rc.2.1', '1.2.3-rc.2') == '1.2.3-rc.2' + # When major, minor, and patch are equal, a pre-release version has lower + # precedence than a normal version. + assert min_ver('1.2.3', '1.2.3-1') == '1.2.3-1' + assert min_ver('1.0.0-alpha', '1.0.0-alpha.1') == '1.0.0-alpha' + + +def test_should_bump_prerelease(): + assert bump_prerelease('3.4.5-rc.9') == '3.4.5-rc.10' + assert bump_prerelease('3.4.5') == '3.4.5-rc.1' + assert bump_prerelease('3.4.5', 'dev') == '3.4.5-dev.1' + assert bump_prerelease('3.4.5', '') == '3.4.5-rc.1' + + +def test_should_ignore_build_on_prerelease_bump(): + assert bump_prerelease('3.4.5-rc.1+build.4') == '3.4.5-rc.2' + + +def test_should_bump_build(): + assert bump_build('3.4.5-rc.1+build.9') == '3.4.5-rc.1+build.10' + assert bump_build('3.4.5-rc.1+0009.dev') == '3.4.5-rc.1+0010.dev' + assert bump_build('3.4.5-rc.1') == '3.4.5-rc.1+build.1' + assert bump_build('3.4.5') == '3.4.5+build.1' + assert bump_build('3.4.5', 'nightly') == '3.4.5+nightly.1' + assert bump_build('3.4.5', '') == '3.4.5+build.1' + + +def test_should_finalize_version(): + assert finalize_version('1.2.3') == '1.2.3' + assert finalize_version('1.2.3-rc.5') == '1.2.3' + assert finalize_version('1.2.3+build.2') == '1.2.3' + assert finalize_version('1.2.3-rc.1+build.5') == '1.2.3' + assert finalize_version('1.2.3-alpha') == '1.2.3' + assert finalize_version('1.2.0') == '1.2.0' + + +def test_should_compare_version_info_objects(): + v1 = VersionInfo(major=0, minor=10, patch=4, prerelease=None, build=None) + v2 = VersionInfo( + major=0, minor=10, patch=4, prerelease='beta.1', build=None) + + # use `not` to enforce using comparision operators + assert v1 != v2 + assert v1 > v2 + assert v1 >= v2 + assert not(v1 < v2) + assert not(v1 <= v2) + assert not(v1 == v2) + + v3 = VersionInfo(major=0, minor=10, patch=4, prerelease=None, build=None) + + assert not(v1 != v3) + assert not(v1 > v3) + assert v1 >= v3 + assert not(v1 < v3) + assert v1 <= v3 + assert v1 == v3 + + v4 = VersionInfo(major=0, minor=10, patch=5, prerelease=None, build=None) + assert v1 != v4 + assert not(v1 > v4) + assert not(v1 >= v4) + assert v1 < v4 + assert v1 <= v4 + assert not(v1 == v4) + + +def test_should_compare_version_dictionaries(): + v1 = VersionInfo(major=0, minor=10, patch=4, prerelease=None, build=None) + v2 = dict(major=0, minor=10, patch=4, prerelease='beta.1', build=None) + + assert v1 != v2 + assert v1 > v2 + assert v1 >= v2 + assert not(v1 < v2) + assert not(v1 <= v2) + assert not(v1 == v2) + + v3 = dict(major=0, minor=10, patch=4, prerelease=None, build=None) + + assert not(v1 != v3) + assert not(v1 > v3) + assert v1 >= v3 + assert not(v1 < v3) + assert v1 <= v3 + assert v1 == v3 + + v4 = dict(major=0, minor=10, patch=5, prerelease=None, build=None) + assert v1 != v4 + assert not(v1 > v4) + assert not(v1 >= v4) + assert v1 < v4 + assert v1 <= v4 + assert not(v1 == v4) + + +def test_should_compare_prerelease_with_numbers_and_letters(): + v1 = VersionInfo(major=1, minor=9, patch=1, prerelease='1unms', build=None) + v2 = VersionInfo(major=1, minor=9, patch=1, prerelease=None, build='1asd') + assert v1 < v2 + assert compare("1.9.1-1unms", "1.9.1+1") == -1 diff --git a/circuitpython/tools/python-semver/tox.ini b/circuitpython/tools/python-semver/tox.ini new file mode 100644 index 0000000..c909157 --- /dev/null +++ b/circuitpython/tools/python-semver/tox.ini @@ -0,0 +1,16 @@ +[tox] +envlist = + flake8 + py{26,27,32,33,34,35,36} + pypy + +[testenv] +commands = py.test -q tests.py +deps = pytest +setenv = + PIP_DISABLE_PIP_VERSION_CHECK = 1 + +[testenv:flake8] +basepython = python3.4 +deps = flake8 +commands = flake8 |