python-semantic-release 9.12.2__py3-none-any.whl → 9.14.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.
Files changed (31) hide show
  1. {python_semantic_release-9.12.2.dist-info → python_semantic_release-9.14.0.dist-info}/METADATA +2 -2
  2. {python_semantic_release-9.12.2.dist-info → python_semantic_release-9.14.0.dist-info}/RECORD +31 -28
  3. {python_semantic_release-9.12.2.dist-info → python_semantic_release-9.14.0.dist-info}/WHEEL +1 -1
  4. semantic_release/__init__.py +1 -1
  5. semantic_release/changelog/context.py +9 -0
  6. semantic_release/cli/changelog_writer.py +5 -4
  7. semantic_release/cli/commands/changelog.py +1 -0
  8. semantic_release/cli/commands/version.py +1 -0
  9. semantic_release/cli/config.py +4 -0
  10. semantic_release/commit_parser/angular.py +60 -33
  11. semantic_release/commit_parser/emoji.py +86 -39
  12. semantic_release/commit_parser/scipy.py +30 -81
  13. semantic_release/commit_parser/token.py +30 -0
  14. semantic_release/commit_parser/util.py +35 -4
  15. semantic_release/data/templates/angular/md/.components/changelog_init.md.j2 +14 -7
  16. semantic_release/data/templates/angular/md/.components/changelog_update.md.j2 +16 -7
  17. semantic_release/data/templates/angular/md/.components/changes.md.j2 +40 -12
  18. semantic_release/data/templates/angular/md/.components/first_release.md.j2 +11 -0
  19. semantic_release/data/templates/angular/md/.components/macros.md.j2 +69 -0
  20. semantic_release/data/templates/angular/md/.release_notes.md.j2 +13 -1
  21. semantic_release/data/templates/angular/md/CHANGELOG.md.j2 +1 -4
  22. semantic_release/data/templates/angular/rst/.components/changelog_init.rst.j2 +19 -12
  23. semantic_release/data/templates/angular/rst/.components/changelog_update.rst.j2 +16 -7
  24. semantic_release/data/templates/angular/rst/.components/changes.rst.j2 +59 -19
  25. semantic_release/data/templates/angular/rst/.components/first_release.rst.j2 +20 -0
  26. semantic_release/data/templates/angular/rst/.components/macros.rst.j2 +83 -2
  27. semantic_release/data/templates/angular/rst/CHANGELOG.rst.j2 +1 -4
  28. {python_semantic_release-9.12.2.dist-info → python_semantic_release-9.14.0.dist-info}/AUTHORS.rst +0 -0
  29. {python_semantic_release-9.12.2.dist-info → python_semantic_release-9.14.0.dist-info}/LICENSE +0 -0
  30. {python_semantic_release-9.12.2.dist-info → python_semantic_release-9.14.0.dist-info}/entry_points.txt +0 -0
  31. {python_semantic_release-9.12.2.dist-info → python_semantic_release-9.14.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-semantic-release
3
- Version: 9.12.2
3
+ Version: 9.14.0
4
4
  Summary: Automatic Semantic Versioning for Python projects
5
5
  Author-email: Rolf Erik Lekang <me@rolflekang.com>
6
6
  License: MIT
@@ -44,7 +44,7 @@ Requires-Dist: sphinxcontrib-apidoc==0.5.0; extra == "docs"
44
44
  Requires-Dist: sphinx-autobuild==2024.2.4; extra == "docs"
45
45
  Requires-Dist: furo~=2024.1; extra == "docs"
46
46
  Provides-Extra: mypy
47
- Requires-Dist: mypy==1.11.2; extra == "mypy"
47
+ Requires-Dist: mypy==1.13.0; extra == "mypy"
48
48
  Requires-Dist: types-requests~=2.32.0; extra == "mypy"
49
49
  Provides-Extra: test
50
50
  Requires-Dist: coverage[toml]~=7.0; extra == "test"
@@ -1,4 +1,4 @@
1
- semantic_release/__init__.py,sha256=DhNOjxINWOV6dyjCQmaizz8XX9v6ReMVyZLlzmMRtiw,1229
1
+ semantic_release/__init__.py,sha256=kHQQzNnUYSMycf0e29x0t_WKKyKzPNRw9RkN73SVOpw,1229
2
2
  semantic_release/__main__.py,sha256=kuotDU7aFKrCBeAJUPWrbIxgJWAmrXUMnztCqWMDMPY,1292
3
3
  semantic_release/const.py,sha256=Z1o2QNh60wSLeF-_1TemMBjU3ZXbV0XghnUFsbTVfOs,831
4
4
  semantic_release/enums.py,sha256=D5B_reQGGKQQT22HO5PUtvn2Bok3fkht6TfJtXkmAUg,1020
@@ -7,45 +7,48 @@ semantic_release/gitproject.py,sha256=izWc4NLdUzAwxGG_fJeqqHW9ivSrPcWBzSaOijQx4f
7
7
  semantic_release/globals.py,sha256=imI9WKGa6MS2pTRAZiWZ2qIJup2eWnBz3OZmIj2YIHM,158
8
8
  semantic_release/helpers.py,sha256=d1jOX0SNyqPc_3wr14xR25FfpqhMd4Ev7MNBOWlScc0,5581
9
9
  semantic_release/changelog/__init__.py,sha256=Bg6Xe5Vt32rWoMscW-hd4sUwiZqzWmsg4CD1EhMesMY,262
10
- semantic_release/changelog/context.py,sha256=y37y3M7UmVKwhu9UatNtlo5zNfTOJI8gGOTIzjYXVa8,5023
10
+ semantic_release/changelog/context.py,sha256=AQY9ZH1c5LjkyMcZyZG8Kjeyg0qvG_ueOWifgshtLWY,5354
11
11
  semantic_release/changelog/release_history.py,sha256=8RU6A4FMgWwwjndOO4WKjbSzPWgVsYbdcZol1xUPnfo,7815
12
12
  semantic_release/changelog/template.py,sha256=J1YJDI_SnXtBbTxoahN0NaVVHN7grOFghLwE9wDjxik,5685
13
13
  semantic_release/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- semantic_release/cli/changelog_writer.py,sha256=qucHQylViw61C8Rn3Lafbeyo80cjs2hxaCztd_EgIJ4,8859
14
+ semantic_release/cli/changelog_writer.py,sha256=4XWAiH2qv3WBLxPm0D0S31-0XDm2QwSwyopiUmjUdJk,9029
15
15
  semantic_release/cli/cli_context.py,sha256=23eyV6KWIpUckUSWILEd7t9dixp-QCY5-kGZnyucEYY,4114
16
- semantic_release/cli/config.py,sha256=0btSbu5KKFZ3HFqR_-WDyToWXljfv1EOuBjBqwwFJcc,30654
16
+ semantic_release/cli/config.py,sha256=XpFT5asSe65ye3Q63h9HaDJ4lTzWKyUBXyn_tZSdamk,30884
17
17
  semantic_release/cli/const.py,sha256=h7XE2D0D__TAZSrUUtVszwvzpkHTMOiQCf97XQNbEvA,163
18
18
  semantic_release/cli/github_actions_output.py,sha256=VYIOb5x5h8eEJdSlXM_mkhT9xXtYi-RgxvnoM7iUn8s,2288
19
19
  semantic_release/cli/masking_filter.py,sha256=DxqjiJyABlzwwwZ1r8JGQpb6QrF00StJFm0-2-s5Fv0,3071
20
20
  semantic_release/cli/util.py,sha256=FyXaBkeL7nXKjy3X9rQLEwvn7p46xPekp2V8Z-5MVrk,3755
21
21
  semantic_release/cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- semantic_release/cli/commands/changelog.py,sha256=kVHcGdfud74-M6hjWf1PS6l95gD4yUlu3CZGBkCt7aY,3765
22
+ semantic_release/cli/commands/changelog.py,sha256=6SLdz8zHuLDXnB8JWz8NSko9c5YNLMmRoBw_9DrkRok,3834
23
23
  semantic_release/cli/commands/generate_config.py,sha256=2xZOu3NpyhBp0pWr7d8ugKl_kjqQgpSsSMHq5wHTfrE,1699
24
24
  semantic_release/cli/commands/main.py,sha256=wHG51tbaAHKra21nRe3-FAfQT81-e5PGhv0KnerS15c,4100
25
25
  semantic_release/cli/commands/publish.py,sha256=SZQlIewvqyIC14dkIIVVFetE0tPsKbO1cUyxnZsicrw,2845
26
- semantic_release/cli/commands/version.py,sha256=PKNoP_b8puzcScKkQEbeB3DviJv49cQ-vjq6v25nG9Q,23931
26
+ semantic_release/cli/commands/version.py,sha256=z4UNNmQ0Fo2e-SKPzmF1CF517X5mk8oaiGttYJAiVzA,24000
27
27
  semantic_release/commit_parser/__init__.py,sha256=cv5HFBdw7OJd4Laj4Ex8ZZ5Tml8GwXgQcXW6Pasr2Ao,615
28
28
  semantic_release/commit_parser/_base.py,sha256=t-Z9ALgAe7aZpYXz1mk3Fe-uAvipgKdNrq4Okg_WW9c,3026
29
- semantic_release/commit_parser/angular.py,sha256=aq8UxCqwernLX2lEGp3Y9OMYMVt_to9rJLEjcscWF34,5911
30
- semantic_release/commit_parser/emoji.py,sha256=foN7wVDW1Lv7A_cR4mq4X2aas17IEwTgQ8xjUXduN8k,3830
31
- semantic_release/commit_parser/scipy.py,sha256=p7Ox0GJGtJ3jKroDDW55Iu1Ma2089VegESRcagwDwJw,6028
29
+ semantic_release/commit_parser/angular.py,sha256=a1AmgstkclzJnRWqSsF4cuO8awuOJe7VAG__1W7WF7U,7276
30
+ semantic_release/commit_parser/emoji.py,sha256=4OQ5yyHBNC-pA7d6KP_pMjKxutwVaBvO_ydqCT1KtJA,5849
31
+ semantic_release/commit_parser/scipy.py,sha256=u_5OWOeM2V7_jZzYfEicKqx0GJBsAqIOfzRX_4cmOqk,3973
32
32
  semantic_release/commit_parser/tag.py,sha256=4uwIKBqUM2SE6UTGIw-a7B6Jg1OONXmGwXsTyL3yZBA,3490
33
- semantic_release/commit_parser/token.py,sha256=BB4ZCyt753CCaBFF95cQ4sFm6Au96wpO0YyTAWdcOvE,1609
34
- semantic_release/commit_parser/util.py,sha256=vLcVDErZrExM55jMffos0hyMbNVQoJ-PeeVDG1Ej51I,730
35
- semantic_release/data/templates/angular/md/.release_notes.md.j2,sha256=bJeaCDzbva8ntefPPeAS3YD0GXkZv-j0_fIbVmnAKbQ,52
36
- semantic_release/data/templates/angular/md/CHANGELOG.md.j2,sha256=tFFPsT-EsZHAuSS6xVEAN_Iofu6c-psFoqdyFn_NW2k,838
33
+ semantic_release/commit_parser/token.py,sha256=-C1ZKG7pdbcGT2nc3-L2APLUUDGTXkbDeNi5mvvUwjk,2621
34
+ semantic_release/commit_parser/util.py,sha256=b9lud_FBjsmimLrvILf_NXvZ2wg8JPDmA364hcfM6is,1710
35
+ semantic_release/data/templates/angular/md/.release_notes.md.j2,sha256=1wGyJGiML0FeSsHeywj4gv5qhAZHvs0wvea7eCIGiwM,558
36
+ semantic_release/data/templates/angular/md/CHANGELOG.md.j2,sha256=FZmrQ-qOIoSoJmAa_NFaRelfmqUpypU2xlDeScdGOf4,729
37
37
  semantic_release/data/templates/angular/md/.components/changelog_header.md.j2,sha256=qNxTuSr59CV_yyimVU_RYp5azCnK0l6nJ03Zf0u5Ugg,166
38
- semantic_release/data/templates/angular/md/.components/changelog_init.md.j2,sha256=d3tS_nCe_ttNQRGl8Jan4H42iJ8hKu03HrfdEdGAh5M,599
39
- semantic_release/data/templates/angular/md/.components/changelog_update.md.j2,sha256=JhXF-vYlqd4qNI8rO2Hte3Jbemo17wdeZ7w3G2nwjus,2384
40
- semantic_release/data/templates/angular/md/.components/changes.md.j2,sha256=oC2amekjxsNjgj0BkVzs6ANswxntOi3ItlQd9VtbVm8,339
38
+ semantic_release/data/templates/angular/md/.components/changelog_init.md.j2,sha256=MyEQdWUemGXKWDhvpTmubZdozd3iaLECZAI1VRlE7mg,862
39
+ semantic_release/data/templates/angular/md/.components/changelog_update.md.j2,sha256=uVF4wbbjTMvl6Kbsq9xy3YIrj-uhBnHylEfA7S76aHI,2606
40
+ semantic_release/data/templates/angular/md/.components/changes.md.j2,sha256=PAt_vMMoP5uMbDQZKemx26m13TNGGAOmBVD1sUU-KNo,1701
41
+ semantic_release/data/templates/angular/md/.components/first_release.md.j2,sha256=jMUZiLwhMqAOjdYOCphJxJr8C411cKRzhsabyebj_AY,162
42
+ semantic_release/data/templates/angular/md/.components/macros.md.j2,sha256=rvMLeThRRmCVyF_utN0j6wrWL5PVb4TYraunOWWcdu4,2275
41
43
  semantic_release/data/templates/angular/md/.components/unreleased_changes.md.j2,sha256=HRLj6cyRfPZXC0s-0Av6s0Gp3jKxWg9AIEtIXBVqJuY,177
42
44
  semantic_release/data/templates/angular/md/.components/versioned_changes.md.j2,sha256=2Hky2mBrC4jltz3mvaiPDD0KQP0ELe5Ag75HgaLpaIE,257
43
- semantic_release/data/templates/angular/rst/CHANGELOG.rst.j2,sha256=OQPAKgqTnYGXFyDTm8hwYsMWXfi_z8I5Fsgtw09sWBQ,840
45
+ semantic_release/data/templates/angular/rst/CHANGELOG.rst.j2,sha256=VmkXEMHiPBdZ1Z47QMxnJBZA0NbFSbKenUbThQVFAGY,731
44
46
  semantic_release/data/templates/angular/rst/.components/changelog_header.rst.j2,sha256=c9xN1SEYLFwMvPYXYKt-ZbYPn2-Ss0V7zepEtFFj3Os,200
45
- semantic_release/data/templates/angular/rst/.components/changelog_init.rst.j2,sha256=dOICVsTMVLewBDC9QMHhX6Ub9rUFlBAzHOGvsFtNzYY,596
46
- semantic_release/data/templates/angular/rst/.components/changelog_update.rst.j2,sha256=L44PJyCb1xge2wC2-w9cAPCLoJDFVIsm8BnZeQOzg94,2381
47
- semantic_release/data/templates/angular/rst/.components/changes.rst.j2,sha256=bF9fv3ufFIsAmcCI1GN9eind5Oc3YB1QPw7BFCPtdS8,938
48
- semantic_release/data/templates/angular/rst/.components/macros.rst.j2,sha256=jTrUMX5TBpUFP5oo2UZTPB-88hX7NMr-LAuddIJdmhw,551
47
+ semantic_release/data/templates/angular/rst/.components/changelog_init.rst.j2,sha256=XD0l3eTyz1yydLKsmSqBk-u8RnO-RdQ2Q8uWezHMAWw,866
48
+ semantic_release/data/templates/angular/rst/.components/changelog_update.rst.j2,sha256=x23-qk9owJrOQaHx8SgSnIZECITjPf1R2awfv9EOHN0,2604
49
+ semantic_release/data/templates/angular/rst/.components/changes.rst.j2,sha256=REgnXdUDQKH2_BakQ-zbQtS4-MJMYKtA1jBXYFhfSjk,2915
50
+ semantic_release/data/templates/angular/rst/.components/first_release.rst.j2,sha256=tChYsdSFMaB0DU9KROYSxijdb7tqaN1psKmDMbrVrJw,443
51
+ semantic_release/data/templates/angular/rst/.components/macros.rst.j2,sha256=C4JkbfM9mfal-fMJJp6dPUT45PsqwfsCFu4pfZe34QI,3176
49
52
  semantic_release/data/templates/angular/rst/.components/unreleased_changes.rst.j2,sha256=ARBhc1ZpKwehGKDvOMqukmN59mTJiHzHsS7rOfKYCt8,202
50
53
  semantic_release/data/templates/angular/rst/.components/versioned_changes.rst.j2,sha256=NZfn1W14QochiAJ43oNKmcrCn_vgfbkKtvOTAw1jEc8,530
51
54
  semantic_release/hvcs/__init__.py,sha256=JwoaLOF-12L-OBo_9-tOXXhdiHKeVungA9865to2oZk,494
@@ -62,10 +65,10 @@ semantic_release/version/algorithm.py,sha256=ofx_bIWq6ptJVr-ekI11IzxzDEctDKFiVwa
62
65
  semantic_release/version/declaration.py,sha256=f6Ld7hIhrqvDrRBapJHr-KDimuyo-4IG8009Zu9BIgU,7357
63
66
  semantic_release/version/translator.py,sha256=P1noIsVBn8u6zNOFjG0xKYOWapxqf_PHSMvMeLJ9kXg,3050
64
67
  semantic_release/version/version.py,sha256=6PCtSbLP88U1daoxnCwHc--YguZo4waGNLqJ5JfeczE,14175
65
- python_semantic_release-9.12.2.dist-info/AUTHORS.rst,sha256=XOReVvpymEFUPsS2QPH97jlfJBVrxwS2eu8-jVAe4gk,230
66
- python_semantic_release-9.12.2.dist-info/LICENSE,sha256=NE85nszX252sdQdu0xgS9qwfYES0k8qS6gW3uO4jRGE,1083
67
- python_semantic_release-9.12.2.dist-info/METADATA,sha256=ONiKGfUl4d-OGkLjgRYVvT-TG-6QYTxvGGmOhi-pvFY,3571
68
- python_semantic_release-9.12.2.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
69
- python_semantic_release-9.12.2.dist-info/entry_points.txt,sha256=r2Jql3GTQyugQnvf34l2eXk1O_Qx6llR_xixG1ZWgD0,105
70
- python_semantic_release-9.12.2.dist-info/top_level.txt,sha256=qYA24nyg3eP-ti5UW7Vuj2aXVmM0wqVHx4mREdRZNAA,17
71
- python_semantic_release-9.12.2.dist-info/RECORD,,
68
+ python_semantic_release-9.14.0.dist-info/AUTHORS.rst,sha256=XOReVvpymEFUPsS2QPH97jlfJBVrxwS2eu8-jVAe4gk,230
69
+ python_semantic_release-9.14.0.dist-info/LICENSE,sha256=NE85nszX252sdQdu0xgS9qwfYES0k8qS6gW3uO4jRGE,1083
70
+ python_semantic_release-9.14.0.dist-info/METADATA,sha256=tY6DDyFypAxFHo8kVuuCecr3h_siBZ7RO88JOQAIZQ4,3571
71
+ python_semantic_release-9.14.0.dist-info/WHEEL,sha256=bFJAMchF8aTQGUgMZzHJyDDMPTO3ToJ7x23SLJa1SVo,92
72
+ python_semantic_release-9.14.0.dist-info/entry_points.txt,sha256=r2Jql3GTQyugQnvf34l2eXk1O_Qx6llR_xixG1ZWgD0,105
73
+ python_semantic_release-9.14.0.dist-info/top_level.txt,sha256=qYA24nyg3eP-ti5UW7Vuj2aXVmM0wqVHx4mREdRZNAA,17
74
+ python_semantic_release-9.14.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.44.0)
2
+ Generator: bdist_wheel (0.45.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -24,7 +24,7 @@ from semantic_release.version import (
24
24
  tags_and_versions,
25
25
  )
26
26
 
27
- __version__ = "9.12.2"
27
+ __version__ = "9.14.0"
28
28
 
29
29
  __all__ = [
30
30
  "CommitParser",
@@ -23,6 +23,7 @@ class ReleaseNotesContext:
23
23
  hvcs_type: str
24
24
  version: Version
25
25
  release: Release
26
+ mask_initial_release: bool
26
27
  filters: tuple[Callable[..., Any], ...] = ()
27
28
 
28
29
  def bind_to_environment(self, env: Environment) -> Environment:
@@ -53,6 +54,7 @@ class ChangelogContext:
53
54
  changelog_mode: Literal["update", "init"]
54
55
  prev_changelog_file: str
55
56
  changelog_insertion_flag: str
57
+ mask_initial_release: bool
56
58
  filters: tuple[Callable[..., Any], ...] = ()
57
59
 
58
60
  def bind_to_environment(self, env: Environment) -> Environment:
@@ -69,6 +71,7 @@ def make_changelog_context(
69
71
  mode: ChangelogMode,
70
72
  prev_changelog_file: Path,
71
73
  insertion_flag: str,
74
+ mask_initial_release: bool,
72
75
  ) -> ChangelogContext:
73
76
  return ChangelogContext(
74
77
  repo_name=hvcs_client.repo_name,
@@ -76,6 +79,7 @@ def make_changelog_context(
76
79
  history=release_history,
77
80
  changelog_mode=mode.value,
78
81
  changelog_insertion_flag=insertion_flag,
82
+ mask_initial_release=mask_initial_release,
79
83
  prev_changelog_file=str(prev_changelog_file),
80
84
  hvcs_type=hvcs_client.__class__.__name__.lower(),
81
85
  filters=(
@@ -111,6 +115,11 @@ def convert_md_to_rst(md_content: str) -> str:
111
115
  "bullets": (regexp(r"^(\s*)-(\s)"), r"\1*\2"),
112
116
  # Replace markdown inline raw content with rst inline raw content
113
117
  "raw-inline": (regexp(r"(?<=\s)(`[^`]+`)(?![`_])"), r"`\1`"),
118
+ # Replace markdown inline link with rst inline link
119
+ "link-inline": (
120
+ regexp(r"(?<=\s)\[([^\]]+)\]\(([^)]+)\)(?=\s|$)"),
121
+ r"`\1 <\2>`_",
122
+ ),
114
123
  }
115
124
 
116
125
  for pattern, replacement in replacements.values():
@@ -178,6 +178,7 @@ def write_changelog_files(
178
178
  mode=runtime_ctx.changelog_mode,
179
179
  insertion_flag=runtime_ctx.changelog_insertion_flag,
180
180
  prev_changelog_file=runtime_ctx.changelog_file,
181
+ mask_initial_release=runtime_ctx.changelog_mask_initial_release,
181
182
  )
182
183
 
183
184
  user_templates = []
@@ -226,6 +227,7 @@ def generate_release_notes(
226
227
  template_dir: Path,
227
228
  history: ReleaseHistory,
228
229
  style: str,
230
+ mask_initial_release: bool,
229
231
  ) -> str:
230
232
  users_tpl_file = template_dir / DEFAULT_RELEASE_NOTES_TPL_FILE
231
233
 
@@ -251,6 +253,7 @@ def generate_release_notes(
251
253
  hvcs_type=hvcs_client.__class__.__name__.lower(),
252
254
  version=release["version"],
253
255
  release=release,
256
+ mask_initial_release=mask_initial_release,
254
257
  filters=(*hvcs_client.get_changelog_context_filters(), autofit_text_width),
255
258
  ).bind_to_environment(
256
259
  # Use a new, non-configurable environment for release notes -
@@ -259,11 +262,9 @@ def generate_release_notes(
259
262
  )
260
263
 
261
264
  # TODO: Remove in v10
262
- release_notes_env.globals["context"] = {
263
- "history": history,
264
- }
265
- release_notes_env.globals["ctx"] = {
265
+ release_notes_env.globals["context"] = release_notes_env.globals["ctx"] = {
266
266
  "history": history,
267
+ "mask_initial_release": mask_initial_release,
267
268
  }
268
269
 
269
270
  return render_release_notes(
@@ -119,6 +119,7 @@ def changelog(cli_ctx: CliContextObj, release_tag: str | None) -> None:
119
119
  runtime.template_dir,
120
120
  release_history,
121
121
  style=runtime.changelog_style,
122
+ mask_initial_release=runtime.changelog_mask_initial_release,
122
123
  )
123
124
 
124
125
  try:
@@ -696,6 +696,7 @@ def version( # noqa: C901
696
696
  runtime.template_dir,
697
697
  history=release_history,
698
698
  style=runtime.changelog_style,
699
+ mask_initial_release=runtime.changelog_mask_initial_release,
699
700
  )
700
701
 
701
702
  exception: Exception | None = None
@@ -123,6 +123,8 @@ class ChangelogEnvironmentConfig(BaseModel):
123
123
  class DefaultChangelogTemplatesConfig(BaseModel):
124
124
  changelog_file: str = "CHANGELOG.md"
125
125
  output_format: ChangelogOutputFormat = ChangelogOutputFormat.NONE
126
+ # TODO: Breaking Change v10, it will become True
127
+ mask_initial_release: bool = False
126
128
 
127
129
  @model_validator(mode="after")
128
130
  def interpret_output_format(self) -> Self:
@@ -511,6 +513,7 @@ class RuntimeContext:
511
513
  version_declarations: Tuple[VersionDeclarationABC, ...]
512
514
  hvcs_client: hvcs.HvcsBase
513
515
  changelog_insertion_flag: str
516
+ changelog_mask_initial_release: bool
514
517
  changelog_mode: ChangelogMode
515
518
  changelog_file: Path
516
519
  changelog_style: str
@@ -795,6 +798,7 @@ class RuntimeContext:
795
798
  hvcs_client=hvcs_client,
796
799
  changelog_file=changelog_file,
797
800
  changelog_mode=raw.changelog.mode,
801
+ changelog_mask_initial_release=raw.changelog.default_templates.mask_initial_release,
798
802
  changelog_insertion_flag=raw.changelog.insertion_flag,
799
803
  assets=raw.assets,
800
804
  commit_author=commit_author,
@@ -8,19 +8,26 @@ from __future__ import annotations
8
8
  import logging
9
9
  import re
10
10
  from functools import reduce
11
+ from itertools import zip_longest
11
12
  from re import compile as regexp
12
13
  from typing import TYPE_CHECKING, Tuple
13
14
 
14
15
  from pydantic.dataclasses import dataclass
15
16
 
16
17
  from semantic_release.commit_parser._base import CommitParser, ParserOptions
17
- from semantic_release.commit_parser.token import ParsedCommit, ParseError, ParseResult
18
+ from semantic_release.commit_parser.token import (
19
+ ParsedCommit,
20
+ ParsedMessageResult,
21
+ ParseError,
22
+ ParseResult,
23
+ )
18
24
  from semantic_release.commit_parser.util import breaking_re, parse_paragraphs
19
25
  from semantic_release.enums import LevelBump
20
26
 
21
27
  if TYPE_CHECKING:
22
28
  from git.objects.commit import Commit
23
29
 
30
+
24
31
  logger = logging.getLogger(__name__)
25
32
 
26
33
 
@@ -65,11 +72,16 @@ class AngularParserOptions(ParserOptions):
65
72
  default_bump_level: LevelBump = LevelBump.NO_RELEASE
66
73
 
67
74
  def __post_init__(self) -> None:
68
- self.tag_to_level = {tag: self.default_bump_level for tag in self.allowed_tags}
69
- for tag in self.patch_tags:
70
- self.tag_to_level[tag] = LevelBump.PATCH
71
- for tag in self.minor_tags:
72
- self.tag_to_level[tag] = LevelBump.MINOR
75
+ self.tag_to_level: dict[str, LevelBump] = dict(
76
+ [
77
+ # we have to do a type ignore as zip_longest provides a type that is not specific enough
78
+ # for our expected output. Due to the empty second array, we know the first is always longest
79
+ # and that means no values in the first entry of the tuples will ever be a LevelBump.
80
+ *zip_longest(self.allowed_tags, (), fillvalue=self.default_bump_level), # type: ignore[list-item]
81
+ *zip_longest(self.patch_tags, (), fillvalue=LevelBump.PATCH), # type: ignore[list-item]
82
+ *zip_longest(self.minor_tags, (), fillvalue=LevelBump.MINOR), # type: ignore[list-item]
83
+ ]
84
+ )
73
85
 
74
86
 
75
87
  class AngularCommitParser(CommitParser[ParseResult, AngularParserOptions]):
@@ -98,6 +110,10 @@ class AngularCommitParser(CommitParser[ParseResult, AngularParserOptions]):
98
110
  ),
99
111
  flags=re.DOTALL,
100
112
  )
113
+ # GitHub & Gitea use (#123), GitLab uses (!123), and BitBucket uses (pull request #123)
114
+ self.mr_selector = regexp(
115
+ r"[\t ]+\((?:pull request )?(?P<mr_number>[#!]\d+)\)[\t ]*$"
116
+ )
101
117
 
102
118
  @staticmethod
103
119
  def get_default_options() -> AngularParserOptions:
@@ -114,27 +130,23 @@ class AngularCommitParser(CommitParser[ParseResult, AngularParserOptions]):
114
130
  accumulator["descriptions"].append(text)
115
131
  return accumulator
116
132
 
117
- # Maybe this can be cached as an optimization, similar to how
118
- # mypy/pytest use their own caching directories, for very large commit
119
- # histories?
120
- # The problem is the cache likely won't be present in CI environments
121
- def parse(self, commit: Commit) -> ParseResult:
122
- """
123
- Attempt to parse the commit message with a regular expression into a
124
- ParseResult
125
- """
126
- message = str(commit.message)
127
- parsed = self.re_parser.match(message)
128
- if not parsed:
129
- return _logged_parse_error(
130
- commit, f"Unable to parse commit message: {message}"
131
- )
133
+ def parse_message(self, message: str) -> ParsedMessageResult | None:
134
+ if not (parsed := self.re_parser.match(message)):
135
+ return None
136
+
132
137
  parsed_break = parsed.group("break")
133
138
  parsed_scope = parsed.group("scope")
134
139
  parsed_subject = parsed.group("subject")
135
140
  parsed_text = parsed.group("text")
136
141
  parsed_type = parsed.group("type")
137
142
 
143
+ linked_merge_request = ""
144
+ if mr_match := self.mr_selector.search(parsed_subject):
145
+ linked_merge_request = mr_match.group("mr_number")
146
+ # TODO: breaking change v10, removes PR number from subject/descriptions
147
+ # expects changelog template to format the line accordingly
148
+ # parsed_subject = self.pr_selector.sub("", parsed_subject).strip()
149
+
138
150
  body_components: dict[str, list[str]] = reduce(
139
151
  self.commit_body_components_separator,
140
152
  [
@@ -157,19 +169,34 @@ class AngularCommitParser(CommitParser[ParseResult, AngularParserOptions]):
157
169
  )
158
170
  )
159
171
 
160
- # TODO: remove in the future
161
- if level_bump == LevelBump.MAJOR:
162
- parsed_type = "breaking"
172
+ return ParsedMessageResult(
173
+ bump=level_bump,
174
+ type=parsed_type,
175
+ category=LONG_TYPE_NAMES.get(parsed_type, parsed_type),
176
+ scope=parsed_scope,
177
+ descriptions=tuple(body_components["descriptions"]),
178
+ breaking_descriptions=tuple(body_components["breaking_descriptions"]),
179
+ linked_merge_request=linked_merge_request,
180
+ )
181
+
182
+ # Maybe this can be cached as an optimization, similar to how
183
+ # mypy/pytest use their own caching directories, for very large commit
184
+ # histories?
185
+ # The problem is the cache likely won't be present in CI environments
186
+ def parse(self, commit: Commit) -> ParseResult:
187
+ """
188
+ Attempt to parse the commit message with a regular expression into a
189
+ ParseResult
190
+ """
191
+ if not (pmsg_result := self.parse_message(str(commit.message))):
192
+ return _logged_parse_error(
193
+ commit, f"Unable to parse commit message: {commit.message!r}"
194
+ )
163
195
 
164
196
  logger.debug(
165
- "commit %s introduces a %s level_bump", commit.hexsha[:8], level_bump
197
+ "commit %s introduces a %s level_bump",
198
+ commit.hexsha[:8],
199
+ pmsg_result.bump,
166
200
  )
167
201
 
168
- return ParsedCommit(
169
- bump=level_bump,
170
- type=LONG_TYPE_NAMES.get(parsed_type, parsed_type),
171
- scope=parsed_scope,
172
- descriptions=body_components["descriptions"],
173
- breaking_descriptions=body_components["breaking_descriptions"],
174
- commit=commit,
175
- )
202
+ return ParsedCommit.from_parsed_message_result(commit, pmsg_result)
@@ -1,13 +1,21 @@
1
1
  """Commit parser which looks for emojis to determine the type of commit"""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import logging
6
+ from itertools import zip_longest
7
+ from re import compile as regexp
4
8
  from typing import Tuple
5
9
 
6
10
  from git.objects.commit import Commit
7
11
  from pydantic.dataclasses import dataclass
8
12
 
9
13
  from semantic_release.commit_parser._base import CommitParser, ParserOptions
10
- from semantic_release.commit_parser.token import ParsedCommit, ParseResult
14
+ from semantic_release.commit_parser.token import (
15
+ ParsedCommit,
16
+ ParsedMessageResult,
17
+ ParseResult,
18
+ )
11
19
  from semantic_release.commit_parser.util import parse_paragraphs
12
20
  from semantic_release.enums import LevelBump
13
21
 
@@ -41,8 +49,26 @@ class EmojiParserOptions(ParserOptions):
41
49
  ":robot:",
42
50
  ":green_apple:",
43
51
  )
52
+ allowed_tags: Tuple[str, ...] = (
53
+ *major_tags,
54
+ *minor_tags,
55
+ *patch_tags,
56
+ )
44
57
  default_bump_level: LevelBump = LevelBump.NO_RELEASE
45
58
 
59
+ def __post_init__(self) -> None:
60
+ self.tag_to_level: dict[str, LevelBump] = dict(
61
+ [
62
+ # we have to do a type ignore as zip_longest provides a type that is not specific enough
63
+ # for our expected output. Due to the empty second array, we know the first is always longest
64
+ # and that means no values in the first entry of the tuples will ever be a LevelBump.
65
+ *zip_longest(self.allowed_tags, (), fillvalue=self.default_bump_level), # type: ignore[list-item]
66
+ *zip_longest(self.patch_tags, (), fillvalue=LevelBump.PATCH), # type: ignore[list-item]
67
+ *zip_longest(self.minor_tags, (), fillvalue=LevelBump.MINOR), # type: ignore[list-item]
68
+ *zip_longest(self.major_tags, (), fillvalue=LevelBump.MAJOR), # type: ignore[list-item]
69
+ ]
70
+ )
71
+
46
72
 
47
73
  class EmojiCommitParser(CommitParser[ParseResult, EmojiParserOptions]):
48
74
  """
@@ -60,56 +86,77 @@ class EmojiCommitParser(CommitParser[ParseResult, EmojiParserOptions]):
60
86
  # TODO: Deprecate in lieu of get_default_options()
61
87
  parser_options = EmojiParserOptions
62
88
 
89
+ def __init__(self, options: EmojiParserOptions | None = None) -> None:
90
+ super().__init__(options)
91
+ prcedence_order_regex = str.join(
92
+ "|",
93
+ [
94
+ *self.options.major_tags,
95
+ *self.options.minor_tags,
96
+ *self.options.patch_tags,
97
+ ],
98
+ )
99
+ self.emoji_selector = regexp(r"(?P<type>%s)" % prcedence_order_regex)
100
+
101
+ # GitHub & Gitea use (#123), GitLab uses (!123), and BitBucket uses (pull request #123)
102
+ self.mr_selector = regexp(
103
+ r"[\t ]+\((?:pull request )?(?P<mr_number>[#!]\d+)\)[\t ]*$"
104
+ )
105
+
63
106
  @staticmethod
64
107
  def get_default_options() -> EmojiParserOptions:
65
108
  return EmojiParserOptions()
66
109
 
67
- def parse(self, commit: Commit) -> ParseResult:
68
- all_emojis = (
69
- self.options.major_tags + self.options.minor_tags + self.options.patch_tags
110
+ def parse_message(self, message: str) -> ParsedMessageResult:
111
+ subject = message.split("\n", maxsplit=1)[0]
112
+
113
+ linked_merge_request = ""
114
+ if mr_match := self.mr_selector.search(subject):
115
+ linked_merge_request = mr_match.group("mr_number")
116
+ # TODO: breaking change v10, removes PR number from subject/descriptions
117
+ # expects changelog template to format the line accordingly
118
+ # subject = self.mr_selector.sub("", subject).strip()
119
+
120
+ # Search for emoji of the highest importance in the subject
121
+ primary_emoji = (
122
+ match.group("type")
123
+ if (match := self.emoji_selector.search(subject))
124
+ else "Other"
70
125
  )
71
126
 
72
- message = str(commit.message)
73
- subject = message.split("\n")[0]
74
-
75
- # Loop over emojis from most important to least important
76
- # Therefore, we find the highest level emoji first
77
- primary_emoji = "Other"
78
- for emoji in all_emojis:
79
- if emoji in subject:
80
- primary_emoji = emoji
81
- break
82
- logger.debug("Selected %s as the primary emoji", primary_emoji)
83
-
84
- # Find which level this commit was from
85
- level_bump = LevelBump.NO_RELEASE
86
- if primary_emoji in self.options.major_tags:
87
- level_bump = LevelBump.MAJOR
88
- elif primary_emoji in self.options.minor_tags:
89
- level_bump = LevelBump.MINOR
90
- elif primary_emoji in self.options.patch_tags:
91
- level_bump = LevelBump.PATCH
92
- else:
93
- level_bump = self.options.default_bump_level
94
- logger.debug(
95
- "commit %s introduces a level bump of %s due to the default_bump_level",
96
- commit.hexsha[:8],
97
- level_bump,
98
- )
99
-
100
- logger.debug(
101
- "commit %s introduces a %s level_bump", commit.hexsha[:8], level_bump
127
+ level_bump = self.options.tag_to_level.get(
128
+ primary_emoji, self.options.default_bump_level
102
129
  )
103
130
 
104
131
  # All emojis will remain part of the returned description
105
- descriptions = parse_paragraphs(message)
106
- return ParsedCommit(
132
+ descriptions = tuple(parse_paragraphs(message))
133
+ return ParsedMessageResult(
107
134
  bump=level_bump,
108
135
  type=primary_emoji,
109
- scope="",
136
+ category=primary_emoji,
137
+ scope="", # TODO: add scope support
138
+ # TODO: breaking change v10, removes breaking change footers from descriptions
139
+ # descriptions=(
140
+ # descriptions[:1] if level_bump is LevelBump.MAJOR else descriptions
141
+ # )
110
142
  descriptions=descriptions,
111
143
  breaking_descriptions=(
112
- descriptions[1:] if level_bump is LevelBump.MAJOR else []
144
+ descriptions[1:] if level_bump is LevelBump.MAJOR else ()
113
145
  ),
114
- commit=commit,
146
+ linked_merge_request=linked_merge_request,
147
+ )
148
+
149
+ def parse(self, commit: Commit) -> ParseResult:
150
+ """
151
+ Attempt to parse the commit message with a regular expression into a
152
+ ParseResult
153
+ """
154
+ pmsg_result = self.parse_message(str(commit.message))
155
+
156
+ logger.debug(
157
+ "commit %s introduces a %s level_bump",
158
+ commit.hexsha[:8],
159
+ pmsg_result.bump,
115
160
  )
161
+
162
+ return ParsedCommit.from_parsed_message_result(commit, pmsg_result)