Merge genscripts rev 406
[gentoolkit.git] / pym / gentoolkit / equery / depgraph.py
1 # Copyright(c) 2009, Gentoo Foundation
2 #
3 # Licensed under the GNU General Public License, v2
4 #
5 # $Header: $
6
7 """Display a direct dependency graph for a given package"""
8
9 from __future__ import print_function
10
11 __docformat__ = 'epytext'
12
13 # =======
14 # Imports
15 # =======
16
17 import sys
18 from functools import partial
19 from getopt import gnu_getopt, GetoptError
20
21 import portage
22
23 import gentoolkit.pprinter as pp
24 from gentoolkit import errors
25 from gentoolkit.equery import format_options, mod_usage, CONFIG
26 from gentoolkit.keyword import determine_keyword
27 from gentoolkit.query import Query
28
29 # =======
30 # Globals
31 # =======
32
33 QUERY_OPTS = {
34         "depth": 1,
35         "no_atom": False,
36         "no_indent": False,
37         "no_useflags": False,
38         "no_mask": False,
39         "in_installed": True,
40         "in_porttree": True,
41         "in_overlay": True,
42         "include_masked": True,
43         "show_progress": (not CONFIG['quiet'])
44 }
45
46 # =========
47 # Functions
48 # =========
49
50 def print_help(with_description=True):
51         """Print description, usage and a detailed help message.
52
53         @type with_description: bool
54         @param with_description: if true, print module's __doc__ string
55         """
56
57         if with_description:
58                 print(__doc__.strip())
59                 print()
60         print("Default depth is set to 1 (direct only). Use --depth=0 for no max.")
61         print()
62         print(mod_usage(mod_name="depgraph"))
63         print()
64         print(pp.command("options"))
65         print(format_options((
66                 (" -h, --help", "display this help message"),
67                 (" -A, --no-atom", "do not show dependency atom"),
68                 (" -M, --no-mask", "do not show masking status"),
69                 (" -U, --no-useflags", "do not show USE flags"),
70                 (" -l, --linear", "do not format the graph by indenting dependencies"),
71                 ("     --depth=N", "limit dependency graph to specified depth")
72         )))
73
74
75 def parse_module_options(module_opts):
76         """Parse module options and update QUERY_OPTS"""
77
78         opts = (x[0] for x in module_opts)
79         posargs = (x[1] for x in module_opts)
80         for opt, posarg in zip(opts, posargs):
81                 if opt in ('-h', '--help'):
82                         print_help()
83                         sys.exit(0)
84                 if opt in ('-A', '--no-atom'):
85                         QUERY_OPTS["no_atom"] = True
86                 if opt in ('-U', '--no-useflags'):
87                         QUERY_OPTS["no_useflags"] = True
88                 if opt in ('-M', '--no-mask'):
89                         QUERY_OPTS["no_mask"] = True
90                 if opt in ('-l', '--linear'):
91                         QUERY_OPTS["no_indent"] = True
92                 if opt in ('--depth'):
93                         if posarg.isdigit():
94                                 depth = int(posarg)
95                         else:
96                                 err = "Module option --depth requires integer (got '%s')"
97                                 sys.stderr.write(pp.error(err % posarg))
98                                 print()
99                                 print_help(with_description=False)
100                                 sys.exit(2)
101                         QUERY_OPTS["depth"] = depth
102
103
104 def depgraph_printer(
105         depth,
106         pkg,
107         dep,
108         no_use=False,
109         no_atom=False,
110         no_indent=False,
111         initial_pkg=False,
112         no_mask=False
113 ):
114         """Display L{gentoolkit.dependencies.Dependencies.graph_depends} results.
115
116         @type depth: int
117         @param depth: depth of indirection, used to calculate indent
118         @type pkg: L{gentoolkit.package.Package}
119         @param pkg: "best match" package matched by B{dep}
120         @type dep: L{gentoolkit.atom.Atom}
121         @param dep: dependency that matched B{pkg}
122         @type no_use: bool
123         @param no_use: don't output USE flags
124         @type no_atom: bool
125         @param no_atom: don't output dep atom
126         @type no_indent: bool
127         @param no_indent: don't output indent based on B{depth}
128         @type initial_pkg: bool
129         @param initial_pkg: somewhat of a hack used to print the root package of
130                 the graph with absolutely no indent
131         """
132         indent = '' if no_indent or initial_pkg else ' ' + (' ' * depth)
133         decorator = '[%3d] ' % depth if no_indent else '`-- '
134         use = ''
135         atom = ''
136         mask = ''
137         try:
138                 if not no_atom:
139                         if dep.operator == '=*':
140                                 atom += ' (=%s*)' % dep.cpv
141                         else:
142                                 atom += ' (%s%s)' % (dep.operator, dep.cpv)
143                 if not no_use and dep is not None and dep.use:
144                         use = ' [%s]' % ' '.join(
145                                 pp.useflag(x, enabled=True) for x in dep.use.tokens
146                         )
147         except AttributeError:
148                 # 'NoneType' object has no attribute 'atom'
149                 pass
150         if pkg and not no_mask:
151                 mask = pkg.mask_status()
152                 if not mask:
153                         mask = [determine_keyword(portage.settings["ARCH"],
154                                 portage.settings["ACCEPT_KEYWORDS"],
155                                 pkg.environment('KEYWORDS'))]
156                 mask = pp.masking(mask)
157         try:
158                 pp.uprint(' '.join(
159                         (indent, decorator, pp.cpv(str(pkg.cpv)), atom, mask, use)
160                         ))
161         except AttributeError:
162                 # 'NoneType' object has no attribute 'cpv'
163                 pp.uprint(''.join((indent, decorator, "(no match for %r)" % dep.atom)))
164
165
166 def make_depgraph(pkg, printer_fn):
167         """Create and display depgraph for each package."""
168
169         print()
170         if CONFIG['verbose']:
171                 pp.uprint(" * " + pp.subsection("dependency graph for ") +
172                         pp.cpv(str(pkg.cpv)))
173         else:
174                 pp.uprint("%s:" % pkg.cpv)
175
176         # Print out the first package
177         printer_fn(0, pkg, None, initial_pkg=True)
178
179         deps = pkg.deps.graph_depends(
180                 max_depth=QUERY_OPTS['depth'],
181                 printer_fn=printer_fn,
182                 # Use this to set this pkg as the graph's root; better way?
183                 result=[(0, pkg)]
184         )
185
186         if CONFIG['verbose']:
187                 pkgname = pp.cpv(str(pkg.cpv))
188                 n_packages = pp.number(str(len(deps)))
189                 max_seen = pp.number(str(max(x[0] for x in deps)))
190                 info = "[ %s stats: packages (%s), max depth (%s) ]"
191                 pp.uprint(info % (pkgname, n_packages, max_seen))
192
193
194 def main(input_args):
195         """Parse input and run the program"""
196
197         short_opts = "hAMUl"
198         long_opts = ('help', 'no-atom', 'no-useflags', 'no-mask', 'depth=')
199
200         try:
201                 module_opts, queries = gnu_getopt(input_args, short_opts, long_opts)
202         except GetoptError as err:
203                 sys.stderr.write(pp.error("Module %s" % err))
204                 print()
205                 print_help(with_description=False)
206                 sys.exit(2)
207
208         parse_module_options(module_opts)
209
210         if not queries:
211                 print_help()
212                 sys.exit(2)
213
214         #
215         # Output
216         #
217
218         first_run = True
219         for query in (Query(x) for x in queries):
220                 if not first_run:
221                         print()
222
223                 matches = query.smart_find(**QUERY_OPTS)
224
225                 if not matches:
226                         raise errors.GentoolkitNoMatches(query)
227
228                 matches.sort()
229
230                 if CONFIG['verbose']:
231                         printer = partial(
232                                 depgraph_printer,
233                                 no_atom=QUERY_OPTS['no_atom'],
234                                 no_indent=QUERY_OPTS['no_indent'],
235                                 no_use=QUERY_OPTS['no_useflags'],
236                                 no_mask=QUERY_OPTS['no_mask']
237                         )
238                 else:
239                         printer = partial(
240                                 depgraph_printer,
241                                 no_atom=True,
242                                 no_indent=True,
243                                 no_use=True,
244                                 no_mask=True
245                         )
246
247                 for pkg in matches:
248                         make_depgraph(pkg, printer)
249
250                 first_run = False
251
252 # vim: set ts=4 sw=4 tw=79: