git-commit-guard 0.5.0__tar.gz → 0.7.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.7.0/.github/workflows/lint-commits.yml +44 -0
- git_commit_guard-0.7.0/.github/workflows/lint-md.yml +26 -0
- git_commit_guard-0.7.0/.github/workflows/test.yml +27 -0
- {git_commit_guard-0.5.0 → git_commit_guard-0.7.0}/.gitignore +1 -0
- {git_commit_guard-0.5.0 → git_commit_guard-0.7.0}/.pre-commit-hooks.yaml +11 -0
- {git_commit_guard-0.5.0 → git_commit_guard-0.7.0}/PKG-INFO +12 -7
- {git_commit_guard-0.5.0 → git_commit_guard-0.7.0}/README.md +11 -6
- {git_commit_guard-0.5.0 → git_commit_guard-0.7.0}/pyproject.toml +6 -0
- {git_commit_guard-0.5.0 → git_commit_guard-0.7.0}/src/git_commit_guard/__init__.py +18 -115
- git_commit_guard-0.7.0/tests/__init__.py +0 -0
- git_commit_guard-0.7.0/tests/test_git_commit_guard.py +464 -0
- {git_commit_guard-0.5.0 → git_commit_guard-0.7.0}/uv.lock +162 -0
- {git_commit_guard-0.5.0 → git_commit_guard-0.7.0}/.python-version +0 -0
- {git_commit_guard-0.5.0 → git_commit_guard-0.7.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,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Lint Markdown
|
|
3
|
+
on: # yamllint disable-line rule:truthy
|
|
4
|
+
pull_request:
|
|
5
|
+
permissions:
|
|
6
|
+
contents: read
|
|
7
|
+
jobs:
|
|
8
|
+
lint-md:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
pull-requests: write
|
|
13
|
+
steps:
|
|
14
|
+
- name: Checkout code
|
|
15
|
+
# yamllint disable-line rule:line-length
|
|
16
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
17
|
+
with:
|
|
18
|
+
persist-credentials: false
|
|
19
|
+
- name: Lint Markdown files with reviewdog
|
|
20
|
+
# yamllint disable-line rule:line-length
|
|
21
|
+
uses: reviewdog/action-markdownlint@3667398db9118d7e78f7a63d10e26ce454ba5f58 # v0.26.2
|
|
22
|
+
with:
|
|
23
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
24
|
+
reporter: github-pr-review
|
|
25
|
+
level: info
|
|
26
|
+
fail_on_error: true
|
|
@@ -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
|
|
@@ -10,3 +10,14 @@
|
|
|
10
10
|
always_run: true
|
|
11
11
|
pass_filenames: true
|
|
12
12
|
minimum_pre_commit_version: 2.8.0
|
|
13
|
+
|
|
14
|
+
- id: commit-guard-signature
|
|
15
|
+
name: commit-guard (signature)
|
|
16
|
+
description: Verify commit is GPG/SSH signed
|
|
17
|
+
entry: commit-guard
|
|
18
|
+
args: [HEAD, --enable, signature]
|
|
19
|
+
language: python
|
|
20
|
+
stages: [post-commit]
|
|
21
|
+
always_run: true
|
|
22
|
+
pass_filenames: false
|
|
23
|
+
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.7.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
|
|
@@ -31,7 +31,8 @@ imperative verb.
|
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
$ commit-guard
|
|
34
|
-
✗ subject does not match 'type(scope): description':
|
|
34
|
+
✗ subject does not match 'type(scope): description':
|
|
35
|
+
Merge pull request #5 from fix/branch
|
|
35
36
|
✗ missing 'Signed-off-by' trailer
|
|
36
37
|
✗ commit is not signed (GPG/SSH)
|
|
37
38
|
```
|
|
@@ -125,14 +126,19 @@ repos:
|
|
|
125
126
|
rev: v0.1.0
|
|
126
127
|
hooks:
|
|
127
128
|
- id: commit-guard
|
|
129
|
+
- id: commit-guard-signature
|
|
128
130
|
```
|
|
129
131
|
|
|
130
|
-
Install the
|
|
132
|
+
Install the hooks:
|
|
131
133
|
|
|
132
134
|
```bash
|
|
133
|
-
pre-commit install --hook-type commit-msg
|
|
135
|
+
pre-commit install --hook-type commit-msg --hook-type post-commit
|
|
134
136
|
```
|
|
135
137
|
|
|
138
|
+
`commit-guard` runs at the `commit-msg` stage and checks message format.
|
|
139
|
+
`commit-guard-signature` runs at the `post-commit` stage and verifies
|
|
140
|
+
the GPG/SSH signature after the commit object is created.
|
|
141
|
+
|
|
136
142
|
To selectively enable or disable checks, pass `args`:
|
|
137
143
|
|
|
138
144
|
```yaml
|
|
@@ -144,10 +150,9 @@ To selectively enable or disable checks, pass `args`:
|
|
|
144
150
|
|
|
145
151
|
commit-guard combines two strategies to detect non-imperative descriptions:
|
|
146
152
|
|
|
147
|
-
1.
|
|
148
|
-
that pass immediately without NLP.
|
|
149
|
-
2. nltk POS tagging as a fallback — flags words tagged as past tense (`VBD`),
|
|
153
|
+
1. nltk POS tagging — flags words tagged as past tense (`VBD`),
|
|
150
154
|
gerund (`VBG`), third person (`VBZ`), etc.
|
|
155
|
+
2. WordNet morphology as a fallback for words the tagger misclassifies.
|
|
151
156
|
|
|
152
157
|
This catches common mistakes like `added logging` or `fixes bug` while
|
|
153
158
|
keeping false positives low.
|
|
@@ -10,7 +10,8 @@ imperative verb.
|
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
$ commit-guard
|
|
13
|
-
✗ subject does not match 'type(scope): description':
|
|
13
|
+
✗ subject does not match 'type(scope): description':
|
|
14
|
+
Merge pull request #5 from fix/branch
|
|
14
15
|
✗ missing 'Signed-off-by' trailer
|
|
15
16
|
✗ commit is not signed (GPG/SSH)
|
|
16
17
|
```
|
|
@@ -104,14 +105,19 @@ repos:
|
|
|
104
105
|
rev: v0.1.0
|
|
105
106
|
hooks:
|
|
106
107
|
- id: commit-guard
|
|
108
|
+
- id: commit-guard-signature
|
|
107
109
|
```
|
|
108
110
|
|
|
109
|
-
Install the
|
|
111
|
+
Install the hooks:
|
|
110
112
|
|
|
111
113
|
```bash
|
|
112
|
-
pre-commit install --hook-type commit-msg
|
|
114
|
+
pre-commit install --hook-type commit-msg --hook-type post-commit
|
|
113
115
|
```
|
|
114
116
|
|
|
117
|
+
`commit-guard` runs at the `commit-msg` stage and checks message format.
|
|
118
|
+
`commit-guard-signature` runs at the `post-commit` stage and verifies
|
|
119
|
+
the GPG/SSH signature after the commit object is created.
|
|
120
|
+
|
|
115
121
|
To selectively enable or disable checks, pass `args`:
|
|
116
122
|
|
|
117
123
|
```yaml
|
|
@@ -123,10 +129,9 @@ To selectively enable or disable checks, pass `args`:
|
|
|
123
129
|
|
|
124
130
|
commit-guard combines two strategies to detect non-imperative descriptions:
|
|
125
131
|
|
|
126
|
-
1.
|
|
127
|
-
that pass immediately without NLP.
|
|
128
|
-
2. nltk POS tagging as a fallback — flags words tagged as past tense (`VBD`),
|
|
132
|
+
1. nltk POS tagging — flags words tagged as past tense (`VBD`),
|
|
129
133
|
gerund (`VBG`), third person (`VBZ`), etc.
|
|
134
|
+
2. WordNet morphology as a fallback for words the tagger misclassifies.
|
|
130
135
|
|
|
131
136
|
This catches common mistakes like `added logging` or `fixes bug` while
|
|
132
137
|
keeping false positives low.
|
|
@@ -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
|
|
|
@@ -369,10 +275,7 @@ def main():
|
|
|
369
275
|
check_body(lines, result)
|
|
370
276
|
if Check.SIGNED_OFF in args.enabled:
|
|
371
277
|
check_signed_off(args.message, result)
|
|
372
|
-
if Check.SIGNATURE in args.enabled:
|
|
373
|
-
|
|
374
|
-
check_signature(args.rev, result)
|
|
375
|
-
else:
|
|
376
|
-
result.warn("signature check skipped (no commit ref)")
|
|
278
|
+
if Check.SIGNATURE in args.enabled and args.rev:
|
|
279
|
+
check_signature(args.rev, result)
|
|
377
280
|
|
|
378
281
|
return _report(result)
|
|
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 "all checks passed" 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
|