1 # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
2 # Chris Ball <cjb@laptop.org>
3 # W. Trevor King <wking@drexel.edu>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 """Add a comment to a bug"""
19 from libbe import cmdutil, bugdir, comment, editor
22 try: # import core module, Python >= 2.5
23 from xml.etree import ElementTree
24 except ImportError: # look for non-core module
25 from elementtree import ElementTree
28 def execute(args, manipulate_encodings=True):
31 >>> bd = bugdir.simple_bug_dir()
33 >>> execute(["a", "This is a comment about a"], manipulate_encodings=False)
35 >>> bug = bd.bug_from_shortname("a")
36 >>> bug.load_comments(load_full=False)
37 >>> comment = bug.comment_root[0]
38 >>> print comment.body
39 This is a comment about a
41 >>> comment.From == bd.user_id
43 >>> comment.time <= int(time.time())
45 >>> comment.in_reply_to is None
48 >>> if 'EDITOR' in os.environ:
49 ... del os.environ["EDITOR"]
50 >>> execute(["b"], manipulate_encodings=False)
51 Traceback (most recent call last):
52 UserError: No comment supplied, and EDITOR not specified.
54 >>> os.environ["EDITOR"] = "echo 'I like cheese' > "
55 >>> execute(["b"], manipulate_encodings=False)
57 >>> bug = bd.bug_from_shortname("b")
58 >>> bug.load_comments(load_full=False)
59 >>> comment = bug.comment_root[0]
60 >>> print comment.body
65 options, args = parser.parse_args(args)
66 complete(options, args, parser)
68 raise cmdutil.UsageError("Please specify a bug or comment id.")
70 raise cmdutil.UsageError("Too many arguments.")
73 if shortname.count(':') > 1:
74 raise cmdutil.UserError("Invalid id '%s'." % shortname)
75 elif shortname.count(':') == 1:
76 # Split shortname generated by Comment.comment_shortnames()
77 bugname = shortname.split(':')[0]
83 bd = bugdir.BugDir(from_disk=True,
84 manipulate_encodings=manipulate_encodings)
85 bug = bd.bug_from_shortname(bugname)
86 bug.load_comments(load_full=False)
88 parent = bug.comment_root.comment_from_shortname(shortname,
89 bug_shortname=bugname)
91 parent = bug.comment_root
93 if len(args) == 1: # try to launch an editor for comment-body entry
95 if parent == bug.comment_root:
96 parent_body = bug.summary+"\n"
98 parent_body = parent.body
99 estr = "Please enter your comment above\n\n> %s\n" \
100 % ("\n> ".join(parent_body.splitlines()))
101 body = editor.editor_string(estr)
102 except editor.CantFindEditor, e:
103 raise cmdutil.UserError, "No comment supplied, and EDITOR not specified."
105 raise cmdutil.UserError("No comment entered.")
106 body = body.decode('utf-8')
107 elif args[1] == '-': # read body from stdin
108 binary = not (options.content_type == None
109 or options.content_type.startswith("text/"))
111 body = sys.stdin.read()
112 if not body.endswith('\n'):
114 else: # read-in without decoding
115 body = sys.__stdin__.read()
116 else: # body = arg[1]
118 if not body.endswith('\n'):
121 if options.XML == False:
122 new = parent.new_reply(body=body)
123 if options.author != None:
124 new.From = options.author
125 if options.alt_id != None:
126 new.alt_id = options.alt_id
127 if options.content_type != None:
128 new.content_type = options.content_type
129 else: # import XML comment [list]
130 # read in the comments
131 str_body = body.encode("unicode_escape").replace(r'\n', '\n')
132 comment_list = ElementTree.XML(str_body)
133 if comment_list.tag not in ["bug", "comment-list"]:
134 raise comment.InvalidXML(
135 comment_list, "root element must be <bug> or <comment-list>")
138 for c in bug.comment_root.traverse():
142 for child in comment_list.getchildren():
143 if child.tag == "comment":
144 new = comment.Comment(bug)
145 new.from_xml(unicode(ElementTree.tostring(child)).decode("unicode_escape"))
146 if new.alt_id in ids:
147 raise cmdutil.UserError(
148 "Clashing comment alt_id: %s" % new.alt_id)
150 if new.alt_id != None:
151 ids.append(new.alt_id)
152 if new.in_reply_to == None:
153 new.in_reply_to = parent.uuid
154 new_comments.append(new)
156 print >> sys.stderr, "Ignoring unknown tag %s in %s" \
157 % (child.tag, comment_list.tag)
159 comment.list_to_root(new_comments,bug,root=parent, # link new comments
160 ignore_missing_references=options.ignore_missing_references)
161 except comment.MissingReference, e:
162 raise cmdutil.UserError(e)
163 # Protect against programmer error causing data loss:
164 kids = [c.uuid for c in parent.traverse()]
165 for nc in new_comments:
166 assert nc.uuid in kids, "%s wasn't added to %s" % (nc.uuid, parent.uuid)
170 parser = cmdutil.CmdOptionParser("be comment ID [COMMENT]")
171 parser.add_option("-a", "--author", metavar="AUTHOR", dest="author",
172 help="Set the comment author", default=None)
173 parser.add_option("--alt-id", metavar="ID", dest="alt_id",
174 help="Set an alternate comment ID", default=None)
175 parser.add_option("-c", "--content-type", metavar="MIME", dest="content_type",
176 help="Set comment content-type (e.g. text/plain)", default=None)
177 parser.add_option("-x", "--xml", action="store_true", default=False,
178 dest='XML', help="Use COMMENT to specify an XML comment description rather than the comment body. The root XML element should be either <bug> or <comment-list> with one or more <comment> children. The syntax for the <comment> elements should match that generated by 'be show --xml COMMENT-ID'. Unrecognized tags are ignored. Missing tags are left at the default value. The comment UUIDs are always auto-generated, so if you set a <uuid> field, but no <alt-id> field, your <uuid> will be used as the comment's <alt-id>. An exception is raised if <alt-id> conflicts with an existing comment.")
179 parser.add_option("-i", "--ignore-missing-references", action="store_true",
180 dest="ignore_missing_references",
181 help="For XML import, if any comment's <in-reply-to> refers to a non-existent comment, ignore it (instead of raising an exception).")
185 To add a comment to a bug, use the bug ID as the argument. To reply
186 to another comment, specify the comment name (as shown in "be show"
187 output). COMMENT, if specified, should be either the text of your
188 comment or "-", in which case the text will be read from stdin. If
189 you do not specify a COMMENT, $EDITOR is used to launch an editor. If
190 COMMENT is unspecified and EDITOR is not set, no comment will be
195 return get_parser().help_str() + longhelp
197 def complete(options, args, parser):
198 for option,value in cmdutil.option_value_pairs(options, parser):
199 if value == "--complete":
200 # no argument-options at the moment, so this is future-proofing
201 raise cmdutil.GetCompletions()
202 for pos,value in enumerate(args):
203 if value == "--complete":
204 if pos == 0: # fist positional argument is a bug or comment id
206 partial = args[1].split(':')[0] # take only bugid portion
211 bd = bugdir.BugDir(from_disk=True,
212 manipulate_encodings=False)
214 for uuid in bd.list_uuids():
215 if uuid.startswith(partial):
216 bug = bd.bug_from_uuid(uuid)
217 if bug.active == True:
220 shortname = bd.bug_shortname(bug)
221 ids.append(shortname)
222 bug.load_comments(load_full=False)
223 for id,comment in bug.comment_shortnames(shortname):
225 except bugdir.NoBugDir:
227 raise cmdutil.GetCompletions(ids)
228 raise cmdutil.GetCompletions()