archae 2026.2.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.
- archae/__init__.py +5 -0
- archae/__main__.py +9 -0
- archae/cli.py +176 -0
- archae/config.py +107 -0
- archae/default_settings.toml +9 -0
- archae/extractor.py +249 -0
- archae/options.yaml +39 -0
- archae/py.typed +0 -0
- archae/util/__init__.py +1 -0
- archae/util/archiver/__init__.py +6 -0
- archae/util/archiver/base_archiver.py +55 -0
- archae/util/archiver/peazip.py +159 -0
- archae/util/archiver/seven_zip.py +199 -0
- archae/util/archiver/unar.py +158 -0
- archae/util/converter/file_size.py +77 -0
- archae/util/enum/__init__.py +5 -0
- archae/util/enum/byte_scale.py +55 -0
- archae/util/file_tracker.py +93 -0
- archae/util/tool_manager.py +112 -0
- archae-2026.2.0.dist-info/METADATA +161 -0
- archae-2026.2.0.dist-info/RECORD +23 -0
- archae-2026.2.0.dist-info/WHEEL +4 -0
- archae-2026.2.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""peazip archiver/extractor implementation."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import ClassVar
|
|
6
|
+
|
|
7
|
+
from .base_archiver import BaseArchiver
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PeazipArchiver(BaseArchiver):
|
|
11
|
+
"""Archiver implementation for peazip."""
|
|
12
|
+
|
|
13
|
+
file_extensions: ClassVar[list[str]] = [
|
|
14
|
+
"appinstaller",
|
|
15
|
+
"appx",
|
|
16
|
+
"appxbundle",
|
|
17
|
+
"gz",
|
|
18
|
+
"tgz",
|
|
19
|
+
"jar",
|
|
20
|
+
"ear",
|
|
21
|
+
"war",
|
|
22
|
+
"emsix",
|
|
23
|
+
"emsixbundle",
|
|
24
|
+
"msix",
|
|
25
|
+
"msixbundle",
|
|
26
|
+
"apk",
|
|
27
|
+
"deb",
|
|
28
|
+
"cab",
|
|
29
|
+
"chm",
|
|
30
|
+
"chw",
|
|
31
|
+
"chi",
|
|
32
|
+
"chq",
|
|
33
|
+
"pptx",
|
|
34
|
+
"pptm ",
|
|
35
|
+
"xlsx",
|
|
36
|
+
"xlsm",
|
|
37
|
+
"docx",
|
|
38
|
+
"docm",
|
|
39
|
+
"7z",
|
|
40
|
+
"s7z",
|
|
41
|
+
"ace",
|
|
42
|
+
"dmg",
|
|
43
|
+
"img",
|
|
44
|
+
"arc",
|
|
45
|
+
"pak",
|
|
46
|
+
"arj",
|
|
47
|
+
"br",
|
|
48
|
+
"bz2",
|
|
49
|
+
"tbz2",
|
|
50
|
+
"crx",
|
|
51
|
+
"z",
|
|
52
|
+
"taz",
|
|
53
|
+
"cpio",
|
|
54
|
+
"arc",
|
|
55
|
+
"pak",
|
|
56
|
+
"iso",
|
|
57
|
+
"img",
|
|
58
|
+
"lzma",
|
|
59
|
+
"wim",
|
|
60
|
+
"swm",
|
|
61
|
+
"esd",
|
|
62
|
+
"msi",
|
|
63
|
+
"msp",
|
|
64
|
+
"rar",
|
|
65
|
+
"r00",
|
|
66
|
+
"rpm",
|
|
67
|
+
"tar",
|
|
68
|
+
"vhd",
|
|
69
|
+
"vhdx",
|
|
70
|
+
"xar",
|
|
71
|
+
"pkg",
|
|
72
|
+
"xpi",
|
|
73
|
+
"xz",
|
|
74
|
+
"txz",
|
|
75
|
+
"ipa",
|
|
76
|
+
"zip",
|
|
77
|
+
"zipx",
|
|
78
|
+
"aar",
|
|
79
|
+
"zst",
|
|
80
|
+
]
|
|
81
|
+
mime_types: ClassVar[list[str]] = [
|
|
82
|
+
"application/appinstaller",
|
|
83
|
+
"application/appx",
|
|
84
|
+
"application/appxbundle",
|
|
85
|
+
"application/gzip",
|
|
86
|
+
"application/java-archive",
|
|
87
|
+
"application/msix",
|
|
88
|
+
"application/msixbundle",
|
|
89
|
+
"application/vnd.android.package-archive",
|
|
90
|
+
"application/vnd.debian.binary-package",
|
|
91
|
+
"application/vnd.ms-cab-compressed",
|
|
92
|
+
"application/vnd.ms-htmlhelp",
|
|
93
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
94
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
95
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
96
|
+
"application/x-7z-compressed",
|
|
97
|
+
"application/x-ace-compressed",
|
|
98
|
+
"application/x-apple-diskimage",
|
|
99
|
+
"application/x-arc",
|
|
100
|
+
"application/x-arj",
|
|
101
|
+
"application/x-brotli",
|
|
102
|
+
"application/x-bzip2",
|
|
103
|
+
"application/x-chrome-extension",
|
|
104
|
+
"application/x-compress",
|
|
105
|
+
"application/x-cpio",
|
|
106
|
+
"application/x-freearc",
|
|
107
|
+
"application/x-iso9660-image",
|
|
108
|
+
"application/x-lzma",
|
|
109
|
+
"application/x-ms-wim",
|
|
110
|
+
"application/x-ole-storage",
|
|
111
|
+
"application/x-rar-compressed",
|
|
112
|
+
"application/x-rpm",
|
|
113
|
+
"application/x-tar",
|
|
114
|
+
"application/x-vhd",
|
|
115
|
+
"application/x-xar",
|
|
116
|
+
"application/x-xpinstall",
|
|
117
|
+
"application/x-xz",
|
|
118
|
+
"application/zip",
|
|
119
|
+
"application/zip",
|
|
120
|
+
"application/zip",
|
|
121
|
+
"application/zstd",
|
|
122
|
+
]
|
|
123
|
+
archiver_name: str = "peazip"
|
|
124
|
+
executable_name: str = "pea"
|
|
125
|
+
|
|
126
|
+
def __init__(self, executable_path: str | Path) -> None:
|
|
127
|
+
"""Initialize the peazip archiver.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
executable_path: Path to the peazip executable.
|
|
131
|
+
"""
|
|
132
|
+
self.executable_path = Path(executable_path)
|
|
133
|
+
|
|
134
|
+
def extract_archive(self, archive_path: Path, extract_dir: Path) -> None:
|
|
135
|
+
"""Extracts an archive to a specified directory.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
archive_path (Path): The path to the archive file.
|
|
139
|
+
extract_dir (Path): The directory to extract the archive to.
|
|
140
|
+
|
|
141
|
+
"""
|
|
142
|
+
command: list[str] = [
|
|
143
|
+
str(self.executable_path),
|
|
144
|
+
"-ext2simple",
|
|
145
|
+
str(archive_path),
|
|
146
|
+
str(extract_dir),
|
|
147
|
+
]
|
|
148
|
+
subprocess.run(command, check=True) # noqa: S603
|
|
149
|
+
|
|
150
|
+
def get_archive_uncompressed_size(self, archive_path: Path) -> int: # noqa: ARG002
|
|
151
|
+
"""Get the uncompressed size of the contents.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
archive_path (Path): The path to the archive file.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
int: The size of the contents
|
|
158
|
+
"""
|
|
159
|
+
return -1
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""7zip archiver/extractor implementation."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import ClassVar
|
|
6
|
+
|
|
7
|
+
from .base_archiver import BaseArchiver
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SevenZipArchiver(BaseArchiver):
|
|
11
|
+
"""Archiver implementation for 7zip."""
|
|
12
|
+
|
|
13
|
+
file_extensions: ClassVar[list[str]] = [
|
|
14
|
+
"7z",
|
|
15
|
+
"s7z",
|
|
16
|
+
"apk",
|
|
17
|
+
"bz2",
|
|
18
|
+
"tbz2",
|
|
19
|
+
"crx",
|
|
20
|
+
"xpi",
|
|
21
|
+
"deb",
|
|
22
|
+
"gz",
|
|
23
|
+
"tgz",
|
|
24
|
+
"ipa",
|
|
25
|
+
"jar",
|
|
26
|
+
"ear",
|
|
27
|
+
"war",
|
|
28
|
+
"lzma",
|
|
29
|
+
"cab",
|
|
30
|
+
"docx",
|
|
31
|
+
"docm",
|
|
32
|
+
"pptx",
|
|
33
|
+
"pptm",
|
|
34
|
+
"xlsx",
|
|
35
|
+
"xlsm",
|
|
36
|
+
"emsix",
|
|
37
|
+
"emsixbundle",
|
|
38
|
+
"msix",
|
|
39
|
+
"appinstaller",
|
|
40
|
+
"appx",
|
|
41
|
+
"appxbundle",
|
|
42
|
+
"msixbundle",
|
|
43
|
+
"z",
|
|
44
|
+
"taz",
|
|
45
|
+
"tar",
|
|
46
|
+
"zip",
|
|
47
|
+
"zipx",
|
|
48
|
+
"appimage",
|
|
49
|
+
"dmg ",
|
|
50
|
+
"img",
|
|
51
|
+
"arj",
|
|
52
|
+
"cpio",
|
|
53
|
+
"cramfs",
|
|
54
|
+
"raw",
|
|
55
|
+
"alz",
|
|
56
|
+
"ext",
|
|
57
|
+
"ext2",
|
|
58
|
+
"ext3",
|
|
59
|
+
"ext4",
|
|
60
|
+
"xar",
|
|
61
|
+
"pkg",
|
|
62
|
+
"fat",
|
|
63
|
+
"gpt",
|
|
64
|
+
"hfs",
|
|
65
|
+
"hfsx",
|
|
66
|
+
"iso",
|
|
67
|
+
"lha",
|
|
68
|
+
"lhz",
|
|
69
|
+
"mbr",
|
|
70
|
+
"chm",
|
|
71
|
+
"chw",
|
|
72
|
+
"chi",
|
|
73
|
+
"chq",
|
|
74
|
+
"msi",
|
|
75
|
+
"msp",
|
|
76
|
+
"vhd",
|
|
77
|
+
"vhdx",
|
|
78
|
+
"ntfs",
|
|
79
|
+
"nsi",
|
|
80
|
+
"exe",
|
|
81
|
+
"nsis",
|
|
82
|
+
"qcow2",
|
|
83
|
+
"qcow",
|
|
84
|
+
"qcow2c",
|
|
85
|
+
"rpm",
|
|
86
|
+
"rar",
|
|
87
|
+
"r00",
|
|
88
|
+
"sqfs",
|
|
89
|
+
"sfs",
|
|
90
|
+
"sqsh",
|
|
91
|
+
"squashfs",
|
|
92
|
+
"scap",
|
|
93
|
+
"uefif",
|
|
94
|
+
"udf",
|
|
95
|
+
"edb",
|
|
96
|
+
"edp",
|
|
97
|
+
"edr",
|
|
98
|
+
"a",
|
|
99
|
+
"ar",
|
|
100
|
+
"deb",
|
|
101
|
+
"lib",
|
|
102
|
+
"vdi",
|
|
103
|
+
"vmdk",
|
|
104
|
+
"wim",
|
|
105
|
+
"swm",
|
|
106
|
+
"esd",
|
|
107
|
+
"xz",
|
|
108
|
+
"txz",
|
|
109
|
+
]
|
|
110
|
+
mime_types: ClassVar[list[str]] = [
|
|
111
|
+
"application/x-7z-compressed",
|
|
112
|
+
"application/vnd.android.package-archive",
|
|
113
|
+
"application/x-bzip2",
|
|
114
|
+
"application/x-chrome-extension",
|
|
115
|
+
"application/x-xpinstall",
|
|
116
|
+
"application/vnd.debian.binary-package",
|
|
117
|
+
"application/gzip",
|
|
118
|
+
"application/java-archive",
|
|
119
|
+
"application/x-lzma",
|
|
120
|
+
"application/vnd.ms-cab-compressed",
|
|
121
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
122
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
123
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
124
|
+
"application/msix",
|
|
125
|
+
"application/appinstaller",
|
|
126
|
+
"application/appx",
|
|
127
|
+
"application/appxbundle",
|
|
128
|
+
"application/msixbundle",
|
|
129
|
+
"application/x-compress",
|
|
130
|
+
"application/x-tar",
|
|
131
|
+
"application/zip",
|
|
132
|
+
"application/x-apple-diskimage",
|
|
133
|
+
"application/x-arj",
|
|
134
|
+
"application/x-cpio",
|
|
135
|
+
"application/vnd.efi.img",
|
|
136
|
+
"application/x-alz-compressed",
|
|
137
|
+
"application/x-xar",
|
|
138
|
+
"application/x-iso9660-image",
|
|
139
|
+
"application/x-lzh",
|
|
140
|
+
"application/vnd.ms-htmlhelp",
|
|
141
|
+
"application/x-ole-storage",
|
|
142
|
+
"application/x-vhd",
|
|
143
|
+
"text/x-nsis",
|
|
144
|
+
"application/x-qemu-disk",
|
|
145
|
+
"application/x-rpm",
|
|
146
|
+
"application/x-rar-compressed",
|
|
147
|
+
"application/vnd.squashfs",
|
|
148
|
+
"application/x-archive",
|
|
149
|
+
"application/x-virtualbox-vdi",
|
|
150
|
+
"application/x-vmdk-disk",
|
|
151
|
+
"application/x-ms-wim",
|
|
152
|
+
"application/x-xz",
|
|
153
|
+
]
|
|
154
|
+
archiver_name: str = "7zip"
|
|
155
|
+
executable_name: str = "7z"
|
|
156
|
+
|
|
157
|
+
def __init__(self, executable_path: str | Path) -> None:
|
|
158
|
+
"""Initialize the 7zip archiver.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
executable_path: Path to the 7zip executable.
|
|
162
|
+
"""
|
|
163
|
+
self.executable_path = Path(executable_path)
|
|
164
|
+
|
|
165
|
+
def extract_archive(self, archive_path: Path, extract_dir: Path) -> None:
|
|
166
|
+
"""Extracts an archive to a specified directory.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
archive_path (Path): The path to the archive file.
|
|
170
|
+
extract_dir (Path): The directory to extract the archive to.
|
|
171
|
+
|
|
172
|
+
"""
|
|
173
|
+
command: list[str] = [
|
|
174
|
+
str(self.executable_path),
|
|
175
|
+
"x",
|
|
176
|
+
str(archive_path),
|
|
177
|
+
f"-o{extract_dir!s}",
|
|
178
|
+
]
|
|
179
|
+
subprocess.run(command, check=True) # noqa: S603
|
|
180
|
+
|
|
181
|
+
def get_archive_uncompressed_size(self, archive_path: Path) -> int:
|
|
182
|
+
"""Get the uncompressed size of the contents.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
archive_path (Path): The path to the archive file.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
int: The size of the contents
|
|
189
|
+
"""
|
|
190
|
+
command: list[str] = [str(self.executable_path), "l", "-slt", str(archive_path)]
|
|
191
|
+
result = subprocess.run(command, check=True, capture_output=True, text=True) # noqa: S603
|
|
192
|
+
|
|
193
|
+
result_lines = str(result.stdout).splitlines()
|
|
194
|
+
exploded_size = 0
|
|
195
|
+
for line in result_lines:
|
|
196
|
+
if line.startswith("Size = "):
|
|
197
|
+
exploded_size += int(line[7:])
|
|
198
|
+
|
|
199
|
+
return exploded_size
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""unar archiver/extractor implementation."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import ClassVar
|
|
6
|
+
|
|
7
|
+
from .base_archiver import BaseArchiver
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class UnarArchiver(BaseArchiver):
|
|
11
|
+
"""Archiver implementation for unar."""
|
|
12
|
+
|
|
13
|
+
file_extensions: ClassVar[list[str]] = [
|
|
14
|
+
"appinstaller",
|
|
15
|
+
"appx",
|
|
16
|
+
"appxbundle",
|
|
17
|
+
"gz",
|
|
18
|
+
"tgz",
|
|
19
|
+
"emsix",
|
|
20
|
+
"emsixbundle",
|
|
21
|
+
"msix",
|
|
22
|
+
"msixbundle",
|
|
23
|
+
"apk",
|
|
24
|
+
"deb",
|
|
25
|
+
"cab",
|
|
26
|
+
"pptx",
|
|
27
|
+
"pptm ",
|
|
28
|
+
"xlsx ",
|
|
29
|
+
"xlsm",
|
|
30
|
+
"docx",
|
|
31
|
+
"docm",
|
|
32
|
+
"7z",
|
|
33
|
+
"s7z",
|
|
34
|
+
"ace",
|
|
35
|
+
"alz",
|
|
36
|
+
"arc",
|
|
37
|
+
"pak",
|
|
38
|
+
"a",
|
|
39
|
+
"ar",
|
|
40
|
+
"deb",
|
|
41
|
+
"lib",
|
|
42
|
+
"arj",
|
|
43
|
+
"bz2",
|
|
44
|
+
"tbz2",
|
|
45
|
+
"crx",
|
|
46
|
+
"z",
|
|
47
|
+
"taz",
|
|
48
|
+
"cpio",
|
|
49
|
+
"arc",
|
|
50
|
+
"pak",
|
|
51
|
+
"iso",
|
|
52
|
+
"img",
|
|
53
|
+
"lha",
|
|
54
|
+
"lhz",
|
|
55
|
+
"lzma",
|
|
56
|
+
"msi",
|
|
57
|
+
"msp",
|
|
58
|
+
"rar",
|
|
59
|
+
"r00",
|
|
60
|
+
"sit",
|
|
61
|
+
"sitx",
|
|
62
|
+
"tar",
|
|
63
|
+
"xar",
|
|
64
|
+
"pkg",
|
|
65
|
+
"xpi",
|
|
66
|
+
"xz",
|
|
67
|
+
"txz",
|
|
68
|
+
"zoo",
|
|
69
|
+
"zip",
|
|
70
|
+
"zipx",
|
|
71
|
+
"aar",
|
|
72
|
+
"nsi",
|
|
73
|
+
"exe",
|
|
74
|
+
"nsis",
|
|
75
|
+
"udf",
|
|
76
|
+
"edb",
|
|
77
|
+
"edp",
|
|
78
|
+
"edr",
|
|
79
|
+
]
|
|
80
|
+
mime_types: ClassVar[list[str]] = [
|
|
81
|
+
"application/appinstaller",
|
|
82
|
+
"application/appx",
|
|
83
|
+
"application/appxbundle",
|
|
84
|
+
"application/gzip",
|
|
85
|
+
"application/msix",
|
|
86
|
+
"application/msixbundle",
|
|
87
|
+
"application/vnd.android.package-archive",
|
|
88
|
+
"application/vnd.debian.binary-package",
|
|
89
|
+
"application/vnd.ms-cab-compressed",
|
|
90
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
91
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
92
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
93
|
+
"application/x-7z-compressed",
|
|
94
|
+
"application/x-ace-compressed",
|
|
95
|
+
"application/x-alz-compressed",
|
|
96
|
+
"application/x-arc",
|
|
97
|
+
"application/x-archive",
|
|
98
|
+
"application/x-arj",
|
|
99
|
+
"application/x-bzip2",
|
|
100
|
+
"application/x-chrome-extension",
|
|
101
|
+
"application/x-compress",
|
|
102
|
+
"application/x-cpio",
|
|
103
|
+
"application/x-freearc",
|
|
104
|
+
"application/x-iso9660-image",
|
|
105
|
+
"application/x-lzh",
|
|
106
|
+
"application/x-lzma",
|
|
107
|
+
"application/x-ole-storage",
|
|
108
|
+
"application/x-rar-compressed",
|
|
109
|
+
"application/x-stuffit",
|
|
110
|
+
"application/x-sit",
|
|
111
|
+
"application/x-stuffitx",
|
|
112
|
+
"application/x-sitx",
|
|
113
|
+
"application/x-tar",
|
|
114
|
+
"application/x-xar",
|
|
115
|
+
"application/x-xpinstall",
|
|
116
|
+
"application/x-xz",
|
|
117
|
+
"application/x-zoo",
|
|
118
|
+
"application/zip",
|
|
119
|
+
"application/zip",
|
|
120
|
+
"text/x-nsis",
|
|
121
|
+
]
|
|
122
|
+
archiver_name: str = "unar"
|
|
123
|
+
executable_name: str = "unar"
|
|
124
|
+
|
|
125
|
+
def __init__(self, executable_path: str | Path) -> None:
|
|
126
|
+
"""Initialize the unar archiver.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
executable_path: Path to the unar executable.
|
|
130
|
+
"""
|
|
131
|
+
self.executable_path = Path(executable_path)
|
|
132
|
+
|
|
133
|
+
def extract_archive(self, archive_path: Path, extract_dir: Path) -> None:
|
|
134
|
+
"""Extracts an archive to a specified directory.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
archive_path (Path): The path to the archive file.
|
|
138
|
+
extract_dir (Path): The directory to extract the archive to.
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
command: list[str] = [
|
|
142
|
+
str(self.executable_path),
|
|
143
|
+
"-o",
|
|
144
|
+
str(extract_dir),
|
|
145
|
+
str(archive_path),
|
|
146
|
+
]
|
|
147
|
+
subprocess.run(command, check=True) # noqa: S603
|
|
148
|
+
|
|
149
|
+
def get_archive_uncompressed_size(self, archive_path: Path) -> int: # noqa: ARG002
|
|
150
|
+
"""Get the uncompressed size of the contents.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
archive_path (Path): The path to the archive file.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
int: The size of the contents
|
|
157
|
+
"""
|
|
158
|
+
return -1
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""File size conversion utilities."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from archae.util.enum.byte_scale import ByteScale
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def compact_value(value: float) -> str:
|
|
9
|
+
"""Convert a float of file size to a FileSize string.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
value (float): The size to convert
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
str: A string with the most collapsed exact byte size rep.
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
exponent = 0
|
|
19
|
+
modulo: float = 0
|
|
20
|
+
while modulo == 0 and exponent < int(ByteScale.PETA.value):
|
|
21
|
+
modulo = value % 1024
|
|
22
|
+
if modulo == 0:
|
|
23
|
+
exponent += 1
|
|
24
|
+
value = int(value / 1024)
|
|
25
|
+
return f"{value}{ByteScale(exponent).prefix_letter}" # type: ignore[call-arg]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def expand_value(value: str | int) -> int:
|
|
29
|
+
"""Convert a FileSize string or int to an int.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
value (str | int): The value to convert as necessary.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
int: Size in bytes
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
return int(value)
|
|
40
|
+
except ValueError:
|
|
41
|
+
pass
|
|
42
|
+
except TypeError:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
# Regex to split number and unit
|
|
46
|
+
match = re.match(r"^(\d+(?:\.\d+)?)\s*([KMGTP]B?)$", str(value), re.IGNORECASE)
|
|
47
|
+
if not match:
|
|
48
|
+
msg = f"{value} is not a valid file size (e.g., 10G, 500M)"
|
|
49
|
+
raise ValueError(msg)
|
|
50
|
+
|
|
51
|
+
number, unit = match.groups()
|
|
52
|
+
number = float(number)
|
|
53
|
+
unit = unit[0].upper()
|
|
54
|
+
|
|
55
|
+
byte_scale = 1024 ** (ByteScale.from_prefix_letter(unit).value)
|
|
56
|
+
|
|
57
|
+
# Default to bytes if no specific unit multiplier, or assume B
|
|
58
|
+
return int(number * byte_scale)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def convert(value: str | int) -> int:
|
|
62
|
+
"""Convert a FileSizeParam to an int.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
value (click.Option): The value to convert as necessary.
|
|
66
|
+
param (str): The param we are validating.
|
|
67
|
+
ctx (click.Context): The click Context to fail if we can't parse it.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
int: Size in bytes
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
return expand_value(value)
|
|
75
|
+
except ValueError as err:
|
|
76
|
+
msg = f"Could not convert {value} to file size: {err}"
|
|
77
|
+
raise ValueError(msg) from err
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Byte scale enum for file size operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Self
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ByteScale(Enum):
|
|
10
|
+
"""Byte scale prefix converter."""
|
|
11
|
+
|
|
12
|
+
NONE = (0, "")
|
|
13
|
+
KILO = (1, "K")
|
|
14
|
+
MEGA = (2, "M")
|
|
15
|
+
GIGA = (3, "G")
|
|
16
|
+
TERA = (4, "T")
|
|
17
|
+
PETA = (5, "P")
|
|
18
|
+
|
|
19
|
+
def __new__(cls, exponent: int, prefix_letter: str) -> Self:
|
|
20
|
+
"""Apply values to the new Enum.
|
|
21
|
+
|
|
22
|
+
__new__ is used to control how new enum members are instantiated.
|
|
23
|
+
It must set the `_value_` attribute and any custom attributes.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
exponent (int): the exponent value for the scale
|
|
27
|
+
prefix_letter (str): the prefix letter for the scale
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
ByteScale: A new ByteScale enum.
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
obj = object.__new__(cls)
|
|
34
|
+
obj._value_ = exponent
|
|
35
|
+
obj.prefix_letter = prefix_letter
|
|
36
|
+
return obj
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def prefix_letter(self) -> str:
|
|
40
|
+
"""Return the prefix letter for this scale."""
|
|
41
|
+
return self._prefix_letter
|
|
42
|
+
|
|
43
|
+
@prefix_letter.setter
|
|
44
|
+
def prefix_letter(self, value: str) -> None:
|
|
45
|
+
"""Setter for prefix letter."""
|
|
46
|
+
self._prefix_letter = value
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def from_prefix_letter(prefix_letter: str) -> Self: # type: ignore[misc]
|
|
50
|
+
"""Static method to look up from a prefix_letter."""
|
|
51
|
+
for member in ByteScale:
|
|
52
|
+
if member.prefix_letter == prefix_letter.upper():
|
|
53
|
+
return member
|
|
54
|
+
msg = f"'{prefix_letter}' is not a valid byte scale prefix letter."
|
|
55
|
+
raise ValueError(msg)
|