diff options
Diffstat (limited to 'becommands')
-rw-r--r-- | becommands/depend.py | 55 | ||||
-rw-r--r-- | becommands/due.py | 106 | ||||
-rw-r--r-- | becommands/html.py | 4 | ||||
-rw-r--r-- | becommands/list.py | 54 | ||||
-rw-r--r-- | becommands/merge.py | 2 | ||||
-rw-r--r-- | becommands/new.py | 4 | ||||
-rw-r--r-- | becommands/show.py | 3 | ||||
-rw-r--r-- | becommands/target.py | 127 |
8 files changed, 273 insertions, 82 deletions
diff --git a/becommands/depend.py b/becommands/depend.py index f50d693..6336793 100644 --- a/becommands/depend.py +++ b/becommands/depend.py @@ -15,7 +15,7 @@ # 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 +from libbe import cmdutil, bugdir, bug, tree import os, copy __desc__ = __doc__ @@ -69,8 +69,8 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): 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}) + bugid_args={0: lambda _bug : _bug.active==True, + 1: lambda _bug : _bug.active==True}) if options.repair == True: if len(args) > 0: @@ -82,7 +82,6 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): 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) @@ -95,10 +94,17 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): for blockee,blocker in fixed]) return 0 + allowed_status_values = \ + cmdutil.select_values(options.status, bug.status_values) + allowed_severity_values = \ + cmdutil.select_values(options.severity, bug.severity_values) + bugA = cmdutil.bug_from_id(bd, args[0]) if options.tree_depth != None: - dtree = DependencyTree(bd, bugA, options.tree_depth) + dtree = DependencyTree(bd, bugA, options.tree_depth, + allowed_status_values, + allowed_severity_values) if len(dtree.blocked_by_tree()) > 0: print "%s blocked by:" % bugA.uuid for depth,node in dtree.blocked_by_tree().thread(): @@ -122,18 +128,18 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): 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]) + 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]) + 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]) + print '\n'.join(["%s\t%s" % (_bug.uuid, _bug.status) + for _bug in blocks]) else: - print '\n'.join([bug.uuid for bug in blocks]) + 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") @@ -143,6 +149,10 @@ def get_parser(): parser.add_option("-s", "--show-status", action="store_true", dest="show_status", default=False, help="Show status of blocking bugs") + parser.add_option("--status", dest="status", metavar="STATUS", + help="Only show bugs matching the STATUS specifier") + parser.add_option("--severity", dest="severity", metavar="SEVERITY", + help="Only show bugs matching the SEVERITY specifier") 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.") @@ -158,6 +168,15 @@ 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> +The --status and --severity options allow you to either blacklist or +whitelist values, for example + $ be list --status open,assigned +will only follow and print dependencies with open or assigned status. +You select blacklist mode by starting the list with a minus sign, for +example + $ be list --severity -target +which will only follow and print dependencies with non-target severity. + In repair mode, add the missing direction to any one-way links. The "|--" symbol in the repair-mode output is inspired by the @@ -239,7 +258,7 @@ def get_blocks(bugdir, bug): def get_blocked_by(bugdir, bug): """ - Return a list of bugs blocking the given bug blocks. + Return a list of bugs blocking the given bug. """ blocked_by = [] for uuid in _get_blocked_by(bug): @@ -310,10 +329,14 @@ class DependencyTree (object): """ Note: should probably be DependencyDiGraph. """ - def __init__(self, bugdir, root_bug, depth_limit=0): + def __init__(self, bugdir, root_bug, depth_limit=0, + allowed_status_values=None, + allowed_severity_values=None): self.bugdir = bugdir self.root_bug = root_bug self.depth_limit = depth_limit + self.allowed_status_values = allowed_status_values + self.allowed_severity_values = allowed_severity_values def _build_tree(self, child_fn): root = tree.Tree() root.bug = self.root_bug @@ -324,6 +347,12 @@ class DependencyTree (object): if self.depth_limit > 0 and node.depth == self.depth_limit: continue for bug in child_fn(self.bugdir, node.bug): + if self.allowed_status_values != None \ + and not bug.status in self.allowed_status_values: + continue + if self.allowed_severity_values != None \ + and not bug.severity in self.allowed_severity_values: + continue child = tree.Tree() child.bug = bug child.depth = node.depth+1 diff --git a/becommands/due.py b/becommands/due.py new file mode 100644 index 0000000..6f11ad4 --- /dev/null +++ b/becommands/due.py @@ -0,0 +1,106 @@ +# 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. +"""Set bug due dates""" +from libbe import cmdutil, bugdir, utility +__desc__ = __doc__ + +DUE_TAG="DUE:" + +def execute(args, manipulate_encodings=True, restrict_file_access=False): + """ + >>> import os + >>> bd = bugdir.SimpleBugDir() + >>> bd.save() + >>> os.chdir(bd.root) + >>> execute(["a"], manipulate_encodings=False) + No due date assigned. + >>> execute(["a", "Thu, 01 Jan 1970 00:00:00 +0000"], manipulate_encodings=False) + >>> execute(["a"], manipulate_encodings=False) + Thu, 01 Jan 1970 00:00:00 +0000 + >>> execute(["a", "none"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE + >>> execute(["a"], manipulate_encodings=False) + No due date 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): + raise cmdutil.UsageError('Incorrect number of arguments.') + bd = bugdir.BugDir(from_disk=True, + manipulate_encodings=manipulate_encodings) + bug = cmdutil.bug_from_id(bd, args[0]) + if len(args) == 1: + due_time = get_due(bug) + if due_time is None: + print "No due date assigned." + else: + print utility.time_to_str(due_time) + else: + if args[1] == "none": + remove_due(bug) + else: + due_time = utility.str_to_time(args[1]) + set_due(bug, due_time) + +def get_parser(): + parser = cmdutil.CmdOptionParser("be due BUG-ID [DATE]") + return parser + +longhelp=""" +If no DATE is specified, the bug's current due date is printed. If +DATE is specified, it will be assigned to the bug. +""" + +def help(): + return get_parser().help_str() + longhelp + +# internal helper functions + +def _generate_due_string(time): + return "%s%s" % (DUE_TAG, utility.time_to_str(time)) + +def _parse_due_string(string): + assert string.startswith(DUE_TAG) + return utility.str_to_time(string[len(DUE_TAG):]) + +# functions exposed to other modules + +def get_due(bug): + matched = [] + for line in bug.extra_strings: + if line.startswith(DUE_TAG): + matched.append(_parse_due_string(line)) + if len(matched) == 0: + return None + if len(matched) > 1: + raise Exception('Several due dates for %s?:\n %s' + % (bug.uuid, '\n '.join(matched))) + return matched[0] + +def remove_due(bug): + estrs = bug.extra_strings + for due_str in [s for s in estrs if s.startswith(DUE_TAG)]: + estrs.remove(due_str) + bug.extra_strings = estrs # reassign to notice change + +def set_due(bug, time): + remove_due(bug) + estrs = bug.extra_strings + estrs.append(_generate_due_string(time)) + bug.extra_strings = estrs # reassign to notice change diff --git a/becommands/html.py b/becommands/html.py index 622a531..a031188 100644 --- a/becommands/html.py +++ b/becommands/html.py @@ -177,7 +177,7 @@ class HTMLGen (object): 'shortname':self.bd.bug_shortname(bug), 'comment_entries':comment_entries, 'generation_time':self.generation_time} - for attr in ['uuid', 'severity', 'status', 'assigned', 'target', + for attr in ['uuid', 'severity', 'status', 'assigned', 'reporter', 'creator', 'time_string', 'summary']: template_info[attr] = self._escape(getattr(bug, attr)) self._write_file(self.bug_file % template_info, [fullpath]) @@ -272,7 +272,7 @@ class HTMLGen (object): if self.verbose: print "\tCreating bug entry for %s" % self.bd.bug_shortname(bug) template_info = {'shortname':self.bd.bug_shortname(bug)} - for attr in ['uuid', 'severity', 'status', 'assigned', 'target', + for attr in ['uuid', 'severity', 'status', 'assigned', 'reporter', 'creator', 'time_string', 'summary']: template_info[attr] = self._escape(getattr(bug, attr)) bug_entries.append(self.index_bug_entry % template_info) diff --git a/becommands/list.py b/becommands/list.py index 2749228..fa5647b 100644 --- a/becommands/list.py +++ b/becommands/list.py @@ -33,8 +33,7 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): >>> os.chdir(bd.root) >>> execute([], manipulate_encodings=False) a:om: Bug A - >>> execute(["--status", "all"], manipulate_encodings=False) - a:om: Bug A + >>> execute(["--status", "closed"], manipulate_encodings=False) b:cm: Bug B >>> bd.cleanup() """ @@ -60,7 +59,7 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): if options.status == "all": status = bug.status_values else: - status = options.status.split(',') + status = cmdutil.select_values(options.status, bug.status_values) else: status = [] if options.active == True: @@ -78,7 +77,8 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): if options.severity == "all": severity = bug.severity_values else: - severity = options.severity.split(',') + severity = cmdutil.select_values(options.severity, + bug.severity_values) else: severity = [] if options.wishlist == True: @@ -93,7 +93,14 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): if options.assigned == "all": assigned = "all" else: - assigned = options.assigned.split(',') + possible_assignees = [] + for _bug in bd: + if _bug.assigned != None \ + and not _bug.assigned in possible_assignees: + possible_assignees.append(_bug.assigned) + assigned = cmdutil.select_values(options.assigned, + possible_assignees) + print 'assigned', assigned else: assigned = [] if options.mine == True: @@ -103,18 +110,6 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): 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(',')] @@ -125,8 +120,6 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): 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 @@ -167,29 +160,26 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): 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("--status", dest="status", metavar="STATUS", + help="Only show bugs matching the STATUS specifier") + parser.add_option("--severity", dest="severity", metavar="SEVERITY", + help="Only show bugs matching the SEVERITY specifier") 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"), + ("x", "xml", "Dump as XML"), ("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")) + ("m", "mine", "List bugs assigned to you")) for s in bools: attr = s[1].replace('-','_') short = "-%c" % s[0] @@ -216,10 +206,12 @@ 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. +to match all values of the criterion. As with the --status and +--severity options for `be depend`, starting the list with a minus +sign makes your selections a blacklist instead of the default +whitelist. status %s @@ -227,8 +219,6 @@ 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. diff --git a/becommands/merge.py b/becommands/merge.py index 31c781f..8cf7e2f 100644 --- a/becommands/merge.py +++ b/becommands/merge.py @@ -51,7 +51,6 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): Severity : minor Status : open Assigned : - Target : Reporter : Creator : John Doe <jdoe@example.com> Created : ... @@ -96,7 +95,6 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): Severity : minor Status : closed Assigned : - Target : Reporter : Creator : Jane Doe <jdoe@example.com> Created : ... diff --git a/becommands/new.py b/becommands/new.py index 92d61e4..00e8a47 100644 --- a/becommands/new.py +++ b/becommands/new.py @@ -37,8 +37,8 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): True >>> print bug.severity minor - >>> bug.target == None - True + >>> print bug.status + open >>> bd.cleanup() """ parser = get_parser() diff --git a/becommands/show.py b/becommands/show.py index 557c63a..ab1708f 100644 --- a/becommands/show.py +++ b/becommands/show.py @@ -33,7 +33,6 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): Severity : minor Status : open Assigned : - Target : Reporter : Creator : John Doe <jdoe@example.com> Created : ... @@ -171,7 +170,7 @@ def output(ids, bd, as_xml=True, with_comments=True): if as_xml: lines.append(comment.xml(indent=4, shortname=bugname)) else: - lines.append(comment.string(shortname=shortname)) + lines.append(comment.string(shortname=bugname)) if spaces_left > 0: spaces_left -= 1 lines.append('') # add a blank line between bugs/comments diff --git a/becommands/target.py b/becommands/target.py index 9a202b1..fbc4b37 100644 --- a/becommands/target.py +++ b/becommands/target.py @@ -18,13 +18,14 @@ # 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""" +"""Assorted bug target manipulations and queries""" from libbe import cmdutil, bugdir +from becommands import depend __desc__ = __doc__ def execute(args, manipulate_encodings=True, restrict_file_access=False): """ - >>> import os + >>> import os, StringIO, sys >>> bd = bugdir.SimpleBugDir() >>> os.chdir(bd.root) >>> execute(["a"], manipulate_encodings=False) @@ -32,8 +33,19 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): >>> execute(["a", "tomorrow"], manipulate_encodings=False) >>> execute(["a"], manipulate_encodings=False) tomorrow - >>> execute(["--list"], manipulate_encodings=False) + + >>> orig_stdout = sys.stdout + >>> tmp_stdout = StringIO.StringIO() + >>> sys.stdout = tmp_stdout + >>> execute(["--resolve", "tomorrow"], manipulate_encodings=False) + >>> sys.stdout = orig_stdout + >>> output = tmp_stdout.getvalue().strip() + >>> target = bd.bug_from_uuid(output) + >>> print target.summary tomorrow + >>> print target.severity + target + >>> execute(["a", "none"], manipulate_encodings=False) >>> execute(["a"], manipulate_encodings=False) No target assigned. @@ -44,52 +56,109 @@ def execute(args, manipulate_encodings=True, restrict_file_access=False): 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 + if (options.resolve == False and len(args) not in (1, 2)) \ + or (options.resolve == True and len(args) not in (0, 1)): + raise cmdutil.UsageError('Incorrect number of arguments.') 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.uuids()]) - for target in sorted(ts): - if target and isinstance(target,str): - print target + if options.resolve == True: + if len(args) == 0: + summary = None + else: + summary = args[0] + bug = bug_from_target_summary(bd, summary) + if bug == None: + print 'No target assigned.' + else: + print bug.uuid return bug = cmdutil.bug_from_id(bd, args[0]) if len(args) == 1: - if bug.target is None: + target = bug_target(bd, bug) + if target is None: print "No target assigned." else: - print bug.target + print target.summary else: - assert len(args) == 2 if args[1] == "none": - bug.target = None + target = remove_target(bd, bug) else: - bug.target = args[1] + target = add_target(bd, bug, 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") + parser = cmdutil.CmdOptionParser("be target BUG-ID [TARGET]\nor: be target --resolve [TARGET]") + parser.add_option("-r", "--resolve", action="store_true", dest="resolve", + help="Print the UUID for the target bug whose summary matches TARGET. If TARGET is not given, print the UUID of the current bugdir target. If that is not set, don't print anything.", + default=False) return parser longhelp=""" -Show or change a bug's target for fixing. +Assorted bug target manipulations and queries. + +If no target is specified, the bug's current target is printed. If +TARGET is specified, it will be assigned to the bug, creating a new +target bug if necessary. -If no target is specified, the current value is printed. If a target -is specified, it will be assigned to the bug. +Targets are free-form; any text may be specified. They will generally +be milestone names or release numbers. The value "none" can be used +to unset the target. -Targets are freeform; any text may be specified. They will generally be -milestone names or release numbers. +In the alternative `be target --resolve TARGET` form, print the UUID +of the target-bug with summary TARGET. If target is not given, return +use the bugdir's current target (see `be set`). -The value "none" can be used to unset the target. +If you want to list all bugs blocking the current target, try + $ be depend --status -closed,fixed,wontfix --severity -target \ + $(be target --resolve) -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'. +If you want to set the current bugdir target by summary (rather than +by UUID), try + $ be set target $(be target --resolve SUMMARY) """ def help(): return get_parser().help_str() + longhelp + +def bug_from_target_summary(bugdir, summary=None): + if summary == None: + if bugdir.target == None: + return None + else: + return bugdir.bug_from_uuid(bugdir.target) + matched = [] + for uuid in bugdir.uuids(): + bug = bugdir.bug_from_uuid(uuid) + if bug.severity == 'target' and bug.summary == summary: + matched.append(bug) + if len(matched) == 0: + return None + if len(matched) > 1: + raise Exception('Several targets with same summary: %s' + % '\n '.join([bug.uuid for bug in matched])) + return matched[0] + +def bug_target(bugdir, bug): + matched = [] + for blocked in depend.get_blocks(bugdir, bug): + if blocked.severity == 'target': + matched.append(blocked) + if len(matched) == 0: + return None + if len(matched) > 1: + raise Exception('This bug (%s) blocks several targets: %s' + % (bug.uuid, + '\n '.join([b.uuid for b in matched]))) + return matched[0] + +def remove_target(bugdir, bug): + target = bug_target(bugdir, bug) + depend.remove_block(target, bug) + return target + +def add_target(bugdir, bug, summary): + target = bug_from_target_summary(bugdir, summary) + if target == None: + target = bugdir.new_bug(summary=summary) + target.severity = 'target' + depend.add_block(target, bug) + return target |