py-zippy 1.0.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.
- py_zippy-1.0.0.dist-info/METADATA +149 -0
- py_zippy-1.0.0.dist-info/RECORD +16 -0
- py_zippy-1.0.0.dist-info/WHEEL +5 -0
- py_zippy-1.0.0.dist-info/entry_points.txt +2 -0
- py_zippy-1.0.0.dist-info/licenses/LICENSE +21 -0
- py_zippy-1.0.0.dist-info/top_level.txt +1 -0
- zippy/__init__.py +8 -0
- zippy/cli.py +286 -0
- zippy/create.py +237 -0
- zippy/extract.py +128 -0
- zippy/list.py +68 -0
- zippy/lock.py +104 -0
- zippy/repair.py +243 -0
- zippy/test.py +153 -0
- zippy/unlock.py +120 -0
- zippy/utils.py +375 -0
@@ -0,0 +1,149 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: py-zippy
|
3
|
+
Version: 1.0.0
|
4
|
+
Summary: Archive Utility Toolkit supporting multi-format workflows, password recovery, and repair helpers.
|
5
|
+
Author-email: John Hauger Mitander <john@on1.no>
|
6
|
+
License: MIT License
|
7
|
+
|
8
|
+
Copyright (c) 2025 John H. Mitander
|
9
|
+
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
12
|
+
in the Software without restriction, including without limitation the rights
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
15
|
+
furnished to do so, subject to the following conditions:
|
16
|
+
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
18
|
+
copies or substantial portions of the Software.
|
19
|
+
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
26
|
+
SOFTWARE.
|
27
|
+
|
28
|
+
Project-URL: Homepage, https://github.com/John0n1/ZIPPY
|
29
|
+
Project-URL: Documentation, https://github.com/John0n1/ZIPPY#readme
|
30
|
+
Project-URL: Issues, https://github.com/John0n1/ZIPPY/issues
|
31
|
+
Keywords: archives,zip,tar,cli,toolkit
|
32
|
+
Classifier: Development Status :: 5 - Production/Stable
|
33
|
+
Classifier: Environment :: Console
|
34
|
+
Classifier: Intended Audience :: System Administrators
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
36
|
+
Classifier: Operating System :: OS Independent
|
37
|
+
Classifier: Programming Language :: Python
|
38
|
+
Classifier: Programming Language :: Python :: 3
|
39
|
+
Classifier: Programming Language :: Python :: 3.9
|
40
|
+
Classifier: Programming Language :: Python :: 3.10
|
41
|
+
Classifier: Programming Language :: Python :: 3.11
|
42
|
+
Classifier: Programming Language :: Python :: 3.12
|
43
|
+
Classifier: Topic :: System :: Archiving
|
44
|
+
Classifier: Topic :: System :: Archiving :: Compression
|
45
|
+
Classifier: Topic :: Utilities
|
46
|
+
Requires-Python: >=3.9
|
47
|
+
Description-Content-Type: text/markdown
|
48
|
+
License-File: LICENSE
|
49
|
+
Requires-Dist: colorama>=0.4.6
|
50
|
+
Requires-Dist: python-dotenv>=1.0
|
51
|
+
Requires-Dist: pyzipper>=0.3.6
|
52
|
+
Requires-Dist: patool>=1.12
|
53
|
+
Dynamic: license-file
|
54
|
+
|
55
|
+
<div align="center">
|
56
|
+
|
57
|
+
# Zippy
|
58
|
+
|
59
|
+
**Multi-purpose archive extraction, repair and brute-force toolkit.**
|
60
|
+
|
61
|
+
</div>
|
62
|
+
|
63
|
+
    
