Skip to content

Commit

Permalink
Feat: added scopes argument to specify valid scopes (#102)
Browse files Browse the repository at this point in the history
  • Loading branch information
thekaveman authored Jul 12, 2024
2 parents d2f40e2 + 3b6ff05 commit 23e7888
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 10 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,18 +129,19 @@ print(is_conventional("custom: this is a conventional commit", types=["custom"])

```shell
$ conventional-pre-commit -h
usage: conventional-pre-commit [-h] [--force-scope] [--strict] [types ...] input
usage: conventional-pre-commit [-h] [--force-scope] [--scopes SCOPES] [--strict] [types ...] input
Check a git commit message for Conventional Commits formatting.
positional arguments:
types Optional list of types to support
input A file containing a git commit message
types Optional list of types to support
input A file containing a git commit message
options:
-h, --help show this help message and exit
--force-scope Force commit to have scope defined.
--strict Force commit to strictly follow Conventional Commits formatting. Disallows fixup! style commits.
-h, --help show this help message and exit
--force-scope Force commit to have scope defined.
--scopes SCOPES Optional list of scopes to support. Scopes should be separated by commas with no spaces (e.g. api,client)
--strict Force commit to strictly follow Conventional Commits formatting. Disallows fixup! style commits.
```

Supply arguments on the command-line, or via the pre-commit `hooks.args` property:
Expand Down
19 changes: 16 additions & 3 deletions conventional_pre_commit/format.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
from typing import List, Optional

CONVENTIONAL_TYPES = ["feat", "fix"]
DEFAULT_TYPES = [
Expand Down Expand Up @@ -26,8 +27,20 @@ def r_types(types):
return "|".join(types)


def r_scope(optional=True):
def _get_scope_pattern(scopes: Optional[List[str]] = None):
scopes_str = r_types(scopes)
escaped_delimiters = list(map(re.escape, [":", ",", "-", "/"])) # type: ignore
delimiters_pattern = r_types(escaped_delimiters)
return rf"\(\s*(?:{scopes_str})(?:\s*(?:{delimiters_pattern})\s*(?:{scopes_str}))*\s*\)"


def r_scope(optional=True, scopes: Optional[List[str]] = None):
"""Regex str for an optional (scope)."""

if scopes:
scopes_pattern = _get_scope_pattern(scopes)
return scopes_pattern

if optional:
return r"(\([\w \/:,-]+\))?"
else:
Expand Down Expand Up @@ -79,7 +92,7 @@ def conventional_types(types=[]):
return types


def is_conventional(input, types=DEFAULT_TYPES, optional_scope=True):
def is_conventional(input, types=DEFAULT_TYPES, optional_scope=True, scopes: Optional[List[str]] = None):
"""
Returns True if input matches Conventional Commits formatting
https://www.conventionalcommits.org
Expand All @@ -89,7 +102,7 @@ def is_conventional(input, types=DEFAULT_TYPES, optional_scope=True):
input = strip_verbose_diff(input)
input = strip_comments(input)
types = conventional_types(types)
pattern = f"^({r_types(types)}){r_scope(optional_scope)}{r_delim()}{r_subject()}{r_body()}"
pattern = f"^({r_types(types)}){r_scope(optional_scope, scopes=scopes)}{r_delim()}{r_subject()}{r_body()}"
regex = re.compile(pattern, re.MULTILINE)

result = regex.match(input)
Expand Down
12 changes: 11 additions & 1 deletion conventional_pre_commit/hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ def main(argv=[]):
parser.add_argument(
"--force-scope", action="store_false", default=True, dest="optional_scope", help="Force commit to have scope defined."
)
parser.add_argument(
"--scopes",
type=str,
default=None,
help="Optional list of scopes to support. Scopes should be separated by commas with no spaces (e.g. api,client)",
)
parser.add_argument(
"--strict",
action="store_true",
Expand Down Expand Up @@ -51,12 +57,16 @@ def main(argv=[]):
"""
)
return RESULT_FAIL
if args.scopes:
scopes = args.scopes.split(",")
else:
scopes = args.scopes

if not args.strict:
if format.has_autosquash_prefix(message):
return RESULT_SUCCESS

if format.is_conventional(message, args.types, args.optional_scope):
if format.is_conventional(message, args.types, args.optional_scope, scopes):
return RESULT_SUCCESS
else:
print(
Expand Down
5 changes: 5 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,8 @@ def conventional_commit_bad_multi_line_path():
@pytest.fixture
def conventional_commit_multi_line_path():
return get_message_path("conventional_commit_multi_line")


@pytest.fixture
def conventional_commit_with_multiple_scopes_path():
return get_message_path("conventional_commit_with_multiple_scopes")
1 change: 1 addition & 0 deletions tests/messages/conventional_commit_with_multiple_scopes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
feat(api,client): added new endpoint with client support
14 changes: 14 additions & 0 deletions tests/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,20 @@ def test_r_scope__special_chars():
assert regex.match("(some,thing)")


def test_r_scope__scopes():
scopes_input = ["api", "client"]
result = format.r_scope(scopes=scopes_input)
regex = re.compile(result)
assert regex.match("(api)")
assert regex.match("(client)")
assert regex.match("(api, client)")
assert regex.match("(api: client)")
assert regex.match("(api/client)")
assert regex.match("(api-client)")
assert not regex.match("(test)")
assert not regex.match("(api; client)")


def test_r_delim():
result = format.r_delim()
regex = re.compile(result)
Expand Down
10 changes: 10 additions & 0 deletions tests/test_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,16 @@ def test_subprocess_success__conventional_with_scope(cmd, conventional_commit_wi
assert result == RESULT_SUCCESS


def test_subprocess_success__conventional_with_multiple_scopes(cmd, conventional_commit_with_multiple_scopes_path):
result = subprocess.call((cmd, "--scopes", "api,client", conventional_commit_with_multiple_scopes_path))
assert result == RESULT_SUCCESS


def test_subprocess_fail__conventional_with_multiple_scopes(cmd, conventional_commit_with_multiple_scopes_path):
result = subprocess.call((cmd, "--scopes", "api", conventional_commit_with_multiple_scopes_path))
assert result == RESULT_FAIL


def test_subprocess_success__fixup_commit(cmd, fixup_commit_path):
result = subprocess.call((cmd, fixup_commit_path))

Expand Down

0 comments on commit 23e7888

Please sign in to comment.