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