Repository navigation
Expand file tree
/
Copy pathpkgdev_showkw.py
More file actions
302 lines (259 loc) · 10.3 KB
/
Copy pathpkgdev_showkw.py
File metadata and controls
302 lines (259 loc) · 10.3 KB
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
"""display package keywords"""
import os
import re
from collections.abc import Iterable
from functools import partial
from pkgcore.ebuild import restricts
from pkgcore.util import commandline
from pkgcore.util import packages as pkgutils
from snakeoil.strings import pluralism
from tabulate import tabulate, tabulate_formats
from .. import cli
_SHOWKW_FORMAT = "showkw"
_TABLE_FORMATS = (_SHOWKW_FORMAT, *tabulate_formats)
showkw = cli.ArgumentParser(prog="pkgdev showkw", description="show package keywords")
showkw.add_argument(
"targets",
metavar="target",
nargs="*",
action=commandline.StoreTarget,
help="extended atom matching of packages",
)
output_opts = showkw.add_argument_group("output options")
output_opts.add_argument(
"-f",
"--format",
default=_SHOWKW_FORMAT,
metavar="FORMAT",
choices=_TABLE_FORMATS,
help="keywords table format",
docs=f"""
Output table using specified tabular format (defaults to compressed,
custom format).
Available formats: {", ".join(_TABLE_FORMATS)}
""",
)
output_opts.add_argument(
"-c", "--collapse", action="store_true", help="show collapsed list of arches"
)
arch_options = showkw.add_argument_group("arch options")
arch_options.add_argument("-s", "--stable", action="store_true", help="show stable arches")
arch_options.add_argument("-u", "--unstable", action="store_true", help="show unstable arches")
arch_options.add_argument(
"-o",
"--only-unstable",
action="store_true",
help="show arches that only have unstable keywords",
)
arch_options.add_argument(
"-p", "--prefix", action="store_true", help="show prefix and non-native arches"
)
arch_options.add_argument("-a", "--arch", action="csv_negations", help="select arches to display")
# TODO: allow multi-repo comma-separated input
target_opts = showkw.add_argument_group("target options")
target_opts.add_argument(
"-r",
"--repo",
dest="selected_repo",
metavar="REPO",
priority=29,
action=commandline.StoreRepoObject,
repo_type="all-raw",
allow_external_repos=True,
help="repo to query (defaults to all ebuild repos)",
)
@showkw.bind_delayed_default(30, "repos")
def _setup_repos(namespace, attr):
target_repo = namespace.selected_repo
all_ebuild_repos = namespace.domain.all_ebuild_repos_raw
namespace.cwd = os.getcwd()
# TODO: move this to StoreRepoObject
if target_repo is None:
# determine target repo from the target directory
for repo in all_ebuild_repos.trees:
if namespace.cwd in repo:
target_repo = repo
break
else:
# determine if CWD is inside an unconfigured repo
target_repo = namespace.domain.find_repo(namespace.cwd, config=namespace.config)
# fallback to using all, unfiltered ebuild repos if no target repo can be found
namespace.repo = target_repo if target_repo is not None else all_ebuild_repos
@showkw.bind_delayed_default(40, "arches")
def _setup_arches(namespace, attr):
default_repo = namespace.config.get_default("repo")
try:
known_arches = {arch for r in namespace.repo.trees for arch in r.config.known_arches}
except AttributeError:
try:
# binary/vdb repos use known arches from the default repo
known_arches = default_repo.config.known_arches
except AttributeError:
# TODO: remove fallback for tests after fixing default repo pull
# from faked config
known_arches = set()
arches = known_arches
if namespace.arch is not None:
disabled_arches, enabled_arches = namespace.arch
disabled_arches = set(disabled_arches)
enabled_arches = set(enabled_arches)
unknown_arches = disabled_arches.difference(known_arches) | enabled_arches.difference(
known_arches
)
if unknown_arches:
unknown = ", ".join(map(repr, sorted(unknown_arches)))
known = ", ".join(sorted(known_arches))
es = pluralism(unknown_arches, plural="es")
showkw.error(f"unknown arch{es}: {unknown} (choices: {known})")
if enabled_arches:
arches = arches.intersection(enabled_arches)
if disabled_arches:
arches = arches - disabled_arches
prefix_arches = {x for x in arches if "-" in x}
native_arches = arches.difference(prefix_arches)
arches = native_arches
if namespace.prefix:
arches = arches.union(prefix_arches)
if namespace.stable:
try:
stable_arches = {
arch for r in namespace.repo.trees for arch in r.config.profiles.arches("stable")
}
except AttributeError:
# binary/vdb repos use stable arches from the default repo
stable_arches = default_repo.config.profiles.arches("stable")
arches = arches.intersection(stable_arches)
namespace.known_arches = known_arches
namespace.prefix_arches = prefix_arches
namespace.native_arches = native_arches
namespace.arches = arches
def _colormap(colors, line):
if colors is None:
return line
return colors[line] + line + colors["reset"]
@showkw.bind_final_check
def _validate_args(parser, namespace):
namespace.pkg_dir = False
# disable colors when not using the native output format
if namespace.format != _SHOWKW_FORMAT:
namespace.color = False
if namespace.color:
# default colors to use for keyword types
_COLORS = {
"+": "\u001b[32m",
"~": "\u001b[33m",
"-": "\u001b[31m",
"*": "\u001b[31m",
"o": "\u001b[90;1m",
"reset": "\u001b[0m",
}
else:
_COLORS = None
namespace.colormap = partial(_colormap, _COLORS)
if not namespace.targets:
if namespace.selected_repo:
# use repo restriction since no targets specified
restriction = restricts.RepositoryDep(namespace.selected_repo.repo_id)
token = namespace.selected_repo.repo_id
else:
# Use a path restriction if we're in a repo, obviously it'll work
# faster if we're in an invididual ebuild dir but we're not that
# restrictive.
try:
restriction = namespace.repo.path_restrict(namespace.cwd)
token = namespace.cwd
except (AttributeError, ValueError):
parser.error("missing target argument and not in a supported repo")
# determine if we're grabbing the keywords for a single pkg in cwd
namespace.pkg_dir = any(
isinstance(x, restricts.PackageDep) for x in reversed(restriction.restrictions)
)
namespace.targets = [(token, restriction)]
def _collapse_arches(options, pkgs):
"""Collapse arches into a single set."""
keywords = set()
stable_keywords = set()
unstable_keywords = set()
for pkg in pkgs:
for x in pkg.keywords:
if x[0] == "~":
unstable_keywords.add(x[1:])
elif x in options.arches:
stable_keywords.add(x)
if options.unstable:
keywords.update(unstable_keywords)
if options.only_unstable:
keywords.update(unstable_keywords.difference(stable_keywords))
if not keywords or options.stable:
keywords.update(stable_keywords)
return sorted(keywords.intersection(options.native_arches)) + sorted(
keywords.intersection(options.prefix_arches)
)
_ansi_escapes = re.compile(r"\x1b\[[0-9;]*m")
def _visible_len(string):
"""Length of a string ignoring color escapes."""
return len(_ansi_escapes.sub("", string))
def _render_showkw(headers: list[str], rows: Iterable[list[str]]):
"""Render a table in the compressed, custom format.
Headers are rotated to run vertically so they don't widen the single
character keyword state columns they label.
"""
rows = [[str(cell).strip() for cell in row] for row in rows]
widths = [max([1, *map(_visible_len, col)]) for col in zip(*rows)]
lines = []
depth = max(map(len, headers))
for chars in zip(*(header.rjust(depth) for header in headers)):
lines.append("".join(f" {c:<{w}}" for c, w in zip(chars, widths)).rstrip())
lines.append("-" * (sum(widths) + len(widths)))
for row in rows:
line = (f" {cell}{' ' * (w - _visible_len(cell))}" for cell, w in zip(row, widths))
lines.append("".join(line).rstrip())
return "\n".join(lines)
def _render_rows(options, pkgs, arches):
"""Build rows for tabular data output."""
for pkg in sorted(pkgs):
keywords = set(pkg.keywords)
row: list[str] = [pkg.fullver]
for arch in arches:
if arch in keywords:
line = "+"
elif f"~{arch}" in keywords:
line = "~"
elif f"-{arch}" in keywords:
line = "-"
elif "-*" in keywords:
line = "*"
else:
line = "o"
row.append(options.colormap(line))
row.extend([pkg.eapi, pkg.fullslot, pkg.repo.repo_id])
yield row
@showkw.bind_main_func
def main(options, out, err):
continued = False
for token, restriction in options.targets:
for pkgs in pkgutils.groupby_pkg(options.repo.itermatch(restriction, sorter=sorted)):
if options.collapse:
out.write(" ".join(_collapse_arches(options, pkgs)))
else:
arches = sorted(options.arches.intersection(options.native_arches))
if options.prefix:
arches += sorted(options.arches.intersection(options.prefix_arches))
headers = [""] + arches + ["eapi", "slot", "repo"]
if continued:
out.write()
if not options.pkg_dir:
pkgs = list(pkgs)
out.write(f"keywords for {pkgs[0].unversioned_atom}:")
data = _render_rows(options, pkgs, arches)
if options.format == _SHOWKW_FORMAT:
table = _render_showkw(headers, data)
else:
table = tabulate(
data, headers=headers, tablefmt=options.format, disable_numparse=True
)
out.write(table)
continued = True
if not continued:
err.write(f"{options.prog}: no matches for {token!r}")
return 1