Skip to content

Commit c0a32fa

Browse files
petermESPtomassebestik
authored andcommitted
feat: Add '--scope-case-insensitive' optional argument
Closes #25
1 parent eb0c25a commit c0a32fa

File tree

9 files changed

+99
-45
lines changed

9 files changed

+99
-45
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ Each component is checked for compliance with the provided or default configurat
4343

4444
If your commit message does not meet the required format, the hook will fail, producing a **report that shows which part of your commit message needs correction**:
4545

46-
<img src="docs/example-output-default-args.jpg" width="800">
46+
<img src="docs/example-output-default-args.png" width="800">
4747

4848
For a custom configuration, the report might look like this:
49-
<img src="docs/example-output-custom-args.jpg" width="800">
49+
<img src="docs/example-output-custom-args.png" width="800">
5050

5151
The hint message suggests that you can preserve your original message and simply edit it in your default editor, without the need to type the whole message again.
5252

@@ -109,6 +109,7 @@ The linter accepts several configurable parameters to tailor commit message vali
109109

110110
- `--types`: Define the types of commits allowed (default: [`change`, `ci`, `docs`, `feat`, `fix`, `refactor`, `remove`, `revert`, `test`]).
111111
- `--scopes`: Specifies a list of allowed scopes. If not defined, all scopes are allowed (restriction is `disabled`).
112+
- `--scope-case-insensitive`: Allows uppercase letters in scope.
112113
- `--subject-min-length`: Set the minimum length for the summary (default: `20`).
113114
- `--subject-max-length`: Set the maximum length for the summary (default: `72`).
114115
- `--body-max-line-length`: Set the maximum line length for the body (default: `100`).

conventional_precommit_linter/hook.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ def check_allowed_types(commit_type: str, args: argparse.Namespace) -> None:
103103
def check_scope(commit_scope: str, args: argparse.Namespace) -> None:
104104
"""Check for scope capitalization and allowed characters"""
105105
regex_scope = r'^[a-z0-9_/.,*-]*$'
106+
if args.scope_case_insensitive:
107+
regex_scope = r'^[a-zA-Z0-9_/.,*-]*$' # adds A-Z to the allowed character set
108+
106109
if commit_scope and not re.match(regex_scope, commit_scope):
107110
rules_output_status['error_scope_capitalization'] = True
108111

