aboutsummaryrefslogtreecommitdiff
blob: 6557f60533a4bf98f9a87100294b2eefa2170749 (plain)
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
#	vim:fileencoding=utf-8
# Copyright 2001-2018 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

__all__ = ['keywords_header']

import portage
import os
import sys
if sys.hexversion < 0x3000000:
	from io import open
from portage import _encodings, _unicode_decode, _unicode_encode
from portage import settings as ports
from portage.output import colorize
from gentoolkit.eshowkw.display_pretty import colorize_string
from gentoolkit.eshowkw.display_pretty import align_string

# Copied from ekeyword, modified to support arch vs ~arch status
def load_profile_data(portdir=None, repo='gentoo'):
	"""Load the list of known arches from the tree

	Args:
	  portdir: The repository to load all data from (and ignore |repo|)
	  repo: Look up this repository by name to locate profile data

	Returns:
	  A dict mapping the keyword to its preferred state:
	  {'x86': ('stable', 'arch'), 'mips': ('dev', '~arch'), ...}
	"""
	if portdir is None:
		portdir = portage.db[portage.root]['vartree'].settings.repositories[repo].location

	arch_status = {}

	try:
		arch_list = os.path.join(portdir, 'profiles', 'arch.list')
		with open(_unicode_encode(arch_list, encoding=_encodings['fs']),
				encoding=_encodings['content']) as f:
			for line in f:
				line = line.split('#', 1)[0].strip()
				if line:
					arch_status[line] = None
	except IOError:
		pass

	try:
		profile_status = {
			'stable': 0,
			'dev': 1,
			'exp': 2,
			None: 3,
		}
		profiles_list = os.path.join(portdir, 'profiles', 'profiles.desc')
		with open(_unicode_encode(profiles_list, encoding=_encodings['fs']),
				encoding=_encodings['content']) as f:
			for line in f:
				line = line.split('#', 1)[0].split()
				if line:
					arch, _profile, status = line
					arch_status.setdefault(arch, status)
					curr_status = profile_status[arch_status[arch]]
					new_status = profile_status[status]
					if new_status < curr_status:
						arch_status[arch] = status
	except IOError:
		pass

	if arch_status:
		arch_status['all'] = None
	else:
		warning('could not read profile files: %s' % arch_list)
		warning('will not be able to verify args are correct')

	# TODO: support arches.desc once the GLEP is finalized
	# for now, we just hardcode ~mips + *-* (fbsd, prefix)
	for k, v in arch_status.items():
		if k == 'mips' or '-' in k:
			arch_status[k] = (v, '~arch')
		else:
			arch_status[k] = (v, 'arch')

	return arch_status

def gen_arch_list(status):
	_arch_status = load_profile_data()
	if status == "stable":
		return [arch for arch in _arch_status if _arch_status[arch][0] == "stable"]
	elif status == "dev":
		return [arch for arch in _arch_status if _arch_status[arch][0] == "dev"]
	elif status == "exp":
		return [arch for arch in _arch_status if _arch_status[arch][0] == "exp"]
	elif status == "arch":
		return [arch for arch in _arch_status if _arch_status[arch][1] == "arch"]
	elif status == "~arch":
		return [arch for arch in _arch_status if _arch_status[arch][1] == "~arch"]
	else:
		raise TypeError

