check-empty 1.2.3__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.2.3 → check_empty-2.0.0}/PKG-INFO +45 -29
- {check_empty-1.2.3 → check_empty-2.0.0}/README.md +31 -24
- check_empty-2.0.0/check_empty/__init__.py +334 -0
- {check_empty-1.2.3 → check_empty-2.0.0}/check_empty/__main__.py +17 -1
- 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.2.3 → check_empty-2.0.0}/pyproject.toml +46 -18
- check_empty-2.0.0/pyproject.toml.orig +91 -0
- check_empty-1.2.3/check_empty/__init__.py +0 -276
- check_empty-1.2.3/pyproject.toml.orig +0 -68
- {check_empty-1.2.3 → check_empty-2.0.0}/AUTHORS.md +0 -0
- {check_empty-1.2.3 → check_empty-2.0.0}/LICENSE +0 -0
- {check_empty-1.2.3 → 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,13 +25,27 @@ 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
|
|
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'
|
|
32
|
+
Requires-Dist: lhafile==0.3.1 ; extra == 'lzh'
|
|
33
|
+
Requires-Dist: rarfile==4.5 ; extra == 'rar'
|
|
31
34
|
Maintainer: Jonathan Dung
|
|
32
35
|
Maintainer-email: Jonathan Dung <jonathandung@yahoo.com>
|
|
33
|
-
Requires-Python: >=3.
|
|
36
|
+
Requires-Python: >=3.10
|
|
37
|
+
Provides-Extra: 7z
|
|
38
|
+
Provides-Extra: ace
|
|
39
|
+
Provides-Extra: all
|
|
40
|
+
Provides-Extra: ar
|
|
41
|
+
Provides-Extra: lzh
|
|
42
|
+
Provides-Extra: rar
|
|
34
43
|
Description-Content-Type: text/markdown
|
|
35
44
|
|
|
36
45
|
# [check-empty](https://pypi.org/p/check-empty)
|
|
37
46
|
|
|
47
|
+
[](https://github.com/TheBSD/StandWithPalestine/blob/main/docs/README.md)
|
|
48
|
+
|
|
38
49
|
A simple, dependency-free [pre-commit](https://pre-commit.com) /
|
|
39
50
|
[prek](https://prek.j178.dev) hook, CLI, library and
|
|
40
51
|
[GitHub Action](https://github.com/marketplace/actions/check-empty-files) conglomerate
|
|
@@ -46,8 +57,7 @@ specified.
|
|
|
46
57
|
|
|
47
58
|
## Prerequisites
|
|
48
59
|
|
|
49
|
-
Supports
|
|
50
|
-
Python 3.6 runtime you can think of. This is the only requirement to use this tool.
|
|
60
|
+
Supports every Python 3.10 runtime. There are no other requirements.
|
|
51
61
|
|
|
52
62
|
## Quickstart
|
|
53
63
|
|
|
@@ -115,7 +125,7 @@ As a pre-commit hook:
|
|
|
115
125
|
# .pre-commit-config.yaml
|
|
116
126
|
repos:
|
|
117
127
|
- repo: https://github.com/jonathandung/check-empty
|
|
118
|
-
rev:
|
|
128
|
+
rev: v2.0.0 # repository version
|
|
119
129
|
hooks:
|
|
120
130
|
- id: check-empty # the hook
|
|
121
131
|
args: # example list of arguments
|
|
@@ -131,7 +141,7 @@ equivalent in `prek.toml` format:
|
|
|
131
141
|
```toml
|
|
132
142
|
[[repos]]
|
|
133
143
|
repo = "https://github.com/jonathandung/check-empty"
|
|
134
|
-
rev = "
|
|
144
|
+
rev = "v2.0.0"
|
|
135
145
|
|
|
136
146
|
[[repos.hooks]]
|
|
137
147
|
id = "check-empty"
|
|
@@ -155,7 +165,7 @@ or (TOML 1.1+):
|
|
|
155
165
|
# using multiline inline tables
|
|
156
166
|
[[repos]]
|
|
157
167
|
repo = "https://github.com/jonathandung/check-empty"
|
|
158
|
-
rev = "
|
|
168
|
+
rev = "v2.0.0"
|
|
159
169
|
hooks = [{
|
|
160
170
|
id = "check-empty",
|
|
161
171
|
args = ["-Q"],
|
|
@@ -175,13 +185,11 @@ As a GitHub Actions workflow step:
|
|
|
175
185
|
|
|
176
186
|
```yaml
|
|
177
187
|
steps:
|
|
178
|
-
- uses: jonathandung/check-empty@
|
|
188
|
+
- uses: jonathandung/check-empty@v2.0.0 # the latest version on the GitHub Actions
|
|
179
189
|
# marketplace; this step will fail and subsequent jobs will not run if any file is
|
|
180
190
|
# not empty
|
|
181
191
|
with:
|
|
182
192
|
python-version: '3.14' # run the script on the latest stable Python version
|
|
183
|
-
# Python down to 3.6 is supported but not recommended due to end-of-life
|
|
184
|
-
verbosity: 1
|
|
185
193
|
filenames: |
|
|
186
194
|
src/mylib/py.typed
|
|
187
195
|
docs/.nojekyll
|
|
@@ -194,26 +202,34 @@ steps:
|
|
|
194
202
|
|
|
195
203
|
Also see the GitHub Action
|
|
196
204
|
[manifest](https://github.com/jonathandung/check-empty/blob/main/action.yaml), which
|
|
197
|
-
contains the accepted action inputs and
|
|
205
|
+
contains the accepted action inputs, action outputs produced and their respective
|
|
206
|
+
descriptions.
|
|
198
207
|
|
|
199
208
|
## Notes
|
|
200
209
|
|
|
201
|
-
1. If your file name starts with a hyphen,
|
|
202
|
-
|
|
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.
|
|
203
213
|
2. Forward slashes can be used even on Windows, so there is no need to escape anything.
|
|
204
214
|
3. Glob patterns are supported on \*nix only. If on Windows, use a shell like Git Bash.
|
|
205
215
|
4. To pass an
|
|
206
216
|
[argfile](https://docs.python.org/3/library/argparse.html#fromfile-prefix-chars), use
|
|
207
217
|
the `@` prefix, and escape files whose names actually start with `@` using the
|
|
208
218
|
double-hyphen syntax.
|
|
209
|
-
5. It may be unintuitive that a directory being "empty" means all its files
|
|
210
|
-
but this project explicitly targets files, since version control systems
|
|
211
|
-
rather than directories.
|
|
212
|
-
6. The program
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
|
217
233
|
|
|
218
234
|
- [API and CLI Reference](https://check-empty.readthedocs.io)
|
|
219
235
|
- [Releases](https://github.com/jonathandung/check-empty/releases)
|
|
@@ -227,15 +243,15 @@ to read the
|
|
|
227
243
|
and the
|
|
228
244
|
[contributing guide](https://github.com/jonathandung/.github/blob/main/CONTRIBUTING.md).
|
|
229
245
|
|
|
230
|
-
To build the docs locally (needs Python 3.12+), install with the
|
|
231
|
-
[group](https://packaging.python.org/en/latest/specifications/dependency-groups)
|
|
232
|
-
|
|
246
|
+
To build the docs locally (needs Python 3.12+ because of Sphinx), install with the
|
|
247
|
+
`docs` [group](https://packaging.python.org/en/latest/specifications/dependency-groups)
|
|
248
|
+
using a package manager that supports it (e.g. pip 25.1+ or uv 0.4.27+), preferably
|
|
249
|
+
into a virtual environment.
|
|
233
250
|
|
|
234
251
|
Tests are run with:
|
|
235
252
|
|
|
236
253
|
```bash
|
|
237
|
-
python -m test_check_empty
|
|
238
|
-
python -m unittest discover # alternative
|
|
254
|
+
python -m test_check_empty
|
|
239
255
|
```
|
|
240
256
|
|
|
241
257
|
at the project root. `pytest` is not needed.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# [check-empty](https://pypi.org/p/check-empty)
|
|
2
2
|
|
|
3
|
+
[](https://github.com/TheBSD/StandWithPalestine/blob/main/docs/README.md)
|
|
4
|
+
|
|
3
5
|
A simple, dependency-free [pre-commit](https://pre-commit.com) /
|
|
4
6
|
[prek](https://prek.j178.dev) hook, CLI, library and
|
|
5
7
|
[GitHub Action](https://github.com/marketplace/actions/check-empty-files) conglomerate
|
|
@@ -11,8 +13,7 @@ specified.
|
|
|
11
13
|
|
|
12
14
|
## Prerequisites
|
|
13
15
|
|
|
14
|
-
Supports
|
|
15
|
-
Python 3.6 runtime you can think of. This is the only requirement to use this tool.
|
|
16
|
+
Supports every Python 3.10 runtime. There are no other requirements.
|
|
16
17
|
|
|
17
18
|
## Quickstart
|
|
18
19
|
|
|
@@ -80,7 +81,7 @@ As a pre-commit hook:
|
|
|
80
81
|
# .pre-commit-config.yaml
|
|
81
82
|
repos:
|
|
82
83
|
- repo: https://github.com/jonathandung/check-empty
|
|
83
|
-
rev:
|
|
84
|
+
rev: v2.0.0 # repository version
|
|
84
85
|
hooks:
|
|
85
86
|
- id: check-empty # the hook
|
|
86
87
|
args: # example list of arguments
|
|
@@ -96,7 +97,7 @@ equivalent in `prek.toml` format:
|
|
|
96
97
|
```toml
|
|
97
98
|
[[repos]]
|
|
98
99
|
repo = "https://github.com/jonathandung/check-empty"
|
|
99
|
-
rev = "
|
|
100
|
+
rev = "v2.0.0"
|
|
100
101
|
|
|
101
102
|
[[repos.hooks]]
|
|
102
103
|
id = "check-empty"
|
|
@@ -120,7 +121,7 @@ or (TOML 1.1+):
|
|
|
120
121
|
# using multiline inline tables
|
|
121
122
|
[[repos]]
|
|
122
123
|
repo = "https://github.com/jonathandung/check-empty"
|
|
123
|
-
rev = "
|
|
124
|
+
rev = "v2.0.0"
|
|
124
125
|
hooks = [{
|
|
125
126
|
id = "check-empty",
|
|
126
127
|
args = ["-Q"],
|
|
@@ -140,13 +141,11 @@ As a GitHub Actions workflow step:
|
|
|
140
141
|
|
|
141
142
|
```yaml
|
|
142
143
|
steps:
|
|
143
|
-
- uses: jonathandung/check-empty@
|
|
144
|
+
- uses: jonathandung/check-empty@v2.0.0 # the latest version on the GitHub Actions
|
|
144
145
|
# marketplace; this step will fail and subsequent jobs will not run if any file is
|
|
145
146
|
# not empty
|
|
146
147
|
with:
|
|
147
148
|
python-version: '3.14' # run the script on the latest stable Python version
|
|
148
|
-
# Python down to 3.6 is supported but not recommended due to end-of-life
|
|
149
|
-
verbosity: 1
|
|
150
149
|
filenames: |
|
|
151
150
|
src/mylib/py.typed
|
|
152
151
|
docs/.nojekyll
|
|
@@ -159,26 +158,34 @@ steps:
|
|
|
159
158
|
|
|
160
159
|
Also see the GitHub Action
|
|
161
160
|
[manifest](https://github.com/jonathandung/check-empty/blob/main/action.yaml), which
|
|
162
|
-
contains the accepted action inputs and
|
|
161
|
+
contains the accepted action inputs, action outputs produced and their respective
|
|
162
|
+
descriptions.
|
|
163
163
|
|
|
164
164
|
## Notes
|
|
165
165
|
|
|
166
|
-
1. If your file name starts with a hyphen,
|
|
167
|
-
|
|
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.
|
|
168
169
|
2. Forward slashes can be used even on Windows, so there is no need to escape anything.
|
|
169
170
|
3. Glob patterns are supported on \*nix only. If on Windows, use a shell like Git Bash.
|
|
170
171
|
4. To pass an
|
|
171
172
|
[argfile](https://docs.python.org/3/library/argparse.html#fromfile-prefix-chars), use
|
|
172
173
|
the `@` prefix, and escape files whose names actually start with `@` using the
|
|
173
174
|
double-hyphen syntax.
|
|
174
|
-
5. It may be unintuitive that a directory being "empty" means all its files
|
|
175
|
-
but this project explicitly targets files, since version control systems
|
|
176
|
-
rather than directories.
|
|
177
|
-
6. The program
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
|
182
189
|
|
|
183
190
|
- [API and CLI Reference](https://check-empty.readthedocs.io)
|
|
184
191
|
- [Releases](https://github.com/jonathandung/check-empty/releases)
|
|
@@ -192,15 +199,15 @@ to read the
|
|
|
192
199
|
and the
|
|
193
200
|
[contributing guide](https://github.com/jonathandung/.github/blob/main/CONTRIBUTING.md).
|
|
194
201
|
|
|
195
|
-
To build the docs locally (needs Python 3.12+), install with the
|
|
196
|
-
[group](https://packaging.python.org/en/latest/specifications/dependency-groups)
|
|
197
|
-
|
|
202
|
+
To build the docs locally (needs Python 3.12+ because of Sphinx), install with the
|
|
203
|
+
`docs` [group](https://packaging.python.org/en/latest/specifications/dependency-groups)
|
|
204
|
+
using a package manager that supports it (e.g. pip 25.1+ or uv 0.4.27+), preferably
|
|
205
|
+
into a virtual environment.
|
|
198
206
|
|
|
199
207
|
Tests are run with:
|
|
200
208
|
|
|
201
209
|
```bash
|
|
202
|
-
python -m test_check_empty
|
|
203
|
-
python -m unittest discover # alternative
|
|
210
|
+
python -m test_check_empty
|
|
204
211
|
```
|
|
205
212
|
|
|
206
213
|
at the project root. `pytest` is not needed.
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# Copyright © 2026 Jonathan Dung. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""Utility to check the emptiness of files.
|
|
4
|
+
|
|
5
|
+
Pre-commit hook, command-line tool and GitHub Action all-in-one.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from contextlib import suppress
|
|
12
|
+
|
|
13
|
+
from . import constants, reporter
|
|
14
|
+
|
|
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
|
|
20
|
+
|
|
21
|
+
from _typeshed import FileDescriptorOrPath
|
|
22
|
+
from typing_extensions import Self
|
|
23
|
+
|
|
24
|
+
__all__ = ('Handler', 'check', 'default_reporter')
|
|
25
|
+
__version__ = '2.0.0'
|
|
26
|
+
"""The version of the package."""
|
|
27
|
+
default_reporter: reporter.Reporter = reporter.Reporter()
|
|
28
|
+
"""The default reporter used by :func:`check`."""
|
|
29
|
+
|
|
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
|
+
|
|
100
|
+
def check(
|
|
101
|
+
files: Iterable[FileDescriptorOrPath],
|
|
102
|
+
*,
|
|
103
|
+
clear: bool = False,
|
|
104
|
+
may_not_exist: bool = False,
|
|
105
|
+
verbosity: SupportsIndex = 2,
|
|
106
|
+
recurse_into: SupportsIndex = constants.RecurseInto.NONE,
|
|
107
|
+
reporter: reporter.abc.ReporterABC = default_reporter,
|
|
108
|
+
) -> int:
|
|
109
|
+
# ruff: ignore[docstring-missing-exception]
|
|
110
|
+
r"""Check the emptiness of files, recursing into directories if passed.
|
|
111
|
+
|
|
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.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
files: an iterable of file descriptors or paths representing the files and
|
|
119
|
+
directories to check; directory descriptors (\*nix) are not supported.
|
|
120
|
+
clear: if ``True``, clear the contents of non-empty files; directories, notably,
|
|
121
|
+
are not purged, but all files within should become empty.
|
|
122
|
+
may_not_exist: if ``True``, do not treat absent files or directories as errors.
|
|
123
|
+
verbosity: how much detail the program should print to stdout.
|
|
124
|
+
recurse_into: a bitwise or of flags indicating which types of archives to
|
|
125
|
+
recurse into; by default, all archives are treated as regular files.
|
|
126
|
+
reporter: The reporter to use for reporting the results. All output of this
|
|
127
|
+
function is produced by the reporter.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
The integer exit code calculated by the reporter.
|
|
131
|
+
|
|
132
|
+
"""
|
|
133
|
+
files = list(files)
|
|
134
|
+
if not files:
|
|
135
|
+
return 0
|
|
136
|
+
k, j, t, n, o = [0] * 5, [], 0, len(files), files.pop
|
|
137
|
+
u, e, _ = j.extend, j.pop, lambda v: lambda _, k=k: k.__setitem__(v, k[v] + 1)
|
|
138
|
+
verbosity, recurse_into = (type(x).__index__(x) for x in (verbosity, recurse_into))
|
|
139
|
+
# ruff: disable[magic-value-comparison]
|
|
140
|
+
if verbosity > 4:
|
|
141
|
+
b = ['']
|
|
142
|
+
f = b.append
|
|
143
|
+
else:
|
|
144
|
+
b, f = None, _(0)
|
|
145
|
+
if verbosity > 3:
|
|
146
|
+
v, ra = [''], ['']
|
|
147
|
+
x, aa = v.append, ra.append
|
|
148
|
+
else:
|
|
149
|
+
v = ra = None
|
|
150
|
+
x = aa = lambda _: None
|
|
151
|
+
if verbosity > 2:
|
|
152
|
+
z, w = [''], ['']
|
|
153
|
+
i = z.append, w.append
|
|
154
|
+
else:
|
|
155
|
+
z = w = None
|
|
156
|
+
i = _(1), _(2)
|
|
157
|
+
if verbosity > 1:
|
|
158
|
+
r = []
|
|
159
|
+
g = r.append
|
|
160
|
+
else:
|
|
161
|
+
r, g = None, _(3)
|
|
162
|
+
# ruff: enable[magic-value-comparison]
|
|
163
|
+
zp, tr, rr, z7, ar, lh, ac = map(recurse_into.__and__, constants.ARCHIVE_FORMATS)
|
|
164
|
+
an = bool(recurse_into)
|
|
165
|
+
if an:
|
|
166
|
+
from . import archives as av
|
|
167
|
+
md = 'a' if clear else 'r'
|
|
168
|
+
if TYPE_CHECKING:
|
|
169
|
+
import tarfile as tr
|
|
170
|
+
import zipfile as zp
|
|
171
|
+
|
|
172
|
+
import acefile as ac
|
|
173
|
+
import arpy as ar
|
|
174
|
+
import lhafile as lh
|
|
175
|
+
import py7zr as z7
|
|
176
|
+
import rarfile as rr
|
|
177
|
+
else:
|
|
178
|
+
if tr:
|
|
179
|
+
import tarfile as tr
|
|
180
|
+
if zp:
|
|
181
|
+
import zipfile as zp
|
|
182
|
+
if ac:
|
|
183
|
+
import acefile as ac
|
|
184
|
+
if ar:
|
|
185
|
+
import arpy as ar
|
|
186
|
+
if lh:
|
|
187
|
+
import lhafile as lh
|
|
188
|
+
if z7:
|
|
189
|
+
import py7zr as z7
|
|
190
|
+
if rr:
|
|
191
|
+
import rarfile as rr
|
|
192
|
+
|
|
193
|
+
def ha(a: str) -> bool:
|
|
194
|
+
|
|
195
|
+
def c() -> None:
|
|
196
|
+
nonlocal t
|
|
197
|
+
g(q)
|
|
198
|
+
t += s
|
|
199
|
+
|
|
200
|
+
def e() -> None:
|
|
201
|
+
c() if s else f(a)
|
|
202
|
+
|
|
203
|
+
def w() -> None:
|
|
204
|
+
if s:
|
|
205
|
+
if clear:
|
|
206
|
+
raise NotImplementedError(m)
|
|
207
|
+
c()
|
|
208
|
+
else:
|
|
209
|
+
f(a)
|
|
210
|
+
|
|
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'):
|
|
219
|
+
aa(a)
|
|
220
|
+
for r in d.getmembers():
|
|
221
|
+
a, s = q = r.name, r.size
|
|
222
|
+
e()
|
|
223
|
+
if clear:
|
|
224
|
+
av.purge_tar(d)
|
|
225
|
+
elif zp and zp.is_zipfile(a):
|
|
226
|
+
with zp.ZipFile(a, md) as d:
|
|
227
|
+
aa(a)
|
|
228
|
+
for r in d.infolist():
|
|
229
|
+
a, s = q = r.filename, r.file_size
|
|
230
|
+
if s:
|
|
231
|
+
c()
|
|
232
|
+
if clear:
|
|
233
|
+
av.clear_file_in_zip(d, r)
|
|
234
|
+
else:
|
|
235
|
+
f(a)
|
|
236
|
+
elif z7 and z7.is_7zfile(a):
|
|
237
|
+
with z7.SevenZipFile(a) as d:
|
|
238
|
+
aa(a)
|
|
239
|
+
for r in d.files:
|
|
240
|
+
a, s = q = r.filename, r.origin.stat().st_size
|
|
241
|
+
e()
|
|
242
|
+
if clear:
|
|
243
|
+
av.purge_7z(d)
|
|
244
|
+
elif rr and rr.is_rarfile(a):
|
|
245
|
+
m = 'Cannot clear files in RAR archives'
|
|
246
|
+
with rr.RarFile(a) as d:
|
|
247
|
+
aa(a)
|
|
248
|
+
for r in d.infolist():
|
|
249
|
+
a, s = q = r.filename, r.file_size
|
|
250
|
+
w()
|
|
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:
|
|
264
|
+
try:
|
|
265
|
+
d = lh.LhaFile(a)
|
|
266
|
+
except (RuntimeError, lh.BadLhafile):
|
|
267
|
+
return False
|
|
268
|
+
m = 'Cannot clear files in LHA archives'
|
|
269
|
+
aa(a)
|
|
270
|
+
for r in d.filelist:
|
|
271
|
+
a, s = q = r.filename, r.file_size
|
|
272
|
+
w()
|
|
273
|
+
else:
|
|
274
|
+
return False
|
|
275
|
+
return True
|
|
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
|
+
|
|
284
|
+
while files:
|
|
285
|
+
a, q = o(), None
|
|
286
|
+
m = isinstance(a, int)
|
|
287
|
+
if not m:
|
|
288
|
+
a = os.fsdecode(a)
|
|
289
|
+
with Handler(*i, a) as h:
|
|
290
|
+
q = os.stat(a) # ruff: ignore[os-stat]
|
|
291
|
+
if q is None:
|
|
292
|
+
continue
|
|
293
|
+
c = h.e
|
|
294
|
+
if q.st_mode & constants.TYPE_MASK == constants.S_IFDIR:
|
|
295
|
+
if m:
|
|
296
|
+
raise constants.DIRFD_UNSUPPORTED
|
|
297
|
+
x(c)
|
|
298
|
+
u(os.scandir(c))
|
|
299
|
+
continue
|
|
300
|
+
if isinstance(a, str) and hb(a):
|
|
301
|
+
continue
|
|
302
|
+
s = q.st_size
|
|
303
|
+
if not s:
|
|
304
|
+
f(c)
|
|
305
|
+
continue
|
|
306
|
+
g((c, s))
|
|
307
|
+
t += s
|
|
308
|
+
if clear:
|
|
309
|
+
h.o()
|
|
310
|
+
del o, files
|
|
311
|
+
while j:
|
|
312
|
+
m = e()
|
|
313
|
+
a = m.path
|
|
314
|
+
if m.is_dir():
|
|
315
|
+
x(a)
|
|
316
|
+
u(os.scandir(a))
|
|
317
|
+
continue
|
|
318
|
+
if hb(a):
|
|
319
|
+
continue
|
|
320
|
+
n += 1
|
|
321
|
+
s = m.stat().st_size
|
|
322
|
+
if not s:
|
|
323
|
+
f(a)
|
|
324
|
+
continue
|
|
325
|
+
g((a, s))
|
|
326
|
+
t += s
|
|
327
|
+
if clear:
|
|
328
|
+
Handler(*i, a).o()
|
|
329
|
+
del j
|
|
330
|
+
x, d = k[1] if z is None else len(z) - 1, k[2] if w is None else len(w) - 1
|
|
331
|
+
y, p = k[3] if r is None else len(r), k[0] if b is None else len(b) - 1
|
|
332
|
+
reporter.report(z, w, v, b, r, x, y, d, p, n, t, verbosity, clear)
|
|
333
|
+
reporter.reset_out()
|
|
334
|
+
return reporter.calculate_result(*map(bool, (y, x and not may_not_exist, d)))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Implementation of the main routine.
|
|
1
|
+
"""Implementation of the main routine."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -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
|
|