├── setup.cfg ├── nodesemver ├── tests │ ├── __init__.py │ ├── todo.md │ ├── test_range_test_method.py │ ├── test_recovery.py │ ├── test_min_satisfying.py │ ├── test_regression.py │ ├── test_invalid_version_number.py │ ├── test_passing_bytes.py │ ├── test_strict_vs_loose_version_number.py │ ├── test_sort.py │ ├── test_intersect_comparators.py │ ├── test_negative_range.py │ ├── test_comparators.py │ ├── test_valid_range.py │ ├── test_for_4digit.py │ ├── test_comparison.py │ ├── test_max_satisfying.py │ ├── test_equality.py │ ├── test_range.py │ └── test_increment_version.py └── __init__.py ├── MANIFEST.in ├── tox.ini ├── .travis.yml ├── Makefile ├── CHANGES.txt ├── .gitignore ├── LICENSE ├── examples └── readme.py ├── .github └── workflows │ └── python-package.yml ├── README.rst └── setup.py /setup.cfg: -------------------------------------------------------------------------------- 1 | [tool:pytest] 2 | addopts = -s 3 | -------------------------------------------------------------------------------- /nodesemver/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft examples 2 | graft semver/tests 3 | global-exclude __pycache__ 4 | global-exclude *.py[co] 5 | -------------------------------------------------------------------------------- /nodesemver/tests/todo.md: -------------------------------------------------------------------------------- 1 | https://github.com/npm/node-semver/blob/master/test/index.js 2 | 3 | - diff versions test 4 | - impement min_satisfying 5 | - implement intersects 6 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py33,py27 3 | 4 | [testenv] 5 | commands = 6 | pip install -e .[testing] 7 | python setup.py test 8 | 9 | [testenv:py27] 10 | basepython = /opt/local/Library/Frameworks/Python.framework/Versions/2.7/bin/python 11 | 12 | [testenv:py33] 13 | basepython = /opt/local/bin//python3.3 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.4" 4 | - "3.5" 5 | - "3.6" 6 | - "3.6-dev" # 3.6 development branch 7 | - "nightly" 8 | # command to install dependencies 9 | install: 10 | - pip install -e . 11 | # command to run tests 12 | script: 13 | - pytest # or py.test for Python versions 3.5 and below 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | pytest 3 | 4 | # DST=README.rst 5 | # SED=$(shell which gsed 2>/dev/null || which sed) 6 | # default: 7 | # echo semver > ${DST} 8 | # echo ================= >> ${DST} 9 | # echo "" >> ${DST} 10 | # echo "python version of [node-semver](https://github.com/isaacs/node-semver)" >> ${DST} 11 | # echo "" >> ${DST} 12 | # echo ".. code:: python\n" >> ${DST} 13 | # cat ./examples/readme.py | $(SED) 's/^\(.\)/ \1/g' >> ${DST} 14 | -------------------------------------------------------------------------------- /nodesemver/tests/test_range_test_method.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import pytest 3 | 4 | cands = [ 5 | ['>=1.2.3', '2.0.0-pre', False, False], 6 | ] 7 | 8 | 9 | @pytest.mark.parametrize("range_, version, loose, expected", cands) 10 | def test_it(range_, version, loose, expected): 11 | from nodesemver import make_semver, satisfies 12 | # assert expected == make_semver(range_, loose=loose).test(version) 13 | assert expected == satisfies(version, range_, loose=loose) 14 | -------------------------------------------------------------------------------- /nodesemver/tests/test_recovery.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import pytest 3 | 4 | 5 | @pytest.mark.skip(reason="not support") 6 | def test_it(): 7 | from nodesemver import max_satisfying 8 | assert max_satisfying(["2.4.3", "2.4.4", "2.5b", "3.0.1-b"], "~2", True) == "2.5b" 9 | 10 | 11 | @pytest.mark.skip(reason="not support") 12 | def test_it2(): 13 | from nodesemver import max_satisfying 14 | assert max_satisfying(["2b", "3.0.1-b"], "~2", True) == "2b" 15 | 16 | 17 | @pytest.mark.skip(reason="not support") 18 | def test_it3(): 19 | from nodesemver import max_satisfying 20 | assert max_satisfying(["2.5b", "v2010.07.06dev"], "~2", True) == "2.5b" 21 | -------------------------------------------------------------------------------- /nodesemver/tests/test_min_satisfying.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import pytest 3 | # node-semver/test/index.js 4 | 5 | cands = [ 6 | [['1.2.3', '1.2.4'], '1.2', '1.2.3', False], 7 | [['1.2.4', '1.2.3'], '1.2', '1.2.3', False], 8 | [['1.2.3', '1.2.4', '1.2.5', '1.2.6'], '~1.2.3', '1.2.3', False], 9 | [['1.1.0', '1.2.0', '1.2.1', '1.3.0', '2.0.0b1', '2.0.0b2', '2.0.0b3', '2.0.0', '2.1.0'], '~2.0.0', '2.0.0', True] 10 | ] 11 | 12 | 13 | 14 | @pytest.mark.parametrize("versions, range_, expect, loose", cands) 15 | def test_it(versions, range_, expect, loose): 16 | from nodesemver import min_satisfying 17 | assert min_satisfying(versions, range_, loose) == expect 18 | -------------------------------------------------------------------------------- /nodesemver/tests/test_regression.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | cands = [ 4 | # https://github.com/podhmo/python-node-semver/issues/5 5 | ["<=1.2", "1.2.0", ["1.1.1", "1.2.0-pre", "1.2.0", "1.1.1-111", "1.1.1-21"]], 6 | ["<=1.2", "1.2", ["1.1.1", "1.2.0-pre", "1.2", "1.1.1-111", "1.1.1-21"]], 7 | ["<=1.2.0", "1.2.0", ["1.1.1", "1.2.0-pre", "1.2.0", "1.1.1-111", "1.1.1-21"]], 8 | ["<=1.2.0", "1.2", ["1.1.1", "1.2.0-pre", "1.2", "1.1.1-111", "1.1.1-21"]], 9 | ] 10 | 11 | 12 | @pytest.mark.parametrize("op, wanted, cands", cands) 13 | def test_it(op, wanted, cands): 14 | from nodesemver import max_satisfying 15 | got = max_satisfying(cands, op, loose=True) 16 | assert got == wanted 17 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 0.9.0 2 | 3 | - RENAME MODULE semver -> nodesemver (#39) 4 | - pre release range fix (#40) 5 | 6 | 0.8.1 7 | 8 | - fix erroneous parsing of $ component version numbers (#44) 9 | 10 | 0.8.0 11 | 12 | - handle 4-digit version correctly (#35) 13 | 14 | 0.7.0 15 | 16 | - include tests in source distributions (#31) 17 | 18 | 0.6.0 19 | 20 | - more strict error handling (InvalidTypeIncluded is added) 21 | 22 | 0.5.1 23 | 24 | - bug fix 25 | 26 | 0.5.0 27 | 28 | - include_prerelease option is added 29 | 30 | 0.4.2 31 | 32 | - fix bug for support 4-digit (handling prelease and build) 33 | 34 | 0.4.0 35 | 36 | - suport 4-digit version (e.g. x.y.z.a) 37 | 38 | 0.3.0 39 | 40 | - drop python2.x support 41 | - bug fix, Add sort key function #14 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | .eggs 11 | venv/ 12 | env/ 13 | bin/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # Installer logs 28 | pip-log.txt 29 | pip-delete-this-directory.txt 30 | 31 | # Unit test / coverage reports 32 | htmlcov/ 33 | .tox/ 34 | .coverage 35 | .cache 36 | nosetests.xml 37 | coverage.xml 38 | 39 | # Translations 40 | *.mo 41 | 42 | # Mr Developer 43 | .mr.developer.cfg 44 | .project 45 | .pydevproject 46 | 47 | # Rope 48 | .ropeproject 49 | 50 | # Django stuff: 51 | *.log 52 | *.pot 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | # misc 58 | node_modules 59 | -------------------------------------------------------------------------------- /nodesemver/tests/test_invalid_version_number.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import pytest 3 | # node-semver/test/index.js 4 | 5 | cands = [ 6 | ('1.2.3.4', False, ValueError), 7 | ('NOT VALID', False, ValueError), 8 | (1.2, False, ValueError), 9 | ("1.2", False, ValueError), 10 | ("1.a.2", False, ValueError), 11 | (None, False, ValueError), 12 | ('X.2', False, ValueError), 13 | ('Infinity.NaN.Infinity', False, ValueError), 14 | ('1.2.3.4', True, None), 15 | ('NOT VALID', True, ValueError), 16 | (1.2, True, ValueError), 17 | ("1.2", True, None), 18 | ("1.a.2", True, ValueError), 19 | (None, True, ValueError), 20 | ('Infinity.NaN.Infinity', True, ValueError), 21 | ('X.2', True, ValueError), 22 | ] 23 | 24 | 25 | @pytest.mark.parametrize("v, loose, exc", cands) 26 | def test_it(v, loose, exc): 27 | import pytest 28 | from nodesemver import make_semver 29 | if exc is not None: 30 | with pytest.raises(exc): 31 | make_semver(v, loose) 32 | else: 33 | make_semver(v, loose) 34 | -------------------------------------------------------------------------------- /nodesemver/tests/test_passing_bytes.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_max_satisfying(): 5 | def _callFUT(versions, range_): 6 | from nodesemver import max_satisfying 7 | max_satisfying(versions, range_) 8 | 9 | from nodesemver import InvalidTypeIncluded 10 | with pytest.raises(InvalidTypeIncluded): 11 | _callFUT([b"1.0.0"], "1.0.0") 12 | with pytest.raises(InvalidTypeIncluded): 13 | _callFUT(["1.0.0"], b"1.0.0") 14 | with pytest.raises(InvalidTypeIncluded): 15 | _callFUT("1.0.0", [b"1.0.0"]) # mistakes 16 | with pytest.raises(InvalidTypeIncluded): 17 | _callFUT(b"1.0.0", ["1.0.0"]) # mistakes 18 | _callFUT(["1.0.0"], "1.0.0") 19 | 20 | 21 | def test_satisfies(): 22 | def _callFUT(version, range_): 23 | from nodesemver import satisfies 24 | satisfies(version, range_) 25 | 26 | from nodesemver import InvalidTypeIncluded 27 | with pytest.raises(InvalidTypeIncluded): 28 | _callFUT(b"1.0.0", "1.0.0") 29 | with pytest.raises(InvalidTypeIncluded): 30 | _callFUT("1.0.0", b"1.0.0") 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 podhmo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/readme.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from nodesemver import satisfies 3 | 4 | assert satisfies("1.2.3-dev.1+abc", ">1.1.0 <2.0.0", include_prerelease=True) 5 | assert not satisfies("1.2.3-dev.1+abc", ">1.1.0 <2.0.0", include_prerelease=False) 6 | assert satisfies("1.2.3", ">1.1 <2.0") 7 | 8 | 9 | from nodesemver import max_satisfying 10 | 11 | versions = ['1.2.3', '1.2.4', '1.2.5', '1.2.6', '2.0.1'] 12 | range_ = '~1.2.3' 13 | assert max_satisfying(versions, range_, loose=False) == '1.2.6' 14 | 15 | 16 | versions = ['1.1.0', '1.2.0', '1.2.1', '1.3.0', '2.0.0b1', '2.0.0b2', '2.0.0b3', '2.0.0', '2.1.0'] 17 | range_ = '~2.0.0' 18 | assert max_satisfying(versions, range_, loose=True) == '2.0.0' 19 | 20 | try: 21 | (max_satisfying(versions, range_, loose=False) == '2.0.0') 22 | except ValueError as e: 23 | assert e.args[0] == "Invalid Version: 2.0.0b1" 24 | 25 | 26 | versions = ['1.2.3', '1.2.4', '1.2.5', '1.2.6-pre.1', '2.0.1'] 27 | range_ = '~1.2.3' 28 | assert max_satisfying(versions, range_, loose=False, include_prerelease=True) == '1.2.6-pre.1' 29 | assert max_satisfying(versions, range_, loose=False, include_prerelease=False) == '1.2.5' 30 | -------------------------------------------------------------------------------- /nodesemver/tests/test_strict_vs_loose_version_number.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import pytest 3 | # node-semver/test/index.js 4 | 5 | cands = [ 6 | ['=1.2.3', '1.2.3'], 7 | ['01.02.03', '1.2.3'], 8 | ['1.2.3-beta.01', '1.2.3-beta.1'], 9 | [' =1.2.3', '1.2.3'], 10 | ['1.2.3foo', '1.2.3-foo'] 11 | ] 12 | 13 | 14 | @pytest.mark.parametrize("loose, strict", cands) 15 | def test_it(loose, strict): 16 | import pytest 17 | from nodesemver import make_semver, eq 18 | 19 | with pytest.raises(ValueError): 20 | make_semver(loose, False) 21 | 22 | lv = make_semver(loose, True) 23 | assert lv.version == strict 24 | assert eq(loose, strict, True) is True 25 | 26 | with pytest.raises(ValueError): 27 | eq(loose, strict, False) 28 | 29 | with pytest.raises(ValueError): 30 | make_semver(strict, False).compare(loose) 31 | 32 | 33 | cands = [ 34 | ['>=01.02.03', '>=1.2.3'], 35 | ['~1.02.03beta', '>=1.2.3-beta <1.3.0'] 36 | ] 37 | 38 | 39 | @pytest.mark.parametrize("loose, comps", cands) 40 | def test_it_for_range(loose, comps): 41 | import pytest 42 | from nodesemver import make_range 43 | 44 | with pytest.raises(ValueError): 45 | make_range(loose, False) 46 | 47 | assert make_range(loose, True).range == comps 48 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python package 5 | 6 | on: 7 | pull_request 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | python-version: [3.7, 3.8, 3.9] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Set up Python ${{ matrix.python-version }} 21 | uses: actions/setup-python@v2 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | - name: Install dependencies 25 | run: | 26 | python -m pip install --upgrade pip 27 | python -m pip install flake8 pytest 28 | python -m pip install -e . 29 | - name: Lint with flake8 30 | run: | 31 | # stop the build if there are Python syntax errors or undefined names 32 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 33 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 34 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 35 | - name: Test with pytest 36 | run: | 37 | pytest 38 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | node-semver 2 | ================= 3 | 4 | .. image:: https://github.com/podhmo/python-node-semver/actions/workflows/python-package.yml/badge.svg 5 | :target: https://github.com/podhmo/python-node-semver/actions/workflows/python-package.yml 6 | 7 | python version of [node-semver](https://github.com/isaacs/node-semver) 8 | 9 | install 10 | ---------------------------------------- 11 | 12 | .. code-block:: console 13 | 14 | pip install node-semver 15 | 16 | examples 17 | ---------------------------------------- 18 | 19 | .. code-block:: python 20 | 21 | from nodesemver import max_satisfying 22 | 23 | versions = ['1.2.3', '1.2.4', '1.2.5', '1.2.6', '2.0.1'] 24 | range_ = '~1.2.3' 25 | assert max_satisfying(versions, range_, loose=False) == '1.2.6' 26 | 27 | 28 | versions = ['1.1.0', '1.2.0', '1.2.1', '1.3.0', '2.0.0b1', '2.0.0b2', '2.0.0b3', '2.0.0', '2.1.0'] 29 | range_ = '~2.0.0' 30 | assert max_satisfying(versions, range_, loose=True) == '2.0.0' 31 | 32 | try: 33 | (max_satisfying(versions, range_, loose=False) == '2.0.0') 34 | except ValueError as e: 35 | assert e.args[0] == "Invalid Version: 2.0.0b1" 36 | 37 | versions = ['1.2.3', '1.2.4', '1.2.5', '1.2.6-pre.1', '2.0.1'] 38 | range_ = '~1.2.3' 39 | assert max_satisfying(versions, range_, loose=False, include_prerelease=True) == '1.2.6-pre.1' 40 | assert max_satisfying(versions, range_, loose=False, include_prerelease=False) == '1.2.5' 41 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import os 4 | import sys 5 | 6 | from setuptools import setup, find_packages 7 | 8 | here = os.path.abspath(os.path.dirname(__file__)) 9 | try: 10 | with open(os.path.join(here, "README.rst")) as f: 11 | README = f.read() 12 | with open(os.path.join(here, "CHANGES.txt")) as f: 13 | CHANGES = f.read() 14 | except IOError: 15 | README = CHANGES = "" 16 | 17 | install_requires = [] 18 | 19 | docs_extras = [] 20 | 21 | tests_require = ["pytest"] 22 | testing_extras = tests_require + [] 23 | 24 | from setuptools.command.test import test as TestCommand 25 | 26 | 27 | class PyTest(TestCommand): 28 | def finalize_options(self): 29 | TestCommand.finalize_options(self) 30 | self.test_args = [] 31 | self.test_suite = True 32 | 33 | def run_tests(self): 34 | import pytest 35 | 36 | pytest.main(self.test_args) 37 | 38 | 39 | setup( 40 | name="node-semver", 41 | version="0.9.0", 42 | description="port of node-semver", 43 | long_description=README + "\n\n" + CHANGES, 44 | classifiers=[ 45 | "Programming Language :: Python", 46 | "Programming Language :: Python :: 3", 47 | "Programming Language :: Python :: Implementation :: CPython", 48 | ], 49 | keywords="version semver node-semver", 50 | author="podhmo", 51 | author_email="ababjam61+github@gmail.com", 52 | url="https://github.com/podhmo/python-node-semver", 53 | packages=find_packages(exclude=["nodesemver.tests"]), 54 | include_package_data=True, 55 | zip_safe=False, 56 | install_requires=install_requires, 57 | extras_require={"testing": testing_extras, "docs": docs_extras}, 58 | tests_require=tests_require, 59 | cmdclass={"test": PyTest}, 60 | entry_points=""" """, 61 | license="mit", 62 | ) 63 | -------------------------------------------------------------------------------- /nodesemver/tests/test_sort.py: -------------------------------------------------------------------------------- 1 | 2 | versions = [ 3 | '0.0.0-foo', 4 | '0.0.0', 5 | '0.0.0', 6 | '0.0.1', 7 | '0.9.0', 8 | '0.9.9', 9 | '0.10.0', 10 | '0.10.0', 11 | '0.10.0', 12 | '0.99.0', 13 | '0.99.0', 14 | '1.0.0', 15 | '1.0.0', 16 | '1.2.3-4', 17 | '1.2.3-4', 18 | '1.2.3-5', 19 | '1.2.3-5', 20 | '1.2.3-4-foo', 21 | '1.2.3-5-Foo', 22 | '1.2.3-5-foo', 23 | '1.2.3-5-foo', 24 | '1.2.3-R2', 25 | '1.2.3-a', 26 | '1.2.3-a.5', 27 | '1.2.3-a.5', 28 | '1.2.3-a.10', 29 | '1.2.3-a.b', 30 | '1.2.3-a.b', 31 | '1.2.3-a.b.c.5.d.100', 32 | '1.2.3-a.b.c.10.d.5', 33 | '1.2.3-asdf', 34 | '1.2.3-r100', 35 | '1.2.3-r100', 36 | '1.2.3-r2', 37 | '1.2.3', 38 | '1.2.3', 39 | '2.0.0', 40 | '2.0.0', 41 | '2.7.2+asdf', 42 | '3.0.0', 43 | ] 44 | 45 | 46 | spec_prerelease_examples = [ 47 | '1.0.0-alpha', 48 | '1.0.0-alpha.1', 49 | '1.0.0-alpha.beta', 50 | '1.0.0-beta', 51 | '1.0.0-beta.2', 52 | '1.0.0-beta.11', 53 | '1.0.0-rc.1', 54 | '1.0.0', 55 | ] 56 | 57 | 58 | def _shuffled_copy(source, seed=0): 59 | import random 60 | random.seed(seed) 61 | _copy = source[:] 62 | random.shuffle(_copy) 63 | # assert _copy != source 64 | return _copy 65 | 66 | 67 | def test_sort(): 68 | from nodesemver import sort 69 | to_sort = _shuffled_copy(versions) 70 | sort(to_sort, True) 71 | assert versions == to_sort 72 | 73 | 74 | def test_prerelease_sort(): 75 | from nodesemver import sort 76 | to_sort = _shuffled_copy(spec_prerelease_examples) 77 | sort(to_sort, False) 78 | assert spec_prerelease_examples == to_sort 79 | 80 | 81 | def test_rsort(): 82 | from nodesemver import rsort 83 | to_sort = _shuffled_copy(versions) 84 | rsort(to_sort, True) 85 | assert list(reversed(versions)) == to_sort 86 | 87 | 88 | def test_prerelease_rsort(): 89 | from nodesemver import rsort 90 | to_sort = _shuffled_copy(spec_prerelease_examples) 91 | rsort(to_sort, False) 92 | assert list(reversed(spec_prerelease_examples)) == to_sort 93 | -------------------------------------------------------------------------------- /nodesemver/tests/test_intersect_comparators.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import pytest 3 | # node-semver/test/index.js 4 | 5 | cands = [ 6 | # One is a Version 7 | ['1.3.0', '>=1.3.0', True], 8 | ['1.3.0', '>1.3.0', False, False], 9 | ['>=1.3.0', '1.3.0', True], 10 | ['>1.3.0', '1.3.0', False, False], 11 | # Same direction increasing 12 | ['>1.3.0', '>1.2.0', True], 13 | ['>1.2.0', '>1.3.0', True], 14 | ['>=1.2.0', '>1.3.0', True], 15 | ['>1.2.0', '>=1.3.0', True], 16 | # Same direction decreasing 17 | ['<1.3.0', '<1.2.0', True], 18 | ['<1.2.0', '<1.3.0', True], 19 | ['<=1.2.0', '<1.3.0', True], 20 | ['<1.2.0', '<=1.3.0', True], 21 | # Different directions, same semver and inclusive operator 22 | ['>=1.3.0', '<=1.3.0', True], 23 | ['>=v1.3.0', '<=1.3.0', True], 24 | ['>=1.3.0', '>=1.3.0', True], 25 | ['<=1.3.0', '<=1.3.0', True], 26 | ['<=1.3.0', '<=v1.3.0', True], 27 | ['>1.3.0', '<=1.3.0', False, False], 28 | ['>=1.3.0', '<1.3.0', False, False], 29 | # Opposite matching directions 30 | ['>1.0.0', '<2.0.0', True], 31 | ['>=1.0.0', '<2.0.0', True], 32 | ['>=1.0.0', '<=2.0.0', True], 33 | ['>1.0.0', '<=2.0.0', True], 34 | ['<=2.0.0', '>1.0.0', True], 35 | ['<=1.0.0', '>=2.0.0', False, False] 36 | ] 37 | 38 | 39 | @pytest.mark.skip(reason="not implemented yet") 40 | # @pytest.mark.parametrize("v0, v1, expect, loose", cands) 41 | def test_it(v0, v1, expect, loose): 42 | from nodesemver import make_comparator, intersects 43 | comparator1 = make_comparator(v0) 44 | comparator2 = make_comparator(v1) 45 | actual1 = comparator1.intersects(comparator2) 46 | actual2 = comparator2.intersects(comparator1) 47 | actual3 = intersects(comparator1, comparator2) 48 | actual4 = intersects(comparator2, comparator1) 49 | actual5 = intersects(comparator1, comparator2, True) 50 | actual6 = intersects(comparator2, comparator1, True) 51 | actual7 = intersects(v0, v1) 52 | actual8 = intersects(v1, v0) 53 | actual9 = intersects(v0, v1, True) 54 | actual10 = intersects(v1, v0, True) 55 | assert actual1 == expect 56 | assert actual2 == expect 57 | assert actual3 == expect 58 | assert actual4 == expect 59 | assert actual5 == expect 60 | assert actual6 == expect 61 | assert actual7 == expect 62 | assert actual8 == expect 63 | assert actual9 == expect 64 | assert actual10 == expect 65 | -------------------------------------------------------------------------------- /nodesemver/tests/test_negative_range.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import pytest 3 | # node-semver/test/index.js 4 | 5 | cands = [ 6 | ['1.0.0 - 2.0.0', '2.2.3', False], 7 | ['1.2.3+asdf - 2.4.3+asdf', '1.2.3-pre.2', False], 8 | ['1.2.3+asdf - 2.4.3+asdf', '2.4.3-alpha', False], 9 | ['^1.2.3+build', '2.0.0', False], 10 | ['^1.2.3+build', '1.2.0', False], 11 | ['^1.2.3', '1.2.3-pre', False], 12 | ['^1.2', '1.2.0-pre', False], 13 | ['>1.2', '1.3.0-beta', False], 14 | ['<=1.2.3', '1.2.3-beta', False], 15 | ['^1.2.3', '1.2.3-beta', False], 16 | ['=0.7.x', '0.7.0-asdf', False], 17 | ['>=0.7.x', '0.7.0-asdf', False], 18 | ['1', '1.0.0beta', True], 19 | ['<1', '1.0.0beta', True], 20 | ['< 1', '1.0.0beta', True], 21 | ['1.0.0', '1.0.1', False], 22 | ['>=1.0.0', '0.0.0', False], 23 | ['>=1.0.0', '0.0.1', False], 24 | ['>=1.0.0', '0.1.0', False], 25 | ['>1.0.0', '0.0.1', False], 26 | ['>1.0.0', '0.1.0', False], 27 | ['<=2.0.0', '3.0.0', False], 28 | ['<=2.0.0', '2.9999.9999', False], 29 | ['<=2.0.0', '2.2.9', False], 30 | ['<2.0.0', '2.9999.9999', False], 31 | ['<2.0.0', '2.2.9', False], 32 | ['>=0.1.97', 'v0.1.93', True], 33 | ['>=0.1.97', '0.1.93', False], 34 | ['0.1.20 || 1.2.4', '1.2.3', False], 35 | ['>=0.2.3 || <0.0.1', '0.0.3', False], 36 | ['>=0.2.3 || <0.0.1', '0.2.2', False], 37 | ['2.x.x', '1.1.3', False], 38 | ['2.x.x', '3.1.3', False], 39 | ['1.2.x', '1.3.3', False], 40 | ['1.2.x || 2.x', '3.1.3', False], 41 | ['1.2.x || 2.x', '1.1.3', False], 42 | ['2.*.*', '1.1.3', False], 43 | ['2.*.*', '3.1.3', False], 44 | ['1.2.*', '1.3.3', False], 45 | ['1.2.* || 2.*', '3.1.3', False], 46 | ['1.2.* || 2.*', '1.1.3', False], 47 | ['2', '1.1.2', False], 48 | ['2.3', '2.4.1', False], 49 | ['~2.4', '2.5.0', False], # >=2.4.0 <2.5.0 50 | ['~2.4', '2.3.9', False], 51 | ['~>3.2.1', '3.3.2', False], # >=3.2.1 <3.3.0 52 | ['~>3.2.1', '3.2.0', False], # >=3.2.1 <3.3.0 53 | ['~1', '0.2.3', False], # >=1.0.0 <2.0.0 54 | ['~>1', '2.2.3', False], 55 | ['~1.0', '1.1.0', False], # >=1.0.0 <1.1.0 56 | ['<1', '1.0.0', False], 57 | ['>=1.2', '1.1.1', False], 58 | ['1', '2.0.0beta', True], 59 | ['~v0.5.4-beta', '0.5.4-alpha', False], 60 | ['=0.7.x', '0.8.2', False], 61 | ['>=0.7.x', '0.6.2', False], 62 | ['<0.7.x', '0.7.2', False], 63 | ['<1.2.3', '1.2.3-beta', False], 64 | ['=1.2.3', '1.2.3-beta', False], 65 | ['>1.2', '1.2.8', False], 66 | ['^0.0.1', '0.0.2', False], 67 | ['^1.2.3', '2.0.0-alpha', False], 68 | ['^1.2.3', '1.2.2', False], 69 | ['^1.2', '1.1.9', False], 70 | ['*', 'v1.2.3-foo', True], 71 | # invalid ranges never satisfied! 72 | ['blerg', '1.2.3', False], 73 | ['git+https: #user:password0123@github.com/foo', '123.0.0', True], 74 | ['^1.2.3', '2.0.0-pre', False], 75 | ['^1.2.3', False, False] 76 | ] 77 | 78 | 79 | @pytest.mark.parametrize("range_, version, loose", cands) 80 | def test_it(range_, version, loose): 81 | from nodesemver import satisfies 82 | assert (not satisfies(version, range_, loose)) is True 83 | -------------------------------------------------------------------------------- /nodesemver/tests/test_comparators.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import pytest 3 | # node-semver/test/index.js 4 | 5 | # // [range, comparators] 6 | # // turn range into a set of individual comparators 7 | cands = [ 8 | ['1.0.0 - 2.0.0', [['>=1.0.0', '<=2.0.0']]], 9 | ['1.0.0', [['1.0.0']]], 10 | ['>=*', [['']]], 11 | ['', [['']]], 12 | ['*', [['']]], 13 | ['*', [['']]], 14 | ['>=1.0.0', [['>=1.0.0']]], 15 | ['>=1.0.0', [['>=1.0.0']]], 16 | ['>=1.0.0', [['>=1.0.0']]], 17 | ['>1.0.0', [['>1.0.0']]], 18 | ['>1.0.0', [['>1.0.0']]], 19 | ['<=2.0.0', [['<=2.0.0']]], 20 | ['1', [['>=1.0.0', '<2.0.0']]], 21 | ['<=2.0.0', [['<=2.0.0']]], 22 | ['<=2.0.0', [['<=2.0.0']]], 23 | ['<2.0.0', [['<2.0.0']]], 24 | ['<2.0.0', [['<2.0.0']]], 25 | ['>= 1.0.0', [['>=1.0.0']]], 26 | ['>= 1.0.0', [['>=1.0.0']]], 27 | ['>= 1.0.0', [['>=1.0.0']]], 28 | ['> 1.0.0', [['>1.0.0']]], 29 | ['> 1.0.0', [['>1.0.0']]], 30 | ['<= 2.0.0', [['<=2.0.0']]], 31 | ['<= 2.0.0', [['<=2.0.0']]], 32 | ['<= 2.0.0', [['<=2.0.0']]], 33 | ['< 2.0.0', [['<2.0.0']]], 34 | ['<\t2.0.0', [['<2.0.0']]], 35 | ['>=0.1.97', [['>=0.1.97']]], 36 | ['>=0.1.97', [['>=0.1.97']]], 37 | ['0.1.20 || 1.2.4', [['0.1.20'], ['1.2.4']]], 38 | ['>=0.2.3 || <0.0.1', [['>=0.2.3'], ['<0.0.1']]], 39 | ['>=0.2.3 || <0.0.1', [['>=0.2.3'], ['<0.0.1']]], 40 | ['>=0.2.3 || <0.0.1', [['>=0.2.3'], ['<0.0.1']]], 41 | ['||', [[''], ['']]], 42 | ['2.x.x', [['>=2.0.0', '<3.0.0']]], 43 | ['1.2.x', [['>=1.2.0', '<1.3.0']]], 44 | ['1.2.x || 2.x', [['>=1.2.0', '<1.3.0'], ['>=2.0.0', '<3.0.0']]], 45 | ['1.2.x || 2.x', [['>=1.2.0', '<1.3.0'], ['>=2.0.0', '<3.0.0']]], 46 | ['x', [['']]], 47 | ['2.*.*', [['>=2.0.0', '<3.0.0']]], 48 | ['1.2.*', [['>=1.2.0', '<1.3.0']]], 49 | ['1.2.* || 2.*', [['>=1.2.0', '<1.3.0'], ['>=2.0.0', '<3.0.0']]], 50 | ['1.2.* || 2.*', [['>=1.2.0', '<1.3.0'], ['>=2.0.0', '<3.0.0']]], 51 | ['*', [['']]], 52 | ['2', [['>=2.0.0', '<3.0.0']]], 53 | ['2.3', [['>=2.3.0', '<2.4.0']]], 54 | ['~2.4', [['>=2.4.0', '<2.5.0']]], 55 | ['~2.4', [['>=2.4.0', '<2.5.0']]], 56 | ['~>3.2.1', [['>=3.2.1', '<3.3.0']]], 57 | ['~1', [['>=1.0.0', '<2.0.0']]], 58 | ['~>1', [['>=1.0.0', '<2.0.0']]], 59 | ['~> 1', [['>=1.0.0', '<2.0.0']]], 60 | ['~1.0', [['>=1.0.0', '<1.1.0']]], 61 | ['~ 1.0', [['>=1.0.0', '<1.1.0']]], 62 | ['~ 1.0.3', [['>=1.0.3', '<1.1.0']]], 63 | ['~> 1.0.3', [['>=1.0.3', '<1.1.0']]], 64 | ['<1', [['<1.0.0']]], 65 | ['< 1', [['<1.0.0']]], 66 | ['>=1', [['>=1.0.0']]], 67 | ['>= 1', [['>=1.0.0']]], 68 | ['<1.2', [['<1.2.0']]], 69 | ['< 1.2', [['<1.2.0']]], 70 | ['1', [['>=1.0.0', '<2.0.0']]], 71 | ['1 2', [['>=1.0.0', '<2.0.0', '>=2.0.0', '<3.0.0']]], 72 | ['1.2 - 3.4.5', [['>=1.2.0', '<=3.4.5']]], 73 | ['1.2.3 - 3.4', [['>=1.2.3', '<3.5.0']]], 74 | ['1.2.3 - 3', [['>=1.2.3', '<4.0.0']]], 75 | ['>*', [['<0.0.0']]], 76 | ['<*', [['<0.0.0']]] 77 | ] 78 | 79 | 80 | @pytest.mark.parametrize("pre, wanted", cands) 81 | def test_it(pre, wanted): 82 | from nodesemver import to_comparators 83 | loose = False 84 | assert to_comparators(pre, loose) == wanted 85 | -------------------------------------------------------------------------------- /nodesemver/tests/test_valid_range.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import pytest 3 | # node-semver/test/index.js 4 | 5 | # // validRange(range) -> result 6 | # // translate ranges into their canonical form 7 | cands = [ 8 | ['1.0.0 - 2.0.0', '>=1.0.0 <=2.0.0', False], 9 | ['1.0.0', '1.0.0', False], 10 | ['>=*', '*', False], 11 | ['', '*', False], 12 | ['*', '*', False], 13 | ['*', '*', False], 14 | ['>=1.0.0', '>=1.0.0', False], 15 | ['>1.0.0', '>1.0.0', False], 16 | ['<=2.0.0', '<=2.0.0', False], 17 | ['1', '>=1.0.0 <2.0.0', False], 18 | ['<=2.0.0', '<=2.0.0', False], 19 | ['<=2.0.0', '<=2.0.0', False], 20 | ['<2.0.0', '<2.0.0', False], 21 | ['<2.0.0', '<2.0.0', False], 22 | ['>= 1.0.0', '>=1.0.0', False], 23 | ['>= 1.0.0', '>=1.0.0', False], 24 | ['>= 1.0.0', '>=1.0.0', False], 25 | ['> 1.0.0', '>1.0.0', False], 26 | ['> 1.0.0', '>1.0.0', False], 27 | ['<= 2.0.0', '<=2.0.0', False], 28 | ['<= 2.0.0', '<=2.0.0', False], 29 | ['<= 2.0.0', '<=2.0.0', False], 30 | ['< 2.0.0', '<2.0.0', False], 31 | ['< 2.0.0', '<2.0.0', False], 32 | ['>=0.1.97', '>=0.1.97', False], 33 | ['>=0.1.97', '>=0.1.97', False], 34 | ['0.1.20 || 1.2.4', '0.1.20||1.2.4', False], 35 | ['>=0.2.3 || <0.0.1', '>=0.2.3||<0.0.1', False], 36 | ['>=0.2.3 || <0.0.1', '>=0.2.3||<0.0.1', False], 37 | ['>=0.2.3 || <0.0.1', '>=0.2.3||<0.0.1', False], 38 | ['||', '||', False], 39 | ['2.x.x', '>=2.0.0 <3.0.0', False], 40 | ['1.2.x', '>=1.2.0 <1.3.0', False], 41 | ['1.2.x || 2.x', '>=1.2.0 <1.3.0||>=2.0.0 <3.0.0', False], 42 | ['1.2.x || 2.x', '>=1.2.0 <1.3.0||>=2.0.0 <3.0.0', False], 43 | ['x', '*', False], 44 | ['2.*.*', '>=2.0.0 <3.0.0', False], 45 | ['1.2.*', '>=1.2.0 <1.3.0', False], 46 | ['1.2.* || 2.*', '>=1.2.0 <1.3.0||>=2.0.0 <3.0.0', False], 47 | ['*', '*', False], 48 | ['2', '>=2.0.0 <3.0.0', False], 49 | ['2.3', '>=2.3.0 <2.4.0', False], 50 | ['~2.4', '>=2.4.0 <2.5.0', False], 51 | ['~2.4', '>=2.4.0 <2.5.0', False], 52 | ['~>3.2.1', '>=3.2.1 <3.3.0', False], 53 | ['~1', '>=1.0.0 <2.0.0', False], 54 | ['~>1', '>=1.0.0 <2.0.0', False], 55 | ['~> 1', '>=1.0.0 <2.0.0', False], 56 | ['~1.0', '>=1.0.0 <1.1.0', False], 57 | ['~ 1.0', '>=1.0.0 <1.1.0', False], 58 | ['^0', '>=0.0.0 <1.0.0', False], 59 | ['^ 1', '>=1.0.0 <2.0.0', False], 60 | ['^0.1', '>=0.1.0 <0.2.0', False], 61 | ['^1.0', '>=1.0.0 <2.0.0', False], 62 | ['^1.2', '>=1.2.0 <2.0.0', False], 63 | ['^0.0.1', '>=0.0.1 <0.0.2', False], 64 | ['^0.0.1-beta', '>=0.0.1-beta <0.0.2', False], 65 | ['^0.1.2', '>=0.1.2 <0.2.0', False], 66 | ['^1.2.3', '>=1.2.3 <2.0.0', False], 67 | ['^1.2.3-beta.4', '>=1.2.3-beta.4 <2.0.0', False], 68 | ['<1', '<1.0.0', False], 69 | ['< 1', '<1.0.0', False], 70 | ['>=1', '>=1.0.0', False], 71 | ['>= 1', '>=1.0.0', False], 72 | ['<1.2', '<1.2.0', False], 73 | ['< 1.2', '<1.2.0', False], 74 | ['1', '>=1.0.0 <2.0.0', False], 75 | ['>01.02.03', '>1.2.3', True], 76 | ['>01.02.03', None, False], 77 | ['~1.2.3beta', '>=1.2.3-beta <1.3.0', True], 78 | ['~1.2.3beta', None, False], 79 | ['^ 1.2 ^ 1', '>=1.2.0 <2.0.0 >=1.0.0 <2.0.0', False] 80 | ] 81 | 82 | 83 | @pytest.mark.parametrize("pre, wanted, loose", cands) 84 | def test_it(pre, wanted, loose): 85 | from nodesemver import valid_range 86 | assert valid_range(pre, loose) == wanted 87 | -------------------------------------------------------------------------------- /nodesemver/tests/test_for_4digit.py: -------------------------------------------------------------------------------- 1 | # https://github.com/podhmo/python-node-semver/issues/15 2 | import pytest 3 | 4 | cands = [ 5 | ( 6 | "4.1.3", True, { 7 | "major": 4, 8 | "minor": 1, 9 | "patch": 3, 10 | "prerelease": [], 11 | "build": [], 12 | "micro_versions": [], 13 | } 14 | ), 15 | ( 16 | "4.1.3+jenkins", True, { 17 | "major": 4, 18 | "minor": 1, 19 | "patch": 3, 20 | "prerelease": [], 21 | "build": ["jenkins"], 22 | "micro_versions": [], 23 | } 24 | ), 25 | ( 26 | "4.1.3-pre", True, { 27 | "major": 4, 28 | "minor": 1, 29 | "patch": 3, 30 | "prerelease": ["pre"], 31 | "build": [], 32 | "micro_versions": [], 33 | } 34 | ), 35 | # 4-digit 36 | ( 37 | "4.1.3.2", True, { 38 | "major": 4, 39 | "minor": 1, 40 | "patch": 3, 41 | "prerelease": [], 42 | "build": [], 43 | "micro_versions": [2], 44 | } 45 | ), 46 | ( 47 | "4.1.3.2+jenkins", True, { 48 | "major": 4, 49 | "minor": 1, 50 | "patch": 3, 51 | "prerelease": [], 52 | "build": ["jenkins"], 53 | "micro_versions": [2], 54 | } 55 | ), 56 | ( 57 | "4.1.3.2-pre", True, { 58 | "major": 4, 59 | "minor": 1, 60 | "patch": 3, 61 | "prerelease": ["pre"], 62 | "build": [], 63 | "micro_versions": [2], 64 | } 65 | ), 66 | ( 67 | "4.1.3.2-pre2", True, { 68 | "major": 4, 69 | "minor": 1, 70 | "patch": 3, 71 | "prerelease": ["pre2"], 72 | "build": [], 73 | "micro_versions": [2], 74 | } 75 | ), 76 | ( 77 | "4.1.3.2-pre.2", True, { 78 | "major": 4, 79 | "minor": 1, 80 | "patch": 3, 81 | "prerelease": ["pre"], 82 | "build": [], 83 | "micro_versions": [2, 2], 84 | } 85 | ), 86 | ( 87 | "4.1.3.2-pre.2+xxx", True, { 88 | "major": 4, 89 | "minor": 1, 90 | "patch": 3, 91 | "prerelease": ["pre"], 92 | "build": ["xxx"], 93 | "micro_versions": [2, 2], 94 | } 95 | ), 96 | ( 97 | "4.1.33.2", True, { 98 | "major": 4, 99 | "minor": 1, 100 | "patch": 33, 101 | "prerelease": [], 102 | "build": [], 103 | "micro_versions": [2], 104 | } 105 | ), 106 | ] 107 | 108 | 109 | @pytest.mark.parametrize("v, loose, expected", cands) 110 | def test_parse(v, loose, expected): 111 | from nodesemver import make_semver 112 | got = make_semver(v, loose=loose) 113 | assert got.raw == v 114 | assert got.major == expected["major"] 115 | assert got.minor == expected["minor"] 116 | assert got.patch == expected["patch"] 117 | assert got.prerelease == expected["prerelease"] 118 | assert got.build == expected["build"] 119 | assert got.micro_versions == expected["micro_versions"] 120 | 121 | 122 | def test_sorted(): 123 | from nodesemver import _sorted 124 | 125 | v1 = "1.1" 126 | v2 = "1.1.1" 127 | v3 = "1.1.1-pre1" 128 | v4 = "1.1.1.1" 129 | v5 = "1.1.1.2" 130 | 131 | versions = [v1, v2, v3, v4, v5] 132 | rversions = list(reversed(versions)) 133 | 134 | got = [v.raw for v in _sorted(versions, loose=True)] 135 | rgot = [v.raw for v in _sorted(rversions, loose=True)] 136 | assert got == rgot 137 | assert got == ['1.1', '1.1.1-pre1', '1.1.1', '1.1.1.1', '1.1.1.2'] 138 | -------------------------------------------------------------------------------- /nodesemver/tests/test_comparison.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import pytest 3 | # node-semver/test/index.js 4 | 5 | # [version1, version2] 6 | # version1 should be greater than version2 7 | cands = [ 8 | ['0.0.0', '0.0.0-foo', False], 9 | ['0.0.1', '0.0.0', False], 10 | ['1.0.0', '0.9.9', False], 11 | ['0.10.0', '0.9.0', False], 12 | ['0.99.0', '0.10.0', False], 13 | ['2.0.0', '1.2.3', False], 14 | ['v0.0.0', '0.0.0-foo', True], 15 | ['v0.0.1', '0.0.0', True], 16 | ['v1.0.0', '0.9.9', True], 17 | ['v0.10.0', '0.9.0', True], 18 | ['v0.99.0', '0.10.0', True], 19 | ['v2.0.0', '1.2.3', True], 20 | ['0.0.0', 'v0.0.0-foo', False], 21 | ['0.0.1', 'v0.0.0', False], 22 | ['1.0.0', 'v0.9.9', False], 23 | ['0.10.0', 'v0.9.0', False], 24 | ['0.99.0', 'v0.10.0', False], 25 | ['2.0.0', 'v1.2.3', False], 26 | ['1.2.3', '1.2.3-asdf', False], 27 | ['1.2.3', '1.2.3-4', False], 28 | ['1.2.3', '1.2.3-4-foo', False], 29 | ['1.2.3-5-foo', '1.2.3-5', False], 30 | ['1.2.3-5', '1.2.3-4', False], 31 | ['1.2.3-5-foo', '1.2.3-5-Foo', False], 32 | ['3.0.0', '2.7.2+asdf', False], 33 | ['1.2.3-a.10', '1.2.3-a.5', False], 34 | ['1.2.3-a.b', '1.2.3-a.5', False], 35 | ['1.2.3-a.b', '1.2.3-a', False], 36 | ['1.2.3-a.b.c.10.d.5', '1.2.3-a.b.c.5.d.100', False], 37 | ['1.2.3-r2', '1.2.3-r100', False], 38 | ['1.2.3-r100', '1.2.3-R2', False], 39 | ] 40 | 41 | 42 | @pytest.mark.parametrize("v0, v1, loose", cands) 43 | def test_gt(v0, v1, loose): 44 | from nodesemver import gt 45 | assert gt(v0, v1, loose) is True 46 | 47 | 48 | @pytest.mark.parametrize("v0, v1, loose", cands) 49 | def test_lt(v0, v1, loose): 50 | from nodesemver import lt 51 | assert lt(v1, v0, loose) is True 52 | 53 | 54 | @pytest.mark.parametrize("v0, v1, loose", cands) 55 | def test_ngt(v0, v1, loose): 56 | from nodesemver import gt 57 | assert (not gt(v1, v0, loose)) is True 58 | 59 | 60 | @pytest.mark.parametrize("v0, v1, loose", cands) 61 | def test_nlt(v0, v1, loose): 62 | from nodesemver import lt 63 | assert (not lt(v0, v1, loose)) is True 64 | 65 | 66 | @pytest.mark.parametrize("v0, v1, loose", cands) 67 | def test_eq(v0, v1, loose): 68 | from nodesemver import eq 69 | assert eq(v0, v0, loose) is True 70 | 71 | 72 | @pytest.mark.parametrize("v0, v1, loose", cands) 73 | def test_eq2(v0, v1, loose): 74 | from nodesemver import eq 75 | assert eq(v1, v1, loose) is True 76 | 77 | 78 | @pytest.mark.parametrize("v0, v1, loose", cands) 79 | def test_cmp(v0, v1, loose): 80 | from nodesemver import cmp 81 | assert cmp(v1, "==", v1, loose) is True 82 | 83 | 84 | @pytest.mark.parametrize("v0, v1, loose", cands) 85 | def test_cmp2(v0, v1, loose): 86 | from nodesemver import cmp 87 | cmp(v0, ">=", v1, loose) is True 88 | 89 | 90 | @pytest.mark.parametrize("v0, v1, loose", cands) 91 | def test_cmp3(v0, v1, loose): 92 | from nodesemver import cmp 93 | cmp(v1, "<=", v0, loose) is True 94 | 95 | 96 | @pytest.mark.parametrize("v0, v1, loose", cands) 97 | def test_cmp4(v0, v1, loose): 98 | from nodesemver import cmp 99 | cmp(v0, "!=", v1, loose) is True 100 | 101 | """ 102 | var v0 = v[0]; 103 | var v1 = v[1]; 104 | var loose = v[2]; 105 | t.ok(gt(v0, v1, loose), "gt('" + v0 + "', '" + v1 + "')"); 106 | t.ok(lt(v1, v0, loose), "lt('" + v1 + "', '" + v0 + "')"); 107 | t.ok(!gt(v1, v0, loose), "!gt('" + v1 + "', '" + v0 + "')"); 108 | t.ok(!lt(v0, v1, loose), "!lt('" + v0 + "', '" + v1 + "')"); 109 | t.ok(eq(v0, v0, loose), "eq('" + v0 + "', '" + v0 + "')"); 110 | t.ok(eq(v1, v1, loose), "eq('" + v1 + "', '" + v1 + "')"); 111 | t.ok(neq(v0, v1, loose), "neq('" + v0 + "', '" + v1 + "')"); 112 | t.ok(cmp(v1, '==', v1, loose), "cmp('" + v1 + "' == '" + v1 + "')"); 113 | t.ok(cmp(v0, '>=', v1, loose), "cmp('" + v0 + "' >= '" + v1 + "')"); 114 | t.ok(cmp(v1, '<=', v0, loose), "cmp('" + v1 + "' <= '" + v0 + "')"); 115 | t.ok(cmp(v0, '!=', v1, loose), "cmp('" + v0 + "' != '" + v1 + "')"); 116 | """ 117 | -------------------------------------------------------------------------------- /nodesemver/tests/test_max_satisfying.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import pytest 3 | # node-semver/test/index.js 4 | 5 | cands = [ 6 | [['1.2.4', '1.2.3', '1.2.5-beta'], '~1.2.3', '1.2.5-beta', False, True], 7 | [['1.2.4', '1.2.3', '1.2.5-beta'], '~1.2.3', '1.2.4', False, False], 8 | [['1.2.3', '1.2.4'], '1.2', '1.2.4', False, False], 9 | [['1.2.4', '1.2.3'], '1.2', '1.2.4', False, False], 10 | [['1.2.3', '1.2.4', '1.2.5', '1.2.6'], '~1.2.3', '1.2.6', False, False], 11 | [['1.1.0', '1.2.0', '1.2.1', '1.3.0', '2.0.0b1', '2.0.0b2', '2.0.0b3', '2.0.0', '2.1.0'], '~2.0.0', '2.0.0', 12 | True, False], 13 | [['1.1.0', '1.2.0', '1.2.1', '1.3.0', '2.0.0b1', '2.0.0b2', '2.0.0b3', '2.0.0', '2.1.0'], '~2.0.0', ValueError, 14 | False, False], 15 | [['1.1.0', '1.2.0', '1.2.1', '1.3.0', '2.0.0b1', '2.0.0b2', '2.0.0', '2.0.1b1', '2.1.0'], '~2.0.0', '2.0.0', 16 | True, False], 17 | [['1.1.0', '1.2.0', '1.2.1', '1.3.0', '2.0.0b1', '2.0.0b2', '2.0.0', '2.0.1b1', '2.1.0'], '~2.0.0', '2.0.1b1', 18 | True, True], 19 | 20 | [['1.0.1-beta'], '1.x', None, False, False], 21 | [['1.0.1-beta'], '1.x', '1.0.1-beta', False, True], 22 | [['1.0.0-beta'], '1.x', '1.0.0-beta', False, True], 23 | [['1.1.0-beta'], '1.0.x', None, False, True], 24 | [['1.0.0-beta', '1.1.0-beta', '1.1.0'], '1.0.x', None, False, False], 25 | [['1.0.0-beta', '1.1.0-beta', '1.1.0'], '1.0.x', '1.0.0-beta', False, True], 26 | 27 | # testing with range '>' and '<' 28 | [['1.0.0-beta', '1.0.0', '1.0.1-beta', '1.0.2-beta'], '>1.0.0 <1.0.2', None, False, False], 29 | [['1.0.0-beta', '1.0.0', '1.0.1-beta', '1.0.2-beta'], '>1.0.0 <1.0.2', '1.0.1-beta', False, True], 30 | [['1.0.0-beta', '1.0.0', '1.0.1', '1.1.0-beta', '1.1.0'], '>1.0.0 <1.1.0', '1.0.1', False, False], 31 | [['1.0.0-beta', '1.0.0', '1.0.1', '1.1.0-beta', '1.1.0'], '>1.0.0 <1.1.0', '1.0.1', False, True], 32 | [['1.0.0-beta', '1.1.0-beta'], '>1.0.0 <1.1.0', None, False, True], 33 | 34 | # testing with range '>=' and '<=' and '<' 35 | [['1.0.0-beta', '1.1.0-beta', '1.0.0'], '>=1.0.0 <=1.0.1', '1.0.0', False, False], 36 | [['1.0.0-beta', '1.1.0-beta', '1.0.0'], '>=1.0.0 <=1.0.1', '1.0.0', False, True], 37 | [['1.0.0-beta', '1.0.0', '1.0.1', '1.1.0-beta'], '>=1.0.0 <=1.1.0', '1.0.1', False, False], 38 | [['1.0.0-beta', '1.0.0', '1.0.1', '1.1.0-beta'], '>=1.0.0 <=1.1.0', '1.1.0-beta', False, True], 39 | [['1.0.0-beta', '1.0.0', '1.0.1', '1.1.0-beta'], '>=1.0.0 <1.1.0', '1.0.1', False, False], 40 | [['1.0.0-beta', '1.0.0', '1.0.1', '1.1.0-beta'], '>=1.0.0 <1.1.0', '1.0.1', False, True], 41 | [['1.0.0-beta', '1.1.0-beta'], '>=1.0.0 <=1.1.0', None, False, False], 42 | [['1.0.0-beta', '1.1.0-beta'], '>=1.0.0 <=1.1.0', '1.1.0-beta', False, True], 43 | 44 | # explicitly specifying range begin/end with prerelease 45 | [['1.0.0-beta', '1.1.0-beta', '1.0.0'], '>=1.0.0-0 <1.0.1', '1.0.0', False, True], 46 | [['1.0.0-beta', '1.1.0-beta', '1.1.0'], '>=1.0.0-0 <1.1.0-0', '1.0.0-beta', False, True], 47 | [['1.0.0-beta', '1.0.0', '1.0.1', '1.1.0-beta', '1.1.0'], '>=1.0.0-0 <1.1.0-0', '1.0.1', False, False], 48 | [['1.0.0-beta', '1.0.0', '1.0.1', '1.1.0-beta', '1.1.0'], '>=1.0.0-0 <1.1.0-0', '1.0.1', False, True], 49 | [['1.0.0-beta', '1.1.0-beta'], '>=1.0.0-0 <1.1.0', '1.0.0-beta', False, True], 50 | 51 | [['1.0.0-pre'], '1.0.x', '1.0.0-pre', False, True], 52 | [['1.0.0-pre'], '>=1.0.x', '1.0.0-pre', False, True], 53 | 54 | [['1.1.0-pre'], '>=1.0.0 <1.1.1-z', None, False, False], 55 | # Note: This test would fail with `node-semver`, see also https://github.com/npm/node-semver/issues/317 56 | [['1.1.1-pre'], '>=1.0.0 <1.1.1-z', '1.1.1-pre', False, False], 57 | # While this one would succeed with `node-semver` 58 | # [['1.0.0-pre'], '>=1.0.0 <=1.1.0', None, False, True], 59 | 60 | ] 61 | 62 | 63 | @pytest.mark.parametrize("versions, range_, expect, loose, include_prerelease", cands) 64 | def test_it(versions, range_, expect, loose, include_prerelease): 65 | from nodesemver import max_satisfying 66 | if isinstance(expect, type) and issubclass(expect, Exception): 67 | with pytest.raises(expect): 68 | max_satisfying(versions, range_, loose, include_prerelease) 69 | else: 70 | assert max_satisfying(versions, range_, loose, include_prerelease) == expect 71 | -------------------------------------------------------------------------------- /nodesemver/tests/test_equality.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import pytest 3 | # node-semver/test/index.js 4 | 5 | cands = [ 6 | ['1.2.3', 'v1.2.3', True], 7 | ['1.2.3', '=1.2.3', True], 8 | ['1.2.3', 'v 1.2.3', True], 9 | ['1.2.3', '= 1.2.3', True], 10 | ['1.2.3', ' v1.2.3', True], 11 | ['1.2.3', ' =1.2.3', True], 12 | ['1.2.3', ' v 1.2.3', True], 13 | ['1.2.3', ' = 1.2.3', True], 14 | ['1.2.3-0', 'v1.2.3-0', True], 15 | ['1.2.3-0', '=1.2.3-0', True], 16 | ['1.2.3-0', 'v 1.2.3-0', True], 17 | ['1.2.3-0', '= 1.2.3-0', True], 18 | ['1.2.3-0', ' v1.2.3-0', True], 19 | ['1.2.3-0', ' =1.2.3-0', True], 20 | ['1.2.3-0', ' v 1.2.3-0', True], 21 | ['1.2.3-0', ' = 1.2.3-0', True], 22 | ['1.2.3-1', 'v1.2.3-1', True], 23 | ['1.2.3-1', '=1.2.3-1', True], 24 | ['1.2.3-1', 'v 1.2.3-1', True], 25 | ['1.2.3-1', '= 1.2.3-1', True], 26 | ['1.2.3-1', ' v1.2.3-1', True], 27 | ['1.2.3-1', ' =1.2.3-1', True], 28 | ['1.2.3-1', ' v 1.2.3-1', True], 29 | ['1.2.3-1', ' = 1.2.3-1', True], 30 | ['1.2.3-beta', 'v1.2.3-beta', True], 31 | ['1.2.3-beta', '=1.2.3-beta', True], 32 | ['1.2.3-beta', 'v 1.2.3-beta', True], 33 | ['1.2.3-beta', '= 1.2.3-beta', True], 34 | ['1.2.3-beta', ' v1.2.3-beta', True], 35 | ['1.2.3-beta', ' =1.2.3-beta', True], 36 | ['1.2.3-beta', ' v 1.2.3-beta', True], 37 | ['1.2.3-beta', ' = 1.2.3-beta', True], 38 | ['1.2.3-beta+build', ' = 1.2.3-beta+otherbuild', True], 39 | ['1.2.3+build', ' = 1.2.3+otherbuild', True], 40 | ['1.2.3-beta+build', '1.2.3-beta+otherbuild', False], 41 | ['1.2.3+build', '1.2.3+otherbuild', False], 42 | [' v1.2.3+build', '1.2.3+otherbuild', False] 43 | ] 44 | 45 | 46 | @pytest.mark.parametrize("v0, v1, loose", cands) 47 | def test_eq(v0, v1, loose): 48 | from nodesemver import eq 49 | assert eq(v0, v1, loose) is True 50 | 51 | 52 | @pytest.mark.parametrize("v0, v1, loose", cands) 53 | def test_neq(v0, v1, loose): 54 | from nodesemver import neq 55 | assert (not neq(v0, v1, loose)) is True 56 | 57 | 58 | @pytest.mark.parametrize("v0, v1, loose", cands) 59 | def test_cmp(v0, v1, loose): 60 | from nodesemver import cmp 61 | assert cmp(v0, "==", v1, loose) is True 62 | 63 | 64 | @pytest.mark.parametrize("v0, v1, loose", cands) 65 | def test_cmp2(v0, v1, loose): 66 | from nodesemver import cmp 67 | assert (not cmp(v0, "!=", v1, loose)) is True 68 | 69 | 70 | @pytest.mark.parametrize("v0, v1, loose", cands) 71 | def test_cmp3(v0, v1, loose): 72 | from nodesemver import cmp 73 | assert (not cmp(v0, "===", v1, loose)) is True 74 | 75 | 76 | @pytest.mark.parametrize("v0, v1, loose", cands) 77 | def test_cmp4(v0, v1, loose): 78 | from nodesemver import cmp 79 | assert cmp(v0, "!==", v1, loose) is True 80 | 81 | 82 | @pytest.mark.parametrize("v0, v1, loose", cands) 83 | def test_gt(v0, v1, loose): 84 | from nodesemver import gt 85 | assert not (gt(v0, v1, loose)) is True 86 | 87 | 88 | @pytest.mark.parametrize("v0, v1, loose", cands) 89 | def test_gte(v0, v1, loose): 90 | from nodesemver import gte 91 | assert (gte(v0, v1, loose)) is True 92 | 93 | 94 | @pytest.mark.parametrize("v0, v1, loose", cands) 95 | def test_lt(v0, v1, loose): 96 | from nodesemver import lt 97 | assert not (lt(v0, v1, loose)) is True 98 | 99 | 100 | @pytest.mark.parametrize("v0, v1, loose", cands) 101 | def test_lte(v0, v1, loose): 102 | from nodesemver import lte 103 | assert (lte(v0, v1, loose)) is True 104 | 105 | """ 106 | var v0 = v[0]; 107 | var v1 = v[1]; 108 | var loose = v[2]; 109 | t.ok(gt(v0, v1, loose), "gt('" + v0 + "', '" + v1 + "')"); 110 | t.ok(lt(v1, v0, loose), "lt('" + v1 + "', '" + v0 + "')"); 111 | t.ok(!gt(v1, v0, loose), "!gt('" + v1 + "', '" + v0 + "')"); 112 | t.ok(!lt(v0, v1, loose), "!lt('" + v0 + "', '" + v1 + "')"); 113 | t.ok(eq(v0, v0, loose), "eq('" + v0 + "', '" + v0 + "')"); 114 | t.ok(eq(v1, v1, loose), "eq('" + v1 + "', '" + v1 + "')"); 115 | t.ok(neq(v0, v1, loose), "neq('" + v0 + "', '" + v1 + "')"); 116 | t.ok(cmp(v1, '==', v1, loose), "cmp('" + v1 + "' == '" + v1 + "')"); 117 | t.ok(cmp(v0, '>=', v1, loose), "cmp('" + v0 + "' >= '" + v1 + "')"); 118 | t.ok(cmp(v1, '<=', v0, loose), "cmp('" + v1 + "' <= '" + v0 + "')"); 119 | t.ok(cmp(v0, '!=', v1, loose), "cmp('" + v0 + "' != '" + v1 + "')"); 120 | """ 121 | -------------------------------------------------------------------------------- /nodesemver/tests/test_range.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import pytest 3 | # node-semver/test/index.js 4 | 5 | cands = [ 6 | ['1.0.0 - 2.0.0', '1.2.3', False, False], 7 | ['^1.2.3+build', '1.2.3', False, False], 8 | ['^1.2.3+build', '1.3.0', False, False], 9 | ['1.2.3-pre+asdf - 2.4.3-pre+asdf', '1.2.3', False, False], 10 | ['1.2.3pre+asdf - 2.4.3-pre+asdf', '1.2.3', True, False], 11 | ['1.2.3-pre+asdf - 2.4.3pre+asdf', '1.2.3', True, False], 12 | ['1.2.3pre+asdf - 2.4.3pre+asdf', '1.2.3', True, False], 13 | ['1.2.3-pre+asdf - 2.4.3-pre+asdf', '1.2.3-pre.2', False, False], 14 | ['1.2.3-pre+asdf - 2.4.3-pre+asdf', '2.4.3-alpha', False, False], 15 | ['1.2.3+asdf - 2.4.3+asdf', '1.2.3', False, False], 16 | ['1.0.0', '1.0.0', False, False], 17 | ['>=*', '0.2.4', False, False], 18 | ['', '1.0.0', False, False], 19 | ['*', '1.2.3', False, False], 20 | ['*', 'v1.2.3', True, False], 21 | ['>=1.0.0', '1.0.0', False, False], 22 | ['>=1.0.0', '1.0.1', False, False], 23 | ['>=1.0.0', '1.1.0', False, False], 24 | ['>1.0.0', '1.0.1', False, False], 25 | ['>1.0.0', '1.0.1-pre.1', False, True], 26 | ['>1.0.0', '1.1.0', False, False], 27 | ['<=2.0.0', '2.0.0', False, False], 28 | ['<=2.0.0', '1.9999.9999', False, False], 29 | ['<=2.0.0', '0.2.9', False, False], 30 | ['<2.0.0', '1.9999.9999', False, False], 31 | ['<2.0.0', '0.2.9', False, False], 32 | ['>= 1.0.0', '1.0.0', False, False], 33 | ['>= 1.0.0', '1.0.1', False, False], 34 | ['>= 1.0.0', '1.1.0', False, False], 35 | ['> 1.0.0', '1.0.1', False, False], 36 | ['> 1.0.0', '1.1.0', False, False], 37 | ['<= 2.0.0', '2.0.0', False, False], 38 | ['<= 2.0.0', '1.9999.9999', False, False], 39 | ['<= 2.0.0', '0.2.9', False, False], 40 | ['< 2.0.0', '1.9999.9999', False, False], 41 | ['<\t2.0.0', '0.2.9', False, False], 42 | ['>=0.1.97', 'v0.1.97', True, False], 43 | ['>=0.1.97', '0.1.97', False, False], 44 | ['0.1.20 || 1.2.4', '1.2.4', False, False], 45 | ['>=0.2.3 || <0.0.1', '0.0.0', False, False], 46 | ['>=0.2.3 || <0.0.1', '0.2.3', False, False], 47 | ['>=0.2.3 || <0.0.1', '0.2.4', False, False], 48 | ['||', '1.3.4', False, False], 49 | ['2.x.x', '2.1.3', False, False], 50 | ['1.2.x', '1.2.3', False, False], 51 | ['1.2.x || 2.x', '2.1.3', False, False], 52 | ['1.2.x || 2.x', '1.2.3', False, False], 53 | ['x', '1.2.3', False, False], 54 | ['2.*.*', '2.1.3', False, False], 55 | ['1.2.*', '1.2.3', False, False], 56 | ['1.2.* || 2.*', '2.1.3', False, False], 57 | ['1.2.* || 2.*', '1.2.3', False, False], 58 | ['*', '1.2.3', False, False], 59 | ['2', '2.1.2', False, False], 60 | ['2.3', '2.3.1', False, False], 61 | ['~x', '0.0.9', False, False], # >=2.4.0 <2.5.0 62 | ['~2', '2.0.9', False, False], # >=2.4.0 <2.5.0 63 | ['~2.4', '2.4.0', False, False], # >=2.4.0 <2.5.0 64 | ['~2.4', '2.4.5', False, False], 65 | ['~>3.2.1', '3.2.2', False, False], # >=3.2.1 <3.3.0, 66 | ['~1', '1.2.3', False, False], # >=1.0.0 <2.0.0 67 | ['~>1', '1.2.3', False, False], 68 | ['~> 1', '1.2.3', False, False], 69 | ['~1.0', '1.0.2', False, False], # >=1.0.0 <1.1.0, 70 | ['~ 1.0', '1.0.2', False, False], 71 | ['~ 1.0.3', '1.0.12', False, False], 72 | ['>=1', '1.0.0', False, False], 73 | ['>= 1', '1.0.0', False, False], 74 | ['<1.2', '1.1.1', False, False], 75 | ['< 1.2', '1.1.1', False, False], 76 | ['~v0.5.4-pre', '0.5.5', False, False], 77 | ['~v0.5.4-pre', '0.5.4', False, False], 78 | ['=0.7.x', '0.7.2', False, False], 79 | ['<=0.7.x', '0.7.2', False, False], 80 | ['>=0.7.x', '0.7.2', False, False], 81 | ['<=0.7.x', '0.6.2', False, False], 82 | ['~1.2.1 >=1.2.3', '1.2.3', False, False], 83 | ['~1.2.1 =1.2.3', '1.2.3', False, False], 84 | ['~1.2.1 1.2.3', '1.2.3', False, False], 85 | ['~1.2.1 >=1.2.3 1.2.3', '1.2.3', False, False], 86 | ['~1.2.1 1.2.3 >=1.2.3', '1.2.3', False, False], 87 | ['~1.2.1 1.2.3', '1.2.3', False, False], 88 | ['>=1.2.1 1.2.3', '1.2.3', False, False], 89 | ['1.2.3 >=1.2.1', '1.2.3', False, False], 90 | ['>=1.2.3 >=1.2.1', '1.2.3', False, False], 91 | ['>=1.2.1 >=1.2.3', '1.2.3', False, False], 92 | ['>=1.2', '1.2.8', False, False], 93 | ['^1.2.3', '1.8.1', False, False], 94 | ['^0.1.2', '0.1.2', False, False], 95 | ['^0.1', '0.1.2', False, False], 96 | ['^0.0.1', '0.0.1', False, False], 97 | ['^1.2', '1.4.2', False, False], 98 | ['^1.2 ^1', '1.4.2', False, False], 99 | ['^1.2.3-alpha', '1.2.3-pre', False, False], 100 | ['^1.2.3-alpha', '1.2.4-pre', False, True], 101 | ['^1.2.0-alpha', '1.2.0-pre', False, False], 102 | ['^0.0.1-alpha', '0.0.1-beta', False, False], 103 | ['^0.1.1-alpha', '0.1.1-beta', False, False], 104 | ['^x', '1.2.3', False, False], 105 | ['x - 1.0.0', '0.9.7', False, False], 106 | ['x - 1.x', '0.9.7', False, False], 107 | ['1.0.0 - x', '1.9.7', False, False], 108 | ['1.x - x', '1.9.7', False, False], 109 | ['<=7.x', '7.9.9', False, False] 110 | ] 111 | 112 | 113 | @pytest.mark.parametrize("range_, version, loose, include_prerelease", cands) 114 | def test_it(range_, version, loose, include_prerelease): 115 | from nodesemver import satisfies 116 | assert satisfies(version, range_, loose, include_prerelease) is True 117 | -------------------------------------------------------------------------------- /nodesemver/tests/test_increment_version.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import pytest 3 | # node-semver/test/index.js 4 | 5 | cands = [ 6 | ['1.2.3', 'major', '2.0.0', False, None], 7 | ['1.2.3', 'minor', '1.3.0', False, None], 8 | ['1.2.3', 'patch', '1.2.4', False, None], 9 | ['1.2.3tag', 'major', '2.0.0', True, None], 10 | ['1.2.3-tag', 'major', '2.0.0', False, None], 11 | ['1.2.3', 'fake', None, False, None], 12 | ['1.2.0-0', 'patch', '1.2.0', False, None], 13 | ['fake', 'major', None, False, None], 14 | ['1.2.3-4', 'major', '2.0.0', False, None], 15 | ['1.2.3-4', 'minor', '1.3.0', False, None], 16 | ['1.2.3-4', 'patch', '1.2.3', False, None], 17 | ['1.2.3-alpha.0.beta', 'major', '2.0.0', False, None], 18 | ['1.2.3-alpha.0.beta', 'minor', '1.3.0', False, None], 19 | ['1.2.3-alpha.0.beta', 'patch', '1.2.3', False, None], 20 | ['1.2.4', 'prerelease', '1.2.5-0', False, None], 21 | ['1.2.3-0', 'prerelease', '1.2.3-1', False, None], 22 | ['1.2.3-alpha.0', 'prerelease', '1.2.3-alpha.1', False, None], 23 | ['1.2.3-alpha.1', 'prerelease', '1.2.3-alpha.2', False, None], 24 | ['1.2.3-alpha.2', 'prerelease', '1.2.3-alpha.3', False, None], 25 | ['1.2.3-alpha.0.beta', 'prerelease', '1.2.3-alpha.1.beta', False, None], 26 | ['1.2.3-alpha.1.beta', 'prerelease', '1.2.3-alpha.2.beta', False, None], 27 | ['1.2.3-alpha.2.beta', 'prerelease', '1.2.3-alpha.3.beta', False, None], 28 | ['1.2.3-alpha.10.0.beta', 'prerelease', '1.2.3-alpha.10.1.beta', False, None], 29 | ['1.2.3-alpha.10.1.beta', 'prerelease', '1.2.3-alpha.10.2.beta', False, None], 30 | ['1.2.3-alpha.10.2.beta', 'prerelease', '1.2.3-alpha.10.3.beta', False, None], 31 | ['1.2.3-alpha.10.beta.0', 'prerelease', '1.2.3-alpha.10.beta.1', False, None], 32 | ['1.2.3-alpha.10.beta.1', 'prerelease', '1.2.3-alpha.10.beta.2', False, None], 33 | ['1.2.3-alpha.10.beta.2', 'prerelease', '1.2.3-alpha.10.beta.3', False, None], 34 | ['1.2.3-alpha.9.beta', 'prerelease', '1.2.3-alpha.10.beta', False, None], 35 | ['1.2.3-alpha.10.beta', 'prerelease', '1.2.3-alpha.11.beta', False, None], 36 | ['1.2.3-alpha.11.beta', 'prerelease', '1.2.3-alpha.12.beta', False, None], 37 | ['1.2.0', 'prepatch', '1.2.1-0', False, None], 38 | ['1.2.0-1', 'prepatch', '1.2.1-0', False, None], 39 | ['1.2.0', 'preminor', '1.3.0-0', False, None], 40 | ['1.2.3-1', 'preminor', '1.3.0-0', False, None], 41 | ['1.2.0', 'premajor', '2.0.0-0', False, None], 42 | ['1.2.3-1', 'premajor', '2.0.0-0', False, None], 43 | ['1.2.0-1', 'minor', '1.2.0', False, None], 44 | ['1.0.0-1', 'major', '1.0.0', False, None], 45 | 46 | ['1.2.3', 'major', '2.0.0', False, 'dev'], 47 | ['1.2.3', 'minor', '1.3.0', False, 'dev'], 48 | ['1.2.3', 'patch', '1.2.4', False, 'dev'], 49 | ['1.2.3tag', 'major', '2.0.0', True, 'dev'], 50 | ['1.2.3-tag', 'major', '2.0.0', False, 'dev'], 51 | ['1.2.3', 'fake', None, False, 'dev'], 52 | ['1.2.0-0', 'patch', '1.2.0', False, 'dev'], 53 | ['fake', 'major', None, False, 'dev'], 54 | ['1.2.3-4', 'major', '2.0.0', False, 'dev'], 55 | ['1.2.3-4', 'minor', '1.3.0', False, 'dev'], 56 | ['1.2.3-4', 'patch', '1.2.3', False, 'dev'], 57 | ['1.2.3-alpha.0.beta', 'major', '2.0.0', False, 'dev'], 58 | ['1.2.3-alpha.0.beta', 'minor', '1.3.0', False, 'dev'], 59 | ['1.2.3-alpha.0.beta', 'patch', '1.2.3', False, 'dev'], 60 | ['1.2.4', 'prerelease', '1.2.5-dev.0', False, 'dev'], 61 | ['1.2.3-0', 'prerelease', '1.2.3-dev.0', False, 'dev'], 62 | ['1.2.3-alpha.0', 'prerelease', '1.2.3-dev.0', False, 'dev'], 63 | ['1.2.3-alpha.0', 'prerelease', '1.2.3-alpha.1', False, 'alpha'], 64 | ['1.2.3-alpha.0.beta', 'prerelease', '1.2.3-dev.0', False, 'dev'], 65 | ['1.2.3-alpha.0.beta', 'prerelease', '1.2.3-alpha.1.beta', False, 'alpha'], 66 | ['1.2.3-alpha.10.0.beta', 'prerelease', '1.2.3-dev.0', False, 'dev'], 67 | ['1.2.3-alpha.10.0.beta', 'prerelease', '1.2.3-alpha.10.1.beta', False, 'alpha'], 68 | ['1.2.3-alpha.10.1.beta', 'prerelease', '1.2.3-alpha.10.2.beta', False, 'alpha'], 69 | ['1.2.3-alpha.10.2.beta', 'prerelease', '1.2.3-alpha.10.3.beta', False, 'alpha'], 70 | ['1.2.3-alpha.10.beta.0', 'prerelease', '1.2.3-dev.0', False, 'dev'], 71 | ['1.2.3-alpha.10.beta.0', 'prerelease', '1.2.3-alpha.10.beta.1', False, 'alpha'], 72 | ['1.2.3-alpha.10.beta.1', 'prerelease', '1.2.3-alpha.10.beta.2', False, 'alpha'], 73 | ['1.2.3-alpha.10.beta.2', 'prerelease', '1.2.3-alpha.10.beta.3', False, 'alpha'], 74 | ['1.2.3-alpha.9.beta', 'prerelease', '1.2.3-dev.0', False, 'dev'], 75 | ['1.2.3-alpha.9.beta', 'prerelease', '1.2.3-alpha.10.beta', False, 'alpha'], 76 | ['1.2.3-alpha.10.beta', 'prerelease', '1.2.3-alpha.11.beta', False, 'alpha'], 77 | ['1.2.3-alpha.11.beta', 'prerelease', '1.2.3-alpha.12.beta', False, 'alpha'], 78 | ['1.2.0', 'prepatch', '1.2.1-dev.0', False, 'dev'], 79 | ['1.2.0-1', 'prepatch', '1.2.1-dev.0', False, 'dev'], 80 | ['1.2.0', 'preminor', '1.3.0-dev.0', False, 'dev'], 81 | ['1.2.3-1', 'preminor', '1.3.0-dev.0', False, 'dev'], 82 | ['1.2.0', 'premajor', '2.0.0-dev.0', False, 'dev'], 83 | ['1.2.3-1', 'premajor', '2.0.0-dev.0', False, 'dev'], 84 | ['1.2.0-1', 'minor', '1.2.0', False, 'dev'], 85 | ['1.0.0-1', 'major', '1.0.0', False, 'dev'], 86 | ['1.2.3-dev.bar', 'prerelease', '1.2.3-dev.0', False, 'dev'] 87 | ] 88 | 89 | 90 | @pytest.mark.parametrize("pre, what, wanted, loose, identifier", cands) 91 | def test_it(pre, what, wanted, loose, identifier): 92 | from nodesemver import inc 93 | assert inc(pre, what, loose, identifier=identifier) == wanted 94 | -------------------------------------------------------------------------------- /nodesemver/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import logging 3 | import re 4 | from functools import cmp_to_key 5 | 6 | 7 | logger = logging.getLogger(__name__) 8 | SEMVER_SPEC_VERSION = '2.0.0' 9 | 10 | 11 | class InvalidTypeIncluded(ValueError): 12 | pass 13 | 14 | 15 | class _R(object): 16 | def __init__(self, i): 17 | self.i = i 18 | 19 | def __call__(self): 20 | v = self.i 21 | self.i += 1 22 | return v 23 | 24 | def value(self): 25 | return self.i 26 | 27 | 28 | class Extendlist(list): 29 | def __setitem__(self, i, v): 30 | try: 31 | list.__setitem__(self, i, v) 32 | except IndexError: 33 | if len(self) == i: 34 | self.append(v) 35 | else: 36 | raise 37 | 38 | 39 | def list_get(xs, i): 40 | try: 41 | return xs[i] 42 | except IndexError: 43 | return None 44 | 45 | 46 | R = _R(0) 47 | src = Extendlist() 48 | regexp = {} 49 | 50 | # The following Regular Expressions can be used for tokenizing, 51 | # validating, and parsing SemVer version strings. 52 | 53 | # ## Numeric Identifier 54 | # A single `0`, or a non-zero digit followed by zero or more digits. 55 | 56 | NUMERICIDENTIFIER = R() 57 | src[NUMERICIDENTIFIER] = '0|[1-9]\\d*' 58 | 59 | NUMERICIDENTIFIERLOOSE = R() 60 | src[NUMERICIDENTIFIERLOOSE] = '[0-9]+' 61 | 62 | 63 | # ## Non-numeric Identifier 64 | # Zero or more digits, followed by a letter or hyphen, and then zero or 65 | # more letters, digits, or hyphens. 66 | 67 | NONNUMERICIDENTIFIER = R() 68 | src[NONNUMERICIDENTIFIER] = '\\d*[a-zA-Z-][a-zA-Z0-9-]*' 69 | 70 | # A non-numeric identifier not beginning with a number 71 | 72 | NONNUMERICIDENTIFIERBEGINNONNUMBER = R() 73 | src[NONNUMERICIDENTIFIERBEGINNONNUMBER] = '[a-zA-Z-][a-zA-Z0-9-]*' 74 | 75 | # ## Main Version 76 | # Three dot-separated numeric identifiers. 77 | 78 | MAINVERSION = R() 79 | src[MAINVERSION] = ('(' + src[NUMERICIDENTIFIER] + ')\\.' + 80 | '(' + src[NUMERICIDENTIFIER] + ')\\.' + 81 | '(' + src[NUMERICIDENTIFIER] + ')') 82 | 83 | MAINVERSIONLOOSE = R() 84 | src[MAINVERSIONLOOSE] = ('(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + 85 | '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + 86 | '(' + src[NUMERICIDENTIFIERLOOSE] + ')') 87 | 88 | 89 | # ## Pre-release Version Identifier 90 | # A numeric identifier, or a non-numeric identifier. 91 | 92 | PRERELEASEIDENTIFIER = R() 93 | src[PRERELEASEIDENTIFIER] = ('(?:' + src[NUMERICIDENTIFIER] + 94 | '|' + src[NONNUMERICIDENTIFIER] + ')') 95 | 96 | PRERELEASEIDENTIFIERLOOSE = R() 97 | src[PRERELEASEIDENTIFIERLOOSE] = ('(?:' + src[NUMERICIDENTIFIERLOOSE] + 98 | '|' + src[NONNUMERICIDENTIFIER] + ')') 99 | 100 | 101 | # ## Pre-release Version 102 | # Hyphen, followed by one or more dot-separated pre-release version 103 | # identifiers. 104 | 105 | PRERELEASE = R() 106 | src[PRERELEASE] = ('(?:-(' + src[PRERELEASEIDENTIFIER] + 107 | '(?:\\.' + src[PRERELEASEIDENTIFIER] + ')*))') 108 | 109 | PRERELEASELOOSE = R() 110 | src[PRERELEASELOOSE] = ('(?:-?((?:(?<=-)' + src[PRERELEASEIDENTIFIERLOOSE] + 111 | '|' + src[NONNUMERICIDENTIFIERBEGINNONNUMBER] + ')' 112 | '(?:\\.' + src[PRERELEASEIDENTIFIERLOOSE] + ')*))') 113 | 114 | # ## Build Metadata Identifier 115 | # Any combination of digits, letters, or hyphens. 116 | 117 | BUILDIDENTIFIER = R() 118 | src[BUILDIDENTIFIER] = '[0-9A-Za-z-]+' 119 | 120 | # ## Build Metadata 121 | # Plus sign, followed by one or more period-separated build metadata 122 | # identifiers. 123 | 124 | BUILD = R() 125 | src[BUILD] = ('(?:\\+(' + src[BUILDIDENTIFIER] + 126 | '(?:\\.' + src[BUILDIDENTIFIER] + ')*))') 127 | 128 | # ## Full Version String 129 | # A main version, followed optionally by a pre-release version and 130 | # build metadata. 131 | 132 | # Note that the only major, minor, patch, and pre-release sections of 133 | # the version string are capturing groups. The build metadata is not a 134 | # capturing group, because it should not ever be used in version 135 | # comparison. 136 | 137 | FULL = R() 138 | FULLPLAIN = ('v?' + src[MAINVERSION] + src[PRERELEASE] + '?' + src[BUILD] + '?') 139 | 140 | src[FULL] = '^' + FULLPLAIN + '$' 141 | 142 | # like full, but allows v1.2.3 and =1.2.3, which people do sometimes. 143 | # also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty 144 | # common in the npm registry. 145 | LOOSEPLAIN = ('[v=\\s]*' + src[MAINVERSIONLOOSE] + 146 | src[PRERELEASELOOSE] + '?' + 147 | src[BUILD] + '?') 148 | 149 | LOOSE = R() 150 | src[LOOSE] = '^' + LOOSEPLAIN + '$' 151 | 152 | GTLT = R() 153 | src[GTLT] = '((?:<|>)?=?)' 154 | 155 | # Something like "2.*" or "1.2.x". 156 | # Note that "x.x" is a valid xRange identifier, meaning "any version" 157 | # Only the first item is strictly required. 158 | XRANGEIDENTIFIERLOOSE = R() 159 | src[XRANGEIDENTIFIERLOOSE] = src[NUMERICIDENTIFIERLOOSE] + '|x|X|\\*' 160 | XRANGEIDENTIFIER = R() 161 | src[XRANGEIDENTIFIER] = src[NUMERICIDENTIFIER] + '|x|X|\\*' 162 | 163 | XRANGEPLAIN = R() 164 | src[XRANGEPLAIN] = ('[v=\\s]*(' + src[XRANGEIDENTIFIER] + ')' + 165 | '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + 166 | '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + 167 | '(?:' + src[PRERELEASE] + ')?' + 168 | src[BUILD] + '?' + 169 | ')?)?') 170 | 171 | XRANGEPLAINLOOSE = R() 172 | src[XRANGEPLAINLOOSE] = ('[v=\\s]*(' + src[XRANGEIDENTIFIERLOOSE] + ')' + 173 | '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + 174 | '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + 175 | '(?:' + src[PRERELEASELOOSE] + ')?' + 176 | src[BUILD] + '?' + 177 | ')?)?') 178 | 179 | XRANGE = R() 180 | src[XRANGE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAIN] + '$' 181 | XRANGELOOSE = R() 182 | src[XRANGELOOSE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAINLOOSE] + '$' 183 | 184 | # Tilde ranges. 185 | # Meaning is "reasonably at or greater than" 186 | LONETILDE = R() 187 | src[LONETILDE] = '(?:~>?)' 188 | 189 | TILDETRIM = R() 190 | src[TILDETRIM] = '(\\s*)' + src[LONETILDE] + '\\s+' 191 | regexp[TILDETRIM] = re.compile(src[TILDETRIM], re.M) 192 | tildeTrimReplace = r'\1~' 193 | 194 | TILDE = R() 195 | src[TILDE] = '^' + src[LONETILDE] + src[XRANGEPLAIN] + '$' 196 | TILDELOOSE = R() 197 | src[TILDELOOSE] = ('^' + src[LONETILDE] + src[XRANGEPLAINLOOSE] + '$') 198 | 199 | # Caret ranges. 200 | # Meaning is "at least and backwards compatible with" 201 | LONECARET = R() 202 | src[LONECARET] = '(?:\\^)' 203 | 204 | CARETTRIM = R() 205 | src[CARETTRIM] = '(\\s*)' + src[LONECARET] + '\\s+' 206 | regexp[CARETTRIM] = re.compile(src[CARETTRIM], re.M) 207 | caretTrimReplace = r'\1^' 208 | 209 | CARET = R() 210 | src[CARET] = '^' + src[LONECARET] + src[XRANGEPLAIN] + '$' 211 | CARETLOOSE = R() 212 | src[CARETLOOSE] = '^' + src[LONECARET] + src[XRANGEPLAINLOOSE] + '$' 213 | 214 | # A simple gt/lt/eq thing, or just "" to indicate "any version" 215 | COMPARATORLOOSE = R() 216 | src[COMPARATORLOOSE] = '^' + src[GTLT] + '\\s*(' + LOOSEPLAIN + ')$|^$' 217 | COMPARATOR = R() 218 | src[COMPARATOR] = '^' + src[GTLT] + '\\s*(' + FULLPLAIN + ')$|^$' 219 | 220 | 221 | # An expression to strip any whitespace between the gtlt and the thing 222 | # it modifies, so that `> 1.2.3` ==> `>1.2.3` 223 | COMPARATORTRIM = R() 224 | src[COMPARATORTRIM] = ('(\\s*)' + src[GTLT] + 225 | '\\s*(' + LOOSEPLAIN + '|' + src[XRANGEPLAIN] + ')') 226 | 227 | # this one has to use the /g flag 228 | regexp[COMPARATORTRIM] = re.compile(src[COMPARATORTRIM], re.M) 229 | comparatorTrimReplace = r'\1\2\3' 230 | 231 | 232 | # Something like `1.2.3 - 1.2.4` 233 | # Note that these all use the loose form, because they'll be 234 | # checked against either the strict or loose comparator form 235 | # later. 236 | HYPHENRANGE = R() 237 | src[HYPHENRANGE] = ('^\\s*(' + src[XRANGEPLAIN] + ')' + 238 | '\\s+-\\s+' + 239 | '(' + src[XRANGEPLAIN] + ')' + 240 | '\\s*$') 241 | 242 | HYPHENRANGELOOSE = R() 243 | src[HYPHENRANGELOOSE] = ('^\\s*(' + src[XRANGEPLAINLOOSE] + ')' + 244 | '\\s+-\\s+' + 245 | '(' + src[XRANGEPLAINLOOSE] + ')' + 246 | '\\s*$') 247 | 248 | # Star ranges basically just allow anything at all. 249 | STAR = R() 250 | src[STAR] = '(<|>)?=?\\s*\\*' 251 | 252 | # version name recovery for convinient 253 | RECOVERYVERSIONNAME = R() 254 | src[RECOVERYVERSIONNAME] = ('v?({n})(?:\\.({n}))?{pre}?'.format(n=src[NUMERICIDENTIFIER], pre=src[PRERELEASELOOSE])) 255 | 256 | # Compile to actual regexp objects. 257 | # All are flag-free, unless they were created above with a flag. 258 | for i in range(R.value()): 259 | logger.debug("genregxp %s %s", i, src[i]) 260 | if i not in regexp: 261 | regexp[i] = re.compile(src[i]) 262 | 263 | 264 | def parse(version, loose): 265 | if loose: 266 | r = regexp[LOOSE] 267 | else: 268 | r = regexp[FULL] 269 | m = r.search(version) 270 | if m: 271 | return semver(version, loose) 272 | else: 273 | return None 274 | 275 | 276 | def valid(version, loose): 277 | v = parse(version, loose) 278 | if v.version: 279 | return v 280 | else: 281 | return None 282 | 283 | 284 | def clean(version, loose): 285 | s = parse(version, loose) 286 | if s: 287 | return s.version 288 | else: 289 | return None 290 | 291 | 292 | NUMERIC = re.compile(r"^\d+$") 293 | 294 | 295 | def semver(version, loose, include_prerelease=False): 296 | if isinstance(version, SemVer): 297 | if version.loose == loose: 298 | return version 299 | else: 300 | version = version.version 301 | elif not isinstance(version, str): # xxx: 302 | raise InvalidTypeIncluded("must be str, but {!r}".format(version)) 303 | 304 | """ 305 | if (!(this instanceof SemVer)) 306 | return new SemVer(version, loose); 307 | """ 308 | return SemVer(version, loose, include_prerelease) 309 | 310 | 311 | make_semver = semver 312 | 313 | 314 | class SemVer(object): 315 | # major, minor, patch, prerelease, build, micro_version 316 | 317 | def __init__(self, version, loose, include_prerelease): 318 | logger.debug("SemVer %s, %s", version, loose) 319 | self.loose = loose 320 | self.include_prerelease = include_prerelease 321 | self.raw = version 322 | self.micro_versions = [] 323 | self.build = [] 324 | 325 | m = regexp[LOOSE if loose else FULL].search(version.strip()) 326 | if not m: 327 | if not loose: 328 | raise ValueError("Invalid Version: {}".format(version)) 329 | if not hasattr(version, "strip"): 330 | raise ValueError("Invalid Version: {}".format(version)) 331 | m = regexp[RECOVERYVERSIONNAME].match(version.strip()) 332 | if m is None: 333 | raise ValueError("Invalid Version: {}".format(version)) 334 | self.major = int(m.group(1)) if m.group(1) else 0 335 | self.minor = int(m.group(2)) if m.group(2) else 0 336 | self.patch = 0 337 | if not m.group(3): 338 | # this is not same behaviour node's semver (see: https://github.com/podhmo/python-node-semver/issues/15) 339 | self.prerelease = [id for id in version.strip()[m.end():].split(".") if id] 340 | if self.prerelease and NUMERIC.search(self.prerelease[0]): 341 | self.patch = int(self.prerelease[0]) 342 | self.prerelease = self.prerelease[1:] 343 | 344 | prerelease = [] 345 | for id in self.prerelease: 346 | if "-" in id: 347 | other = prerelease 348 | ks = id.split("-") 349 | elif "+" in id: 350 | other = self.build 351 | ks = id.split("+") 352 | else: 353 | other = None 354 | ks = [id] 355 | for k in ks: 356 | if NUMERIC.search(k): 357 | self.micro_versions.append(int(k)) 358 | elif other is None: 359 | raise ValueError("Invalid Version: {}".format(version)) 360 | else: 361 | other.append(k) 362 | self.prerelease = prerelease 363 | self.prerelease = [(int(id) if NUMERIC.search(id) else id)for id in self.prerelease] 364 | else: 365 | self.prerelease = [(int(id) if NUMERIC.search(id) else id) 366 | for id in m.group(3).split(".")] 367 | else: 368 | # these are actually numbers 369 | self.major = int(m.group(1)) 370 | self.minor = int(m.group(2)) 371 | self.patch = int(m.group(3)) 372 | # numberify any prerelease numeric ids 373 | if not m.group(4): 374 | self.prerelease = [] 375 | else: 376 | 377 | self.prerelease = [(int(id) if NUMERIC.search(id) else id) 378 | for id in m.group(4).split(".")] 379 | if m.group(5): 380 | self.build = m.group(5).split(".") 381 | 382 | self.format() # xxx: 383 | 384 | def format(self): 385 | self.version = "{}.{}.{}".format(self.major, self.minor, self.patch) 386 | if len(self.prerelease) > 0: 387 | self.version += ("-{}".format(".".join(str(v) for v in self.prerelease))) 388 | elif self.micro_versions: 389 | self.version += ".{}".format(".".join(str(v) for v in self.micro_versions)) 390 | return self.version 391 | 392 | def __repr__(self): 393 | return "".format(self) 394 | 395 | def __str__(self): 396 | return self.version 397 | 398 | def compare(self, other): 399 | logger.debug('SemVer.compare %s %s %s', self.version, self.loose, other) 400 | if not isinstance(other, SemVer): 401 | other = make_semver(other, self.loose) 402 | result = self.compare_main(other) or self.compare_pre(other) or self.compare_micro(other) 403 | logger.debug("compare result %s", result) 404 | return result 405 | 406 | def compare_main(self, other): 407 | if not isinstance(other, SemVer): 408 | other = make_semver(other, self.loose) 409 | 410 | return (compare_identifiers(str(self.major), str(other.major)) or 411 | compare_identifiers(str(self.minor), str(other.minor)) or 412 | compare_identifiers(str(self.patch), str(other.patch))) 413 | 414 | def compare_pre(self, other): 415 | if not isinstance(other, SemVer): 416 | other = make_semver(other, self.loose) 417 | 418 | if self.include_prerelease: 419 | return 0 420 | 421 | # NOT having a prerelease is > having one 422 | is_self_more_than_zero = len(self.prerelease) > 0 423 | is_other_more_than_zero = len(other.prerelease) > 0 424 | 425 | if not is_self_more_than_zero and is_other_more_than_zero: 426 | return 1 427 | elif is_self_more_than_zero and not is_other_more_than_zero: 428 | return -1 429 | elif not is_self_more_than_zero and not is_other_more_than_zero: 430 | return 0 431 | 432 | i = 0 433 | while True: 434 | a = list_get(self.prerelease, i) 435 | b = list_get(other.prerelease, i) 436 | logger.debug("prerelease compare %s: %s %s", i, a, b) 437 | i += 1 438 | if a is None and b is None: 439 | return 0 440 | elif b is None: 441 | return 1 442 | elif a is None: 443 | return -1 444 | elif a == b: 445 | continue 446 | else: 447 | return compare_identifiers(str(a), str(b)) 448 | 449 | def compare_micro(self, other): 450 | if self.micro_versions == other.micro_versions: 451 | return 0 452 | return -1 if self.micro_versions < other.micro_versions else 1 453 | 454 | def inc(self, release, identifier=None): 455 | logger.debug("inc release %s %s", self.prerelease, release) 456 | if release == 'premajor': 457 | self.prerelease = [] 458 | self.patch = 0 459 | self.minor = 0 460 | self.major += 1 461 | self.inc('pre', identifier=identifier) 462 | elif release == "preminor": 463 | self.prerelease = [] 464 | self.patch = 0 465 | self.minor += 1 466 | self.inc('pre', identifier=identifier) 467 | elif release == "prepatch": 468 | # If this is already a prerelease, it will bump to the next version 469 | # drop any prereleases that might already exist, since they are not 470 | # relevant at this point. 471 | self.prerelease = [] 472 | self.inc('patch', identifier=identifier) 473 | self.inc('pre', identifier=identifier) 474 | elif release == 'prerelease': 475 | # If the input is a non-prerelease version, this acts the same as 476 | # prepatch. 477 | if len(self.prerelease) == 0: 478 | self.inc("patch", identifier=identifier) 479 | self.inc("pre", identifier=identifier) 480 | elif release == "major": 481 | # If this is a pre-major version, bump up to the same major version. 482 | # Otherwise increment major. 483 | # 1.0.0-5 bumps to 1.0.0 484 | # 1.1.0 bumps to 2.0.0 485 | if self.minor != 0 or self.patch != 0 or len(self.prerelease) == 0: 486 | self.major += 1 487 | self.minor = 0 488 | self.patch = 0 489 | self.prerelease = [] 490 | elif release == "minor": 491 | # If this is a pre-minor version, bump up to the same minor version. 492 | # Otherwise increment minor. 493 | # 1.2.0-5 bumps to 1.2.0 494 | # 1.2.1 bumps to 1.3.0 495 | if self.patch != 0 or len(self.prerelease) == 0: 496 | self.minor += 1 497 | self.patch = 0 498 | self.prerelease = [] 499 | elif release == "patch": 500 | # If this is not a pre-release version, it will increment the patch. 501 | # If it is a pre-release it will bump up to the same patch version. 502 | # 1.2.0-5 patches to 1.2.0 503 | # 1.2.0 patches to 1.2.1 504 | if len(self.prerelease) == 0: 505 | self.patch += 1 506 | self.prerelease = [] 507 | elif release == "pre": 508 | # This probably shouldn't be used publically. 509 | # 1.0.0 "pre" would become 1.0.0-0 which is the wrong direction. 510 | logger.debug("inc prerelease %s", self.prerelease) 511 | if len(self.prerelease) == 0: 512 | self.prerelease = [0] 513 | else: 514 | i = len(self.prerelease) - 1 515 | while i >= 0: 516 | if isinstance(self.prerelease[i], int): 517 | self.prerelease[i] += 1 518 | i -= 2 519 | i -= 1 520 | # ## this is needless code in python ## 521 | # if i == -1: # didn't increment anything 522 | # self.prerelease.append(0) 523 | if identifier is not None: 524 | # 1.2.0-beta.1 bumps to 1.2.0-beta.2, 525 | # 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 526 | if self.prerelease[0] == identifier: 527 | if not isinstance(self.prerelease[1], int): 528 | self.prerelease = [identifier, 0] 529 | else: 530 | self.prerelease = [identifier, 0] 531 | else: 532 | raise ValueError('invalid increment argument: {}'.format(release)) 533 | self.format() 534 | self.raw = self.version 535 | return self 536 | 537 | 538 | def inc(version, release, loose, identifier=None): # wow! 539 | try: 540 | return make_semver(version, loose).inc(release, identifier=identifier).version 541 | except ValueError as e: 542 | logger.info(e, exc_info=2) 543 | return None 544 | 545 | 546 | def compare_identifiers(a, b): 547 | anum = NUMERIC.search(a) 548 | bnum = NUMERIC.search(b) 549 | 550 | if anum and bnum: 551 | a = int(a) 552 | b = int(b) 553 | 554 | if anum and not bnum: 555 | return -1 556 | elif bnum and not anum: 557 | return 1 558 | elif a < b: 559 | return -1 560 | elif a > b: 561 | return 1 562 | else: 563 | return 0 564 | 565 | 566 | def rcompare_identifiers(a, b): 567 | return compare_identifiers(b, a) 568 | 569 | 570 | def compare(a, b, loose): 571 | return make_semver(a, loose).compare(b) 572 | 573 | 574 | def compare_loose(a, b): 575 | return compare(a, b, True) 576 | 577 | 578 | def rcompare(a, b, loose): 579 | return compare(b, a, loose) 580 | 581 | 582 | def _prerelease_key(prerelease): 583 | """Sort key for prereleases. 584 | 585 | Precedence for two pre-release versions with the same 586 | major, minor, and patch version MUST be determined by 587 | comparing each dot separated identifier from left to 588 | right until a difference is found as follows: 589 | identifiers consisting of only digits are compare 590 | numerically and identifiers with letters or hyphens 591 | are compared lexically in ASCII sort order. Numeric 592 | identifiers always have lower precedence than non- 593 | numeric identifiers. A larger set of pre-release 594 | fields has a higher precedence than a smaller set, 595 | if all of the preceding identifiers are equal. 596 | """ 597 | for entry in prerelease: 598 | if isinstance(entry, int): 599 | # Assure numerics always sort before string 600 | yield ('', entry) 601 | else: 602 | # Use ASCII compare: 603 | yield (entry,) 604 | 605 | 606 | def _make_key_function(loose): 607 | def key_function(version): 608 | v = make_semver(version, loose) 609 | key = (v.major, v.minor, v.patch) 610 | if v.micro_versions: 611 | key = key + v.micro_versions 612 | if v.prerelease: 613 | key = key + (0,) + tuple(_prerelease_key( 614 | v.prerelease)) 615 | else: 616 | # NOT having a prerelease is > having one 617 | key = key + (1,) 618 | 619 | return key 620 | return key_function 621 | 622 | loose_key_function = _make_key_function(True) 623 | full_key_function = _make_key_function(False) 624 | 625 | 626 | def sort(list, loose): 627 | keyf = loose_key_function if loose else full_key_function 628 | list.sort(key=keyf) 629 | return list 630 | 631 | 632 | def rsort(list, loose): 633 | keyf = loose_key_function if loose else full_key_function 634 | list.sort(key=keyf, reverse=True) 635 | return list 636 | 637 | 638 | def gt(a, b, loose): 639 | return compare(a, b, loose) > 0 640 | 641 | 642 | def lt(a, b, loose): 643 | return compare(a, b, loose) < 0 644 | 645 | 646 | def eq(a, b, loose): 647 | return compare(a, b, loose) == 0 648 | 649 | 650 | def neq(a, b, loose): 651 | return compare(a, b, loose) != 0 652 | 653 | 654 | def gte(a, b, loose): 655 | return compare(a, b, loose) >= 0 656 | 657 | 658 | def lte(a, b, loose): 659 | return compare(a, b, loose) <= 0 660 | 661 | 662 | def cmp(a, op, b, loose): 663 | logger.debug("cmp: %s", op) 664 | if op == "===": 665 | return a == b 666 | elif op == "!==": 667 | return a != b 668 | elif op == "" or op == "=" or op == "==": 669 | return eq(a, b, loose) 670 | elif op == "!=": 671 | return neq(a, b, loose) 672 | elif op == ">": 673 | return gt(a, b, loose) 674 | elif op == ">=": 675 | return gte(a, b, loose) 676 | elif op == "<": 677 | return lt(a, b, loose) 678 | elif op == "<=": 679 | return lte(a, b, loose) 680 | else: 681 | raise ValueError("Invalid operator: {}".format(op)) 682 | 683 | 684 | def comparator(comp, loose): 685 | if isinstance(comp, Comparator): 686 | if(comp.loose == loose): 687 | return comp 688 | else: 689 | comp = comp.value 690 | 691 | # if (!(this instanceof Comparator)) 692 | # return new Comparator(comp, loose) 693 | return Comparator(comp, loose) 694 | 695 | 696 | make_comparator = comparator 697 | 698 | ANY = object() 699 | 700 | 701 | class Comparator(object): 702 | semver = None 703 | 704 | def __init__(self, comp, loose): 705 | logger.debug("comparator: %s %s", comp, loose) 706 | self.loose = loose 707 | self.parse(comp) 708 | 709 | if self.semver == ANY: 710 | self.value = "" 711 | else: 712 | self.value = self.operator + self.semver.version 713 | 714 | def parse(self, comp): 715 | if self.loose: 716 | r = regexp[COMPARATORLOOSE] 717 | else: 718 | r = regexp[COMPARATOR] 719 | logger.debug("parse comp=%s", comp) 720 | m = r.search(comp) 721 | 722 | if m is None: 723 | raise ValueError("Invalid comparator: {}".format(comp)) 724 | 725 | self.operator = m.group(1) 726 | # if it literally is just '>' or '' then allow anything. 727 | if m.group(2) is None: 728 | self.semver = ANY 729 | else: 730 | self.semver = semver(m.group(2), self.loose) 731 | 732 | def __repr__(self): 733 | return ''.format(self) 734 | 735 | def __str__(self): 736 | return self.value 737 | 738 | def test(self, version): 739 | logger.debug('Comparator, test %s, %s', version, self.loose) 740 | if self.semver == ANY: 741 | return True 742 | else: 743 | return cmp(version, self.operator, self.semver, self.loose) 744 | 745 | 746 | def make_range(range_, loose): 747 | if isinstance(range_, Range) and range_.loose == loose: 748 | return range_ 749 | elif not isinstance(range_, str): # xxx: 750 | raise InvalidTypeIncluded("must be str, but {!r}".format(range_)) 751 | 752 | # if (!(this instanceof Range)) 753 | # return new Range(range, loose); 754 | return Range(range_, loose) 755 | 756 | 757 | class Range(object): 758 | def __init__(self, range_, loose, _split_rx=re.compile(r"\s*\|\|\s*")): 759 | self.loose = loose 760 | # First, split based on boolean or || 761 | self.raw = range_ 762 | xs = [self.parse_range(r.strip()) for r in _split_rx.split(range_)] 763 | self.set = [r for r in xs if r] 764 | 765 | if not len(self.set): 766 | raise ValueError("Invalid SemVer Range: {}".format(range_)) 767 | 768 | self.format() 769 | 770 | def __repr__(self): 771 | return ''.format(self.range) 772 | 773 | def format(self): 774 | self.range = "||".join([" ".join(c.value for c in comps).strip() for comps in self.set]).strip() 775 | logger.debug("Range format %s", self.range) 776 | return self.range 777 | 778 | def __str__(self): 779 | return self.range 780 | 781 | def parse_range(self, range_): 782 | loose = self.loose 783 | logger.debug('range %s %s', range_, loose) 784 | # `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` 785 | if loose: 786 | hr = regexp[HYPHENRANGELOOSE] 787 | else: 788 | hr = regexp[HYPHENRANGE] 789 | 790 | range_ = hr.sub(hyphen_replace, range_,) 791 | logger.debug('hyphen replace %s', range_) 792 | 793 | # `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` 794 | range_ = regexp[COMPARATORTRIM].sub(comparatorTrimReplace, range_) 795 | logger.debug('comparator trim %s, %s', range_, regexp[COMPARATORTRIM]) 796 | 797 | # `~ 1.2.3` => `~1.2.3` 798 | range_ = regexp[TILDETRIM].sub(tildeTrimReplace, range_) 799 | 800 | # `^ 1.2.3` => `^1.2.3` 801 | range_ = regexp[CARETTRIM].sub(caretTrimReplace, range_) 802 | 803 | # normalize spaces 804 | range_ = " ".join(re.split(r"\s+", range_)) 805 | 806 | # At this point, the range is completely trimmed and 807 | # ready to be split into comparators. 808 | if loose: 809 | comp_re = regexp[COMPARATORLOOSE] 810 | else: 811 | comp_re = regexp[COMPARATOR] 812 | set_ = re.split(r"\s+", ' '.join([parse_comparator(comp, loose) for comp in range_.split(" ")])) 813 | if self.loose: 814 | # in loose mode, throw out any that are not valid comparators 815 | set_ = [comp for comp in set_ if comp_re.search(comp)] 816 | set_ = [make_comparator(comp, loose) for comp in set_] 817 | return set_ 818 | 819 | def test(self, version, include_prerelease=False): 820 | if not version: # xxx 821 | return False 822 | 823 | if isinstance(version, str): 824 | version = make_semver(version, loose=self.loose, include_prerelease=include_prerelease) 825 | 826 | for e in self.set: 827 | if test_set(e, version, include_prerelease=include_prerelease): 828 | return True 829 | return False 830 | 831 | 832 | # Mostly just for testing and legacy API reasons 833 | def to_comparators(range_, loose): 834 | return [" ".join([c.value for c in comp]).strip().split(" ") 835 | for comp in make_range(range_, loose).set] 836 | 837 | 838 | # comprised of xranges, tildes, stars, and gtlt's at this point. 839 | # already replaced the hyphen ranges 840 | # turn into a set of JUST comparators. 841 | 842 | def parse_comparator(comp, loose): 843 | logger.debug('comp %s', comp) 844 | comp = replace_carets(comp, loose) 845 | logger.debug('caret %s', comp) 846 | comp = replace_tildes(comp, loose) 847 | logger.debug('tildes %s', comp) 848 | comp = replace_xranges(comp, loose) 849 | logger.debug('xrange %s', comp) 850 | comp = replace_stars(comp, loose) 851 | logger.debug('stars %s', comp) 852 | return comp 853 | 854 | 855 | def is_x(id): 856 | return id is None or id == "" or id.lower() == "x" or id == "*" 857 | 858 | 859 | # ~, ~> --> * (any, kinda silly) 860 | # ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0 861 | # ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0 862 | # ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0 863 | # ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0 864 | # ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0 865 | 866 | def replace_tildes(comp, loose): 867 | return " ".join([replace_tilde(c, loose) 868 | for c in re.split(r"\s+", comp.strip())]) 869 | 870 | 871 | def replace_tilde(comp, loose): 872 | if loose: 873 | r = regexp[TILDELOOSE] 874 | else: 875 | r = regexp[TILDE] 876 | 877 | def repl(mob): 878 | _ = mob.group(0) 879 | M, m, p, pr, _ = mob.groups() 880 | logger.debug("tilde %s %s %s %s %s %s", comp, _, M, m, p, pr) 881 | if is_x(M): 882 | ret = "" 883 | elif is_x(m): 884 | ret = '>=' + M + '.0.0 <' + str(int(M) + 1) + '.0.0' 885 | elif is_x(p): 886 | # ~1.2 == >=1.2.0 <1.3.0 887 | ret = '>=' + M + '.' + m + '.0 <' + M + '.' + str(int(m) + 1) + '.0' 888 | elif pr: 889 | logger.debug("replaceTilde pr %s", pr) 890 | if (pr[0] != "-"): 891 | pr = '-' + pr 892 | ret = '>=' + M + '.' + m + '.' + p + pr + ' <' + M + '.' + str(int(m) + 1) + '.0' 893 | else: 894 | # ~1.2.3 == >=1.2.3 <1.3.0 895 | ret = '>=' + M + '.' + m + '.' + p + ' <' + M + '.' + str(int(m) + 1) + '.0' 896 | logger.debug('tilde return, %s', ret) 897 | return ret 898 | return r.sub(repl, comp) 899 | 900 | 901 | # ^ --> * (any, kinda silly) 902 | # ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0 903 | # ^2.0, ^2.0.x --> >=2.0.0 <3.0.0 904 | # ^1.2, ^1.2.x --> >=1.2.0 <2.0.0 905 | # ^1.2.3 --> >=1.2.3 <2.0.0 906 | # ^1.2.0 --> >=1.2.0 <2.0.0 907 | def replace_carets(comp, loose): 908 | return " ".join([replace_caret(c, loose) 909 | for c in re.split(r"\s+", comp.strip())]) 910 | 911 | 912 | def replace_caret(comp, loose): 913 | if loose: 914 | r = regexp[CARETLOOSE] 915 | else: 916 | r = regexp[CARET] 917 | 918 | def repl(mob): 919 | m0 = mob.group(0) 920 | M, m, p, pr, _ = mob.groups() 921 | logger.debug("caret %s %s %s %s %s %s", comp, m0, M, m, p, pr) 922 | 923 | if is_x(M): 924 | ret = "" 925 | elif is_x(m): 926 | ret = '>=' + M + '.0.0 <' + str((int(M) + 1)) + '.0.0' 927 | elif is_x(p): 928 | if M == "0": 929 | ret = '>=' + M + '.' + m + '.0 <' + M + '.' + str((int(m) + 1)) + '.0' 930 | else: 931 | ret = '>=' + M + '.' + m + '.0 <' + str(int(M) + 1) + '.0.0' 932 | elif pr: 933 | logger.debug('replaceCaret pr %s', pr) 934 | if pr[0] != "-": 935 | pr = "-" + pr 936 | if M == "0": 937 | if m == "0": 938 | ret = '>=' + M + '.' + m + '.' + (p or "") + pr + ' <' + M + '.' + m + "." + str(int(p or 0) + 1) 939 | else: 940 | ret = '>=' + M + '.' + m + '.' + (p or "") + pr + ' <' + M + '.' + str(int(m) + 1) + '.0' 941 | else: 942 | ret = '>=' + M + '.' + m + '.' + (p or "") + pr + ' <' + str(int(M) + 1) + '.0.0' 943 | else: 944 | if M == "0": 945 | if m == "0": 946 | ret = '>=' + M + '.' + m + '.' + (p or "") + ' <' + M + '.' + m + "." + str(int(p or 0) + 1) 947 | else: 948 | ret = '>=' + M + '.' + m + '.' + (p or "") + ' <' + M + '.' + str((int(m) + 1)) + '.0' 949 | else: 950 | ret = '>=' + M + '.' + m + '.' + (p or "") + ' <' + str(int(M) + 1) + '.0.0' 951 | logger.debug('caret return %s', ret) 952 | return ret 953 | 954 | return r.sub(repl, comp) 955 | 956 | 957 | def replace_xranges(comp, loose): 958 | logger.debug('replaceXRanges %s %s', comp, loose) 959 | return " ".join([replace_xrange(c, loose) 960 | for c in re.split(r"\s+", comp.strip())]) 961 | 962 | 963 | def replace_xrange(comp, loose): 964 | comp = comp.strip() 965 | if loose: 966 | r = regexp[XRANGELOOSE] 967 | else: 968 | r = regexp[XRANGE] 969 | 970 | def repl(mob): 971 | ret = mob.group(0) 972 | gtlt, M, m, p, pr, _ = mob.groups() 973 | 974 | logger.debug("xrange %s %s %s %s %s %s %s", comp, ret, gtlt, M, m, p, pr) 975 | 976 | xM = is_x(M) 977 | xm = xM or is_x(m) 978 | xp = xm or is_x(p) 979 | any_x = xp 980 | 981 | if gtlt == "=" and any_x: 982 | gtlt = "" 983 | 984 | logger.debug("xrange gtlt=%s any_x=%s", gtlt, any_x) 985 | if xM: 986 | if gtlt == '>' or gtlt == '<': 987 | # nothing is allowed 988 | ret = '<0.0.0' 989 | else: 990 | ret = '*' 991 | elif gtlt and any_x: 992 | # replace X with 0, and then append the -0 min-prerelease 993 | if xm: 994 | m = 0 995 | if xp: 996 | p = 0 997 | 998 | if gtlt == ">": 999 | # >1 => >=2.0.0 1000 | # >1.2 => >=1.3.0 1001 | # >1.2.3 => >= 1.2.4 1002 | gtlt = ">=" 1003 | if xm: 1004 | M = int(M) + 1 1005 | m = 0 1006 | p = 0 1007 | elif xp: 1008 | m = int(m) + 1 1009 | p = 0 1010 | elif gtlt == '<=': 1011 | # <=0.7.x is actually <0.8.0, since any 0.7.x should 1012 | # pass. Similarly, <=7.x is actually <8.0.0, etc. 1013 | gtlt = '<' 1014 | if xm: 1015 | M = int(M) + 1 1016 | else: 1017 | m = int(m) + 1 1018 | 1019 | ret = gtlt + str(M) + '.' + str(m) + '.' + str(p) 1020 | elif xm: 1021 | ret = '>=' + M + '.0.0 <' + str(int(M) + 1) + '.0.0' 1022 | elif xp: 1023 | ret = '>=' + M + '.' + m + '.0 <' + M + '.' + str(int(m) + 1) + '.0' 1024 | logger.debug('xRange return %s', ret) 1025 | 1026 | return ret 1027 | return r.sub(repl, comp) 1028 | 1029 | 1030 | # Because * is AND-ed with everything else in the comparator, 1031 | # and '' means "any version", just remove the *s entirely. 1032 | def replace_stars(comp, loose): 1033 | logger.debug('replaceStars %s %s', comp, loose) 1034 | # Looseness is ignored here. star is always as loose as it gets! 1035 | return regexp[STAR].sub("", comp.strip()) 1036 | 1037 | 1038 | # This function is passed to string.replace(re[HYPHENRANGE]) 1039 | # M, m, patch, prerelease, build 1040 | # 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 1041 | # 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do 1042 | # 1.2 - 3.4 => >=1.2.0 <3.5.0 1043 | def hyphen_replace(mob): 1044 | from_, fM, fm, fp, fpr, fb, to, tM, tm, tp, tpr, tb = mob.groups() 1045 | if is_x(fM): 1046 | from_ = "" 1047 | elif is_x(fm): 1048 | from_ = '>=' + fM + '.0.0' 1049 | elif is_x(fp): 1050 | from_ = '>=' + fM + '.' + fm + '.0' 1051 | else: 1052 | from_ = ">=" + from_ 1053 | 1054 | if is_x(tM): 1055 | to = "" 1056 | elif is_x(tm): 1057 | to = '<' + str(int(tM) + 1) + '.0.0' 1058 | elif is_x(tp): 1059 | to = '<' + tM + '.' + str(int(tm) + 1) + '.0' 1060 | elif tpr: 1061 | to = '<=' + tM + '.' + tm + '.' + tp + '-' + tpr 1062 | else: 1063 | to = '<=' + to 1064 | return (from_ + ' ' + to).strip() 1065 | 1066 | 1067 | def test_set(set_, version, include_prerelease=False): 1068 | for e in set_: 1069 | if not e.test(version): 1070 | return False 1071 | if len(version.prerelease) > 0 and not include_prerelease: 1072 | # Find the set of versions that are allowed to have prereleases 1073 | # For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 1074 | # That should allow `1.2.3-pr.2` to pass. 1075 | # However, `1.2.4-alpha.notready` should NOT be allowed, 1076 | # even though it's within the range set by the comparators. 1077 | for e in set_: 1078 | if e.semver == ANY: 1079 | continue 1080 | if len(e.semver.prerelease) > 0: 1081 | allowed = e.semver 1082 | if allowed.major == version.major and allowed.minor == version.minor and allowed.patch == version.patch: 1083 | return True 1084 | # Version has a -pre, but it's not one of the ones we like. 1085 | return False 1086 | return True 1087 | 1088 | 1089 | def satisfies(version, range_, loose=False, include_prerelease=False): 1090 | try: 1091 | range_ = make_range(range_, loose) 1092 | except InvalidTypeIncluded: 1093 | raise 1094 | except ValueError as e: 1095 | logger.info(e, exc_info=2) 1096 | return False 1097 | return range_.test(version, include_prerelease=include_prerelease) 1098 | 1099 | 1100 | def max_satisfying(versions, range_, loose=False, include_prerelease=False): 1101 | try: 1102 | range_ob = make_range(range_, loose=loose) 1103 | except InvalidTypeIncluded: 1104 | raise 1105 | except ValueError as e: 1106 | logger.info(e, exc_info=2) 1107 | return None 1108 | max_ = None 1109 | max_sv = None 1110 | for v in versions: 1111 | if range_ob.test(v, include_prerelease=include_prerelease): # satisfies(v, range_, loose=loose) 1112 | if max_ is None or max_sv.compare(v) == -1: # compare(max, v, true) 1113 | max_ = v 1114 | max_sv = make_semver(max_, loose=loose) 1115 | return max_ 1116 | 1117 | 1118 | def min_satisfying(versions, range_, loose=False, include_prerelease=False): 1119 | try: 1120 | range_ob = make_range(range_, loose=loose) 1121 | except InvalidTypeIncluded: 1122 | raise 1123 | except ValueError as e: 1124 | logger.info(e, exc_info=2) 1125 | return None 1126 | min_ = None 1127 | min_sv = None 1128 | for v in versions: 1129 | if range_ob.test(v, include_prerelease=include_prerelease): # satisfies(v, range_, loose=loose) 1130 | if min_ is None or min_sv.compare(v) == 1: # compare(min, v, true) 1131 | min_ = v 1132 | min_sv = make_semver(min_, loose=loose) 1133 | return min_ 1134 | 1135 | 1136 | def valid_range(range_, loose): 1137 | try: 1138 | # Return '*' instead of '' so that truthiness works. 1139 | # This will throw if it's invalid anyway 1140 | return make_range(range_, loose).range or "*" 1141 | except TypeError as e: 1142 | raise InvalidTypeIncluded("{} (range_={!r}".format(e, range_)) 1143 | except ValueError as e: 1144 | logger.info(e, exc_info=2) 1145 | return None 1146 | 1147 | 1148 | # Determine if version is less than all the versions possible in the range 1149 | def ltr(version, range_, loose): 1150 | return outside(version, range_, "<", loose) 1151 | 1152 | 1153 | # Determine if version is greater than all the versions possible in the range. 1154 | def rtr(version, range_, loose): 1155 | return outside(version, range_, ">", loose) 1156 | 1157 | 1158 | def outside(version, range_, hilo, loose): 1159 | version = make_semver(version, loose) 1160 | range_ = make_range(range_, loose) 1161 | 1162 | if hilo == ">": 1163 | gtfn = gt 1164 | ltefn = lte 1165 | ltfn = lt 1166 | comp = ">" 1167 | ecomp = ">=" 1168 | elif hilo == "<": 1169 | gtfn = lt 1170 | ltefn = gte 1171 | ltfn = gt 1172 | comp = "<" 1173 | ecomp = "<=" 1174 | else: 1175 | raise ValueError("Must provide a hilo val of '<' or '>'") 1176 | 1177 | # If it satisifes the range it is not outside 1178 | if satisfies(version, range_, loose): 1179 | return False 1180 | 1181 | # From now on, variable terms are as if we're in "gtr" mode. 1182 | # but note that everything is flipped for the "ltr" function. 1183 | for comparators in range_.set: 1184 | high = None 1185 | low = None 1186 | 1187 | for comparator in comparators: 1188 | high = high or comparator 1189 | low = low or comparator 1190 | 1191 | if gtfn(comparator.semver, high.semver, loose): 1192 | high = comparator 1193 | elif ltfn(comparator.semver, low.semver, loose): 1194 | low = comparator 1195 | 1196 | # If the edge version comparator has a operator then our version 1197 | # isn't outside it 1198 | if high.operator == comp or high.operator == ecomp: 1199 | return False 1200 | 1201 | # If the lowest version comparator has an operator and our version 1202 | # is less than it then it isn't higher than the range 1203 | if (not low.operator or low.operator == comp) and ltefn(version, low.semver): 1204 | return False 1205 | elif low.operator == ecomp and ltfn(version, low.semver): 1206 | return False 1207 | return True 1208 | 1209 | 1210 | # helpers 1211 | def _sorted(versions, loose=False, key=None): 1212 | key = key or cmp_to_key(lambda x, y: x.compare(y)) 1213 | return sorted([make_semver(v, loose=loose) for v in versions], key=key) 1214 | --------------------------------------------------------------------------------