check-empty 1.3.0__tar.gz → 3.0.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.
- {check_empty-1.3.0 → check_empty-3.0.0}/PKG-INFO +36 -37
- {check_empty-1.3.0 → check_empty-3.0.0}/README.md +27 -25
- {check_empty-1.3.0 → check_empty-3.0.0}/check_empty/__init__.py +146 -52
- {check_empty-1.3.0 → check_empty-3.0.0}/check_empty/__main__.py +19 -3
- check_empty-3.0.0/check_empty/archives.py +120 -0
- check_empty-3.0.0/check_empty/constants.py +66 -0
- check_empty-3.0.0/check_empty/reporter/__init__.py +99 -0
- check_empty-3.0.0/check_empty/reporter/abcdef.py +131 -0
- check_empty-3.0.0/check_empty/reporter/dct.py +140 -0
- check_empty-3.0.0/check_empty/reporter/exceptions.py +74 -0
- {check_empty-1.3.0 → check_empty-3.0.0}/pyproject.toml +32 -49
- check_empty-3.0.0/pyproject.toml.orig +92 -0
- check_empty-1.3.0/check_empty/archives.py +0 -45
- check_empty-1.3.0/check_empty/constants.py +0 -44
- check_empty-1.3.0/check_empty/reporter.py +0 -144
- check_empty-1.3.0/check_empty/util.py +0 -86
- check_empty-1.3.0/pyproject.toml.orig +0 -97
- {check_empty-1.3.0 → check_empty-3.0.0}/AUTHORS.md +0 -0
- {check_empty-1.3.0 → check_empty-3.0.0}/LICENSE +0 -0
- {check_empty-1.3.0 → check_empty-3.0.0}/check_empty/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: check-empty
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: Command-line utility, as well as a hook usable by the Python pre-commit framework, and a GitHub Action, to ensure that selected files are empty.
|
|
5
5
|
Keywords: check,command-line,empty,files,github-action,helper,hook,lightweight,pre-commit,prek,tool,utility
|
|
6
6
|
Author: Jonathan Dung
|
|
@@ -15,9 +15,6 @@ Classifier: Natural Language :: English
|
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
17
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
21
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -28,24 +25,24 @@ Classifier: Programming Language :: Python :: 3.16
|
|
|
28
25
|
Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
|
|
29
26
|
Classifier: Topic :: Utilities
|
|
30
27
|
Classifier: Typing :: Typed
|
|
31
|
-
Requires-Dist: py7zr==1.1.3 ;
|
|
32
|
-
Requires-Dist:
|
|
33
|
-
Requires-Dist:
|
|
34
|
-
Requires-Dist:
|
|
35
|
-
Requires-Dist: check-empty[7z,lzh,rar]==1.3.0 ; extra == 'all'
|
|
28
|
+
Requires-Dist: py7zr==1.1.3 ; extra == '7z'
|
|
29
|
+
Requires-Dist: acefile==0.6.14 ; extra == 'ace'
|
|
30
|
+
Requires-Dist: check-empty[7z,ace,ar,lzh,rar]==3.0.0 ; extra == 'all'
|
|
31
|
+
Requires-Dist: arpy==2.4.0 ; extra == 'ar'
|
|
36
32
|
Requires-Dist: lhafile==0.3.1 ; extra == 'lzh'
|
|
37
|
-
Requires-Dist: rarfile==4.5 ;
|
|
38
|
-
Requires-Dist: rarfile==4.2 ; python_full_version < '3.10' and extra == 'rar'
|
|
33
|
+
Requires-Dist: rarfile==4.5 ; extra == 'rar'
|
|
39
34
|
Maintainer: Jonathan Dung
|
|
40
35
|
Maintainer-email: Jonathan Dung <jonathandung@yahoo.com>
|
|
41
|
-
Requires-Python: >=3.
|
|
36
|
+
Requires-Python: >=3.10
|
|
42
37
|
Provides-Extra: 7z
|
|
38
|
+
Provides-Extra: ace
|
|
43
39
|
Provides-Extra: all
|
|
40
|
+
Provides-Extra: ar
|
|
44
41
|
Provides-Extra: lzh
|
|
45
42
|
Provides-Extra: rar
|
|
46
43
|
Description-Content-Type: text/markdown
|
|
47
44
|
|
|
48
|
-
# [check-empty](https://pypi.org/p/check-empty)
|
|
45
|
+
# [check-empty](https://pypi.org/p/check-empty/3.0.0)
|
|
49
46
|
|
|
50
47
|
[](https://github.com/TheBSD/StandWithPalestine/blob/main/docs/README.md)
|
|
51
48
|
|
|
@@ -60,11 +57,8 @@ specified.
|
|
|
60
57
|
|
|
61
58
|
## Prerequisites
|
|
62
59
|
|
|
63
|
-
Supports
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
We cannot support Python 3.6 because of a vulnerability in `py7zr` 0.19.2, which is the
|
|
67
|
-
latest version of `py7zr` that supports Python 3.6.
|
|
60
|
+
Supports every Python 3.10 runtime. There are no other requirements unless you want to
|
|
61
|
+
check if files in certain types of (compressed) archives are empty.
|
|
68
62
|
|
|
69
63
|
## Quickstart
|
|
70
64
|
|
|
@@ -132,7 +126,7 @@ As a pre-commit hook:
|
|
|
132
126
|
# .pre-commit-config.yaml
|
|
133
127
|
repos:
|
|
134
128
|
- repo: https://github.com/jonathandung/check-empty
|
|
135
|
-
rev:
|
|
129
|
+
rev: v3.0.0 # repository version
|
|
136
130
|
hooks:
|
|
137
131
|
- id: check-empty # the hook
|
|
138
132
|
args: # example list of arguments
|
|
@@ -148,7 +142,7 @@ equivalent in `prek.toml` format:
|
|
|
148
142
|
```toml
|
|
149
143
|
[[repos]]
|
|
150
144
|
repo = "https://github.com/jonathandung/check-empty"
|
|
151
|
-
rev = "
|
|
145
|
+
rev = "v3.0.0"
|
|
152
146
|
|
|
153
147
|
[[repos.hooks]]
|
|
154
148
|
id = "check-empty"
|
|
@@ -172,7 +166,7 @@ or (TOML 1.1+):
|
|
|
172
166
|
# using multiline inline tables
|
|
173
167
|
[[repos]]
|
|
174
168
|
repo = "https://github.com/jonathandung/check-empty"
|
|
175
|
-
rev = "
|
|
169
|
+
rev = "v3.0.0"
|
|
176
170
|
hooks = [{
|
|
177
171
|
id = "check-empty",
|
|
178
172
|
args = ["-Q"],
|
|
@@ -192,13 +186,11 @@ As a GitHub Actions workflow step:
|
|
|
192
186
|
|
|
193
187
|
```yaml
|
|
194
188
|
steps:
|
|
195
|
-
- uses: jonathandung/check-empty@
|
|
189
|
+
- uses: jonathandung/check-empty@v3.0.0 # the latest version on the GitHub Actions
|
|
196
190
|
# marketplace; this step will fail and subsequent jobs will not run if any file is
|
|
197
191
|
# not empty
|
|
198
192
|
with:
|
|
199
193
|
python-version: '3.14' # run the script on the latest stable Python version
|
|
200
|
-
# Python down to 3.7 is supported but not recommended due to end-of-life
|
|
201
|
-
verbosity: 1
|
|
202
194
|
filenames: |
|
|
203
195
|
src/mylib/py.typed
|
|
204
196
|
docs/.nojekyll
|
|
@@ -211,26 +203,34 @@ steps:
|
|
|
211
203
|
|
|
212
204
|
Also see the GitHub Action
|
|
213
205
|
[manifest](https://github.com/jonathandung/check-empty/blob/main/action.yaml), which
|
|
214
|
-
contains the accepted action inputs and
|
|
206
|
+
contains the accepted action inputs, action outputs produced and their respective
|
|
207
|
+
descriptions.
|
|
215
208
|
|
|
216
209
|
## Notes
|
|
217
210
|
|
|
218
|
-
1. If your file name starts with a hyphen,
|
|
219
|
-
|
|
211
|
+
1. If your file name starts with a hyphen, use a command of the form
|
|
212
|
+
`check-empty -- -this_is_actually_a_file.txt` to avoid having the filename
|
|
213
|
+
misinterpreted as a flag.
|
|
220
214
|
2. Forward slashes can be used even on Windows, so there is no need to escape anything.
|
|
221
215
|
3. Glob patterns are supported on \*nix only. If on Windows, use a shell like Git Bash.
|
|
222
216
|
4. To pass an
|
|
223
217
|
[argfile](https://docs.python.org/3/library/argparse.html#fromfile-prefix-chars), use
|
|
224
218
|
the `@` prefix, and escape files whose names actually start with `@` using the
|
|
225
219
|
double-hyphen syntax.
|
|
226
|
-
5. It may be unintuitive that a directory being "empty" means all its files
|
|
227
|
-
but this project explicitly targets files, since version control systems
|
|
228
|
-
rather than directories.
|
|
229
|
-
6. The program
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
220
|
+
5. It may be unintuitive that a directory or archive being "empty" means all its files
|
|
221
|
+
are empty, but this project explicitly targets files, since version control systems
|
|
222
|
+
track files rather than directories.
|
|
223
|
+
6. The program can recurse into some archives if specified, but it requires certain
|
|
224
|
+
libraries to be installed to do so for certain formats. .7z (corresponding to the `7z`
|
|
225
|
+
extra) needs `py7zr`, .rar (the `rar` extra) needs `rarfile`, .lha / .lzh (the `lzh`
|
|
226
|
+
extra) needs `lhafile`, .a / .ar / .lib (the `ar` extra) needs `arpy`, .ace (the `ace`
|
|
227
|
+
extra) needs `acefile`. These may also slow down the checking significantly for large
|
|
228
|
+
directories, since magic numbers must be read for every file and the I/O overhead
|
|
229
|
+
accumulates. All the above extras are included in the `all` extra.
|
|
230
|
+
7. Keep weird characters in your filenames to a minimum. They may become a problem in
|
|
231
|
+
GitHub Actions usage.
|
|
232
|
+
|
|
233
|
+
## Additional links
|
|
234
234
|
|
|
235
235
|
- [API and CLI Reference](https://check-empty.readthedocs.io)
|
|
236
236
|
- [Releases](https://github.com/jonathandung/check-empty/releases)
|
|
@@ -252,8 +252,7 @@ into a virtual environment.
|
|
|
252
252
|
Tests are run with:
|
|
253
253
|
|
|
254
254
|
```bash
|
|
255
|
-
python -m test_check_empty
|
|
256
|
-
python -m unittest discover # alternative
|
|
255
|
+
python -m test_check_empty
|
|
257
256
|
```
|
|
258
257
|
|
|
259
258
|
at the project root. `pytest` is not needed.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# [check-empty](https://pypi.org/p/check-empty)
|
|
1
|
+
# [check-empty](https://pypi.org/p/check-empty/3.0.0)
|
|
2
2
|
|
|
3
3
|
[](https://github.com/TheBSD/StandWithPalestine/blob/main/docs/README.md)
|
|
4
4
|
|
|
@@ -13,11 +13,8 @@ specified.
|
|
|
13
13
|
|
|
14
14
|
## Prerequisites
|
|
15
15
|
|
|
16
|
-
Supports
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
We cannot support Python 3.6 because of a vulnerability in `py7zr` 0.19.2, which is the
|
|
20
|
-
latest version of `py7zr` that supports Python 3.6.
|
|
16
|
+
Supports every Python 3.10 runtime. There are no other requirements unless you want to
|
|
17
|
+
check if files in certain types of (compressed) archives are empty.
|
|
21
18
|
|
|
22
19
|
## Quickstart
|
|
23
20
|
|
|
@@ -85,7 +82,7 @@ As a pre-commit hook:
|
|
|
85
82
|
# .pre-commit-config.yaml
|
|
86
83
|
repos:
|
|
87
84
|
- repo: https://github.com/jonathandung/check-empty
|
|
88
|
-
rev:
|
|
85
|
+
rev: v3.0.0 # repository version
|
|
89
86
|
hooks:
|
|
90
87
|
- id: check-empty # the hook
|
|
91
88
|
args: # example list of arguments
|
|
@@ -101,7 +98,7 @@ equivalent in `prek.toml` format:
|
|
|
101
98
|
```toml
|
|
102
99
|
[[repos]]
|
|
103
100
|
repo = "https://github.com/jonathandung/check-empty"
|
|
104
|
-
rev = "
|
|
101
|
+
rev = "v3.0.0"
|
|
105
102
|
|
|
106
103
|
[[repos.hooks]]
|
|
107
104
|
id = "check-empty"
|
|
@@ -125,7 +122,7 @@ or (TOML 1.1+):
|
|
|
125
122
|
# using multiline inline tables
|
|
126
123
|
[[repos]]
|
|
127
124
|
repo = "https://github.com/jonathandung/check-empty"
|
|
128
|
-
rev = "
|
|
125
|
+
rev = "v3.0.0"
|
|
129
126
|
hooks = [{
|
|
130
127
|
id = "check-empty",
|
|
131
128
|
args = ["-Q"],
|
|
@@ -145,13 +142,11 @@ As a GitHub Actions workflow step:
|
|
|
145
142
|
|
|
146
143
|
```yaml
|
|
147
144
|
steps:
|
|
148
|
-
- uses: jonathandung/check-empty@
|
|
145
|
+
- uses: jonathandung/check-empty@v3.0.0 # the latest version on the GitHub Actions
|
|
149
146
|
# marketplace; this step will fail and subsequent jobs will not run if any file is
|
|
150
147
|
# not empty
|
|
151
148
|
with:
|
|
152
149
|
python-version: '3.14' # run the script on the latest stable Python version
|
|
153
|
-
# Python down to 3.7 is supported but not recommended due to end-of-life
|
|
154
|
-
verbosity: 1
|
|
155
150
|
filenames: |
|
|
156
151
|
src/mylib/py.typed
|
|
157
152
|
docs/.nojekyll
|
|
@@ -164,26 +159,34 @@ steps:
|
|
|
164
159
|
|
|
165
160
|
Also see the GitHub Action
|
|
166
161
|
[manifest](https://github.com/jonathandung/check-empty/blob/main/action.yaml), which
|
|
167
|
-
contains the accepted action inputs and
|
|
162
|
+
contains the accepted action inputs, action outputs produced and their respective
|
|
163
|
+
descriptions.
|
|
168
164
|
|
|
169
165
|
## Notes
|
|
170
166
|
|
|
171
|
-
1. If your file name starts with a hyphen,
|
|
172
|
-
|
|
167
|
+
1. If your file name starts with a hyphen, use a command of the form
|
|
168
|
+
`check-empty -- -this_is_actually_a_file.txt` to avoid having the filename
|
|
169
|
+
misinterpreted as a flag.
|
|
173
170
|
2. Forward slashes can be used even on Windows, so there is no need to escape anything.
|
|
174
171
|
3. Glob patterns are supported on \*nix only. If on Windows, use a shell like Git Bash.
|
|
175
172
|
4. To pass an
|
|
176
173
|
[argfile](https://docs.python.org/3/library/argparse.html#fromfile-prefix-chars), use
|
|
177
174
|
the `@` prefix, and escape files whose names actually start with `@` using the
|
|
178
175
|
double-hyphen syntax.
|
|
179
|
-
5. It may be unintuitive that a directory being "empty" means all its files
|
|
180
|
-
but this project explicitly targets files, since version control systems
|
|
181
|
-
rather than directories.
|
|
182
|
-
6. The program
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
176
|
+
5. It may be unintuitive that a directory or archive being "empty" means all its files
|
|
177
|
+
are empty, but this project explicitly targets files, since version control systems
|
|
178
|
+
track files rather than directories.
|
|
179
|
+
6. The program can recurse into some archives if specified, but it requires certain
|
|
180
|
+
libraries to be installed to do so for certain formats. .7z (corresponding to the `7z`
|
|
181
|
+
extra) needs `py7zr`, .rar (the `rar` extra) needs `rarfile`, .lha / .lzh (the `lzh`
|
|
182
|
+
extra) needs `lhafile`, .a / .ar / .lib (the `ar` extra) needs `arpy`, .ace (the `ace`
|
|
183
|
+
extra) needs `acefile`. These may also slow down the checking significantly for large
|
|
184
|
+
directories, since magic numbers must be read for every file and the I/O overhead
|
|
185
|
+
accumulates. All the above extras are included in the `all` extra.
|
|
186
|
+
7. Keep weird characters in your filenames to a minimum. They may become a problem in
|
|
187
|
+
GitHub Actions usage.
|
|
188
|
+
|
|
189
|
+
## Additional links
|
|
187
190
|
|
|
188
191
|
- [API and CLI Reference](https://check-empty.readthedocs.io)
|
|
189
192
|
- [Releases](https://github.com/jonathandung/check-empty/releases)
|
|
@@ -205,8 +208,7 @@ into a virtual environment.
|
|
|
205
208
|
Tests are run with:
|
|
206
209
|
|
|
207
210
|
```bash
|
|
208
|
-
python -m test_check_empty
|
|
209
|
-
python -m unittest discover # alternative
|
|
211
|
+
python -m test_check_empty
|
|
210
212
|
```
|
|
211
213
|
|
|
212
214
|
at the project root. `pytest` is not needed.
|
|
@@ -1,26 +1,94 @@
|
|
|
1
1
|
# Copyright © 2026 Jonathan Dung. All rights reserved.
|
|
2
2
|
# SPDX-License-Identifier: MIT
|
|
3
|
-
"""Utility to check the emptiness of files
|
|
3
|
+
"""Utility to check the emptiness of files.
|
|
4
4
|
|
|
5
5
|
Pre-commit hook, command-line tool and GitHub Action all-in-one.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
from contextlib import suppress
|
|
11
11
|
|
|
12
|
-
from
|
|
12
|
+
from check_empty import constants, reporter
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
__all__ = ('Handler', 'check', 'default_reporter')
|
|
15
|
+
__version__ = '3.0.0'
|
|
16
|
+
"""The version of the package."""
|
|
17
|
+
default_reporter: reporter.Reporter = reporter.Reporter()
|
|
18
|
+
"""The default reporter used by :func:`check`."""
|
|
19
|
+
TYPE_CHECKING = False
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from collections.abc import Callable, Iterable
|
|
22
|
+
from types import TracebackType
|
|
23
|
+
from typing import Any, SupportsIndex
|
|
17
24
|
|
|
18
25
|
from _typeshed import FileDescriptorOrPath
|
|
26
|
+
from typing_extensions import Self
|
|
19
27
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
|
|
29
|
+
class Handler:
|
|
30
|
+
"""Used to handle errors when opening a file."""
|
|
31
|
+
|
|
32
|
+
__slots__ = 'a', 'c', 'f', 'j', 'v'
|
|
33
|
+
a: int | str
|
|
34
|
+
"""The file descriptor or string path to the file being handled."""
|
|
35
|
+
c: bool
|
|
36
|
+
"""Whether an I/O error was handled."""
|
|
37
|
+
f: Callable[[OSError], object]
|
|
38
|
+
"""The function to call on the I/O error being handled."""
|
|
39
|
+
j: Callable[[str], object]
|
|
40
|
+
"""The function to call when handling the case where the file is missing.
|
|
41
|
+
|
|
42
|
+
Should take the path to the file or a string encapsulating the validity of the file
|
|
43
|
+
descriptor as the only argument.
|
|
44
|
+
"""
|
|
45
|
+
v: str | None
|
|
46
|
+
"""The cached value of :attr:`e`."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, *a: Any): # ruff: ignore[any-type]
|
|
49
|
+
"""Initialize the handler."""
|
|
50
|
+
self.j, self.f, self.a = a
|
|
51
|
+
self.v = None
|
|
52
|
+
|
|
53
|
+
def __enter__(self) -> Self:
|
|
54
|
+
"""Enter the handler context.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
The handler itself.
|
|
58
|
+
"""
|
|
59
|
+
self.c = False
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
def o(self) -> None:
|
|
63
|
+
"""Clear the file immediately."""
|
|
64
|
+
with self, open(self.a, 'wb'):
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def e(self, s: str = 'invalid fd (negative): %d', t: str = 'fd: %d') -> str:
|
|
69
|
+
"""The file path itself, or the file descriptor coerced to a string."""
|
|
70
|
+
r = self.v
|
|
71
|
+
if r is None:
|
|
72
|
+
a = self.a
|
|
73
|
+
self.v = r = (t, s)[a < 0] % a if isinstance(a, int) else a
|
|
74
|
+
return r
|
|
75
|
+
|
|
76
|
+
def __exit__(
|
|
77
|
+
self,
|
|
78
|
+
t: type[BaseException] | None,
|
|
79
|
+
v: BaseException | None,
|
|
80
|
+
_: TracebackType | None,
|
|
81
|
+
) -> bool:
|
|
82
|
+
"""Exit the handler context.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Whether there was an exception that is to be suppressed.
|
|
86
|
+
"""
|
|
87
|
+
if t is None or not isinstance(v, OSError):
|
|
88
|
+
return False
|
|
89
|
+
self.j(self.e) if v.errno == constants.ENOENT else self.f(v)
|
|
90
|
+
self.c = True
|
|
91
|
+
return True
|
|
24
92
|
|
|
25
93
|
|
|
26
94
|
def check(
|
|
@@ -28,16 +96,17 @@ def check(
|
|
|
28
96
|
*,
|
|
29
97
|
clear: bool = False,
|
|
30
98
|
may_not_exist: bool = False,
|
|
31
|
-
verbosity:
|
|
32
|
-
recurse_into:
|
|
33
|
-
reporter: reporter.
|
|
99
|
+
verbosity: SupportsIndex = 2,
|
|
100
|
+
recurse_into: SupportsIndex = constants.RecurseInto.NONE,
|
|
101
|
+
reporter: reporter.abcdef.ReporterABC = default_reporter,
|
|
34
102
|
) -> int:
|
|
35
103
|
# ruff: ignore[docstring-missing-exception]
|
|
36
104
|
r"""Check the emptiness of files, recursing into directories if passed.
|
|
37
105
|
|
|
38
|
-
If using a custom reporter
|
|
39
|
-
|
|
40
|
-
:meth
|
|
106
|
+
If using a custom reporter and you want to use all the information that can be
|
|
107
|
+
gathered during the check, remember to pass a ``verbosity`` greater than or equal
|
|
108
|
+
to 5, since some parameters to :meth:`~check_empty.reporter.Reporter.report` will
|
|
109
|
+
take values of ``None`` otherwise.
|
|
41
110
|
|
|
42
111
|
Args:
|
|
43
112
|
files: an iterable of file descriptors or paths representing the files and
|
|
@@ -55,13 +124,14 @@ def check(
|
|
|
55
124
|
The integer exit code calculated by the reporter.
|
|
56
125
|
|
|
57
126
|
"""
|
|
127
|
+
from os import fsdecode, scandir, stat
|
|
128
|
+
|
|
58
129
|
files = list(files)
|
|
59
130
|
if not files:
|
|
60
131
|
return 0
|
|
61
132
|
k, j, t, n, o = [0] * 5, [], 0, len(files), files.pop
|
|
62
133
|
u, e, _ = j.extend, j.pop, lambda v: lambda _, k=k: k.__setitem__(v, k[v] + 1)
|
|
63
134
|
verbosity, recurse_into = (type(x).__index__(x) for x in (verbosity, recurse_into))
|
|
64
|
-
# ruff: disable[magic-value-comparison]
|
|
65
135
|
if verbosity > 4:
|
|
66
136
|
b = ['']
|
|
67
137
|
f = b.append
|
|
@@ -74,7 +144,7 @@ def check(
|
|
|
74
144
|
v = ra = None
|
|
75
145
|
x = aa = lambda _: None
|
|
76
146
|
if verbosity > 2:
|
|
77
|
-
z, w = [''], [
|
|
147
|
+
z, w = [''], []
|
|
78
148
|
i = z.append, w.append
|
|
79
149
|
else:
|
|
80
150
|
z = w = None
|
|
@@ -84,21 +154,17 @@ def check(
|
|
|
84
154
|
g = r.append
|
|
85
155
|
else:
|
|
86
156
|
r, g = None, _(3)
|
|
87
|
-
|
|
88
|
-
zp, tr, rr, z7, lh = map(
|
|
89
|
-
recurse_into.__and__,
|
|
90
|
-
(x for x in constants.RecurseInto if x > 0 == x & (x - 1))
|
|
91
|
-
if __import__('sys').version_info < (3, 11)
|
|
92
|
-
else constants.RecurseInto,
|
|
93
|
-
)
|
|
157
|
+
zp, tr, rr, z7, ar, lh, ac = map(recurse_into.__and__, constants.ARCHIVE_FORMATS)
|
|
94
158
|
an = bool(recurse_into)
|
|
95
159
|
if an:
|
|
96
|
-
from
|
|
160
|
+
from check_empty import archives as av
|
|
97
161
|
md = 'a' if clear else 'r'
|
|
98
|
-
if
|
|
162
|
+
if TYPE_CHECKING:
|
|
99
163
|
import tarfile as tr
|
|
100
164
|
import zipfile as zp
|
|
101
165
|
|
|
166
|
+
import acefile as ac
|
|
167
|
+
import arpy as ar
|
|
102
168
|
import lhafile as lh
|
|
103
169
|
import py7zr as z7
|
|
104
170
|
import rarfile as rr
|
|
@@ -107,6 +173,10 @@ def check(
|
|
|
107
173
|
import tarfile as tr
|
|
108
174
|
if zp:
|
|
109
175
|
import zipfile as zp
|
|
176
|
+
if ac:
|
|
177
|
+
import acefile as ac
|
|
178
|
+
if ar:
|
|
179
|
+
import arpy as ar
|
|
110
180
|
if lh:
|
|
111
181
|
import lhafile as lh
|
|
112
182
|
if z7:
|
|
@@ -115,8 +185,6 @@ def check(
|
|
|
115
185
|
import rarfile as rr
|
|
116
186
|
|
|
117
187
|
def ha(a: str) -> bool:
|
|
118
|
-
if not an:
|
|
119
|
-
return False
|
|
120
188
|
|
|
121
189
|
def c() -> None:
|
|
122
190
|
nonlocal t
|
|
@@ -134,15 +202,19 @@ def check(
|
|
|
134
202
|
else:
|
|
135
203
|
f(a)
|
|
136
204
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
205
|
+
def v(f: Callable[[str], Any], e: type[BaseException]) -> bool:
|
|
206
|
+
nonlocal d
|
|
207
|
+
with suppress(e):
|
|
208
|
+
d = f(a)
|
|
209
|
+
return True
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
if tr and v(tr.open, tr.TarError):
|
|
140
213
|
aa(a)
|
|
141
214
|
for r in d.getmembers():
|
|
142
215
|
a, s = q = r.name, r.size
|
|
143
216
|
e()
|
|
144
|
-
if clear
|
|
145
|
-
ar.clear_tar(d)
|
|
217
|
+
av.purge_tar(d) if clear else d.close()
|
|
146
218
|
elif zp and zp.is_zipfile(a):
|
|
147
219
|
with zp.ZipFile(a, md) as d:
|
|
148
220
|
aa(a)
|
|
@@ -151,7 +223,7 @@ def check(
|
|
|
151
223
|
if s:
|
|
152
224
|
c()
|
|
153
225
|
if clear:
|
|
154
|
-
|
|
226
|
+
av.clear_file_in_zip(d, r)
|
|
155
227
|
else:
|
|
156
228
|
f(a)
|
|
157
229
|
elif z7 and z7.is_7zfile(a):
|
|
@@ -161,7 +233,7 @@ def check(
|
|
|
161
233
|
a, s = q = r.filename, r.origin.stat().st_size
|
|
162
234
|
e()
|
|
163
235
|
if clear:
|
|
164
|
-
|
|
236
|
+
av.purge_7z(d)
|
|
165
237
|
elif rr and rr.is_rarfile(a):
|
|
166
238
|
m = 'Cannot clear files in RAR archives'
|
|
167
239
|
with rr.RarFile(a) as d:
|
|
@@ -169,7 +241,19 @@ def check(
|
|
|
169
241
|
for r in d.infolist():
|
|
170
242
|
a, s = q = r.filename, r.file_size
|
|
171
243
|
w()
|
|
172
|
-
|
|
244
|
+
elif ar and v(ar.Archive, ar.ArchiveFormatError):
|
|
245
|
+
aa(a)
|
|
246
|
+
for r in d.infolist():
|
|
247
|
+
a, s = q = r.name, r.size
|
|
248
|
+
e()
|
|
249
|
+
if clear:
|
|
250
|
+
av.purge_ar(d, ar.HEADER_GNU)
|
|
251
|
+
elif ac and v(ac.open, ac.AceError):
|
|
252
|
+
aa(a)
|
|
253
|
+
for r in filter(ac.AceMember.is_reg, d):
|
|
254
|
+
a, s = q = r.filename, r.size
|
|
255
|
+
w()
|
|
256
|
+
elif lh:
|
|
173
257
|
try:
|
|
174
258
|
d = lh.LhaFile(a)
|
|
175
259
|
except (RuntimeError, lh.BadLhafile):
|
|
@@ -179,25 +263,35 @@ def check(
|
|
|
179
263
|
for r in d.filelist:
|
|
180
264
|
a, s = q = r.filename, r.file_size
|
|
181
265
|
w()
|
|
266
|
+
else:
|
|
267
|
+
return False
|
|
182
268
|
return True
|
|
183
269
|
|
|
270
|
+
def hb(a: str) -> bool:
|
|
271
|
+
try:
|
|
272
|
+
return an and ha(a)
|
|
273
|
+
except Exception as e: # ruff: ignore[blind-except]
|
|
274
|
+
i[1](e)
|
|
275
|
+
return True
|
|
276
|
+
|
|
184
277
|
while files:
|
|
185
278
|
a, q = o(), None
|
|
186
279
|
m = isinstance(a, int)
|
|
187
280
|
if not m:
|
|
188
|
-
a =
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
with util.Handler(*i, a) as h:
|
|
192
|
-
q = os.stat(a) # ruff: ignore[os-stat]
|
|
281
|
+
a = fsdecode(a)
|
|
282
|
+
with Handler(*i, a) as h:
|
|
283
|
+
q = stat(a) # ruff: ignore[os-stat]
|
|
193
284
|
if q is None:
|
|
194
285
|
continue
|
|
195
286
|
c = h.e
|
|
196
287
|
if q.st_mode & constants.TYPE_MASK == constants.S_IFDIR:
|
|
197
288
|
if m:
|
|
198
289
|
raise constants.DIRFD_UNSUPPORTED
|
|
199
|
-
|
|
200
|
-
|
|
290
|
+
with h:
|
|
291
|
+
u(scandir(c))
|
|
292
|
+
x(c)
|
|
293
|
+
continue
|
|
294
|
+
if isinstance(a, str) and hb(a):
|
|
201
295
|
continue
|
|
202
296
|
s = q.st_size
|
|
203
297
|
if not s:
|
|
@@ -212,10 +306,11 @@ def check(
|
|
|
212
306
|
m = e()
|
|
213
307
|
a = m.path
|
|
214
308
|
if m.is_dir():
|
|
215
|
-
|
|
216
|
-
|
|
309
|
+
with Handler(*i, a):
|
|
310
|
+
u(scandir(a))
|
|
311
|
+
x(a)
|
|
217
312
|
continue
|
|
218
|
-
if
|
|
313
|
+
if hb(a):
|
|
219
314
|
continue
|
|
220
315
|
n += 1
|
|
221
316
|
s = m.stat().st_size
|
|
@@ -225,10 +320,9 @@ def check(
|
|
|
225
320
|
g((a, s))
|
|
226
321
|
t += s
|
|
227
322
|
if clear:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
x, d = k[1] if z is None else len(z) - 1, k[2] if w is None else len(w) - 1
|
|
323
|
+
Handler(*i, a).o()
|
|
324
|
+
x, d = k[1] if z is None else len(z) - 1, k[2] if w is None else len(w)
|
|
231
325
|
y, p = k[3] if r is None else len(r), k[0] if b is None else len(b) - 1
|
|
232
|
-
reporter.report(z, w, v, b, r, x, y, d, p,
|
|
233
|
-
reporter.
|
|
326
|
+
reporter.report(z, w, v, b, r, x, y, d, p, n, t, verbosity, clear)
|
|
327
|
+
reporter.reset_out()
|
|
234
328
|
return reporter.calculate_result(*map(bool, (y, x and not may_not_exist, d)))
|
|
@@ -4,9 +4,6 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import check_empty as c
|
|
6
6
|
|
|
7
|
-
TYPE_CHECKING = False
|
|
8
|
-
if TYPE_CHECKING:
|
|
9
|
-
from collections.abc import Iterable
|
|
10
7
|
__all__ = ('main',)
|
|
11
8
|
_p = __import__('argparse').ArgumentParser(
|
|
12
9
|
'check-empty',
|
|
@@ -33,6 +30,19 @@ f(
|
|
|
33
30
|
f('-Q', '--quiet', action='count', default=0, help='decrease output verbosity')
|
|
34
31
|
f('-V', '--verbose', action='count', default=0, help='increase output verbosity')
|
|
35
32
|
f('-o', '--out', help='write output to this file instead of stdout')
|
|
33
|
+
f = _p.add_argument_group(
|
|
34
|
+
'archive recursion', 'options for recursing into archives'
|
|
35
|
+
).add_argument
|
|
36
|
+
f('-z', '--zip', action='store_true', help='recurse into .zip archives')
|
|
37
|
+
f('-t', '--tar', action='store_true', help='recurse into .tar archives')
|
|
38
|
+
f('-r', '--rar', action='store_true', help='recurse into .rar archives')
|
|
39
|
+
f('-a', '--ar', action='store_true', help='recurse into .a, .ar and .lib archives')
|
|
40
|
+
f('-7', '--7z', action='store_true', dest='z7', help='recurse into .7z archives')
|
|
41
|
+
f('-l', '--lzh', action='store_true', help='recurse into .lzh and .lha archives')
|
|
42
|
+
f('-A', '--ace', action='store_true', help='recurse into .ace archives')
|
|
43
|
+
TYPE_CHECKING = False
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from collections.abc import Iterable
|
|
36
46
|
|
|
37
47
|
|
|
38
48
|
def _r():
|
|
@@ -57,10 +67,16 @@ def main(argv: Iterable[str] | None = None) -> int:
|
|
|
57
67
|
if o is not None:
|
|
58
68
|
# ruff: ignore[open-file-with-context-handler]
|
|
59
69
|
c.default_reporter.to(open(o, 'w', encoding='utf-8'))
|
|
70
|
+
m = c.constants
|
|
71
|
+
r = m.RecurseInto.NONE
|
|
72
|
+
for a in m.ARCHIVE_FORMATS:
|
|
73
|
+
if getattr(n, a._name_.lower()):
|
|
74
|
+
r |= a
|
|
60
75
|
return c.check(
|
|
61
76
|
n.filenames,
|
|
62
77
|
clear=n.clear,
|
|
63
78
|
may_not_exist=n.may_not_exist,
|
|
79
|
+
recurse_into=r,
|
|
64
80
|
verbosity=2 + n.verbose - n.quiet,
|
|
65
81
|
)
|
|
66
82
|
|