Coverage for ghtc/parser.py: 94%
53 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-11-25 11:59 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-11-25 11:59 +0000
1from typing import Optional, List, Dict
2import re
3from ghtc.models import (
4 ConventionalCommitType,
5 ConventionalCommitFooter,
6 ConventionalCommitMessage,
7)
10TYPE_MAPPINGS: Dict[str, ConventionalCommitType] = {
11 "feat": ConventionalCommitType.FEAT,
12 "fix": ConventionalCommitType.FIX,
13 "build": ConventionalCommitType.BUILD,
14 "chore": ConventionalCommitType.CHORE,
15 "ci": ConventionalCommitType.CI,
16 "docs": ConventionalCommitType.DOCS,
17 "doc": ConventionalCommitType.DOCS,
18 "style": ConventionalCommitType.STYLE,
19 "refactor": ConventionalCommitType.REFACTOR,
20 "perf": ConventionalCommitType.PERF,
21 "perfs": ConventionalCommitType.PERF,
22 "test": ConventionalCommitType.TEST,
23 "tests": ConventionalCommitType.TEST,
24}
25TITLE_REGEX = r"^([a-zA-Z0-9_-]+)(!{0,1})(\([a-zA-Z0-9_-]*\)){0,1}(!{0,1}): (.*)$"
26TITLE_COMPILED_REGEX = re.compile(TITLE_REGEX)
27FOOTER_REGEX1 = r"^([a-zA-Z0-9_-]+): (.*)$"
28FOOTER_COMPILED_REGEX1 = re.compile(FOOTER_REGEX1)
29FOOTER_REGEX2 = r"^([a-zA-Z0-9_-]+) #(.*)$"
30FOOTER_COMPILED_REGEX2 = re.compile(FOOTER_REGEX2)
31BREAKING_CHANGE_FOOTER_REGEX = r"^BREAKING[- ]CHANGE: (.*)$"
32BREAKING_CHANGE_FOOTER_COMPILED_REGEX = re.compile(BREAKING_CHANGE_FOOTER_REGEX)
35def type_string_to_commit_type(type_str: str) -> ConventionalCommitType:
36 if type_str not in TYPE_MAPPINGS:
37 return ConventionalCommitType.OTHER
38 return TYPE_MAPPINGS[type_str]
41def parse(commit_message: str) -> Optional[ConventionalCommitMessage]:
42 if not commit_message:
43 return None
44 lines = commit_message.splitlines()
45 first_line = lines[0]
46 match = TITLE_COMPILED_REGEX.match(first_line)
47 if match is None:
48 return None
49 type_str = match[1].lower()
50 breaking = False
51 if match[2] or match[4]:
52 breaking = True
53 scope = None
54 if match[3]:
55 scope = match[3].lower()[1:-1]
56 description = match[5]
57 body = None
58 footers: List[ConventionalCommitFooter] = []
59 if len(lines) > 1 and lines[1] == "":
60 for line in lines[1:]:
61 if not line:
62 continue
63 tmp1 = FOOTER_COMPILED_REGEX1.match(line)
64 tmp2 = FOOTER_COMPILED_REGEX2.match(line)
65 tmp3 = BREAKING_CHANGE_FOOTER_COMPILED_REGEX.match(line)
66 if len(footers) == 0 and tmp1 is None and tmp2 is None and tmp3 is None:
67 if body is None:
68 body = f"{line}"
69 else:
70 body += f"\n{line}"
71 else:
72 if tmp3 is not None:
73 breaking = True
74 footers.append(ConventionalCommitFooter("BREAKING CHANGE", tmp3[1]))
75 elif tmp1 is not None:
76 footers.append(ConventionalCommitFooter(tmp1[1], tmp1[2]))
77 elif tmp2 is not None:
78 footers.append(ConventionalCommitFooter(tmp2[1], tmp2[2]))
79 return ConventionalCommitMessage(
80 type=type_string_to_commit_type(type_str),
81 scope=scope,
82 body=body,
83 footers=footers,
84 description=description,
85 breaking=breaking,
86 )