|
1
|
|
|
#!/usr/bin/env python |
|
2
|
|
|
|
|
3
|
|
|
# 'Assumptions': input string follows Sem Ver 2.0 |
|
4
|
|
|
# with 2 'Limitations' on pre-release metadata and build metadata: |
|
5
|
|
|
|
|
6
|
|
|
# 1) if user wants to include pre-release info, they must |
|
7
|
|
|
# - separate with dash (-) from M.m.p (ie 1.0.0-dev) |
|
8
|
|
|
# - only include characters from [a-z] |
|
9
|
|
|
# 2) no build metadata are supported and string MUST end with patch or pre-release metadata |
|
10
|
|
|
|
|
11
|
|
|
import re |
|
12
|
|
|
import sys |
|
13
|
|
|
|
|
14
|
|
|
if len(sys.argv) != 2: |
|
15
|
|
|
print("Usage: process_sem_ver.py <version>") |
|
16
|
|
|
print("Example: process_sem_ver.py 1.0.0-dev") |
|
17
|
|
|
sys.exit(1) |
|
18
|
|
|
|
|
19
|
|
|
semver: str = sys.argv[1] |
|
20
|
|
|
|
|
21
|
|
|
|
|
22
|
|
|
VERSION_PATTERN = r""" |
|
23
|
|
|
v? |
|
24
|
|
|
(?: |
|
25
|
|
|
(?:(?P<epoch>[0-9]+)!)? # epoch |
|
26
|
|
|
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment |
|
27
|
|
|
(?P<pre> # pre-release |
|
28
|
|
|
[-_\.]? |
|
29
|
|
|
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview)) |
|
30
|
|
|
[-_\.]? |
|
31
|
|
|
(?P<pre_n>[0-9]+)? |
|
32
|
|
|
)? |
|
33
|
|
|
(?P<post> # post release |
|
34
|
|
|
(?:-(?P<post_n1>[0-9]+)) |
|
35
|
|
|
| |
|
36
|
|
|
(?: |
|
37
|
|
|
[-_\.]? |
|
38
|
|
|
(?P<post_l>post|rev|r) |
|
39
|
|
|
[-_\.]? |
|
40
|
|
|
(?P<post_n2>[0-9]+)? |
|
41
|
|
|
) |
|
42
|
|
|
)? |
|
43
|
|
|
(?P<dev> # dev release |
|
44
|
|
|
[-_\.]? |
|
45
|
|
|
(?P<dev_l>dev) |
|
46
|
|
|
[-_\.]? |
|
47
|
|
|
(?P<dev_n>[0-9]+)? |
|
48
|
|
|
)? |
|
49
|
|
|
) |
|
50
|
|
|
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version |
|
51
|
|
|
""" |
|
52
|
|
|
|
|
53
|
|
|
_regex = re.compile( |
|
54
|
|
|
r"^\s*" + VERSION_PATTERN + r"\s*$", |
|
55
|
|
|
re.VERBOSE | re.IGNORECASE, |
|
56
|
|
|
) |
|
57
|
|
|
|
|
58
|
|
|
|
|
59
|
|
|
# Verify input string meets our 'Hard Requirements', otherwise show message: |
|
60
|
|
|
# - indicating what happened and crashed the program |
|
61
|
|
|
# - what caused the crash |
|
62
|
|
|
# - how the user input is part of the cause |
|
63
|
|
|
# - what the user should try to do, to fix the issue |
|
64
|
|
|
|
|
65
|
|
|
# 'Hard Requirements', based on 'Assumptions' and 'Limitations' (see above): |
|
66
|
|
|
# - string must be at least 5 characters long |
|
67
|
|
|
# - string mush have 2 dots |
|
68
|
|
|
# - string must end with a patch or pre-release metadata |
|
69
|
|
|
# - string must have a dash (-) if pre-release metadata is included |
|
70
|
|
|
# - if prerelase metadata is included, it must be only characheters from [a-z] |
|
71
|
|
|
|
|
72
|
|
|
# Verify string is at least 5 characters long |
|
73
|
|
|
if len(semver) < 5: |
|
74
|
|
|
print("[ERROR]: Sem Ver Version string must be at least 5 characters long") |
|
75
|
|
|
print(f"Your input: {semver}") |
|
76
|
|
|
print(f"Your input is only {len(semver)} characters long") |
|
77
|
|
|
print("Please try again with a version string that is at least 5 characters long") |
|
78
|
|
|
sys.exit(1) |
|
79
|
|
|
|
|
80
|
|
|
# Verify string has 2 dots |
|
81
|
|
|
if semver.count(".") != 2: |
|
82
|
|
|
print("[ERROR]: Version string must have 2 dots") |
|
83
|
|
|
print(f"Your input: {semver}") |
|
84
|
|
|
print(f"Your input has {semver.count('.')} dots") |
|
85
|
|
|
print("Please try again with a version string that has 2 dots") |
|
86
|
|
|
sys.exit(1) |
|
87
|
|
|
|
|
88
|
|
|
|
|
89
|
|
|
# Interact with the Sem Ver 2.0 Regular Expression at https://regex101.com/r/Ly7O1x/3/ |
|
90
|
|
|
# Sem Ver 2.0 Docs related section at: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string |
|
91
|
|
|
|
|
92
|
|
|
# Note: Sem Ver 2.0 requires dash (-) to separate pre-release metadata |
|
93
|
|
|
|
|
94
|
|
|
# Verify string ends with a patch or pre-release metadata |
|
95
|
|
|
## Sem Ver 2.0 requires dash (-) to separate pre-release metadata |
|
96
|
|
|
|
|
97
|
|
|
if "-" not in semver: # if no Sem Ver 2.0 prerelease separator found in the string |
|
98
|
|
|
# for us now it is impossible to have pre-release metadata |
|
99
|
|
|
# and the string MUST be in Major.Minor.Patch format only |
|
100
|
|
|
|
|
101
|
|
|
if semver[-1] not in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]: |
|
102
|
|
|
# ERROR: Should be dealing with M.m.p case, but last character is not digit |
|
103
|
|
|
print("[ERROR]: Version string must end with a patch or pre-release metadata") |
|
104
|
|
|
print(f"Your input: {semver}") |
|
105
|
|
|
print(f"Your input ends with {semver[-1]}") |
|
106
|
|
|
print( |
|
107
|
|
|
"Please try again with a version string that ends with a patch or pre-release metadata" |
|
108
|
|
|
) |
|
109
|
|
|
print( |
|
110
|
|
|
"EXPLANATION: Since we did not find a dash (-) in the input, we expect to", |
|
111
|
|
|
" the Input Version String to be of 'Major.Minor.Patch' format." |
|
112
|
|
|
" So, 'Patch' must be the last part of the string, thus the last digit must be a number." |
|
113
|
|
|
f"But we found {semver[-1]} instead\n." |
|
114
|
|
|
"If you intended to include 'pre-release' metadata," |
|
115
|
|
|
" please concatenate a dash (-) to the mandatory starting 'Major.Minor.Patch' part" |
|
116
|
|
|
" and then add your 'pre-release' metadata, ie '1.0.0-dev'.", |
|
117
|
|
|
) |
|
118
|
|
|
sys.exit(1) |
|
119
|
|
|
|
|
120
|
|
|
# more reg ex checks can go here, but that not really the purpose of this script |
|
121
|
|
|
|
|
122
|
|
|
else: # if Sem Ver 2.0 prerelease separator found in the string |
|
123
|
|
|
# 1) Given the above condition |
|
124
|
|
|
# 2) Given scripts 'Limitations': |
|
125
|
|
|
# - only characters [a-z] are allowed for prerelease metadata |
|
126
|
|
|
# - we do not support 'build metadata' of Sem Ver 2.0 |
|
127
|
|
|
|
|
128
|
|
|
# Then |
|
129
|
|
|
# - expect to find exactly 1 dash (-), |
|
130
|
|
|
# - the dash is right after Patch (ie Major.Minor.Patch-Prerelaese) |
|
131
|
|
|
# - only [a-z] characters are found in prerelease substring |
|
132
|
|
|
# - prerelease substring is not empty |
|
133
|
|
|
|
|
134
|
|
|
if semver.count("-") != 1: |
|
135
|
|
|
# ERROR: Should be dealing with M.m.p-prerelase case, but more than 1 dash found |
|
136
|
|
|
print("[ERROR]: Version string must have exactly 1 dash (-)") |
|
137
|
|
|
print(f"Your input: {semver}") |
|
138
|
|
|
print(f"Your input has {semver.count('-')} dashes") |
|
139
|
|
|
print("Please try again with a version string that has exactly 1 dash (-)") |
|
140
|
|
|
print( |
|
141
|
|
|
"EXPLANATION: Since, we found a dash (-) in the input, and given the script 'Limitation' that we do not support build-metadata, we expect", |
|
142
|
|
|
" the Input Version String to be of 'Major.Minor.Patch-Prerelease' format.", |
|
143
|
|
|
) |
|
144
|
|
|
sys.exit(1) |
|
145
|
|
|
|
|
146
|
|
|
prerelease: str = semver.split("-")[1] # get the prerelease substring |
|
147
|
|
|
|
|
148
|
|
|
if prerelease == "": |
|
149
|
|
|
# ERROR: Should be dealing with M.m.p-prerelase case, but prerelease substring is empty |
|
150
|
|
|
print("[ERROR]: Version string must have a non-empty prerelease substring") |
|
151
|
|
|
print(f"Your input: {semver}") |
|
152
|
|
|
print("Your input has an empty prerelease substring") |
|
153
|
|
|
print( |
|
154
|
|
|
"Please try again with a version string that has a non-empty prerelease substring" |
|
155
|
|
|
) |
|
156
|
|
|
print( |
|
157
|
|
|
"EXPLANATION: Since, we found a dash (-) in the input, and given the script 'Limitation' that we do not support build-metadata, we expect", |
|
158
|
|
|
" the Input Version String to be of 'Major.Minor.Patch-Prerelease' format.", |
|
159
|
|
|
) |
|
160
|
|
|
sys.exit(1) |
|
161
|
|
|
|
|
162
|
|
|
# english alphabet has 26 characters |
|
163
|
|
|
# lowercase_chars = set(chr(ord("a") + i) for i in range(26)) |
|
164
|
|
|
# if lowercase_chars.issuperset(prerelease) is False: |
|
165
|
|
|
# # ERROR: Should be dealing with M.m.p-prerelase case, but found non [a-z] characters in prerelease substring |
|
166
|
|
|
# print("[ERROR]: Version string's prerelease must have only [a-z] characters") |
|
167
|
|
|
# print(f"Your input: {semver}") |
|
168
|
|
|
# print(f"Your input has {prerelease}") |
|
169
|
|
|
# print( |
|
170
|
|
|
# "Please try again with a version string that has only [a-z] characters in prerelease substring" |
|
171
|
|
|
# ) |
|
172
|
|
|
# print( |
|
173
|
|
|
# "EXPLANATION: Since we found a dash (-) in the input, we expect to", |
|
174
|
|
|
# " the Input Version String to be of 'Major.Minor.Patch-Prerelease' format.", |
|
175
|
|
|
# ) |
|
176
|
|
|
# sys.exit(1) |
|
177
|
|
|
|
|
178
|
|
|
# more reg ex checks can go here, but that not really the purpose of this script |
|
179
|
|
|
|
|
180
|
|
|
|
|
181
|
|
|
# If we got here, then the input string is a valid Sem Ver 2.0 string |
|
182
|
|
|
# And valid as input to the rest of the script |
|
183
|
|
|
|
|
184
|
|
|
|
|
185
|
|
|
# CRITICAL to be in par with Pip sdist /wheel and python -m build operations |
|
186
|
|
|
# https://peps.python.org/pep-0440/#compatibility-with-other-version-schemes |
|
187
|
|
|
|
|
188
|
|
|
|
|
189
|
|
|
parsed_versions_string = _regex.match(semver) |
|
190
|
|
|
|
|
191
|
|
|
prerelease = parsed_versions_string.group("pre") or parsed_versions_string.group("dev") or '' |
|
192
|
|
|
|
|
193
|
|
|
sep = '' |
|
194
|
|
|
|
|
195
|
|
|
to_add = '' |
|
196
|
|
|
if prerelease: |
|
197
|
|
|
prerelease = prerelease.replace('.', '').replace('-', '').replace('_', '') |
|
198
|
|
|
|
|
199
|
|
|
if prerelease.startswith('rc'): |
|
200
|
|
|
sep = '' |
|
201
|
|
|
elif prerelease.startswith('dev'): |
|
202
|
|
|
sep = '.' |
|
203
|
|
|
else: |
|
204
|
|
|
# don't have code hre to handle yet |
|
205
|
|
|
print('ERROR: Our current limitation is that prerelease must start with rc or dev') |
|
206
|
|
|
print(f'Your input: {semver}') |
|
207
|
|
|
print(f'Your input has {prerelease}') |
|
208
|
|
|
sys.exit(1) |
|
209
|
|
|
|
|
210
|
|
|
string = prerelease.replace('rc', '').replace('dev', '') |
|
211
|
|
|
|
|
212
|
|
|
try: |
|
213
|
|
|
int(string) |
|
214
|
|
|
# last part is a number already, so we keep that as it is |
|
215
|
|
|
to_add = '' |
|
216
|
|
|
except ValueError: |
|
217
|
|
|
to_add = '0' |
|
218
|
|
|
|
|
219
|
|
|
|
|
220
|
|
|
print( |
|
221
|
|
|
f'{parsed_versions_string.group("epoch") or ""}' |
|
222
|
|
|
f'{parsed_versions_string.group("release")}' |
|
223
|
|
|
f'{sep}{prerelease}{to_add}' |
|
224
|
|
|
) |
|
225
|
|
|
|
|
226
|
|
|
assert sys.argv[1] == semver |
|
227
|
|
|
|
|
228
|
|
|
# here it safe to implement the logic simply as: |
|
229
|
|
|
# if there is a dash then convert to dot and add trailing zero (0), else return as it is |
|
230
|
|
|
# print(sys.argv[1] if "-" not in sys.argv[1] else sys.argv[1].replace("-", ".") + "0") |
|
231
|
|
|
|
|
232
|
|
|
|
|
233
|
|
|
# ALT Format 1 |
|
234
|
|
|
# we provide oneliner with exa same print statement, except we skip all checks |
|
235
|
|
|
|
|
236
|
|
|
# print(sys.argv[1] if "-" not in sys.argv[1] else sys.argv[1].replace("-", ".") + "0") |
|
237
|
|
|
|
|
238
|
|
|
|
|
239
|
|
|
# ALT Format 2 |
|
240
|
|
|
# We provide python script, as shell command, with same input and output as this script |
|
241
|
|
|
# ( we skip all the checks, and provide the shell starting with python -c '' ) |
|
242
|
|
|
|
|
243
|
|
|
# python -c 'import sys; print(sys.argv[1] if "-" not in sys.argv[1] else sys.argv[1].replace("-", ".") + "0")' 1.0.0-dev |
|
244
|
|
|
|
|
245
|
|
|
|
|
246
|
|
|
# PARSED_DISTRO_SEMVER=$(python -c 'import sys; print(sys.argv[1] if "-" not in sys.argv[1] else sys.argv[1].replace("-", ".") + "0")' "${PARSED_VERSION}") |
|
247
|
|
|
|
|
248
|
|
|
# if [[ "${PARSED_DISTRO_SEMVER}" != "${TAG_SEM_VER}" ]]; then |
|
249
|
|
|
# echo "ERROR: Version in __init__.py (${PARSED_DISTRO_SEMVER}) does not match tag (${TAG_SEM_VER})" |
|
250
|
|
|
# exit 1 |
|
251
|
|
|
# fi |
|
252
|
|
|
|
|
253
|
|
|
|
|
254
|
|
|
# PARSER="scripts/parse_version.py" |
|
255
|
|
|
# PARSED_VERSION=$(python "${PARSER}") |
|
256
|
|
|
|