Comment should probably have it's own file too...
I also tried to clean up the interface for setting status and
severity. Both attributes involve selecting strings from predefined
lists. The lists of valid strings (and descriptions of each string)
are now defined in bug.py. The bug.py lists are then used to generate
appropriate help strings in becommands/status.py and severity.py.
This should make it easier to keep the help strings in synch with the
validation information.
The original status strings weren't documented, and I didn't know what
they all ment, so I elimanted some of them. 'in-progress' and
'disabled' are no longer with us. Of course, it would be simple to
add them back in if people don't agree with me on that. Due to the
loss of 'disabled' I had to change the status of two bugs (11e and
597) to 'closed'. I removed becommands/inprogress.py as well. It's
functionality was replaced by the more general status.py command,
which mimics the severity.py command.
-status=disabled
+status=closed
-status=disabled
+status=closed
import sys
import os
import becommands.severity
+import becommands.status
import becommands.list
import becommands.show
import becommands.set_root
import becommands.new
import becommands.close
import becommands.open
-import becommands.inprogress
__doc__ = """Bugs Everywhere - Distributed bug tracking
Supported becommands
close: close a bug
open: re-open a bug
severity: %s
+ status: %s
Unimplemented becommands
comment: append a comment to a bug
-""" % becommands.severity.__desc__
+""" % (becommands.severity.__desc__,
+ becommands.status.__desc__)
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""Assign an individual or group to fix a bug"""
-from libbe import bugdir, cmdutil, names
+from libbe import cmdutil, names
__desc__ = __doc__
def execute(args):
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""Add a comment to a bug"""
-from libbe import bugdir, cmdutil, names, utility
+from libbe import cmdutil, names, utility
+from libbe.bug import new_comment
import os
def execute(args):
"""
if not body.endswith('\n'):
body+='\n'
- comment = bugdir.new_comment(bug, body)
+ comment = new_comment(bug, body)
if parent_comment is not None:
comment.in_reply_to = parent_comment.uuid
comment.save()
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""Print help for given subcommand"""
-from libbe import bugdir, cmdutil, names, utility
+from libbe import cmdutil, names, utility
def execute(args):
"""
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""List bugs"""
-from libbe import bugdir, cmdutil, names
+from libbe import cmdutil, names
+from libbe.bug import cmp_severity, cmp_time
import os
def execute(args):
options, args = get_parser().parse_args(args)
other_bugs.append(bug)
def list_bugs(cur_bugs, title, no_target=False):
- def cmp_date(bug1, bug2):
- return -cmp(bug1.time, bug2.time)
- cur_bugs.sort(cmp_date)
- cur_bugs.sort(bugdir.cmp_severity)
+ cur_bugs.sort(cmp_time)
+ cur_bugs.sort(cmp_severity)
if len(cur_bugs) > 0:
print cmdutil.underlined(title)
for bug in cur_bugs:
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""Create a new bug"""
-from libbe import bugdir, cmdutil, names, utility
+from libbe import cmdutil, names, utility
+from libbe.bug import new_bug
+
def execute(args):
"""
>>> import os, time
if len(args) != 1:
raise cmdutil.UserError("Please supply a summary message")
dir = cmdutil.bug_tree()
- bug = bugdir.new_bug(dir)
+ bug = new_bug(dir)
bug.summary = args[0]
bug.save()
bugs = (dir.list())
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""Show or change a bug's severity level"""
-from libbe import bugdir
from libbe import cmdutil
+from libbe.bug import severity_values, severity_description
+
__desc__ = __doc__
def execute(args):
elif len(args) == 2:
try:
bug.severity = args[1]
- except bugdir.InvalidValue, e:
+ except ValueError, e:
if e.name != "severity":
raise
raise cmdutil.UserError ("Invalid severity level: %s" % e.value)
parser = cmdutil.CmdOptionParser("be severity bug-id [severity]")
return parser
-longhelp="""
-Show or change a bug's severity level.
+longhelp=["""
+Show or change a bug's severity level.
If no severity is specified, the current value is printed. If a severity level
is specified, it will be assigned to the bug.
Severity levels are:
-wishlist: A feature that could improve usefulness, but not a bug.
- minor: The standard bug level.
- serious: A bug that requires workarounds.
-critical: A bug that prevents some features from working at all.
- fatal: A bug that makes the package unusable.
-"""
+"""]
+longest_severity_len = max([len(s) for s in severity_values])
+for severity in severity_values :
+ description = severity_description[severity]
+ s = "%*s : %s\n" % (longest_severity_len, severity, description)
+ longhelp.append(s)
+longhelp = ''.join(longhelp)
+
def help():
return get_parser().help_str() + longhelp
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""Show a particular bug"""
-from libbe import bugdir, cmdutil, utility
+from libbe import cmdutil, utility
+from libbe.bug import thread_comments
import os
def execute(args):
for c_name, comment in cmdutil.iter_comment_name(bug, unique_name):
name_map[comment.uuid] = c_name
comments.append(comment)
- threaded = bugdir.thread_comments(comments)
+ threaded = thread_comments(comments)
cmdutil.print_threaded_comments(threaded, name_map)
def get_parser():
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-"""Bug fixing in progress"""
+"""Show or change a bug's status"""
from libbe import cmdutil
+from libbe.bug import status_values, status_description
+__desc__ = __doc__
+
def execute(args):
"""
>>> from libbe import tests
>>> import os
>>> dir = tests.simple_bug_dir()
>>> os.chdir(dir.dir)
- >>> dir.get_bug("a").status
- u'open'
>>> execute(["a"])
- >>> dir.get_bug("a").status
- u'in-progress'
+ open
+ >>> execute(["a", "closed"])
+ >>> execute(["a"])
+ closed
+ >>> execute(["a", "none"])
+ Traceback (most recent call last):
+ UserError: Invalid status: none
>>> tests.clean_up()
"""
options, args = get_parser().parse_args(args)
- if len(args) !=1:
- raise cmdutil.UserError("Please specify a bug id.")
+ assert(len(args) in (0, 1, 2))
+ if len(args) == 0:
+ print help()
+ return
bug = cmdutil.get_bug(args[0])
- bug.status = "in-progress"
- bug.save()
+ if len(args) == 1:
+ print bug.status
+ elif len(args) == 2:
+ try:
+ bug.status = args[1]
+ except ValueError, e:
+ if e.name != "status":
+ raise
+ raise cmdutil.UserError ("Invalid status: %s" % e.value)
+ bug.save()
def get_parser():
- parser = cmdutil.CmdOptionParser("be inprogress BUG-ID")
+ parser = cmdutil.CmdOptionParser("be status bug-id [status]")
return parser
-longhelp="""
-Mark a bug as 'in-progress'.
-"""
+longhelp=["""
+Show or change a bug's severity level.
+
+If no severity is specified, the current value is printed. If a severity level
+is specified, it will be assigned to the bug.
+
+Severity levels are:
+"""]
+longest_status_len = max([len(s) for s in status_values])
+for status in status_values :
+ description = status_description[status]
+ s = "%*s : %s\n" % (longest_status_len, status, description)
+ longhelp.append(s)
+longhelp = ''.join(longhelp)
def help():
return get_parser().help_str() + longhelp
import os.path
import errno
from libbe import bugdir, rcs, cmdutil
+from libbe.bug import Bug
def execute(args):
options, args = get_parser().parse_args(args)
for uuid in root.list_uuids():
old_bug = OldBug(root.bugs_path, uuid)
- new_bug = bugdir.Bug(root.bugs_path, None)
+ new_bug = Bug(root.bugs_path, None)
new_bug.uuid = old_bug.uuid
new_bug.summary = old_bug.summary
new_bug.creator = old_bug.creator
--- /dev/null
+# Copyright (C) 2005 Aaron Bentley and Panometrics, Inc.
+# <abentley@panoramicfeedback.com>
+#
+# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+import os
+import os.path
+import errno
+import names
+import mapfile
+import time
+import utility
+from rcs import rcs_by_name
+
+
+### Define and describe valid bug categories
+# Use a tuple of (category, description) tuples since we don't have
+# ordered dicts in Python yet http://www.python.org/dev/peps/pep-0372/
+
+# in order of increasing severity
+severity_level_def = (
+ ("wishlist","A feature that could improve usefullness, but not a bug."),
+ ("minor","The standard bug level."),
+ ("serious","A bug that requires workarounds."),
+ ("critical","A bug that prevents some features from working at all."),
+ ("fatal","A bug that makes the package unusable."))
+
+# in order of increasing resolution
+# roughly following http://www.bugzilla.org/docs/3.2/en/html/lifecycle.html
+active_status_def = (
+ ("unconfirmed","A possible bug which lacks independent existance confirmation."),
+ ("open","A working bug that has not been assigned to a developer."),
+ ("assigned","A working bug that has been assigned to a developer."),
+ ("test","The code has been adjusted, but the fix is still being tested."))
+inactive_status_def = (
+ ("closed", "The bug is no longer relevant."),
+ ("fixed", "The bug should no longer occur."),
+ ("wontfix","It's not a bug, it's a feature."))
+
+
+### Convert the description tuples to more useful formats
+
+severity_values = tuple([val for val,description in severity_level_def])
+severity_description = dict(severity_level_def)
+severity_index = {}
+for i in range(len(severity_values)):
+ severity_index[severity_values[i]] = i
+
+active_status_values = tuple(val for val,description in active_status_def)
+inactive_status_values = tuple(val for val,description in inactive_status_def)
+status_values = active_status_values + inactive_status_values
+status_description = dict(active_status_def+inactive_status_def)
+status_index = {}
+for i in range(len(status_values)):
+ status_index[status_values[i]] = i
+
+
+def checked_property(name, valid):
+ """
+ Provide access to an attribute name, testing for valid values.
+ """
+ def getter(self):
+ value = getattr(self, "_"+name)
+ if value not in valid:
+ raise InvalidValue(name, value)
+ return value
+
+ def setter(self, value):
+ if value not in valid:
+ raise InvalidValue(name, value)
+ return setattr(self, "_"+name, value)
+ return property(getter, setter)
+
+
+class Bug(object):
+ severity = checked_property("severity", severity_values)
+ status = checked_property("status", status_values)
+
+ def __init__(self, path, uuid, rcs_name):
+ self.path = path
+ self.uuid = uuid
+ if uuid is not None:
+ dict = mapfile.map_load(self.get_path("values"))
+ else:
+ dict = {}
+
+ self.rcs_name = rcs_name
+
+ self.summary = dict.get("summary")
+ self.creator = dict.get("creator")
+ self.target = dict.get("target")
+ self.status = dict.get("status", "open")
+ self.severity = dict.get("severity", "minor")
+ self.assigned = dict.get("assigned")
+ self.time = dict.get("time")
+ if self.time is not None:
+ self.time = utility.str_to_time(self.time)
+
+ def __repr__(self):
+ return "Bug(uuid=%r)" % self.uuid
+
+ def get_path(self, file):
+ return os.path.join(self.path, self.uuid, file)
+
+ def _get_active(self):
+ return self.status in active_status_values
+
+ active = property(_get_active)
+
+ def add_attr(self, map, name):
+ value = getattr(self, name)
+ if value is not None:
+ map[name] = value
+
+ def save(self):
+ map = {}
+ self.add_attr(map, "assigned")
+ self.add_attr(map, "summary")
+ self.add_attr(map, "creator")
+ self.add_attr(map, "target")
+ self.add_attr(map, "status")
+ self.add_attr(map, "severity")
+ if self.time is not None:
+ map["time"] = utility.time_to_str(self.time)
+ path = self.get_path("values")
+ mapfile.map_save(rcs_by_name(self.rcs_name), path, map)
+
+ def _get_rcs(self):
+ return rcs_by_name(self.rcs_name)
+
+ rcs = property(_get_rcs)
+
+ def new_comment(self):
+ if not os.path.exists(self.get_path("comments")):
+ self.rcs.mkdir(self.get_path("comments"))
+ comm = Comment(None, self)
+ comm.uuid = names.uuid()
+ return comm
+
+ def get_comment(self, uuid):
+ return Comment(uuid, self)
+
+ def iter_comment_ids(self):
+ path = self.get_path("comments")
+ if not os.path.isdir(path):
+ return
+ try:
+ for uuid in os.listdir(path):
+ if (uuid.startswith('.')):
+ continue
+ yield uuid
+ except OSError, e:
+ if e.errno != errno.ENOENT:
+ raise
+ return
+
+ def list_comments(self):
+ comments = [Comment(id, self) for id in self.iter_comment_ids()]
+ comments.sort(cmp_time)
+ return comments
+
+def new_bug(dir, uuid=None):
+ bug = dir.new_bug(uuid)
+ bug.creator = names.creator()
+ bug.severity = "minor"
+ bug.status = "open"
+ bug.time = time.time()
+ return bug
+
+def new_comment(bug, body=None):
+ comm = bug.new_comment()
+ comm.From = names.creator()
+ comm.date = time.time()
+ comm.body = body
+ return comm
+
+def add_headers(obj, map, names):
+ map_names = {}
+ for name in names:
+ map_names[name] = pyname_to_header(name)
+ add_attrs(obj, map, names, map_names)
+
+def add_attrs(obj, map, names, map_names=None):
+ if map_names is None:
+ map_names = {}
+ for name in names:
+ map_names[name] = name
+
+ for name in names:
+ value = getattr(obj, name)
+ if value is not None:
+ map[map_names[name]] = value
+
+
+class Comment(object):
+ def __init__(self, uuid, bug):
+ object.__init__(self)
+ self.uuid = uuid
+ self.bug = bug
+ if self.uuid is not None and self.bug is not None:
+ map = mapfile.map_load(self.get_path("values"))
+ self.time = utility.str_to_time(map["Date"])
+ self.From = map["From"]
+ self.in_reply_to = map.get("In-reply-to")
+ self.content_type = map.get("Content-type", "text/plain")
+ self.body = file(self.get_path("body")).read().decode("utf-8")
+ else:
+ self.time = None
+ self.From = None
+ self.in_reply_to = None
+ self.content_type = "text/plain"
+ self.body = None
+
+ def save(self):
+ map_file = {"Date": utility.time_to_str(self.time)}
+ add_headers(self, map_file, ("From", "in_reply_to", "content_type"))
+ if not os.path.exists(self.get_path(None)):
+ self.bug.rcs.mkdir(self.get_path(None))
+ mapfile.map_save(self.bug.rcs, self.get_path("values"), map_file)
+ self.bug.rcs.set_file_contents(self.get_path("body"),
+ self.body.encode('utf-8'))
+
+
+ def get_path(self, name):
+ my_dir = os.path.join(self.bug.get_path("comments"), self.uuid)
+ if name is None:
+ return my_dir
+ return os.path.join(my_dir, name)
+
+
+def thread_comments(comments):
+ child_map = {}
+ top_comments = []
+ for comment in comments:
+ child_map[comment.uuid] = []
+ for comment in comments:
+ if comment.in_reply_to is None or comment.in_reply_to not in child_map:
+ top_comments.append(comment)
+ continue
+ child_map[comment.in_reply_to].append(comment)
+
+ def recurse_children(comment):
+ child_list = []
+ for child in child_map[comment.uuid]:
+ child_list.append(recurse_children(child))
+ return (comment, child_list)
+ return [recurse_children(c) for c in top_comments]
+
+def pyname_to_header(name):
+ return name.capitalize().replace('_', '-')
+
+
+
+class MockBug:
+ def __init__(self, attr, value):
+ setattr(self, attr, value)
+
+# the general rule for bug sorting is that "more important" bugs are
+# less than "less important" bugs. This way sorting a list of bugs
+# will put the most important bugs first in the list. When relative
+# importance is unclear, the sorting follows some arbitrary convention
+# (i.e. dictionary order).
+
+def cmp_severity(bug_1, bug_2):
+ """
+ Compare the severity levels of two bugs, with more severe bugs comparing
+ as less.
+
+ >>> attr="severity"
+ >>> cmp_severity(MockBug(attr,"wishlist"), MockBug(attr,"wishlist")) == 0
+ True
+ >>> cmp_severity(MockBug(attr,"wishlist"), MockBug(attr,"minor")) > 0
+ True
+ >>> cmp_severity(MockBug(attr,"critical"), MockBug(attr,"wishlist")) < 0
+ True
+ """
+ return -cmp(severity_index[bug_1.severity], severity_index[bug_2.severity])
+
+def cmp_status(bug_1, bug_2):
+ """
+ Compare the status levels of two bugs, with more 'open' bugs
+ comparing as less.
+
+ >>> attr="status"
+ >>> cmp_status(MockBug(attr,"open"), MockBug(attr,"open")) == 0
+ True
+ >>> cmp_status(MockBug(attr,"open"), MockBug(attr,"closed")) < 0
+ True
+ >>> cmp_status(MockBug(attr,"closed"), MockBug(attr,"open")) > 0
+ True
+ """
+ val_2 = status_index[bug_2.status]
+ return cmp(status_index[bug_1.status], status_index[bug_2.status])
+
+def cmp_attr(bug_1, bug_2, attr, invert=False):
+ """
+ Compare a general attribute between two bugs using the conventional
+ comparison rule for that attribute type. If invert == True, sort
+ *against* that convention.
+ >>> attr="severity"
+ >>> cmp_attr(MockBug(attr,1), MockBug(attr,2), attr, invert=False) < 0
+ True
+ >>> cmp_attr(MockBug(attr,1), MockBug(attr,2), attr, invert=True) > 0
+ True
+ >>> cmp_attr(MockBug(attr,1), MockBug(attr,1), attr) == 0
+ True
+ """
+ if invert == True :
+ return -cmp(getattr(bug_1, attr), getattr(bug_2, attr))
+ else :
+ return cmp(getattr(bug_1, attr), getattr(bug_2, attr))
+
+# alphabetical rankings (a < z)
+cmp_creator = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "creator")
+cmp_assigned = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "assigned")
+# chronological rankings (newer < older)
+cmp_time = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "time", invert=True)
+
+def cmp_full(bug_1, bug_2, cmp_list=(cmp_status,cmp_severity,cmp_assigned,
+ cmp_time,cmp_creator)):
+ for comparison in cmp_list :
+ val = comparison(bug_1, bug_2)
+ if val != 0 :
+ return val
+ return 0
+
+class InvalidValue(ValueError):
+ def __init__(self, name, value):
+ msg = "Cannot assign value %s to %s" % (value, name)
+ Exception.__init__(self, msg)
+ self.name = name
+ self.value = value
import time
import utility
from rcs import rcs_by_name
+from bug import Bug
class NoBugDir(Exception):
def __init__(self, path):
raise
rcs.mkdir(os.path.join(root, "bugs"))
set_version(root, rcs)
- map_save(rcs, os.path.join(root, "settings"), {"rcs_name": rcs.name})
+ mapfile.map_save(rcs,
+ os.path.join(root, "settings"), {"rcs_name": rcs.name})
return BugDir(os.path.join(path, ".be"))
self.dir = dir
self.bugs_path = os.path.join(self.dir, "bugs")
try:
- self.settings = map_load(os.path.join(self.dir, "settings"))
- except NoSuchFile:
+ self.settings = mapfile.map_load(os.path.join(self.dir, "settings"))
+ except mapfile.NoSuchFile:
self.settings = {"rcs_name": "None"}
rcs_name = setting_property("rcs_name", ("None", "bzr", "git", "Arch", "hg"))
target = setting_property("target")
def save_settings(self):
- map_save(self.rcs, os.path.join(self.dir, "settings"), self.settings)
+ mapfile.map_save(self.rcs,
+ os.path.join(self.dir, "settings"), self.settings)
def get_rcs(self):
if self._rcs is not None and self.rcs_name == self._rcs.name:
bug.uuid = uuid
return bug
-class InvalidValue(Exception):
+class InvalidValue(ValueError):
def __init__(self, name, value):
msg = "Cannot assign value %s to %s" % (value, name)
Exception.__init__(self, msg)
self.name = name
self.value = value
-
-
-def checked_property(name, valid):
- def getter(self):
- value = getattr(self, "_"+name)
- if value not in valid:
- raise InvalidValue(name, value)
- return value
-
- def setter(self, value):
- if value not in valid:
- raise InvalidValue(name, value)
- return setattr(self, "_"+name, value)
- return property(getter, setter)
-
-severity_levels = ("wishlist", "minor", "serious", "critical", "fatal")
-active_status = ("open", "in-progress", "waiting", "new", "verified")
-inactive_status = ("closed", "disabled", "fixed", "wontfix", "waiting")
-
-severity_value = {}
-for i in range(len(severity_levels)):
- severity_value[severity_levels[i]] = i
-
-class Bug(object):
- status = checked_property("status", (None,)+active_status+inactive_status)
- severity = checked_property("severity", (None, "wishlist", "minor",
- "serious", "critical", "fatal"))
-
- def __init__(self, path, uuid, rcs_name):
- self.path = path
- self.uuid = uuid
- if uuid is not None:
- dict = map_load(self.get_path("values"))
- else:
- dict = {}
-
- self.rcs_name = rcs_name
-
- self.summary = dict.get("summary")
- self.creator = dict.get("creator")
- self.target = dict.get("target")
- self.status = dict.get("status")
- self.severity = dict.get("severity")
- self.assigned = dict.get("assigned")
- self.time = dict.get("time")
- if self.time is not None:
- self.time = utility.str_to_time(self.time)
-
- def __repr__(self):
- return "Bug(uuid=%r)" % self.uuid
-
- def get_path(self, file):
- return os.path.join(self.path, self.uuid, file)
-
- def _get_active(self):
- return self.status in active_status
-
- active = property(_get_active)
-
- def add_attr(self, map, name):
- value = getattr(self, name)
- if value is not None:
- map[name] = value
-
- def save(self):
- map = {}
- self.add_attr(map, "assigned")
- self.add_attr(map, "summary")
- self.add_attr(map, "creator")
- self.add_attr(map, "target")
- self.add_attr(map, "status")
- self.add_attr(map, "severity")
- if self.time is not None:
- map["time"] = utility.time_to_str(self.time)
- path = self.get_path("values")
- map_save(rcs_by_name(self.rcs_name), path, map)
-
- def _get_rcs(self):
- return rcs_by_name(self.rcs_name)
-
- rcs = property(_get_rcs)
-
- def new_comment(self):
- if not os.path.exists(self.get_path("comments")):
- self.rcs.mkdir(self.get_path("comments"))
- comm = Comment(None, self)
- comm.uuid = names.uuid()
- return comm
-
- def get_comment(self, uuid):
- return Comment(uuid, self)
-
- def iter_comment_ids(self):
- path = self.get_path("comments")
- if not os.path.isdir(path):
- return
- try:
- for uuid in os.listdir(path):
- if (uuid.startswith('.')):
- continue
- yield uuid
- except OSError, e:
- if e.errno != errno.ENOENT:
- raise
- return
-
- def list_comments(self):
- comments = [Comment(id, self) for id in self.iter_comment_ids()]
- comments.sort(cmp_date)
- return comments
-
-def cmp_date(comm1, comm2):
- return cmp(comm1.date, comm2.date)
-
-def new_bug(dir, uuid=None):
- bug = dir.new_bug(uuid)
- bug.creator = names.creator()
- bug.severity = "minor"
- bug.status = "open"
- bug.time = time.time()
- return bug
-
-def new_comment(bug, body=None):
- comm = bug.new_comment()
- comm.From = names.creator()
- comm.date = time.time()
- comm.body = body
- return comm
-
-def add_headers(obj, map, names):
- map_names = {}
- for name in names:
- map_names[name] = pyname_to_header(name)
- add_attrs(obj, map, names, map_names)
-
-def add_attrs(obj, map, names, map_names=None):
- if map_names is None:
- map_names = {}
- for name in names:
- map_names[name] = name
-
- for name in names:
- value = getattr(obj, name)
- if value is not None:
- map[map_names[name]] = value
-
-
-class Comment(object):
- def __init__(self, uuid, bug):
- object.__init__(self)
- self.uuid = uuid
- self.bug = bug
- if self.uuid is not None and self.bug is not None:
- mapfile = map_load(self.get_path("values"))
- self.date = utility.str_to_time(mapfile["Date"])
- self.From = mapfile["From"]
- self.in_reply_to = mapfile.get("In-reply-to")
- self.content_type = mapfile.get("Content-type", "text/plain")
- self.body = file(self.get_path("body")).read().decode("utf-8")
- else:
- self.date = None
- self.From = None
- self.in_reply_to = None
- self.content_type = "text/plain"
- self.body = None
-
- def save(self):
- map_file = {"Date": utility.time_to_str(self.date)}
- add_headers(self, map_file, ("From", "in_reply_to", "content_type"))
- if not os.path.exists(self.get_path(None)):
- self.bug.rcs.mkdir(self.get_path(None))
- map_save(self.bug.rcs, self.get_path("values"), map_file)
- self.bug.rcs.set_file_contents(self.get_path("body"),
- self.body.encode('utf-8'))
-
-
- def get_path(self, name):
- my_dir = os.path.join(self.bug.get_path("comments"), self.uuid)
- if name is None:
- return my_dir
- return os.path.join(my_dir, name)
-
-
-def thread_comments(comments):
- child_map = {}
- top_comments = []
- for comment in comments:
- child_map[comment.uuid] = []
- for comment in comments:
- if comment.in_reply_to is None or comment.in_reply_to not in child_map:
- top_comments.append(comment)
- continue
- child_map[comment.in_reply_to].append(comment)
-
- def recurse_children(comment):
- child_list = []
- for child in child_map[comment.uuid]:
- child_list.append(recurse_children(child))
- return (comment, child_list)
- return [recurse_children(c) for c in top_comments]
-
-
-def pyname_to_header(name):
- return name.capitalize().replace('_', '-')
-
-
-def map_save(rcs, path, map):
- """Save the map as a mapfile to the specified path"""
- add = not os.path.exists(path)
- output = file(path, "wb")
- mapfile.generate(output, map)
- if add:
- rcs.add_id(path)
-
-class NoSuchFile(Exception):
- def __init__(self, pathname):
- Exception.__init__(self, "No such file: %s" % pathname)
-
-
-def map_load(path):
- try:
- return mapfile.parse(file(path, "rb"))
- except IOError, e:
- if e.errno != errno.ENOENT:
- raise e
- raise NoSuchFile(path)
-
-
-class MockBug:
- def __init__(self, severity):
- self.severity = severity
-
-def cmp_severity(bug_1, bug_2):
- """
- Compare the severity levels of two bugs, with more sever bugs comparing
- as less.
-
- >>> cmp_severity(MockBug(None), MockBug(None))
- 0
- >>> cmp_severity(MockBug("wishlist"), MockBug(None)) < 0
- True
- >>> cmp_severity(MockBug(None), MockBug("wishlist")) > 0
- True
- >>> cmp_severity(MockBug("critical"), MockBug("wishlist")) < 0
- True
- """
- val_1 = severity_value.get(bug_1.severity)
- val_2 = severity_value.get(bug_2.severity)
- return -cmp(val_1, val_2)
(This is a user-friendly id, not the comment uuid)
"""
def key(comment):
- return comment.date
+ return comment.time
for num, comment in enumerate(sorted(bug.list_comments(), key=key)):
yield ("%s:%d" % (unique_name, num+1), comment)
print >> s, "--------- Comment ---------"
print >> s, "Name: %s" % name_map[comment.uuid]
print >> s, "From: %s" % comment.From
- print >> s, "Date: %s\n" % utility.time_to_str(comment.date)
+ print >> s, "Date: %s\n" % utility.time_to_str(comment.time)
print >> s, comment.body.rstrip('\n')
s.seek(0)
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+import os.path
+import errno
import utility
+
class IllegalKey(Exception):
def __init__(self, key):
Exception.__init__(self, 'Illegal key "%s"' % key)
result[name] = value
return result
+def map_save(rcs, path, map):
+ """Save the map as a mapfile to the specified path"""
+ add = not os.path.exists(path)
+ output = file(path, "wb")
+ generate(output, map)
+ if add:
+ rcs.add_id(path)
+
+class NoSuchFile(Exception):
+ def __init__(self, pathname):
+ Exception.__init__(self, "No such file: %s" % pathname)
+
+
+def map_load(path):
+ try:
+ return parse(file(path, "rb"))
+ except IOError, e:
+ if e.errno != errno.ENOENT:
+ raise e
+ raise NoSuchFile(path)
+
def split_diff3(this, other, f):
"""Split a file or string with diff3 conflicts into two files.
import shutil
import os
import os.path
-from libbe import bugdir, arch
+from libbe import bugdir, bug, arch
cleanable = []
def clean_up():
global cleanable
def simple_bug_dir():
dir = bug_arch_dir()
- bug_a = bugdir.new_bug(dir, "a")
- bug_b = bugdir.new_bug(dir, "b")
+ bug_a = bug.new_bug(dir, "a")
+ bug_b = bug.new_bug(dir, "b")
bug_b.status = "closed"
bug_a.save()
bug_b.save()