Merge genscripts rev 406
[gentoolkit.git] / pym / gentoolkit / equery / uses.py
1 # Copyright(c) 2009, Gentoo Foundation
2 #
3 # Licensed under the GNU General Public License, v2
4 #
5 # $Header: $
6
7 """Display USE flags for a given package"""
8
9 from __future__ import print_function
10
11 __docformat__ = 'epytext'
12
13 # =======
14 # Imports
15 # =======
16
17 import os
18 import sys
19 from functools import partial
20 from getopt import gnu_getopt, GetoptError
21 from glob import glob
22
23 from portage import settings
24
25 import gentoolkit.pprinter as pp
26 from gentoolkit import errors
27 from gentoolkit.equery import format_options, mod_usage, CONFIG
28 from gentoolkit.helpers import uniqify
29 from gentoolkit.textwrap_ import TextWrapper
30 from gentoolkit.query import Query
31
32 # =======
33 # Globals
34 # =======
35
36 QUERY_OPTS = {"all_versions" : False}
37
38 # =========
39 # Functions
40 # =========
41
42 def print_help(with_description=True):
43         """Print description, usage and a detailed help message.
44
45         @type with_description: bool
46         @param with_description: if true, print module's __doc__ string
47         """
48
49         if with_description:
50                 print(__doc__.strip())
51                 print()
52         print(mod_usage(mod_name=__name__.split('.')[-1]))
53         print()
54         print(pp.command("options"))
55         print(format_options((
56                 (" -h, --help", "display this help message"),
57                 (" -a, --all", "include all package versions")
58         )))
59
60
61 def display_useflags(output):
62         """Print USE flag descriptions and statuses.
63
64         @type output: list
65         @param output: [(inuse, inused, flag, desc, restrict), ...]
66                 inuse (int) = 0 or 1; if 1, flag is set in make.conf
67                 inused (int) = 0 or 1; if 1, package is installed with flag enabled
68                 flag (str) = the name of the USE flag
69                 desc (str) = the flag's description
70                 restrict (str) = corresponds to the text of restrict in metadata
71         """
72
73         maxflag_len = len(max([t[2] for t in output], key=len))
74
75         twrap = TextWrapper()
76         twrap.width = CONFIG['termWidth']
77         twrap.subsequent_indent = " " * (maxflag_len + 8)
78
79         markers = ("-", "+")
80         color = (
81                 partial(pp.useflag, enabled=False), partial(pp.useflag, enabled=True)
82         )
83         for in_makeconf, in_installed, flag, desc, restrict in output:
84                 if CONFIG['verbose']:
85                         flag_name = ""
86                         if in_makeconf != in_installed:
87                                 flag_name += pp.emph(" %s %s" %
88                                         (markers[in_makeconf], markers[in_installed]))
89                         else:
90                                 flag_name += (" %s %s" %
91                                         (markers[in_makeconf], markers[in_installed]))
92
93                         flag_name += " " + color[in_makeconf](flag.ljust(maxflag_len))
94                         flag_name += " : "
95
96                         # print description
97                         if restrict:
98                                 restrict = "(%s %s)" % (pp.emph("Restricted to"),
99                                         pp.cpv(restrict))
100                                 twrap.initial_indent = flag_name
101                                 pp.uprint(twrap.fill(restrict))
102                                 if desc:
103                                         twrap.initial_indent = twrap.subsequent_indent
104                                         pp.uprint(twrap.fill(desc))
105                                 else:
106                                         print(" : <unknown>")
107                         else:
108                                 if desc:
109                                         twrap.initial_indent = flag_name
110                                         desc = twrap.fill(desc)
111                                         pp.uprint(desc)
112                                 else:
113                                         twrap.initial_indent = flag_name
114                                         print(twrap.fill("<unknown>"))
115                 else:
116                         pp.uprint(markers[in_makeconf] + flag)
117
118
119 def get_global_useflags():
120         """Get global and expanded USE flag variables from
121         PORTDIR/profiles/use.desc and PORTDIR/profiles/desc/*.desc respectively.
122
123         @rtype: dict
124         @return: {'flag_name': 'flag description', ...}
125         """
126
127         global_usedesc = {}
128         # Get global USE flag descriptions
129         try:
130                 path = os.path.join(settings["PORTDIR"], 'profiles', 'use.desc')
131                 with open(path) as open_file:
132                         for line in open_file:
133                                 if line.startswith('#'):
134                                         continue
135                                 # Ex. of fields: ['syslog', 'Enables support for syslog\n']
136                                 fields = line.split(" - ", 1)
137                                 if len(fields) == 2:
138                                         global_usedesc[fields[0]] = fields[1].rstrip()
139         except IOError:
140                 sys.stderr.write(
141                         pp.warn(
142                                 "Could not load USE flag descriptions from %s" % pp.path(path)
143                         )
144                 )
145
146         del path, open_file
147         # Add USE_EXPANDED variables to usedesc hash -- Bug #238005
148         for path in glob(os.path.join(settings["PORTDIR"],
149                 'profiles', 'desc', '*.desc')):
150                 try:
151                         with open(path) as open_file:
152                                 for line in open_file:
153                                         if line.startswith('#'):
154                                                 continue
155                                         fields = [field.strip() for field in line.split(" - ", 1)]
156                                         if len(fields) == 2:
157                                                 expanded_useflag = "%s_%s" % \
158                                                         (path.split("/")[-1][0:-5], fields[0])
159                                                 global_usedesc[expanded_useflag] = fields[1]
160                 except IOError:
161                         sys.stderr.write(
162                                 pp.warn("Could not load USE flag descriptions from %s" % path)
163                         )
164
165         return global_usedesc
166
167
168 def get_output_descriptions(pkg, global_usedesc):
169         """Prepare descriptions and usage information for each USE flag."""
170
171         if pkg.metadata is None:
172                 local_usedesc = []
173         else:
174                 local_usedesc = pkg.metadata.use()
175         iuse = pkg.environment("IUSE")
176
177         if iuse:
178                 usevar = uniqify([x.lstrip('+-') for x in iuse.split()])
179                 usevar.sort()
180         else:
181                 usevar = []
182
183         if pkg.is_installed():
184                 used_flags = pkg.use().split()
185         else:
186                 used_flags = settings["USE"].split()
187
188         # store (inuse, inused, flag, desc, restrict)
189         output = []
190         for flag in usevar:
191                 inuse = False
192                 inused = False
193
194                 local_use = None
195                 for use in local_usedesc:
196                         if use.name == flag:
197                                 local_use = use
198                                 break
199
200                 try:
201                         desc = local_use.description
202                 except AttributeError:
203                         try:
204                                 desc = global_usedesc[flag]
205                         except KeyError:
206                                 desc = ""
207
208                 try:
209                         restrict = local_use.restrict
210                         restrict = restrict if restrict is not None else ""
211                 except AttributeError:
212                         restrict = ""
213
214                 if flag in pkg.settings("USE").split():
215                         inuse = True
216                 if flag in used_flags:
217                         inused = True
218
219                 output.append((inuse, inused, flag, desc, restrict))
220
221         return output
222
223
224 def parse_module_options(module_opts):
225         """Parse module options and update QUERY_OPTS"""
226
227         opts = (x[0] for x in module_opts)
228         for opt in opts:
229                 if opt in ('-h', '--help'):
230                         print_help()
231                         sys.exit(0)
232                 elif opt in ('-a', '--all'):
233                         QUERY_OPTS['all_versions'] = True
234
235
236 def print_legend():
237         """Print a legend to explain the output format."""
238
239         print("[ Legend : %s - flag is set in make.conf       ]" % pp.emph("U"))
240         print("[        : %s - package is installed with flag ]" % pp.emph("I"))
241         print("[ Colors : %s, %s                         ]" % (
242                 pp.useflag("set", enabled=True), pp.useflag("unset", enabled=False)))
243
244
245 def main(input_args):
246         """Parse input and run the program"""
247
248         short_opts = "ha"
249         long_opts = ('help', 'all')
250
251         try:
252                 module_opts, queries = gnu_getopt(input_args, short_opts, long_opts)
253         except GetoptError as err:
254                 sys.stderr.write(pp.error("Module %s" % err))
255                 print()
256                 print_help(with_description=False)
257                 sys.exit(2)
258
259         parse_module_options(module_opts)
260
261         if not queries:
262                 print_help()
263                 sys.exit(2)
264
265         #
266         # Output
267         #
268
269         first_run = True
270         legend_printed = False
271         for query in (Query(x) for x in queries):
272                 if not first_run:
273                         print()
274
275                 if QUERY_OPTS["all_versions"]:
276                         matches = query.find(include_masked=True)
277                 else:
278                         matches = [query.find_best()]
279
280                 if not any(matches):
281                         raise errors.GentoolkitNoMatches(query)
282
283                 matches.sort()
284
285                 global_usedesc = get_global_useflags()
286                 for pkg in matches:
287
288                         output = get_output_descriptions(pkg, global_usedesc)
289                         if output:
290                                 if CONFIG['verbose']:
291                                         if not legend_printed:
292                                                 print_legend()
293                                                 legend_printed = True
294                                         print((" * Found these USE flags for %s:" %
295                                                 pp.cpv(str(pkg.cpv))))
296                                         print(pp.emph(" U I"))
297                                 display_useflags(output)
298                         else:
299                                 if CONFIG['verbose']:
300                                         sys.stderr.write(
301                                                 pp.warn("No USE flags found for %s" % pp.cpv(pkg.cpv))
302                                         )
303
304                 first_run = False
305
306 # vim: set ts=4 sw=4 tw=79: