|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | | -import sys |
4 | 3 | from itertools import zip_longest |
5 | | -from typing import IO, TYPE_CHECKING, Any, Union |
| 4 | +from typing import IO |
6 | 5 |
|
7 | 6 | from commitizen.changelog import Metadata |
8 | 7 |
|
9 | 8 | from .base import BaseFormat |
10 | 9 |
|
11 | | -if TYPE_CHECKING: |
12 | | - # TypeAlias is Python 3.10+ but backported in typing-extensions |
13 | | - if sys.version_info >= (3, 10): |
14 | | - from typing import TypeAlias |
15 | | - else: |
16 | | - from typing_extensions import TypeAlias |
17 | | - |
18 | | - |
19 | | -# Can't use `|` operator and native type because of https://bugs.python.org/issue42233 only fixed in 3.10 |
20 | | -TitleKind: TypeAlias = Union[str, tuple[str, str]] |
21 | | - |
22 | 10 |
|
23 | 11 | class RestructuredText(BaseFormat): |
24 | 12 | extension = "rst" |
25 | 13 |
|
26 | | - def get_metadata_from_file(self, file: IO[Any]) -> Metadata: |
| 14 | + def get_metadata_from_file(self, file: IO[str]) -> Metadata: |
27 | 15 | """ |
28 | 16 | RestructuredText section titles are not one-line-based, |
29 | 17 | they spread on 2 or 3 lines and levels are not predefined |
30 | | - but determined byt their occurrence order. |
| 18 | + but determined by their occurrence order. |
31 | 19 |
|
32 | 20 | It requires its own algorithm. |
33 | 21 |
|
34 | 22 | For a more generic approach, you need to rely on `docutils`. |
35 | 23 | """ |
36 | | - meta = Metadata() |
37 | | - unreleased_title_kind: TitleKind | None = None |
38 | | - in_overlined_title = False |
39 | | - lines = file.readlines() |
| 24 | + out_metadata = Metadata() |
| 25 | + unreleased_title_kind: str | tuple[str, str] | None = None |
| 26 | + is_overlined_title = False |
| 27 | + lines = [line.strip().lower() for line in file.readlines()] |
| 28 | + |
40 | 29 | for index, (first, second, third) in enumerate( |
41 | 30 | zip_longest(lines, lines[1:], lines[2:], fillvalue="") |
42 | 31 | ): |
43 | | - first = first.strip().lower() |
44 | | - second = second.strip().lower() |
45 | | - third = third.strip().lower() |
46 | 32 | title: str | None = None |
47 | | - kind: TitleKind | None = None |
48 | | - if self.is_overlined_title(first, second, third): |
| 33 | + kind: str | tuple[str, str] | None = None |
| 34 | + if _is_overlined_title(first, second, third): |
49 | 35 | title = second |
50 | 36 | kind = (first[0], third[0]) |
51 | | - in_overlined_title = True |
52 | | - elif not in_overlined_title and self.is_underlined_title(first, second): |
| 37 | + is_overlined_title = True |
| 38 | + elif not is_overlined_title and _is_underlined_title(first, second): |
53 | 39 | title = first |
54 | 40 | kind = second[0] |
55 | 41 | else: |
56 | | - in_overlined_title = False |
57 | | - |
58 | | - if title: |
59 | | - if "unreleased" in title: |
60 | | - unreleased_title_kind = kind |
61 | | - meta.unreleased_start = index |
62 | | - continue |
63 | | - elif unreleased_title_kind and unreleased_title_kind == kind: |
64 | | - meta.unreleased_end = index |
65 | | - # Try to find the latest release done |
66 | | - if version := self.tag_rules.search_version(title): |
67 | | - meta.latest_version = version[0] |
68 | | - meta.latest_version_tag = version[1] |
69 | | - meta.latest_version_position = index |
70 | | - break |
71 | | - if meta.unreleased_start is not None and meta.unreleased_end is None: |
72 | | - meta.unreleased_end = ( |
73 | | - meta.latest_version_position if meta.latest_version else index + 1 |
| 42 | + is_overlined_title = False |
| 43 | + |
| 44 | + if not title: |
| 45 | + continue |
| 46 | + |
| 47 | + if "unreleased" in title: |
| 48 | + unreleased_title_kind = kind |
| 49 | + out_metadata.unreleased_start = index |
| 50 | + continue |
| 51 | + |
| 52 | + if unreleased_title_kind and unreleased_title_kind == kind: |
| 53 | + out_metadata.unreleased_end = index |
| 54 | + # Try to find the latest release done |
| 55 | + if version := self.tag_rules.search_version(title): |
| 56 | + out_metadata.latest_version = version[0] |
| 57 | + out_metadata.latest_version_tag = version[1] |
| 58 | + out_metadata.latest_version_position = index |
| 59 | + break |
| 60 | + |
| 61 | + if ( |
| 62 | + out_metadata.unreleased_start is not None |
| 63 | + and out_metadata.unreleased_end is None |
| 64 | + ): |
| 65 | + out_metadata.unreleased_end = ( |
| 66 | + out_metadata.latest_version_position |
| 67 | + if out_metadata.latest_version |
| 68 | + else len(lines) |
74 | 69 | ) |
75 | 70 |
|
76 | | - return meta |
77 | | - |
78 | | - def is_overlined_title(self, first: str, second: str, third: str) -> bool: |
79 | | - return ( |
80 | | - len(first) >= len(second) |
81 | | - and len(first) == len(third) |
82 | | - and all(char == first[0] for char in first[1:]) |
83 | | - and first[0] == third[0] |
84 | | - and self.is_underlined_title(second, third) |
85 | | - ) |
86 | | - |
87 | | - def is_underlined_title(self, first: str, second: str) -> bool: |
88 | | - return ( |
89 | | - len(second) >= len(first) |
90 | | - and not second.isalnum() |
91 | | - and all(char == second[0] for char in second[1:]) |
92 | | - ) |
| 71 | + return out_metadata |
| 72 | + |
| 73 | + |
| 74 | +def _is_overlined_title(first: str, second: str, third: str) -> bool: |
| 75 | + return ( |
| 76 | + len(first) == len(third) >= len(second) |
| 77 | + and first[0] == third[0] |
| 78 | + and all(char == first[0] for char in first) |
| 79 | + and _is_underlined_title(second, third) |
| 80 | + ) |
| 81 | + |
| 82 | + |
| 83 | +def _is_underlined_title(first: str, second: str) -> bool: |
| 84 | + return ( |
| 85 | + len(second) >= len(first) |
| 86 | + and not second.isalnum() |
| 87 | + and all(char == second[0] for char in second) |
| 88 | + ) |
0 commit comments