/
compress_excluding_ds_store.py
115 lines (81 loc) 路 3.24 KB
/
compress_excluding_ds_store.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# coding: utf-8
"""Compresses a directory into a zip file excluding .DS_Store files in MacOS.
"""
import argparse
import zipfile
from pathlib import Path
from typing import Iterable, List, Iterator
settings = {'zip_extension': '.zip', 'verbose': False}
def main():
"""Main function."""
parser = build_parser()
args = parser.parse_args()
skipped_names = args.skipped_names
directory = args.directory
settings['verbose'] = args.verbose
zip_file = make_zip_path(directory)
try:
compress_dir(zip_file, directory, skipped_names)
except CompressionError as e:
parser.error('{}'.format(e))
template = 'Zip file "{}" has been created (excluded names: "{}").'
print(template.format(zip_file, ','.join(skipped_names)))
def build_parser() -> argparse.ArgumentParser:
"""Builds the command line parser."""
parser = argparse.ArgumentParser('Compress a directory excluding .DS_Store.')
parser.add_argument(
'--skipped-names',
type=skipped_names_type,
default='.DS_Store',
help='Skipped names.',
)
parser.add_argument(
'--verbose', action='store_true', help='Make the output verbose.'
)
parser.add_argument('directory', type=directory_type, help='Target directory.')
return parser
def directory_type(directory_path: str) -> Path:
"""Processes an argument for "directory" type."""
try:
path = Path(directory_path)
except TypeError as e:
message = 'Specified path is incorrect: "{}".'.format(directory_path)
raise argparse.ArgumentTypeError(message)
if not path.is_dir():
message = 'Specified directory not found: "{}".'.format(path)
raise argparse.ArgumentTypeError(message)
return path
def skipped_names_type(names: str) -> List[str]:
"""Processes an argument for "skipped names" type."""
return names.split(',')
def make_zip_path(directory: Path) -> Path:
"""Generates a zip path from the target directory."""
return directory.with_suffix(settings['zip_extension'])
def compress_dir(zip_out: Path, directory: Path, skipped_names: Iterable[str]):
"""Compresses a directory into a zip file.
"""
if zip_out.exists():
message = 'Specified zip file already exists: "{}".'.format(zip_out)
raise CompressionError(message)
with zipfile.ZipFile(str(zip_out.absolute()), 'w') as z:
if settings['verbose']:
print('Zip file has been initialized: "{}".'.format(z.filename))
for f in iter_all_files(directory):
if f.name in skipped_names:
if settings['verbose']:
print('Skipped: "{}".'.format(f))
continue
z.write(f, f.relative_to(directory))
if settings['verbose']:
print('Added: "{}".'.format(f))
def iter_all_files(directory: Path) -> Iterator[Path]:
"""Aggregates the file paths in a directory recursively."""
if not directory.is_dir():
message = 'Specified directory is not found: "{}".'.format(directory)
raise CompressionError(message)
return directory.glob('**/*.*')
class CompressionError(Exception):
"""A custom error type for the compression process."""
pass
if __name__ == '__main__':
main()