-
Notifications
You must be signed in to change notification settings - Fork 12
/
defaults
executable file
·168 lines (128 loc) · 6.13 KB
/
defaults
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#!/usr/bin/env python
import argparse
import collections
import configparser
import glob
import itertools
import os
import posixpath
import shlex
import subprocess
import sys
import tarfile
import tempfile
import urllib.request
import tqdm
EXIT_FAILURE = 1
DEFAULT_PACMAN_CACHE_PATH = '/var/cache/pacman/pkg/'
PACMAN_CONF_PATH = '/etc/pacman.conf'
MAKEPKG_CONF_PATH = '/etc/makepkg.conf'
PACKAGE_FILE_NAME_GLOB_FORMAT = '{name}-{version}-*{pkgext}'
CHUNK_SIZE = 1048576
DESCRIPTION = '''Inspect the original version of files belonging to installed packages.
If the editor is not specified using --editor, the environment variables EDIT
and VISUAL will be used; alternatively --stdout can be used to pipe the original
file's contents directly to stdout.
Files are searched for in packages located in the "CacheDir" directories specified
in /etc/pacman.conf (default: /var/cache/pacman/pkg/) and in the "PKGDEST"
directory specified in /etc/makepkg.conf, if defined. Defaults will only work
with foreign packages, if the "PKGDEST" directory is specified.
If the package can't be found in either, defaults attempt to download the latest
version of the package from the available pacman mirrors, however this only
works with native packages.'''
argument_parser = argparse.ArgumentParser(description=DESCRIPTION, formatter_class=argparse.RawDescriptionHelpFormatter)
argument_parser.add_argument('--editor', metavar='PATH', default=os.getenv('EDITOR', os.getenv('VISUAL')))
argument_parser.add_argument('--stdout', action='store_true')
argument_parser.add_argument('path')
Package = collections.namedtuple('Package', ('name', 'version'))
def get_command_stdout(*arguments):
try:
return subprocess.check_output(arguments, universal_newlines=True, stderr=subprocess.DEVNULL)
except subprocess.CalledProcessError:
pass
def get_owning_package(path):
output = get_command_stdout('pacman', '-Qo', path)
if output is None:
return
return Package(*output.rsplit(None, 2)[1:])
def get_package_download_url(name):
output = get_command_stdout('pacman', '-Sp', name)
if output is not None:
return output.strip()
def read_chunks(file, size=CHUNK_SIZE):
chunk = file.read(size)
while chunk:
yield chunk
chunk = file.read(size)
def download_package(name, path, chunk_size=CHUNK_SIZE):
url = get_package_download_url(name)
with urllib.request.urlopen(url) as response, open(path, 'wb') as file:
with tqdm.tqdm(desc=posixpath.basename(response.url), total=response.length, unit_scale=True) as progress_bar:
for chunk in read_chunks(response, chunk_size):
file.write(chunk)
progress_bar.update(chunk_size)
def get_pacman_cache_paths():
config = configparser.ConfigParser(allow_no_value=True)
config.read(PACMAN_CONF_PATH)
return shlex.split(config.get('options', 'CacheDir', fallback=DEFAULT_PACMAN_CACHE_PATH))
# Naive and incomplete, but good enough to get access to most important variables
def get_makepkg_conf():
variables = {}
with open(MAKEPKG_CONF_PATH) as file:
for line in (l.strip() for l in file if not l.startswith('#')):
parts = line.split('=', 1)
if len(parts) > 1:
key, value = parts
# Parse the value using shell-like syntax
parts = shlex.split(value)
variables[key] = parts[0] if parts else ''
return variables
def main(arguments):
if not arguments.stdout and not arguments.editor:
argument_parser.exit(EXIT_FAILURE, 'Could not determine editor.\n')
path = os.path.abspath(os.path.expanduser(arguments.path))
package = get_owning_package(path)
if not package:
argument_parser.exit(EXIT_FAILURE, 'Could not determine owning package.\n')
print(arguments.path, 'is part of', package.name, package.version, file=sys.stderr)
# Try to find package in pacman's cache directory
makepkg_conf = get_makepkg_conf()
package_file_name_glob = PACKAGE_FILE_NAME_GLOB_FORMAT.format(
name=package.name, version=package.version, pkgext=makepkg_conf['PKGEXT'])
pacman_package_globs = tuple(os.path.join(glob.escape(p), package_file_name_glob) for p in get_pacman_cache_paths())
# Flatten glob results to a single tuple
pacman_package_paths = tuple(p for p in itertools.chain(*(glob.glob(g) for g in pacman_package_globs)))
# Try to find package in makepkg's package destination directory
makepkg_pkgdest = makepkg_conf.get('PKGDEST')
makepkg_package_glob = os.path.join(glob.escape(makepkg_pkgdest), package_file_name_glob) if makepkg_pkgdest else ''
package_paths = pacman_package_paths or glob.glob(makepkg_package_glob)
if package_paths:
package_path = package_paths[0]
else:
# Package doesn't seem to be saved locally, attempt to find the download URL using the current mirrors
download_url = get_package_download_url(package.name)
if not download_url:
argument_parser.exit(
EXIT_FAILURE, 'Could not find package on the system or determine download URL.\n')
# Write to stderr so --stdout can be used to pipe binary output directly into a file
print('Package not found in cache, downloading...', file=sys.stderr)
package_path = tempfile.mkstemp()[1]
download_package(package.name, package_path)
package_file = tarfile.open(package_path)
member_path = os.path.relpath(path, '/')
member_file = package_file.extractfile(member_path)
# Write file to stdout if requested
if arguments.stdout:
for chunk in read_chunks(member_file):
sys.stdout.buffer.write(chunk)
return
# Write member file contents to temporary file
file_descriptor, extraction_path = tempfile.mkstemp(os.path.basename(member_path))
with os.fdopen(file_descriptor, 'wb') as file:
for chunk in read_chunks(member_file):
file.write(chunk)
# Open original and local file in the configured editor
subprocess.run(shlex.split(arguments.editor) + [path, extraction_path])
if __name__ == '__main__':
arguments = argument_parser.parse_args()
main(arguments)