setup.py: make libbe._version optional.
[be.git] / libbe / command / show.py
1 # Copyright (C) 2005-2012 Aaron Bentley <abentley@panoramicfeedback.com>
2 #                         Chris Ball <cjb@laptop.org>
3 #                         Gianluca Montecchi <gian@grys.it>
4 #                         Thomas Gerigk <tgerigk@gmx.de>
5 #                         Thomas Habets <thomas@habets.pp.se>
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 sys
24
25 import libbe
26 import libbe.command
27 import libbe.command.util
28 import libbe.util.id
29 import libbe.version
30
31
32 class Show (libbe.command.Command):
33     """Show a particular bug, comment, or combination of both.
34
35     >>> import sys
36     >>> import libbe.bugdir
37     >>> bd = libbe.bugdir.SimpleBugDir(memory=False)
38     >>> io = libbe.command.StringInputOutput()
39     >>> io.stdout = sys.stdout
40     >>> io.stdout.encoding = 'ascii'
41     >>> ui = libbe.command.UserInterface(io=io)
42     >>> ui.storage_callbacks.set_storage(bd.storage)
43     >>> cmd = Show(ui=ui)
44
45     >>> ret = ui.run(cmd, args=['/a',])  # doctest: +ELLIPSIS
46               ID : a
47       Short name : abc/a
48         Severity : minor
49           Status : open
50         Assigned : 
51         Reporter : 
52          Creator : John Doe <jdoe@example.com>
53          Created : ...
54     Bug A
55     <BLANKLINE>
56
57     >>> ret = ui.run(cmd, {'xml':True}, ['/a'])  # doctest: +ELLIPSIS
58     <?xml version="1.0" encoding="..." ?>
59     <be-xml>
60       <version>
61         <tag>...</tag>
62         <committer>...</committer>
63         <date>...</date>
64         <revision>...</revision>
65       </version>
66       <bug>
67         <uuid>a</uuid>
68         <short-name>abc/a</short-name>
69         <severity>minor</severity>
70         <status>open</status>
71         <creator>John Doe &lt;jdoe@example.com&gt;</creator>
72         <created>Thu, 01 Jan 1970 00:00:00 +0000</created>
73         <summary>Bug A</summary>
74       </bug>
75     </be-xml>
76     >>> ui.cleanup()
77     >>> bd.cleanup()
78     """
79     name = 'show'
80
81     def __init__(self, *args, **kwargs):
82         libbe.command.Command.__init__(self, *args, **kwargs)
83         self.options.extend([
84                 libbe.command.Option(name='xml', short_name='x',
85                                      help='Dump as XML'),
86                 libbe.command.Option(name='only-raw-body',
87                     help="When printing only a single comment, just print it's"
88                   " body.  This allows extraction of non-text content types."),
89                 libbe.command.Option(name='no-comments', short_name='c',
90                     help="Disable comment output.  This is useful if you just "
91                          "want more details on a bug's current status."),
92                 ])
93         self.args.extend([
94                 libbe.command.Argument(
95                     name='id', metavar='ID', default=None,
96                     optional=True, repeatable=True,
97                     completion_callback=libbe.command.util.complete_bug_comment_id),
98                 ])
99
100     def _run(self, **params):
101         bugdirs = self._get_bugdirs()
102         if params['only-raw-body'] == True:
103             if len(params['id']) != 1:
104                 raise libbe.command.UserError(
105                     'only one ID accepted with --only-raw-body')
106             bugdir,bug,comment = (
107                 libbe.command.util.bugdir_bug_comment_from_user_id(
108                     bugdirs, params['id'][0]))
109             if comment == bug.comment_root:
110                 raise libbe.command.UserError(
111                     "--only-raw-body requires a comment ID, not '%s'"
112                     % params['id'][0])
113             sys.__stdout__.write(comment.body)
114             return 0
115         print >> self.stdout, \
116             output(bugdirs, params['id'], encoding=self.stdout.encoding,
117                    as_xml=params['xml'],
118                    with_comments=not params['no-comments'])
119         return 0
120
121     def _long_help(self):
122         return """
123 Show all information about the bugs or comments whose IDs are given.
124 If no IDs are given, show the entire repository.
125
126 Without the --xml flag set, it's probably not a good idea to mix bug
127 and comment IDs in a single call, but you're free to do so if you
128 like.  With the --xml flag set, there will never be any root comments,
129 so mix and match away (the bug listings for directly requested
130 comments will be restricted to the bug uuid and the requested
131 comment(s)).
132
133 Directly requested comments will be grouped by their parent bug and
134 placed at the end of the output, so the ordering may not match the
135 order of the listed IDs.
136 """
137
138 def _sort_ids(bugdirs, ids, with_comments=True):
139     bugs = []
140     root_comments = {}
141     for id in ids:
142         p = libbe.util.id.parse_user(bugdirs, id)
143         if p['type'] == 'bug':
144             bugs.append(p['bug'])
145         elif with_comments == True:
146             if p['bug'] not in root_comments:
147                 root_comments[p['bug']] = [p['comment']]
148             else:
149                 root_comments[p['bug']].append(p['comment'])
150     for bugname in root_comments.keys():
151         assert bugname not in bugs, \
152             'specifically requested both #/%s/%s# and #/%s#' \
153             % (bugname, root_comments[bugname][0], bugname)
154     return (bugs, root_comments)
155
156 def _xml_header(encoding):
157     lines = ['<?xml version="1.0" encoding="%s" ?>' % encoding,
158              '<be-xml>',
159              '  <version>',
160              '    <tag>%s</tag>' % libbe.version.version()]
161     for tag,value in sorted(libbe.version.version_info.items()):
162         lines.append('    <%s>%s</%s>' % (tag, value, tag))
163     lines.append('  </version>')
164     return lines
165
166 def _xml_footer():
167     return ['</be-xml>']
168
169 def output(bugdirs, ids, encoding, as_xml=True, with_comments=True):
170     if ids == None or len(ids) == 0:
171         ids = []
172         for bugdir in bugdirs.values():
173             bugdir.load_all_bugs()
174             ids.extend([bug.id.user() for bug in bugdir])
175     uuids,root_comments = _sort_ids(bugdirs, ids, with_comments)
176     lines = []
177     if as_xml:
178         lines.extend(_xml_header(encoding))
179     else:
180         spaces_left = len(ids) - 1
181     for bugname in uuids:
182         bug = libbe.command.util.bug_from_uuid(bugdirs, bugname)
183         if as_xml:
184             lines.append(bug.xml(indent=2, show_comments=with_comments))
185         else:
186             lines.append(bug.string(show_comments=with_comments))
187             if spaces_left > 0:
188                 spaces_left -= 1
189                 lines.append('') # add a blank line between bugs/comments
190     for bugname,comments in root_comments.items():
191         bug = libbe.command.util.bug_from_uuid(bugdirs, bugname)
192         if as_xml:
193             lines.extend(['  <bug>', '    <uuid>%s</uuid>' % bug.uuid])
194         for commname in comments:
195             try:
196                 comment = bug.comment_root.comment_from_uuid(commname)
197             except KeyError, e:
198                 raise libbe.command.UserError(e.message)
199             if as_xml:
200                 lines.append(comment.xml(indent=4))
201             else:
202                 lines.append(comment.string())
203                 if spaces_left > 0:
204                     spaces_left -= 1
205                     lines.append('') # add a blank line between bugs/comments
206         if as_xml:
207             lines.append('</bug>')
208     if as_xml:
209         lines.extend(_xml_footer())
210     return '\n'.join(lines)