|
64
|
+
|
65
|
+
```bash
|
66
|
+
|
67
|
+
- **Broad format coverage** - (`tar`, `tar.gz`, `tar.bz2`, `tar.xz`, `tar.lzma`), (`gzip`, `bz2`, `xz`, `lzma`), `7z`, `rar`, `zstd` and ZIP family (including APK/JAR/WAR/IPA/EAR).
|
68
|
+
- **Consistent UX** - consistent logging, progress indicators, and tab completion for every command.
|
69
|
+
- **Security tooling** - create or re-lock encrypted ZIPs, run smart dictionary attacks with the bundled password list, and capture verbose traces when needed.
|
70
|
+
- **Integrity & repair** - repair, attempt salvage extractions, and perform best-effort recovery for TAR and single-file formats. It grabs whatever it can, even if files look broken or corrupted. This is achieved by scanning the archive for valid headers and footers, and extracting the data in between, then rebuilding the file structure using advanced data pointers and heuristics.
|
71
|
+
|
72
|
+
## Installation
|
73
|
+
|
74
|
+
### From PyPI (recommended)
|
75
|
+
|
76
|
+
```bash
|
77
|
+
python -m pip install zippy
|
78
|
+
```
|
79
|
+
|
80
|
+
### From source
|
81
|
+
|
82
|
+
```bash
|
83
|
+
git clone https://github.com/John0n1/ZIPPY.git
|
84
|
+
cd ZIPPY
|
85
|
+
python -m pip install .
|
86
|
+
|
87
|
+
### Debian package
|
88
|
+
|
89
|
+
```bash
|
90
|
+
sudo apt install ./zippy_*.deb
|
91
|
+
```
|
92
|
+
|
93
|
+
## Quick start
|
94
|
+
|
95
|
+
```bash
|
96
|
+
# Extract an archive
|
97
|
+
zippy --extract backups/site.tar.xz -o ./site
|
98
|
+
|
99
|
+
# Create a password-protected ZIP from multiple paths
|
100
|
+
zippy --lock secure.zip -f docs,images -p "Tru5ted!"
|
101
|
+
|
102
|
+
# List TAR.BZ2 contents
|
103
|
+
zippy --list datasets.tar.bz2
|
104
|
+
|
105
|
+
# Attempt unlocking with the bundled wordlist
|
106
|
+
zippy --unlock encrypted.zip -d password_list.txt --verbose
|
107
|
+
|
108
|
+
# Run salvage repair on a damaged tarball
|
109
|
+
zippy --repair broken.tar.gz --repair-mode remove_corrupted
|
110
|
+
```
|
111
|
+
|
112
|
+
Run `zippy --help` for the full command reference or `zippy --version` to confirm the installed release.
|
113
|
+
|
114
|
+
## Supported archive formats
|
115
|
+
|
116
|
+
| Family | Types |
|
117
|
+
| ------ | ----- |
|
118
|
+
| ZIP | `zip` (with optional password protection) |
|
119
|
+
| TAR | `tar`, `tar.gz`, `tar.bz2`, `tar.xz`, `tar.lzma`, `.tgz`, `.tbz`, `.txz`, `.tlz` |
|
120
|
+
| Single-file | `gzip` (`.gz`), `bz2` (`.bz2`), `xz` (`.xz`), `lzma` (`.lzma`) |
|
121
|
+
|
122
|
+
Unsupported formats (e.g. 7z, rar, zstd) will raise a friendly error instructing you to choose a supported type.
|
123
|
+
|
124
|
+
## Configuration & automation
|
125
|
+
|
126
|
+
- Use `--save-config <file>` to capture the current flag set (including passwords or dictionary paths if provided).
|
127
|
+
- Rehydrate saved flags via `--load-config <file>` for repeatable batch jobs.
|
128
|
+
- Disable animations with `--no-animation` for CI environments.
|
129
|
+
|
130
|
+
## Logging & colours
|
131
|
+
|
132
|
+
Logging defaults to concise `INFO` output. Add `--verbose` for `DEBUG` traces. Colour output automatically downgrades in non-interactive terminals, while animations fall back to plain log messages when disabled or redirected. Windows terminals are supported through `colorama`.
|
133
|
+
|
134
|
+
## Password dictionary
|
135
|
+
|
136
|
+
The bundled `password_list.txt` contains hundreds of common credentials, curated for demonstration purposes. The unlock command trims duplicates, ignores comments (`# ...`), and safely handles mixed encodings. Supply your own list with `--dictionary <file>` for larger attacks.
|
137
|
+
|
138
|
+
## Contributing
|
139
|
+
Pull requests are welcome—please open an issue describing the enhancement before submitting substantial changes.
|
140
|
+
|
141
|
+
## License
|
142
|
+
|
143
|
+
Zippy is released under the MIT License. See `LICENSE` for the full text.
|
144
|
+
|
145
|
+
## Disclaimer
|
146
|
+
|
147
|
+
Zippy is provided "as is" without warranty of any kind. Use at your own risk and keep backups of critical data.
|
148
|
+
|
149
|
+
**The author is not responsible for any data loss or damage resulting from the use of this software.**
|
@@ -0,0 +1,16 @@
|
|
1
|
+
py_zippy-1.0.0.dist-info/licenses/LICENSE,sha256=9VKiQ2gJZk1vRNRUsnO3nBWxbNyA65LmMo1QORMNyZ0,1073
|
2
|
+
zippy/__init__.py,sha256=smZtZ8pgEvlsAVLKZhQqxDtzrk8K9X3VPSfyNqhjkyU,181
|
3
|
+
zippy/cli.py,sha256=YM0dRTBVAaDjv0E_NcrMfdHOhcuYbQL4KFWx_OZN5vI,8385
|
4
|
+
zippy/create.py,sha256=H3Yb2UDtJoZ2DBB7sdiWdXoXOVVrSNSjD4e_uh1CPFw,9567
|
5
|
+
zippy/extract.py,sha256=fGU2_9ORsL50EydvhzLgNwVQk2XZ6XAevp-tJ38vkeA,4773
|
6
|
+
zippy/list.py,sha256=7oYCHFKAlyuHw4CpZAmnRz7jmdPC0_yzKNNrs1nvAs8,2336
|
7
|
+
zippy/lock.py,sha256=118GnvT3yfIv8wP-DF7M4NCK9Yus1RKtQwdnB0OTlh0,3396
|
8
|
+
zippy/repair.py,sha256=7jCgHuv-dVhYFK3iVmpSX4f6JVWF1jbO8TWzfLAH6zk,9700
|
9
|
+
zippy/test.py,sha256=9nHneW9q_JUq3Lg3XEYIz4TDYVqK7RfyXFkLrv37xhA,5542
|
10
|
+
zippy/unlock.py,sha256=9AQ-6C8jUD0PbE-bXD9LZnropqP5zBh2L6_jHemJtxE,4428
|
11
|
+
zippy/utils.py,sha256=YQECi0vyOmz-RqX6bnnODzs162nAD72flw_bsBrCwkQ,10721
|
12
|
+
py_zippy-1.0.0.dist-info/METADATA,sha256=b170Pu_W2fU-O9UIjiNK3RcGmzQ5yTHVCu6pabxZ9eg,6647
|
13
|
+
py_zippy-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
14
|
+
py_zippy-1.0.0.dist-info/entry_points.txt,sha256=MuEnhP2ZUmhg38SIktO91HhqwYmODGjKj8nfyfI72C0,41
|
15
|
+
py_zippy-1.0.0.dist-info/top_level.txt,sha256=Y5J0yny13o5cYQhIr0YVS4WX5bYyFP0iFbMQ1WBa12E,6
|
16
|
+
py_zippy-1.0.0.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 John H. Mitander
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1 @@
|
|
1
|
+
zippy
|
zippy/__init__.py
ADDED
zippy/cli.py
ADDED
@@ -0,0 +1,286 @@
|
|
1
|
+
import argparse
|
2
|
+
import json
|
3
|
+
import os
|
4
|
+
import readline
|
5
|
+
import sys
|
6
|
+
from typing import Iterable, Optional
|
7
|
+
|
8
|
+
from dotenv import load_dotenv
|
9
|
+
|
10
|
+
from . import __version__
|
11
|
+
from .create import create_archive
|
12
|
+
from .extract import extract_archive
|
13
|
+
from .list import list_archive_contents
|
14
|
+
from .lock import lock_archive
|
15
|
+
from .repair import repair_archive
|
16
|
+
from .test import test_archive_integrity
|
17
|
+
from .unlock import unlock_archive
|
18
|
+
from .utils import (
|
19
|
+
Fore,
|
20
|
+
SUPPORTED_ARCHIVE_TYPES,
|
21
|
+
ZippyError,
|
22
|
+
color_text,
|
23
|
+
configure_logging,
|
24
|
+
get_logger,
|
25
|
+
handle_errors,
|
26
|
+
validate_path,
|
27
|
+
)
|
28
|
+
|
29
|
+
SCRIPT_NAME = "zippy"
|
30
|
+
CONFIG_FILE = "zippy_config.json"
|
31
|
+
PASSWORD_DICT_DEFAULT = "password_list.txt"
|
32
|
+
|
33
|
+
|
34
|
+
def _format_supported_types() -> str:
|
35
|
+
unique_types = sorted({value for value in SUPPORTED_ARCHIVE_TYPES.values()})
|
36
|
+
return ", ".join(unique_types)
|
37
|
+
|
38
|
+
|
39
|
+
def display_banner() -> None:
|
40
|
+
banner = f"{SCRIPT_NAME.upper()} v{__version__}"
|
41
|
+
print(color_text(banner, Fore.CYAN if Fore else None))
|
42
|
+
|
43
|
+
|
44
|
+
def setup_auto_completion(flags: Iterable[str]) -> None:
|
45
|
+
def completer(text: str, state: int) -> Optional[str]:
|
46
|
+
options = [flag for flag in flags if flag.startswith(text)]
|
47
|
+
return options[state] if state < len(options) else None
|
48
|
+
|
49
|
+
readline.set_completer(completer)
|
50
|
+
readline.parse_and_bind("tab: complete")
|
51
|
+
|
52
|
+
|
53
|
+
def build_parser() -> argparse.ArgumentParser:
|
54
|
+
parser = argparse.ArgumentParser(
|
55
|
+
description=f"{SCRIPT_NAME.upper()} - Archive Utility Toolkit",
|
56
|
+
)
|
57
|
+
command_group = parser.add_mutually_exclusive_group(required=True)
|
58
|
+
command_group.add_argument(
|
59
|
+
"--extract",
|
60
|
+
"-x",
|
61
|
+
dest="command",
|
62
|
+
action="store_const",
|
63
|
+
const="extract",
|
64
|
+
help="Extract archive contents",
|
65
|
+
)
|
66
|
+
command_group.add_argument(
|
67
|
+
"--create",
|
68
|
+
"-c",
|
69
|
+
dest="command",
|
70
|
+
action="store_const",
|
71
|
+
const="create",
|
72
|
+
help="Create a new archive",
|
73
|
+
)
|
74
|
+
command_group.add_argument(
|
75
|
+
"--list",
|
76
|
+
"-l",
|
77
|
+
dest="command",
|
78
|
+
action="store_const",
|
79
|
+
const="list",
|
80
|
+
help="List archive contents",
|
81
|
+
)
|
82
|
+
command_group.add_argument(
|
83
|
+
"--test",
|
84
|
+
"-t",
|
85
|
+
dest="command",
|
86
|
+
action="store_const",
|
87
|
+
const="test",
|
88
|
+
help="Test archive integrity",
|
89
|
+
)
|
90
|
+
command_group.add_argument(
|
91
|
+
"--unlock",
|
92
|
+
"-u",
|
93
|
+
dest="command",
|
94
|
+
action="store_const",
|
95
|
+
const="unlock",
|
96
|
+
help="Attempt to unlock a password-protected ZIP",
|
97
|
+
)
|
98
|
+
command_group.add_argument(
|
99
|
+
"--lock",
|
100
|
+
dest="command",
|
101
|
+
action="store_const",
|
102
|
+
const="lock",
|
103
|
+
help="Create or re-lock a password-protected ZIP",
|
104
|
+
)
|
105
|
+
command_group.add_argument(
|
106
|
+
"--repair",
|
107
|
+
"-r",
|
108
|
+
dest="command",
|
109
|
+
action="store_const",
|
110
|
+
const="repair",
|
111
|
+
help="[Experimental] Attempt archive repair",
|
112
|
+
)
|
113
|
+
parser.add_argument("archive_file", nargs="?", help="Path to the archive file.")
|
114
|
+
parser.add_argument(
|
115
|
+
"-o",
|
116
|
+
"--output",
|
117
|
+
dest="output_path",
|
118
|
+
default=".",
|
119
|
+
help="Output directory for extraction (default: .)",
|
120
|
+
)
|
121
|
+
parser.add_argument(
|
122
|
+
"-p", "--password", dest="password", help="Password for archive operations"
|
123
|
+
)
|
124
|
+
parser.add_argument(
|
125
|
+
"-d",
|
126
|
+
"--dictionary",
|
127
|
+
dest="dictionary_file",
|
128
|
+
default=PASSWORD_DICT_DEFAULT,
|
129
|
+
help=f"Dictionary file for unlock attempts (default: {PASSWORD_DICT_DEFAULT})",
|
130
|
+
)
|
131
|
+
parser.add_argument(
|
132
|
+
"-f",
|
133
|
+
"--files",
|
134
|
+
dest="files_to_add",
|
135
|
+
help="Comma-separated files/directories to add",
|
136
|
+
)
|
137
|
+
parser.add_argument("--type", dest="archive_type", help="Force archive type")
|
138
|
+
parser.add_argument(
|
139
|
+
"--repair-mode",
|
140
|
+
dest="repair_mode",
|
141
|
+
default="remove_corrupted",
|
142
|
+
choices=["remove_corrupted", "scan_only"],
|
143
|
+
help="Repair mode for ZIP archives",
|
144
|
+
)
|
145
|
+
parser.add_argument("--verbose", action="store_true", help="Enable verbose logging")
|
146
|
+
parser.add_argument(
|
147
|
+
"--no-animation", action="store_true", help="Disable loading animation"
|
148
|
+
)
|
149
|
+
parser.add_argument(
|
150
|
+
"--save-config", dest="save_config_file", help="Save current settings to JSON"
|
151
|
+
)
|
152
|
+
parser.add_argument(
|
153
|
+
"--load-config", dest="load_config_file", help="Load settings from JSON"
|
154
|
+
)
|
155
|
+
parser.add_argument("--version", action="version", version=f"{SCRIPT_NAME} {__version__}")
|
156
|
+
return parser
|
157
|
+
|
158
|
+
|
159
|
+
def _persist_config(args: argparse.Namespace, path: str) -> None:
|
160
|
+
data = vars(args).copy()
|
161
|
+
with open(path, "w", encoding="utf-8") as handle:
|
162
|
+
json.dump(data, handle, indent=4)
|
163
|
+
get_logger(__name__).info("Configuration saved to %s", path)
|
164
|
+
|
165
|
+
|
166
|
+
def _load_config(path: str) -> dict:
|
167
|
+
if not os.path.exists(path):
|
168
|
+
handle_errors(f"Configuration file not found: {path}")
|
169
|
+
with open(path, "r", encoding="utf-8") as handle:
|
170
|
+
data = json.load(handle)
|
171
|
+
get_logger(__name__).info("Configuration loaded from %s", path)
|
172
|
+
return data
|
173
|
+
|
174
|
+
|
175
|
+
def _apply_loaded_config(args: argparse.Namespace, config: dict) -> None:
|
176
|
+
for key, value in config.items():
|
177
|
+
if hasattr(args, key):
|
178
|
+
setattr(args, key, value)
|
179
|
+
|
180
|
+
|
181
|
+
def _validate_archive_path(command: str, archive_path: Optional[str]) -> None:
|
182
|
+
if not archive_path:
|
183
|
+
handle_errors("Archive file path is required for this command.")
|
184
|
+
if command in {"extract", "list", "test", "unlock", "repair"}:
|
185
|
+
validate_path(archive_path, "Archive file path", must_exist=True, is_dir=False)
|
186
|
+
elif command in {"create", "lock"}:
|
187
|
+
directory = os.path.dirname(os.path.abspath(archive_path)) or "."
|
188
|
+
validate_path(directory, "Output directory", must_exist=True, is_dir=True)
|
189
|
+
|
190
|
+
|
191
|
+
def _execute_command(args: argparse.Namespace) -> None:
|
192
|
+
command = args.command
|
193
|
+
archive_path = args.archive_file
|
194
|
+
_validate_archive_path(command, archive_path)
|
195
|
+
|
196
|
+
if command == "extract":
|
197
|
+
extract_archive(
|
198
|
+
archive_path,
|
199
|
+
args.output_path,
|
200
|
+
args.password,
|
201
|
+
args.verbose,
|
202
|
+
args.no_animation,
|
203
|
+
)
|
204
|
+
elif command == "create":
|
205
|
+
create_archive(
|
206
|
+
archive_path,
|
207
|
+
args.files_to_add,
|
208
|
+
args.archive_type,
|
209
|
+
args.password,
|
210
|
+
args.verbose,
|
211
|
+
args.no_animation,
|
212
|
+
)
|
213
|
+
elif command == "list":
|
214
|
+
list_archive_contents(archive_path, args.verbose, args.no_animation)
|
215
|
+
elif command == "test":
|
216
|
+
test_archive_integrity(
|
217
|
+
archive_path, args.verbose, args.no_animation, args.password
|
218
|
+
)
|
219
|
+
elif command == "unlock":
|
220
|
+
unlock_archive(
|
221
|
+
archive_path,
|
222
|
+
args.dictionary_file,
|
223
|
+
args.password,
|
224
|
+
args.verbose,
|
225
|
+
args.no_animation,
|
226
|
+
)
|
227
|
+
elif command == "lock":
|
228
|
+
lock_archive(
|
229
|
+
archive_path,
|
230
|
+
args.files_to_add,
|
231
|
+
"zip",
|
232
|
+
args.password,
|
233
|
+
args.verbose,
|
234
|
+
args.no_animation,
|
235
|
+
)
|
236
|
+
elif command == "repair":
|
237
|
+
repair_archive(
|
238
|
+
archive_path,
|
239
|
+
args.verbose,
|
240
|
+
args.no_animation,
|
241
|
+
args.repair_mode,
|
242
|
+
args.password,
|
243
|
+
)
|
244
|
+
else: # pragma: no cover - defensive
|
245
|
+
handle_errors("Invalid command. See 'help'.")
|
246
|
+
|
247
|
+
|
248
|
+
def main(argv: Optional[list[str]] = None) -> int:
|
249
|
+
load_dotenv()
|
250
|
+
parser = build_parser()
|
251
|
+
args = parser.parse_args(argv)
|
252
|
+
configure_logging(args.verbose)
|
253
|
+
command_flags = [
|
254
|
+
"--extract",
|
255
|
+
"--create",
|
256
|
+
"--list",
|
257
|
+
"--test",
|
258
|
+
"--unlock",
|
259
|
+
"--lock",
|
260
|
+
"--repair",
|
261
|
+
]
|
262
|
+
setup_auto_completion(command_flags)
|
263
|
+
|
264
|
+
if args.save_config_file:
|
265
|
+
_persist_config(args, args.save_config_file)
|
266
|
+
return 0
|
267
|
+
|
268
|
+
if args.load_config_file:
|
269
|
+
config = _load_config(args.load_config_file)
|
270
|
+
_apply_loaded_config(args, config)
|
271
|
+
|
272
|
+
display_banner()
|
273
|
+
try:
|
274
|
+
_execute_command(args)
|
275
|
+
except ZippyError as error:
|
276
|
+
get_logger(__name__).error(str(error))
|
277
|
+
return getattr(error, "exit_code", 1)
|
278
|
+
return 0
|
279
|
+
|
280
|
+
|
281
|
+
if __name__ == "__main__": # pragma: no cover
|
282
|
+
try:
|
283
|
+
sys.exit(main())
|
284
|
+
except ZippyError as error:
|
285
|
+
get_logger(__name__).error(str(error))
|
286
|
+
sys.exit(getattr(error, "exit_code", 1))
|