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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: check-empty
3
- Version: 1.3.0
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 ; python_full_version >= '3.10' and extra == '7z'
32
- Requires-Dist: py7zr==1.0.0 ; python_full_version == '3.9.*' and extra == '7z'
33
- Requires-Dist: py7zr==0.22.0 ; python_full_version == '3.8.*' and extra == '7z'
34
- Requires-Dist: py7zr==0.20.6 ; python_full_version == '3.7.*' and extra == '7z'
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 ; python_full_version >= '3.10' and extra == 'rar'
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.7
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 CPython 3.7+, PyPy 7.3.4+, GraalPy 19.0+ out-of-the-box, and most likely every
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: v1.3.0 # repository version
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 = "v1.3.0"
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 = "v1.3.0"
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@v1.3.0 # the latest version on the GitHub Actions
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 descriptions thereof.
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, to avoid having it misinterpreted as a flag,
219
- use a command of the form `check-empty -- -this_is_actually_a_file.txt`.
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 are empty,
227
- but this project explicitly targets files, since version control systems track files
228
- rather than directories.
229
- 6. The program does not recurse into archives, since identification of compressed
230
- archives would require reading the first few bytes of each file seen, which is
231
- error-prone and inefficient.
232
-
233
- ## Additional Links
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 # explicit; or
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 CPython 3.7+, PyPy 7.3.4+, GraalPy 19.0+ out-of-the-box, and most likely every
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: v1.3.0 # repository version
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 = "v1.3.0"
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 = "v1.3.0"
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@v1.3.0 # the latest version on the GitHub Actions
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 descriptions thereof.
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, to avoid having it misinterpreted as a flag,
172
- use a command of the form `check-empty -- -this_is_actually_a_file.txt`.
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 are empty,
180
- but this project explicitly targets files, since version control systems track files
181
- rather than directories.
182
- 6. The program does not recurse into archives, since identification of compressed
183
- archives would require reading the first few bytes of each file seen, which is
184
- error-prone and inefficient.
185
-
186
- ## Additional Links
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 # explicit; or
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 and directories.
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, util
13
+ from . import constants, reporter
13
14
 
14
- if constants.TYPE_CHECKING:
15
- import typing
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__ = '1.3.0'
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: typing.SupportsIndex = 2,
32
- recurse_into: typing.SupportsIndex = constants.RecurseInto.NONE,
33
- reporter: reporter.Reporter = default_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, remember to pass a ``verbosity`` greater than or equal
39
- to 5, since some info will be missing from the parameters passed to
40
- :meth:`Reporter.report` otherwise.
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 ar
166
+ from . import archives as av
97
167
  md = 'a' if clear else 'r'
98
- if constants.TYPE_CHECKING:
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
- y = []
138
- if tr and util.try_tar(a, y.append):
139
- d = y.pop()
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
- ar.clear_tar(d)
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
- ar.clear_file_in_zip(d, r)
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
- ar.clear_7z(d)
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
- else:
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
- if ha(a):
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 ha(a):
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
- util.Handler(*i, a).o()
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, t, n, verbosity, clear)
233
- reporter.reset_stream()
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