Merge genscripts rev 406
[gentoolkit.git] / pym / gentoolkit / equery / list_.py
1 # Copyright(c) 2009, Gentoo Foundation
2 #
3 # Licensed under the GNU General Public License, v2 or higher
4 #
5 # $Header: $
6
7 """List installed packages matching the query pattern"""
8
9 from __future__ import print_function
10
11 __docformat__ = 'epytext'
12
13 # =======
14 # Imports
15 # =======
16
17 import sys
18 from getopt import gnu_getopt, GetoptError
19
20 import gentoolkit
21 import gentoolkit.pprinter as pp
22 from gentoolkit.equery import format_options, mod_usage, CONFIG
23 from gentoolkit.helpers import get_installed_cpvs
24 from gentoolkit.package import PackageFormatter, FORMAT_TMPL_VARS
25 from gentoolkit.query import Query
26
27 # =======
28 # Globals
29 # =======
30
31 QUERY_OPTS = {
32         "duplicates": False,
33         "in_installed": True,
34         "in_porttree": False,
35         "in_overlay": False,
36         "include_mask_reason": False,
37         "is_regex": False,
38         "show_progress": (not CONFIG['quiet']),
39         "package_format": None
40 }
41
42 # =========
43 # Functions
44 # =========
45
46 def print_help(with_description=True):
47         """Print description, usage and a detailed help message.
48
49         @type with_description: bool
50         @param with_description: if true, print module's __doc__ string
51         """
52
53         if with_description:
54                 print(__doc__.strip())
55                 print()
56
57         # Deprecation warning added by djanderson, 12/2008
58         depwarning = (
59                 "Default action for this module has changed in Gentoolkit 0.3.",
60                 "Use globbing to simulate the old behavior (see man equery).",
61                 "Use '*' to check all installed packages.",
62                 "Use 'foo-bar/*' to filter by category."
63         )
64         for line in depwarning:
65                 sys.stderr.write(pp.warn(line))
66         print()
67
68         print(mod_usage(mod_name="list"))
69         print()
70         print(pp.command("options"))
71         print(format_options((
72                 (" -h, --help", "display this help message"),
73                 (" -d, --duplicates", "list only installed duplicate packages"),
74                 (" -f, --full-regex", "query is a regular expression"),
75                 (" -m, --mask-reason", "include reason for package mask"),
76                 (" -I, --exclude-installed",
77                         "exclude installed packages from output"),
78                 (" -o, --overlay-tree", "list packages in overlays"),
79                 (" -p, --portage-tree", "list packages in the main portage tree"),
80                 (" -F, --format=TMPL", "specify a custom output format"),
81         ("              TMPL",
82                         "a format template using (see man page):")
83         )))
84         print(" " * 24, ', '.join(pp.emph(x) for x in FORMAT_TMPL_VARS))                        
85
86
87 def get_duplicates(matches):
88         """Return only packages that have more than one version installed."""
89
90         dups = {}
91         result = []
92         for pkg in matches:
93                 if pkg.cp in dups:
94                         dups[pkg.cp].append(pkg)
95                 else:
96                         dups[pkg.cp] = [pkg]
97
98         for cpv in dups.values():
99                 if len(cpv) > 1:
100                         result.extend(cpv)
101
102         return result
103
104
105 def parse_module_options(module_opts):
106         """Parse module options and update QUERY_OPTS"""
107
108         opts = (x[0] for x in module_opts)
109         posargs = (x[1] for x in module_opts)
110         for opt, posarg in zip(opts, posargs):
111                 if opt in ('-h', '--help'):
112                         print_help()
113                         sys.exit(0)
114                 elif opt in ('-I', '--exclude-installed'):
115                         QUERY_OPTS['in_installed'] = False
116                 elif opt in ('-p', '--portage-tree'):
117                         QUERY_OPTS['in_porttree'] = True
118                 elif opt in ('-o', '--overlay-tree'):
119                         QUERY_OPTS['in_overlay'] = True
120                 elif opt in ('-f', '--full-regex'):
121                         QUERY_OPTS['is_regex'] = True
122                 elif opt in ('-m', '--mask-reason'):
123                         QUERY_OPTS['include_mask_reason'] = True
124                 elif opt in ('-e', '--exact-name'):
125                         sys.stderr.write(pp.warn("-e, --exact-name is now default."))
126                         sys.stderr.write(
127                                 pp.warn("Use globbing to simulate the old behavior.")
128                         )
129                         print()
130                 elif opt in ('-d', '--duplicates'):
131                         QUERY_OPTS['duplicates'] = True
132                 elif opt in ('-F', '--format'):
133                         QUERY_OPTS["package_format"] = posarg
134
135
136 def main(input_args):
137         """Parse input and run the program"""
138
139         short_opts = "hdefiImopF:" # -i, -e were options for default actions
140
141         # 04/09: djanderson
142         # --all is no longer needed. Kept for compatibility.
143         # --installed is no longer needed. Kept for compatibility.
144         # --exact-name is no longer needed. Kept for compatibility.
145         long_opts = ('help', 'all', 'installed', 'exclude-installed',
146                 'mask-reason', 'portage-tree', 'overlay-tree', 'format=', 'full-regex',
147                 'exact-name', 'duplicates')
148
149         try:
150                 module_opts, queries = gnu_getopt(input_args, short_opts, long_opts)
151         except GetoptError as err:
152                 sys.stderr.write(pp.error("Module %s" % err))
153                 print()
154                 print_help(with_description=False)
155                 sys.exit(2)
156
157         parse_module_options(module_opts)
158
159         # Only search installed packages when listing duplicate packages
160         if QUERY_OPTS["duplicates"]:
161                 QUERY_OPTS["in_installed"] = True
162                 QUERY_OPTS["in_porttree"] = False
163                 QUERY_OPTS["in_overlay"] = False
164                 QUERY_OPTS["include_mask_reason"] = False
165
166         if not queries:
167                 print_help()
168                 sys.exit(2)
169
170         first_run = True
171         for query in (Query(x, QUERY_OPTS['is_regex']) for x in queries):
172                 if not first_run:
173                         print()
174
175                 matches = query.smart_find(**QUERY_OPTS)
176
177                 # Find duplicate packages
178                 if QUERY_OPTS["duplicates"]:
179                         matches = get_duplicates(matches)
180
181                 matches.sort()
182
183                 #
184                 # Output
185                 #
186
187                 for pkg in matches:
188                         pkgstr = PackageFormatter(
189                                 pkg,
190                                 do_format=CONFIG['verbose'],
191                                 custom_format=QUERY_OPTS["package_format"]
192                         )
193
194                         if (QUERY_OPTS["in_porttree"] and
195                                 not QUERY_OPTS["in_overlay"]):
196                                 if not 'P' in pkgstr.location:
197                                         continue
198                         if (QUERY_OPTS["in_overlay"] and
199                                 not QUERY_OPTS["in_porttree"]):
200                                 if not 'O' in pkgstr.location:
201                                         continue
202                         pp.uprint(pkgstr)
203
204                         if QUERY_OPTS["include_mask_reason"]:
205                                 ms_int, ms_orig = pkgstr.format_mask_status()
206                                 if ms_int < 3:
207                                         # ms_int is a number representation of mask level.
208                                         # Only 2 and above are "hard masked" and have reasons.
209                                         continue
210                                 mask_reason = pkg.mask_reason()
211                                 if not mask_reason:
212                                         # Package not on system or not masked
213                                         continue
214                                 elif not any(mask_reason):
215                                         print(" * No mask reason given")
216                                 else:
217                                         status = ', '.join(ms_orig)
218                                         explanation = mask_reason[0]
219                                         mask_location = mask_reason[1]
220                                         pp.uprint(" * Masked by %r" % status)
221                                         pp.uprint(" * %s:" % mask_location)
222                                         pp.uprint('\n'.join(
223                                                 [' * %s' % line.lstrip(' #')
224                                                         for line in explanation.splitlines()]
225                                                 ))
226
227                 first_run = False
228
229 # vim: set ts=4 sw=4 tw=79: