Skip to content

Commit

Permalink
Change: Abstract Version and VersionCalculator
Browse files Browse the repository at this point in the history
Change Version and VersionCalculator into abstract bases classes with a
defined interface to allow implementing specific implementations for a
versioning scheme like PEP 440.

Also move VersionCommand into an own "private" module.
  • Loading branch information
bjoernricks authored and y0urself committed Mar 14, 2023
1 parent c305be0 commit 88758e1
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 200 deletions.
74 changes: 74 additions & 0 deletions pontos/version/_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Copyright (C) 2023 Greenbone Networks GmbH
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from abc import ABC, abstractmethod
from pathlib import Path
from typing import Literal, Union

from .scheme import VersioningScheme
from .version import Version, VersionUpdate


class VersionCommand(ABC):
"""Generic class usable to implement the
version commands for several programming languages"""

project_file_name: str

def __init__(self, versioning_scheme: VersioningScheme) -> None:
self.project_file_path = Path.cwd() / self.project_file_name
self.versioning_scheme = versioning_scheme

@abstractmethod
def get_current_version(self) -> Version:
"""Get the current version of this project"""

@abstractmethod
def verify_version(
self, version: Union[Literal["current"], Version, None]
) -> None:
"""
Verify the current version of this project
Args:
version: Version to check against the current applied version of
this project. If version is None or "current" the command should
verify if all version information is consistent, for example if
the version information in several files is the same.
"""

@abstractmethod
def update_version(
self, new_version: Version, *, force: bool = False
) -> VersionUpdate:
"""
Update the current version of this project
Args:
new_version: Use this version in the update
force: Force updating the version even if the current version is the
same as the new version
Returns:
The version update including the changed files
"""

def project_found(self) -> bool:
"""
Returns True if a command has detected a corresponding project
"""
return self.project_file_path.exists()
217 changes: 77 additions & 140 deletions pontos/version/calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,68 +15,71 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from abc import ABC, abstractmethod
from datetime import datetime
from typing import Type

from .errors import VersionError
from .version import Version, parse_version
from pontos.version.errors import VersionError

from .version import Version

