Skip to content

Commit 23e7888

Browse files
authored
Feat: added scopes argument to specify valid scopes (#102)
2 parents d2f40e2 + 3b6ff05 commit 23e7888

File tree

7 files changed

+64
-10
lines changed

7 files changed

+64
-10
lines changed

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,18 +129,19 @@ print(is_conventional("custom: this is a conventional commit", types=["custom"])
129129

130130
```shell
131131
$ conventional-pre-commit -h
132-
usage: conventional-pre-commit [-h] [--force-scope] [--strict] [types ...] input
132+
usage: conventional-pre-commit [-h] [--force-scope] [--scopes SCOPES] [--strict] [types ...] input
133133
134134
Check a git commit message for Conventional Commits formatting.
135135
136136
positional arguments:
137-
types Optional list of types to support
138-
input A file containing a git commit message
137+
types Optional list of types to support
138+
input A file containing a git commit message
139139
140140
options:
141-
-h, --help show this help message and exit
142-
--force-scope Force commit to have scope defined.
143-
--strict Force commit to strictly follow Conventional Commits formatting. Disallows fixup! style commits.
141+
-h, --help show this help message and exit
142+
--force-scope Force commit to have scope defined.
143+
--scopes SCOPES Optional list of scopes to support. Scopes should be separated by commas with no spaces (e.g. api,client)
144+
--strict Force commit to strictly follow Conventional Commits formatting. Disallows fixup! style commits.
144145
```
145146

146147
Supply arguments on the command-line, or via the pre-commit `hooks.args` property:

conventional_pre_commit/format.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import re
2+
from typing import List, Optional
23

34
CONVENTIONAL_TYPES = ["feat", "fix"]
45
DEFAULT_TYPES = [
@@ -26,8 +27,20 @@ def r_types(types):
2627
return "|".join(types)
2728

2829

29-
def r_scope(optional=True):
30+
def _get_scope_pattern(scopes: Optional[List[str]] = None):
31+
scopes_str = r_types(scopes)
32+
escaped_delimiters = list(map(re.escape, [":", ",", "-", "/"])) # type: ignore
33+
delimiters_pattern = r_types(escaped_delimiters)
34+
return rf"\(\s*(?:{scopes_str})(?:\s*(?:{delimiters_pattern})\s*(?:{scopes_str}))*\s*\)"
35+
36+
37+
def r_scope(optional=True, scopes: Optional[List[str]] = None):
3038
"""Regex str for an optional (scope)."""
39+
40+
if scopes:
41+
scopes_pattern = _get_scope_pattern(scopes)
42+
return scopes_pattern
43+
3144
if optional:
3245
return r"(\([\w \/:,-]+\))?"
3346
else:
@@ -79,7 +92,7 @@ def conventional_types(types=[]):
7992
return types
8093

8194

82-
def is_conventional(input, types=DEFAULT_TYPES, optional_scope=True):
95+
def is_conventional(input, types=DEFAULT_TYPES, optional_scope=True, scopes: Optional[List[str]] = None):
8396
"""
8497
Returns True if input matches Conventional Commits formatting
8598
https://www.conventionalcommits.org
@@ -89,7 +102,7 @@ def is_conventional(input, types=DEFAULT_TYPES, optional_scope=True):
89102
input = strip_verbose_diff(input)
90103
input = strip_comments(input)
91104
types = conventional_types(types)
92-
pattern = f"^({r_types(types)}){r_scope(optional_scope)}{r_delim()}{r_subject()}{r_body()}"
105+
pattern = f"^({r_types(types)}){r_scope(optional_scope, scopes=scopes)}{r_delim()}{r_subject()}{r_body()}"
93106
regex = re.compile(pattern, re.MULTILINE)
94107

95108
result = regex.match(input)

conventional_pre_commit/hook.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ def main(argv=[]):
2323
parser.add_argument(
2424
"--force-scope", action="store_false", default=True, dest="optional_scope", help="Force commit to have scope defined."
2525
)
26+
parser.add_argument(
27+
"--scopes",
28+
type=str,
29+
default=None,
30+
help="Optional list of scopes to support. Scopes should be separated by commas with no spaces (e.g. api,client)",
31+
)
2632
parser.add_argument(
2733
"--strict",
2834
action="store_true",
@@ -51,12 +57,16 @@ def main(argv=[]):
5157
"""
5258
)
5359
return RESULT_FAIL
60+
if args.scopes:
61+
scopes = args.scopes.split(",")
62+
else:
63+
scopes = args.scopes
5464

5565
if not args.strict:
5666
if format.has_autosquash_prefix(message):
5767
return RESULT_SUCCESS
5868

59-
if format.is_conventional(message, args.types, args.optional_scope):
69+
if format.is_conventional(message, args.types, args.optional_scope, scopes):
6070
return RESULT_SUCCESS
6171
else:
6272
print(

tests/conftest.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,8 @@ def conventional_commit_bad_multi_line_path():
5252
@pytest.fixture
5353
def conventional_commit_multi_line_path():
5454
return get_message_path("conventional_commit_multi_line")
55+
56+
57+
@pytest.fixture
58+
def conventional_commit_with_multiple_scopes_path():
59+
return get_message_path("conventional_commit_with_multiple_scopes")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
feat(api,client): added new endpoint with client support

tests/test_format.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@ def test_r_scope__special_chars():
6464
assert regex.match("(some,thing)")
6565

6666

67+
def test_r_scope__scopes():
68+
scopes_input = ["api", "client"]
69+
result = format.r_scope(scopes=scopes_input)
70+
regex = re.compile(result)
71+
assert regex.match("(api)")
72+
assert regex.match("(client)")
73+
assert regex.match("(api, client)")
74+
assert regex.match("(api: client)")
75+
assert regex.match("(api/client)")
76+
assert regex.match("(api-client)")
77+
assert not regex.match("(test)")
78+
assert not regex.match("(api; client)")
79+
80+
6781
def test_r_delim():
6882
result = format.r_delim()
6983
regex = re.compile(result)

tests/test_hook.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,16 @@ def test_subprocess_success__conventional_with_scope(cmd, conventional_commit_wi
142142
assert result == RESULT_SUCCESS
143143

144144

145+
def test_subprocess_success__conventional_with_multiple_scopes(cmd, conventional_commit_with_multiple_scopes_path):
146+
result = subprocess.call((cmd, "--scopes", "api,client", conventional_commit_with_multiple_scopes_path))
147+
assert result == RESULT_SUCCESS
148+
149+
150+
def test_subprocess_fail__conventional_with_multiple_scopes(cmd, conventional_commit_with_multiple_scopes_path):
151+
result = subprocess.call((cmd, "--scopes", "api", conventional_commit_with_multiple_scopes_path))
152+
assert result == RESULT_FAIL
153+
154+
145155
def test_subprocess_success__fixup_commit(cmd, fixup_commit_path):
146156
result = subprocess.call((cmd, fixup_commit_path))
147157

0 commit comments

Comments
 (0)