@@ -171,16 +174,22 @@ def print_report(commit_type: str, commit_scope: Optional[str], commit_summary:
171174
rule_messages.append(
172175
f"{_get_icon_for_rule(rules_output_status['error_scope_format'])} {_color_blue('(<optional-scope>)')} if used, must be enclosed in parentheses"
173176
)
174-
rule_messages.append(
175-
f"{_get_icon_for_rule(rules_output_status['error_scope_capitalization'])} {_color_blue('(<optional-scope>)')} if used, must be written in lower case without whitespace"
176-
)
177+
178+
if args.scope_case_insensitive:
179+
rule_messages.append(
180+
f"{_get_icon_for_rule(rules_output_status['error_scope_capitalization'])} {_color_blue('(<optional-scope>)')} if used, must not contain whitespace"
181+
)
182+
else:
183+
rule_messages.append(
184+
f"{_get_icon_for_rule(rules_output_status['error_scope_capitalization'])} {_color_blue('(<optional-scope>)')} if used, must be written in lower case without whitespace"
185+
)
177186
if args.scopes:
178187
rule_messages.append(
179188
f"{_get_icon_for_rule(rules_output_status['error_scope_allowed'])} {_color_blue('(<optional-scope>)')} if used, must be one of the following allowed scopes: [{_color_blue(', '.join(args.scopes))}]"
180189
)
181190

182191
# SUMMARY messages
183-
rule_messages.append(f"{_get_icon_for_rule(rules_output_status['error_summary_period'])} {_color_orange('<summary>')} must not end with a period")
192+
rule_messages.append(f"{_get_icon_for_rule(rules_output_status['error_summary_period'])} {_color_orange('<summary>')} must not end with a period '.'")
184193
rule_messages.append(
185194
f"{_get_icon_for_rule(rules_output_status['error_summary_length'])} {_color_orange('<summary>')} must be between {args.subject_min_length} and {args.subject_max_length} characters long"
186195
)
@@ -222,6 +231,7 @@ def parse_args(argv: List[str]) -> argparse.Namespace:
222231
parser.add_argument('--subject-max-length', type=int, default=72, help="Maximum length of the 'Summary'")
223232
parser.add_argument('--body-max-line-length', type=int, default=100, help="Maximum length of the 'Body' line")
224233
parser.add_argument('--summary-uppercase', action='store_true', help="'Summary' must start with an uppercase letter")
234+
parser.add_argument('--scope-case-insensitive', action='store_true', help='Allow uppercase letters in the optional scope.')
225235
parser.add_argument('--allow-breaking', action='store_true', help='Allow exclamation mark in the commit type')
226236
parser.add_argument('input', type=str, help='A file containing a git commit message')
227237
return parser.parse_args(argv)

docs/example-output-custom-args.jpg

-162 KB
Binary file not shown.

docs/example-output-custom-args.png

129 KB
Loading

docs/example-output-default-args.jpg

-152 KB
Binary file not shown.

docs/example-output-default-args.png

130 KB
Loading

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
from conventional_precommit_linter.hook import rules_output_status
44

55

6-
@pytest.fixture(scope='session')
6+
@pytest.fixture()
77
def default_rules_output_status():
88
return rules_output_status.copy()

tests/test_custom_args.py

Lines changed: 80 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import tempfile
2+
from typing import List
3+
from typing import Tuple
24

35
import pytest
46

@@ -7,10 +9,34 @@
79

810
# Default values for the commit message format
911
TYPES = 'change,ci,docs,feat,fix,refactor,remove,revert,fox'
10-
SCOPES = 'bootloader,bt,esp32,esp-rom,examples,examples*storage,rom,wifi'
12+
SCOPES = 'bootloader,bt,Bt,esp32,esp-rom,examples,examples*storage,rom,wifi'
1113
SUBJECT_MIN_LENGTH = 21
1214
SUBJECT_MAX_LENGTH = 53
1315
BODY_MAX_LINE_LENGTH = 107
16+
DEFAULT_ARGV_TUPLE: Tuple[str, ...] = (
17+
'--types',
18+
TYPES,
19+
'--scopes',
20+
SCOPES,
21+
'--subject-min-length',
22+
str(SUBJECT_MIN_LENGTH),
23+
'--subject-max-length',
24+
str(SUBJECT_MAX_LENGTH),
25+
'--body-max-line-length',
26+
str(BODY_MAX_LINE_LENGTH),
27+
'--summary-uppercase',
28+
'--allow-breaking',
29+
)
30+
31+
32+
# Construct the argument list for main with the new constants
33+
def get_argv_list(scope_case_insensitive_arg=False) -> List:
34+
argv_list: List = list(DEFAULT_ARGV_TUPLE)
35+
if scope_case_insensitive_arg:
36+
argv_list.append('--scope-case-insensitive')
37+
38+
print(argv_list)
39+
return argv_list
1440

1541

1642
# Dynamic test naming based on the commit message
@@ -24,109 +50,121 @@ def commit_message_id(commit_message): # pylint: disable=redefined-outer-name
2450
# Expected PASS: Message with scope and body
2551
'feat(bootloader): This is commit message with scope and body\n\nThis is a text of body',
2652
{},
53+
get_argv_list(),
2754
),
2855
(
2956
# Expected PASS: Message with scope, without body
3057
'change(wifi): This is commit message with scope without body',
3158
{},
59+
get_argv_list(),
3260
),
3361
(
3462
# Expected PASS: Message with scope (with hyphen in scope), without body
3563
'change(esp-rom): This is commit message with hyphen in scope',
3664
{},
65+
get_argv_list(),
3766
),
3867
(
3968
# Expected PASS: Message with scope (with asterisk in scope), without body
4069
'change(examples*storage): This is commit message with asterisk in scope',
4170
{},
71+
get_argv_list(),
4272
),
4373
(
4474
# Expected FAIL: Message with not allowed scope and body
4575
'feat(tomas): This is commit message with scope and body\n\nThis is a text of body',
4676
{'error_scope_allowed': True},
77+
get_argv_list(),
4778
),
4879
(
4980
# Expected FAIL: Message with scope (with comma in scope), without body
5081
'change(examples,storage): This is commit message with comma in scope',
5182
{'error_scope_allowed': True},
83+
get_argv_list(),
5284
),
5385
(
5486
# Expected PASS: Message with scope (with slash in scope), without body
5587
'change(examples/storage): This is commit message with slash in scope',
5688
{'error_scope_allowed': True},
89+
get_argv_list(),
5790
),
5891
(
5992
# Expected PASS: Message without scope, with body
6093
"change: This is commit message without scope with body\n\nThis is a text of body\n# Please enter the commit message for your changes. Lines starting\n# with '#' will be ignored, and an empty message aborts the commit.\n#",
6194
{},
95+
get_argv_list(),
6296
),
6397
(
6498
# Expected PASS: Message without scope, without body
6599
'change: This is commit message without scope and body',
66100
{},
101+
get_argv_list(),
67102
),
68103
(
69104
# Expected PASS: Test of additional types
70105
'fox(esp32): Testing additional types\n\nThis is a text of body',
71106
{},
107+
get_argv_list(),
72108
),
73109
(
74110
# Expected PASS: 'body' line longer (custom arg 107 chars)
75111
'fix(bt): Update database schemas\n\nUpdating the database schema to include fields and user profile preferences, cleaning up unnecessary calls',
76112
{},
113+
get_argv_list(),
77114
),
78115
(
79116
# Expected PASS: Message without scope with exclamation mark
80117
'change!: This is commit with exclamation mark',
81118
{},
119+
get_argv_list(),
82120
),
83121
(
84122
# Expected PASS: Message with scope with exclamation mark
85123
'change(rom)!: This is commit with exclamation mark',
86124
{},
125+
get_argv_list(),
87126
),
88127
(
89128
# Expected FAIL: Message with scope with 2 exclamation marks
90129
'change(rom)!!: This is commit message with 2 exclamations',
91130
{'error_type': True},
131+
get_argv_list(),
92132
),
93133
(
94134
# Expected FAIL: missing colon between 'type' (and 'scope') and 'summary'
95135
'change this is commit message without body',
96136
{'missing_colon': True},
137+
get_argv_list(),
97138
),
98139
(
99140
# Expected FAIL: empty commit message
100141
' \n\n \n',
101142
{'empty_message': True},
143+
get_argv_list(),
102144
),
103145
(
104146
# Expected FAIL: 'summary' too short
105147
'fix: Fix bug',
106148
{'error_summary_length': True},
149+
get_argv_list(),
107150
),
108151
(
109152
# Expected FAIL: 'summary' too long
110153
'change(rom): Refactor authentication flow for enhanced security measures',
111154
{'error_summary_length': True},
155+
get_argv_list(),
112156
),
113157
(
114158
# Expected FAIL: 'summary' ends with period
115159
'change(rom): Fixed the another bug.',
116160
{'error_summary_period': True},
161+
get_argv_list(),
117162
),
118163
(
119164
# Expected FAIL: 'summary' starts with lowercase
120165
'change(rom): this message starts with lowercase',
121166
{'error_summary_capitalization': True},
122-
),
123-
(
124-
# Expected FAIL: uppercase in 'scope', with body
125-
'change(Bt): Added new feature with change\n\nThis feature adds functionality',
126-
{
127-
'error_scope_capitalization': True,
128-
'error_scope_allowed': True,
129-
},
167+
get_argv_list(),
130168
),
131169
(
132170
# Expected FAIL: uppercase in 'scope', no body
@@ -136,41 +174,49 @@ def commit_message_id(commit_message): # pylint: disable=redefined-outer-name
136174
'error_summary_length': True,
137175
'error_scope_allowed': True,
138176
},
177+
get_argv_list(),
139178
),
140179
(
141180
# Expected FAIL: not allowed 'type' with scope and body
142181
'delete(bt): Added new feature with change\n\nThis feature adds functionality',
143182
{'error_type': True},
183+
get_argv_list(),
144184
),
145185
(
146186
# Expected FAIL: not allowed 'type' without scope and without body
147187
'delete: Added new feature with change',
148188
{'error_type': True},
189+
get_argv_list(),
149190
),
150191
(
151192
# Expected FAIL: not allowed 'type' without scope and without body
152193
'wip: Added new feature with change',
153194
{'error_type': True},
195+
get_argv_list(),
154196
),
155197
(
156198
# Expected FAIL: not allowed 'type' (type starts with uppercase)
157199
'Fix(bt): Added new feature with change\n\nThis feature adds functionality',
158200
{'error_type': True},
201+
get_argv_list(),
159202
),
160203
(
161204
# Expected FAIL: missing blank line between 'summary' and 'body'
162205
'change: Added new feature with change\nThis feature adds functionality',
163206
{'error_body_format': True},
207+
get_argv_list(),
164208
),
165209
(
166210
# Expected FAIL: 'body' line too long
167211
'fix(bt): Update database schemas\n\nUpdating the database schema to include new fields and user profile preferences, cleaning up unnecessary calls',
168212
{'error_body_length': True},
213+
get_argv_list(),
169214
),
170215
(
171216
# Expected FAIL: 'scope' missing parentheses
172217
'fix(bt: Update database schemas\n\nUpdating the database schema to include new fields.',
173218
{'error_scope_format': True},
219+
get_argv_list(),
174220
),
175221
(
176222
# Expected FAIL: allowed special 'type', uppercase in 'scope' required, 'summary' too long, 'summary' ends with period
@@ -182,6 +228,27 @@ def commit_message_id(commit_message): # pylint: disable=redefined-outer-name
182228
'error_summary_length': True,
183229
'error_summary_period': True,
184230
},
231+
get_argv_list(),
232+
),
233+
(
234+
# Expected FAIL: uppercase in 'scope'
235+
'change(Bt): Added new feature with change\n\nThis feature adds functionality',
236+
{
237+
'error_scope_capitalization': True,
238+
},
239+
get_argv_list(),
240+
),
241+
(
242+
# Expected Pass: Allowed uppercase in 'scope'.
243+
'change(Bt): Added new feature with change\n\nThis feature adds functionality',
244+
{},
245+
get_argv_list(scope_case_insensitive_arg=True),
246+
),
247+
(
248+
# Expected Fail: Summary ending with a period '.'
249+
'change(Bt): Added new feature with change.',
250+
{'error_summary_period': True},
251+
get_argv_list(scope_case_insensitive_arg=True),
185252
),
186253
],
187254
# Use the commit message to generate IDs for each test case
@@ -190,11 +257,11 @@ def commit_message_id(commit_message): # pylint: disable=redefined-outer-name
190257
def commit_message(request, default_rules_output_status):
191258
# Combine the default dictionary with the test-specific dictionary
192259
combined_output = {**default_rules_output_status, **request.param[1]}
193-
return request.param[0], combined_output
260+
return request.param[0], combined_output, request.param[2]
194261

