diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee3d6a7..fc57b21 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ repos: - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 21.7b0 hooks: - id: black diff --git a/CHANGELOG.md b/CHANGELOG.md index cca9eea..2d0d1cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.0.0.dev2] - 2021-08-04 +### Fixed +- `keepachangelog.release` will now properly bump version in case the number of digit to compare was previously increased (such as if version 9 and 10 existed). + +### Added +- `keepachangelog.to_sorted_semantic` to be able to sort semantic versions. + ## [2.0.0.dev1] - 2021-05-27 ### Changed - `keepachangelog.release` will now return `None` instead of throwing an exception if there is no Unreleased content. @@ -77,7 +84,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Initial release. -[Unreleased]: https://github.com/Colin-b/keepachangelog/compare/v2.0.0.dev1...HEAD +[Unreleased]: https://github.com/Colin-b/keepachangelog/compare/v2.0.0.dev2...HEAD +[2.0.0.dev2]: https://github.com/Colin-b/keepachangelog/compare/v2.0.0.dev1...v2.0.0.dev2 [2.0.0.dev1]: https://github.com/Colin-b/keepachangelog/compare/v2.0.0.dev0...v2.0.0.dev1 [2.0.0.dev0]: https://github.com/Colin-b/keepachangelog/compare/v1.0.0...v2.0.0.dev0 [1.0.0]: https://github.com/Colin-b/keepachangelog/compare/v0.5.0...v1.0.0 diff --git a/LICENSE b/LICENSE index 17be259..b70e7e0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Colin Bounouar +Copyright (c) 2021 Colin Bounouar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index d95175e..3e3c240 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Build status Coverage Code style: black -Number of tests +Number of tests Number of downloads

