|
1 | | -from __future__ import annotations |
2 | | - |
3 | | -import subprocess |
4 | | -from dataclasses import dataclass |
5 | | -from enum import Enum |
6 | | -from functools import wraps, total_ordering |
7 | | -from pathlib import Path |
8 | | -from shutil import which |
9 | | -from typing import Any |
10 | | - |
11 | | -from exasol.toolbox.error import ToolboxError |
12 | | - |
13 | | - |
14 | | -def _index_or(container, index, default): |
15 | | - try: |
16 | | - return container[index] |
17 | | - except IndexError: |
18 | | - return default |
19 | | - |
20 | | - |
21 | | -class ReleaseTypes(Enum): |
22 | | - Major = "major" |
23 | | - Minor = "minor" |
24 | | - Patch = "patch" |
25 | | - |
26 | | - def __str__(self): |
27 | | - return self.name.lower() |
28 | | - |
29 | | - |
30 | | -def poetry_command(func): |
31 | | - @wraps(func) |
32 | | - def wrapper(*args, **kwargs): |
33 | | - cmd = which("poetry") |
34 | | - if not cmd: |
35 | | - raise ToolboxError("Couldn't find poetry executable") |
36 | | - try: |
37 | | - return func(*args, **kwargs) |
38 | | - except subprocess.CalledProcessError as ex: |
39 | | - raise ToolboxError(f"Failed to execute: {ex.cmd}") from ex |
40 | | - |
41 | | - return wrapper |
42 | | - |
43 | | - |
44 | | -@total_ordering |
45 | | -@dataclass(frozen=True) |
46 | | -class Version: |
47 | | - major: int |
48 | | - minor: int |
49 | | - patch: int |
50 | | - |
51 | | - def __str__(self): |
52 | | - return f"{self.major}.{self.minor}.{self.patch}" |
53 | | - |
54 | | - def __lt__(self, other: object): |
55 | | - if not isinstance(other, Version): |
56 | | - return NotImplemented |
57 | | - return ( |
58 | | - self.major < other.major |
59 | | - or (self.major <= other.major and self.minor < other.minor) |
60 | | - or ( |
61 | | - self.major <= other.major |
62 | | - and self.minor <= other.minor |
63 | | - and self.patch < other.patch |
64 | | - ) |
65 | | - ) |
66 | | - |
67 | | - def __eq__(self, other: object): |
68 | | - if not isinstance(other, Version): |
69 | | - return NotImplemented |
70 | | - return ( |
71 | | - self.major == other.major |
72 | | - and self.minor == other.minor |
73 | | - and self.patch == other.patch |
74 | | - ) |
75 | | - |
76 | | - @staticmethod |
77 | | - def from_string(version): |
78 | | - parts = [int(number, base=0) for number in version.split(".")] |
79 | | - if len(parts) > 3: |
80 | | - raise ValueError( |
81 | | - "Version has an invalid format, " |
82 | | - f"expected: '<major>.<minor>.<patch>', actual: '{version}'" |
83 | | - ) |
84 | | - version = [_index_or(parts, i, 0) for i in range(3)] |
85 | | - return Version(*version) |
86 | | - |
87 | | - @staticmethod |
88 | | - @poetry_command |
89 | | - def from_poetry(): |
90 | | - output = subprocess.run( |
91 | | - ["poetry", "version", "--no-ansi", "--short"], |
92 | | - capture_output=True, |
93 | | - text=True, |
94 | | - ) |
95 | | - return Version.from_string(output.stdout.strip()) |
96 | | - |
97 | | - @staticmethod |
98 | | - @poetry_command |
99 | | - def upgrade_version_from_poetry(t: ReleaseTypes): |
100 | | - output = subprocess.run( |
101 | | - ["poetry", "version", str(t), "--dry-run", "--no-ansi", "--short"], |
102 | | - capture_output=True, |
103 | | - text=True, |
104 | | - ) |
105 | | - return Version.from_string(output.stdout.strip()) |
106 | | - |
107 | | - @staticmethod |
108 | | - def from_python_module(path: Path) -> Version: |
109 | | - """Retrieve version information from the `version` module""" |
110 | | - with open(path, encoding="utf-8") as file: |
111 | | - _locals: dict[str, Any] = {} |
112 | | - _globals: dict[str, Any] = {} |
113 | | - exec(file.read(), _locals, _globals) |
114 | | - |
115 | | - try: |
116 | | - version = _globals["VERSION"] |
117 | | - except KeyError as ex: |
118 | | - raise ToolboxError("Couldn't find version within module") from ex |
119 | | - |
120 | | - return Version.from_string(version) |
0 commit comments