timeout-dead 0.1.0__py3-none-any.whl
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.
- timeout_dead/__init__.py +6 -0
- timeout_dead/_version.py +24 -0
- timeout_dead/cli.py +302 -0
- timeout_dead-0.1.0.dist-info/METADATA +129 -0
- timeout_dead-0.1.0.dist-info/RECORD +9 -0
- timeout_dead-0.1.0.dist-info/WHEEL +5 -0
- timeout_dead-0.1.0.dist-info/entry_points.txt +4 -0
- timeout_dead-0.1.0.dist-info/licenses/LICENSE +24 -0
- timeout_dead-0.1.0.dist-info/top_level.txt +1 -0
timeout_dead/__init__.py
ADDED
timeout_dead/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
timeout_dead/cli.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""Легковесная утилита для запуска команд с таймаутом."""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import signal
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
import threading
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# MARK: Constants
|
|
16
|
+
# ------------------------------------------------
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _Const:
|
|
20
|
+
DEFAULT_TIMEOUT_S: int = 60
|
|
21
|
+
GRACE_PERIOD_S: float = 1.0
|
|
22
|
+
HEADER_SEPARATOR: str = "-" * 60
|
|
23
|
+
|
|
24
|
+
MSG_NO_COMMAND: str = "Error: no command specified"
|
|
25
|
+
MSG_BASH_NOT_FOUND: str = "bash not found in PATH — Git Bash is required"
|
|
26
|
+
MSG_TIMEOUT: str = "Timeout exceeded {} seconds"
|
|
27
|
+
MSG_EXEC_ERROR: str = "Execution error: {}"
|
|
28
|
+
|
|
29
|
+
SIGNAL_NAMES: tuple[str, ...] = ("TERM", "KILL", "HUP", "INT")
|
|
30
|
+
|
|
31
|
+
SIGNAL_MAP: dict[str, int] = {
|
|
32
|
+
"TERM": signal.SIGTERM,
|
|
33
|
+
"KILL": getattr(signal, "SIGKILL", signal.SIGTERM),
|
|
34
|
+
"HUP": getattr(signal, "SIGHUP", signal.SIGTERM),
|
|
35
|
+
"INT": signal.SIGINT,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# MARK: Private Helpers
|
|
40
|
+
# ------------------------------------------------
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _is_windows() -> bool:
|
|
44
|
+
return os.name == "nt"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ------------------------------------------------
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _find_bash() -> str:
|
|
51
|
+
"""
|
|
52
|
+
Ищет bash в PATH.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
str: путь к исполняемому файлу bash
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
SystemExit: если bash не найден
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
bash_path = shutil.which("bash")
|
|
62
|
+
|
|
63
|
+
if bash_path is None:
|
|
64
|
+
print(f"\n{_Const.MSG_BASH_NOT_FOUND}", file=sys.stderr)
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
|
|
67
|
+
return bash_path
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# MARK: Process termination
|
|
71
|
+
# ------------------------------------------------
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _terminate_process(
|
|
75
|
+
process: subprocess.Popen[bytes] | subprocess.Popen[str],
|
|
76
|
+
*,
|
|
77
|
+
force: bool = False,
|
|
78
|
+
signal_num: int = signal.SIGTERM,
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Завершает процесс — мягко (выбранным сигналом) или жёстко."""
|
|
81
|
+
|
|
82
|
+
if process.poll() is not None:
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
if _is_windows():
|
|
87
|
+
if force:
|
|
88
|
+
process.kill()
|
|
89
|
+
|
|
90
|
+
elif signal_num == signal.SIGINT:
|
|
91
|
+
process.send_signal(signal.SIGINT)
|
|
92
|
+
|
|
93
|
+
else:
|
|
94
|
+
ctrl_break = getattr(signal, "CTRL_BREAK_EVENT", signal.SIGTERM)
|
|
95
|
+
process.send_signal(ctrl_break)
|
|
96
|
+
|
|
97
|
+
else:
|
|
98
|
+
pgid = os.getpgid(process.pid) # pyright: ignore[reportAttributeAccessIssue]
|
|
99
|
+
|
|
100
|
+
if force:
|
|
101
|
+
os.killpg(pgid, signal.SIGKILL) # pyright: ignore[reportAttributeAccessIssue]
|
|
102
|
+
|
|
103
|
+
else:
|
|
104
|
+
os.killpg(pgid, signal_num) # pyright: ignore[reportAttributeAccessIssue]
|
|
105
|
+
|
|
106
|
+
except (ProcessLookupError, OSError):
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# ------------------------------------------------
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _kill_with_timeout(
|
|
114
|
+
process: subprocess.Popen[bytes] | subprocess.Popen[str],
|
|
115
|
+
timeout: int,
|
|
116
|
+
signal_num: int = signal.SIGTERM,
|
|
117
|
+
) -> None:
|
|
118
|
+
"""Убивает процесс по таймауту с двухэтапной логикой."""
|
|
119
|
+
|
|
120
|
+
if process.poll() is not None:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
_terminate_process(process, signal_num=signal_num)
|
|
124
|
+
time.sleep(_Const.GRACE_PERIOD_S)
|
|
125
|
+
|
|
126
|
+
if process.poll() is None:
|
|
127
|
+
_terminate_process(process, force=True)
|
|
128
|
+
|
|
129
|
+
print(f"\n{_Const.MSG_TIMEOUT.format(timeout)}", file=sys.stderr)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# MARK: Public API
|
|
133
|
+
# ------------------------------------------------
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def run_command(
|
|
137
|
+
command_string: str,
|
|
138
|
+
timeout: int = _Const.DEFAULT_TIMEOUT_S,
|
|
139
|
+
signal_name: str = "TERM",
|
|
140
|
+
no_output: bool = False,
|
|
141
|
+
) -> int:
|
|
142
|
+
"""
|
|
143
|
+
Запускает команду с таймаутом через bash.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
command_string (str): команда для выполнения
|
|
147
|
+
timeout (int): таймаут в секундах
|
|
148
|
+
signal_name (str): имя сигнала для мягкого завершения
|
|
149
|
+
no_output (bool): подавлять ли обычный вывод
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
int: код возврата процесса (-1 при ошибке запуска)
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
signal_num = _Const.SIGNAL_MAP.get(signal_name, signal.SIGTERM)
|
|
156
|
+
process: subprocess.Popen[bytes] | subprocess.Popen[str] | None = None
|
|
157
|
+
timer: threading.Timer | None = None
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
creationflags = subprocess.CREATE_NEW_PROCESS_GROUP if _is_windows() else 0
|
|
161
|
+
start_new_session = not _is_windows()
|
|
162
|
+
|
|
163
|
+
process = subprocess.Popen(
|
|
164
|
+
[_find_bash(), "-c", command_string],
|
|
165
|
+
stdout=subprocess.PIPE,
|
|
166
|
+
stderr=subprocess.PIPE,
|
|
167
|
+
text=True,
|
|
168
|
+
creationflags=creationflags,
|
|
169
|
+
start_new_session=start_new_session,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
timer = threading.Timer(
|
|
173
|
+
timeout,
|
|
174
|
+
_kill_with_timeout,
|
|
175
|
+
args=(process, timeout, signal_num),
|
|
176
|
+
)
|
|
177
|
+
timer.start()
|
|
178
|
+
|
|
179
|
+
stdout, stderr = process.communicate()
|
|
180
|
+
timer.cancel()
|
|
181
|
+
|
|
182
|
+
if not no_output:
|
|
183
|
+
if stdout:
|
|
184
|
+
print(stdout, end="")
|
|
185
|
+
if stderr:
|
|
186
|
+
print(stderr, end="", file=sys.stderr)
|
|
187
|
+
|
|
188
|
+
return process.returncode
|
|
189
|
+
|
|
190
|
+
except Exception as e:
|
|
191
|
+
if timer:
|
|
192
|
+
timer.cancel()
|
|
193
|
+
if process and process.poll() is None:
|
|
194
|
+
try:
|
|
195
|
+
_terminate_process(process, force=True)
|
|
196
|
+
|
|
197
|
+
except Exception:
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
print(f"{_Const.MSG_EXEC_ERROR.format(e)}", file=sys.stderr)
|
|
201
|
+
|
|
202
|
+
return -1
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# ------------------------------------------------
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def parse_arguments(argv: list[str] | None = None) -> argparse.Namespace:
|
|
209
|
+
"""Разбирает аргументы командной строки."""
|
|
210
|
+
|
|
211
|
+
parser = argparse.ArgumentParser(
|
|
212
|
+
description="Lightweight command timeout utility.",
|
|
213
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
parser.add_argument(
|
|
217
|
+
"--sec",
|
|
218
|
+
type=int,
|
|
219
|
+
default=_Const.DEFAULT_TIMEOUT_S,
|
|
220
|
+
help=f"timeout in seconds (default: {_Const.DEFAULT_TIMEOUT_S})",
|
|
221
|
+
metavar="SECONDS",
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
parser.add_argument(
|
|
225
|
+
"--signal",
|
|
226
|
+
type=str.upper,
|
|
227
|
+
default="TERM",
|
|
228
|
+
choices=list(_Const.SIGNAL_NAMES),
|
|
229
|
+
help="signal to send on timeout (default: TERM)",
|
|
230
|
+
metavar="SIGNAL",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
parser.add_argument(
|
|
234
|
+
"--no-output",
|
|
235
|
+
action="store_true",
|
|
236
|
+
default=False,
|
|
237
|
+
help="suppress normal output (stdout, stderr, header, footer)",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
parser.add_argument(
|
|
241
|
+
"command",
|
|
242
|
+
nargs=argparse.REMAINDER,
|
|
243
|
+
help="command to execute",
|
|
244
|
+
metavar="COMMAND",
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
return parser.parse_args(argv)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# ------------------------------------------------
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def print_header(command: str, timeout: int) -> None:
|
|
254
|
+
"""Выводит заголовок выполнения."""
|
|
255
|
+
|
|
256
|
+
print(f"Running: {command}")
|
|
257
|
+
print(f"Timeout: {timeout} seconds")
|
|
258
|
+
print(_Const.HEADER_SEPARATOR)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# ------------------------------------------------
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def print_footer(return_code: int) -> None:
|
|
265
|
+
"""Выводит футер выполнения."""
|
|
266
|
+
|
|
267
|
+
print(_Const.HEADER_SEPARATOR)
|
|
268
|
+
print(f"Exit code: {return_code}")
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# ------------------------------------------------
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def main(argv: list[str] | None = None) -> None:
|
|
275
|
+
"""Главная точка входа."""
|
|
276
|
+
|
|
277
|
+
args = parse_arguments(argv)
|
|
278
|
+
|
|
279
|
+
if not args.command:
|
|
280
|
+
print(f"{_Const.MSG_NO_COMMAND}", file=sys.stderr)
|
|
281
|
+
sys.exit(1)
|
|
282
|
+
|
|
283
|
+
command_string = " ".join(args.command)
|
|
284
|
+
|
|
285
|
+
if not args.no_output:
|
|
286
|
+
print_header(command_string, args.sec)
|
|
287
|
+
|
|
288
|
+
return_code = run_command(
|
|
289
|
+
command_string,
|
|
290
|
+
timeout=args.sec,
|
|
291
|
+
signal_name=args.signal,
|
|
292
|
+
no_output=args.no_output,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
if not args.no_output:
|
|
296
|
+
print_footer(return_code)
|
|
297
|
+
|
|
298
|
+
sys.exit(return_code)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
if __name__ == "__main__":
|
|
302
|
+
main()
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: timeout-dead
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight command timeout utility with zero runtime dependencies
|
|
5
|
+
Author-email: Dmitry Krivoruchko <umbrella.leaf.for.work@gmail.com>
|
|
6
|
+
License-Expression: Unlicense
|
|
7
|
+
Project-URL: Repository, https://github.com/UmbrellaLeaf5/timeout-dead
|
|
8
|
+
Project-URL: Issues, https://github.com/UmbrellaLeaf5/timeout-dead/issues
|
|
9
|
+
Keywords: timeout,cli,command,process,kill,signal,developer-tools
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: System :: System Shells
|
|
18
|
+
Classifier: Topic :: Utilities
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
24
|
+
Requires-Dist: pyright>=1.1.409; extra == "dev"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# timeout-dead
|
|
28
|
+
|
|
29
|
+
[](https://pypi.org/project/timeout-dead/)
|
|
30
|
+
[](https://python.org)
|
|
31
|
+
[](LICENSE)
|
|
32
|
+
[](https://github.com/UmbrellaLeaf5/timeout-dead/actions/workflows/tests.yml)
|
|
33
|
+
[](https://github.com/UmbrellaLeaf5/timeout-dead/actions/workflows/ruff.yml)
|
|
34
|
+
[](https://github.com/UmbrellaLeaf5/timeout-dead/actions/workflows/pyright.yml)
|
|
35
|
+
|
|
36
|
+
<img align="right" height="256" src="icon.png"/>
|
|
37
|
+
|
|
38
|
+
**Lightweight command timeout utility with zero runtime dependencies.**
|
|
39
|
+
Runs any shell command with a configurable time limit and termination signal.
|
|
40
|
+
If the command exceeds the timeout, `timeout-dead` sends the chosen signal,
|
|
41
|
+
waits a 1-second grace period, then force-kills the process.
|
|
42
|
+
Works on Linux, macOS, and Windows (Git Bash / WSL).
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install timeout-dead
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or via uv:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
uv tool install timeout-dead
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Requires Python 3.10 or later. Zero runtime dependencies — pure Python standard library.
|
|
57
|
+
|
|
58
|
+
## Quick start
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Run a command with default 60s timeout
|
|
62
|
+
timeout-dead "python -c 'print(42)'"
|
|
63
|
+
|
|
64
|
+
# Short alias also works
|
|
65
|
+
time-d "echo hello"
|
|
66
|
+
|
|
67
|
+
# Specify a custom timeout
|
|
68
|
+
timeout-dead --sec 120 "npm run build"
|
|
69
|
+
|
|
70
|
+
# Use SIGINT instead of default SIGTERM
|
|
71
|
+
timeout-dead --signal INT --sec 30 "long-running-server"
|
|
72
|
+
|
|
73
|
+
# Run silently — suppress all normal output
|
|
74
|
+
timeout-dead --no-output "curl -s https://example.com"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Usage
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
usage: timeout-dead [-h] [--sec SECONDS] [--signal SIGNAL] [--no-output] COMMAND ...
|
|
81
|
+
|
|
82
|
+
Lightweight command timeout utility.
|
|
83
|
+
|
|
84
|
+
positional arguments:
|
|
85
|
+
COMMAND command to execute
|
|
86
|
+
|
|
87
|
+
options:
|
|
88
|
+
-h, --help show this help message and exit
|
|
89
|
+
--sec SECONDS timeout in seconds (default: 60)
|
|
90
|
+
--signal SIGNAL signal to send on timeout (TERM, KILL, HUP, INT)
|
|
91
|
+
--no-output suppress normal output (stdout, stderr, header, footer)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## How it works
|
|
95
|
+
|
|
96
|
+
1. `timeout-dead` starts the command in a new process group (Unix) / console group (Windows).
|
|
97
|
+
2. A background timer waits for the specified timeout.
|
|
98
|
+
3. If the command finishes in time, its output and exit code are forwarded.
|
|
99
|
+
4. If the timeout expires:
|
|
100
|
+
- The chosen signal is sent to the process group.
|
|
101
|
+
- After 1 second, if the process is still running, `SIGKILL` (Unix) or `process.kill()` (Windows) is sent.
|
|
102
|
+
- A `Timeout exceeded` message is printed to stderr.
|
|
103
|
+
|
|
104
|
+
## Signal reference
|
|
105
|
+
|
|
106
|
+
| Signal | Unix | Windows |
|
|
107
|
+
|--------|------|---------|
|
|
108
|
+
| `TERM` | `SIGTERM` (15) — terminate gracefully | `CTRL_BREAK_EVENT` — console break |
|
|
109
|
+
| `KILL` | `SIGKILL` (9) — force kill | Falls back to `TerminateProcess` |
|
|
110
|
+
| `HUP` | `SIGHUP` (1) — hangup | Falls back to `TerminateProcess` |
|
|
111
|
+
| `INT` | `SIGINT` (2) — interrupt (Ctrl+C) | `CTRL_C_EVENT` — console interrupt |
|
|
112
|
+
|
|
113
|
+
## Development
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
git clone https://github.com/UmbrellaLeaf5/timeout-dead
|
|
117
|
+
cd timeout-dead
|
|
118
|
+
uv sync --extra dev
|
|
119
|
+
uv run pytest tests/ -v
|
|
120
|
+
uv run ruff check src/timeout_dead/ tests/
|
|
121
|
+
uv run ruff format --check .
|
|
122
|
+
uv run pyright src/timeout_dead/
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
[Unlicense](LICENSE) — public domain.
|
|
128
|
+
|
|
129
|
+
<a href="https://www.flaticon.com/free-icons/timeout" title="timeout icons">Timeout icons created by Those Icons - Flaticon</a>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
timeout_dead/__init__.py,sha256=dmORBMW91c-w2ZmNOTNP1scTT7Sj_I4Uiirv4UswMGM,116
|
|
2
|
+
timeout_dead/_version.py,sha256=n_5vdJsPNu7wZ57LGuRL585uvll-hiuvZUBWzdG0RQU,520
|
|
3
|
+
timeout_dead/cli.py,sha256=4Ox74_htWGjD9S6c2cbfdlV7LS8nAzQcP44Fyz6lsZs,7180
|
|
4
|
+
timeout_dead-0.1.0.dist-info/licenses/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
|
|
5
|
+
timeout_dead-0.1.0.dist-info/METADATA,sha256=O-IYUmoKvSiEVNvrJVC8TrgTLNQas7qfoBbmXx5A8uY,4705
|
|
6
|
+
timeout_dead-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
7
|
+
timeout_dead-0.1.0.dist-info/entry_points.txt,sha256=pYdLZaqgi6T6EE6woFSUVY2_tlVeNmZL-ciU5zm0aYg,123
|
|
8
|
+
timeout_dead-0.1.0.dist-info/top_level.txt,sha256=86YL6ohLAQPyhX9lDRLqOVrhBozouE8w97PgfFRMaSc,13
|
|
9
|
+
timeout_dead-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
|
2
|
+
|
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
+
distribute this software, either in source code form or as a compiled
|
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
+
means.
|
|
7
|
+
|
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
+
of this software dedicate any and all copyright interest in the
|
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
|
11
|
+
of the public at large and to the detriment of our heirs and
|
|
12
|
+
successors. We intend this dedication to be an overt act of
|
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
+
software under copyright law.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For more information, please refer to <https://unlicense.org>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
timeout_dead
|