aboutsummaryrefslogtreecommitdiffstats
path: root/interfaces/email/interactive/becommands
diff options
context:
space:
mode:
Diffstat (limited to 'interfaces/email/interactive/becommands')
-rw-r--r--interfaces/email/interactive/becommands/assign.py87
-rw-r--r--interfaces/email/interactive/becommands/close.py60
-rw-r--r--interfaces/email/interactive/becommands/comment.py228
-rw-r--r--interfaces/email/interactive/becommands/commit.py78
-rw-r--r--interfaces/email/interactive/becommands/depend.py339
-rw-r--r--interfaces/email/interactive/becommands/diff.py120
-rw-r--r--interfaces/email/interactive/becommands/help.py68
-rw-r--r--interfaces/email/interactive/becommands/html.py588
-rw-r--r--interfaces/email/interactive/becommands/init.py100
-rw-r--r--interfaces/email/interactive/becommands/list.py248
-rw-r--r--interfaces/email/interactive/becommands/merge.py165
-rw-r--r--interfaces/email/interactive/becommands/new.py80
-rw-r--r--interfaces/email/interactive/becommands/open.py58
-rw-r--r--interfaces/email/interactive/becommands/remove.py62
-rw-r--r--interfaces/email/interactive/becommands/set.py130
-rw-r--r--interfaces/email/interactive/becommands/severity.py103
-rw-r--r--interfaces/email/interactive/becommands/show.py116
-rw-r--r--interfaces/email/interactive/becommands/status.py115
-rw-r--r--interfaces/email/interactive/becommands/subscribe.py390
-rw-r--r--interfaces/email/interactive/becommands/tag.py134
-rw-r--r--interfaces/email/interactive/becommands/target.py95
21 files changed, 3364 insertions, 0 deletions
diff --git a/interfaces/email/interactive/becommands/assign.py b/interfaces/email/interactive/becommands/assign.py
new file mode 100644
index 0000000..794f028
--- /dev/null
+++ b/interfaces/email/interactive/becommands/assign.py
@@ -0,0 +1,87 @@
+# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
+# Marien Zwart <marienz@gentoo.org>
+# Thomas Gerigk <tgerigk@gmx.de>
+# W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Assign an individual or group to fix a bug"""
+from libbe import cmdutil, bugdir
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> import os
+ >>> bd = bugdir.SimpleBugDir()
+ >>> os.chdir(bd.root)
+ >>> bd.bug_from_shortname("a").assigned is None
+ True
+
+ >>> execute(["a"], manipulate_encodings=False)
+ >>> bd._clear_bugs()
+ >>> bd.bug_from_shortname("a").assigned == bd.user_id
+ True
+
+ >>> execute(["a", "someone"], manipulate_encodings=False)
+ >>> bd._clear_bugs()
+ >>> print bd.bug_from_shortname("a").assigned
+ someone
+
+ >>> execute(["a","none"], manipulate_encodings=False)
+ >>> bd._clear_bugs()
+ >>> bd.bug_from_shortname("a").assigned is None
+ True
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ cmdutil.default_complete(options, args, parser,
+ bugid_args={0: lambda bug : bug.active==True})
+ assert(len(args) in (0, 1, 2))
+ if len(args) == 0:
+ raise cmdutil.UsageError("Please specify a bug id.")
+ if len(args) > 2:
+ help()
+ raise cmdutil.UsageError("Too many arguments.")
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bug = cmdutil.bug_from_shortname(bd, args[0])
+ bug = bd.bug_from_shortname(args[0])
+ if len(args) == 1:
+ bug.assigned = bd.user_id
+ elif len(args) == 2:
+ if args[1] == "none":
+ bug.assigned = None
+ else:
+ bug.assigned = args[1]
+ bd.save()
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be assign BUG-ID [ASSIGNEE]")
+ return parser
+
+longhelp = """
+Assign a person to fix a bug.
+
+By default, the bug is self-assigned. If an assignee is specified, the bug
+will be assigned to that person.
+
+Assignees should be the person's Bugs Everywhere identity, the string that
+appears in Creator fields.
+
+To un-assign a bug, specify "none" for the assignee.
+"""
+
+def help():
+ return get_parser().help_str() + longhelp
diff --git a/interfaces/email/interactive/becommands/close.py b/interfaces/email/interactive/becommands/close.py
new file mode 100644
index 0000000..0532ed2
--- /dev/null
+++ b/interfaces/email/interactive/becommands/close.py
@@ -0,0 +1,60 @@
+# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
+# Marien Zwart <marienz@gentoo.org>
+# Thomas Gerigk <tgerigk@gmx.de>
+# W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Close a bug"""
+from libbe import cmdutil, bugdir
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> from libbe import bugdir
+ >>> import os
+ >>> bd = bugdir.SimpleBugDir()
+ >>> os.chdir(bd.root)
+ >>> print bd.bug_from_shortname("a").status
+ open
+ >>> execute(["a"], manipulate_encodings=False)
+ >>> bd._clear_bugs()
+ >>> print bd.bug_from_shortname("a").status
+ closed
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ cmdutil.default_complete(options, args, parser,
+ bugid_args={0: lambda bug : bug.active==True})
+ if len(args) == 0:
+ raise cmdutil.UsageError("Please specify a bug id.")
+ if len(args) > 1:
+ raise cmdutil.UsageError("Too many arguments.")
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bug = cmdutil.bug_from_shortname(bd, args[0])
+ bug.status = "closed"
+ bd.save()
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be close BUG-ID")
+ return parser
+
+longhelp="""
+Close the bug identified by BUG-ID.
+"""
+
+def help():
+ return get_parser().help_str() + longhelp
diff --git a/interfaces/email/interactive/becommands/comment.py b/interfaces/email/interactive/becommands/comment.py
new file mode 100644
index 0000000..9a614b2
--- /dev/null
+++ b/interfaces/email/interactive/becommands/comment.py
@@ -0,0 +1,228 @@
+# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
+# Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Add a comment to a bug"""
+from libbe import cmdutil, bugdir, comment, editor
+import os
+import sys
+try: # import core module, Python >= 2.5
+ from xml.etree import ElementTree
+except ImportError: # look for non-core module
+ from elementtree import ElementTree
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> import time
+ >>> bd = bugdir.SimpleBugDir()
+ >>> os.chdir(bd.root)
+ >>> execute(["a", "This is a comment about a"], manipulate_encodings=False)
+ >>> bd._clear_bugs()
+ >>> bug = cmdutil.bug_from_shortname(bd, "a")
+ >>> bug.load_comments(load_full=False)
+ >>> comment = bug.comment_root[0]
+ >>> print comment.body
+ This is a comment about a
+ <BLANKLINE>
+ >>> comment.author == bd.user_id
+ True
+ >>> comment.time <= int(time.time())
+ True
+ >>> comment.in_reply_to is None
+ True
+
+ >>> if 'EDITOR' in os.environ:
+ ... del os.environ["EDITOR"]
+ >>> execute(["b"], manipulate_encodings=False)
+ Traceback (most recent call last):
+ UserError: No comment supplied, and EDITOR not specified.
+
+ >>> os.environ["EDITOR"] = "echo 'I like cheese' > "
+ >>> execute(["b"], manipulate_encodings=False)
+ >>> bd._clear_bugs()
+ >>> bug = cmdutil.bug_from_shortname(bd, "b")
+ >>> bug.load_comments(load_full=False)
+ >>> comment = bug.comment_root[0]
+ >>> print comment.body
+ I like cheese
+ <BLANKLINE>
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ complete(options, args, parser)
+ if len(args) == 0:
+ raise cmdutil.UsageError("Please specify a bug or comment id.")
+ if len(args) > 2:
+ raise cmdutil.UsageError("Too many arguments.")
+
+ shortname = args[0]
+ if shortname.count(':') > 1:
+ raise cmdutil.UserError("Invalid id '%s'." % shortname)
+ elif shortname.count(':') == 1:
+ # Split shortname generated by Comment.comment_shortnames()
+ bugname = shortname.split(':')[0]
+ is_reply = True
+ else:
+ bugname = shortname
+ is_reply = False
+
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bug = cmdutil.bug_from_shortname(bd, bugname)
+ bug.load_comments(load_full=False)
+ if is_reply:
+ parent = bug.comment_root.comment_from_shortname(shortname,
+ bug_shortname=bugname)
+ else:
+ parent = bug.comment_root
+
+ if len(args) == 1: # try to launch an editor for comment-body entry
+ try:
+ if parent == bug.comment_root:
+ parent_body = bug.summary+"\n"
+ else:
+ parent_body = parent.body
+ estr = "Please enter your comment above\n\n> %s\n" \
+ % ("\n> ".join(parent_body.splitlines()))
+ body = editor.editor_string(estr)
+ except editor.CantFindEditor, e:
+ raise cmdutil.UserError, "No comment supplied, and EDITOR not specified."
+ if body is None:
+ raise cmdutil.UserError("No comment entered.")
+ elif args[1] == '-': # read body from stdin
+ binary = not (options.content_type == None
+ or options.content_type.startswith("text/"))
+ if not binary:
+ body = sys.stdin.read()
+ if not body.endswith('\n'):
+ body+='\n'
+ else: # read-in without decoding
+ body = sys.__stdin__.read()
+ else: # body = arg[1]
+ body = args[1]
+ if not body.endswith('\n'):
+ body+='\n'
+
+ if options.XML == False:
+ new = parent.new_reply(body=body)
+ if options.author != None:
+ new.author = options.author
+ if options.alt_id != None:
+ new.alt_id = options.alt_id
+ if options.content_type != None:
+ new.content_type = options.content_type
+ else: # import XML comment [list]
+ # read in the comments
+ str_body = body.encode("unicode_escape").replace(r'\n', '\n')
+ comment_list = ElementTree.XML(str_body)
+ if comment_list.tag not in ["bug", "comment-list"]:
+ raise comment.InvalidXML(
+ comment_list, "root element must be <bug> or <comment-list>")
+ new_comments = []
+ ids = []
+ for c in bug.comment_root.traverse():
+ ids.append(c.uuid)
+ if c.alt_id != None:
+ ids.append(c.alt_id)
+ for child in comment_list.getchildren():
+ if child.tag == "comment":
+ new = comment.Comment(bug)
+ new.from_xml(unicode(ElementTree.tostring(child)).decode("unicode_escape"))
+ if new.alt_id in ids:
+ raise cmdutil.UserError(
+ "Clashing comment alt_id: %s" % new.alt_id)
+ ids.append(new.uuid)
+ if new.alt_id != None:
+ ids.append(new.alt_id)
+ if new.in_reply_to == None:
+ new.in_reply_to = parent.uuid
+ new_comments.append(new)
+ else:
+ print >> sys.stderr, "Ignoring unknown tag %s in %s" \
+ % (child.tag, comment_list.tag)
+ try:
+ comment.list_to_root(new_comments,bug,root=parent, # link new comments
+ ignore_missing_references=options.ignore_missing_references)
+ except comment.MissingReference, e:
+ raise cmdutil.UserError(e)
+ # Protect against programmer error causing data loss:
+ kids = [c.uuid for c in parent.traverse()]
+ for nc in new_comments:
+ assert nc.uuid in kids, "%s wasn't added to %s" % (nc.uuid, parent.uuid)
+ nc.save()
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be comment ID [COMMENT]")
+ parser.add_option("-a", "--author", metavar="AUTHOR", dest="author",
+ help="Set the comment author", default=None)
+ parser.add_option("--alt-id", metavar="ID", dest="alt_id",
+ help="Set an alternate comment ID", default=None)
+ parser.add_option("-c", "--content-type", metavar="MIME", dest="content_type",
+ help="Set comment content-type (e.g. text/plain)", default=None)
+ parser.add_option("-x", "--xml", action="store_true", default=False,
+ 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.")
+ parser.add_option("-i", "--ignore-missing-references", action="store_true",
+ dest="ignore_missing_references",
+ help="For XML import, if any comment's <in-reply-to> refers to a non-existent comment, ignore it (instead of raising an exception).")
+ return parser
+
+longhelp="""
+To add a comment to a bug, use the bug ID as the argument. To reply
+to another comment, specify the comment name (as shown in "be show"
+output). COMMENT, if specified, should be either the text of your
+comment or "-", in which case the text will be read from stdin. If
+you do not specify a COMMENT, $EDITOR is used to launch an editor. If
+COMMENT is unspecified and EDITOR is not set, no comment will be
+created.
+"""
+
+def help():
+ return get_parser().help_str() + longhelp
+
+def complete(options, args, parser):
+ for option,value in cmdutil.option_value_pairs(options, parser):
+ if value == "--complete":
+ # no argument-options at the moment, so this is future-proofing
+ raise cmdutil.GetCompletions()
+ for pos,value in enumerate(args):
+ if value == "--complete":
+ if pos == 0: # fist positional argument is a bug or comment id
+ if len(args) >= 2:
+ partial = args[1].split(':')[0] # take only bugid portion
+ else:
+ partial = ""
+ ids = []
+ try:
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=False)
+ bugs = []
+ for uuid in bd.list_uuids():
+ if uuid.startswith(partial):
+ bug = bd.bug_from_uuid(uuid)
+ if bug.active == True:
+ bugs.append(bug)
+ for bug in bugs:
+ shortname = bd.bug_shortname(bug)
+ ids.append(shortname)
+ bug.load_comments(load_full=False)
+ for id,comment in bug.comment_shortnames(shortname):
+ ids.append(id)
+ except bugdir.NoBugDir:
+ pass
+ raise cmdutil.GetCompletions(ids)
+ raise cmdutil.GetCompletions()
diff --git a/interfaces/email/interactive/becommands/commit.py b/interfaces/email/interactive/becommands/commit.py
new file mode 100644
index 0000000..dc70e7e
--- /dev/null
+++ b/interfaces/email/interactive/becommands/commit.py
@@ -0,0 +1,78 @@
+# Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Commit the currently pending changes to the repository"""
+from libbe import cmdutil, bugdir, editor, vcs
+import sys
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> import os, time
+ >>> from libbe import bug
+ >>> bd = bugdir.SimpleBugDir()
+ >>> os.chdir(bd.root)
+ >>> full_path = "testfile"
+ >>> test_contents = "A test file"
+ >>> bd.vcs.set_file_contents(full_path, test_contents)
+ >>> execute(["Added %s." % (full_path)], manipulate_encodings=False) # doctest: +ELLIPSIS
+ Committed ...
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ cmdutil.default_complete(options, args, parser)
+ if len(args) != 1:
+ raise cmdutil.UsageError("Please supply a commit message")
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ if args[0] == '-': # read summary from stdin
+ assert options.body != "EDITOR", \
+ "Cannot spawn and editor when the summary is using stdin."
+ summary = sys.stdin.readline()
+ else:
+ summary = args[0]
+ if options.body == None:
+ body = None
+ elif options.body == "EDITOR":
+ body = editor.editor_string("Please enter your commit message above")
+ else:
+ body = bd.vcs.get_file_contents(options.body, allow_no_vcs=True)
+ try:
+ revision = bd.vcs.commit(summary, body=body,
+ allow_empty=options.allow_empty)
+ except vcs.EmptyCommit, e:
+ print e
+ return 1
+ else:
+ print "Committed %s" % revision
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be commit COMMENT")
+ parser.add_option("-b", "--body", metavar="FILE", dest="body",
+ help='Provide a detailed body for the commit message. In the special case that FILE == "EDITOR", spawn an editor to enter the body text (in which case you cannot use stdin for the summary)', default=None)
+ parser.add_option("-a", "--allow-empty", dest="allow_empty",
+ help="Allow empty commits",
+ default=False, action="store_true")
+ return parser
+
+longhelp="""
+Commit the current repository status. The summary specified on the
+commandline is a string (only one line) that describes the commit
+briefly or "-", in which case the string will be read from stdin.
+"""
+
+def help():
+ return get_parser().help_str() + longhelp
diff --git a/interfaces/email/interactive/becommands/depend.py b/interfaces/email/interactive/becommands/depend.py
new file mode 100644
index 0000000..f72b8ba
--- /dev/null
+++ b/interfaces/email/interactive/becommands/depend.py
@@ -0,0 +1,339 @@
+# Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Add/remove bug dependencies"""
+from libbe import cmdutil, bugdir, tree
+import os, copy
+__desc__ = __doc__
+
+BLOCKS_TAG="BLOCKS:"
+BLOCKED_BY_TAG="BLOCKED-BY:"
+
+class BrokenLink (Exception):
+ def __init__(self, blocked_bug, blocking_bug, blocks=True):
+ if blocks == True:
+ msg = "Missing link: %s blocks %s" \
+ % (blocking_bug.uuid, blocked_bug.uuid)
+ else:
+ msg = "Missing link: %s blocked by %s" \
+ % (blocked_bug.uuid, blocking_bug.uuid)
+ Exception.__init__(self, msg)
+ self.blocked_bug = blocked_bug
+ self.blocking_bug = blocking_bug
+
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> from libbe import utility
+ >>> bd = bugdir.SimpleBugDir()
+ >>> bd.save()
+ >>> os.chdir(bd.root)
+ >>> execute(["a", "b"], manipulate_encodings=False)
+ a blocked by:
+ b
+ >>> execute(["a"], manipulate_encodings=False)
+ a blocked by:
+ b
+ >>> execute(["--show-status", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ a blocked by:
+ b closed
+ >>> execute(["b", "a"], manipulate_encodings=False)
+ b blocked by:
+ a
+ b blocks:
+ a
+ >>> execute(["--show-status", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ a blocked by:
+ b closed
+ a blocks:
+ b closed
+ >>> execute(["-r", "b", "a"], manipulate_encodings=False)
+ b blocks:
+ a
+ >>> execute(["-r", "a", "b"], manipulate_encodings=False)
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ cmdutil.default_complete(options, args, parser,
+ bugid_args={0: lambda bug : bug.active==True,
+ 1: lambda bug : bug.active==True})
+
+ if options.repair == True:
+ if len(args) > 0:
+ raise cmdutil.UsageError("No arguments with --repair calls.")
+ elif len(args) < 1:
+ raise cmdutil.UsageError("Please a bug id.")
+ elif len(args) > 2:
+ help()
+ raise cmdutil.UsageError("Too many arguments.")
+ elif len(args) == 2 and options.tree_depth != None:
+ raise cmdutil.UsageError("Only one bug id used in tree mode.")
+
+
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ if options.repair == True:
+ good,fixed,broken = check_dependencies(bd, repair_broken_links=True)
+ assert len(broken) == 0, broken
+ if len(fixed) > 0:
+ print "Fixed the following links:"
+ print "\n".join(["%s |-- %s" % (blockee.uuid, blocker.uuid)
+ for blockee,blocker in fixed])
+ return 0
+
+ bugA = cmdutil.bug_from_shortname(bd, args[0])
+
+ if options.tree_depth != None:
+ dtree = DependencyTree(bd, bugA, options.tree_depth)
+ if len(dtree.blocked_by_tree()) > 0:
+ print "%s blocked by:" % bugA.uuid
+ for depth,node in dtree.blocked_by_tree().thread():
+ if depth == 0: continue
+ print "%s%s" % (" "*(depth), node.bug.string(shortlist=True))
+ if len(dtree.blocks_tree()) > 0:
+ print "%s blocks:" % bugA.uuid
+ for depth,node in dtree.blocks_tree().thread():
+ if depth == 0: continue
+ print "%s%s" % (" "*(depth), node.bug.string(shortlist=True))
+ return 0
+
+ if len(args) == 2:
+ bugB = cmdutil.bug_from_shortname(bd, args[1])
+ if options.remove == True:
+ remove_block(bugA, bugB)
+ else: # add the dependency
+ add_block(bugA, bugB)
+
+ blocked_by = get_blocked_by(bd, bugA)
+ if len(blocked_by) > 0:
+ print "%s blocked by:" % bugA.uuid
+ if options.show_status == True:
+ print '\n'.join(["%s\t%s" % (bug.uuid, bug.status)
+ for bug in blocked_by])
+ else:
+ print '\n'.join([bug.uuid for bug in blocked_by])
+ blocks = get_blocks(bd, bugA)
+ if len(blocks) > 0:
+ print "%s blocks:" % bugA.uuid
+ if options.show_status == True:
+ print '\n'.join(["%s\t%s" % (bug.uuid, bug.status)
+ for bug in blocks])
+ else:
+ print '\n'.join([bug.uuid for bug in blocks])
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be depend BUG-ID [BUG-ID]\nor: be depend --repair")
+ parser.add_option("-r", "--remove", action="store_true",
+ dest="remove", default=False,
+ help="Remove dependency (instead of adding it)")
+ parser.add_option("-s", "--show-status", action="store_true",
+ dest="show_status", default=False,
+ help="Show status of blocking bugs")
+ parser.add_option("-t", "--tree-depth", metavar="DEPTH", default=None,
+ type="int", dest="tree_depth",
+ help="Print dependency tree rooted at BUG-ID with DEPTH levels of both blockers and blockees. Set DEPTH <= 0 to disable the depth limit.")
+ parser.add_option("--repair", action="store_true",
+ dest="repair", default=False,
+ help="Check for and repair one-way links")
+ return parser
+
+longhelp="""
+Set a dependency with the second bug (B) blocking the first bug (A).
+If bug B is not specified, just print a list of bugs blocking (A).
+
+To search for bugs blocked by a particular bug, try
+ $ be list --extra-strings BLOCKED-BY:<your-bug-uuid>
+
+In repair mode, add the missing direction to any one-way links.
+
+The "|--" symbol in the repair-mode output is inspired by the
+"negative feedback" arrow common in biochemistry. See, for example
+ http://www.nature.com/nature/journal/v456/n7223/images/nature07513-f5.0.jpg
+"""
+
+def help():
+ return get_parser().help_str() + longhelp
+
+# internal helper functions
+
+def _generate_blocks_string(blocked_bug):
+ return "%s%s" % (BLOCKS_TAG, blocked_bug.uuid)
+
+def _generate_blocked_by_string(blocking_bug):
+ return "%s%s" % (BLOCKED_BY_TAG, blocking_bug.uuid)
+
+def _parse_blocks_string(string):
+ assert string.startswith(BLOCKS_TAG)
+ return string[len(BLOCKS_TAG):]
+
+def _parse_blocked_by_string(string):
+ assert string.startswith(BLOCKED_BY_TAG)
+ return string[len(BLOCKED_BY_TAG):]
+
+def _add_remove_extra_string(bug, string, add):
+ estrs = bug.extra_strings
+ if add == True:
+ estrs.append(string)
+ else: # remove the string
+ estrs.remove(string)
+ bug.extra_strings = estrs # reassign to notice change
+
+def _get_blocks(bug):
+ uuids = []
+ for line in bug.extra_strings:
+ if line.startswith(BLOCKS_TAG):
+ uuids.append(_parse_blocks_string(line))
+ return uuids
+
+def _get_blocked_by(bug):
+ uuids = []
+ for line in bug.extra_strings:
+ if line.startswith(BLOCKED_BY_TAG):
+ uuids.append(_parse_blocked_by_string(line))
+ return uuids
+
+def _repair_one_way_link(blocked_bug, blocking_bug, blocks=None):
+ if blocks == True: # add blocks link
+ blocks_string = _generate_blocks_string(blocked_bug)
+ _add_remove_extra_string(blocking_bug, blocks_string, add=True)
+ else: # add blocked by link
+ blocked_by_string = _generate_blocked_by_string(blocking_bug)
+ _add_remove_extra_string(blocked_bug, blocked_by_string, add=True)
+
+# functions exposed to other modules
+
+def add_block(blocked_bug, blocking_bug):
+ blocked_by_string = _generate_blocked_by_string(blocking_bug)
+ _add_remove_extra_string(blocked_bug, blocked_by_string, add=True)
+ blocks_string = _generate_blocks_string(blocked_bug)
+ _add_remove_extra_string(blocking_bug, blocks_string, add=True)
+
+def remove_block(blocked_bug, blocking_bug):
+ blocked_by_string = _generate_blocked_by_string(blocking_bug)
+ _add_remove_extra_string(blocked_bug, blocked_by_string, add=False)
+ blocks_string = _generate_blocks_string(blocked_bug)
+ _add_remove_extra_string(blocking_bug, blocks_string, add=False)
+
+def get_blocks(bugdir, bug):
+ """
+ Return a list of bugs that the given bug blocks.
+ """
+ blocks = []
+ for uuid in _get_blocks(bug):
+ blocks.append(bugdir.bug_from_uuid(uuid))
+ return blocks
+
+def get_blocked_by(bugdir, bug):
+ """
+ Return a list of bugs blocking the given bug blocks.
+ """
+ blocked_by = []
+ for uuid in _get_blocked_by(bug):
+ blocked_by.append(bugdir.bug_from_uuid(uuid))
+ return blocked_by
+
+def check_dependencies(bugdir, repair_broken_links=False):
+ """
+ Check that links are bi-directional for all bugs in bugdir.
+
+ >>> bd = bugdir.SimpleBugDir(sync_with_disk=False)
+ >>> a = bd.bug_from_uuid("a")
+ >>> b = bd.bug_from_uuid("b")
+ >>> blocked_by_string = _generate_blocked_by_string(b)
+ >>> _add_remove_extra_string(a, blocked_by_string, add=True)
+ >>> good,repaired,broken = check_dependencies(bd, repair_broken_links=False)
+ >>> good
+ []
+ >>> repaired
+ []
+ >>> broken
+ [(Bug(uuid='a'), Bug(uuid='b'))]
+ >>> _get_blocks(b)
+ []
+ >>> good,repaired,broken = check_dependencies(bd, repair_broken_links=True)
+ >>> _get_blocks(b)
+ ['a']
+ >>> good
+ []
+ >>> repaired
+ [(Bug(uuid='a'), Bug(uuid='b'))]
+ >>> broken
+ []
+ """
+ if bugdir.sync_with_disk == True:
+ bugdir.load_all_bugs()
+ good_links = []
+ fixed_links = []
+ broken_links = []
+ for bug in bugdir:
+ for blocker in get_blocked_by(bugdir, bug):
+ blocks = get_blocks(bugdir, blocker)
+ if (bug, blocks) in good_links+fixed_links+broken_links:
+ continue # already checked that link
+ if bug not in blocks:
+ if repair_broken_links == True:
+ _repair_one_way_link(bug, blocker, blocks=True)
+ fixed_links.append((bug, blocker))
+ else:
+ broken_links.append((bug, blocker))
+ else:
+ good_links.append((bug, blocker))
+ for blockee in get_blocks(bugdir, bug):
+ blocked_by = get_blocked_by(bugdir, blockee)
+ if (blockee, bug) in good_links+fixed_links+broken_links:
+ continue # already checked that link
+ if bug not in blocked_by:
+ if repair_broken_links == True:
+ _repair_one_way_link(blockee, bug, blocks=False)
+ fixed_links.append((blockee, bug))
+ else:
+ broken_links.append((blockee, bug))
+ else:
+ good_links.append((blockee, bug))
+ return (good_links, fixed_links, broken_links)
+
+class DependencyTree (object):
+ """
+ Note: should probably be DependencyDiGraph.
+ """
+ def __init__(self, bugdir, root_bug, depth_limit=0):
+ self.bugdir = bugdir
+ self.root_bug = root_bug
+ self.depth_limit = depth_limit
+ def _build_tree(self, child_fn):
+ root = tree.Tree()
+ root.bug = self.root_bug
+ root.depth = 0
+ stack = [root]
+ while len(stack) > 0:
+ node = stack.pop()
+ if self.depth_limit > 0 and node.depth == self.depth_limit:
+ continue
+ for bug in child_fn(self.bugdir, node.bug):
+ child = tree.Tree()
+ child.bug = bug
+ child.depth = node.depth+1
+ node.append(child)
+ stack.append(child)
+ return root
+ def blocks_tree(self):
+ if not hasattr(self, "_blocks_tree"):
+ self._blocks_tree = self._build_tree(get_blocks)
+ return self._blocks_tree
+ def blocked_by_tree(self):
+ if not hasattr(self, "_blocked_by_tree"):
+ self._blocked_by_tree = self._build_tree(get_blocked_by)
+ return self._blocked_by_tree
diff --git a/interfaces/email/interactive/becommands/diff.py b/interfaces/email/interactive/becommands/diff.py
new file mode 100644
index 0000000..b6ac5b0
--- /dev/null
+++ b/interfaces/email/interactive/becommands/diff.py
@@ -0,0 +1,120 @@
+# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
+# W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""Compare bug reports with older tree"""
+from libbe import cmdutil, bugdir, diff
+import os
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> import os
+ >>> bd = bugdir.SimpleBugDir()
+ >>> bd.set_sync_with_disk(True)
+ >>> original = bd.vcs.commit("Original status")
+ >>> bug = bd.bug_from_uuid("a")
+ >>> bug.status = "closed"
+ >>> changed = bd.vcs.commit("Closed bug a")
+ >>> os.chdir(bd.root)
+ >>> if bd.vcs.versioned == True:
+ ... execute([original], manipulate_encodings=False)
+ ... else:
+ ... print "Modified bugs:\\n a:cm: Bug A\\n Changed bug settings:\\n status: open -> closed"
+ Modified bugs:
+ a:cm: Bug A
+ Changed bug settings:
+ status: open -> closed
+ >>> if bd.vcs.versioned == True:
+ ... execute(["--modified", original], manipulate_encodings=False)
+ ... else:
+ ... print "a"
+ a
+ >>> if bd.vcs.versioned == False:
+ ... execute([original], manipulate_encodings=False)
+ ... else:
+ ... print "This directory is not revision-controlled."
+ This directory is not revision-controlled.
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ cmdutil.default_complete(options, args, parser)
+ if len(args) == 0:
+ revision = None
+ if len(args) == 1:
+ revision = args[0]
+ if len(args) > 1:
+ raise cmdutil.UsageError("Too many arguments.")
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ if bd.vcs.versioned == False:
+ print "This directory is not revision-controlled."
+ else:
+ if revision == None: # get the most recent revision
+ revision = bd.vcs.revision_id(-1)
+ old_bd = bd.duplicate_bugdir(revision)
+ d = diff.Diff(old_bd, bd)
+ tree = d.report_tree()
+
+ uuids = []
+ if options.all == True:
+ options.new = options.modified = options.removed = True
+ if options.new == True:
+ uuids.extend([c.name for c in tree.child_by_path("/bugs/new")])
+ if options.modified == True:
+ uuids.extend([c.name for c in tree.child_by_path("/bugs/mod")])
+ if options.removed == True:
+ uuids.extend([c.name for c in tree.child_by_path("/bugs/rem")])
+ if (options.new or options.modified or options.removed) == True:
+ print "\n".join(uuids)
+ else :
+ rep = tree.report_string()
+ if rep != None:
+ print rep
+ bd.remove_duplicate_bugdir()
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be diff [options] REVISION")
+ # boolean options
+ bools = (("n", "new", "Print UUIDS for new bugs"),
+ ("m", "modified", "Print UUIDS for modified bugs"),
+ ("r", "removed", "Print UUIDS for removed bugs"),
+ ("a", "all", "Print UUIDS for all changed bugs"))
+ for s in bools:
+ attr = s[1].replace('-','_')
+ short = "-%c" % s[0]
+ long = "--%s" % s[1]
+ help = s[2]
+ parser.add_option(short, long, action="store_true",
+ default=False, dest=attr, help=help)
+ return parser
+
+longhelp="""
+Uses the VCS to compare the current tree with a previous tree, and
+prints a pretty report. If REVISION is given, it is a specifier for
+the particular previous tree to use. Specifiers are specific to their
+VCS.
+
+For Arch your specifier must be a fully-qualified revision name.
+
+Besides the standard summary output, you can use the options to output
+UUIDS for the different categories. This output can be used as the
+input to 'be show' to get and understanding of the current status.
+"""
+
+def help():
+ return get_parser().help_str() + longhelp
diff --git a/interfaces/email/interactive/becommands/help.py b/interfaces/email/interactive/becommands/help.py
new file mode 100644
index 0000000..a8f346a
--- /dev/null
+++ b/interfaces/email/interactive/becommands/help.py
@@ -0,0 +1,68 @@
+# Copyright (C) 2006-2009 Aaron Bentley and Panometrics, Inc.
+# Thomas Gerigk <tgerigk@gmx.de>
+# W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Print help for given subcommand"""
+from libbe import cmdutil, utility
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=False):
+ """
+ Print help of specified command (the manipulate_encodings argument
+ is ignored).
+
+ >>> execute(["help"])
+ Usage: be help [COMMAND]
+ <BLANKLINE>
+ Options:
+ -h, --help Print a help message
+ --complete Print a list of available completions
+ <BLANKLINE>
+ Print help for specified command or list of all commands.
+ <BLANKLINE>
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ complete(options, args, parser)
+ if len(args) > 1:
+ raise cmdutil.UsageError("Too many arguments.")
+ if len(args) == 0:
+ print cmdutil.help()
+ else:
+ try:
+ print cmdutil.help(args[0])
+ except AttributeError:
+ print "No help available"
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be help [COMMAND]")
+ return parser
+
+longhelp="""
+Print help for specified command or list of all commands.
+"""
+
+def help():
+ return get_parser().help_str() + longhelp
+
+def complete(options, args, parser):
+ for option, value in cmdutil.option_value_pairs(options, parser):
+ if value == "--complete":
+ # no argument-options at the moment, so this is future-proofing
+ raise cmdutil.GetCompletions()
+ if "--complete" in args:
+ cmds = [command for command,module in cmdutil.iter_commands()]
+ raise cmdutil.GetCompletions(cmds)
diff --git a/interfaces/email/interactive/becommands/html.py b/interfaces/email/interactive/becommands/html.py
new file mode 100644
index 0000000..908c714
--- /dev/null
+++ b/interfaces/email/interactive/becommands/html.py
@@ -0,0 +1,588 @@
+# Copyright (C) 2009 Gianluca Montecchi <gian@grys.it>
+# W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Generate a static HTML dump of the current repository status"""
+from libbe import cmdutil, bugdir, bug
+#from html_data import *
+import codecs, os, re, string, time
+import xml.sax.saxutils, htmlentitydefs
+
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> import os
+ >>> bd = bugdir.SimpleBugDir()
+ >>> os.chdir(bd.root)
+ >>> execute([], manipulate_encodings=False)
+ Creating the html output in html_export
+ >>> os.path.exists("./html_export")
+ True
+ >>> os.path.exists("./html_export/index.html")
+ True
+ >>> os.path.exists("./html_export/index_inactive.html")
+ True
+ >>> os.path.exists("./html_export/bugs")
+ True
+ >>> os.path.exists("./html_export/bugs/a.html")
+ True
+ >>> os.path.exists("./html_export/bugs/b.html")
+ True
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ complete(options, args, parser)
+ cmdutil.default_complete(options, args, parser,
+ bugid_args={0: lambda bug : bug.active==False})
+
+ if len(args) == 0:
+ out_dir = options.outdir
+ print "Creating the html output in %s"%out_dir
+ else:
+ out_dir = args[0]
+ if len(args) > 0:
+ raise cmdutil.UsageError, "Too many arguments."
+
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bd.load_all_bugs()
+ status_list = bug.status_values
+ severity_list = bug.severity_values
+ st = {}
+ se = {}
+ stime = {}
+ bugs_active = []
+ bugs_inactive = []
+ for s in status_list:
+ st[s] = 0
+ for b in sorted(bd, reverse=True):
+ stime[b.uuid] = b.time
+ if b.active == True:
+ bugs_active.append(b)
+ else:
+ bugs_inactive.append(b)
+ st[b.status] += 1
+ ordered_bug_list = sorted([(value,key) for (key,value) in stime.items()])
+ ordered_bug_list_in = sorted([(value,key) for (key,value) in stime.items()])
+ #open_bug_list = sorted([(value,key) for (key,value) in bugs.items()])
+
+ html_gen = BEHTMLGen(bd)
+ html_gen.create_index_file(out_dir, st, bugs_active, ordered_bug_list, "active", bd.encoding)
+ html_gen.create_index_file(out_dir, st, bugs_inactive, ordered_bug_list, "inactive", bd.encoding)
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be open OUTPUT_DIR")
+ parser.add_option("-o", "--output", metavar="export_dir", dest="outdir",
+ help="Set the output path, default is ./html_export", default="html_export")
+ return parser
+
+longhelp="""
+Generate a set of html pages representing the current state of the bug
+directory.
+"""
+
+def help():
+ return get_parser().help_str() + longhelp
+
+def complete(options, args, parser):
+ for option, value in cmdutil.option_value_pairs(options, parser):
+ if "--complete" in args:
+ raise cmdutil.GetCompletions() # no positional arguments for list
+
+
+def escape(string):
+ if string == None:
+ return ""
+ chars = []
+ for char in xml.sax.saxutils.escape(string):
+ codepoint = ord(char)
+ if codepoint in htmlentitydefs.codepoint2name:
+ char = "&%s;" % htmlentitydefs.codepoint2name[codepoint]
+ chars.append(char)
+ return "".join(chars)
+
+class BEHTMLGen():
+ def __init__(self, bd):
+ self.index_value = ""
+ self.bd = bd
+
+ self.css_file = """
+ body {
+ font-family: "lucida grande", "sans serif";
+ color: #333;
+ width: auto;
+ margin: auto;
+ }
+
+
+ div.main {
+ padding: 20px;
+ margin: auto;
+ padding-top: 0;
+ margin-top: 1em;
+ background-color: #fcfcfc;
+ }
+
+ .comment {
+ padding: 20px;
+ margin: auto;
+ padding-top: 20px;
+ margin-top: 0;
+ }
+
+ .commentF {
+ padding: 0px;
+ margin: auto;
+ padding-top: 0px;
+ paddin-bottom: 20px;
+ margin-top: 0;
+ }
+
+ tb {
+ border = 1;
+ }
+
+ .wishlist-row {
+ background-color: #B4FF9B;
+ width: auto;
+ }
+
+ .minor-row {
+ background-color: #FCFF98;
+ width: auto;
+ }
+
+
+ .serious-row {
+ background-color: #FFB648;
+ width: auto;
+ }
+
+ .critical-row {
+ background-color: #FF752A;
+ width: auto;
+ }
+
+ .fatal-row {
+ background-color: #FF3300;
+ width: auto;
+ }
+
+ .person {
+ font-family: courier;
+ }
+
+ a, a:visited {
+ background: inherit;
+ text-decoration: none;
+ }
+
+ a {
+ color: #003d41;
+ }
+
+ a:visited {
+ color: #553d41;
+ }
+
+ ul {
+ list-style-type: none;
+ padding: 0;
+ }
+
+ p {
+ width: auto;
+ }
+
+ .inline-status-image {
+ position: relative;
+ top: 0.2em;
+ }
+
+ .dimmed {
+ color: #bbb;
+ }
+
+ table {
+ border-style: 10px solid #313131;
+ border-spacing: 0;
+ width: auto;
+ }
+
+ table.log {
+ }
+
+ td {
+ border-width: 0;
+ border-style: none;
+ padding-right: 0.5em;
+ padding-left: 0.5em;
+ width: auto;
+ }
+
+ .td_sel {
+ background-color: #afafaf;
+ border: 1px solid #afafaf;
+ font-weight:bold;
+ padding-right: 1em;
+ padding-left: 1em;
+
+ }
+
+ .td_nsel {
+ border: 0px;
+ padding-right: 1em;
+ padding-left: 1em;
+ }
+
+ tr {
+ vertical-align: top;
+ width: auto;
+ }
+
+ h1 {
+ padding: 0.5em;
+ background-color: #305275;
+ margin-top: 0;
+ margin-bottom: 0;
+ color: #fff;
+ margin-left: -20px;
+ margin-right: -20px;
+ }
+
+ wid {
+ text-transform: uppercase;
+ font-size: smaller;
+ margin-top: 1em;
+ margin-left: -0.5em;
+ /*background: #fffbce;*/
+ /*background: #628a0d;*/
+ padding: 5px;
+ color: #305275;
+ }
+
+ .attrname {
+ text-align: right;
+ font-size: smaller;
+ }
+
+ .attrval {
+ color: #222;
+ }
+
+ .issue-closed-fixed {
+ background-image: "green-check.png";
+ }
+
+ .issue-closed-wontfix {
+ background-image: "red-check.png";
+ }
+
+ .issue-closed-reorg {
+ background-image: "blue-check.png";
+ }
+
+ .inline-issue-link {
+ text-decoration: underline;
+ }
+
+ img {
+ border: 0;
+ }
+
+
+ div.footer {
+ font-size: small;
+ padding-left: 20px;
+ padding-right: 20px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ margin: auto;
+ background: #305275;
+ color: #fffee7;
+ }
+
+ .footer a {
+ color: #508d91;
+ }
+
+
+ .header {
+ font-family: "lucida grande", "sans serif";
+ font-size: smaller;
+ background-color: #a9a9a9;
+ text-align: left;
+
+ padding-right: 0.5em;
+ padding-left: 0.5em;
+
+ }
+
+
+ .selected-cell {
+ background-color: #e9e9e2;
+ }
+
+ .plain-cell {
+ background-color: #f9f9f9;
+ }
+
+
+ .logcomment {
+ padding-left: 4em;
+ font-size: smaller;
+ }
+
+ .id {
+ font-family: courier;
+ }
+
+ .table_bug {
+ background-color: #afafaf;
+ border: 2px solid #afafaf;
+ }
+
+ .message {
+ }
+
+ .progress-meter-done {
+ background-color: #03af00;
+ }
+
+ .progress-meter-undone {
+ background-color: #ddd;
+ }
+
+ .progress-meter {
+ }
+
+ """
+
+ self.index_first = """
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>BugsEverywhere Issue Tracker</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%s" />
+ <link rel="stylesheet" href="style.css" type="text/css" />
+ </head>
+ <body>
+
+
+ <div class="main">
+ <h1>BugsEverywhere Bug List</h1>
+ <p></p>
+ <table>
+
+ <tr>
+ <td class="%%s"><a href="index.html">Active Bugs</a></td>
+ <td class="%%s"><a href="index_inactive.html">Inactive Bugs</a></td>
+ </tr>
+
+ </table>
+ <table class="table_bug">
+ <tbody>
+ """ % self.bd.encoding
+
+ self.bug_line ="""
+ <tr class="%s-row">
+ <td ><a href="bugs/%s.html">%s</a></td>
+ <td ><a href="bugs/%s.html">%s</a></td>
+ <td><a href="bugs/%s.html">%s</a></td>
+ <td><a href="bugs/%s.html">%s</a></td>
+ <td><a href="bugs/%s.html">%s</a></td>
+ </tr>
+ """
+
+ self.detail_first = """
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>BugsEverywhere Issue Tracker</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%s" />
+ <link rel="stylesheet" href="../style.css" type="text/css" />
+ </head>
+ <body>
+
+
+ <div class="main">
+ <h1>BugsEverywhere Bug List</h1>
+ <h5><a href="%%s">Back to Index</a></h5>
+ <h2>Bug: _bug_id_</h2>
+ <table >
+ <tbody>
+ """ % self.bd.encoding
+
+
+
+ self.detail_line ="""
+ <tr>
+ <td align="right">%s</td><td>%s</td>
+ </tr>
+ """
+
+ self.index_last = """
+ </tbody>
+ </table>
+
+ </div>
+
+ <div class="footer">Generated by <a href="http://www.bugseverywhere.org/">BugsEverywhere</a> on %s</div>
+
+ </body>
+ </html>
+ """
+
+ self.comment_section = """
+ """
+
+ self.begin_comment_section ="""
+ <tr>
+ <td align="right">Comments:
+ </td>
+ <td>
+ """
+
+
+ self.end_comment_section ="""
+ </td>
+ </tr>
+ """
+
+ self.detail_last = """
+ </tbody>
+ </table>
+ </div>
+ <h5><a href="%s">Back to Index</a></h5>
+ <div class="footer">Generated by <a href="http://www.bugseverywhere.org/">BugsEverywhere</a>.</div>
+ </body>
+ </html>
+ """
+
+
+ def create_index_file(self, out_dir_path, summary, bugs, ordered_bug, fileid, encoding):
+ try:
+ os.stat(out_dir_path)
+ except:
+ try:
+ os.mkdir(out_dir_path)
+ except:
+ raise cmdutil.UsageError, "Cannot create output directory."
+ try:
+ FO = codecs.open(out_dir_path+"/style.css", "w", encoding)
+ FO.write(self.css_file)
+ FO.close()
+ except:
+ raise cmdutil.UsageError, "Cannot create the style.css file."
+
+ try:
+ os.mkdir(out_dir_path+"/bugs")
+ except:
+ pass
+
+ try:
+ if fileid == "active":
+ FO = codecs.open(out_dir_path+"/index.html", "w", encoding)
+ FO.write(self.index_first%('td_sel','td_nsel'))
+ if fileid == "inactive":
+ FO = codecs.open(out_dir_path+"/index_inactive.html", "w", encoding)
+ FO.write(self.index_first%('td_nsel','td_sel'))
+ except:
+ raise cmdutil.UsageError, "Cannot create the index.html file."
+
+ c = 0
+ t = len(bugs) - 1
+ for l in range(t, -1, -1):
+ line = self.bug_line%(escape(bugs[l].severity),
+ escape(bugs[l].uuid), escape(bugs[l].uuid[0:3]),
+ escape(bugs[l].uuid), escape(bugs[l].status),
+ escape(bugs[l].uuid), escape(bugs[l].severity),
+ escape(bugs[l].uuid), escape(bugs[l].summary),
+ escape(bugs[l].uuid), escape(bugs[l].time_string)
+ )
+ FO.write(line)
+ c += 1
+ self.create_detail_file(bugs[l], out_dir_path, fileid, encoding)
+ when = time.ctime()
+ FO.write(self.index_last%when)
+
+
+ def create_detail_file(self, bug, out_dir_path, fileid, encoding):
+ f = "%s.html"%bug.uuid
+ p = out_dir_path+"/bugs/"+f
+ try:
+ FD = codecs.open(p, "w", encoding)
+ except:
+ raise cmdutil.UsageError, "Cannot create the detail html file."
+
+ detail_first_ = re.sub('_bug_id_', bug.uuid[0:3], self.detail_first)
+ if fileid == "active":
+ FD.write(detail_first_%"../index.html")
+ if fileid == "inactive":
+ FD.write(detail_first_%"../index_inactive.html")
+
+
+
+ bug_ = self.bd.bug_from_shortname(bug.uuid)
+ bug_.load_comments(load_full=True)
+
+ FD.write(self.detail_line%("ID : ", bug.uuid))
+ FD.write(self.detail_line%("Short name : ", escape(bug.uuid[0:3])))
+ FD.write(self.detail_line%("Severity : ", escape(bug.severity)))
+ FD.write(self.detail_line%("Status : ", escape(bug.status)))
+ FD.write(self.detail_line%("Assigned : ", escape(bug.assigned)))
+ FD.write(self.detail_line%("Target : ", escape(bug.target)))
+ FD.write(self.detail_line%("Reporter : ", escape(bug.reporter)))
+ FD.write(self.detail_line%("Creator : ", escape(bug.creator)))
+ FD.write(self.detail_line%("Created : ", escape(bug.time_string)))
+ FD.write(self.detail_line%("Summary : ", escape(bug.summary)))
+ FD.write("<tr><td colspan=\"2\"><hr /></td></tr>")
+ FD.write(self.begin_comment_section)
+ tr = []
+ b = ''
+ level = 0
+ stack = []
+ for depth,comment in bug_.comment_root.thread(flatten=False):
+ while len(stack) > depth:
+ stack.pop(-1) # pop non-parents off the stack
+ FD.write("</div>\n") # close non-parent <div class="comment...
+ assert len(stack) == depth
+ stack.append(comment)
+ lines = ["--------- Comment ---------",
+ "Name: %s" % comment.uuid,
+ "From: %s" % escape(comment.author),
+ "Date: %s" % escape(comment.date),
+ ""]
+ lines.extend(escape(comment.body).splitlines())
+ if depth == 0:
+ FD.write('<div class="commentF">')
+ else:
+ FD.write('<div class="comment">')
+ FD.write("<br />\n".join(lines)+"<br />\n")
+ while len(stack) > 0:
+ stack.pop(-1)
+ FD.write("</div>\n") # close every remaining <div class="comment...
+ FD.write(self.end_comment_section)
+ if fileid == "active":
+ FD.write(self.detail_last%"../index.html")
+ if fileid == "inactive":
+ FD.write(self.detail_last%"../index_inactive.html")
+ FD.close()
+
+
diff --git a/interfaces/email/interactive/becommands/init.py b/interfaces/email/interactive/becommands/init.py
new file mode 100644
index 0000000..1125d93
--- /dev/null
+++ b/interfaces/email/interactive/becommands/init.py
@@ -0,0 +1,100 @@
+# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
+# W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Assign the root directory for bug tracking"""
+import os.path
+from libbe import cmdutil, bugdir
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> from libbe import utility, vcs
+ >>> import os
+ >>> dir = utility.Dir()
+ >>> try:
+ ... bugdir.BugDir(dir.path)
+ ... except bugdir.NoBugDir, e:
+ ... True
+ True
+ >>> execute(['--root', dir.path], manipulate_encodings=False)
+ No revision control detected.
+ Directory initialized.
+ >>> del(dir)
+
+ >>> dir = utility.Dir()
+ >>> os.chdir(dir.path)
+ >>> vcs = vcs.installed_vcs()
+ >>> vcs.init('.')
+ >>> print vcs.name
+ Arch
+ >>> execute([], manipulate_encodings=False)
+ Using Arch for revision control.
+ Directory initialized.
+ >>> vcs.cleanup()
+
+ >>> try:
+ ... execute(['--root', '.'], manipulate_encodings=False)
+ ... except cmdutil.UserError, e:
+ ... str(e).startswith("Directory already initialized: ")
+ True
+ >>> execute(['--root', '/highly-unlikely-to-exist'], manipulate_encodings=False)
+ Traceback (most recent call last):
+ UserError: No such directory: /highly-unlikely-to-exist
+ >>> os.chdir('/')
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ cmdutil.default_complete(options, args, parser)
+ if len(args) > 0:
+ raise cmdutil.UsageError
+ try:
+ bd = bugdir.BugDir(options.root_dir, from_disk=False,
+ sink_to_existing_root=False,
+ assert_new_BugDir=True,
+ manipulate_encodings=manipulate_encodings)
+ except bugdir.NoRootEntry:
+ raise cmdutil.UserError("No such directory: %s" % options.root_dir)
+ except bugdir.AlreadyInitialized:
+ raise cmdutil.UserError("Directory already initialized: %s" % options.root_dir)
+ bd.save()
+ if bd.vcs.name is not "None":
+ print "Using %s for revision control." % bd.vcs.name
+ else:
+ print "No revision control detected."
+ print "Directory initialized."
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be init")
+ parser.add_option("-r", "--root", metavar="DIR", dest="root_dir",
+ help="Set root dir to something other than the current directory.",
+ default=".")
+ return parser
+
+longhelp="""
+This command initializes Bugs Everywhere support for the specified directory
+and all its subdirectories. It will auto-detect any supported revision control
+system. You can use "be set vcs_name" to change the vcs being used.
+
+The directory defaults to your current working directory.
+
+It is usually a good idea to put the Bugs Everywhere root at the source code
+root, but you can put it anywhere. If you root Bugs Everywhere in a
+subdirectory, then only bugs created in that subdirectory (and its children)
+will appear there.
+"""
+
+def help():
+ return get_parser().help_str() + longhelp
diff --git a/interfaces/email/interactive/becommands/list.py b/interfaces/email/interactive/becommands/list.py
new file mode 100644
index 0000000..12e1e29
--- /dev/null
+++ b/interfaces/email/interactive/becommands/list.py
@@ -0,0 +1,248 @@
+# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
+# Chris Ball <cjb@laptop.org>
+# Oleg Romanyshyn <oromanyshyn@panoramicfeedback.com>
+# W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""List bugs"""
+from libbe import cmdutil, bugdir, bug
+import os
+import re
+__desc__ = __doc__
+
+# get a list of * for cmp_*() comparing two bugs.
+AVAILABLE_CMPS = [fn[4:] for fn in dir(bug) if fn[:4] == 'cmp_']
+AVAILABLE_CMPS.remove("attr") # a cmp_* template.
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> import os
+ >>> bd = bugdir.SimpleBugDir()
+ >>> os.chdir(bd.root)
+ >>> execute([], manipulate_encodings=False)
+ a:om: Bug A
+ >>> execute(["--status", "all"], manipulate_encodings=False)
+ a:om: Bug A
+ b:cm: Bug B
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ complete(options, args, parser)
+ if len(args) > 0:
+ raise cmdutil.UsageError("Too many arguments.")
+ cmp_list = []
+ if options.sort_by != None:
+ for cmp in options.sort_by.split(','):
+ if cmp not in AVAILABLE_CMPS:
+ raise cmdutil.UserError(
+ "Invalid sort on '%s'.\nValid sorts:\n %s"
+ % (cmp, '\n '.join(AVAILABLE_CMPS)))
+ cmp_list.append(eval('bug.cmp_%s' % cmp))
+
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bd.load_all_bugs()
+ # select status
+ if options.status != None:
+ if options.status == "all":
+ status = bug.status_values
+ else:
+ status = options.status.split(',')
+ else:
+ status = []
+ if options.active == True:
+ status.extend(list(bug.active_status_values))
+ if options.unconfirmed == True:
+ status.append("unconfirmed")
+ if options.open == True:
+ status.append("opened")
+ if options.test == True:
+ status.append("test")
+ if status == []: # set the default value
+ status = bug.active_status_values
+ # select severity
+ if options.severity != None:
+ if options.severity == "all":
+ severity = bug.severity_values
+ else:
+ severity = options.severity.split(',')
+ else:
+ severity = []
+ if options.wishlist == True:
+ severity.extend("wishlist")
+ if options.important == True:
+ serious = bug.severity_values.index("serious")
+ severity.append(list(bug.severity_values[serious:]))
+ if severity == []: # set the default value
+ severity = bug.severity_values
+ # select assigned
+ if options.assigned != None:
+ if options.assigned == "all":
+ assigned = "all"
+ else:
+ assigned = options.assigned.split(',')
+ else:
+ assigned = []
+ if options.mine == True:
+ assigned.extend('-')
+ if assigned == []: # set the default value
+ assigned = "all"
+ for i in range(len(assigned)):
+ if assigned[i] == '-':
+ assigned[i] = bd.user_id
+ # select target
+ if options.target != None:
+ if options.target == "all":
+ target = "all"
+ else:
+ target = options.target.split(',')
+ else:
+ target = []
+ if options.cur_target == True:
+ target.append(bd.target)
+ if target == []: # set the default value
+ target = "all"
+ if options.extra_strings != None:
+ extra_string_regexps = [re.compile(x) for x in options.extra_strings.split(',')]
+
+ def filter(bug):
+ if status != "all" and not bug.status in status:
+ return False
+ if severity != "all" and not bug.severity in severity:
+ return False
+ if assigned != "all" and not bug.assigned in assigned:
+ return False
+ if target != "all" and not bug.target in target:
+ return False
+ if options.extra_strings != None:
+ if len(bug.extra_strings) == 0 and len(extra_string_regexps) > 0:
+ return False
+ for string in bug.extra_strings:
+ for regexp in extra_string_regexps:
+ if not regexp.match(string):
+ return False
+ return True
+
+ bugs = [b for b in bd if filter(b) ]
+ if len(bugs) == 0 and options.xml == False:
+ print "No matching bugs found"
+
+ def list_bugs(cur_bugs, title=None, just_uuids=False, xml=False):
+ if xml == True:
+ print '<?xml version="1.0" encoding="%s" ?>' % bd.encoding
+ print "<bugs>"
+ if len(cur_bugs) > 0:
+ if title != None and xml == False:
+ print cmdutil.underlined(title)
+ for bg in cur_bugs:
+ if xml == True:
+ print bg.xml(show_comments=True)
+ elif just_uuids:
+ print bg.uuid
+ else:
+ print bg.string(shortlist=True)
+ if xml == True:
+ print "</bugs>"
+
+ # sort bugs
+ cmp_list.extend(bug.DEFAULT_CMP_FULL_CMP_LIST)
+ cmp_fn = bug.BugCompoundComparator(cmp_list=cmp_list)
+ bugs.sort(cmp_fn)
+
+ # print list of bugs
+ list_bugs(bugs, just_uuids=options.uuids, xml=options.xml)
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be list [options]")
+ parser.add_option("-s", "--status", metavar="STATUS", dest="status",
+ help="List bugs matching STATUS", default=None)
+ parser.add_option("-v", "--severity", metavar="SEVERITY", dest="severity",
+ help="List bugs matching SEVERITY", default=None)
+ parser.add_option("-a", "--assigned", metavar="ASSIGNED", dest="assigned",
+ help="List bugs matching ASSIGNED", default=None)
+ parser.add_option("-t", "--target", metavar="TARGET", dest="target",
+ help="List bugs matching TARGET", default=None)
+ parser.add_option("-e", "--extra-strings", metavar="STRINGS", dest="extra_strings",
+ help="List bugs matching _all_ extra strings in comma-seperated list STRINGS. e.g. --extra-strings TAG:working,TAG:xml", default=None)
+ parser.add_option("-S", "--sort", metavar="SORT-BY", dest="sort_by",
+ help="Adjust bug-sort criteria with comma-separated list SORT-BY. e.g. \"--sort creator,time\". Available criteria: %s" % ','.join(AVAILABLE_CMPS), default=None)
+ # boolean options. All but uuids and xml are special cases of long forms
+ bools = (("u", "uuids", "Only print the bug UUIDS"),
+ ("w", "wishlist", "List bugs with 'wishlist' severity"),
+ ("i", "important", "List bugs with >= 'serious' severity"),
+ ("A", "active", "List all active bugs"),
+ ("U", "unconfirmed", "List unconfirmed bugs"),
+ ("o", "open", "List open bugs"),
+ ("T", "test", "List bugs in testing"),
+ ("m", "mine", "List bugs assigned to you"),
+ ("c", "cur-target", "List bugs for the current target"),
+ ("x", "xml", "Dump as XML"))
+ for s in bools:
+ attr = s[1].replace('-','_')
+ short = "-%c" % s[0]
+ long = "--%s" % s[1]
+ help = s[2]
+ parser.add_option(short, long, action="store_true",
+ dest=attr, help=help)
+ return parser
+
+
+def help():
+ longhelp="""
+This command lists bugs. Normally it prints a short string like
+ 576:om: Allow attachments
+Where
+ 576 the bug id
+ o the bug status is 'open' (first letter)
+ m the bug severity is 'minor' (first letter)
+ Allo... the bug summary string
+
+You can optionally (-u) print only the bug ids.
+
+There are several criteria that you can filter by:
+ * status
+ * severity
+ * assigned (who the bug is assigned to)
+ * target (bugfix deadline)
+Allowed values for each criterion may be given in a comma seperated
+list. The special string "all" may be used with any of these options
+to match all values of the criterion.
+
+status
+ %s
+severity
+ %s
+assigned
+ free form, with the string '-' being a shortcut for yourself.
+target
+ free form
+
+In addition, there are some shortcut options that set boolean flags.
+The boolean options are ignored if the matching string option is used.
+""" % (','.join(bug.status_values),
+ ','.join(bug.severity_values))
+ return get_parser().help_str() + longhelp
+
+def complete(options, args, parser):
+ for option, value in cmdutil.option_value_pairs(options, parser):
+ if value == "--complete":
+ if option == "status":
+ raise cmdutil.GetCompletions(bug.status_values)
+ elif option == "severity":
+ raise cmdutil.GetCompletions(bug.severity_values)
+ raise cmdutil.GetCompletions()
+ if "--complete" in args:
+ raise cmdutil.GetCompletions() # no positional arguments for list
diff --git a/interfaces/email/interactive/becommands/merge.py b/interfaces/email/interactive/becommands/merge.py
new file mode 100644
index 0000000..f212b01
--- /dev/null
+++ b/interfaces/email/interactive/becommands/merge.py
@@ -0,0 +1,165 @@
+# Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Merge duplicate bugs"""
+from libbe import cmdutil, bugdir
+import os, copy
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> from libbe import utility
+ >>> bd = bugdir.SimpleBugDir()
+ >>> bd.set_sync_with_disk(True)
+ >>> a = bd.bug_from_shortname("a")
+ >>> a.comment_root.time = 0
+ >>> dummy = a.new_comment("Testing")
+ >>> dummy.time = 1
+ >>> dummy = dummy.new_reply("Testing...")
+ >>> dummy.time = 2
+ >>> b = bd.bug_from_shortname("b")
+ >>> b.status = "open"
+ >>> b.comment_root.time = 0
+ >>> dummy = b.new_comment("1 2")
+ >>> dummy.time = 1
+ >>> dummy = dummy.new_reply("1 2 3 4")
+ >>> dummy.time = 2
+ >>> os.chdir(bd.root)
+ >>> execute(["a", "b"], manipulate_encodings=False)
+ Merging bugs a and b
+ >>> bd._clear_bugs()
+ >>> a = bd.bug_from_shortname("a")
+ >>> a.load_comments()
+ >>> mergeA = a.comment_from_shortname(":3")
+ >>> mergeA.time = 3
+ >>> print a.string(show_comments=True) # doctest: +ELLIPSIS
+ ID : a
+ Short name : a
+ Severity : minor
+ Status : open
+ Assigned :
+ Target :
+ Reporter :
+ Creator : John Doe <jdoe@example.com>
+ Created : ...
+ Bug A
+ --------- Comment ---------
+ Name: a:1
+ From: ...
+ Date: ...
+ <BLANKLINE>
+ Testing
+ --------- Comment ---------
+ Name: a:2
+ From: ...
+ Date: ...
+ <BLANKLINE>
+ Testing...
+ --------- Comment ---------
+ Name: a:3
+ From: ...
+ Date: ...
+ <BLANKLINE>
+ Merged from bug b
+ --------- Comment ---------
+ Name: a:4
+ From: ...
+ Date: ...
+ <BLANKLINE>
+ 1 2
+ --------- Comment ---------
+ Name: a:5
+ From: ...
+ Date: ...
+ <BLANKLINE>
+ 1 2 3 4
+ >>> b = bd.bug_from_shortname("b")
+ >>> b.load_comments()
+ >>> mergeB = b.comment_from_shortname(":3")
+ >>> mergeB.time = 3
+ >>> print b.string(show_comments=True) # doctest: +ELLIPSIS
+ ID : b
+ Short name : b
+ Severity : minor
+ Status : closed
+ Assigned :
+ Target :
+ Reporter :
+ Creator : Jane Doe <jdoe@example.com>
+ Created : ...
+ Bug B
+ --------- Comment ---------
+ Name: b:1
+ From: ...
+ Date: ...
+ <BLANKLINE>
+ 1 2
+ --------- Comment ---------
+ Name: b:2
+ From: ...
+ Date: ...
+ <BLANKLINE>
+ 1 2 3 4
+ --------- Comment ---------
+ Name: b:3
+ From: ...
+ Date: ...
+ <BLANKLINE>
+ Merged into bug a
+ >>> print b.status
+ closed
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ cmdutil.default_complete(options, args, parser,
+ bugid_args={0: lambda bug : bug.active==True,
+ 1: lambda bug : bug.active==True})
+
+ if len(args) < 2:
+ raise cmdutil.UsageError("Please specify two bug ids.")
+ if len(args) > 2:
+ help()
+ raise cmdutil.UsageError("Too many arguments.")
+
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bugA = cmdutil.bug_from_shortname(bd, args[0])
+ bugA.load_comments()
+ bugB = cmdutil.bug_from_shortname(bd, args[1])
+ bugB.load_comments()
+ mergeA = bugA.new_comment("Merged from bug %s" % bugB.uuid)
+ newCommTree = copy.deepcopy(bugB.comment_root)
+ for comment in newCommTree.traverse(): # all descendant comments
+ comment.bug = bugA
+ comment.save() # force onto disk under bugA
+ for comment in newCommTree: # just the child comments
+ mergeA.add_reply(comment, allow_time_inversion=True)
+ bugB.new_comment("Merged into bug %s" % bugA.uuid)
+ bugB.status = "closed"
+ print "Merging bugs %s and %s" % (bugA.uuid, bugB.uuid)
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be merge BUG-ID BUG-ID")
+ return parser
+
+longhelp="""
+The second bug (B) is merged into the first (A). This adds merge
+comments to both bugs, closes B, and appends B's comment tree to A's
+merge comment.
+"""
+
+def help():
+ return get_parser().help_str() + longhelp
diff --git a/interfaces/email/interactive/becommands/new.py b/interfaces/email/interactive/becommands/new.py
new file mode 100644
index 0000000..a8ee2ec
--- /dev/null
+++ b/interfaces/email/interactive/becommands/new.py
@@ -0,0 +1,80 @@
+# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
+# W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Create a new bug"""
+from libbe import cmdutil, bugdir
+import sys
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> import os, time
+ >>> from libbe import bug
+ >>> bd = bugdir.SimpleBugDir()
+ >>> os.chdir(bd.root)
+ >>> bug.uuid_gen = lambda: "X"
+ >>> execute (["this is a test",], manipulate_encodings=False)
+ Created bug with ID X
+ >>> bd._clear_bugs()
+ >>> bug = bd.bug_from_uuid("X")
+ >>> print bug.summary
+ this is a test
+ >>> bug.time <= int(time.time())
+ True
+ >>> print bug.severity
+ minor
+ >>> bug.target == None
+ True
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ cmdutil.default_complete(options, args, parser)
+ if len(args) != 1:
+ raise cmdutil.UsageError("Please supply a summary message")
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ if args[0] == '-': # read summary from stdin
+ summary = sys.stdin.readline()
+ else:
+ summary = args[0]
+ bug = bd.new_bug(summary=summary.strip())
+ if options.reporter != None:
+ bug.reporter = options.reporter
+ else:
+ bug.reporter = bug.creator
+ if options.assigned != None:
+ bug.assigned = options.assigned
+ elif bd.default_assignee != None:
+ bug.assigned = bd.default_assignee
+ print "Created bug with ID %s" % bd.bug_shortname(bug)
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be new SUMMARY")
+ parser.add_option("-r", "--reporter", metavar="REPORTER", dest="reporter",
+ help="The user who reported the bug", default=None)
+ parser.add_option("-a", "--assigned", metavar="ASSIGNED", dest="assigned",
+ help="The developer in charge of the bug", default=None)
+ return parser
+
+longhelp="""
+Create a new bug, with a new ID. The summary specified on the
+commandline is a string (only one line) that describes the bug briefly
+or "-", in which case the string will be read from stdin.
+"""
+
+def help():
+ return get_parser().help_str() + longhelp
diff --git a/interfaces/email/interactive/becommands/open.py b/interfaces/email/interactive/becommands/open.py
new file mode 100644
index 0000000..0c6bf05
--- /dev/null
+++ b/interfaces/email/interactive/becommands/open.py
@@ -0,0 +1,58 @@
+# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
+# Marien Zwart <marienz@gentoo.org>
+# Thomas Gerigk <tgerigk@gmx.de>
+# W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Re-open a bug"""
+from libbe import cmdutil, bugdir
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> import os
+ >>> bd = bugdir.SimpleBugDir()
+ >>> os.chdir(bd.root)
+ >>> print bd.bug_from_shortname("b").status
+ closed
+ >>> execute(["b"], manipulate_encodings=False)
+ >>> bd._clear_bugs()
+ >>> print bd.bug_from_shortname("b").status
+ open
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ cmdutil.default_complete(options, args, parser,
+ bugid_args={0: lambda bug : bug.active==False})
+ if len(args) == 0:
+ raise cmdutil.UsageError, "Please specify a bug id."
+ if len(args) > 1:
+ raise cmdutil.UsageError, "Too many arguments."
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bug = cmdutil.bug_from_shortname(bd, args[0])
+ bug.status = "open"
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be open BUG-ID")
+ return parser
+
+longhelp="""
+Mark a bug as 'open'.
+"""
+
+def help():
+ return get_parser().help_str() + longhelp
diff --git a/interfaces/email/interactive/becommands/remove.py b/interfaces/email/interactive/becommands/remove.py
new file mode 100644
index 0000000..8d85033
--- /dev/null
+++ b/interfaces/email/interactive/becommands/remove.py
@@ -0,0 +1,62 @@
+# Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Remove (delete) a bug and its comments"""
+from libbe import cmdutil, bugdir
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> from libbe import mapfile
+ >>> import os
+ >>> bd = bugdir.SimpleBugDir()
+ >>> os.chdir(bd.root)
+ >>> print bd.bug_from_shortname("b").status
+ closed
+ >>> execute (["b"], manipulate_encodings=False)
+ Removed bug b
+ >>> bd._clear_bugs()
+ >>> try:
+ ... bd.bug_from_shortname("b")
+ ... except bugdir.NoBugMatches:
+ ... print "Bug not found"
+ Bug not found
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ cmdutil.default_complete(options, args, parser,
+ bugid_args={0: lambda bug : bug.active==True})
+ if len(args) != 1:
+ raise cmdutil.UsageError, "Please specify a bug id."
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bug = cmdutil.bug_from_shortname(bd, args[0])
+ bd.remove_bug(bug)
+ print "Removed bug %s" % bug.uuid
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be remove BUG-ID")
+ return parser
+
+longhelp="""
+Remove (delete) an existing bug. Use with caution: if you're not using a
+revision control system, there may be no way to recover the lost information.
+You should use this command, for example, to get rid of blank or otherwise
+mangled bugs.
+"""
+
+def help():
+ return get_parser().help_str() + longhelp
diff --git a/interfaces/email/interactive/becommands/set.py b/interfaces/email/interactive/becommands/set.py
new file mode 100644
index 0000000..f7e68d3
--- /dev/null
+++ b/interfaces/email/interactive/becommands/set.py
@@ -0,0 +1,130 @@
+# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
+# Chris Ball <cjb@laptop.org>
+# Marien Zwart <marienz@gentoo.org>
+# Thomas Gerigk <tgerigk@gmx.de>
+# W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Change tree settings"""
+import textwrap
+from libbe import cmdutil, bugdir, vcs, settings_object
+__desc__ = __doc__
+
+def _value_string(bd, setting):
+ val = bd.settings.get(setting, settings_object.EMPTY)
+ if val == settings_object.EMPTY:
+ default = getattr(bd, bd._setting_name_to_attr_name(setting))
+ if default not in [None, settings_object.EMPTY]:
+ val = "None (%s)" % default
+ else:
+ val = None
+ return str(val)
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> import os
+ >>> bd = bugdir.SimpleBugDir()
+ >>> os.chdir(bd.root)
+ >>> execute(["target"], manipulate_encodings=False)
+ None
+ >>> execute(["target", "tomorrow"], manipulate_encodings=False)
+ >>> execute(["target"], manipulate_encodings=False)
+ tomorrow
+ >>> execute(["target", "none"], manipulate_encodings=False)
+ >>> execute(["target"], manipulate_encodings=False)
+ None
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ complete(options, args, parser)
+ if len(args) > 2:
+ raise cmdutil.UsageError, "Too many arguments"
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ if len(args) == 0:
+ keys = bd.settings_properties
+ keys.sort()
+ for key in keys:
+ print "%16s: %s" % (key, _value_string(bd, key))
+ elif len(args) == 1:
+ print _value_string(bd, args[0])
+ else:
+ if args[1] == "none":
+ setattr(bd, args[0], settings_object.EMPTY)
+ else:
+ if args[0] not in bd.settings_properties:
+ msg = "Invalid setting %s\n" % args[0]
+ msg += 'Allowed settings:\n '
+ msg += '\n '.join(bd.settings_properties)
+ raise cmdutil.UserError(msg)
+ old_setting = bd.settings.get(args[0])
+ setattr(bd, args[0], args[1])
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be set [NAME] [VALUE]")
+ return parser
+
+def get_bugdir_settings():
+ settings = []
+ for s in bugdir.BugDir.settings_properties:
+ settings.append(s)
+ settings.sort()
+ documented_settings = []
+ for s in settings:
+ set = getattr(bugdir.BugDir, s)
+ dstr = set.__doc__.strip()
+ # per-setting comment adjustments
+ if s == "vcs_name":
+ lines = dstr.split('\n')
+ while lines[0].startswith("This property defaults to") == False:
+ lines.pop(0)
+ assert len(lines) != None, \
+ "Unexpected vcs_name docstring:\n '%s'" % dstr
+ lines.insert(
+ 0, "The name of the revision control system to use.\n")
+ dstr = '\n'.join(lines)
+ doc = textwrap.wrap(dstr, width=70, initial_indent=' ',
+ subsequent_indent=' ')
+ documented_settings.append("%s\n%s" % (s, '\n'.join(doc)))
+ return documented_settings
+
+longhelp="""
+Show or change per-tree settings.
+
+If name and value are supplied, the name is set to a new value.
+If no value is specified, the current value is printed.
+If no arguments are provided, all names and values are listed.
+
+To unset a setting, set it to "none".
+
+Allowed settings are:
+
+%s""" % ('\n'.join(get_bugdir_settings()),)
+
+def help():
+ return get_parser().help_str() + longhelp
+
+def complete(options, args, parser):
+ for option, value in cmdutil.option_value_pairs(options, parser):
+ if value == "--complete":
+ # no argument-options at the moment, so this is future-proofing
+ raise cmdutil.GetCompletions()
+ for pos,value in enumerate(args):
+ if value == "--complete":
+ if pos == 0: # first positional argument is a setting name
+ props = bugdir.BugDir.settings_properties
+ raise cmdutil.GetCompletions(props)
+ raise cmdutil.GetCompletions() # no positional arguments for list
diff --git a/interfaces/email/interactive/becommands/severity.py b/interfaces/email/interactive/becommands/severity.py
new file mode 100644
index 0000000..660586e
--- /dev/null
+++ b/interfaces/email/interactive/becommands/severity.py
@@ -0,0 +1,103 @@
+# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
+# Marien Zwart <marienz@gentoo.org>
+# Thomas Gerigk <tgerigk@gmx.de>
+# W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Show or change a bug's severity level"""
+from libbe import cmdutil, bugdir, bug
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> import os
+ >>> bd = bugdir.SimpleBugDir()
+ >>> os.chdir(bd.root)
+ >>> execute(["a"], manipulate_encodings=False)
+ minor
+ >>> execute(["a", "wishlist"], manipulate_encodings=False)
+ >>> execute(["a"], manipulate_encodings=False)
+ wishlist
+ >>> execute(["a", "none"], manipulate_encodings=False)
+ Traceback (most recent call last):
+ UserError: Invalid severity level: none
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ complete(options, args, parser)
+ if len(args) not in (1,2):
+ raise cmdutil.UsageError
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bug = cmdutil.bug_from_shortname(bd, args[0])
+ if len(args) == 1:
+ print bug.severity
+ elif len(args) == 2:
+ try:
+ bug.severity = args[1]
+ except ValueError, e:
+ if e.name != "severity":
+ raise e
+ raise cmdutil.UserError ("Invalid severity level: %s" % e.value)
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be severity BUG-ID [SEVERITY]")
+ return parser
+
+def help():
+ longhelp=["""
+Show or change a bug's severity level.
+
+If no severity is specified, the current value is printed. If a severity level
+is specified, it will be assigned to the bug.
+
+Severity levels are:
+"""]
+ try: # See if there are any per-tree severity configurations
+ bd = bugdir.BugDir(from_disk=True, manipulate_encodings=False)
+ except bugdir.NoBugDir, e:
+ pass # No tree, just show the defaults
+ longest_severity_len = max([len(s) for s in bug.severity_values])
+ for severity in bug.severity_values :
+ description = bug.severity_description[severity]
+ s = "%*s : %s\n" % (longest_severity_len, severity, description)
+ longhelp.append(s)
+ longhelp = ''.join(longhelp)
+ return get_parser().help_str() + longhelp
+
+def complete(options, args, parser):
+ for option,value in cmdutil.option_value_pairs(options, parser):
+ if value == "--complete":
+ # no argument-options at the moment, so this is future-proofing
+ raise cmdutil.GetCompletions()
+ for pos,value in enumerate(args):
+ if value == "--complete":
+ try: # See if there are any per-tree severity configurations
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=False)
+ except bugdir.NoBugDir:
+ bd = None
+ if pos == 0: # fist positional argument is a bug id
+ ids = []
+ if bd != None:
+ bd.load_all_bugs()
+ filter = lambda bg : bg.active==True
+ bugs = [bg for bg in bd if filter(bg)==True]
+ ids = [bd.bug_shortname(bg) for bg in bugs]
+ raise cmdutil.GetCompletions(ids)
+ elif pos == 1: # second positional argument is a severity
+ raise cmdutil.GetCompletions(bug.severity_values)
+ raise cmdutil.GetCompletions()
diff --git a/interfaces/email/interactive/becommands/show.py b/interfaces/email/interactive/becommands/show.py
new file mode 100644
index 0000000..50bd6eb
--- /dev/null
+++ b/interfaces/email/interactive/becommands/show.py
@@ -0,0 +1,116 @@
+# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
+# Chris Ball <cjb@laptop.org>
+# Thomas Gerigk <tgerigk@gmx.de>
+# Thomas Habets <thomas@habets.pp.se>
+# W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Show a particular bug"""
+import sys
+from libbe import cmdutil, bugdir
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> import os
+ >>> bd = bugdir.SimpleBugDir()
+ >>> os.chdir(bd.root)
+ >>> execute (["a",], manipulate_encodings=False) # doctest: +ELLIPSIS
+ ID : a
+ Short name : a
+ Severity : minor
+ Status : open
+ Assigned :
+ Target :
+ Reporter :
+ Creator : John Doe <jdoe@example.com>
+ Created : ...
+ Bug A
+ <BLANKLINE>
+ >>> execute (["--xml", "a"], manipulate_encodings=False) # doctest: +ELLIPSIS
+ <?xml version="1.0" encoding="..." ?>
+ <bug>
+ <uuid>a</uuid>
+ <short-name>a</short-name>
+ <severity>minor</severity>
+ <status>open</status>
+ <creator>John Doe &lt;jdoe@example.com&gt;</creator>
+ <created>...</created>
+ <summary>Bug A</summary>
+ </bug>
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ cmdutil.default_complete(options, args, parser,
+ bugid_args={-1: lambda bug : bug.active==True})
+ if len(args) == 0:
+ raise cmdutil.UsageError
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ if options.XML:
+ print '<?xml version="1.0" encoding="%s" ?>' % bd.encoding
+ for shortname in args:
+ if shortname.count(':') > 1:
+ raise cmdutil.UserError("Invalid id '%s'." % shortname)
+ elif shortname.count(':') == 1:
+ # Split shortname generated by Comment.comment_shortnames()
+ bugname = shortname.split(':')[0]
+ is_comment = True
+ else:
+ bugname = shortname
+ is_comment = False
+ if is_comment == True and options.comments == False:
+ continue
+ bug = cmdutil.bug_from_shortname(bd, bugname)
+ if is_comment == False:
+ if options.XML:
+ print bug.xml(show_comments=options.comments)
+ else:
+ print bug.string(show_comments=options.comments)
+ else:
+ comment = bug.comment_root.comment_from_shortname(
+ shortname, bug_shortname=bugname)
+ if options.XML:
+ print comment.xml(shortname=shortname)
+ else:
+ if len(args) == 1 and options.only_raw_body == True:
+ sys.__stdout__.write(comment.body)
+ else:
+ print comment.string(shortname=shortname)
+ if shortname != args[-1] and options.XML == False:
+ print "" # add a blank line between bugs/comments
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be show [options] ID [ID ...]")
+ parser.add_option("-x", "--xml", action="store_true", default=False,
+ dest='XML', help="Dump as XML")
+ parser.add_option("--only-raw-body", action="store_true",
+ dest='only_raw_body',
+ help="When printing only a single comment, just print it's body. This allows extraction of non-text content types.")
+ parser.add_option("-c", "--no-comments", dest="comments",
+ action="store_false", default=True,
+ help="Disable comment output. This is useful if you just want more details on a bug's current status.")
+ return parser
+
+longhelp="""
+Show all information about the bugs or comments whose IDs are given.
+
+It's probably not a good idea to mix bug and comment IDs in a single
+call, but you're free to do so if you like.
+"""
+
+def help():
+ return get_parser().help_str() + longhelp
diff --git a/interfaces/email/interactive/becommands/status.py b/interfaces/email/interactive/becommands/status.py
new file mode 100644
index 0000000..f315003
--- /dev/null
+++ b/interfaces/email/interactive/becommands/status.py
@@ -0,0 +1,115 @@
+# Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Show or change a bug's status"""
+from libbe import cmdutil, bugdir, bug
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> import os
+ >>> bd = bugdir.SimpleBugDir()
+ >>> os.chdir(bd.root)
+ >>> execute(["a"], manipulate_encodings=False)
+ open
+ >>> execute(["a", "closed"], manipulate_encodings=False)
+ >>> execute(["a"], manipulate_encodings=False)
+ closed
+ >>> execute(["a", "none"], manipulate_encodings=False)
+ Traceback (most recent call last):
+ UserError: Invalid status: none
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ complete(options, args, parser)
+ if len(args) not in (1,2):
+ raise cmdutil.UsageError
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bug = cmdutil.bug_from_shortname(bd, args[0])
+ if len(args) == 1:
+ print bug.status
+ else:
+ try:
+ bug.status = args[1]
+ except ValueError, e:
+ if e.name != "status":
+ raise
+ raise cmdutil.UserError ("Invalid status: %s" % e.value)
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be status BUG-ID [STATUS]")
+ return parser
+
+
+def help():
+ try: # See if there are any per-tree status configurations
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=False)
+ except bugdir.NoBugDir, e:
+ pass # No tree, just show the defaults
+ longest_status_len = max([len(s) for s in bug.status_values])
+ active_statuses = []
+ for status in bug.active_status_values :
+ description = bug.status_description[status]
+ s = "%*s : %s" % (longest_status_len, status, description)
+ active_statuses.append(s)
+ inactive_statuses = []
+ for status in bug.inactive_status_values :
+ description = bug.status_description[status]
+ s = "%*s : %s" % (longest_status_len, status, description)
+ inactive_statuses.append(s)
+ longhelp="""
+Show or change a bug's status.
+
+If no status is specified, the current value is printed. If a status
+is specified, it will be assigned to the bug.
+
+There are two classes of statuses, active and inactive, which are only
+important for commands like "be list" that show only active bugs by
+default.
+
+Active status levels are:
+ %s
+Inactive status levels are:
+ %s
+
+You can overide the list of allowed statuses on a per-repository basis.
+See "be set --help" for more details.
+""" % ('\n '.join(active_statuses), '\n '.join(inactive_statuses))
+ return get_parser().help_str() + longhelp
+
+def complete(options, args, parser):
+ for option,value in cmdutil.option_value_pairs(options, parser):
+ if value == "--complete":
+ # no argument-options at the moment, so this is future-proofing
+ raise cmdutil.GetCompletions()
+ for pos,value in enumerate(args):
+ if value == "--complete":
+ try: # See if there are any per-tree status configurations
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=False)
+ except bugdir.NoBugDir:
+ bd = None
+ if pos == 0: # fist positional argument is a bug id
+ ids = []
+ if bd != None:
+ bd.load_all_bugs()
+ ids = [bd.bug_shortname(bg) for bg in bd]
+ raise cmdutil.GetCompletions(ids)
+ elif pos == 1: # second positional argument is a status
+ raise cmdutil.GetCompletions(bug.status_values)
+ raise cmdutil.GetCompletions()
diff --git a/interfaces/email/interactive/becommands/subscribe.py b/interfaces/email/interactive/becommands/subscribe.py
new file mode 100644
index 0000000..0a23057
--- /dev/null
+++ b/interfaces/email/interactive/becommands/subscribe.py
@@ -0,0 +1,390 @@
+# Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""(Un)subscribe to change notification"""
+from libbe import cmdutil, bugdir, tree
+import os, copy
+__desc__ = __doc__
+
+TAG="SUBSCRIBE:"
+
+class SubscriptionType (tree.Tree):
+ """
+ Trees of subscription types to allow users to select exactly what
+ notifications they want to subscribe to.
+ """
+ def __init__(self, type_name, *args, **kwargs):
+ tree.Tree.__init__(self, *args, **kwargs)
+ self.type = type_name
+ def __str__(self):
+ return self.type
+ def __repr__(self):
+ return "<SubscriptionType: %s>" % str(self)
+ def string_tree(self, indent=0):
+ lines = []
+ for depth,node in self.thread():
+ lines.append("%s%s" % (" "*(indent+2*depth), node))
+ return "\n".join(lines)
+
+BUGDIR_TYPE_NEW = SubscriptionType("new")
+BUGDIR_TYPE_ALL = SubscriptionType("all", [BUGDIR_TYPE_NEW])
+
+# same name as BUGDIR_TYPE_ALL for consistency
+BUG_TYPE_ALL = SubscriptionType(str(BUGDIR_TYPE_ALL))
+
+INVALID_TYPE = SubscriptionType("INVALID")
+
+class InvalidType (ValueError):
+ def __init__(self, type_name, type_root):
+ msg = "Invalid type %s for tree:\n%s" \
+ % (type_name, type_root.string_tree(4))
+ ValueError.__init__(self, msg)
+ self.type_name = type_name
+ self.type_root = type_root
+
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> bd = bugdir.SimpleBugDir()
+ >>> bd.set_sync_with_disk(True)
+ >>> os.chdir(bd.root)
+ >>> a = bd.bug_from_shortname("a")
+ >>> print a.extra_strings
+ []
+ >>> execute(["-s","John Doe <j@doe.com>", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ Subscriptions for a:
+ John Doe <j@doe.com> all *
+ >>> bd._clear_bugs() # resync our copy of bug
+ >>> a = bd.bug_from_shortname("a")
+ >>> print a.extra_strings
+ ['SUBSCRIBE:John Doe <j@doe.com>\\tall\\t*']
+ >>> execute(["-s","Jane Doe <J@doe.com>", "-S", "a.com,b.net", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ Subscriptions for a:
+ Jane Doe <J@doe.com> all a.com,b.net
+ John Doe <j@doe.com> all *
+ >>> execute(["-s","Jane Doe <J@doe.com>", "-S", "a.edu", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ Subscriptions for a:
+ Jane Doe <J@doe.com> all a.com,a.edu,b.net
+ John Doe <j@doe.com> all *
+ >>> execute(["-u", "-s","Jane Doe <J@doe.com>", "-S", "a.com", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ Subscriptions for a:
+ Jane Doe <J@doe.com> all a.edu,b.net
+ John Doe <j@doe.com> all *
+ >>> execute(["-s","Jane Doe <J@doe.com>", "-S", "*", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ Subscriptions for a:
+ Jane Doe <J@doe.com> all *
+ John Doe <j@doe.com> all *
+ >>> execute(["-u", "-s","Jane Doe <J@doe.com>", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ Subscriptions for a:
+ John Doe <j@doe.com> all *
+ >>> execute(["-u", "-s","John Doe <j@doe.com>", "a"], manipulate_encodings=False)
+ >>> execute(["-s","Jane Doe <J@doe.com>", "-t", "new", "DIR"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ Subscriptions for bug directory:
+ Jane Doe <J@doe.com> new *
+ >>> execute(["-s","Jane Doe <J@doe.com>", "DIR"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
+ Subscriptions for bug directory:
+ Jane Doe <J@doe.com> all *
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ cmdutil.default_complete(options, args, parser,
+ bugid_args={0: lambda bug : bug.active==True})
+
+ if len(args) > 1:
+ help()
+ raise cmdutil.UsageError("Too many arguments.")
+
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+
+ subscriber = options.subscriber
+ if subscriber == None:
+ subscriber = bd.user_id
+ if options.unsubscribe == True:
+ if options.servers == None:
+ options.servers = "INVALID"
+ if options.types == None:
+ options.types = "INVALID"
+ else:
+ if options.servers == None:
+ options.servers = "*"
+ if options.types == None:
+ options.types = "all"
+ servers = options.servers.split(",")
+ types = options.types.split(",")
+
+ if len(args) == 0 or args[0] == "DIR": # directory-wide subscriptions
+ type_root = BUGDIR_TYPE_ALL
+ entity = bd
+ entity_name = "bug directory"
+ else: # bug-specific subscriptions
+ type_root = BUG_TYPE_ALL
+ bug = bd.bug_from_shortname(args[0])
+ entity = bug
+ entity_name = bug.uuid
+ if options.list_all == True:
+ entity_name = "anything in the bug directory"
+
+ types = [type_from_name(name, type_root, default=INVALID_TYPE,
+ default_ok=options.unsubscribe)
+ for name in types]
+ estrs = entity.extra_strings
+ if options.list == True or options.list_all == True:
+ pass
+ else: # alter subscriptions
+ if options.unsubscribe == True:
+ estrs = unsubscribe(estrs, subscriber, types, servers, type_root)
+ else: # add the tag
+ estrs = subscribe(estrs, subscriber, types, servers, type_root)
+ entity.extra_strings = estrs # reassign to notice change
+
+ if options.list_all == True:
+ bd.load_all_bugs()
+ subscriptions = get_bugdir_subscribers(bd, servers[0])
+ else:
+ subscriptions = []
+ for estr in entity.extra_strings:
+ if estr.startswith(TAG):
+ subscriptions.append(estr[len(TAG):])
+
+ if len(subscriptions) > 0:
+ print "Subscriptions for %s:" % entity_name
+ print '\n'.join(subscriptions)
+
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be subscribe ID")
+ parser.add_option("-u", "--unsubscribe", action="store_true",
+ dest="unsubscribe", default=False,
+ help="Unsubscribe instead of subscribing.")
+ parser.add_option("-a", "--list-all", action="store_true",
+ dest="list_all", default=False,
+ help="List all subscribers (no ID argument, read only action).")
+ parser.add_option("-l", "--list", action="store_true",
+ dest="list", default=False,
+ help="List subscribers (read only action).")
+ parser.add_option("-s", "--subscriber", dest="subscriber",
+ metavar="SUBSCRIBER",
+ help="Email address of the subscriber (defaults to bugdir.user_id).")
+ parser.add_option("-S", "--servers", dest="servers", metavar="SERVERS",
+ help="Servers from which you want notification.")
+ parser.add_option("-t", "--type", dest="types", metavar="TYPES",
+ help="Types of changes you wish to be notified about.")
+ return parser
+
+longhelp="""
+ID can be either a bug id, or blank/"DIR", in which case it refers to the
+whole bug directory.
+
+SERVERS specifies the servers from which you would like to receive
+notification. Multiple severs may be specified in a comma-separated
+list, or you can use "*" to match all servers (the default). If you
+have not selected a server, it should politely refrain from notifying
+you of changes, although there is no way to guarantee this behavior.
+
+Available TYPES:
+ For bugs:
+%s
+ For DIR :
+%s
+
+For unsubscription, any listed SERVERS and TYPES are removed from your
+subscription. Either the catch-all server "*" or type "%s" will
+remove SUBSCRIBER entirely from the specified ID.
+
+This command is intended for use primarily by public interfaces, since
+if you're just hacking away on your private repository, you'll known
+what's changed ;). This command just (un)sets the appropriate
+subscriptions, and leaves it up to each interface to perform the
+notification.
+""" % (BUG_TYPE_ALL.string_tree(6), BUGDIR_TYPE_ALL.string_tree(6),
+ BUGDIR_TYPE_ALL)
+
+def help():
+ return get_parser().help_str() + longhelp
+
+# internal helper functions
+
+def _generate_string(subscriber, types, servers):
+ types = sorted([str(t) for t in types])
+ servers = sorted(servers)
+ return "%s%s\t%s\t%s" % (TAG,subscriber,",".join(types),",".join(servers))
+
+def _parse_string(string, type_root):
+ assert string.startswith(TAG), string
+ string = string[len(TAG):]
+ subscriber,types,servers = string.split("\t")
+ types = [type_from_name(name, type_root) for name in types.split(",")]
+ return (subscriber,types,servers.split(","))
+
+def _get_subscriber(extra_strings, subscriber, type_root):
+ for i,string in enumerate(extra_strings):
+ if string.startswith(TAG):
+ s,ts,srvs = _parse_string(string, type_root)
+ if s == subscriber:
+ return i,s,ts,srvs # match!
+ return None # no match
+
+# functions exposed to other modules
+
+def type_from_name(name, type_root, default=None, default_ok=False):
+ if name == str(type_root):
+ return type_root
+ for t in type_root.traverse():
+ if name == str(t):
+ return t
+ if default_ok:
+ return default
+ raise InvalidType(name, type_root)
+
+def subscribe(extra_strings, subscriber, types, servers, type_root):
+ args = _get_subscriber(extra_strings, subscriber, type_root)
+ if args == None: # no match
+ extra_strings.append(_generate_string(subscriber, types, servers))
+ return extra_strings
+ # Alter matched string
+ i,s,ts,srvs = args
+ for t in types:
+ if t not in ts:
+ ts.append(t)
+ # remove descendant types
+ all_ts = copy.copy(ts)
+ for t in all_ts:
+ for tt in all_ts:
+ if tt in ts and t.has_descendant(tt):
+ ts.remove(tt)
+ if "*" in servers+srvs:
+ srvs = ["*"]
+ else:
+ srvs = list(set(servers+srvs))
+ extra_strings[i] = _generate_string(subscriber, ts, srvs)
+ return extra_strings
+
+def unsubscribe(extra_strings, subscriber, types, servers, type_root):
+ args = _get_subscriber(extra_strings, subscriber, type_root)
+ if args == None: # no match
+ return extra_strings # pass
+ # Remove matched string
+ i,s,ts,srvs = args
+ all_ts = copy.copy(ts)
+ for t in types:
+ for tt in all_ts:
+ if tt in ts and t.has_descendant(tt):
+ ts.remove(tt)
+ if "*" in servers+srvs:
+ srvs = []
+ else:
+ for srv in servers:
+ if srv in srvs:
+ srvs.remove(srv)
+ if len(ts) == 0 or len(srvs) == 0:
+ extra_strings.pop(i)
+ else:
+ extra_strings[i] = _generate_string(subscriber, ts, srvs)
+ return extra_strings
+
+def get_subscribers(extra_strings, type, server, type_root,
+ match_ancestor_types=False,
+ match_descendant_types=False):
+ """
+ Set match_ancestor_types=True if you want to find eveyone who
+ cares about your particular type.
+
+ Set match_descendant_types=True if you want to find subscribers
+ who may only care about some subset of your type. This is useful
+ for generating lists of all the subscribers in a given set of
+ extra_strings.
+
+ >>> def sgs(*args, **kwargs):
+ ... return sorted(get_subscribers(*args, **kwargs))
+ >>> es = []
+ >>> es = subscribe(es, "John Doe <j@doe.com>", [BUGDIR_TYPE_ALL], ["a.com"], BUGDIR_TYPE_ALL)
+ >>> es = subscribe(es, "Jane Doe <J@doe.com>", [BUGDIR_TYPE_NEW], ["*"], BUGDIR_TYPE_ALL)
+ >>> sgs(es, BUGDIR_TYPE_ALL, "a.com", BUGDIR_TYPE_ALL)
+ ['John Doe <j@doe.com>']
+ >>> sgs(es, BUGDIR_TYPE_ALL, "a.com", BUGDIR_TYPE_ALL, match_descendant_types=True)
+ ['Jane Doe <J@doe.com>', 'John Doe <j@doe.com>']
+ >>> sgs(es, BUGDIR_TYPE_ALL, "b.net", BUGDIR_TYPE_ALL, match_descendant_types=True)
+ ['Jane Doe <J@doe.com>']
+ >>> sgs(es, BUGDIR_TYPE_NEW, "a.com", BUGDIR_TYPE_ALL)
+ ['Jane Doe <J@doe.com>']
+ >>> sgs(es, BUGDIR_TYPE_NEW, "a.com", BUGDIR_TYPE_ALL, match_ancestor_types=True)
+ ['Jane Doe <J@doe.com>', 'John Doe <j@doe.com>']
+ """
+ for string in extra_strings:
+ if not string.startswith(TAG):
+ continue
+ subscriber,types,servers = _parse_string(string, type_root)
+ type_match = False
+ if type in types:
+ type_match = True
+ if type_match == False and match_ancestor_types == True:
+ for t in types:
+ if t.has_descendant(type):
+ type_match = True
+ break
+ if type_match == False and match_descendant_types == True:
+ for t in types:
+ if type.has_descendant(t):
+ type_match = True
+ break
+ server_match = False
+ if server in servers or servers == ["*"] or server == "*":
+ server_match = True
+ if type_match == True and server_match == True:
+ yield subscriber
+
+def get_bugdir_subscribers(bugdir, server):
+ """
+ I have a bugdir. Who cares about it, and what do they care about?
+ Returns a dict of dicts:
+ subscribers[user][id] = types
+ where id is either a bug.uuid (in the case of a bug subscription)
+ or "DIR" (in the case of a bugdir subscription).
+
+ Only checks bugs that are currently in memory, so you might want
+ to call bugdir.load_all_bugs() first.
+
+ >>> bd = bugdir.SimpleBugDir(sync_with_disk=False)
+ >>> a = bd.bug_from_shortname("a")
+ >>> bd.extra_strings = subscribe(bd.extra_strings, "John Doe <j@doe.com>", [BUGDIR_TYPE_ALL], ["a.com"], BUGDIR_TYPE_ALL)
+ >>> bd.extra_strings = subscribe(bd.extra_strings, "Jane Doe <J@doe.com>", [BUGDIR_TYPE_NEW], ["*"], BUGDIR_TYPE_ALL)
+ >>> a.extra_strings = subscribe(a.extra_strings, "John Doe <j@doe.com>", [BUG_TYPE_ALL], ["a.com"], BUG_TYPE_ALL)
+ >>> subscribers = get_bugdir_subscribers(bd, "a.com")
+ >>> subscribers["Jane Doe <J@doe.com>"]["DIR"]
+ [<SubscriptionType: new>]
+ >>> subscribers["John Doe <j@doe.com>"]["DIR"]
+ [<SubscriptionType: all>]
+ >>> subscribers["John Doe <j@doe.com>"]["a"]
+ [<SubscriptionType: all>]
+ >>> get_bugdir_subscribers(bd, "b.net")
+ {'Jane Doe <J@doe.com>': {'DIR': [<SubscriptionType: new>]}}
+ >>> bd.cleanup()
+ """
+ subscribers = {}
+ for sub in get_subscribers(bugdir.extra_strings, BUGDIR_TYPE_ALL, server,
+ BUGDIR_TYPE_ALL, match_descendant_types=True):
+ i,s,ts,srvs = _get_subscriber(bugdir.extra_strings,sub,BUGDIR_TYPE_ALL)
+ subscribers[sub] = {"DIR":ts}
+ for bug in bugdir:
+ for sub in get_subscribers(bug.extra_strings, BUG_TYPE_ALL, server,
+ BUG_TYPE_ALL, match_descendant_types=True):
+ i,s,ts,srvs = _get_subscriber(bug.extra_strings,sub,BUG_TYPE_ALL)
+ if sub in subscribers:
+ subscribers[sub][bug.uuid] = ts
+ else:
+ subscribers[sub] = {bug.uuid:ts}
+ return subscribers
diff --git a/interfaces/email/interactive/becommands/tag.py b/interfaces/email/interactive/becommands/tag.py
new file mode 100644
index 0000000..ecd853f
--- /dev/null
+++ b/interfaces/email/interactive/becommands/tag.py
@@ -0,0 +1,134 @@
+# Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Tag a bug, or search bugs for tags"""
+from libbe import cmdutil, bugdir
+import os, copy
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> from libbe import utility
+ >>> bd = bugdir.SimpleBugDir()
+ >>> bd.set_sync_with_disk(True)
+ >>> os.chdir(bd.root)
+ >>> a = bd.bug_from_shortname("a")
+ >>> print a.extra_strings
+ []
+ >>> execute(["a", "GUI"], manipulate_encodings=False)
+ Tags for a:
+ GUI
+ >>> bd._clear_bugs() # resync our copy of bug
+ >>> a = bd.bug_from_shortname("a")
+ >>> print a.extra_strings
+ ['TAG:GUI']
+ >>> execute(["a", "later"], manipulate_encodings=False)
+ Tags for a:
+ GUI
+ later
+ >>> execute(["a"], manipulate_encodings=False)
+ Tags for a:
+ GUI
+ later
+ >>> execute(["--list"], manipulate_encodings=False)
+ GUI
+ later
+ >>> execute(["a", "Alphabetically first"], manipulate_encodings=False)
+ Tags for a:
+ Alphabetically first
+ GUI
+ later
+ >>> bd._clear_bugs() # resync our copy of bug
+ >>> a = bd.bug_from_shortname("a")
+ >>> print a.extra_strings
+ ['TAG:Alphabetically first', 'TAG:GUI', 'TAG:later']
+ >>> a.extra_strings = []
+ >>> print a.extra_strings
+ []
+ >>> execute(["a"], manipulate_encodings=False)
+ >>> bd._clear_bugs() # resync our copy of bug
+ >>> a = bd.bug_from_shortname("a")
+ >>> print a.extra_strings
+ []
+ >>> execute(["a", "Alphabetically first"], manipulate_encodings=False)
+ Tags for a:
+ Alphabetically first
+ >>> execute(["--remove", "a", "Alphabetically first"], manipulate_encodings=False)
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ cmdutil.default_complete(options, args, parser,
+ bugid_args={0: lambda bug : bug.active==True})
+
+ if len(args) == 0 and options.list == False:
+ raise cmdutil.UsageError("Please specify a bug id.")
+ elif len(args) > 2 or (len(args) > 0 and options.list == True):
+ help()
+ raise cmdutil.UsageError("Too many arguments.")
+
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ if options.list:
+ bd.load_all_bugs()
+ tags = []
+ for bug in bd:
+ for estr in bug.extra_strings:
+ if estr.startswith("TAG:"):
+ tag = estr[4:]
+ if tag not in tags:
+ tags.append(tag)
+ tags.sort()
+ if len(tags) > 0:
+ print '\n'.join(tags)
+ return
+ bug = cmdutil.bug_from_shortname(bd, args[0])
+ if len(args) == 2:
+ given_tag = args[1]
+ estrs = bug.extra_strings
+ tag_string = "TAG:%s" % given_tag
+ if options.remove == True:
+ estrs.remove(tag_string)
+ else: # add the tag
+ estrs.append(tag_string)
+ bug.extra_strings = estrs # reassign to notice change
+
+ tags = []
+ for estr in bug.extra_strings:
+ if estr.startswith("TAG:"):
+ tags.append(estr[4:])
+
+ if len(tags) > 0:
+ print "Tags for %s:" % bug.uuid
+ print '\n'.join(tags)
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be tag BUG-ID [TAG]\nor: be tag --list")
+ parser.add_option("-r", "--remove", action="store_true", dest="remove",
+ help="Remove TAG (instead of adding it)")
+ parser.add_option("-l", "--list", action="store_true", dest="list",
+ help="List all available tags and exit")
+ return parser
+
+longhelp="""
+If TAG is given, add TAG to BUG-ID. If it is not specified, just
+print the tags for BUG-ID.
+
+To search for bugs with a particular tag, try
+ $ be list --extra-strings TAG:<your-tag>
+"""
+
+def help():
+ return get_parser().help_str() + longhelp
diff --git a/interfaces/email/interactive/becommands/target.py b/interfaces/email/interactive/becommands/target.py
new file mode 100644
index 0000000..7e41451
--- /dev/null
+++ b/interfaces/email/interactive/becommands/target.py
@@ -0,0 +1,95 @@
+# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
+# Chris Ball <cjb@laptop.org>
+# Gianluca Montecchi <gian@grys.it>
+# Marien Zwart <marienz@gentoo.org>
+# Thomas Gerigk <tgerigk@gmx.de>
+# W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""Show or change a bug's target for fixing"""
+from libbe import cmdutil, bugdir
+__desc__ = __doc__
+
+def execute(args, manipulate_encodings=True):
+ """
+ >>> import os
+ >>> bd = bugdir.SimpleBugDir()
+ >>> os.chdir(bd.root)
+ >>> execute(["a"], manipulate_encodings=False)
+ No target assigned.
+ >>> execute(["a", "tomorrow"], manipulate_encodings=False)
+ >>> execute(["a"], manipulate_encodings=False)
+ tomorrow
+ >>> execute(["--list"], manipulate_encodings=False)
+ tomorrow
+ >>> execute(["a", "none"], manipulate_encodings=False)
+ >>> execute(["a"], manipulate_encodings=False)
+ No target assigned.
+ >>> bd.cleanup()
+ """
+ parser = get_parser()
+ options, args = parser.parse_args(args)
+ cmdutil.default_complete(options, args, parser,
+ bugid_args={0: lambda bug : bug.active==True})
+
+ if len(args) not in (1, 2):
+ if not (options.list == True and len(args) == 0):
+ raise cmdutil.UsageError
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ if options.list:
+ ts = set([bd.bug_from_uuid(bug).target for bug in bd.list_uuids()])
+ for target in sorted(ts):
+ if target and isinstance(target,str):
+ print target
+ return
+ bug = cmdutil.bug_from_shortname(bd, args[0])
+ if len(args) == 1:
+ if bug.target is None:
+ print "No target assigned."
+ else:
+ print bug.target
+ else:
+ assert len(args) == 2
+ if args[1] == "none":
+ bug.target = None
+ else:
+ bug.target = args[1]
+
+def get_parser():
+ parser = cmdutil.CmdOptionParser("be target BUG-ID [TARGET]\nor: be target --list")
+ parser.add_option("-l", "--list", action="store_true", dest="list",
+ help="List all available targets and exit")
+ return parser
+
+longhelp="""
+Show or change a bug's target for fixing.
+
+If no target is specified, the current value is printed. If a target
+is specified, it will be assigned to the bug.
+
+Targets are freeform; any text may be specified. They will generally be
+milestone names or release numbers.
+
+The value "none" can be used to unset the target.
+
+In the alternative `be target --list` form print a list of all
+currently specified targets. Note that bug status
+(i.e. opened/closed) is ignored. If you want to list all bugs
+matching a current target, see `be list --target TARGET'.
+"""
+
+def help():
+ return get_parser().help_str() + longhelp