check-empty 0.2.0__tar.gz → 0.6.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-0.2.0 → check_empty-0.6.0}/AUTHORS.md +1 -1
- {check_empty-0.2.0 → check_empty-0.6.0}/PKG-INFO +53 -20
- check_empty-0.6.0/README.md +110 -0
- check_empty-0.6.0/check_empty/__init__.py +184 -0
- check_empty-0.6.0/check_empty/__main__.py +58 -0
- {check_empty-0.2.0 → check_empty-0.6.0}/pyproject.toml +11 -7
- check_empty-0.2.0/README.md +0 -77
- check_empty-0.2.0/check_empty/__init__.py +0 -9
- check_empty-0.2.0/check_empty/__main__.py +0 -83
- {check_empty-0.2.0 → check_empty-0.6.0}/LICENSE +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: check-empty
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Command-line utility, as well as a hook usable by the Python pre-commit framework, to ensure that selected files are empty.
|
|
5
|
-
Keywords: check,command-line,empty,github-action,hook,lightweight,pre-commit,prek,tool,utility
|
|
3
|
+
Version: 0.6.0
|
|
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
|
+
Keywords: check,command-line,empty,files,github-action,helper,hook,lightweight,pre-commit,prek,tool,utility
|
|
6
6
|
Author: Jonathan Dung
|
|
7
7
|
Author-email: Jonathan Dung <jonathandung@yahoo.com>
|
|
8
8
|
License-Expression: MIT
|
|
@@ -29,33 +29,44 @@ Classifier: Topic :: Utilities
|
|
|
29
29
|
Classifier: Typing :: Typed
|
|
30
30
|
Maintainer: Jonathan Dung
|
|
31
31
|
Maintainer-email: Jonathan Dung <jonathandung@yahoo.com>
|
|
32
|
-
Requires-Python: >=3.
|
|
32
|
+
Requires-Python: >=3.6
|
|
33
33
|
Description-Content-Type: text/markdown
|
|
34
34
|
|
|
35
35
|
# check-empty
|
|
36
36
|
|
|
37
|
-
A simple [pre-commit](https://pre-commit.com)/
|
|
37
|
+
A simple, dependency-free and intuitive [pre-commit](https://pre-commit.com) /
|
|
38
|
+
[prek](https://prek.j178.dev) hook, CLI, library and GitHub Action conglomerate that
|
|
38
39
|
makes sure selected files are empty according to a filesystem stat call and clears them
|
|
39
|
-
effectively
|
|
40
|
-
|
|
40
|
+
effectively with minimal I/O if required, written in Python. Also available as a tool on
|
|
41
|
+
uv. Supports CPython 3.6+, PyPy 7.0+, GraalPy 19.0+ and most likely every Python 3.6
|
|
42
|
+
runtime you can think of.
|
|
41
43
|
|
|
42
44
|
## Quickstart
|
|
43
45
|
|
|
46
|
+
Without installation (testing out the capabilities):
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uvx check-empty -Q src/mylib/py.typed docs/.nojekyll static/.gitkeep some_directory
|
|
50
|
+
```
|
|
51
|
+
|
|
44
52
|
```bash
|
|
45
|
-
|
|
53
|
+
# uv
|
|
54
|
+
uv tool install check-empty # bare executable on PATH
|
|
55
|
+
uv pip install check-empty # if you want the library as well
|
|
56
|
+
|
|
46
57
|
pip install check-empty # pip
|
|
47
58
|
```
|
|
48
59
|
|
|
49
60
|
Check the version with:
|
|
50
61
|
|
|
51
62
|
```bash
|
|
52
|
-
check-empty -v
|
|
63
|
+
check-empty --version # or check-empty -v
|
|
53
64
|
```
|
|
54
65
|
|
|
55
66
|
Run the CLI:
|
|
56
67
|
|
|
57
68
|
```bash
|
|
58
|
-
check-empty -
|
|
69
|
+
check-empty -Q src/mylib/py.typed docs/.nojekyll static/.gitkeep some_directory
|
|
59
70
|
```
|
|
60
71
|
|
|
61
72
|
As a pre-commit hook:
|
|
@@ -64,16 +75,17 @@ As a pre-commit hook:
|
|
|
64
75
|
# .pre-commit-config.yaml
|
|
65
76
|
repos:
|
|
66
77
|
- repo: https://github.com/jonathandung/check-empty
|
|
67
|
-
rev: v0.
|
|
78
|
+
rev: v0.6.0 # repository version
|
|
68
79
|
hooks:
|
|
69
80
|
- id: check-empty # the hook
|
|
70
81
|
args: # example list of arguments
|
|
71
|
-
-
|
|
72
|
-
# below: paths to files to clear or keep empty,
|
|
73
|
-
# root
|
|
82
|
+
- -Q # flag to silence output (shorthand for --quiet)
|
|
83
|
+
# below: paths to files/directories to clear or keep empty, relative to project
|
|
84
|
+
# root (absolute paths are possible but not recommended)
|
|
74
85
|
- src/mylib/py.typed
|
|
75
86
|
- docs/.nojekyll
|
|
76
87
|
- static/.gitkeep
|
|
88
|
+
- some_directory
|
|
77
89
|
```
|
|
78
90
|
|
|
79
91
|
equivalent in `prek.toml` format:
|
|
@@ -81,14 +93,29 @@ equivalent in `prek.toml` format:
|
|
|
81
93
|
```toml
|
|
82
94
|
[[repos]]
|
|
83
95
|
repo = "https://github.com/jonathandung/check-empty"
|
|
84
|
-
rev = "v0.
|
|
96
|
+
rev = "v0.6.0"
|
|
97
|
+
|
|
98
|
+
[[repos.hooks]]
|
|
99
|
+
id = "check-empty"
|
|
100
|
+
args = [
|
|
101
|
+
"-Q",
|
|
102
|
+
"src/mylib/py.typed",
|
|
103
|
+
"docs/.nojekyll",
|
|
104
|
+
"static/.gitkeep",
|
|
105
|
+
"some_directory"
|
|
106
|
+
]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
or a more terse format (TOML 1.1+):
|
|
110
|
+
|
|
111
|
+
```toml
|
|
112
|
+
[[repos]]
|
|
113
|
+
repo = "https://github.com/jonathandung/check-empty"
|
|
114
|
+
rev = "v0.6.0"
|
|
85
115
|
hooks = [{
|
|
86
116
|
id = "check-empty",
|
|
87
117
|
args = [
|
|
88
|
-
"
|
|
89
|
-
"src/mylib/py.typed",
|
|
90
|
-
"docs/.nojekyll",
|
|
91
|
-
"static/.gitkeep",
|
|
118
|
+
"-Q", "src/mylib/py.typed", "docs/.nojekyll", "static/.gitkeep", "some_directory"
|
|
92
119
|
]
|
|
93
120
|
}]
|
|
94
121
|
```
|
|
@@ -97,7 +124,7 @@ As a GitHub action step:
|
|
|
97
124
|
|
|
98
125
|
```yaml
|
|
99
126
|
steps:
|
|
100
|
-
- uses: jonathandung/check-empty@v0.
|
|
127
|
+
- uses: jonathandung/check-empty@v0.6.0 # the latest version on the GitHub Actions
|
|
101
128
|
# marketplace; this step will fail and subsequent jobs will not run if any file is
|
|
102
129
|
# not empty
|
|
103
130
|
with:
|
|
@@ -108,4 +135,10 @@ steps:
|
|
|
108
135
|
src/mylib/py.typed
|
|
109
136
|
docs/.nojekyll
|
|
110
137
|
static/.gitkeep
|
|
138
|
+
some_directory
|
|
111
139
|
```
|
|
140
|
+
|
|
141
|
+
## Development
|
|
142
|
+
|
|
143
|
+
If you wish to contribute to this project, you are more than welcome, but please
|
|
144
|
+
remember to read the [contributing guide](CONTRIBUTING.md).
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# check-empty
|
|
2
|
+
|
|
3
|
+
A simple, dependency-free and intuitive [pre-commit](https://pre-commit.com) /
|
|
4
|
+
[prek](https://prek.j178.dev) hook, CLI, library and GitHub Action conglomerate that
|
|
5
|
+
makes sure selected files are empty according to a filesystem stat call and clears them
|
|
6
|
+
effectively with minimal I/O if required, written in Python. Also available as a tool on
|
|
7
|
+
uv. Supports CPython 3.6+, PyPy 7.0+, GraalPy 19.0+ and most likely every Python 3.6
|
|
8
|
+
runtime you can think of.
|
|
9
|
+
|
|
10
|
+
## Quickstart
|
|
11
|
+
|
|
12
|
+
Without installation (testing out the capabilities):
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
uvx check-empty -Q src/mylib/py.typed docs/.nojekyll static/.gitkeep some_directory
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# uv
|
|
20
|
+
uv tool install check-empty # bare executable on PATH
|
|
21
|
+
uv pip install check-empty # if you want the library as well
|
|
22
|
+
|
|
23
|
+
pip install check-empty # pip
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Check the version with:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
check-empty --version # or check-empty -v
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Run the CLI:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
check-empty -Q src/mylib/py.typed docs/.nojekyll static/.gitkeep some_directory
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
As a pre-commit hook:
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
# .pre-commit-config.yaml
|
|
42
|
+
repos:
|
|
43
|
+
- repo: https://github.com/jonathandung/check-empty
|
|
44
|
+
rev: v0.6.0 # repository version
|
|
45
|
+
hooks:
|
|
46
|
+
- id: check-empty # the hook
|
|
47
|
+
args: # example list of arguments
|
|
48
|
+
- -Q # flag to silence output (shorthand for --quiet)
|
|
49
|
+
# below: paths to files/directories to clear or keep empty, relative to project
|
|
50
|
+
# root (absolute paths are possible but not recommended)
|
|
51
|
+
- src/mylib/py.typed
|
|
52
|
+
- docs/.nojekyll
|
|
53
|
+
- static/.gitkeep
|
|
54
|
+
- some_directory
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
equivalent in `prek.toml` format:
|
|
58
|
+
|
|
59
|
+
```toml
|
|
60
|
+
[[repos]]
|
|
61
|
+
repo = "https://github.com/jonathandung/check-empty"
|
|
62
|
+
rev = "v0.6.0"
|
|
63
|
+
|
|
64
|
+
[[repos.hooks]]
|
|
65
|
+
id = "check-empty"
|
|
66
|
+
args = [
|
|
67
|
+
"-Q",
|
|
68
|
+
"src/mylib/py.typed",
|
|
69
|
+
"docs/.nojekyll",
|
|
70
|
+
"static/.gitkeep",
|
|
71
|
+
"some_directory"
|
|
72
|
+
]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
or a more terse format (TOML 1.1+):
|
|
76
|
+
|
|
77
|
+
```toml
|
|
78
|
+
[[repos]]
|
|
79
|
+
repo = "https://github.com/jonathandung/check-empty"
|
|
80
|
+
rev = "v0.6.0"
|
|
81
|
+
hooks = [{
|
|
82
|
+
id = "check-empty",
|
|
83
|
+
args = [
|
|
84
|
+
"-Q", "src/mylib/py.typed", "docs/.nojekyll", "static/.gitkeep", "some_directory"
|
|
85
|
+
]
|
|
86
|
+
}]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
As a GitHub action step:
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
steps:
|
|
93
|
+
- uses: jonathandung/check-empty@v0.6.0 # the latest version on the GitHub Actions
|
|
94
|
+
# marketplace; this step will fail and subsequent jobs will not run if any file is
|
|
95
|
+
# not empty
|
|
96
|
+
with:
|
|
97
|
+
python-version: '3.14' # run the script on the latest stable Python version
|
|
98
|
+
# Python down to 3.6 is supported but not recommended due to end-of-life
|
|
99
|
+
quiet: true
|
|
100
|
+
filenames: |
|
|
101
|
+
src/mylib/py.typed
|
|
102
|
+
docs/.nojekyll
|
|
103
|
+
static/.gitkeep
|
|
104
|
+
some_directory
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Development
|
|
108
|
+
|
|
109
|
+
If you wish to contribute to this project, you are more than welcome, but please
|
|
110
|
+
remember to read the [contributing guide](CONTRIBUTING.md).
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Copyright © 2026 Jonathan Dung. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""Utility to check the emptiness of files and directories.
|
|
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
|
+
|
|
12
|
+
__all__ = ('check',)
|
|
13
|
+
__version__ = '0.6.0'
|
|
14
|
+
|
|
15
|
+
TYPE_CHECKING = False
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from collections.abc import Iterable
|
|
18
|
+
|
|
19
|
+
from _typeshed import FileDescriptorOrPath
|
|
20
|
+
from typing_extensions import Literal, SupportsIndex
|
|
21
|
+
|
|
22
|
+
DIRECTORY_DESCRIPTOR_UNSUPPORTED: BaseException = (
|
|
23
|
+
SystemError('got directory descriptor on Windows??')
|
|
24
|
+
if os.name == 'nt'
|
|
25
|
+
else NotImplementedError('directory descriptors are not supported')
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class _Handler:
|
|
30
|
+
__slots__ = 'a', 'c', 'f', 'j', 'v'
|
|
31
|
+
|
|
32
|
+
def __init__(self, *a):
|
|
33
|
+
self.j, self.f, self.a = a
|
|
34
|
+
self.v = None
|
|
35
|
+
|
|
36
|
+
def __enter__(self):
|
|
37
|
+
self.c = False
|
|
38
|
+
return self
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def e(self, s='invalid fd (negative): %d', t='fd: %d'): # ruff: ignore[property-with-parameters]
|
|
42
|
+
r = self.v
|
|
43
|
+
if r is not None:
|
|
44
|
+
return r
|
|
45
|
+
a = self.a
|
|
46
|
+
self.v = r = (s if a < 0 else t) % a if isinstance(a, int) else os.fsdecode(a)
|
|
47
|
+
return r
|
|
48
|
+
|
|
49
|
+
def _x(self, v):
|
|
50
|
+
self.j(self.e) if v.errno == 2 else self.f(str(v))
|
|
51
|
+
|
|
52
|
+
def __exit__(self, t, v, _):
|
|
53
|
+
if t is None or not issubclass(t, OSError):
|
|
54
|
+
return False
|
|
55
|
+
self._x(v)
|
|
56
|
+
self.c = True
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def check( # ruff: ignore[too-many-branches, too-many-locals, too-many-statements]
|
|
61
|
+
files: Iterable[FileDescriptorOrPath],
|
|
62
|
+
*,
|
|
63
|
+
clear: bool = False,
|
|
64
|
+
may_not_exist: bool = False,
|
|
65
|
+
verbosity: SupportsIndex = 2,
|
|
66
|
+
) -> Literal[0, 1, 4, 5, 8, 9, 12, 13]:
|
|
67
|
+
"""Check the emptiness of files and directories.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
files: an iterable of file descriptors or paths representing the files and
|
|
71
|
+
directories to check; directory descriptors (Unix) are not supported.
|
|
72
|
+
clear: if True, clear the contents of non-empty files; directories, notably,
|
|
73
|
+
are not purged, but all files within should become empty.
|
|
74
|
+
may_not_exist: if True, do not treat absent files or directories as errors.
|
|
75
|
+
verbosity: how much detail the program should print to stdout; if 0, print
|
|
76
|
+
nothing; verbosity > 5 is equivalent to verbosity = 5.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
The integer exit code. A bitwise or of 1 (some files were not empty), 4 (some
|
|
80
|
+
files or directories were absent and `-m`/`--may-not-exist` was not specified)
|
|
81
|
+
and 8 (caught OSError while processing some files), such that 0 is the only
|
|
82
|
+
return value that represents success as expected. The 2 bit is skipped since it
|
|
83
|
+
conflicts with the exit code of `argparse.ArgumentParser` when it encounters
|
|
84
|
+
invalid arguments.
|
|
85
|
+
|
|
86
|
+
"""
|
|
87
|
+
files = list(files)
|
|
88
|
+
if not files:
|
|
89
|
+
return 0
|
|
90
|
+
k, j, t, n, o = [0] * 4, [], 0, len(files), files.pop
|
|
91
|
+
u, e = j.extend, j.pop
|
|
92
|
+
|
|
93
|
+
def _(v, k=k):
|
|
94
|
+
def f(_):
|
|
95
|
+
k[v] += 1
|
|
96
|
+
|
|
97
|
+
return f
|
|
98
|
+
|
|
99
|
+
verbosity = type(verbosity).__index__(verbosity)
|
|
100
|
+
if verbosity > 4:
|
|
101
|
+
b = ['']
|
|
102
|
+
f = b.append
|
|
103
|
+
else:
|
|
104
|
+
b, f = None, _(0)
|
|
105
|
+
if verbosity > 2:
|
|
106
|
+
z, w = [''], ['']
|
|
107
|
+
i = z.append, w.append
|
|
108
|
+
else:
|
|
109
|
+
z = w = None
|
|
110
|
+
i = _(1), _(2)
|
|
111
|
+
if verbosity > 1:
|
|
112
|
+
r = ['']
|
|
113
|
+
g = r.append
|
|
114
|
+
else:
|
|
115
|
+
r, g = None, _(3)
|
|
116
|
+
while files:
|
|
117
|
+
a = o()
|
|
118
|
+
with _Handler(*i, a) as h:
|
|
119
|
+
q = os.stat(a)
|
|
120
|
+
if h.c:
|
|
121
|
+
continue
|
|
122
|
+
c = h.e
|
|
123
|
+
if (q.st_mode >> 12) & 15 == 4:
|
|
124
|
+
if isinstance(a, int):
|
|
125
|
+
raise DIRECTORY_DESCRIPTOR_UNSUPPORTED
|
|
126
|
+
if verbosity > 3:
|
|
127
|
+
print('Recursing into directory:', c)
|
|
128
|
+
u(os.scandir(c))
|
|
129
|
+
continue
|
|
130
|
+
s = q.st_size
|
|
131
|
+
if not s:
|
|
132
|
+
f(c)
|
|
133
|
+
continue
|
|
134
|
+
g(f'{c} ({s} bytes)')
|
|
135
|
+
t += s
|
|
136
|
+
if clear:
|
|
137
|
+
with h, open(a, 'wb'):
|
|
138
|
+
...
|
|
139
|
+
del o, files
|
|
140
|
+
while j:
|
|
141
|
+
m = e()
|
|
142
|
+
a = m.path
|
|
143
|
+
if m.is_dir():
|
|
144
|
+
if verbosity > 3:
|
|
145
|
+
print('Recursing into directory:', a)
|
|
146
|
+
u(os.scandir(a))
|
|
147
|
+
continue
|
|
148
|
+
n += 1
|
|
149
|
+
s = m.stat().st_size
|
|
150
|
+
if not s:
|
|
151
|
+
f(a)
|
|
152
|
+
continue
|
|
153
|
+
g(f'{a} ({s} bytes)')
|
|
154
|
+
t += s
|
|
155
|
+
if clear:
|
|
156
|
+
with _Handler(*i, a), open(a, 'wb'):
|
|
157
|
+
...
|
|
158
|
+
x = k[1] if z is None else len(z) - 1
|
|
159
|
+
y = k[3] if r is None else len(r) - 1
|
|
160
|
+
d = k[2] if w is None else len(w) - 1
|
|
161
|
+
v = bool(y) | (bool(x) and not may_not_exist) << 2 | bool(d) << 3
|
|
162
|
+
if verbosity <= 0:
|
|
163
|
+
return v # ty: ignore[invalid-return-type]
|
|
164
|
+
if verbosity > 2:
|
|
165
|
+
print(*z, sep='\nNot found: ')
|
|
166
|
+
print(f'{x} file{"" if x == 1 else "s"} not found' if x else 'All files were found')
|
|
167
|
+
if verbosity > 2:
|
|
168
|
+
print(*w, sep='\nError: ')
|
|
169
|
+
if d:
|
|
170
|
+
print(f'{d} I/O error{"s" if d > 1 else ""} encountered')
|
|
171
|
+
if verbosity > 4:
|
|
172
|
+
print(*b, sep='\nEmpty: ')
|
|
173
|
+
if verbosity > 2:
|
|
174
|
+
p = k[0] if b is None else len(b) - 1
|
|
175
|
+
print(f'{p} empty file{"" if p == 1 else "s"}' if p else 'No empty files')
|
|
176
|
+
if y:
|
|
177
|
+
if verbosity > 1:
|
|
178
|
+
print(*r, sep='\nCleared: ' if clear else '\nNot empty: ', end='')
|
|
179
|
+
print(end='\n\n')
|
|
180
|
+
print(y, 'offending files' if y > 1 else 'offending file')
|
|
181
|
+
print(f'Total size: {t} bytes')
|
|
182
|
+
elif n > x:
|
|
183
|
+
print('All found files were empty')
|
|
184
|
+
return v # ty: ignore[invalid-return-type]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Implementation of the main routine. Also exports the argument parser."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import check_empty as c
|
|
6
|
+
|
|
7
|
+
TYPE_CHECKING = False
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from collections.abc import Iterable
|
|
10
|
+
from typing import Literal
|
|
11
|
+
|
|
12
|
+
parser = __import__('argparse').ArgumentParser(
|
|
13
|
+
'check-empty',
|
|
14
|
+
description='Assert or enforce that some files, or even directories, are empty. '
|
|
15
|
+
'Makes some reasonable assumptions, such as the absence of another process '
|
|
16
|
+
'modifying a file or directory involved, while running.',
|
|
17
|
+
epilog='It is preferred that you use this as a pre-commit/prek hook or GitHub '
|
|
18
|
+
'Action for most cases which are not one-off.',
|
|
19
|
+
)
|
|
20
|
+
f = parser.add_argument
|
|
21
|
+
f('filenames', nargs='+', help='the files that should be empty')
|
|
22
|
+
f('-v', '--version', action='version', version='check-empty v' + c.__version__)
|
|
23
|
+
f('-c', '--clear', action='store_true', help='clear files that are not empty')
|
|
24
|
+
f(
|
|
25
|
+
'-m',
|
|
26
|
+
'--may-not-exist',
|
|
27
|
+
action='store_true',
|
|
28
|
+
help='do not fail solely because some files are not present',
|
|
29
|
+
)
|
|
30
|
+
f('-Q', '--quiet', action='count', default=0, help='decrease output verbosity')
|
|
31
|
+
f('-V', '--verbose', action='count', default=0, help='increase output verbosity')
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def main(argv: Iterable[str] | None = None) -> Literal[0, 1, 2, 4, 5, 8, 9, 12, 13]:
|
|
35
|
+
"""Run the hook on the files in the command-line arguments passed.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
argv: a list of arguments; default `sys.argv[1:]`.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
The exit code. See `check` for details.
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
n = parser.parse_args(argv)
|
|
46
|
+
except SystemExit as e:
|
|
47
|
+
return e.code # ty: ignore[invalid-return-type]
|
|
48
|
+
return c.check(
|
|
49
|
+
n.filenames,
|
|
50
|
+
clear=n.clear,
|
|
51
|
+
may_not_exist=n.may_not_exist,
|
|
52
|
+
verbosity=2 + n.verbose - n.quiet,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if __name__ == '__main__':
|
|
57
|
+
parser.exit(main())
|
|
58
|
+
del f, TYPE_CHECKING
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["uv_build>=0.11.
|
|
2
|
+
requires = ["uv_build>=0.11.31,<0.12"]
|
|
3
3
|
build-backend = "uv_build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
authors = [{name = "Jonathan Dung", email = "jonathandung@yahoo.com"}]
|
|
7
7
|
classifiers = ["Development Status :: 3 - Alpha", "Environment :: Console", "Intended Audience :: Developers", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.15", "Programming Language :: Python :: Free Threading :: 3 - Stable", "Topic :: Utilities", "Typing :: Typed"]
|
|
8
|
-
description = "Command-line utility, as well as a hook usable by the Python pre-commit framework, to ensure that selected files are empty."
|
|
9
|
-
keywords = ["check", "command-line", "empty", "github-action", "hook", "lightweight", "pre-commit", "prek", "tool", "utility"]
|
|
8
|
+
description = "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."
|
|
9
|
+
keywords = ["check", "command-line", "empty", "files", "github-action", "helper", "hook", "lightweight", "pre-commit", "prek", "tool", "utility"]
|
|
10
10
|
license = "MIT"
|
|
11
11
|
license-files = ["LICENSE", "AUTHORS.md"]
|
|
12
12
|
maintainers = [{name = "Jonathan Dung", email = "jonathandung@yahoo.com"}]
|
|
13
13
|
name = "check-empty"
|
|
14
14
|
readme = "README.md"
|
|
15
|
-
requires-python = ">=3.
|
|
16
|
-
version = "0.
|
|
15
|
+
requires-python = ">=3.6"
|
|
16
|
+
version = "0.6.0"
|
|
17
17
|
|
|
18
18
|
[project.scripts]
|
|
19
19
|
check-empty = "check_empty.__main__:main"
|
|
@@ -28,7 +28,7 @@ type-checking-imports = false
|
|
|
28
28
|
quote-style = "single"
|
|
29
29
|
|
|
30
30
|
[tool.ruff.lint]
|
|
31
|
-
ignore = ["ANN", "COM", "PT", "PTH", "avoidable-escaped-quote", "bad-quotes-inline-string", "complex-structure", "docstring-extraneous-exception", "incorrect-blank-line-before-class", "multi-line-summary-second-line", "print", "unnecessary-dunder-call"]
|
|
31
|
+
ignore = ["ANN", "COM", "FBT", "PT", "PTH", "avoidable-escaped-quote", "bad-quotes-inline-string", "complex-structure", "docstring-extraneous-exception", "import-outside-top-level", "incorrect-blank-line-before-class", "magic-value-comparison", "multi-line-summary-second-line", "non-empty-init-module", "print", "unnecessary-dunder-call"]
|
|
32
32
|
preview = true
|
|
33
33
|
select = ["ALL"]
|
|
34
34
|
|
|
@@ -53,8 +53,12 @@ python-platform = "all"
|
|
|
53
53
|
all = "error"
|
|
54
54
|
missing-override-decorator = "ignore"
|
|
55
55
|
|
|
56
|
+
[[tool.ty.overrides]]
|
|
57
|
+
include = ["check_empty/__init__.py"]
|
|
58
|
+
rules = {not-iterable = "ignore"}
|
|
59
|
+
|
|
56
60
|
[tool.uv]
|
|
57
|
-
required-version = ">=0.11.
|
|
61
|
+
required-version = ">=0.11.31,<0.12"
|
|
58
62
|
|
|
59
63
|
[tool.uv.build-backend]
|
|
60
64
|
module-name = "check_empty"
|
check_empty-0.2.0/README.md
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# check-empty
|
|
2
|
-
|
|
3
|
-
A simple [pre-commit](https://pre-commit.com)/[prek](https://prek.j178.dev) hook that
|
|
4
|
-
makes sure selected files are empty according to a filesystem stat call and clears them
|
|
5
|
-
effectively on demand with a write-mode open and immediate close. Also acts as a CLI,
|
|
6
|
-
library and GitHub Action conglomerate.
|
|
7
|
-
|
|
8
|
-
## Quickstart
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
uv tool install check-empty # uv
|
|
12
|
-
pip install check-empty # pip
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Check the version with:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
check-empty -v
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Run the CLI:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
check-empty -q src/mylib/py.typed docs/.nojekyll static/.gitkeep
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
As a pre-commit hook:
|
|
28
|
-
|
|
29
|
-
```yaml
|
|
30
|
-
# .pre-commit-config.yaml
|
|
31
|
-
repos:
|
|
32
|
-
- repo: https://github.com/jonathandung/check-empty
|
|
33
|
-
rev: v0.2.0 # repository version
|
|
34
|
-
hooks:
|
|
35
|
-
- id: check-empty # the hook
|
|
36
|
-
args: # example list of arguments
|
|
37
|
-
- --quiet # flag to silence output (equivalent to -q)
|
|
38
|
-
# below: paths to files to clear or keep empty, either relative to the project
|
|
39
|
-
# root or absolute (not shown)
|
|
40
|
-
- src/mylib/py.typed
|
|
41
|
-
- docs/.nojekyll
|
|
42
|
-
- static/.gitkeep
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
equivalent in `prek.toml` format:
|
|
46
|
-
|
|
47
|
-
```toml
|
|
48
|
-
[[repos]]
|
|
49
|
-
repo = "https://github.com/jonathandung/check-empty"
|
|
50
|
-
rev = "v0.2.0"
|
|
51
|
-
hooks = [{
|
|
52
|
-
id = "check-empty",
|
|
53
|
-
args = [
|
|
54
|
-
"--quiet",
|
|
55
|
-
"src/mylib/py.typed",
|
|
56
|
-
"docs/.nojekyll",
|
|
57
|
-
"static/.gitkeep",
|
|
58
|
-
]
|
|
59
|
-
}]
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
As a GitHub action step:
|
|
63
|
-
|
|
64
|
-
```yaml
|
|
65
|
-
steps:
|
|
66
|
-
- uses: jonathandung/check-empty@v0.2.0 # the latest version on the GitHub Actions
|
|
67
|
-
# marketplace; this step will fail and subsequent jobs will not run if any file is
|
|
68
|
-
# not empty
|
|
69
|
-
with:
|
|
70
|
-
python-version: '3.14' # run the script on the latest stable Python version
|
|
71
|
-
# Python down to 3.6 is supported but not recommended due to end-of-life
|
|
72
|
-
quiet: true
|
|
73
|
-
filenames: |
|
|
74
|
-
src/mylib/py.typed
|
|
75
|
-
docs/.nojekyll
|
|
76
|
-
static/.gitkeep
|
|
77
|
-
```
|
|
@@ -1,9 +0,0 @@
|
|
|
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
|
-
__all__ = ('main',)
|
|
9
|
-
from check_empty.__main__ import __version__ as __version__, main
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
#!usr/bin/env python3
|
|
2
|
-
"""Implementation of the main routine."""
|
|
3
|
-
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
from os.path import getsize
|
|
7
|
-
|
|
8
|
-
TYPE_CHECKING = False
|
|
9
|
-
if TYPE_CHECKING:
|
|
10
|
-
from typing import Literal, Sequence
|
|
11
|
-
|
|
12
|
-
__version__ = '0.1'
|
|
13
|
-
parser = __import__('argparse').ArgumentParser(
|
|
14
|
-
'check-empty',
|
|
15
|
-
description='Assert or enforce that some files are empty.',
|
|
16
|
-
epilog='It is preferred that you use this as a pre-commit/prek hook or GitHub '
|
|
17
|
-
'Action for most cases which are not one-off.',
|
|
18
|
-
)
|
|
19
|
-
f = parser.add_argument
|
|
20
|
-
f('filenames', nargs='+', help='the files that should be empty')
|
|
21
|
-
f('-v', '--version', action='version', version='check-empty v' + __version__)
|
|
22
|
-
f('-c', '--clear', action='store_true', help='clear files that are not empty')
|
|
23
|
-
f('-m', '--must-exist', action='store_true', help='fail if any file is absent')
|
|
24
|
-
f('-q', '--quiet', action='store_true', help='suppress output')
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def main(argv: Sequence[str] | None = None) -> Literal[0, 1, 2, 3]:
|
|
28
|
-
"""Run the hook on the files in the (command-line) arguments passed.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
argv: A list of arguments excluding the executable name, default sys.argv[1:].
|
|
32
|
-
|
|
33
|
-
Returns:
|
|
34
|
-
The exit code. A bitwise or of 1 (some files were not empty) and 2 (some files
|
|
35
|
-
were absent and --must-exist was specified), such that 0 is the only return
|
|
36
|
-
value that represents success as expected.
|
|
37
|
-
|
|
38
|
-
"""
|
|
39
|
-
r, z, n = [''], [''], parser.parse_args(argv)
|
|
40
|
-
g, j, c, t, i = r.append, z.append, n.clear, 0, n.filenames
|
|
41
|
-
if c:
|
|
42
|
-
for a in i:
|
|
43
|
-
try:
|
|
44
|
-
s = getsize(a)
|
|
45
|
-
except FileNotFoundError:
|
|
46
|
-
j(a)
|
|
47
|
-
continue
|
|
48
|
-
if not s:
|
|
49
|
-
continue
|
|
50
|
-
g(f'{a} ({s} bytes)')
|
|
51
|
-
t += s
|
|
52
|
-
with open(a, 'wb'):
|
|
53
|
-
...
|
|
54
|
-
else:
|
|
55
|
-
for a in i:
|
|
56
|
-
try:
|
|
57
|
-
s = getsize(a)
|
|
58
|
-
except FileNotFoundError:
|
|
59
|
-
j(a)
|
|
60
|
-
continue
|
|
61
|
-
if not s:
|
|
62
|
-
continue
|
|
63
|
-
g(f'{a} ({s} bytes)')
|
|
64
|
-
t += s
|
|
65
|
-
p, x, y = not n.quiet, len(z) - 1, len(r) - 1
|
|
66
|
-
v = n.must_exist << 1 if x else 0
|
|
67
|
-
if p:
|
|
68
|
-
print(*z, sep='\nNot found: ')
|
|
69
|
-
print(f'{x} files not found' if x else 'All files were found')
|
|
70
|
-
if y:
|
|
71
|
-
if p:
|
|
72
|
-
print(*r, sep='\nCleared: ' if c else '\nNot empty: ', end='\n\n')
|
|
73
|
-
print(y, 'offending files' if y > 1 else 'offending file')
|
|
74
|
-
print(f'Total size: {t} bytes')
|
|
75
|
-
return v | 1
|
|
76
|
-
if p:
|
|
77
|
-
print('All found files were empty')
|
|
78
|
-
return v
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if __name__ == '__main__':
|
|
82
|
-
raise SystemExit(main())
|
|
83
|
-
del f, TYPE_CHECKING
|
|
File without changes
|