setup.py: make libbe._version optional.
[be.git] / libbe / command / target.py
1 # Copyright (C) 2005-2012 Aaron Bentley <abentley@panoramicfeedback.com>
2 #                         Chris Ball <cjb@laptop.org>
3 #                         Gianluca Montecchi <gian@grys.it>
4 #                         Marien Zwart <marien.zwart@gmail.com>
5 #                         Thomas Gerigk <tgerigk@gmx.de>
6 #                         W. Trevor King <wking@tremily.us>
7 #
8 # This file is part of Bugs Everywhere.
9 #
10 # Bugs Everywhere is free software: you can redistribute it and/or modify it
11 # under the terms of the GNU General Public License as published by the Free
12 # Software Foundation, either version 2 of the License, or (at your option) any
13 # later version.
14 #
15 # Bugs Everywhere is distributed in the hope that it will be useful, but
16 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 # FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
18 # more details.
19 #
20 # You should have received a copy of the GNU General Public License along with
21 # Bugs Everywhere.  If not, see <http://www.gnu.org/licenses/>.
22
23 import libbe
24 import libbe.command
25 import libbe.command.util
26 import libbe.command.depend
27 import libbe.util.id
28
29
30 class Target (libbe.command.Command):
31     """Assorted bug target manipulations and queries
32
33     >>> import os, StringIO, sys
34     >>> import libbe.bugdir
35     >>> bd = libbe.bugdir.SimpleBugDir(memory=False)
36     >>> io = libbe.command.StringInputOutput()
37     >>> io.stdout = sys.stdout
38     >>> ui = libbe.command.UserInterface(io=io)
39     >>> ui.storage_callbacks.set_storage(bd.storage)
40     >>> cmd = Target(ui=ui)
41
42     >>> ret = ui.run(cmd, args=['/a'])
43     No target assigned.
44     >>> ret = ui.run(cmd, args=['/a', 'tomorrow'])
45     >>> ret = ui.run(cmd, args=['/a'])
46     tomorrow
47
48     >>> ui.io.stdout = StringIO.StringIO()
49     >>> ret = ui.run(cmd, {'resolve':True}, ['tomorrow'])
50     >>> output = ui.io.get_stdout().strip()
51     >>> bd.flush_reload()
52     >>> target = bd.bug_from_uuid(output)
53     >>> print target.summary
54     tomorrow
55     >>> print target.severity
56     target
57
58     >>> ui.io.stdout = sys.stdout
59     >>> ret = ui.run(cmd, args=['/a', 'none'])
60     >>> ret = ui.run(cmd, args=['/a'])
61     No target assigned.
62     >>> ui.cleanup()
63     >>> bd.cleanup()
64     """
65     name = 'target'
66
67     def __init__(self, *args, **kwargs):
68         libbe.command.Command.__init__(self, *args, **kwargs)
69         self.options.extend([
70                 libbe.command.Option(name='resolve', short_name='r',
71                     help="Print the UUID for the target bug whose summary "
72                     "matches TARGET.  If TARGET is not given, print the UUID "
73                     "of the current bugdir target."),
74                 libbe.command.Option(name='bugdir', short_name='b',
75                     help='Short bugdir UUID for the target resolution.  You '
76                     'only need to set this if you have multiple bugdirs in '
77                     'your repository.',
78                     arg=libbe.command.Argument(
79                         name='bugdir', metavar='ID', default=None,
80                         completion_callback=libbe.command.util.complete_bugdir_id)),
81                 ])
82         self.args.extend([
83                 libbe.command.Argument(
84                     name='id', metavar='BUG-ID', optional=True,
85                     completion_callback=libbe.command.util.complete_bug_id),
86                 libbe.command.Argument(
87                     name='target', metavar='TARGET', optional=True,
88                     completion_callback=complete_target),
89                 ])
90
91     def _run(self, **params):
92         if params['resolve'] == False:
93             if params['id'] == None:
94                 raise libbe.command.UserError('Please specify a bug id.')
95         else:
96             if params['target'] != None:
97                 raise libbe.command.UserError('Too many arguments')
98             params['target'] = params.pop('id')
99         bugdirs = self._get_bugdirs()
100         if params['resolve'] == True:
101             if params['bugdir']:
102                 bugdir = bugdirs[bugdir]
103             elif len(bugdirs) == 1:
104                 bugdir = bugdirs.values()[0]
105             else:
106                 raise libbe.command.UserError(
107                     'Ambiguous bugdir {}'.format(sorted(bugdirs.values())))
108             bug = bug_from_target_summary(bugdirs, bugdir, params['target'])
109             if bug == None:
110                 print >> self.stdout, 'No target assigned.'
111             else:
112                 print >> self.stdout, bug.uuid
113             return 0
114         bugdir,bug,comment = (
115             libbe.command.util.bugdir_bug_comment_from_user_id(
116                 bugdirs, params['id']))
117         if params['target'] == None:
118             target = bug_target(bugdirs, bug)
119             if target == None:
120                 print >> self.stdout, 'No target assigned.'
121             else:
122                 print >> self.stdout, target.summary
123         else:
124             if params['target'] == 'none':
125                 target = remove_target(bugdirs, bug)
126             else:
127                 target = add_target(bugdirs, bugdir, bug, params['target'])
128         return 0
129
130     def usage(self):
131         return 'usage: be %(name)s BUG-ID [TARGET]\nor:    be %(name)s --resolve [TARGET]' \
132             % vars(self.__class__)
133
134     def _long_help(self):
135         return """
136 Assorted bug target manipulations and queries.
137
138 If no target is specified, the bug's current target is printed.  If
139 TARGET is specified, it will be assigned to the bug, creating a new
140 target bug if necessary.
141
142 Targets are free-form; any text may be specified.  They will generally
143 be milestone names or release numbers.  The value "none" can be used
144 to unset the target.
145
146 In the alternative `be target --resolve TARGET` form, print the UUID
147 of the target-bug with summary TARGET.  If target is not given, return
148 use the bugdir's current target (see `be set`).
149
150 If you want to list all bugs blocking the current target, try
151   $ be depend --status -closed,fixed,wontfix --severity -target \
152     $(be target --resolve)
153
154 If you want to set the current bugdir target by summary (rather than
155 by UUID), try
156   $ be set target $(be target --resolve SUMMARY)
157 """
158
159 def bug_from_target_summary(bugdirs, bugdir, summary=None):
160     if summary == None:
161         if bugdir.target == None:
162             return None
163         else:
164             return bugdir.bug_from_uuid(bugdir.target)
165     matched = []
166     for uuid in bugdir.uuids():
167         bug = bugdir.bug_from_uuid(uuid)
168         if bug.severity == 'target' and bug.summary == summary:
169             matched.append(bug)
170     if len(matched) == 0:
171         return None
172     if len(matched) > 1:
173         raise Exception('Several targets with same summary:  %s'
174                         % '\n  '.join([bug.uuid for bug in matched]))
175     return matched[0]
176
177 def bug_target(bugdirs, bug):
178     if bug.severity == 'target':
179         return bug
180     matched = []
181     for blocked in libbe.command.depend.get_blocks(bugdirs, bug):
182         if blocked.severity == 'target':
183             matched.append(blocked)
184     if len(matched) == 0:
185         return None
186     if len(matched) > 1:
187         raise Exception('This bug (%s) blocks several targets:  %s'
188                         % (bug.uuid,
189                            '\n  '.join([b.uuid for b in matched])))
190     return matched[0]
191
192 def remove_target(bugdirs, bug):
193     target = bug_target(bugdirs, bug)
194     libbe.command.depend.remove_block(target, bug)
195     return target
196
197 def add_target(bugdirs, bugdir, bug, summary):
198     target = bug_from_target_summary(bugdirs, bugdir, summary)
199     if target == None:
200         target = bugdir.new_bug(summary=summary)
201         target.severity = 'target'
202     libbe.command.depend.add_block(target, bug)
203     return target
204
205 def targets(bugdirs):
206     """Generate all possible target bug summaries."""
207     for bugdir in bugdirs.values():
208         bugdir.load_all_bugs()
209         for bug in bugdir:
210             if bug.severity == 'target':
211                 yield bug.summary
212
213 def target_dict(bugdirs):
214     """
215     Return a dict with bug UUID keys and bug summary values for all
216     target bugs.
217     """
218     ret = {}
219     for bug in targets(bugdirs):
220         ret[bug.uuid] = bug
221     return ret
222
223 def complete_target(command, argument, fragment=None):
224     """List possible command completions for fragment."""
225     return targets(command._get_bugdirs())