class VersionCalculator:
def next_patch_version(self, current_version: Version) -> Version:
"""
Get the next patch version from a valid version

Examples:
"1.2.3" will return "1.2.4"
"1.2.3.dev1" will return "1.2.3"
class VersionCalculator(ABC):
"""
An abstract base class for calculating a next version from a version
"""

version_cls: Type[Version]

@classmethod
def version_from_string(cls, version: str) -> Version:
"""
Create a version from a version string
if current_version.is_prerelease:
next_version = parse_version(
f"{current_version.major}."
f"{current_version.minor}."
f"{current_version.micro}"
)
else:
next_version = parse_version(
f"{current_version.major}."
f"{current_version.minor}."
f"{current_version.micro + 1}"
)
Args:
version: Version string to parse
return next_version
Raises:
VersionError: If the version string is invalid.
Returns:
A new version instance
"""
return cls.version_cls.from_string(version)

def next_calendar_version(self, current_version: Version) -> Version:
@classmethod
def next_calendar_version(cls, current_version: Version) -> Version:
"""
Find the correct next calendar version by checking latest version and
the today's date
Raises:
VersionError if version is invalid.
VersionError: If version is invalid.
"""

today = datetime.today()
current_year_short = today.year % 100

if current_version.major < current_year_short or (
current_version.major == current_year_short
and current_version.minor < today.month
):
return parse_version(f"{current_year_short}.{today.month}.0")
return cls.version_from_string(
f"{current_year_short}.{today.month}.0"
)

if (
current_version.major == today.year % 100
and current_version.minor == today.month
):
if current_version.dev is None:
release_version = parse_version(
release_version = cls.version_from_string(
f"{current_year_short}.{today.month}."
f"{current_version.micro + 1}"
f"{current_version.patch + 1}"
)
else:
release_version = parse_version(
release_version = cls.version_from_string(
f"{current_year_short}.{today.month}."
f"{current_version.micro}"
f"{current_version.patch}"
)
return release_version
else:
Expand All @@ -85,147 +88,81 @@ def next_calendar_version(self, current_version: Version) -> Version:
f"'{current_year_short}.{today.month}'."
)

def next_minor_version(self, current_version: Version) -> Version:
"""
Get the next minor version from a valid version
Examples:
"1.2.3" will return "1.3.0"
"1.2.3.dev1" will return "1.3.0"
"""
return parse_version(
f"{current_version.major}.{current_version.minor + 1}.0"
)

def next_major_version(self, current_version: Version) -> Version:
@classmethod
def next_major_version(cls, current_version: Version) -> Version:
"""
Get the next major version from a valid version
Examples:
"1.2.3" will return "2.0.0"
"1.2.3.dev1" will return "2.0.0"
"1.2.3-alpha1" will return "2.0.0"
"""
return parse_version(f"{current_version.major + 1}.0.0")
return cls.version_from_string(f"{current_version.major + 1}.0.0")

def next_dev_version(self, current_version: Version) -> Version:
@classmethod
def next_minor_version(cls, current_version: Version) -> Version:
"""
Get the next development version from a valid version
Get the next minor version from a valid version
Examples:
"1.2.3" will return "1.2.4.dev1"
"1.2.3.dev1" will return "1.2.3.dev2"
"1.2.3" will return "1.3.0"
"1.2.3.dev1" will return "1.3.0"
"1.2.3-alpha1" will return "1.3.0"
"""
if current_version.is_devrelease:
release_version = parse_version(
f"{current_version.major}."
f"{current_version.minor}."
f"{current_version.micro }.dev{current_version.dev + 1}"
)
elif current_version.is_prerelease:
release_version = parse_version(
f"{current_version.major}."
f"{current_version.minor}."
f"{current_version.micro }{current_version.pre[0]}"
f"{current_version.pre[1]}+dev1"
)
else:
release_version = parse_version(
f"{current_version.major}."
f"{current_version.minor}."
f"{current_version.micro + 1 }.dev1"
)

return release_version
return cls.version_from_string(
f"{current_version.major}.{current_version.minor + 1}.0"
)

def next_alpha_version(self, current_version: Version) -> Version:
@classmethod
def next_patch_version(cls, current_version: Version) -> Version:
"""
Get the next alpha version from a valid version
Get the next patch version from a valid version
Examples:
"1.2.3" will return "1.2.4a1"
"1.2.3.dev1" will return "1.2.3a1"
"1.2.3" will return "1.2.4"
"1.2.3.dev1" will return "1.2.4"
"1.2.3-alpha1" will return "1.2.4"
"""
if current_version.is_devrelease:
release_version = parse_version(
if current_version.is_pre_release:
next_version = cls.version_from_string(
f"{current_version.major}."
f"{current_version.minor}."
f"{current_version.micro }a1"
)
elif current_version.is_prerelease and current_version.pre[0] == "a":
release_version = parse_version(
f"{current_version.major}."
f"{current_version.minor}."
f"{current_version.micro }a{current_version.pre[1] + 1}"
f"{current_version.patch}"
)
else:
release_version = parse_version(
next_version = cls.version_from_string(
f"{current_version.major}."
f"{current_version.minor}."
f"{current_version.micro + 1 }a1"
f"{current_version.patch + 1}"
)

return release_version
return next_version

def next_beta_version(self, current_version: Version) -> Version:
@staticmethod
@abstractmethod
def next_dev_version(current_version: Version) -> Version:
"""
Get the next alpha version from a valid version
Examples:
"1.2.3" will return "1.2.4b1"
"1.2.3.dev1" will return "1.2.3b1"
Get the next development version from a valid version
"""
if current_version.is_devrelease or (
current_version.is_prerelease and current_version.pre[0] == "a"
):
release_version = parse_version(
f"{current_version.major}."
f"{current_version.minor}."
f"{current_version.micro }b1"
)
elif current_version.is_prerelease and current_version.pre[0] == "b":
release_version = parse_version(
f"{current_version.major}."
f"{current_version.minor}."
f"{current_version.micro }b{current_version.pre[1] + 1}"
)
else:
release_version = parse_version(
f"{current_version.major}."
f"{current_version.minor}."
f"{current_version.micro + 1 }b1"
)

return release_version

def next_release_candidate_version(
self, current_version: Version
) -> Version:
@staticmethod
@abstractmethod
def next_alpha_version(current_version: Version) -> Version:
"""
Get the next alpha version from a valid version
"""

Examples:
"1.2.3" will return "1.2.4rc1"
"1.2.3.dev1" will return "1.2.3rc1"
@staticmethod
@abstractmethod
def next_beta_version(current_version: Version) -> Version:
"""
Get the next beta version from a valid version
"""
if current_version.is_devrelease or (
current_version.is_prerelease and current_version.pre[0] != "rc"
):
release_version = parse_version(
f"{current_version.major}."
f"{current_version.minor}."
f"{current_version.micro }rc1"
)
elif current_version.is_prerelease and current_version.pre[0] == "rc":
release_version = parse_version(
f"{current_version.major}."
f"{current_version.minor}."
f"{current_version.micro }rc{current_version.pre[1] + 1}"
)
else:
release_version = parse_version(
f"{current_version.major}."
f"{current_version.minor}."
f"{current_version.micro + 1 }rc1"
)

return release_version
@staticmethod
@abstractmethod
def next_release_candidate_version(current_version: Version) -> Version:
"""
Get the next release candidate version from a valid version
"""
Loading

0 comments on commit 88758e1

Please sign in to comment.