python-semantic-release 9.19.0__py3-none-any.whl → 9.20.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_semantic_release-9.19.0.dist-info → python_semantic_release-9.20.0.dist-info}/METADATA +4 -2
- {python_semantic_release-9.19.0.dist-info → python_semantic_release-9.20.0.dist-info}/RECORD +24 -19
- semantic_release/__init__.py +1 -1
- semantic_release/__main__.py +7 -3
- semantic_release/cli/commands/main.py +3 -6
- semantic_release/cli/commands/version.py +30 -15
- semantic_release/cli/config.py +48 -42
- semantic_release/cli/util.py +1 -1
- semantic_release/data/templates/angular/md/.components/changes.md.j2 +4 -4
- semantic_release/data/templates/angular/md/.release_notes.md.j2 +2 -2
- semantic_release/data/templates/angular/rst/.components/changes.rst.j2 +4 -4
- semantic_release/globals.py +4 -2
- semantic_release/helpers.py +6 -0
- semantic_release/version/declaration.py +38 -132
- semantic_release/version/declarations/__init__.py +0 -0
- semantic_release/version/declarations/enum.py +12 -0
- semantic_release/version/declarations/i_version_replacer.py +67 -0
- semantic_release/version/declarations/pattern.py +241 -0
- semantic_release/version/declarations/toml.py +148 -0
- {python_semantic_release-9.19.0.dist-info → python_semantic_release-9.20.0.dist-info}/AUTHORS.rst +0 -0
- {python_semantic_release-9.19.0.dist-info → python_semantic_release-9.20.0.dist-info}/LICENSE +0 -0
- {python_semantic_release-9.19.0.dist-info → python_semantic_release-9.20.0.dist-info}/WHEEL +0 -0
- {python_semantic_release-9.19.0.dist-info → python_semantic_release-9.20.0.dist-info}/entry_points.txt +0 -0
- {python_semantic_release-9.19.0.dist-info → python_semantic_release-9.20.0.dist-info}/top_level.txt +0 -0
{python_semantic_release-9.19.0.dist-info → python_semantic_release-9.20.0.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-semantic-release
|
|
3
|
-
Version: 9.
|
|
3
|
+
Version: 9.20.0
|
|
4
4
|
Summary: Automatic Semantic Versioning for Python projects
|
|
5
5
|
Author-email: Rolf Erik Lekang <me@rolflekang.com>
|
|
6
6
|
License: MIT
|
|
@@ -33,6 +33,7 @@ Requires-Dist: importlib-resources~=6.0
|
|
|
33
33
|
Requires-Dist: pydantic~=2.0
|
|
34
34
|
Requires-Dist: rich~=13.0
|
|
35
35
|
Requires-Dist: shellingham~=1.5
|
|
36
|
+
Requires-Dist: Deprecated~=1.2
|
|
36
37
|
Provides-Extra: build
|
|
37
38
|
Requires-Dist: build~=1.2; extra == "build"
|
|
38
39
|
Provides-Extra: dev
|
|
@@ -45,7 +46,8 @@ Requires-Dist: sphinxcontrib-apidoc==0.5.0; extra == "docs"
|
|
|
45
46
|
Requires-Dist: sphinx-autobuild==2024.2.4; extra == "docs"
|
|
46
47
|
Requires-Dist: furo~=2024.1; extra == "docs"
|
|
47
48
|
Provides-Extra: mypy
|
|
48
|
-
Requires-Dist: mypy==1.
|
|
49
|
+
Requires-Dist: mypy==1.15.0; extra == "mypy"
|
|
50
|
+
Requires-Dist: types-Deprecated~=1.2; extra == "mypy"
|
|
49
51
|
Requires-Dist: types-requests~=2.32.0; extra == "mypy"
|
|
50
52
|
Requires-Dist: types-pyyaml~=6.0; extra == "mypy"
|
|
51
53
|
Provides-Extra: test
|
{python_semantic_release-9.19.0.dist-info → python_semantic_release-9.20.0.dist-info}/RECORD
RENAMED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
semantic_release/__init__.py,sha256=
|
|
2
|
-
semantic_release/__main__.py,sha256=
|
|
1
|
+
semantic_release/__init__.py,sha256=3oeARQKsIRDyheNZV8Tz48-9tVR4TZcIbaK1QTt1V1U,1229
|
|
2
|
+
semantic_release/__main__.py,sha256=pksxr6g1vkKq98Q1lShsxG8tk55IMiSMHzAHKyFU5x0,1704
|
|
3
3
|
semantic_release/const.py,sha256=wInJR7vcOgT1ysm5VuJQ6lD_ZGYnCwRVKz7Uz3htQc4,861
|
|
4
4
|
semantic_release/enums.py,sha256=vrEw1UNRcNrFjPqOFnuUzfeoqKj0ChixVVlyk5fqbng,1744
|
|
5
5
|
semantic_release/errors.py,sha256=PY9rmviSFBZkqawW6VXbUfmF9C_RNOIObcmeGxLefMo,2904
|
|
6
6
|
semantic_release/gitproject.py,sha256=G4XrucN-ZwT1Kj4RMrABcr1vWb0bjKgurEeJjcL-61c,9422
|
|
7
|
-
semantic_release/globals.py,sha256=
|
|
8
|
-
semantic_release/helpers.py,sha256=
|
|
7
|
+
semantic_release/globals.py,sha256=TV0c_Ir1eZIahPvLOBdm_SeE8IRL5X3h5ZiVwPjDHNM,256
|
|
8
|
+
semantic_release/helpers.py,sha256=H-23ZRg_zq2-eXGQJnCPwzAh6txmhCUBogxFIR6Qssc,9979
|
|
9
9
|
semantic_release/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
semantic_release/changelog/__init__.py,sha256=Bg6Xe5Vt32rWoMscW-hd4sUwiZqzWmsg4CD1EhMesMY,262
|
|
11
11
|
semantic_release/changelog/context.py,sha256=WeLQ2BvYEWunIF8XEl6ldQE4IRg0d7r8mYSBi-TqK7o,5988
|
|
@@ -14,17 +14,17 @@ semantic_release/changelog/template.py,sha256=R3V5m-7kv9ES23e_g37fe17tk-ESgvwV0C
|
|
|
14
14
|
semantic_release/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
15
|
semantic_release/cli/changelog_writer.py,sha256=92DntVFFGdzFc5eBUgrRXbSOSV9KVvV769NyhLOTirg,9281
|
|
16
16
|
semantic_release/cli/cli_context.py,sha256=Nop71LdVCJOeSUHgTXunMyK3xAu_QKQC2cRp1QBVkX0,4134
|
|
17
|
-
semantic_release/cli/config.py,sha256
|
|
17
|
+
semantic_release/cli/config.py,sha256=-8xKYBwlMINnWQz9e7ppRtS_-GwUGHxfqelAf4k0FQ8,33516
|
|
18
18
|
semantic_release/cli/const.py,sha256=h7XE2D0D__TAZSrUUtVszwvzpkHTMOiQCf97XQNbEvA,163
|
|
19
19
|
semantic_release/cli/github_actions_output.py,sha256=6oNwjnQBg9XF5QgGc4TgbwX_-W0aj65VwGSL4ALvqVg,2296
|
|
20
20
|
semantic_release/cli/masking_filter.py,sha256=ric34rnXfN5RiAVVaKnhiMJOxTnEl26kI06jQqZPZoQ,3072
|
|
21
|
-
semantic_release/cli/util.py,sha256=
|
|
21
|
+
semantic_release/cli/util.py,sha256=kkn_bJmlbC5hR4GjV1l4uBpL1SfluSkb9WkDUTg9G0o,3720
|
|
22
22
|
semantic_release/cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
23
|
semantic_release/cli/commands/changelog.py,sha256=i3j6jhcfhFo3d7Bgrp1tD01vJkuTZvtaWX7bPteLQbA,5345
|
|
24
24
|
semantic_release/cli/commands/generate_config.py,sha256=2xZOu3NpyhBp0pWr7d8ugKl_kjqQgpSsSMHq5wHTfrE,1699
|
|
25
|
-
semantic_release/cli/commands/main.py,sha256=
|
|
25
|
+
semantic_release/cli/commands/main.py,sha256=P0YlSPLNXxB6CRQFQStslw1fCrURvN5skUd7-Ij5VKw,4271
|
|
26
26
|
semantic_release/cli/commands/publish.py,sha256=y_LalPti_kZeQJzl2CR2pTZUK8DCMvNSTe4NaMC5TJA,2875
|
|
27
|
-
semantic_release/cli/commands/version.py,sha256=
|
|
27
|
+
semantic_release/cli/commands/version.py,sha256=zwxZoi76vP3oWUXiQ86V1rb6meCRe5gouwFGJ-4dD58,25578
|
|
28
28
|
semantic_release/commit_parser/__init__.py,sha256=6euiDgj9bwOx1rP96vUjq090usviXkbo7OVOnRBGfcw,742
|
|
29
29
|
semantic_release/commit_parser/_base.py,sha256=oDifeTmFDpS238cp_DDrGzfidaKeAD5olCB5IM4Q6z8,3058
|
|
30
30
|
semantic_release/commit_parser/angular.py,sha256=UM88ethWT34__kDgsBXXb26d0V8FN1ZFjcU7psuOhgY,19259
|
|
@@ -34,12 +34,12 @@ semantic_release/commit_parser/scipy.py,sha256=0rYZglJ7uib-1Deu4J30wHh7AZS8KfO0e
|
|
|
34
34
|
semantic_release/commit_parser/tag.py,sha256=oGB3lgyp2Eu3Tg3jjxqNzN86N6bokSaFu6f4Ir6IS_k,3546
|
|
35
35
|
semantic_release/commit_parser/token.py,sha256=ECgi7eeSgk3Biq1Y_ChbFJZQLkrUpNvGhIaEOXrNC4M,7904
|
|
36
36
|
semantic_release/commit_parser/util.py,sha256=_ACiopznjwINn4t1zPHl8bxZEc0zOAURTycNU-sXs3M,3959
|
|
37
|
-
semantic_release/data/templates/angular/md/.release_notes.md.j2,sha256=
|
|
37
|
+
semantic_release/data/templates/angular/md/.release_notes.md.j2,sha256=DlMVAJMGqE27TwJ-2kviYaFhd3uWqXiU6Ikl15Ukne8,2512
|
|
38
38
|
semantic_release/data/templates/angular/md/CHANGELOG.md.j2,sha256=FZmrQ-qOIoSoJmAa_NFaRelfmqUpypU2xlDeScdGOf4,729
|
|
39
39
|
semantic_release/data/templates/angular/md/.components/changelog_header.md.j2,sha256=qNxTuSr59CV_yyimVU_RYp5azCnK0l6nJ03Zf0u5Ugg,166
|
|
40
40
|
semantic_release/data/templates/angular/md/.components/changelog_init.md.j2,sha256=MyEQdWUemGXKWDhvpTmubZdozd3iaLECZAI1VRlE7mg,862
|
|
41
41
|
semantic_release/data/templates/angular/md/.components/changelog_update.md.j2,sha256=uVF4wbbjTMvl6Kbsq9xy3YIrj-uhBnHylEfA7S76aHI,2606
|
|
42
|
-
semantic_release/data/templates/angular/md/.components/changes.md.j2,sha256=
|
|
42
|
+
semantic_release/data/templates/angular/md/.components/changes.md.j2,sha256=hGBKJhZwnyNRPpSeNA9k3n46_gCn9fvKPRKfzg-bfXw,5965
|
|
43
43
|
semantic_release/data/templates/angular/md/.components/first_release.md.j2,sha256=-S2aJV3ka4YicLs8UF6BEV-CDnL8iXLcNRRIiENFrYE,421
|
|
44
44
|
semantic_release/data/templates/angular/md/.components/macros.md.j2,sha256=RzBVwKSCb-WmRIMFVQL9U157mAih-PzZ_iRcfyWkG20,8196
|
|
45
45
|
semantic_release/data/templates/angular/md/.components/unreleased_changes.md.j2,sha256=HRLj6cyRfPZXC0s-0Av6s0Gp3jKxWg9AIEtIXBVqJuY,177
|
|
@@ -48,7 +48,7 @@ semantic_release/data/templates/angular/rst/CHANGELOG.rst.j2,sha256=VmkXEMHiPBdZ
|
|
|
48
48
|
semantic_release/data/templates/angular/rst/.components/changelog_header.rst.j2,sha256=c9xN1SEYLFwMvPYXYKt-ZbYPn2-Ss0V7zepEtFFj3Os,200
|
|
49
49
|
semantic_release/data/templates/angular/rst/.components/changelog_init.rst.j2,sha256=XD0l3eTyz1yydLKsmSqBk-u8RnO-RdQ2Q8uWezHMAWw,866
|
|
50
50
|
semantic_release/data/templates/angular/rst/.components/changelog_update.rst.j2,sha256=x23-qk9owJrOQaHx8SgSnIZECITjPf1R2awfv9EOHN0,2604
|
|
51
|
-
semantic_release/data/templates/angular/rst/.components/changes.rst.j2,sha256=
|
|
51
|
+
semantic_release/data/templates/angular/rst/.components/changes.rst.j2,sha256=XIJQOzH7vzPOZszMaAJBa5O4m-syfwraBLpHnX3QmU4,7352
|
|
52
52
|
semantic_release/data/templates/angular/rst/.components/first_release.rst.j2,sha256=huaO-B3BRs7h2LRks2a6-656W2qzURkzLiyuKvYxMTg,462
|
|
53
53
|
semantic_release/data/templates/angular/rst/.components/macros.rst.j2,sha256=WLNUD2H2V-5vrwT7TKelwQ2wclLcZxFs0E2Lk3Ld10U,9096
|
|
54
54
|
semantic_release/data/templates/angular/rst/.components/unreleased_changes.rst.j2,sha256=ARBhc1ZpKwehGKDvOMqukmN59mTJiHzHsS7rOfKYCt8,202
|
|
@@ -64,13 +64,18 @@ semantic_release/hvcs/token_auth.py,sha256=ZjT56-NIPB4OKIt1qwHCu1TavXnrWFIBl9ARl
|
|
|
64
64
|
semantic_release/hvcs/util.py,sha256=guxisysY_IW5tv7aaV-iVPEVJzgbOs375kiRRpSquTI,2879
|
|
65
65
|
semantic_release/version/__init__.py,sha256=CLhtGQry9dLIij5XyRa9ZevxU_1p8tjMTSQ-K_GMpWM,270
|
|
66
66
|
semantic_release/version/algorithm.py,sha256=s5lso4Py-PiVzuPhgeJOz6IDzIdqc6EeoUirmBZ8IXY,16696
|
|
67
|
-
semantic_release/version/declaration.py,sha256=
|
|
67
|
+
semantic_release/version/declaration.py,sha256=aDpgfh0G-35TLXU6x4y3V0nFr8WBR4bw1GDldf0v8_Q,3638
|
|
68
68
|
semantic_release/version/translator.py,sha256=P1noIsVBn8u6zNOFjG0xKYOWapxqf_PHSMvMeLJ9kXg,3050
|
|
69
69
|
semantic_release/version/version.py,sha256=6PCtSbLP88U1daoxnCwHc--YguZo4waGNLqJ5JfeczE,14175
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
python_semantic_release-9.
|
|
76
|
-
python_semantic_release-9.
|
|
70
|
+
semantic_release/version/declarations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
71
|
+
semantic_release/version/declarations/enum.py,sha256=3n5Py9DoFkmItIdsmtQrJgmAhepTv_1EnogzSdXu1Wg,244
|
|
72
|
+
semantic_release/version/declarations/i_version_replacer.py,sha256=oP6BxJuxwI44roI6448tomShv1sMoy9ry8TlhhIQtfc,2416
|
|
73
|
+
semantic_release/version/declarations/pattern.py,sha256=Hv2TBdWdS6d00FYOs-Ja_tfafzEw3OS4Wn5atS1z9Z0,8295
|
|
74
|
+
semantic_release/version/declarations/toml.py,sha256=IKAwG2iu5OWUOytCW0CqKgskU4VifsnD7I6Dkm2EKjo,4945
|
|
75
|
+
python_semantic_release-9.20.0.dist-info/AUTHORS.rst,sha256=XOReVvpymEFUPsS2QPH97jlfJBVrxwS2eu8-jVAe4gk,230
|
|
76
|
+
python_semantic_release-9.20.0.dist-info/LICENSE,sha256=NE85nszX252sdQdu0xgS9qwfYES0k8qS6gW3uO4jRGE,1083
|
|
77
|
+
python_semantic_release-9.20.0.dist-info/METADATA,sha256=hTj0AawcG8vb-Wo-b6NuLtDVA8RcwQzeb3ULXHL3dho,3897
|
|
78
|
+
python_semantic_release-9.20.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
79
|
+
python_semantic_release-9.20.0.dist-info/entry_points.txt,sha256=r2Jql3GTQyugQnvf34l2eXk1O_Qx6llR_xixG1ZWgD0,105
|
|
80
|
+
python_semantic_release-9.20.0.dist-info/top_level.txt,sha256=qYA24nyg3eP-ti5UW7Vuj2aXVmM0wqVHx4mREdRZNAA,17
|
|
81
|
+
python_semantic_release-9.20.0.dist-info/RECORD,,
|
semantic_release/__init__.py
CHANGED
semantic_release/__main__.py
CHANGED
|
@@ -8,6 +8,7 @@ from traceback import format_exception
|
|
|
8
8
|
|
|
9
9
|
from semantic_release import globals
|
|
10
10
|
from semantic_release.cli.commands.main import main as cli_main
|
|
11
|
+
from semantic_release.enums import SemanticReleaseLogLevels
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
def main() -> None:
|
|
@@ -18,7 +19,7 @@ def main() -> None:
|
|
|
18
19
|
print("\n-- User Abort! --", file=sys.stderr)
|
|
19
20
|
sys.exit(127)
|
|
20
21
|
except Exception as err: # noqa: BLE001, graceful error handling across application
|
|
21
|
-
if globals.
|
|
22
|
+
if globals.log_level <= SemanticReleaseLogLevels.DEBUG:
|
|
22
23
|
print(f"{err.__class__.__name__}: {err}\n", file=sys.stderr)
|
|
23
24
|
etype, value, traceback = sys.exc_info()
|
|
24
25
|
print(
|
|
@@ -35,9 +36,12 @@ def main() -> None:
|
|
|
35
36
|
file=sys.stderr,
|
|
36
37
|
)
|
|
37
38
|
|
|
38
|
-
print(
|
|
39
|
+
print(
|
|
40
|
+
str.join("\n", [f"::ERROR:: {line}" for line in str(err).splitlines()]),
|
|
41
|
+
file=sys.stderr,
|
|
42
|
+
)
|
|
39
43
|
|
|
40
|
-
if
|
|
44
|
+
if globals.log_level > SemanticReleaseLogLevels.DEBUG:
|
|
41
45
|
print(
|
|
42
46
|
"Run semantic-release in very verbose mode (-vv) to see the full traceback.",
|
|
43
47
|
file=sys.stderr,
|
|
@@ -116,10 +116,10 @@ def main(
|
|
|
116
116
|
SemanticReleaseLogLevels.SILLY,
|
|
117
117
|
]
|
|
118
118
|
|
|
119
|
-
log_level = log_levels[verbosity]
|
|
119
|
+
globals.log_level = log_levels[verbosity]
|
|
120
120
|
|
|
121
121
|
logging.basicConfig(
|
|
122
|
-
level=log_level,
|
|
122
|
+
level=globals.log_level,
|
|
123
123
|
format=FORMAT,
|
|
124
124
|
datefmt="[%X]",
|
|
125
125
|
handlers=[
|
|
@@ -130,10 +130,7 @@ def main(
|
|
|
130
130
|
)
|
|
131
131
|
|
|
132
132
|
logger = logging.getLogger(__name__)
|
|
133
|
-
logger.debug("logging level set to: %s", logging.getLevelName(log_level))
|
|
134
|
-
|
|
135
|
-
if log_level <= logging.DEBUG:
|
|
136
|
-
globals.debug = True
|
|
133
|
+
logger.debug("logging level set to: %s", logging.getLevelName(globals.log_level))
|
|
137
134
|
|
|
138
135
|
if noop:
|
|
139
136
|
rprint(
|
|
@@ -39,12 +39,12 @@ from semantic_release.version.translator import VersionTranslator
|
|
|
39
39
|
|
|
40
40
|
if TYPE_CHECKING: # pragma: no cover
|
|
41
41
|
from pathlib import Path
|
|
42
|
-
from typing import
|
|
42
|
+
from typing import Mapping, Sequence
|
|
43
43
|
|
|
44
44
|
from git.refs.tag import Tag
|
|
45
45
|
|
|
46
46
|
from semantic_release.cli.cli_context import CliContextObj
|
|
47
|
-
from semantic_release.version.declaration import
|
|
47
|
+
from semantic_release.version.declaration import IVersionReplacer
|
|
48
48
|
from semantic_release.version.version import Version
|
|
49
49
|
|
|
50
50
|
|
|
@@ -135,28 +135,43 @@ def version_from_forced_level(
|
|
|
135
135
|
|
|
136
136
|
def apply_version_to_source_files(
|
|
137
137
|
repo_dir: Path,
|
|
138
|
-
version_declarations:
|
|
138
|
+
version_declarations: Sequence[IVersionReplacer],
|
|
139
139
|
version: Version,
|
|
140
140
|
noop: bool = False,
|
|
141
141
|
) -> list[str]:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
if len(version_declarations) < 1:
|
|
143
|
+
return []
|
|
144
|
+
|
|
145
|
+
if not noop:
|
|
146
|
+
log.debug("Updating version %s in repository files...", version)
|
|
147
|
+
|
|
148
|
+
paths = list(
|
|
149
|
+
map(
|
|
150
|
+
lambda decl, new_version=version, noop=noop: ( # type: ignore[misc]
|
|
151
|
+
decl.update_file_w_version(new_version=new_version, noop=noop)
|
|
152
|
+
),
|
|
153
|
+
version_declarations,
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
repo_filepaths = [
|
|
158
|
+
str(updated_file.relative_to(repo_dir))
|
|
159
|
+
for updated_file in paths
|
|
160
|
+
if updated_file is not None
|
|
145
161
|
]
|
|
146
162
|
|
|
147
163
|
if noop:
|
|
148
164
|
noop_report(
|
|
149
|
-
|
|
150
|
-
|
|
165
|
+
str.join(
|
|
166
|
+
"",
|
|
167
|
+
[
|
|
168
|
+
"would have updated versions in the following paths:",
|
|
169
|
+
*[f"\n {filepath}" for filepath in repo_filepaths],
|
|
170
|
+
],
|
|
171
|
+
)
|
|
151
172
|
)
|
|
152
|
-
return paths
|
|
153
|
-
|
|
154
|
-
log.debug("writing version %s to source paths %s", version, paths)
|
|
155
|
-
for declaration in version_declarations:
|
|
156
|
-
new_content = declaration.replace(new_version=version)
|
|
157
|
-
declaration.path.write_text(new_content)
|
|
158
173
|
|
|
159
|
-
return
|
|
174
|
+
return repo_filepaths
|
|
160
175
|
|
|
161
176
|
|
|
162
177
|
def shell(
|
semantic_release/cli/config.py
CHANGED
|
@@ -46,7 +46,7 @@ from semantic_release.commit_parser import (
|
|
|
46
46
|
ScipyCommitParser,
|
|
47
47
|
TagCommitParser,
|
|
48
48
|
)
|
|
49
|
-
from semantic_release.const import COMMIT_MESSAGE, DEFAULT_COMMIT_AUTHOR
|
|
49
|
+
from semantic_release.const import COMMIT_MESSAGE, DEFAULT_COMMIT_AUTHOR
|
|
50
50
|
from semantic_release.errors import (
|
|
51
51
|
DetachedHeadGitError,
|
|
52
52
|
InvalidConfiguration,
|
|
@@ -55,11 +55,9 @@ from semantic_release.errors import (
|
|
|
55
55
|
ParserLoadError,
|
|
56
56
|
)
|
|
57
57
|
from semantic_release.helpers import dynamic_import
|
|
58
|
-
from semantic_release.version.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
VersionDeclarationABC,
|
|
62
|
-
)
|
|
58
|
+
from semantic_release.version.declarations.i_version_replacer import IVersionReplacer
|
|
59
|
+
from semantic_release.version.declarations.pattern import PatternVersionDeclaration
|
|
60
|
+
from semantic_release.version.declarations.toml import TomlVersionDeclaration
|
|
63
61
|
from semantic_release.version.translator import VersionTranslator
|
|
64
62
|
|
|
65
63
|
log = logging.getLogger(__name__)
|
|
@@ -555,7 +553,7 @@ class RuntimeContext:
|
|
|
555
553
|
commit_author: Actor
|
|
556
554
|
commit_message: str
|
|
557
555
|
changelog_excluded_commit_patterns: Tuple[Pattern[str], ...]
|
|
558
|
-
version_declarations: Tuple[
|
|
556
|
+
version_declarations: Tuple[IVersionReplacer, ...]
|
|
559
557
|
hvcs_client: hvcs.HvcsBase
|
|
560
558
|
changelog_insertion_flag: str
|
|
561
559
|
changelog_mask_initial_release: bool
|
|
@@ -665,6 +663,17 @@ class RuntimeContext:
|
|
|
665
663
|
if raw.commit_parser in _known_commit_parsers
|
|
666
664
|
else dynamic_import(raw.commit_parser)
|
|
667
665
|
)
|
|
666
|
+
except ValueError as err:
|
|
667
|
+
raise ParserLoadError(
|
|
668
|
+
str.join(
|
|
669
|
+
"\n",
|
|
670
|
+
[
|
|
671
|
+
f"Unrecognized commit parser value: {raw.commit_parser!r}.",
|
|
672
|
+
str(err),
|
|
673
|
+
"Unable to load the given parser! Check your configuration!",
|
|
674
|
+
],
|
|
675
|
+
)
|
|
676
|
+
) from err
|
|
668
677
|
except ModuleNotFoundError as err:
|
|
669
678
|
raise ParserLoadError(
|
|
670
679
|
str.join(
|
|
@@ -727,44 +736,41 @@ class RuntimeContext:
|
|
|
727
736
|
|
|
728
737
|
commit_author = Actor(*_commit_author_valid.groups())
|
|
729
738
|
|
|
730
|
-
version_declarations: list[
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
version_declarations.append(vd)
|
|
743
|
-
|
|
744
|
-
for decl in () if raw.version_variables is None else raw.version_variables:
|
|
745
|
-
try:
|
|
746
|
-
path, variable = decl.split(":", maxsplit=1)
|
|
747
|
-
# VersionDeclarationABC handles path existence check
|
|
748
|
-
search_text = str.join(
|
|
749
|
-
"",
|
|
739
|
+
version_declarations: list[IVersionReplacer] = []
|
|
740
|
+
|
|
741
|
+
try:
|
|
742
|
+
version_declarations.extend(
|
|
743
|
+
TomlVersionDeclaration.from_string_definition(definition)
|
|
744
|
+
for definition in iter(raw.version_toml or ())
|
|
745
|
+
)
|
|
746
|
+
except ValueError as err:
|
|
747
|
+
raise InvalidConfiguration(
|
|
748
|
+
str.join(
|
|
749
|
+
"\n",
|
|
750
750
|
[
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
f"""(?x)(?P<quote1>['"])?(?<![\\w.-]){variable}(?P=quote1)?""",
|
|
754
|
-
# Supports walrus, equals sign, or colon as assignment operator ignoring whitespace separation
|
|
755
|
-
r"\s*(:=|[:=])\s*",
|
|
756
|
-
# Supports optional matching quotations around version number of a SEMVER pattern
|
|
757
|
-
f"""(?P<quote2>['"])?(?P<version>{SEMVER_REGEX.pattern})(?P=quote2)?""",
|
|
751
|
+
"Invalid 'version_toml' configuration",
|
|
752
|
+
str(err),
|
|
758
753
|
],
|
|
759
754
|
)
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
755
|
+
) from err
|
|
756
|
+
|
|
757
|
+
try:
|
|
758
|
+
version_declarations.extend(
|
|
759
|
+
PatternVersionDeclaration.from_string_definition(
|
|
760
|
+
definition, raw.tag_format
|
|
761
|
+
)
|
|
762
|
+
for definition in iter(raw.version_variables or ())
|
|
763
|
+
)
|
|
764
|
+
except ValueError as err:
|
|
765
|
+
raise InvalidConfiguration(
|
|
766
|
+
str.join(
|
|
767
|
+
"\n",
|
|
768
|
+
[
|
|
769
|
+
"Invalid 'version_variables' configuration",
|
|
770
|
+
str(err),
|
|
771
|
+
],
|
|
772
|
+
)
|
|
773
|
+
) from err
|
|
768
774
|
|
|
769
775
|
# Provide warnings if the token is missing
|
|
770
776
|
if not raw.remote.token:
|
semantic_release/cli/util.py
CHANGED
|
@@ -28,7 +28,7 @@ def noop_report(msg: str) -> None:
|
|
|
28
28
|
Rich-prints a msg with a standard prefix to report when an action is not being
|
|
29
29
|
taken due to a "noop" flag
|
|
30
30
|
"""
|
|
31
|
-
fullmsg = "[bold cyan]:shield:
|
|
31
|
+
fullmsg = "[bold cyan][:shield: NOP] " + msg
|
|
32
32
|
rprint(fullmsg)
|
|
33
33
|
|
|
34
34
|
|
|
@@ -18,7 +18,7 @@ EXAMPLE:
|
|
|
18
18
|
- Fix bug ([#11](https://domain.com/namespace/repo/pull/11),
|
|
19
19
|
[`abcdef1`](https://domain.com/namespace/repo/commit/HASH))
|
|
20
20
|
|
|
21
|
-
###
|
|
21
|
+
### Breaking Changes
|
|
22
22
|
|
|
23
23
|
- With the change _____, the change causes ___ effect. Ultimately, this section
|
|
24
24
|
it is a more detailed description of the breaking change. With an optional
|
|
@@ -27,7 +27,7 @@ EXAMPLE:
|
|
|
27
27
|
- **scope**: this breaking change has a scope to identify the part of the code that
|
|
28
28
|
this breaking change applies to for better context.
|
|
29
29
|
|
|
30
|
-
###
|
|
30
|
+
### Additional Release Information
|
|
31
31
|
|
|
32
32
|
- This is a release note that provides additional information about the release
|
|
33
33
|
that is not a breaking change or a feature/bug fix.
|
|
@@ -96,7 +96,7 @@ EXAMPLE:
|
|
|
96
96
|
%}{#
|
|
97
97
|
# # PRINT BREAKING CHANGE DESCRIPTIONS (header & descriptions)
|
|
98
98
|
#}{{ "\n"
|
|
99
|
-
}}{{ "###
|
|
99
|
+
}}{{ "### Breaking Changes\n"
|
|
100
100
|
}}{{
|
|
101
101
|
"\n%s\n" | format(brking_descriptions | unique | join("\n\n"))
|
|
102
102
|
}}{#
|
|
@@ -129,7 +129,7 @@ EXAMPLE:
|
|
|
129
129
|
%}{#
|
|
130
130
|
# # PRINT RELEASE NOTICE INFORMATION (header & descriptions)
|
|
131
131
|
#}{{ "\n"
|
|
132
|
-
}}{{ "###
|
|
132
|
+
}}{{ "### Additional Release Information\n"
|
|
133
133
|
}}{{
|
|
134
134
|
"\n%s\n" | format(release_notices | unique | join("\n\n"))
|
|
135
135
|
}}{#
|
|
@@ -14,13 +14,13 @@ _This release is published under the MIT License._
|
|
|
14
14
|
|
|
15
15
|
- Fix bug (#11, [`abcdef1`](https://domain.com/namespace/repo/commit/HASH))
|
|
16
16
|
|
|
17
|
-
###
|
|
17
|
+
### Breaking Changes
|
|
18
18
|
|
|
19
19
|
- With the change _____, the change causes ___ effect. Ultimately, this section it is a more detailed description of the breaking change. With an optional scope prefix like the commit messages above.
|
|
20
20
|
|
|
21
21
|
- **scope**: this breaking change has a scope to identify the part of the code that this breaking change applies to for better context.
|
|
22
22
|
|
|
23
|
-
###
|
|
23
|
+
### Additional Release Information
|
|
24
24
|
|
|
25
25
|
- This is a release note that provides additional information about the release that is not a breaking change or a feature/bug fix.
|
|
26
26
|
|
|
@@ -18,7 +18,7 @@ Bug Fixes
|
|
|
18
18
|
|
|
19
19
|
* Fix bug (`#11`_, `8a7b8ec`_)
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Breaking Changes
|
|
22
22
|
----------------
|
|
23
23
|
|
|
24
24
|
* With the change _____, the change causes ___ effect. Ultimately, this section
|
|
@@ -28,7 +28,7 @@ BREAKING CHANGES
|
|
|
28
28
|
* **scope**: this breaking change has a scope to identify the part of the code that
|
|
29
29
|
this breaking change applies to for better context.
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Additional Release Information
|
|
32
32
|
------------------------------
|
|
33
33
|
|
|
34
34
|
* This is a release note that provides additional information about the release
|
|
@@ -124,7 +124,7 @@ ADDITIONAL RELEASE INFORMATION
|
|
|
124
124
|
%}{#
|
|
125
125
|
# # PRINT BREAKING CHANGE DESCRIPTIONS (header & descriptions)
|
|
126
126
|
#}{{ "\n"
|
|
127
|
-
}}{{ "
|
|
127
|
+
}}{{ "Breaking Changes\n"
|
|
128
128
|
}}{{ '----------------\n'
|
|
129
129
|
}}{{
|
|
130
130
|
"\n%s\n" | format(brking_descriptions | unique | join("\n\n"))
|
|
@@ -158,7 +158,7 @@ ADDITIONAL RELEASE INFORMATION
|
|
|
158
158
|
%}{#
|
|
159
159
|
# # PRINT RELEASE NOTICE INFORMATION (header & descriptions)
|
|
160
160
|
#}{{ "\n"
|
|
161
|
-
}}{{ "
|
|
161
|
+
}}{{ "Additional Release Information\n"
|
|
162
162
|
}}{{ "------------------------------\n"
|
|
163
163
|
}}{{
|
|
164
164
|
"\n%s\n" | format(release_notices | unique | join("\n\n"))
|
semantic_release/globals.py
CHANGED
|
@@ -2,5 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
from semantic_release.enums import SemanticReleaseLogLevels
|
|
6
|
+
|
|
7
|
+
log_level: SemanticReleaseLogLevels = SemanticReleaseLogLevels.WARNING
|
|
8
|
+
"""int: Logging level for semantic-release"""
|
semantic_release/helpers.py
CHANGED
|
@@ -157,6 +157,12 @@ def dynamic_import(import_path: str) -> Any:
|
|
|
157
157
|
Dynamically import an object from a conventionally formatted "module:attribute"
|
|
158
158
|
string
|
|
159
159
|
"""
|
|
160
|
+
if ":" not in import_path:
|
|
161
|
+
raise ValueError(
|
|
162
|
+
f"Invalid import path {import_path!r}, must use 'module:Class' format"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Split the import path into module and attribute
|
|
160
166
|
module_name, attr = import_path.split(":", maxsplit=1)
|
|
161
167
|
|
|
162
168
|
# Check if the module is a file path, if it can be resolved and exists on disk then import as a file
|
|
@@ -1,19 +1,43 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
import re
|
|
3
|
+
# TODO: Remove v10
|
|
5
4
|
from abc import ABC, abstractmethod
|
|
5
|
+
from logging import getLogger
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
from semantic_release.version.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from deprecated.sphinx import deprecated
|
|
10
|
+
|
|
11
|
+
from semantic_release.version.declarations.enum import VersionStampType
|
|
12
|
+
from semantic_release.version.declarations.i_version_replacer import IVersionReplacer
|
|
13
|
+
from semantic_release.version.declarations.pattern import PatternVersionDeclaration
|
|
14
|
+
from semantic_release.version.declarations.toml import TomlVersionDeclaration
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
17
|
+
from semantic_release.version.version import Version
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Globals
|
|
21
|
+
__all__ = [
|
|
22
|
+
"IVersionReplacer",
|
|
23
|
+
"VersionStampType",
|
|
24
|
+
"PatternVersionDeclaration",
|
|
25
|
+
"TomlVersionDeclaration",
|
|
26
|
+
"VersionDeclarationABC",
|
|
27
|
+
]
|
|
28
|
+
log = getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@deprecated(
|
|
32
|
+
version="9.20.0",
|
|
33
|
+
reason=str.join(
|
|
34
|
+
" ",
|
|
35
|
+
[
|
|
36
|
+
"Refactored to composition paradigm using the new IVersionReplacer interface.",
|
|
37
|
+
"This class will be removed in a future release",
|
|
38
|
+
],
|
|
39
|
+
),
|
|
40
|
+
)
|
|
17
41
|
class VersionDeclarationABC(ABC):
|
|
18
42
|
"""
|
|
19
43
|
ABC for classes representing a location in which a version is declared somewhere
|
|
@@ -40,13 +64,8 @@ class VersionDeclarationABC(ABC):
|
|
|
40
64
|
self._content = self.path.read_text()
|
|
41
65
|
return self._content
|
|
42
66
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def _(self, _: Any) -> None:
|
|
46
|
-
raise AttributeError("'content' cannot be set directly")
|
|
47
|
-
|
|
48
|
-
@content.deleter # type: ignore[attr-defined]
|
|
49
|
-
def _(self) -> None:
|
|
67
|
+
@content.deleter
|
|
68
|
+
def content(self) -> None:
|
|
50
69
|
log.debug("resetting instance-stored source file contents")
|
|
51
70
|
self._content = None
|
|
52
71
|
|
|
@@ -86,116 +105,3 @@ class VersionDeclarationABC(ABC):
|
|
|
86
105
|
log.debug("writing content to %r", self.path.resolve())
|
|
87
106
|
self.path.write_text(content)
|
|
88
107
|
self._content = None
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
class TomlVersionDeclaration(VersionDeclarationABC):
|
|
92
|
-
"""VersionDeclarationABC implementation which manages toml-format source files."""
|
|
93
|
-
|
|
94
|
-
def _load(self) -> Dotty:
|
|
95
|
-
"""Load the content of the source file into a Dotty for easier searching"""
|
|
96
|
-
loaded = tomlkit.loads(self.content)
|
|
97
|
-
return Dotty(loaded)
|
|
98
|
-
|
|
99
|
-
def parse(self) -> set[Version]:
|
|
100
|
-
"""Look for the version in the source content"""
|
|
101
|
-
content = self._load()
|
|
102
|
-
maybe_version: str = content.get(self.search_text) # type: ignore[return-value]
|
|
103
|
-
if maybe_version is not None:
|
|
104
|
-
log.debug(
|
|
105
|
-
"Found a key %r that looks like a version (%r)",
|
|
106
|
-
self.search_text,
|
|
107
|
-
maybe_version,
|
|
108
|
-
)
|
|
109
|
-
valid_version = Version.parse(maybe_version)
|
|
110
|
-
return {valid_version} if valid_version else set()
|
|
111
|
-
# Maybe in future raise error if not found?
|
|
112
|
-
return set()
|
|
113
|
-
|
|
114
|
-
def replace(self, new_version: Version) -> str:
|
|
115
|
-
"""
|
|
116
|
-
Replace the version in the source content with `new_version`, and return the
|
|
117
|
-
updated content.
|
|
118
|
-
"""
|
|
119
|
-
content = self._load()
|
|
120
|
-
if self.search_text in content:
|
|
121
|
-
log.info(
|
|
122
|
-
"found %r in source file contents, replacing with %s",
|
|
123
|
-
self.search_text,
|
|
124
|
-
new_version,
|
|
125
|
-
)
|
|
126
|
-
content[self.search_text] = str(new_version)
|
|
127
|
-
|
|
128
|
-
return tomlkit.dumps(cast(Dict[str, Any], content))
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
class PatternVersionDeclaration(VersionDeclarationABC):
|
|
132
|
-
"""
|
|
133
|
-
VersionDeclarationABC implementation representing a version number in a particular
|
|
134
|
-
file. The version number is identified by a regular expression, which should be
|
|
135
|
-
provided in `search_text`.
|
|
136
|
-
"""
|
|
137
|
-
|
|
138
|
-
_VERSION_GROUP_NAME = "version"
|
|
139
|
-
|
|
140
|
-
def __init__(self, path: Path | str, search_text: str) -> None:
|
|
141
|
-
super().__init__(path, search_text)
|
|
142
|
-
self.search_re = re.compile(self.search_text, flags=re.MULTILINE)
|
|
143
|
-
if self._VERSION_GROUP_NAME not in self.search_re.groupindex:
|
|
144
|
-
raise ValueError(
|
|
145
|
-
f"Invalid search text {self.search_text!r}; must use 'version' as a "
|
|
146
|
-
"named group, for example (?P<version>...) . For more info on named "
|
|
147
|
-
"groups see https://docs.python.org/3/library/re.html"
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
# The pattern should be a regular expression with a single group,
|
|
151
|
-
# containing the version to replace.
|
|
152
|
-
def parse(self) -> set[Version]:
|
|
153
|
-
"""
|
|
154
|
-
Return the versions matching this pattern.
|
|
155
|
-
Because a pattern can match in multiple places, this method returns a
|
|
156
|
-
set of matches. Generally, there should only be one element in this
|
|
157
|
-
set (i.e. even if the version is specified in multiple places, it
|
|
158
|
-
should be the same version in each place), but it falls on the caller
|
|
159
|
-
to check for this condition.
|
|
160
|
-
"""
|
|
161
|
-
versions = {
|
|
162
|
-
Version.parse(m.group(self._VERSION_GROUP_NAME))
|
|
163
|
-
for m in self.search_re.finditer(self.content, re.MULTILINE)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
log.debug(
|
|
167
|
-
"Parsing current version: path=%r pattern=%r num_matches=%s",
|
|
168
|
-
self.path.resolve(),
|
|
169
|
-
self.search_text,
|
|
170
|
-
len(versions),
|
|
171
|
-
)
|
|
172
|
-
return versions
|
|
173
|
-
|
|
174
|
-
def replace(self, new_version: Version) -> str:
|
|
175
|
-
"""
|
|
176
|
-
Update the versions.
|
|
177
|
-
This method reads the underlying file, replaces each occurrence of the
|
|
178
|
-
matched pattern, then writes the updated file.
|
|
179
|
-
:param new_version: The new version number as a `Version` instance
|
|
180
|
-
"""
|
|
181
|
-
n = 0
|
|
182
|
-
|
|
183
|
-
def swap_version(m: re.Match[str]) -> str:
|
|
184
|
-
nonlocal n
|
|
185
|
-
n += 1
|
|
186
|
-
s = m.string
|
|
187
|
-
i, j = m.span()
|
|
188
|
-
log.debug("match spans characters %s:%s", i, j)
|
|
189
|
-
ii, jj = m.span(self._VERSION_GROUP_NAME)
|
|
190
|
-
log.debug("version group spans characters %s:%s", ii, jj)
|
|
191
|
-
return s[i:ii] + str(new_version) + s[jj:j]
|
|
192
|
-
|
|
193
|
-
new_content, n_matches = self.search_re.subn(
|
|
194
|
-
swap_version, self.content, re.MULTILINE
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
log.debug(
|
|
198
|
-
"path=%r pattern=%r num_matches=%r", self.path, self.search_text, n_matches
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
return new_content
|
|
File without changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABCMeta, abstractmethod
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from semantic_release.version.version import Version
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class IVersionReplacer(metaclass=ABCMeta):
|
|
13
|
+
"""
|
|
14
|
+
Interface for subclasses that replace a version string in a source file.
|
|
15
|
+
|
|
16
|
+
Methods generally have a base implementation are implemented here but
|
|
17
|
+
likely just provide a not-supported message but return gracefully
|
|
18
|
+
|
|
19
|
+
This class cannot be instantiated directly but must be inherited from
|
|
20
|
+
and implement the designated abstract methods.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def __subclasshook__(cls, subclass: type) -> bool:
|
|
25
|
+
# Validate that the subclass implements all of the abstract methods.
|
|
26
|
+
# This supports isinstance and issubclass checks.
|
|
27
|
+
return bool(
|
|
28
|
+
cls is IVersionReplacer
|
|
29
|
+
and all(
|
|
30
|
+
bool(hasattr(subclass, method) and callable(getattr(subclass, method)))
|
|
31
|
+
for method in IVersionReplacer.__abstractmethods__
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def parse(self) -> set[Version]:
|
|
37
|
+
"""
|
|
38
|
+
Return a set of the versions which can be parsed from the file.
|
|
39
|
+
Because a source can match in multiple places, this method returns a
|
|
40
|
+
set of matches. Generally, there should only be one element in this
|
|
41
|
+
set (i.e. even if the version is specified in multiple places, it
|
|
42
|
+
should be the same version in each place), but enforcing that condition
|
|
43
|
+
is not mandatory or expected.
|
|
44
|
+
"""
|
|
45
|
+
raise NotImplementedError # pragma: no cover
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def replace(self, new_version: Version) -> str:
|
|
49
|
+
"""
|
|
50
|
+
Replace the version in the source content with `new_version`, and return
|
|
51
|
+
the updated content.
|
|
52
|
+
|
|
53
|
+
:param new_version: The new version number as a `Version` instance
|
|
54
|
+
"""
|
|
55
|
+
raise NotImplementedError # pragma: no cover
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def update_file_w_version(
|
|
59
|
+
self, new_version: Version, noop: bool = False
|
|
60
|
+
) -> Path | None:
|
|
61
|
+
"""
|
|
62
|
+
This method reads the underlying file, replaces each occurrence of the
|
|
63
|
+
matched pattern, then writes the updated file.
|
|
64
|
+
|
|
65
|
+
:param new_version: The new version number as a `Version` instance
|
|
66
|
+
"""
|
|
67
|
+
raise NotImplementedError # pragma: no cover
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from logging import getLogger
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from re import (
|
|
6
|
+
MULTILINE,
|
|
7
|
+
compile as regexp,
|
|
8
|
+
error as RegExpError, # noqa: N812
|
|
9
|
+
escape as regex_escape,
|
|
10
|
+
)
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
from deprecated.sphinx import deprecated
|
|
14
|
+
|
|
15
|
+
from semantic_release.cli.util import noop_report
|
|
16
|
+
from semantic_release.const import SEMVER_REGEX
|
|
17
|
+
from semantic_release.version.declarations.enum import VersionStampType
|
|
18
|
+
from semantic_release.version.declarations.i_version_replacer import IVersionReplacer
|
|
19
|
+
from semantic_release.version.version import Version
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
22
|
+
from re import Match
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
log = getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class VersionSwapper:
|
|
29
|
+
"""Callable to replace a version number in a string with a new version number."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, new_version_str: str, group_match_name: str) -> None:
|
|
32
|
+
self.version_str = new_version_str
|
|
33
|
+
self.group_match_name = group_match_name
|
|
34
|
+
|
|
35
|
+
def __call__(self, match: Match[str]) -> str:
|
|
36
|
+
i, j = match.span()
|
|
37
|
+
ii, jj = match.span(self.group_match_name)
|
|
38
|
+
return f"{match.string[i:ii]}{self.version_str}{match.string[jj:j]}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PatternVersionDeclaration(IVersionReplacer):
|
|
42
|
+
"""
|
|
43
|
+
VersionDeclarationABC implementation representing a version number in a particular
|
|
44
|
+
file. The version number is identified by a regular expression, which should be
|
|
45
|
+
provided in `search_text`.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
_VERSION_GROUP_NAME = "version"
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self, path: Path | str, search_text: str, stamp_format: VersionStampType
|
|
52
|
+
) -> None:
|
|
53
|
+
self._content: str | None = None
|
|
54
|
+
self._path = Path(path).resolve()
|
|
55
|
+
self._stamp_format = stamp_format
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
self._search_pattern = regexp(search_text, flags=MULTILINE)
|
|
59
|
+
except RegExpError as err:
|
|
60
|
+
raise ValueError(
|
|
61
|
+
f"Invalid regular expression for search text: {search_text!r}"
|
|
62
|
+
) from err
|
|
63
|
+
|
|
64
|
+
if self._VERSION_GROUP_NAME not in self._search_pattern.groupindex:
|
|
65
|
+
raise ValueError(
|
|
66
|
+
str.join(
|
|
67
|
+
" ",
|
|
68
|
+
[
|
|
69
|
+
f"Invalid search text {search_text!r}; must use",
|
|
70
|
+
f"'{self._VERSION_GROUP_NAME}' as a named group, for example",
|
|
71
|
+
f"(?P<{self._VERSION_GROUP_NAME}>...) . For more info on named",
|
|
72
|
+
"groups see https://docs.python.org/3/library/re.html",
|
|
73
|
+
],
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def content(self) -> str:
|
|
79
|
+
"""A cached property that stores the content of the configured source file."""
|
|
80
|
+
if self._content is None:
|
|
81
|
+
log.debug("No content stored, reading from source file %s", self._path)
|
|
82
|
+
|
|
83
|
+
if not self._path.exists():
|
|
84
|
+
raise FileNotFoundError(f"path {self._path!r} does not exist")
|
|
85
|
+
|
|
86
|
+
self._content = self._path.read_text()
|
|
87
|
+
|
|
88
|
+
return self._content
|
|
89
|
+
|
|
90
|
+
@content.deleter
|
|
91
|
+
def content(self) -> None:
|
|
92
|
+
self._content = None
|
|
93
|
+
|
|
94
|
+
@deprecated(
|
|
95
|
+
version="9.20.0",
|
|
96
|
+
reason="Function is unused and will be removed in a future release",
|
|
97
|
+
)
|
|
98
|
+
def parse(self) -> set[Version]: # pragma: no cover
|
|
99
|
+
"""
|
|
100
|
+
Return the versions matching this pattern.
|
|
101
|
+
Because a pattern can match in multiple places, this method returns a
|
|
102
|
+
set of matches. Generally, there should only be one element in this
|
|
103
|
+
set (i.e. even if the version is specified in multiple places, it
|
|
104
|
+
should be the same version in each place), but it falls on the caller
|
|
105
|
+
to check for this condition.
|
|
106
|
+
"""
|
|
107
|
+
versions = {
|
|
108
|
+
Version.parse(m.group(self._VERSION_GROUP_NAME))
|
|
109
|
+
for m in self._search_pattern.finditer(self.content)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
log.debug(
|
|
113
|
+
"Parsing current version: path=%r pattern=%r num_matches=%s",
|
|
114
|
+
self._path.resolve(),
|
|
115
|
+
self._search_pattern,
|
|
116
|
+
len(versions),
|
|
117
|
+
)
|
|
118
|
+
return versions
|
|
119
|
+
|
|
120
|
+
def replace(self, new_version: Version) -> str:
|
|
121
|
+
"""
|
|
122
|
+
Replace the version in the source content with `new_version`, and return
|
|
123
|
+
the updated content.
|
|
124
|
+
|
|
125
|
+
:param new_version: The new version number as a `Version` instance
|
|
126
|
+
"""
|
|
127
|
+
new_content, n_matches = self._search_pattern.subn(
|
|
128
|
+
VersionSwapper(
|
|
129
|
+
new_version_str=(
|
|
130
|
+
new_version.as_tag()
|
|
131
|
+
if self._stamp_format == VersionStampType.TAG_FORMAT
|
|
132
|
+
else str(new_version)
|
|
133
|
+
),
|
|
134
|
+
group_match_name=self._VERSION_GROUP_NAME,
|
|
135
|
+
),
|
|
136
|
+
self.content,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
log.debug(
|
|
140
|
+
"path=%r pattern=%r num_matches=%r",
|
|
141
|
+
self._path,
|
|
142
|
+
self._search_pattern,
|
|
143
|
+
n_matches,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return new_content
|
|
147
|
+
|
|
148
|
+
def update_file_w_version(
|
|
149
|
+
self, new_version: Version, noop: bool = False
|
|
150
|
+
) -> Path | None:
|
|
151
|
+
if noop:
|
|
152
|
+
if not self._path.exists():
|
|
153
|
+
noop_report(
|
|
154
|
+
f"FILE NOT FOUND: cannot stamp version in non-existent file {self._path}",
|
|
155
|
+
)
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
if len(self._search_pattern.findall(self.content)) < 1:
|
|
159
|
+
noop_report(
|
|
160
|
+
f"VERSION PATTERN NOT FOUND: no version to stamp in file {self._path}",
|
|
161
|
+
)
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
return self._path
|
|
165
|
+
|
|
166
|
+
new_content = self.replace(new_version)
|
|
167
|
+
if new_content == self.content:
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
self._path.write_text(new_content)
|
|
171
|
+
del self.content
|
|
172
|
+
|
|
173
|
+
return self._path
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def from_string_definition(
|
|
177
|
+
cls, replacement_def: str, tag_format: str
|
|
178
|
+
) -> PatternVersionDeclaration:
|
|
179
|
+
"""
|
|
180
|
+
create an instance of self from a string representing one item
|
|
181
|
+
of the "version_variables" list in the configuration
|
|
182
|
+
"""
|
|
183
|
+
parts = replacement_def.split(":", maxsplit=2)
|
|
184
|
+
|
|
185
|
+
if len(parts) <= 1:
|
|
186
|
+
raise ValueError(
|
|
187
|
+
f"Invalid replacement definition {replacement_def!r}, missing ':'"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if len(parts) == 2:
|
|
191
|
+
# apply default version_type of "number_format" (ie. "1.2.3")
|
|
192
|
+
parts = [*parts, VersionStampType.NUMBER_FORMAT.value]
|
|
193
|
+
|
|
194
|
+
path, variable, version_type = parts
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
stamp_type = VersionStampType(version_type)
|
|
198
|
+
except ValueError as err:
|
|
199
|
+
raise ValueError(
|
|
200
|
+
str.join(
|
|
201
|
+
" ",
|
|
202
|
+
[
|
|
203
|
+
"Invalid stamp type, must be one of:",
|
|
204
|
+
str.join(", ", [e.value for e in VersionStampType]),
|
|
205
|
+
],
|
|
206
|
+
)
|
|
207
|
+
) from err
|
|
208
|
+
|
|
209
|
+
# DEFAULT: naked (no v-prefixed) semver version
|
|
210
|
+
value_replace_pattern_str = (
|
|
211
|
+
f"(?P<{cls._VERSION_GROUP_NAME}>{SEMVER_REGEX.pattern})"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if version_type == VersionStampType.TAG_FORMAT.value:
|
|
215
|
+
tag_parts = tag_format.strip().split(r"{version}", maxsplit=1)
|
|
216
|
+
value_replace_pattern_str = str.join(
|
|
217
|
+
"",
|
|
218
|
+
[
|
|
219
|
+
f"(?P<{cls._VERSION_GROUP_NAME}>",
|
|
220
|
+
regex_escape(tag_parts[0]),
|
|
221
|
+
SEMVER_REGEX.pattern,
|
|
222
|
+
(regex_escape(tag_parts[1]) if len(tag_parts) > 1 else ""),
|
|
223
|
+
")",
|
|
224
|
+
],
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
search_text = str.join(
|
|
228
|
+
"",
|
|
229
|
+
[
|
|
230
|
+
# Supports optional matching quotations around variable name
|
|
231
|
+
# Negative lookbehind to ensure we don't match part of a variable name
|
|
232
|
+
f"""(?x)(?P<quote1>['"])?(?<![\\w.-]){regex_escape(variable)}(?P=quote1)?""",
|
|
233
|
+
# Supports walrus, equals sign, colon, or @ as assignment operator
|
|
234
|
+
# ignoring whitespace separation
|
|
235
|
+
r"\s*(:=|[:=@])\s*",
|
|
236
|
+
# Supports optional matching quotations around a version pattern (tag or raw format)
|
|
237
|
+
f"""(?P<quote2>['"])?{value_replace_pattern_str}(?P=quote2)?""",
|
|
238
|
+
],
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return cls(path, search_text, stamp_type)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from logging import getLogger
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, cast
|
|
6
|
+
|
|
7
|
+
import tomlkit
|
|
8
|
+
from deprecated.sphinx import deprecated
|
|
9
|
+
from dotty_dict import Dotty
|
|
10
|
+
|
|
11
|
+
from semantic_release.cli.util import noop_report
|
|
12
|
+
from semantic_release.version.declarations.enum import VersionStampType
|
|
13
|
+
from semantic_release.version.declarations.i_version_replacer import IVersionReplacer
|
|
14
|
+
from semantic_release.version.version import Version
|
|
15
|
+
|
|
16
|
+
# globals
|
|
17
|
+
log = getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TomlVersionDeclaration(IVersionReplacer):
|
|
21
|
+
def __init__(
|
|
22
|
+
self, path: Path | str, search_text: str, stamp_format: VersionStampType
|
|
23
|
+
) -> None:
|
|
24
|
+
self._content: str | None = None
|
|
25
|
+
self._path = Path(path).resolve()
|
|
26
|
+
self._stamp_format = stamp_format
|
|
27
|
+
self._search_text = search_text
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def content(self) -> str:
|
|
31
|
+
"""A cached property that stores the content of the configured source file."""
|
|
32
|
+
if self._content is None:
|
|
33
|
+
log.debug("No content stored, reading from source file %s", self._path)
|
|
34
|
+
|
|
35
|
+
if not self._path.exists():
|
|
36
|
+
raise FileNotFoundError(f"path {self._path!r} does not exist")
|
|
37
|
+
|
|
38
|
+
self._content = self._path.read_text()
|
|
39
|
+
|
|
40
|
+
return self._content
|
|
41
|
+
|
|
42
|
+
@content.deleter
|
|
43
|
+
def content(self) -> None:
|
|
44
|
+
self._content = None
|
|
45
|
+
|
|
46
|
+
@deprecated(
|
|
47
|
+
version="9.20.0",
|
|
48
|
+
reason="Function is unused and will be removed in a future release",
|
|
49
|
+
)
|
|
50
|
+
def parse(self) -> set[Version]: # pragma: no cover
|
|
51
|
+
"""Look for the version in the source content"""
|
|
52
|
+
content = self._load()
|
|
53
|
+
maybe_version: str = content.get(self._search_text) # type: ignore[return-value]
|
|
54
|
+
if maybe_version is not None:
|
|
55
|
+
log.debug(
|
|
56
|
+
"Found a key %r that looks like a version (%r)",
|
|
57
|
+
self._search_text,
|
|
58
|
+
maybe_version,
|
|
59
|
+
)
|
|
60
|
+
valid_version = Version.parse(maybe_version)
|
|
61
|
+
return {valid_version} if valid_version else set()
|
|
62
|
+
# Maybe in future raise error if not found?
|
|
63
|
+
return set()
|
|
64
|
+
|
|
65
|
+
def replace(self, new_version: Version) -> str:
|
|
66
|
+
"""
|
|
67
|
+
Replace the version in the source content with `new_version`, and return the
|
|
68
|
+
updated content.
|
|
69
|
+
"""
|
|
70
|
+
content = self._load()
|
|
71
|
+
if self._search_text in content:
|
|
72
|
+
log.info(
|
|
73
|
+
"found %r in source file contents, replacing with %s",
|
|
74
|
+
self._search_text,
|
|
75
|
+
new_version,
|
|
76
|
+
)
|
|
77
|
+
content[self._search_text] = (
|
|
78
|
+
new_version.as_tag()
|
|
79
|
+
if self._stamp_format == VersionStampType.TAG_FORMAT
|
|
80
|
+
else str(new_version)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return tomlkit.dumps(cast(Dict[str, Any], content))
|
|
84
|
+
|
|
85
|
+
def _load(self) -> Dotty:
|
|
86
|
+
"""Load the content of the source file into a Dotty for easier searching"""
|
|
87
|
+
return Dotty(tomlkit.loads(self.content))
|
|
88
|
+
|
|
89
|
+
def update_file_w_version(
|
|
90
|
+
self, new_version: Version, noop: bool = False
|
|
91
|
+
) -> Path | None:
|
|
92
|
+
if noop:
|
|
93
|
+
if not self._path.exists():
|
|
94
|
+
noop_report(
|
|
95
|
+
f"FILE NOT FOUND: cannot stamp version in non-existent file {self._path!r}",
|
|
96
|
+
)
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
if self._search_text not in self._load():
|
|
100
|
+
noop_report(
|
|
101
|
+
f"VERSION PATTERN NOT FOUND: no version to stamp in file {self._path!r}",
|
|
102
|
+
)
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
return self._path
|
|
106
|
+
|
|
107
|
+
new_content = self.replace(new_version)
|
|
108
|
+
if new_content == self.content:
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
self._path.write_text(new_content)
|
|
112
|
+
del self.content
|
|
113
|
+
|
|
114
|
+
return self._path
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def from_string_definition(cls, replacement_def: str) -> TomlVersionDeclaration:
|
|
118
|
+
"""
|
|
119
|
+
create an instance of self from a string representing one item
|
|
120
|
+
of the "version_toml" list in the configuration
|
|
121
|
+
"""
|
|
122
|
+
parts = replacement_def.split(":", maxsplit=2)
|
|
123
|
+
|
|
124
|
+
if len(parts) <= 1:
|
|
125
|
+
raise ValueError(
|
|
126
|
+
f"Invalid TOML replacement definition {replacement_def!r}, missing ':'"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if len(parts) == 2:
|
|
130
|
+
# apply default version_type of "number_format" (ie. "1.2.3")
|
|
131
|
+
parts = [*parts, VersionStampType.NUMBER_FORMAT.value]
|
|
132
|
+
|
|
133
|
+
path, search_text, version_type = parts
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
stamp_type = VersionStampType(version_type)
|
|
137
|
+
except ValueError as err:
|
|
138
|
+
raise ValueError(
|
|
139
|
+
str.join(
|
|
140
|
+
" ",
|
|
141
|
+
[
|
|
142
|
+
"Invalid stamp type, must be one of:",
|
|
143
|
+
str.join(", ", [e.value for e in VersionStampType]),
|
|
144
|
+
],
|
|
145
|
+
)
|
|
146
|
+
) from err
|
|
147
|
+
|
|
148
|
+
return cls(path, search_text, stamp_type)
|
{python_semantic_release-9.19.0.dist-info → python_semantic_release-9.20.0.dist-info}/AUTHORS.rst
RENAMED
|
File without changes
|
{python_semantic_release-9.19.0.dist-info → python_semantic_release-9.20.0.dist-info}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_semantic_release-9.19.0.dist-info → python_semantic_release-9.20.0.dist-info}/top_level.txt
RENAMED
|
File without changes
|