diff --git a/keepachangelog/__init__.py b/keepachangelog/__init__.py index d40e7a6..8d3641a 100644 --- a/keepachangelog/__init__.py +++ b/keepachangelog/__init__.py @@ -1,2 +1,3 @@ from keepachangelog.version import __version__ from keepachangelog._changelog import to_dict, to_raw_dict, release, from_dict +from keepachangelog._versioning import to_sorted_semantic diff --git a/keepachangelog/_versioning.py b/keepachangelog/_versioning.py index 616b729..45dffc1 100644 --- a/keepachangelog/_versioning.py +++ b/keepachangelog/_versioning.py @@ -1,6 +1,6 @@ import re -from typing import Tuple, Optional - +from functools import cmp_to_key +from typing import Tuple, Optional, Iterable, List initial_semantic_version = { "major": 0, @@ -60,25 +60,76 @@ def bump(unreleased: dict, semantic_version: dict) -> dict: return semantic_version -def semantic_order(version: Tuple[str, dict]) -> str: - _, semantic_version = version +def _compare(first_version: str, second_version: str) -> int: + if first_version > second_version: + return 1 + + if first_version < second_version: + return -1 + + return 0 + + +def semantic_order( + first_version: Tuple[str, dict], second_version: Tuple[str, dict] +) -> int: + _, semantic_first_version = first_version + _, semantic_second_version = second_version + + major_difference = _compare( + semantic_first_version["major"], semantic_second_version["major"] + ) + if major_difference: + return major_difference + + minor_difference = _compare( + semantic_first_version["minor"], semantic_second_version["minor"] + ) + if minor_difference: + return minor_difference + + patch_difference = _compare( + semantic_first_version["patch"], semantic_second_version["patch"] + ) + if patch_difference: + return patch_difference + # Ensure release is "bigger than" pre-release - pre_release_order = ( - f"0{semantic_version['prerelease']}" if semantic_version["prerelease"] else "1" + pre_release_difference = _compare( + f"0{semantic_first_version['prerelease']}" + if semantic_first_version["prerelease"] + else "1", + f"0{semantic_second_version['prerelease']}" + if semantic_second_version["prerelease"] + else "1", ) - return f"{semantic_version['major']}.{semantic_version['minor']}.{semantic_version['patch']}.{pre_release_order}" + + return pre_release_difference def actual_version(changelog: dict) -> Tuple[Optional[str], dict]: - versions = sorted( + versions = to_sorted_semantic(changelog.keys()) + return versions.pop() if versions else (None, initial_semantic_version.copy()) + + +def to_sorted_semantic(versions: Iterable[str]) -> List[Tuple[str, dict]]: + """ + Convert a list of string semantic versions to a sorted list of semantic versions. + Note: unreleased is not considered as a semantic version and will thus be removed from the resulting versions. + + :param versions: un-ordered list of semantic versions (as string). Can contains unreleased. + :return: An ordered (first element is the oldest version, last element is the newest (highest)) list of versions. + Each version is represented as a 2-tuple: first one is the string version, second one is a dictionary containing: + 'major', 'minor', 'patch', 'prerelease', 'buildmetadata' keys. + """ + return sorted( [ (version, to_semantic(version)) - for version in changelog.keys() + for version in versions if version != "unreleased" ], - key=semantic_order, + key=cmp_to_key(semantic_order), ) - return versions.pop() if versions else (None, initial_semantic_version.copy()) def guess_unreleased_version( diff --git a/keepachangelog/version.py b/keepachangelog/version.py index 19294f4..a4a8380 100644 --- a/keepachangelog/version.py +++ b/keepachangelog/version.py @@ -3,4 +3,4 @@ # Major should be incremented in case there is a breaking change. (eg: 2.5.8 -> 3.0.0) # Minor should be incremented in case there is an enhancement. (eg: 2.5.8 -> 2.6.0) # Patch should be incremented in case there is a bug fix. (eg: 2.5.8 -> 2.5.9) -__version__ = "2.0.0.dev1" +__version__ = "2.0.0.dev2" diff --git a/setup.py b/setup.py index 17deebe..2849455 100644 --- a/setup.py +++ b/setup.py @@ -40,9 +40,9 @@ "testing": [ # Used to check starlette endpoint "requests==2.*", - "starlette==0.13.*", + "starlette==0.16.*", # Used to check flask-restx endpoint - "flask-restx==0.4.*", + "flask-restx==0.5.*", # Used to check coverage "pytest-cov==2.*", ] diff --git a/tests/test_changelog_release.py b/tests/test_changelog_release.py index a2e99b9..7053c5b 100644 --- a/tests/test_changelog_release.py +++ b/tests/test_changelog_release.py @@ -417,6 +417,87 @@ def unstable_changelog(tmpdir): return changelog_file_path +@pytest.fixture +def major_digit_changelog(tmpdir): + changelog_file_path = os.path.join(tmpdir, "MAJOR_DIGIT_CHANGELOG.md") + with open(changelog_file_path, "wt") as file: + file.write( + """# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +### Changed +- Enhancement + +## [10.9.90] - 2018-05-31 +### Changed +- Enhancement + +## [9.10.100] - 2018-05-31 +### Changed +- Enhancement +""" + ) + return changelog_file_path + + +@pytest.fixture +def minor_digit_changelog(tmpdir): + changelog_file_path = os.path.join(tmpdir, "MINOR_DIGIT_CHANGELOG.md") + with open(changelog_file_path, "wt") as file: + file.write( + """# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +### Added +- Enhancement + +## [9.9.100] - 2018-05-31 +### Added +- Enhancement + +## [9.10.90] - 2018-05-31 +### Added +- Enhancement +""" + ) + return changelog_file_path + + +@pytest.fixture +def patch_digit_changelog(tmpdir): + changelog_file_path = os.path.join(tmpdir, "PATCH_DIGIT_CHANGELOG.md") + with open(changelog_file_path, "wt") as file: + file.write( + """# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +### Fixed +- Enhancement + +## [9.9.10] - 2018-05-31 +### Fixed +- Enhancement + +## [9.9.9] - 2018-05-31 +### Fixed +- Enhancement +""" + ) + return changelog_file_path + + def test_major_release(major_changelog, mock_date): assert keepachangelog.release(major_changelog) == "2.0.0" with open(major_changelog) as file: @@ -563,6 +644,171 @@ def test_minor_release(minor_changelog, mock_date): ) +def test_major_digit_release(major_digit_changelog, mock_date): + assert keepachangelog.release(major_digit_changelog) == "11.0.0" + with open(major_digit_changelog) as file: + assert ( + file.read() + == """# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [11.0.0] - 2021-03-19 +### Changed +- Enhancement + +## [10.9.90] - 2018-05-31 +### Changed +- Enhancement + +## [9.10.100] - 2018-05-31 +### Changed +- Enhancement +""" + ) + + +def test_minor_digit_release(minor_digit_changelog, mock_date): + assert keepachangelog.release(minor_digit_changelog) == "9.11.0" + with open(minor_digit_changelog) as file: + assert ( + file.read() + == """# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [9.11.0] - 2021-03-19 +### Added +- Enhancement + +## [9.9.100] - 2018-05-31 +### Added +- Enhancement + +## [9.10.90] - 2018-05-31 +### Added +- Enhancement +""" + ) + + +def test_patch_digit_release(patch_digit_changelog, mock_date): + assert keepachangelog.release(patch_digit_changelog) == "9.9.11" + with open(patch_digit_changelog) as file: + assert ( + file.read() + == """# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [9.9.11] - 2021-03-19 +### Fixed +- Enhancement + +## [9.9.10] - 2018-05-31 +### Fixed +- Enhancement + +## [9.9.9] - 2018-05-31 +### Fixed +- Enhancement +""" + ) + + +def test_sorted_major_digit_semantic_release(major_digit_changelog, mock_date): + assert keepachangelog.to_sorted_semantic( + keepachangelog.to_raw_dict(major_digit_changelog) + ) == [ + ( + "9.10.100", + { + "buildmetadata": None, + "major": 9, + "minor": 10, + "patch": 100, + "prerelease": None, + }, + ), + ( + "10.9.90", + { + "buildmetadata": None, + "major": 10, + "minor": 9, + "patch": 90, + "prerelease": None, + }, + ), + ] + + +def test_sorted_minor_digit_semantic_release(minor_digit_changelog, mock_date): + assert keepachangelog.to_sorted_semantic( + keepachangelog.to_raw_dict(minor_digit_changelog) + ) == [ + ( + "9.9.100", + { + "buildmetadata": None, + "major": 9, + "minor": 9, + "patch": 100, + "prerelease": None, + }, + ), + ( + "9.10.90", + { + "buildmetadata": None, + "major": 9, + "minor": 10, + "patch": 90, + "prerelease": None, + }, + ), + ] + + +def test_sorted_patch_digit_semantic_release(patch_digit_changelog, mock_date): + assert keepachangelog.to_sorted_semantic( + keepachangelog.to_raw_dict(patch_digit_changelog) + ) == [ + ( + "9.9.9", + { + "buildmetadata": None, + "major": 9, + "minor": 9, + "patch": 9, + "prerelease": None, + }, + ), + ( + "9.9.10", + { + "buildmetadata": None, + "major": 9, + "minor": 9, + "patch": 10, + "prerelease": None, + }, + ), + ] + + def test_patch_release(patch_changelog, mock_date): assert keepachangelog.release(patch_changelog) == "1.1.1" with open(patch_changelog) as file: