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.
@@ -10,4 +10,4 @@
10
10
 
11
11
  ## All other contributors and their affiliations
12
12
 
13
- None at the moment.
13
+ None at the moment.
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: check-empty
3
- Version: 0.2.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.7
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)/[prek](https://prek.j178.dev) hook that
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 on demand with a write-mode open and immediate close. Also acts as a CLI,
40
- library and GitHub Action conglomerate.
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
- uv tool install check-empty # uv
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 -q src/mylib/py.typed docs/.nojekyll static/.gitkeep
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.2.0 # repository version
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
- - --quiet # flag to silence output (equivalent to -q)
72
- # below: paths to files to clear or keep empty, either relative to the project
73
- # root or absolute (not shown)
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.2.0"
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
- "--quiet",
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.2.0 # the latest version on the GitHub Actions
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.29,<0.12"]
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.7"
16
- version = "0.2.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.29,<0.12"
61
+ required-version = ">=0.11.31,<0.12"
58
62
 
59
63
  [tool.uv.build-backend]
60
64
  module-name = "check_empty"
@@ -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