195262

196263
def test_commit_message_with_args(commit_message, default_rules_output_status): # pylint: disable=redefined-outer-name
197-
message_text, expected_output = commit_message
264+
message_text, expected_output, cli_arguments = commit_message
198265

199266
# Reset rules_output_status to its default state before each test case
200267
rules_output_status.clear()
@@ -205,30 +272,10 @@ def test_commit_message_with_args(commit_message, default_rules_output_status):
205272
temp.write(message_text)
206273
temp_file_name = temp.name
207274

208-
# Construct the argument list for main with the new constants
209-
argv = [
210-
'--types',
211-
TYPES,
212-
'--scopes',
213-
SCOPES,
214-
'--subject-min-length',
215-
str(SUBJECT_MIN_LENGTH),
216-
'--subject-max-length',
217-
str(SUBJECT_MAX_LENGTH),
218-
'--body-max-line-length',
219-
str(BODY_MAX_LINE_LENGTH),
220-
'--summary-uppercase',
221-
'--allow-breaking',
222-
temp_file_name,
223-
]
275+
cli_arguments.append(temp_file_name)
224276

225277
# Run the main function of your script with the temporary file and arguments
226-
try:
227-
main(argv)
228-
229-
finally:
230-
# Clean up the temporary file after the test
231-
temp.close()
278+
main(cli_arguments)
232279

233280
# Retrieve the actual rules_output_status after running the main function
234281
actual_output = rules_output_status

tests/test_default_args.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,7 @@ def test_commit_message(commit_message, default_rules_output_status): # pylint:
186186
temp.write(message_text)
187187
temp_file_name = temp.name
188188

189-
# Run the main function of your script with the temporary file
190-
try:
191-
main([temp_file_name]) # Pass the file name as a positional argument
192-
finally:
193-
temp.close() # Clean up the temporary file after the test
189+
main([temp_file_name]) # Pass the file name as a positional argument
194190

195191
# Retrieve the actual rules_output_status after running the main function
196192
actual_output = rules_output_status

0 commit comments

Comments
 (0)