diff --git a/src/check_python_versions/sources/tox.py b/src/check_python_versions/sources/tox.py index 1c441ed..df076cf 100644 --- a/src/check_python_versions/sources/tox.py +++ b/src/check_python_versions/sources/tox.py @@ -106,6 +106,8 @@ def tox_env_to_py_version(env: str) -> Optional[Version]: return Version.from_string('PyPy' + env[4:]) elif env.startswith('py') and len(env) >= 4 and env[2:].isdigit(): return Version.from_string(f'{env[2]}.{env[3:]}') + elif env.startswith('py') and '.' in env: + return Version.from_string(f'{env[2:]}', has_dot=True) else: return None @@ -223,7 +225,10 @@ def update_tox_envlist(envlist: str, new_versions: SortedVersionList) -> str: def toxenv_for_version(ver: Version) -> str: """Compute a tox environment name for a Python version.""" - return f"py{ver.major}{ver.minor if ver.minor >= 0 else ''}" + _ret_str = f"py{ver.major}" \ + f"{'.' if ver.has_dot else ''}" \ + f"{ver.minor if ver.minor >= 0 else ''}" + return _ret_str def should_keep(env: str, new_versions: VersionList) -> bool: @@ -236,7 +241,7 @@ def should_keep(env: str, new_versions: VersionList) -> bool: or 3.x version respectively in ``new_versions``. """ - if not re.match(r'py(py)?\d*($|-)', env): + if not re.match(r'py(py)?(\d[.])?\d*($|-)', env): return True if env == 'pypy': return any(ver.major == 2 for ver in new_versions) diff --git a/src/check_python_versions/versions.py b/src/check_python_versions/versions.py index 0aff51c..38f5a4f 100644 --- a/src/check_python_versions/versions.py +++ b/src/check_python_versions/versions.py @@ -43,9 +43,10 @@ class Version(NamedTuple): major: int = -1 # I'd've preferred to use None, but it complicates sorting minor: int = -1 suffix: str = '' + has_dot: bool = False @classmethod - def from_string(cls, v: str) -> 'Version': + def from_string(cls, v: str, has_dot: bool = False) -> 'Version': m = VERSION_RX.match(v) assert m is not None prefix, major, minor, suffix = m.groups() @@ -54,6 +55,7 @@ def from_string(cls, v: str) -> 'Version': int(major) if major else -1, int(minor[1:]) if minor else -1, suffix, + has_dot, ) def __repr__(self) -> str: @@ -62,6 +64,7 @@ def __repr__(self) -> str: f'major={self.major!r}' if self.major != -1 else '', f'minor={self.minor!r}' if self.minor != -1 else '', f'suffix={self.suffix!r}' if self.suffix else '', + f'dot={self.has_dot!r}' if self.has_dot else '', ] if part)) def __str__(self) -> str: diff --git a/tests/sources/test_tox.py b/tests/sources/test_tox.py index d83723f..a34063d 100644 --- a/tests/sources/test_tox.py +++ b/tests/sources/test_tox.py @@ -15,8 +15,11 @@ from check_python_versions.versions import Version -def v(versions: List[str]) -> List[Version]: - return [Version.from_string(v) for v in versions] +def v(versions: List[str], has_dot: List[bool] = []) -> List[Version]: + if not has_dot: + has_dot = [False] * len(versions) + return [Version.from_string(v, has_dot=d) + for v, d in zip(versions, has_dot)] def test_get_tox_ini_python_versions(tmp_path): @@ -28,6 +31,16 @@ def test_get_tox_ini_python_versions(tmp_path): assert get_tox_ini_python_versions(tox_ini) == v(['2.7', '3.6', '3.10']) +def test_get_tox_ini_dotted_python_versions(tmp_path): + tox_ini = tmp_path / "tox.ini" + tox_ini.write_text(textwrap.dedent("""\ + [tox] + envlist = py2.7,py3.6,py2.7-docs,pylint,py3.10 + """)) + assert get_tox_ini_python_versions(tox_ini) == \ + v(['2.7', '3.6', '3.10'], has_dot=[True, True, True]) + + def test_get_tox_ini_python_versions_syntax_error(tmp_path): tox_ini = tmp_path / "tox.ini" tox_ini.write_text(textwrap.dedent("""\ @@ -107,6 +120,51 @@ def test_update_tox_ini_python_versions(): """) +def test_update_tox_ini_dotted_python_versions(): + fp = StringIO(textwrap.dedent("""\ + [tox] + envlist = py2.6, py2.7 + """)) + fp.name = 'tox.ini' + result = update_tox_ini_python_versions(fp, + v(['3.6', '3.7', '3.10'], + has_dot=[True, True, True])) + assert "".join(result) == textwrap.dedent("""\ + [tox] + envlist = py3.6, py3.7, py3.10 + """) + + +def test_update_tox_ini_one_dotted_python_versions(): + fp = StringIO(textwrap.dedent("""\ + [tox] + envlist = py26, py2.7 + """)) + fp.name = 'tox.ini' + result = update_tox_ini_python_versions(fp, + v(['3.6', '3.7', '3.10'], + has_dot=[True, True, True])) + assert "".join(result) == textwrap.dedent("""\ + [tox] + envlist = py3.6, py3.7, py3.10 + """) + + +def test_update_tox_ini_mixed_dotted_python_versions(): + fp = StringIO(textwrap.dedent("""\ + [tox] + envlist = py26, py2.7 + """)) + fp.name = 'tox.ini' + result = update_tox_ini_python_versions(fp, + v(['3.6', '3.7', '3.10'], + has_dot=[True, False, True])) + assert "".join(result) == textwrap.dedent("""\ + [tox] + envlist = py3.6, py37, py3.10 + """) + + def test_update_tox_ini_python_syntax_error(capsys): fp = StringIO(textwrap.dedent("""\ [tox @@ -154,6 +212,13 @@ def test_update_tox_envlist_with_spaces(): assert result == 'py36, py37, pypy3' +def test_update_tox_envlist_with_dots_and_spaces(): + result = update_tox_envlist( + 'py27, py34, py35', + v(['3.6', '3.7'], has_dot=[True, False])) + assert result == 'py3.6, py37' + + @pytest.mark.parametrize('s, expected', [ # note that configparser trims leading whitespace, so \n is never # followed by a space diff --git a/tests/test_versions.py b/tests/test_versions.py index d814613..9da3598 100644 --- a/tests/test_versions.py +++ b/tests/test_versions.py @@ -13,6 +13,10 @@ def test_version_from_string() -> None: assert Version.from_string('3') == Version(major=3) assert Version.from_string('3.0') == Version(major=3, minor=0) assert Version.from_string('3.6') == Version(major=3, minor=6) + assert Version.from_string('3.6') == \ + Version(major=3, minor=6, has_dot=False) + assert Version.from_string('3.6', has_dot=True) == \ + Version(major=3, minor=6, has_dot=True) assert Version.from_string('3.10-dev') == Version('', 3, 10, '-dev') assert Version.from_string('PyPy') == Version('PyPy') assert Version.from_string('PyPy3') == Version('PyPy', 3) @@ -26,6 +30,8 @@ def test_version_repr() -> None: assert repr(Version(major=3)) == 'Version(major=3)' assert repr(Version(major=3, minor=0)) == 'Version(major=3, minor=0)' assert repr(Version(major=3, minor=6)) == 'Version(major=3, minor=6)' + assert repr(Version(major=3, minor=6, has_dot=True)) == \ + 'Version(major=3, minor=6, dot=True)' assert repr(Version(major=3, minor=10, suffix='-dev')) == \ "Version(major=3, minor=10, suffix='-dev')" assert repr(Version(prefix='PyPy')) == "Version(prefix='PyPy')"