class keywords_header:
	__IMPARCHS = gen_arch_list("stable")
	__DEV_ARCHS = gen_arch_list("dev")
	__EXP_ARCHS = gen_arch_list("exp")
	__TESTING_KW_ARCHS = gen_arch_list("~arch")
	__ADDITIONAL_FIELDS = [ 'eapi', 'unused', 'slot' ]
	__EXTRA_FIELDS = [ 'repo' ]

	@staticmethod
	def __readKeywords():
		"""Read all available keywords from portage."""
		return [x for x in ports.archlist()
			if not x.startswith('~')]

	@staticmethod
	def __isPrefix(k):
		spl = k.split('-')
		# *-fbsd are not prefix
		return len(spl) > 1 and spl[1] != 'fbsd'

	def __sortKeywords(self, keywords, prefix = False, required_keywords = []):
		"""Sort keywords: order by status (IMP, then DEV, then EXP, then
		prefix), then by name."""

		# user specified only some keywords to display
		if len(required_keywords) != 0:
			tmpkeywords = [k for k in keywords
				if k in required_keywords]
			# idiots might specify non-existant archs
			if len(tmpkeywords) != 0:
				keywords = tmpkeywords

		normal = [k for k in keywords if not self.__isPrefix(k)]
		if prefix:
			longer = [k for k in keywords if self.__isPrefix(k)]
			normal.extend(longer)

		lists = (self.__IMPARCHS + self.__DEV_ARCHS), self.__EXP_ARCHS
		levels = {}
		for kw in normal:
			for level, ls in enumerate(lists):
				if kw in ls:
					levels[kw] = level
					break

		# sort by, in order (to match Bugzilla):
		# 1. non-prefix, then prefix (stable output between -P and not)
		# 2. arch, then ~arch
		# 3. profile stability
		# 4. short keywords, then long (prefix, fbsd)
		# 5. keyword name in reverse component order
		normal.sort(key=lambda kw: (self.__isPrefix(kw),
			kw in self.__TESTING_KW_ARCHS,
			levels.get(kw, 99),
			kw.count('-'),
			list(reversed(kw.split('-')))))
		return normal

	def __readAdditionalFields(self):
		"""Prepare list of aditional fileds displayed by eshowkw (2nd part)"""
		return self.__ADDITIONAL_FIELDS

	def __readExtraFields(self):
		"""Prepare list of extra fileds displayed by eshowkw (3rd part)"""
		return self.__EXTRA_FIELDS

	def __formatKeywords(self, keywords, align, length):
		"""Append colors and align keywords properly"""
		tmp = []
		for keyword in keywords:
			tmp2 = keyword
			keyword = align_string(keyword, align, length)
			# % are used as separators for further split so we wont loose spaces and coloring
			keyword = '%'.join(list(keyword))
			if tmp2 in self.__IMPARCHS:
				tmp.append(colorize_string('darkyellow', keyword))
			elif tmp2 in self.__EXP_ARCHS:
				tmp.append(colorize_string('darkgray', keyword))
			else:
				tmp.append(keyword)
		return tmp

	@staticmethod
	def __formatAdditional(additional, align, length):
		"""Align additional items properly"""
		# % are used as separators for further split so we wont loose spaces and coloring
		return ['%'.join(align_string(x, align, length)) for x in additional]

	def __prepareExtra(self, extra, align, length):
		content = []
		content.append(''.ljust(length, '-'))
		content.extend(self.__formatAdditional(extra, align, length))
		return content

	def __prepareResult(self, keywords, additional, align, length):
		"""Parse keywords and additional fields into one list with proper separators"""
		content = []
		content.append(''.ljust(length, '-'))
		content.extend(self.__formatKeywords(keywords, align, length))
		content.append(''.ljust(length, '-'))
		content.extend(self.__formatAdditional(additional, align, length))
		return content

	def __init__(self, prefix = False, required_keywords = [], keywords_align = 'bottom'):
		"""Initialize keywords header."""
		additional = self.__readAdditionalFields()
		extra = self.__readExtraFields()
		self.keywords = self.__sortKeywords(self.__readKeywords(), prefix, required_keywords)
		self.length = max(
			max([len(x) for x in self.keywords]),
			max([len(x) for x in additional]),
			max([len(x) for x in extra])
		)
		#len(max([max(self.keywords, key=len), max(additional, key=len)], key=len))
		self.keywords_count = len(self.keywords)
		self.additional_count = len(additional)
		self.extra_count = len(extra)
		self.content = self.__prepareResult(self.keywords, additional, keywords_align, self.length)
		self.extra = self.__prepareExtra(extra, keywords_align, self.length)