aboutsummaryrefslogtreecommitdiffstats
path: root/becommands
diff options
context:
space:
mode:
authorChris Ball <cjb@laptop.org>2009-10-05 23:15:39 -0400
committerChris Ball <cjb@laptop.org>2009-10-05 23:15:39 -0400
commitb6a50c703369467132876f21efa8e4ffc672afae (patch)
tree905a9ae34efa2bce47d754893093b016d18d0162 /becommands
parent6a639574fa95e50f82fa3052e5524b961295a7ab (diff)
parente35ccf95ea89b6e622202caae30d3b8cca3f2473 (diff)
downloadbugseverywhere-b6a50c703369467132876f21efa8e4ffc672afae.tar.gz
Large merge from W. Trevor King, including Gianluca's HTML export.
Diffstat (limited to 'becommands')
-rw-r--r--becommands/assign.py15
-rw-r--r--becommands/close.py12
-rw-r--r--becommands/comment.py34
-rw-r--r--becommands/commit.py13
-rw-r--r--becommands/depend.py321
-rw-r--r--becommands/diff.py67
-rw-r--r--becommands/help.py6
-rw-r--r--becommands/html.py588
-rw-r--r--becommands/init.py28
-rw-r--r--becommands/list.py17
-rw-r--r--becommands/merge.py14
-rw-r--r--becommands/new.py12
-rw-r--r--becommands/open.py12
-rw-r--r--becommands/remove.py14
-rw-r--r--becommands/set.py24
-rw-r--r--becommands/severity.py18
-rw-r--r--becommands/show.py14
-rw-r--r--becommands/status.py21
-rw-r--r--becommands/subscribe.py390
-rw-r--r--becommands/tag.py26
-rw-r--r--becommands/target.py22
21 files changed, 1469 insertions, 199 deletions
diff --git a/becommands/assign.py b/becommands/assign.py
index 536bca6..794f028 100644
--- a/becommands/assign.py
+++ b/becommands/assign.py
@@ -20,28 +20,29 @@
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
>>> bd.bug_from_shortname("a").assigned is None
True
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
>>> bd._clear_bugs()
>>> bd.bug_from_shortname("a").assigned == bd.user_id
True
- >>> execute(["a", "someone"], test=True)
+ >>> execute(["a", "someone"], manipulate_encodings=False)
>>> bd._clear_bugs()
>>> print bd.bug_from_shortname("a").assigned
someone
- >>> execute(["a","none"], test=True)
+ >>> 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)
@@ -53,7 +54,9 @@ def execute(args, test=False):
if len(args) > 2:
help()
raise cmdutil.UsageError("Too many arguments.")
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ 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
diff --git a/becommands/close.py b/becommands/close.py
index 0ba8f50..0532ed2 100644
--- a/becommands/close.py
+++ b/becommands/close.py
@@ -20,18 +20,19 @@
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> from libbe import bugdir
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
>>> print bd.bug_from_shortname("a").status
open
- >>> execute(["a"], test=True)
+ >>> 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)
@@ -41,8 +42,9 @@ def execute(args, test=False):
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=not test)
- bug = bd.bug_from_shortname(args[0])
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bug = cmdutil.bug_from_shortname(bd, args[0])
bug.status = "closed"
bd.save()
diff --git a/becommands/comment.py b/becommands/comment.py
index 55b5913..9a614b2 100644
--- a/becommands/comment.py
+++ b/becommands/comment.py
@@ -25,20 +25,20 @@ except ImportError: # look for non-core module
from elementtree import ElementTree
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import time
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
- >>> execute(["a", "This is a comment about a"], test=True)
+ >>> execute(["a", "This is a comment about a"], manipulate_encodings=False)
>>> bd._clear_bugs()
- >>> bug = bd.bug_from_shortname("a")
+ >>> 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.From == bd.user_id
+ >>> comment.author == bd.user_id
True
>>> comment.time <= int(time.time())
True
@@ -47,19 +47,20 @@ def execute(args, test=False):
>>> if 'EDITOR' in os.environ:
... del os.environ["EDITOR"]
- >>> execute(["b"], test=True)
+ >>> 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"], test=True)
+ >>> execute(["b"], manipulate_encodings=False)
>>> bd._clear_bugs()
- >>> bug = bd.bug_from_shortname("b")
+ >>> 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)
@@ -68,10 +69,10 @@ def execute(args, test=False):
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)
+ raise cmdutil.UserError("Invalid id '%s'." % shortname)
elif shortname.count(':') == 1:
# Split shortname generated by Comment.comment_shortnames()
bugname = shortname.split(':')[0]
@@ -79,17 +80,17 @@ def execute(args, test=False):
else:
bugname = shortname
is_reply = False
-
+
bd = bugdir.BugDir(from_disk=True,
- manipulate_encodings=not test)
- bug = bd.bug_from_shortname(bugname)
+ 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:
@@ -103,7 +104,6 @@ def execute(args, test=False):
raise cmdutil.UserError, "No comment supplied, and EDITOR not specified."
if body is None:
raise cmdutil.UserError("No comment entered.")
- body = body.decode('utf-8')
elif args[1] == '-': # read body from stdin
binary = not (options.content_type == None
or options.content_type.startswith("text/"))
@@ -117,11 +117,11 @@ def execute(args, test=False):
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.From = options.author
+ new.author = options.author
if options.alt_id != None:
new.alt_id = options.alt_id
if options.content_type != None:
diff --git a/becommands/commit.py b/becommands/commit.py
index 4f3bdbd..dc70e7e 100644
--- a/becommands/commit.py
+++ b/becommands/commit.py
@@ -14,7 +14,7 @@
# 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, rcs
+from libbe import cmdutil, bugdir, editor, vcs
import sys
__desc__ = __doc__
@@ -22,13 +22,14 @@ def execute(args, manipulate_encodings=True):
"""
>>> import os, time
>>> from libbe import bug
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
>>> full_path = "testfile"
>>> test_contents = "A test file"
- >>> bd.rcs.set_file_contents(full_path, test_contents)
+ >>> 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)
@@ -48,11 +49,11 @@ def execute(args, manipulate_encodings=True):
elif options.body == "EDITOR":
body = editor.editor_string("Please enter your commit message above")
else:
- body = bd.rcs.get_file_contents(options.body, allow_no_rcs=True)
+ body = bd.vcs.get_file_contents(options.body, allow_no_vcs=True)
try:
- revision = bd.rcs.commit(summary, body=body,
+ revision = bd.vcs.commit(summary, body=body,
allow_empty=options.allow_empty)
- except rcs.EmptyCommit, e:
+ except vcs.EmptyCommit, e:
print e
return 1
else:
diff --git a/becommands/depend.py b/becommands/depend.py
index 4a23b0f..f72b8ba 100644
--- a/becommands/depend.py
+++ b/becommands/depend.py
@@ -14,26 +14,56 @@
# 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
+from libbe import cmdutil, bugdir, tree
import os, copy
__desc__ = __doc__
-def execute(args, test=False):
+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.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> bd.save()
>>> os.chdir(bd.root)
- >>> execute(["a", "b"], test=True)
- Blocks on a:
+ >>> execute(["a", "b"], manipulate_encodings=False)
+ a blocked by:
b
- >>> execute(["a"], test=True)
- Blocks on a:
+ >>> execute(["a"], manipulate_encodings=False)
+ a blocked by:
b
- >>> execute(["--show-status", "a"], test=True) # doctest: +NORMALIZE_WHITESPACE
- Blocks on a:
+ >>> 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", "a", "b"], test=True)
+ >>> 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)
@@ -41,45 +71,83 @@ def execute(args, test=False):
bugid_args={0: lambda bug : bug.active==True,
1: lambda bug : bug.active==True})
- if len(args) < 1:
+ 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.")
- if len(args) > 2:
+ elif len(args) > 2:
help()
raise cmdutil.UsageError("Too many arguments.")
-
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
- bugA = bd.bug_from_shortname(args[0])
+ 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 = bd.bug_from_shortname(args[1])
- estrs = bugA.extra_strings
- depend_string = "BLOCKED-BY:%s" % bugB.uuid
+ bugB = cmdutil.bug_from_shortname(bd, args[1])
if options.remove == True:
- estrs.remove(depend_string)
+ remove_block(bugA, bugB)
else: # add the dependency
- estrs.append(depend_string)
- bugA.extra_strings = estrs # reassign to notice change
-
- depends = []
- for estr in bugA.extra_strings:
- if estr.startswith("BLOCKED-BY:"):
- uuid = estr[11:]
- if options.show_status == True:
- blocker = bd.bug_from_uuid(uuid)
- block_string = "%s\t%s" % (uuid, blocker.status)
- else:
- block_string = uuid
- depends.append(block_string)
- if len(depends) > 0:
- print "Blocks on %s:" % bugA.uuid
- print '\n'.join(depends)
+ 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]")
- parser.add_option("-r", "--remove", action="store_true", dest="remove",
+ 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",
+ 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="""
@@ -88,7 +156,184 @@ 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/becommands/diff.py b/becommands/diff.py
index 13402c0..b6ac5b0 100644
--- a/becommands/diff.py
+++ b/becommands/diff.py
@@ -20,23 +20,35 @@ from libbe import cmdutil, bugdir, diff
import os
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> bd.set_sync_with_disk(True)
- >>> original = bd.rcs.commit("Original status")
+ >>> original = bd.vcs.commit("Original status")
>>> bug = bd.bug_from_uuid("a")
>>> bug.status = "closed"
- >>> changed = bd.rcs.commit("Closed bug a")
+ >>> changed = bd.vcs.commit("Closed bug a")
>>> os.chdir(bd.root)
- >>> if bd.rcs.versioned == True:
- ... execute([original], test=True)
+ >>> if bd.vcs.versioned == True:
+ ... execute([original], manipulate_encodings=False)
... else:
- ... print "a:cm: Bug A\\nstatus: open -> closed\\n"
- Modified bug reports:
- a:cm: Bug A
- status: open -> closed
+ ... 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)
@@ -47,28 +59,31 @@ def execute(args, test=False):
revision = args[0]
if len(args) > 1:
raise cmdutil.UsageError("Too many arguments.")
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
- if bd.rcs.versioned == False:
+ 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)
- r,m,a = diff.bug_diffs(old_bd, bd)
-
- optbugs = []
+ 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:
- optbugs.extend(a)
+ uuids.extend([c.name for c in tree.child_by_path("/bugs/new")])
if options.modified == True:
- optbugs.extend([new for old,new in m])
+ uuids.extend([c.name for c in tree.child_by_path("/bugs/mod")])
if options.removed == True:
- optbugs.extend(r)
- if len(optbugs) > 0:
- for bug in optbugs:
- print bug.uuid
+ 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 = diff.diff_report((r,m,a), old_bd, bd).encode(bd.encoding)
- if len(rep) > 0:
+ rep = tree.report_string()
+ if rep != None:
print rep
bd.remove_duplicate_bugdir()
@@ -85,14 +100,14 @@ def get_parser():
long = "--%s" % s[1]
help = s[2]
parser.add_option(short, long, action="store_true",
- dest=attr, help=help)
+ default=False, dest=attr, help=help)
return parser
longhelp="""
-Uses the RCS to compare the current tree with a previous tree, and
+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
-RCS.
+VCS.
For Arch your specifier must be a fully-qualified revision name.
diff --git a/becommands/help.py b/becommands/help.py
index a8ae338..a8f346a 100644
--- a/becommands/help.py
+++ b/becommands/help.py
@@ -19,9 +19,11 @@
from libbe import cmdutil, utility
__desc__ = __doc__
-def execute(args):
+def execute(args, manipulate_encodings=False):
"""
- Print help of specified command.
+ Print help of specified command (the manipulate_encodings argument
+ is ignored).
+
>>> execute(["help"])
Usage: be help [COMMAND]
<BLANKLINE>
diff --git a/becommands/html.py b/becommands/html.py
new file mode 100644
index 0000000..908c714
--- /dev/null
+++ b/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/becommands/init.py b/becommands/init.py
index 5b2a416..1125d93 100644
--- a/becommands/init.py
+++ b/becommands/init.py
@@ -19,9 +19,9 @@ import os.path
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
- >>> from libbe import utility, rcs
+ >>> from libbe import utility, vcs
>>> import os
>>> dir = utility.Dir()
>>> try:
@@ -29,28 +29,28 @@ def execute(args, test=False):
... except bugdir.NoBugDir, e:
... True
True
- >>> execute(['--root', dir.path], test=True)
+ >>> execute(['--root', dir.path], manipulate_encodings=False)
No revision control detected.
Directory initialized.
>>> del(dir)
>>> dir = utility.Dir()
>>> os.chdir(dir.path)
- >>> rcs = rcs.installed_rcs()
- >>> rcs.init('.')
- >>> print rcs.name
+ >>> vcs = vcs.installed_vcs()
+ >>> vcs.init('.')
+ >>> print vcs.name
Arch
- >>> execute([], test=True)
+ >>> execute([], manipulate_encodings=False)
Using Arch for revision control.
Directory initialized.
- >>> rcs.cleanup()
+ >>> vcs.cleanup()
>>> try:
- ... execute(['--root', '.'], test=True)
+ ... execute(['--root', '.'], manipulate_encodings=False)
... except cmdutil.UserError, e:
... str(e).startswith("Directory already initialized: ")
True
- >>> execute(['--root', '/highly-unlikely-to-exist'], test=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('/')
@@ -64,14 +64,14 @@ def execute(args, test=False):
bd = bugdir.BugDir(options.root_dir, from_disk=False,
sink_to_existing_root=False,
assert_new_BugDir=True,
- manipulate_encodings=not test)
+ 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.rcs.name is not "None":
- print "Using %s for revision control." % bd.rcs.name
+ 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."
@@ -86,7 +86,7 @@ def get_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 rcs_name" to change the rcs being used.
+system. You can use "be set vcs_name" to change the vcs being used.
The directory defaults to your current working directory.
diff --git a/becommands/list.py b/becommands/list.py
index 5ba1821..12e1e29 100644
--- a/becommands/list.py
+++ b/becommands/list.py
@@ -26,16 +26,17 @@ __desc__ = __doc__
AVAILABLE_CMPS = [fn[4:] for fn in dir(bug) if fn[:4] == 'cmp_']
AVAILABLE_CMPS.remove("attr") # a cmp_* template.
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
- >>> execute([], test=True)
+ >>> execute([], manipulate_encodings=False)
a:om: Bug A
- >>> execute(["--status", "all"], test=True)
+ >>> 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)
@@ -46,11 +47,13 @@ def execute(args, test=False):
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)))
+ 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=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
bd.load_all_bugs()
# select status
if options.status != None:
diff --git a/becommands/merge.py b/becommands/merge.py
index 4aaefa8..f212b01 100644
--- a/becommands/merge.py
+++ b/becommands/merge.py
@@ -18,10 +18,10 @@ from libbe import cmdutil, bugdir
import os, copy
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> from libbe import utility
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> bd.set_sync_with_disk(True)
>>> a = bd.bug_from_shortname("a")
>>> a.comment_root.time = 0
@@ -37,7 +37,7 @@ def execute(args, test=False):
>>> dummy = dummy.new_reply("1 2 3 4")
>>> dummy.time = 2
>>> os.chdir(bd.root)
- >>> execute(["a", "b"], test=True)
+ >>> execute(["a", "b"], manipulate_encodings=False)
Merging bugs a and b
>>> bd._clear_bugs()
>>> a = bd.bug_from_shortname("a")
@@ -120,6 +120,7 @@ def execute(args, test=False):
Merged into bug a
>>> print b.status
closed
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -133,10 +134,11 @@ def execute(args, test=False):
help()
raise cmdutil.UsageError("Too many arguments.")
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
- bugA = bd.bug_from_shortname(args[0])
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bugA = cmdutil.bug_from_shortname(bd, args[0])
bugA.load_comments()
- bugB = bd.bug_from_shortname(args[1])
+ 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)
diff --git a/becommands/new.py b/becommands/new.py
index af599d7..a8ee2ec 100644
--- a/becommands/new.py
+++ b/becommands/new.py
@@ -19,16 +19,16 @@ from libbe import cmdutil, bugdir
import sys
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os, time
>>> from libbe import bug
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
>>> bug.uuid_gen = lambda: "X"
- >>> execute (["this is a test",], test=True)
+ >>> execute (["this is a test",], manipulate_encodings=False)
Created bug with ID X
- >>> bd.load()
+ >>> bd._clear_bugs()
>>> bug = bd.bug_from_uuid("X")
>>> print bug.summary
this is a test
@@ -38,13 +38,15 @@ def execute(args, test=False):
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=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
if args[0] == '-': # read summary from stdin
summary = sys.stdin.readline()
else:
diff --git a/becommands/open.py b/becommands/open.py
index 2ef5f43..0c6bf05 100644
--- a/becommands/open.py
+++ b/becommands/open.py
@@ -20,17 +20,18 @@
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
>>> print bd.bug_from_shortname("b").status
closed
- >>> execute(["b"], test=True)
+ >>> 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)
@@ -40,8 +41,9 @@ def execute(args, test=False):
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=not test)
- bug = bd.bug_from_shortname(args[0])
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
+ bug = cmdutil.bug_from_shortname(bd, args[0])
bug.status = "open"
def get_parser():
diff --git a/becommands/remove.py b/becommands/remove.py
index d79a7be..8d85033 100644
--- a/becommands/remove.py
+++ b/becommands/remove.py
@@ -17,22 +17,23 @@
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> from libbe import mapfile
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
>>> print bd.bug_from_shortname("b").status
closed
- >>> execute (["b"], test=True)
+ >>> execute (["b"], manipulate_encodings=False)
Removed bug b
>>> bd._clear_bugs()
>>> try:
... bd.bug_from_shortname("b")
- ... except KeyError:
+ ... except bugdir.NoBugMatches:
... print "Bug not found"
Bug not found
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -40,8 +41,9 @@ def execute(args, test=False):
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=not test)
- bug = bd.bug_from_shortname(args[0])
+ 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
diff --git a/becommands/set.py b/becommands/set.py
index 0c0862f..f7e68d3 100644
--- a/becommands/set.py
+++ b/becommands/set.py
@@ -19,7 +19,7 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Change tree settings"""
import textwrap
-from libbe import cmdutil, bugdir, rcs, settings_object
+from libbe import cmdutil, bugdir, vcs, settings_object
__desc__ = __doc__
def _value_string(bd, setting):
@@ -32,26 +32,28 @@ def _value_string(bd, setting):
val = None
return str(val)
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
- >>> execute(["target"], test=True)
+ >>> execute(["target"], manipulate_encodings=False)
None
- >>> execute(["target", "tomorrow"], test=True)
- >>> execute(["target"], test=True)
+ >>> execute(["target", "tomorrow"], manipulate_encodings=False)
+ >>> execute(["target"], manipulate_encodings=False)
tomorrow
- >>> execute(["target", "none"], test=True)
- >>> execute(["target"], test=True)
+ >>> 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=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
if len(args) == 0:
keys = bd.settings_properties
keys.sort()
@@ -85,12 +87,12 @@ def get_bugdir_settings():
set = getattr(bugdir.BugDir, s)
dstr = set.__doc__.strip()
# per-setting comment adjustments
- if s == "rcs_name":
+ 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 rcs_name docstring:\n '%s'" % dstr
+ "Unexpected vcs_name docstring:\n '%s'" % dstr
lines.insert(
0, "The name of the revision control system to use.\n")
dstr = '\n'.join(lines)
diff --git a/becommands/severity.py b/becommands/severity.py
index 65467e3..660586e 100644
--- a/becommands/severity.py
+++ b/becommands/severity.py
@@ -20,27 +20,29 @@
from libbe import cmdutil, bugdir, bug
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
minor
- >>> execute(["a", "wishlist"], test=True)
- >>> execute(["a"], test=True)
+ >>> execute(["a", "wishlist"], manipulate_encodings=False)
+ >>> execute(["a"], manipulate_encodings=False)
wishlist
- >>> execute(["a", "none"], test=True)
+ >>> 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=not test)
- bug = bd.bug_from_shortname(args[0])
+ 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:
diff --git a/becommands/show.py b/becommands/show.py
index e43cfb9..50bd6eb 100644
--- a/becommands/show.py
+++ b/becommands/show.py
@@ -22,12 +22,12 @@ import sys
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
- >>> execute (["a",], test=True) # doctest: +ELLIPSIS
+ >>> execute (["a",], manipulate_encodings=False) # doctest: +ELLIPSIS
ID : a
Short name : a
Severity : minor
@@ -39,7 +39,7 @@ def execute(args, test=False):
Created : ...
Bug A
<BLANKLINE>
- >>> execute (["--xml", "a"], test=True) # doctest: +ELLIPSIS
+ >>> execute (["--xml", "a"], manipulate_encodings=False) # doctest: +ELLIPSIS
<?xml version="1.0" encoding="..." ?>
<bug>
<uuid>a</uuid>
@@ -50,6 +50,7 @@ def execute(args, test=False):
<created>...</created>
<summary>Bug A</summary>
</bug>
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -57,7 +58,8 @@ def execute(args, test=False):
bugid_args={-1: lambda bug : bug.active==True})
if len(args) == 0:
raise cmdutil.UsageError
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ 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:
@@ -72,7 +74,7 @@ def execute(args, test=False):
is_comment = False
if is_comment == True and options.comments == False:
continue
- bug = bd.bug_from_shortname(bugname)
+ bug = cmdutil.bug_from_shortname(bd, bugname)
if is_comment == False:
if options.XML:
print bug.xml(show_comments=options.comments)
diff --git a/becommands/status.py b/becommands/status.py
index edc948d..f315003 100644
--- a/becommands/status.py
+++ b/becommands/status.py
@@ -17,27 +17,29 @@
from libbe import cmdutil, bugdir, bug
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
open
- >>> execute(["a", "closed"], test=True)
- >>> execute(["a"], test=True)
+ >>> execute(["a", "closed"], manipulate_encodings=False)
+ >>> execute(["a"], manipulate_encodings=False)
closed
- >>> execute(["a", "none"], test=True)
+ >>> 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=not test)
- bug = bd.bug_from_shortname(args[0])
+ 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:
@@ -55,7 +57,8 @@ def get_parser():
def help():
try: # See if there are any per-tree status configurations
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=False)
+ 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])
diff --git a/becommands/subscribe.py b/becommands/subscribe.py
new file mode 100644
index 0000000..0a23057
--- /dev/null
+++ b/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/becommands/tag.py b/becommands/tag.py
index 216ffbc..ecd853f 100644
--- a/becommands/tag.py
+++ b/becommands/tag.py
@@ -18,34 +18,34 @@ from libbe import cmdutil, bugdir
import os, copy
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> from libbe import utility
- >>> bd = bugdir.simple_bug_dir()
+ >>> 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"], test=True)
+ >>> 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"], test=True)
+ >>> execute(["a", "later"], manipulate_encodings=False)
Tags for a:
GUI
later
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
Tags for a:
GUI
later
- >>> execute(["--list"], test=True)
+ >>> execute(["--list"], manipulate_encodings=False)
GUI
later
- >>> execute(["a", "Alphabetically first"], test=True)
+ >>> execute(["a", "Alphabetically first"], manipulate_encodings=False)
Tags for a:
Alphabetically first
GUI
@@ -57,15 +57,16 @@ def execute(args, test=False):
>>> a.extra_strings = []
>>> print a.extra_strings
[]
- >>> execute(["a"], test=True)
+ >>> 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"], test=True)
+ >>> execute(["a", "Alphabetically first"], manipulate_encodings=False)
Tags for a:
Alphabetically first
- >>> execute(["--remove", "a", "Alphabetically first"], test=True)
+ >>> execute(["--remove", "a", "Alphabetically first"], manipulate_encodings=False)
+ >>> bd.cleanup()
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -78,7 +79,8 @@ def execute(args, test=False):
help()
raise cmdutil.UsageError("Too many arguments.")
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
if options.list:
bd.load_all_bugs()
tags = []
@@ -92,7 +94,7 @@ def execute(args, test=False):
if len(tags) > 0:
print '\n'.join(tags)
return
- bug = bd.bug_from_shortname(args[0])
+ bug = cmdutil.bug_from_shortname(bd, args[0])
if len(args) == 2:
given_tag = args[1]
estrs = bug.extra_strings
diff --git a/becommands/target.py b/becommands/target.py
index 527b16a..7e41451 100644
--- a/becommands/target.py
+++ b/becommands/target.py
@@ -22,21 +22,22 @@
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
- >>> bd = bugdir.simple_bug_dir()
+ >>> bd = bugdir.SimpleBugDir()
>>> os.chdir(bd.root)
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
No target assigned.
- >>> execute(["a", "tomorrow"], test=True)
- >>> execute(["a"], test=True)
+ >>> execute(["a", "tomorrow"], manipulate_encodings=False)
+ >>> execute(["a"], manipulate_encodings=False)
tomorrow
- >>> execute(["--list"], test=True)
+ >>> execute(["--list"], manipulate_encodings=False)
tomorrow
- >>> execute(["a", "none"], test=True)
- >>> execute(["a"], test=True)
+ >>> 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)
@@ -46,14 +47,15 @@ def execute(args, test=False):
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=not test)
+ 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 = bd.bug_from_shortname(args[0])
+ bug = cmdutil.bug_from_shortname(bd, args[0])
if len(args) == 1:
if bug.target is None:
print "No target assigned."