git-commit-guard 0.4.0__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- git_commit_guard-0.6.0/.github/workflows/lint-commits.yml +44 -0
- git_commit_guard-0.6.0/.github/workflows/test.yml +27 -0
- {git_commit_guard-0.4.0 → git_commit_guard-0.6.0}/.gitignore +1 -0
- git_commit_guard-0.6.0/.pre-commit-hooks.yaml +12 -0
- {git_commit_guard-0.4.0 → git_commit_guard-0.6.0}/PKG-INFO +50 -2
- {git_commit_guard-0.4.0 → git_commit_guard-0.6.0}/README.md +49 -1
- {git_commit_guard-0.4.0 → git_commit_guard-0.6.0}/pyproject.toml +6 -0
- {git_commit_guard-0.4.0 → git_commit_guard-0.6.0}/src/git_commit_guard/__init__.py +16 -110
- git_commit_guard-0.6.0/tests/__init__.py +0 -0
- git_commit_guard-0.6.0/tests/test_git_commit_guard.py +464 -0
- {git_commit_guard-0.4.0 → git_commit_guard-0.6.0}/uv.lock +162 -0
- {git_commit_guard-0.4.0 → git_commit_guard-0.6.0}/.python-version +0 -0
- {git_commit_guard-0.4.0 → git_commit_guard-0.6.0}/LICENSE +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Lint commits
|
|
3
|
+
on: # yamllint disable-line rule:truthy
|
|
4
|
+
pull_request:
|
|
5
|
+
permissions:
|
|
6
|
+
contents: read
|
|
7
|
+
jobs:
|
|
8
|
+
lint-commits:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
contents: write
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout code
|
|
14
|
+
# yamllint disable-line rule:line-length
|
|
15
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
16
|
+
with:
|
|
17
|
+
persist-credentials: false
|
|
18
|
+
fetch-depth: 0
|
|
19
|
+
- name: Install Python
|
|
20
|
+
# yamllint disable-line rule:line-length
|
|
21
|
+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
22
|
+
with:
|
|
23
|
+
python-version: '3.12'
|
|
24
|
+
- name: Install uv
|
|
25
|
+
# yamllint disable-line rule:line-length
|
|
26
|
+
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
|
27
|
+
- name: Install commit-guard
|
|
28
|
+
run: uv pip install --system .
|
|
29
|
+
- name: Cache NLTK data
|
|
30
|
+
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
|
31
|
+
with:
|
|
32
|
+
path: ~/nltk_data
|
|
33
|
+
key: nltk-averaged-perceptron-tagger-punkt
|
|
34
|
+
- name: Lint commits
|
|
35
|
+
env:
|
|
36
|
+
BASE_REF: ${{ github.base_ref }}
|
|
37
|
+
run: |-
|
|
38
|
+
commits=$(git log --no-merges --format="%H" "origin/$BASE_REF"..HEAD)
|
|
39
|
+
failed=0
|
|
40
|
+
for sha in $commits; do
|
|
41
|
+
echo "--- checking $sha ---"
|
|
42
|
+
commit-guard --disable signature "$sha" || failed=1
|
|
43
|
+
done
|
|
44
|
+
exit $failed
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Test
|
|
3
|
+
on: # yamllint disable-line rule:truthy
|
|
4
|
+
pull_request:
|
|
5
|
+
permissions:
|
|
6
|
+
contents: read
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- name: Checkout code
|
|
12
|
+
# yamllint disable-line rule:line-length
|
|
13
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
14
|
+
with:
|
|
15
|
+
persist-credentials: false
|
|
16
|
+
- name: Install uv
|
|
17
|
+
# yamllint disable-line rule:line-length
|
|
18
|
+
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
|
19
|
+
- name: Cache NLTK data
|
|
20
|
+
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
|
21
|
+
with:
|
|
22
|
+
path: ~/nltk_data
|
|
23
|
+
key: nltk-averaged-perceptron-tagger-punkt
|
|
24
|
+
- name: Run tests with coverage
|
|
25
|
+
run: |-
|
|
26
|
+
uv run --dev pytest \
|
|
27
|
+
tests/ --cov=git_commit_guard --cov-report=term-missing
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
- id: commit-guard
|
|
3
|
+
name: commit-guard
|
|
4
|
+
description: >-
|
|
5
|
+
Opinionated conventional commit message linter
|
|
6
|
+
with imperative mood detection
|
|
7
|
+
entry: commit-guard --message-file
|
|
8
|
+
language: python
|
|
9
|
+
stages: [commit-msg]
|
|
10
|
+
always_run: true
|
|
11
|
+
pass_filenames: true
|
|
12
|
+
minimum_pre_commit_version: 2.8.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-commit-guard
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Opinionated conventional commit message linter with imperative mood detection
|
|
5
5
|
Project-URL: Homepage, https://github.com/benner/commit-guard
|
|
6
6
|
Project-URL: Repository, https://github.com/benner/commit-guard
|
|
@@ -27,11 +27,33 @@ Unlike regular expression only tools, commit-guard uses
|
|
|
27
27
|
NLP (nltk POS tagging) to verify that commit descriptions start with an
|
|
28
28
|
imperative verb.
|
|
29
29
|
|
|
30
|
+
## Example
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
$ commit-guard
|
|
34
|
+
✗ subject does not match 'type(scope): description': Merge pull request #5 from fix/branch
|
|
35
|
+
✗ missing 'Signed-off-by' trailer
|
|
36
|
+
✗ commit is not signed (GPG/SSH)
|
|
37
|
+
```
|
|
38
|
+
|
|
30
39
|
## Installation
|
|
31
40
|
|
|
41
|
+
From PyPI:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv tool install git-commit-guard
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
or:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pipx install git-commit-guard
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
From a local clone:
|
|
54
|
+
|
|
32
55
|
```bash
|
|
33
56
|
uv tool install -e .
|
|
34
|
-
commit-guard
|
|
35
57
|
```
|
|
36
58
|
|
|
37
59
|
During development:
|
|
@@ -92,6 +114,32 @@ git rev-list --no-merges origin/main..HEAD | while read -r rev; do
|
|
|
92
114
|
done
|
|
93
115
|
```
|
|
94
116
|
|
|
117
|
+
### pre-commit
|
|
118
|
+
|
|
119
|
+
Add to your `.pre-commit-config.yaml`:
|
|
120
|
+
|
|
121
|
+
```yaml
|
|
122
|
+
---
|
|
123
|
+
repos:
|
|
124
|
+
- repo: https://github.com/benner/commit-guard
|
|
125
|
+
rev: v0.1.0
|
|
126
|
+
hooks:
|
|
127
|
+
- id: commit-guard
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Install the hook:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
pre-commit install --hook-type commit-msg
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
To selectively enable or disable checks, pass `args`:
|
|
137
|
+
|
|
138
|
+
```yaml
|
|
139
|
+
- id: commit-guard
|
|
140
|
+
args: ["--enable", "subject,imperative"]
|
|
141
|
+
```
|
|
142
|
+
|
|
95
143
|
## Imperative mood detection
|
|
96
144
|
|
|
97
145
|
commit-guard combines two strategies to detect non-imperative descriptions:
|
|
@@ -6,11 +6,33 @@ Unlike regular expression only tools, commit-guard uses
|
|
|
6
6
|
NLP (nltk POS tagging) to verify that commit descriptions start with an
|
|
7
7
|
imperative verb.
|
|
8
8
|
|
|
9
|
+
## Example
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
$ commit-guard
|
|
13
|
+
✗ subject does not match 'type(scope): description': Merge pull request #5 from fix/branch
|
|
14
|
+
✗ missing 'Signed-off-by' trailer
|
|
15
|
+
✗ commit is not signed (GPG/SSH)
|
|
16
|
+
```
|
|
17
|
+
|
|
9
18
|
## Installation
|
|
10
19
|
|
|
20
|
+
From PyPI:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv tool install git-commit-guard
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
or:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pipx install git-commit-guard
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
From a local clone:
|
|
33
|
+
|
|
11
34
|
```bash
|
|
12
35
|
uv tool install -e .
|
|
13
|
-
commit-guard
|
|
14
36
|
```
|
|
15
37
|
|
|
16
38
|
During development:
|
|
@@ -71,6 +93,32 @@ git rev-list --no-merges origin/main..HEAD | while read -r rev; do
|
|
|
71
93
|
done
|
|
72
94
|
```
|
|
73
95
|
|
|
96
|
+
### pre-commit
|
|
97
|
+
|
|
98
|
+
Add to your `.pre-commit-config.yaml`:
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
---
|
|
102
|
+
repos:
|
|
103
|
+
- repo: https://github.com/benner/commit-guard
|
|
104
|
+
rev: v0.1.0
|
|
105
|
+
hooks:
|
|
106
|
+
- id: commit-guard
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Install the hook:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pre-commit install --hook-type commit-msg
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
To selectively enable or disable checks, pass `args`:
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
- id: commit-guard
|
|
119
|
+
args: ["--enable", "subject,imperative"]
|
|
120
|
+
```
|
|
121
|
+
|
|
74
122
|
## Imperative mood detection
|
|
75
123
|
|
|
76
124
|
commit-guard combines two strategies to detect non-imperative descriptions:
|
|
@@ -7,6 +7,7 @@ from enum import StrEnum
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
9
|
import nltk
|
|
10
|
+
from nltk.corpus import wordnet
|
|
10
11
|
|
|
11
12
|
TYPES = frozenset(
|
|
12
13
|
{
|
|
@@ -24,112 +25,7 @@ TYPES = frozenset(
|
|
|
24
25
|
}
|
|
25
26
|
)
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
{
|
|
29
|
-
"add",
|
|
30
|
-
"allow",
|
|
31
|
-
"apply",
|
|
32
|
-
"avoid",
|
|
33
|
-
"bump",
|
|
34
|
-
"change",
|
|
35
|
-
"check",
|
|
36
|
-
"clean",
|
|
37
|
-
"clear",
|
|
38
|
-
"configure",
|
|
39
|
-
"correct",
|
|
40
|
-
"create",
|
|
41
|
-
"catch",
|
|
42
|
-
"define",
|
|
43
|
-
"delete",
|
|
44
|
-
"deprecate",
|
|
45
|
-
"disable",
|
|
46
|
-
"document",
|
|
47
|
-
"drop",
|
|
48
|
-
"enable",
|
|
49
|
-
"enforce",
|
|
50
|
-
"ensure",
|
|
51
|
-
"exclude",
|
|
52
|
-
"export",
|
|
53
|
-
"extend",
|
|
54
|
-
"extract",
|
|
55
|
-
"fix",
|
|
56
|
-
"format",
|
|
57
|
-
"guard",
|
|
58
|
-
"handle",
|
|
59
|
-
"ignore",
|
|
60
|
-
"implement",
|
|
61
|
-
"import",
|
|
62
|
-
"improve",
|
|
63
|
-
"include",
|
|
64
|
-
"increase",
|
|
65
|
-
"initialize",
|
|
66
|
-
"inline",
|
|
67
|
-
"install",
|
|
68
|
-
"introduce",
|
|
69
|
-
"invalidate",
|
|
70
|
-
"limit",
|
|
71
|
-
"log",
|
|
72
|
-
"make",
|
|
73
|
-
"mark",
|
|
74
|
-
"merge",
|
|
75
|
-
"migrate",
|
|
76
|
-
"move",
|
|
77
|
-
"normalize",
|
|
78
|
-
"open",
|
|
79
|
-
"optimize",
|
|
80
|
-
"override",
|
|
81
|
-
"parse",
|
|
82
|
-
"pass",
|
|
83
|
-
"patch",
|
|
84
|
-
"pin",
|
|
85
|
-
"port",
|
|
86
|
-
"prevent",
|
|
87
|
-
"print",
|
|
88
|
-
"provide",
|
|
89
|
-
"publish",
|
|
90
|
-
"reduce",
|
|
91
|
-
"refactor",
|
|
92
|
-
"release",
|
|
93
|
-
"remove",
|
|
94
|
-
"rename",
|
|
95
|
-
"reorganize",
|
|
96
|
-
"replace",
|
|
97
|
-
"require",
|
|
98
|
-
"reset",
|
|
99
|
-
"resolve",
|
|
100
|
-
"restore",
|
|
101
|
-
"restrict",
|
|
102
|
-
"return",
|
|
103
|
-
"revert",
|
|
104
|
-
"report",
|
|
105
|
-
"run",
|
|
106
|
-
"separate",
|
|
107
|
-
"set",
|
|
108
|
-
"show",
|
|
109
|
-
"simplify",
|
|
110
|
-
"skip",
|
|
111
|
-
"sort",
|
|
112
|
-
"split",
|
|
113
|
-
"start",
|
|
114
|
-
"store",
|
|
115
|
-
"stop",
|
|
116
|
-
"support",
|
|
117
|
-
"suppress",
|
|
118
|
-
"switch",
|
|
119
|
-
"sync",
|
|
120
|
-
"track",
|
|
121
|
-
"trim",
|
|
122
|
-
"unify",
|
|
123
|
-
"update",
|
|
124
|
-
"upgrade",
|
|
125
|
-
"use",
|
|
126
|
-
"wait",
|
|
127
|
-
"validate",
|
|
128
|
-
"vendor",
|
|
129
|
-
"verify",
|
|
130
|
-
"wrap",
|
|
131
|
-
}
|
|
132
|
-
)
|
|
28
|
+
_NON_IMPERATIVE_SUFFIX_RE = re.compile(r"(?:ing|ed)$")
|
|
133
29
|
|
|
134
30
|
SUBJECT_RE = re.compile(
|
|
135
31
|
r"^(?P<type>\w+)(?:\((?P<scope>[^)]+)\))?!?:\s+(?P<desc>.+)$",
|
|
@@ -189,6 +85,7 @@ class Result:
|
|
|
189
85
|
def _ensure_nltk_data():
|
|
190
86
|
_download_if_missing("taggers/averaged_perceptron_tagger_eng")
|
|
191
87
|
_download_if_missing("tokenizers/punkt_tab")
|
|
88
|
+
_download_if_missing("corpora/wordnet")
|
|
192
89
|
|
|
193
90
|
|
|
194
91
|
def _download_if_missing(resource):
|
|
@@ -228,12 +125,21 @@ def check_imperative(desc, result):
|
|
|
228
125
|
if not tokens:
|
|
229
126
|
return
|
|
230
127
|
first = tokens[0]
|
|
231
|
-
if first
|
|
128
|
+
if _NON_IMPERATIVE_SUFFIX_RE.search(first):
|
|
129
|
+
result.error(f"expected imperative verb, got '{first}' (non-imperative suffix)")
|
|
130
|
+
return
|
|
131
|
+
base = wordnet.morphy(first, wordnet.VERB)
|
|
132
|
+
if base is not None and base != first:
|
|
133
|
+
result.error(
|
|
134
|
+
f"expected imperative verb, got '{first}' (inflected form of '{base}')"
|
|
135
|
+
)
|
|
232
136
|
return
|
|
233
|
-
tagged = nltk.pos_tag(tokens)
|
|
234
|
-
if tagged[
|
|
137
|
+
tagged = nltk.pos_tag(["to", *tokens])
|
|
138
|
+
if tagged[1][1] != "VB":
|
|
139
|
+
if wordnet.morphy(first, wordnet.VERB) == first:
|
|
140
|
+
return
|
|
235
141
|
result.error(
|
|
236
|
-
f"expected imperative verb, got '{tagged[
|
|
142
|
+
f"expected imperative verb, got '{tagged[1][0]}' (POS={tagged[1][1]})",
|
|
237
143
|
)
|
|
238
144
|
|
|
239
145
|
|
|
File without changes
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from argparse import ArgumentParser
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from git_commit_guard import (
|
|
7
|
+
Result,
|
|
8
|
+
_download_if_missing,
|
|
9
|
+
_ensure_nltk_data,
|
|
10
|
+
_get_message,
|
|
11
|
+
_parse_checks,
|
|
12
|
+
_report,
|
|
13
|
+
_strip_comments,
|
|
14
|
+
check_body,
|
|
15
|
+
check_imperative,
|
|
16
|
+
check_signature,
|
|
17
|
+
check_signed_off,
|
|
18
|
+
check_subject,
|
|
19
|
+
main,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# ruff: noqa: S101 # Use of `assert` detected
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture(scope="session", autouse=True)
|
|
26
|
+
def nltk_data():
|
|
27
|
+
_ensure_nltk_data()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestResult:
|
|
31
|
+
def test_ok_when_empty(self):
|
|
32
|
+
assert Result().ok
|
|
33
|
+
|
|
34
|
+
def test_not_ok_with_error(self):
|
|
35
|
+
r = Result()
|
|
36
|
+
r.error("bad")
|
|
37
|
+
assert not r.ok
|
|
38
|
+
|
|
39
|
+
def test_ok_with_only_warn(self):
|
|
40
|
+
r = Result()
|
|
41
|
+
r.warn("hmm")
|
|
42
|
+
assert r.ok
|
|
43
|
+
|
|
44
|
+
def test_ok_with_only_info(self):
|
|
45
|
+
r = Result()
|
|
46
|
+
r.info("fyi")
|
|
47
|
+
assert r.ok
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TestStripComments:
|
|
51
|
+
def test_removes_comment_lines(self):
|
|
52
|
+
assert _strip_comments("# comment\nfoo") == "foo"
|
|
53
|
+
|
|
54
|
+
def test_keeps_non_comment_lines(self):
|
|
55
|
+
assert _strip_comments("foo\nbar") == "foo\nbar"
|
|
56
|
+
|
|
57
|
+
def test_removes_indented_comments(self):
|
|
58
|
+
assert _strip_comments(" # indented\nfoo") == "foo"
|
|
59
|
+
|
|
60
|
+
def test_empty_string(self):
|
|
61
|
+
assert _strip_comments("") == ""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TestCheckSubject:
|
|
65
|
+
def test_valid_simple(self):
|
|
66
|
+
r = Result()
|
|
67
|
+
desc = check_subject("fix: add token refresh", r)
|
|
68
|
+
assert desc == "add token refresh"
|
|
69
|
+
assert r.ok
|
|
70
|
+
|
|
71
|
+
def test_valid_with_scope(self):
|
|
72
|
+
r = Result()
|
|
73
|
+
desc = check_subject("feat(auth): add login", r)
|
|
74
|
+
assert desc == "add login"
|
|
75
|
+
assert r.ok
|
|
76
|
+
|
|
77
|
+
def test_valid_breaking_change(self):
|
|
78
|
+
r = Result()
|
|
79
|
+
check_subject("feat!: drop v1 support", r)
|
|
80
|
+
assert r.ok
|
|
81
|
+
|
|
82
|
+
def test_valid_breaking_change_with_scope(self):
|
|
83
|
+
r = Result()
|
|
84
|
+
check_subject("feat(api)!: drop v1", r)
|
|
85
|
+
assert r.ok
|
|
86
|
+
|
|
87
|
+
def test_invalid_format(self):
|
|
88
|
+
r = Result()
|
|
89
|
+
desc = check_subject("not a commit", r)
|
|
90
|
+
assert desc is None
|
|
91
|
+
assert not r.ok
|
|
92
|
+
|
|
93
|
+
def test_unknown_type(self):
|
|
94
|
+
r = Result()
|
|
95
|
+
check_subject("unknown: add thing", r)
|
|
96
|
+
assert not r.ok
|
|
97
|
+
|
|
98
|
+
def test_uppercase_description(self):
|
|
99
|
+
r = Result()
|
|
100
|
+
check_subject("fix: Add token", r)
|
|
101
|
+
assert not r.ok
|
|
102
|
+
|
|
103
|
+
def test_trailing_period(self):
|
|
104
|
+
r = Result()
|
|
105
|
+
check_subject("fix: add token.", r)
|
|
106
|
+
assert not r.ok
|
|
107
|
+
|
|
108
|
+
def test_subject_too_long(self):
|
|
109
|
+
r = Result()
|
|
110
|
+
check_subject("fix: " + "a" * 68, r) # 73 chars total
|
|
111
|
+
assert not r.ok
|
|
112
|
+
|
|
113
|
+
def test_subject_at_max_length(self):
|
|
114
|
+
r = Result()
|
|
115
|
+
check_subject("fix: " + "a" * 67, r) # exactly 72 chars
|
|
116
|
+
assert r.ok
|
|
117
|
+
|
|
118
|
+
@pytest.mark.parametrize(
|
|
119
|
+
"type_",
|
|
120
|
+
[
|
|
121
|
+
"feat",
|
|
122
|
+
"fix",
|
|
123
|
+
"docs",
|
|
124
|
+
"style",
|
|
125
|
+
"refactor",
|
|
126
|
+
"perf",
|
|
127
|
+
"test",
|
|
128
|
+
"build",
|
|
129
|
+
"ci",
|
|
130
|
+
"chore",
|
|
131
|
+
"revert",
|
|
132
|
+
],
|
|
133
|
+
)
|
|
134
|
+
def test_all_valid_types(self, type_):
|
|
135
|
+
r = Result()
|
|
136
|
+
check_subject(f"{type_}: add thing", r)
|
|
137
|
+
assert r.ok
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class TestCheckBody:
|
|
141
|
+
def test_subject_only(self):
|
|
142
|
+
r = Result()
|
|
143
|
+
check_body(["fix: add thing"], r)
|
|
144
|
+
assert not r.ok
|
|
145
|
+
|
|
146
|
+
def test_subject_and_blank_only(self):
|
|
147
|
+
r = Result()
|
|
148
|
+
check_body(["fix: add thing", ""], r)
|
|
149
|
+
assert not r.ok
|
|
150
|
+
|
|
151
|
+
def test_valid_body(self):
|
|
152
|
+
r = Result()
|
|
153
|
+
check_body(["fix: add thing", "", "body text here"], r)
|
|
154
|
+
assert r.ok
|
|
155
|
+
|
|
156
|
+
def test_missing_blank_line(self):
|
|
157
|
+
r = Result()
|
|
158
|
+
check_body(["fix: add thing", "body text", "more"], r)
|
|
159
|
+
assert not r.ok
|
|
160
|
+
|
|
161
|
+
def test_blank_body_content(self):
|
|
162
|
+
r = Result()
|
|
163
|
+
check_body(["fix: add thing", "", " "], r)
|
|
164
|
+
assert not r.ok
|
|
165
|
+
|
|
166
|
+
def test_multiline_body(self):
|
|
167
|
+
r = Result()
|
|
168
|
+
check_body(["fix: add thing", "", "line one", "line two"], r)
|
|
169
|
+
assert r.ok
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class TestCheckSignedOff:
|
|
173
|
+
def test_valid(self):
|
|
174
|
+
r = Result()
|
|
175
|
+
check_signed_off(
|
|
176
|
+
"fix: add thing\n\nbody\n\nSigned-off-by: John Doe <john@example.com>",
|
|
177
|
+
r,
|
|
178
|
+
)
|
|
179
|
+
assert r.ok
|
|
180
|
+
|
|
181
|
+
def test_missing(self):
|
|
182
|
+
r = Result()
|
|
183
|
+
check_signed_off("fix: add thing\n\nbody", r)
|
|
184
|
+
assert not r.ok
|
|
185
|
+
|
|
186
|
+
def test_malformed_no_email(self):
|
|
187
|
+
r = Result()
|
|
188
|
+
check_signed_off("fix: add thing\n\nSigned-off-by: John Doe", r)
|
|
189
|
+
assert not r.ok
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class TestCheckImperative:
|
|
193
|
+
def test_imperative_verb_passes(self):
|
|
194
|
+
r = Result()
|
|
195
|
+
check_imperative("add token refresh", r)
|
|
196
|
+
assert r.ok
|
|
197
|
+
|
|
198
|
+
def test_ed_suffix_fails(self):
|
|
199
|
+
r = Result()
|
|
200
|
+
check_imperative("added token refresh", r)
|
|
201
|
+
assert not r.ok
|
|
202
|
+
|
|
203
|
+
def test_ing_suffix_fails(self):
|
|
204
|
+
r = Result()
|
|
205
|
+
check_imperative("adding token refresh", r)
|
|
206
|
+
assert not r.ok
|
|
207
|
+
|
|
208
|
+
def test_third_person_fails(self):
|
|
209
|
+
r = Result()
|
|
210
|
+
check_imperative("adds token refresh", r)
|
|
211
|
+
assert not r.ok
|
|
212
|
+
|
|
213
|
+
def test_third_person_es_suffix_fails(self):
|
|
214
|
+
r = Result()
|
|
215
|
+
check_imperative("fixes the bug", r)
|
|
216
|
+
assert not r.ok
|
|
217
|
+
|
|
218
|
+
def test_non_whitelist_imperative_passes(self):
|
|
219
|
+
r = Result()
|
|
220
|
+
check_imperative("refactor authentication module", r)
|
|
221
|
+
assert r.ok
|
|
222
|
+
|
|
223
|
+
def test_write_imperative_passes(self):
|
|
224
|
+
r = Result()
|
|
225
|
+
check_imperative("write unit tests", r)
|
|
226
|
+
assert r.ok
|
|
227
|
+
|
|
228
|
+
def test_tagger_misclassified_verb_passes(self):
|
|
229
|
+
# 'disable' is tagged non-VB by the tagger but wordnet confirms it as a verb
|
|
230
|
+
r = Result()
|
|
231
|
+
check_imperative("disable feature flag", r)
|
|
232
|
+
assert r.ok
|
|
233
|
+
|
|
234
|
+
def test_refactor_passes(self):
|
|
235
|
+
r = Result()
|
|
236
|
+
check_imperative("refactor authentication module", r)
|
|
237
|
+
assert r.ok
|
|
238
|
+
|
|
239
|
+
def test_vendor_passes(self):
|
|
240
|
+
r = Result()
|
|
241
|
+
check_imperative("vendor third-party libs", r)
|
|
242
|
+
assert r.ok
|
|
243
|
+
|
|
244
|
+
def test_configure_passes(self):
|
|
245
|
+
r = Result()
|
|
246
|
+
check_imperative("configure logging pipeline", r)
|
|
247
|
+
assert r.ok
|
|
248
|
+
|
|
249
|
+
def test_empty_desc_passes(self):
|
|
250
|
+
r = Result()
|
|
251
|
+
check_imperative("", r)
|
|
252
|
+
assert r.ok
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class TestDownloadIfMissing:
|
|
256
|
+
def test_skips_download_when_present(self):
|
|
257
|
+
with (
|
|
258
|
+
patch("git_commit_guard.nltk.data.find"),
|
|
259
|
+
patch("git_commit_guard.nltk.download") as mock_dl,
|
|
260
|
+
):
|
|
261
|
+
_download_if_missing("tokenizers/punkt_tab")
|
|
262
|
+
mock_dl.assert_not_called()
|
|
263
|
+
|
|
264
|
+
def test_downloads_when_missing(self):
|
|
265
|
+
with (
|
|
266
|
+
patch("git_commit_guard.nltk.data.find", side_effect=LookupError),
|
|
267
|
+
patch("git_commit_guard.nltk.download") as mock_dl,
|
|
268
|
+
):
|
|
269
|
+
_download_if_missing("tokenizers/punkt_tab")
|
|
270
|
+
mock_dl.assert_called_once_with("punkt_tab", quiet=True)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class TestCheckSignature:
|
|
274
|
+
def test_unsigned_commit(self):
|
|
275
|
+
r = Result()
|
|
276
|
+
proc = MagicMock(returncode=1)
|
|
277
|
+
with patch("git_commit_guard.subprocess.run", return_value=proc):
|
|
278
|
+
check_signature("abc123", r)
|
|
279
|
+
assert not r.ok
|
|
280
|
+
|
|
281
|
+
def test_gpg_signed_commit(self):
|
|
282
|
+
r = Result()
|
|
283
|
+
proc = MagicMock(returncode=0, stderr="gpg signature verified")
|
|
284
|
+
with patch("git_commit_guard.subprocess.run", return_value=proc):
|
|
285
|
+
check_signature("abc123", r)
|
|
286
|
+
assert r.ok
|
|
287
|
+
assert any("GPG" in msg for _, msg in r.errors)
|
|
288
|
+
|
|
289
|
+
def test_ssh_signed_commit(self):
|
|
290
|
+
r = Result()
|
|
291
|
+
proc = MagicMock(returncode=0, stderr="Good ssh signature")
|
|
292
|
+
with patch("git_commit_guard.subprocess.run", return_value=proc):
|
|
293
|
+
check_signature("abc123", r)
|
|
294
|
+
assert r.ok
|
|
295
|
+
assert any("SSH" in msg for _, msg in r.errors)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class TestGetMessage:
|
|
299
|
+
def test_success(self):
|
|
300
|
+
with patch(
|
|
301
|
+
"git_commit_guard.subprocess.check_output",
|
|
302
|
+
return_value="fix: add thing\n\n",
|
|
303
|
+
):
|
|
304
|
+
assert _get_message("abc123") == "fix: add thing"
|
|
305
|
+
|
|
306
|
+
def test_unknown_revision(self):
|
|
307
|
+
err = subprocess.CalledProcessError(128, "git")
|
|
308
|
+
err.stderr = "fatal: unknown revision 'abc'"
|
|
309
|
+
with (
|
|
310
|
+
patch("git_commit_guard.subprocess.check_output", side_effect=err),
|
|
311
|
+
pytest.raises(SystemExit, match="no commits yet"),
|
|
312
|
+
):
|
|
313
|
+
_get_message("abc")
|
|
314
|
+
|
|
315
|
+
def test_ambiguous_argument(self):
|
|
316
|
+
err = subprocess.CalledProcessError(128, "git")
|
|
317
|
+
err.stderr = "fatal: ambiguous argument 'HEAD'"
|
|
318
|
+
with (
|
|
319
|
+
patch("git_commit_guard.subprocess.check_output", side_effect=err),
|
|
320
|
+
pytest.raises(SystemExit, match="no commits yet"),
|
|
321
|
+
):
|
|
322
|
+
_get_message("HEAD")
|
|
323
|
+
|
|
324
|
+
def test_other_git_error(self):
|
|
325
|
+
err = subprocess.CalledProcessError(128, "git")
|
|
326
|
+
err.stderr = "fatal: not a git repository"
|
|
327
|
+
with (
|
|
328
|
+
patch("git_commit_guard.subprocess.check_output", side_effect=err),
|
|
329
|
+
pytest.raises(SystemExit, match="git error"),
|
|
330
|
+
):
|
|
331
|
+
_get_message("abc")
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class TestParseChecks:
|
|
335
|
+
def test_invalid_check_name(self):
|
|
336
|
+
parser = ArgumentParser()
|
|
337
|
+
with pytest.raises(SystemExit):
|
|
338
|
+
_parse_checks(parser, "invalid")
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class TestReport:
|
|
342
|
+
def test_all_passed(self, capsys):
|
|
343
|
+
r = Result()
|
|
344
|
+
ret = _report(r)
|
|
345
|
+
assert ret == 0
|
|
346
|
+
assert "all checks passed" in capsys.readouterr().err
|
|
347
|
+
|
|
348
|
+
def test_with_error(self, capsys):
|
|
349
|
+
r = Result()
|
|
350
|
+
r.error("something broke")
|
|
351
|
+
ret = _report(r)
|
|
352
|
+
assert ret == 1
|
|
353
|
+
assert "something broke" in capsys.readouterr().err
|
|
354
|
+
|
|
355
|
+
def test_with_warning_returns_zero(self, capsys):
|
|
356
|
+
r = Result()
|
|
357
|
+
r.warn("heads up")
|
|
358
|
+
ret = _report(r)
|
|
359
|
+
assert ret == 0
|
|
360
|
+
captured = capsys.readouterr().err
|
|
361
|
+
assert "heads up" in captured
|
|
362
|
+
assert "all checks passed" in captured
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
_VALID_MSG = "fix: add thing\n\nbody text\n\nSigned-off-by: A User <a@b.com>"
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class TestMain:
|
|
369
|
+
def test_from_message_file(self, tmp_path, capsys):
|
|
370
|
+
f = tmp_path / "msg"
|
|
371
|
+
f.write_text(_VALID_MSG)
|
|
372
|
+
with patch(
|
|
373
|
+
"sys.argv",
|
|
374
|
+
["cg", "--message-file", str(f), "--disable", "signature"],
|
|
375
|
+
):
|
|
376
|
+
assert main() == 0
|
|
377
|
+
assert "all checks passed" in capsys.readouterr().err
|
|
378
|
+
|
|
379
|
+
def test_from_stdin(self):
|
|
380
|
+
stdin = MagicMock()
|
|
381
|
+
stdin.isatty.return_value = False
|
|
382
|
+
stdin.read.return_value = _VALID_MSG
|
|
383
|
+
with (
|
|
384
|
+
patch("sys.argv", ["cg", "--disable", "signature"]),
|
|
385
|
+
patch("sys.stdin", stdin),
|
|
386
|
+
):
|
|
387
|
+
assert main() == 0
|
|
388
|
+
|
|
389
|
+
def test_from_rev(self):
|
|
390
|
+
with (
|
|
391
|
+
patch("sys.argv", ["cg", "abc123", "--disable", "signature"]),
|
|
392
|
+
patch("git_commit_guard._get_message", return_value=_VALID_MSG),
|
|
393
|
+
):
|
|
394
|
+
assert main() == 0
|
|
395
|
+
|
|
396
|
+
def test_from_head(self):
|
|
397
|
+
stdin = MagicMock()
|
|
398
|
+
stdin.isatty.return_value = True
|
|
399
|
+
with (
|
|
400
|
+
patch("sys.argv", ["cg", "--disable", "signature"]),
|
|
401
|
+
patch("sys.stdin", stdin),
|
|
402
|
+
patch("git_commit_guard._get_message", return_value=_VALID_MSG),
|
|
403
|
+
):
|
|
404
|
+
assert main() == 0
|
|
405
|
+
|
|
406
|
+
def test_enable_flag(self, tmp_path):
|
|
407
|
+
f = tmp_path / "msg"
|
|
408
|
+
f.write_text(_VALID_MSG)
|
|
409
|
+
with patch("sys.argv", ["cg", "--message-file", str(f), "--enable", "subject"]):
|
|
410
|
+
assert main() == 0
|
|
411
|
+
|
|
412
|
+
def test_invalid_message_returns_one(self, tmp_path):
|
|
413
|
+
f = tmp_path / "msg"
|
|
414
|
+
f.write_text("not a valid commit")
|
|
415
|
+
with patch(
|
|
416
|
+
"sys.argv",
|
|
417
|
+
[
|
|
418
|
+
"cg",
|
|
419
|
+
"--message-file",
|
|
420
|
+
str(f),
|
|
421
|
+
"--disable",
|
|
422
|
+
"signature,body,signed-off,imperative",
|
|
423
|
+
],
|
|
424
|
+
):
|
|
425
|
+
assert main() == 1
|
|
426
|
+
|
|
427
|
+
def test_signature_skipped_without_rev(self, tmp_path, capsys):
|
|
428
|
+
f = tmp_path / "msg"
|
|
429
|
+
f.write_text(_VALID_MSG)
|
|
430
|
+
with patch(
|
|
431
|
+
"sys.argv",
|
|
432
|
+
["cg", "--message-file", str(f), "--enable", "signature"],
|
|
433
|
+
):
|
|
434
|
+
ret = main()
|
|
435
|
+
assert ret == 0
|
|
436
|
+
assert "skipped" in capsys.readouterr().err
|
|
437
|
+
|
|
438
|
+
def test_imperative_only_no_subject_check(self, tmp_path):
|
|
439
|
+
# imperative enabled, subject not — desc starts as None, parsed from line
|
|
440
|
+
f = tmp_path / "msg"
|
|
441
|
+
f.write_text(_VALID_MSG)
|
|
442
|
+
with patch(
|
|
443
|
+
"sys.argv",
|
|
444
|
+
["cg", "--message-file", str(f), "--enable", "imperative"],
|
|
445
|
+
):
|
|
446
|
+
assert main() == 0
|
|
447
|
+
|
|
448
|
+
def test_signature_with_rev(self):
|
|
449
|
+
proc = MagicMock(returncode=0, stderr="gpg signature verified")
|
|
450
|
+
with (
|
|
451
|
+
patch("sys.argv", ["cg", "abc123", "--enable", "signature"]),
|
|
452
|
+
patch("git_commit_guard._get_message", return_value=_VALID_MSG),
|
|
453
|
+
patch("git_commit_guard.subprocess.run", return_value=proc),
|
|
454
|
+
):
|
|
455
|
+
assert main() == 0
|
|
456
|
+
|
|
457
|
+
def test_invalid_check_name_exits(self, tmp_path):
|
|
458
|
+
f = tmp_path / "msg"
|
|
459
|
+
f.write_text(_VALID_MSG)
|
|
460
|
+
with (
|
|
461
|
+
patch("sys.argv", ["cg", "--message-file", str(f), "--enable", "bogus"]),
|
|
462
|
+
pytest.raises(SystemExit),
|
|
463
|
+
):
|
|
464
|
+
main()
|
|
@@ -23,6 +23,90 @@ wheels = [
|
|
|
23
23
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
|
24
24
|
]
|
|
25
25
|
|
|
26
|
+
[[package]]
|
|
27
|
+
name = "coverage"
|
|
28
|
+
version = "7.13.5"
|
|
29
|
+
source = { registry = "https://pypi.org/simple" }
|
|
30
|
+
sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" }
|
|
31
|
+
wheels = [
|
|
32
|
+
{ url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" },
|
|
33
|
+
{ url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" },
|
|
34
|
+
{ url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" },
|
|
35
|
+
{ url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" },
|
|
36
|
+
{ url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" },
|
|
37
|
+
{ url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" },
|
|
38
|
+
{ url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" },
|
|
39
|
+
{ url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" },
|
|
40
|
+
{ url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" },
|
|
41
|
+
{ url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" },
|
|
42
|
+
{ url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" },
|
|
43
|
+
{ url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" },
|
|
44
|
+
{ url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" },
|
|
45
|
+
{ url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" },
|
|
46
|
+
{ url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" },
|
|
47
|
+
{ url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" },
|
|
48
|
+
{ url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" },
|
|
49
|
+
{ url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" },
|
|
50
|
+
{ url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" },
|
|
51
|
+
{ url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" },
|
|
52
|
+
{ url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" },
|
|
53
|
+
{ url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" },
|
|
54
|
+
{ url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" },
|
|
55
|
+
{ url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" },
|
|
56
|
+
{ url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" },
|
|
57
|
+
{ url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" },
|
|
58
|
+
{ url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" },
|
|
59
|
+
{ url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" },
|
|
60
|
+
{ url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" },
|
|
61
|
+
{ url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" },
|
|
62
|
+
{ url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" },
|
|
63
|
+
{ url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" },
|
|
64
|
+
{ url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" },
|
|
65
|
+
{ url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" },
|
|
66
|
+
{ url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" },
|
|
67
|
+
{ url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" },
|
|
68
|
+
{ url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" },
|
|
69
|
+
{ url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" },
|
|
70
|
+
{ url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" },
|
|
71
|
+
{ url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" },
|
|
72
|
+
{ url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" },
|
|
73
|
+
{ url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" },
|
|
74
|
+
{ url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" },
|
|
75
|
+
{ url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" },
|
|
76
|
+
{ url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" },
|
|
77
|
+
{ url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" },
|
|
78
|
+
{ url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" },
|
|
79
|
+
{ url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" },
|
|
80
|
+
{ url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" },
|
|
81
|
+
{ url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" },
|
|
82
|
+
{ url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" },
|
|
83
|
+
{ url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" },
|
|
84
|
+
{ url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" },
|
|
85
|
+
{ url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" },
|
|
86
|
+
{ url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" },
|
|
87
|
+
{ url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" },
|
|
88
|
+
{ url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" },
|
|
89
|
+
{ url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" },
|
|
90
|
+
{ url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" },
|
|
91
|
+
{ url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" },
|
|
92
|
+
{ url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" },
|
|
93
|
+
{ url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" },
|
|
94
|
+
{ url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" },
|
|
95
|
+
{ url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" },
|
|
96
|
+
{ url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" },
|
|
97
|
+
{ url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" },
|
|
98
|
+
{ url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" },
|
|
99
|
+
{ url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" },
|
|
100
|
+
{ url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" },
|
|
101
|
+
{ url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" },
|
|
102
|
+
{ url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" },
|
|
103
|
+
{ url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" },
|
|
104
|
+
{ url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" },
|
|
105
|
+
{ url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" },
|
|
106
|
+
{ url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" },
|
|
107
|
+
{ url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" },
|
|
108
|
+
]
|
|
109
|
+
|
|
26
110
|
[[package]]
|
|
27
111
|
name = "git-commit-guard"
|
|
28
112
|
source = { editable = "." }
|
|
@@ -30,9 +114,30 @@ dependencies = [
|
|
|
30
114
|
{ name = "nltk" },
|
|
31
115
|
]
|
|
32
116
|
|
|
117
|
+
[package.dev-dependencies]
|
|
118
|
+
dev = [
|
|
119
|
+
{ name = "pytest" },
|
|
120
|
+
{ name = "pytest-cov" },
|
|
121
|
+
]
|
|
122
|
+
|
|
33
123
|
[package.metadata]
|
|
34
124
|
requires-dist = [{ name = "nltk", specifier = ">=3.9.3" }]
|
|
35
125
|
|
|
126
|
+
[package.metadata.requires-dev]
|
|
127
|
+
dev = [
|
|
128
|
+
{ name = "pytest", specifier = ">=9.0.2" },
|
|
129
|
+
{ name = "pytest-cov", specifier = ">=7.1.0" },
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
[[package]]
|
|
133
|
+
name = "iniconfig"
|
|
134
|
+
version = "2.3.0"
|
|
135
|
+
source = { registry = "https://pypi.org/simple" }
|
|
136
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
|
137
|
+
wheels = [
|
|
138
|
+
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
|
139
|
+
]
|
|
140
|
+
|
|
36
141
|
[[package]]
|
|
37
142
|
name = "joblib"
|
|
38
143
|
version = "1.5.3"
|
|
@@ -57,6 +162,63 @@ wheels = [
|
|
|
57
162
|
{ url = "https://files.pythonhosted.org/packages/c2/7e/9af5a710a1236e4772de8dfcc6af942a561327bb9f42b5b4a24d0cf100fd/nltk-3.9.3-py3-none-any.whl", hash = "sha256:60b3db6e9995b3dd976b1f0fa7dec22069b2677e759c28eb69b62ddd44870522", size = 1525385, upload-time = "2026-02-24T12:05:46.54Z" },
|
|
58
163
|
]
|
|
59
164
|
|
|
165
|
+
[[package]]
|
|
166
|
+
name = "packaging"
|
|
167
|
+
version = "26.0"
|
|
168
|
+
source = { registry = "https://pypi.org/simple" }
|
|
169
|
+
sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
|
|
170
|
+
wheels = [
|
|
171
|
+
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
[[package]]
|
|
175
|
+
name = "pluggy"
|
|
176
|
+
version = "1.6.0"
|
|
177
|
+
source = { registry = "https://pypi.org/simple" }
|
|
178
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
|
179
|
+
wheels = [
|
|
180
|
+
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
[[package]]
|
|
184
|
+
name = "pygments"
|
|
185
|
+
version = "2.19.2"
|
|
186
|
+
source = { registry = "https://pypi.org/simple" }
|
|
187
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
|
188
|
+
wheels = [
|
|
189
|
+
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
[[package]]
|
|
193
|
+
name = "pytest"
|
|
194
|
+
version = "9.0.2"
|
|
195
|
+
source = { registry = "https://pypi.org/simple" }
|
|
196
|
+
dependencies = [
|
|
197
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
198
|
+
{ name = "iniconfig" },
|
|
199
|
+
{ name = "packaging" },
|
|
200
|
+
{ name = "pluggy" },
|
|
201
|
+
{ name = "pygments" },
|
|
202
|
+
]
|
|
203
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
|
|
204
|
+
wheels = [
|
|
205
|
+
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
[[package]]
|
|
209
|
+
name = "pytest-cov"
|
|
210
|
+
version = "7.1.0"
|
|
211
|
+
source = { registry = "https://pypi.org/simple" }
|
|
212
|
+
dependencies = [
|
|
213
|
+
{ name = "coverage" },
|
|
214
|
+
{ name = "pluggy" },
|
|
215
|
+
{ name = "pytest" },
|
|
216
|
+
]
|
|
217
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" }
|
|
218
|
+
wheels = [
|
|
219
|
+
{ url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" },
|
|
220
|
+
]
|
|
221
|
+
|
|
60
222
|
[[package]]
|
|
61
223
|
name = "regex"
|
|
62
224
|
version = "2026.2.28"
|
|
File without changes
|
|
File without changes
|