diff options
Diffstat (limited to 'libbe')
-rw-r--r-- | libbe/arch.py | 1 | ||||
-rw-r--r-- | libbe/beuuid.py | 1 | ||||
-rw-r--r-- | libbe/bug.py | 17 | ||||
-rw-r--r-- | libbe/bugdir.py | 3 | ||||
-rw-r--r-- | libbe/bzr.py | 1 | ||||
-rw-r--r-- | libbe/cmdutil.py | 67 | ||||
-rw-r--r-- | libbe/comment.py | 152 | ||||
-rw-r--r-- | libbe/config.py | 1 | ||||
-rw-r--r-- | libbe/diff.py | 1 | ||||
-rw-r--r-- | libbe/editor.py | 1 | ||||
-rw-r--r-- | libbe/encoding.py | 1 | ||||
-rw-r--r-- | libbe/mapfile.py | 5 | ||||
-rw-r--r-- | libbe/plugin.py | 1 | ||||
-rw-r--r-- | libbe/rcs.py | 1 | ||||
-rw-r--r-- | libbe/settings_object.py | 26 | ||||
-rw-r--r-- | libbe/utility.py | 23 |
16 files changed, 216 insertions, 86 deletions
diff --git a/libbe/arch.py b/libbe/arch.py index 3051b34..8f7603b 100644 --- a/libbe/arch.py +++ b/libbe/arch.py @@ -2,7 +2,6 @@ # Ben Finney <ben+python@benfinney.id.au> # James Rowe <jnrowe@ukfsn.org> # W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/beuuid.py b/libbe/beuuid.py index de67cb7..020ea9f 100644 --- a/libbe/beuuid.py +++ b/libbe/beuuid.py @@ -1,5 +1,4 @@ # Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/bug.py b/libbe/bug.py index dfa49f2..8cb8a0a 100644 --- a/libbe/bug.py +++ b/libbe/bug.py @@ -1,7 +1,6 @@ # Copyright (C) 2008-2009 Chris Ball <cjb@laptop.org> # Thomas Habets <thomas@habets.pp.se> # W. Trevor King <wking@drexel.edu> -# <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 @@ -68,7 +67,7 @@ def load_severities(severity_def): global severity_values global severity_description global severity_index - if severity_def == settings_object.EMPTY: + if severity_def == None: return severity_values = tuple([val for val,description in severity_def]) severity_description = dict(severity_def) @@ -88,9 +87,9 @@ def load_status(active_status_def, inactive_status_def): global status_values global status_description global status_index - if active_status_def == settings_object.EMPTY: + if active_status_def == None: active_status_def = globals()["active_status_def"] - if inactive_status_def == settings_object.EMPTY: + if inactive_status_def == None: inactive_status_def = globals()["inactive_status_def"] active_status_values = tuple([val for val,description in active_status_def]) inactive_status_values = tuple([val for val,description in inactive_status_def]) @@ -178,7 +177,7 @@ class Bug(settings_object.SavedSettingsObject): def time_string(): return {} def _get_time(self): - if self.time_string == None or self.time_string == settings_object.EMPTY: + if self.time_string in [None, settings_object.EMPTY]: return None return utility.str_to_time(self.time_string) def _set_time(self, value): @@ -255,7 +254,7 @@ class Bug(settings_object.SavedSettingsObject): def _setting_attr_string(self, setting): value = getattr(self, setting) - if value == settings_object.EMPTY: + if value in [None, settings_object.EMPTY]: return "" else: return str(value) @@ -283,7 +282,7 @@ class Bug(settings_object.SavedSettingsObject): ("summary", self.summary)] ret = '<bug>\n' for (k,v) in info: - if v is not settings_object.EMPTY: + if v is not None: ret += ' <%s>%s</%s>\n' % (k,xml.sax.saxutils.escape(v),k) for estr in self.extra_strings: ret += ' <extra-string>%s</extra-string>\n' % estr @@ -477,8 +476,8 @@ def cmp_attr(bug_1, bug_2, attr, invert=False): return 1 val_1 = getattr(bug_1, attr) val_2 = getattr(bug_2, attr) - if val_1 == settings_object.EMPTY: val_1 = None - if val_2 == settings_object.EMPTY: val_2 = None + if val_1 == None: val_1 = None + if val_2 == None: val_2 = None if invert == True : return -cmp(val_1, val_2) diff --git a/libbe/bugdir.py b/libbe/bugdir.py index d4a39cb..fed9aa3 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -3,7 +3,6 @@ # Chris Ball <cjb@laptop.org> # Oleg Romanyshyn <oromanyshyn@panoramicfeedback.com> # W. Trevor King <wking@drexel.edu> -# <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 @@ -136,7 +135,7 @@ class BugDir (list, settings_object.SavedSettingsObject): return settings_object.versioned_property(**kwargs) @_versioned_property(name="target", - doc="The current project development target") + doc="The current project development target.") def target(): return {} def _guess_encoding(self): diff --git a/libbe/bzr.py b/libbe/bzr.py index d73392a..56a1648 100644 --- a/libbe/bzr.py +++ b/libbe/bzr.py @@ -2,7 +2,6 @@ # Ben Finney <ben+python@benfinney.id.au> # Marien Zwart <marienz@gentoo.org> # W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py index edc6442..7589241 100644 --- a/libbe/cmdutil.py +++ b/libbe/cmdutil.py @@ -1,7 +1,6 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # Oleg Romanyshyn <oromanyshyn@panoramicfeedback.com> # W. Trevor King <wking@drexel.edu> -# <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 @@ -16,6 +15,7 @@ # 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 glob import optparse import os from textwrap import TextWrapper @@ -32,10 +32,10 @@ class UserError(Exception): def __init__(self, msg): Exception.__init__(self, msg) -class UserErrorWrap(UserError): - def __init__(self, exception): - UserError.__init__(self, str(exception)) - self.exception = exception +class UnknownCommand(UserError): + def __init__(self, cmd): + Exception.__init__(self, "Unknown command '%s'" % cmd) + self.cmd = cmd class UsageError(Exception): pass @@ -56,24 +56,27 @@ def iter_commands(): def get_command(command_name): """Retrieves the module for a user command - >>> get_command("asdf") - Traceback (most recent call last): - UserError: Unknown command asdf + >>> try: + ... get_command("asdf") + ... except UnknownCommand, e: + ... print e + Unknown command 'asdf' >>> repr(get_command("list")).startswith("<module 'becommands.list' from ") True """ cmd = plugin.get_plugin("becommands", command_name.replace("-", "_")) if cmd is None: - raise UserError("Unknown command %s" % command_name) + raise UnknownCommand(command_name) return cmd def execute(cmd, args): enc = encoding.get_encoding() - get_command(cmd).execute([a.decode(enc) for a in args]) + cmd = get_command(cmd) + cmd.execute([a.decode(enc) for a in args]) return 0 -def help(cmd=None): +def help(cmd=None, parser=None): if cmd != None: return get_command(cmd).help() else: @@ -82,17 +85,15 @@ def help(cmd=None): cmdlist.append((name, module.__desc__)) longest_cmd_len = max([len(name) for name,desc in cmdlist]) ret = ["Bugs Everywhere - Distributed bug tracking", - "", - "usage: be [command] [command_options ...] [command_args ...]", - "or: be help", - "or: be help [command]", - "", - "Supported commands"] + "", "Supported commands"] for name, desc in cmdlist: numExtraSpaces = longest_cmd_len-len(name) ret.append("be %s%*s %s" % (name, numExtraSpaces, "", desc)) - - return "\n".join(ret) + ret.extend(["", "Run", " be help [command]", "for more information."]) + longhelp = "\n".join(ret) + if parser == None: + return longhelp + return parser.help_str() + "\n" + longhelp def completions(cmd): parser = get_command(cmd).get_parser() @@ -106,6 +107,13 @@ def raise_get_help(option, opt, value, parser): def raise_get_completions(option, opt, value, parser): print "got completion arg" + if hasattr(parser, "command") and parser.command == "be": + comps = [] + for command, module in iter_commands(): + comps.append(command) + for opt in parser.option_list: + comps.append(opt.get_opt_string()) + raise GetCompletions(comps) raise GetCompletions(completions(sys.argv[1])) class CmdOptionParser(optparse.OptionParser): @@ -141,7 +149,7 @@ def option_value_pairs(options, parser): def default_complete(options, args, parser, bugid_args={}): """ - A dud complete implementation for becommands to that the + A dud complete implementation for becommands so that the --complete argument doesn't cause any problems. Use this until you've set up a command-specific complete function. @@ -149,15 +157,25 @@ def default_complete(options, args, parser, bugid_args={}): arguments taking bug shortnames and the values are functions for filtering, since that's a common enough operation. e.g. for "be open [options] BUGID" - bugid_args = {0: lambda bug : bug.active == False} + bugid_args = {0: lambda bug : bug.active == False} + A positional argument of -1 specifies all remaining arguments + (e.g in the case of "be show BUGID BUGID ..."). """ for option,value in option_value_pairs(options, parser): if value == "--complete": raise cmdutil.GetCompletions() + if len(bugid_args.keys()) > 0: + max_pos_arg = max(bugid_args.keys()) + else: + max_pos_arg = -1 for pos,value in enumerate(args): if value == "--complete": + filter = None if pos in bugid_args: filter = bugid_args[pos] + if pos > max_pos_arg and -1 in bugid_args: + filter = bugid_args[-1] + if filter != None: bugshortnames = [] try: bd = bugdir.BugDir(from_disk=True, @@ -170,6 +188,13 @@ def default_complete(options, args, parser, bugid_args={}): raise GetCompletions(bugshortnames) raise GetCompletions() +def complete_path(path): + """List possible path completions for path.""" + comps = glob.glob(path+"*") + glob.glob(path+"/*") + if len(comps) == 1 and os.path.isdir(comps[0]): + comps.extend(glob.glob(comps[0]+"/*")) + return comps + def underlined(instring): """Produces a version of a string that is underlined with '=' diff --git a/libbe/comment.py b/libbe/comment.py index 9d4f744..68deaf3 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -2,7 +2,6 @@ # Copyright (C) 2008-2009 Chris Ball <cjb@laptop.org> # Thomas Habets <thomas@habets.pp.se> # W. Trevor King <wking@drexel.edu> -# <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 @@ -18,12 +17,17 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA -import email.mime.base, email.encoders +import base64 import os import os.path +import sys import time +import types +try: # import core module, Python >= 2.5 + from xml.etree import ElementTree +except ImportError: # look for non-core module + from elementtree import ElementTree import xml.sax.saxutils -import textwrap import doctest from beuuid import uuid_gen @@ -43,16 +47,33 @@ class InvalidShortname(KeyError): self.shortname = shortname self.shortnames = shortnames +class InvalidXML(ValueError): + def __init__(self, element, comment): + msg = "Invalid comment xml: %s\n %s\n" \ + % (comment, ElementTree.tostring(element)) + ValueError.__init__(self, msg) + self.element = element + self.comment = comment + +class MissingReference(ValueError): + def __init__(self, comment): + msg = "Missing reference to %s" % (comment.in_reply_to) + ValueError.__init__(self, msg) + self.reference = comment.in_reply_to + self.comment = comment INVALID_UUID = "!!~~\n INVALID-UUID \n~~!!" -def _list_to_root(comments, bug): +def list_to_root(comments, bug, root=None, + ignore_missing_references=False): """ - Convert a raw list of comments to single (dummy) root comment. We - use a dummy root comment, because there can be several comment - threads rooted on the same parent bug. To simplify comment - interaction, we condense these threads into a single thread with a - Comment dummy root. + Convert a raw list of comments to single root comment. We use a + dummy root comment by default, because there can be several + comment threads rooted on the same parent bug. To simplify + comment interaction, we condense these threads into a single + thread with a Comment dummy root. Can also be used to append + a list of subcomments to a non-dummy root comment, so long as + all the new comments are descendants of the root comment. No Comment method should use the dummy comment. """ @@ -61,17 +82,34 @@ def _list_to_root(comments, bug): for comment in comments: assert comment.uuid != None uuid_map[comment.uuid] = comment + for comment in comments: + if comment.alt_id != None and comment.alt_id not in uuid_map: + uuid_map[comment.alt_id] = comment + if root == None: + root = Comment(bug, uuid=INVALID_UUID) + else: + uuid_map[root.uuid] = root for comm in comments: + if comm.in_reply_to == INVALID_UUID: + comm.in_reply_to = None rep = comm.in_reply_to - if rep == None or rep == settings_object.EMPTY or rep == bug.uuid: + if rep == None or rep == bug.uuid: root_comments.append(comm) else: parentUUID = comm.in_reply_to - parent = uuid_map[parentUUID] - parent.add_reply(comm) - dummy_root = Comment(bug, uuid=INVALID_UUID) - dummy_root.extend(root_comments) - return dummy_root + try: + parent = uuid_map[parentUUID] + parent.add_reply(comm) + except KeyError, e: + if ignore_missing_references == True: + print >> sys.stderr, \ + "Ignoring missing reference to %s" % parentUUID + comm.in_reply_to = None + root_comments.append(comm) + else: + raise MissingReference(comm) + root.extend(root_comments) + return root def loadComments(bug, load_full=False): """ @@ -90,7 +128,7 @@ def loadComments(bug, load_full=False): comm.load_settings() dummy = comm.body # force the body to load comments.append(comm) - return _list_to_root(comments, bug) + return list_to_root(comments, bug) def saveComments(bug): path = bug.get_path("comments") @@ -122,6 +160,10 @@ class Comment(Tree, settings_object.SavedSettingsObject): kwargs["required_saved_properties"]=required_saved_properties return settings_object.versioned_property(**kwargs) + @_versioned_property(name="Alt-id", + doc="Alternate ID for linking imported comments. Internally comments are linked (via In-reply-to) to the parent's UUID. However, these UUIDs are generated internally, so Alt-id is provided as a user-controlled linking target.") + def alt_id(): return {} + @_versioned_property(name="From", doc="The author of the comment") def From(): return {} @@ -217,7 +259,7 @@ class Comment(Tree, settings_object.SavedSettingsObject): def _setting_attr_string(self, setting): value = getattr(self, setting) - if value == settings_object.EMPTY: + if value in [None, settings_object.EMPTY]: return "" else: return str(value) @@ -248,8 +290,9 @@ class Comment(Tree, settings_object.SavedSettingsObject): msg = email.mime.base.MIMEBase(maintype, subtype) msg.set_payload(self.body or "") email.encoders.encode_base64(msg) - body = msg.as_string() + body = base64.encodestring(self.body or "") info = [("uuid", self.uuid), + ("alt-id", self.alt_id), ("short-name", shortname), ("in-reply-to", self.in_reply_to), ("from", self._setting_attr_string("From")), @@ -258,13 +301,77 @@ class Comment(Tree, settings_object.SavedSettingsObject): ("body", body)] lines = ["<comment>"] for (k,v) in info: - if v not in [settings_object.EMPTY, None]: + if v != None: lines.append(' <%s>%s</%s>' % (k,xml.sax.saxutils.escape(v),k)) lines.append("</comment>") istring = ' '*indent sep = '\n' + istring return istring + sep.join(lines).rstrip('\n') + def from_xml(self, xml_string, verbose=True): + """ + Note: If alt-id is not given, translates any <uuid> fields to + <alt-id> fields. + >>> commA = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n") + >>> commA.uuid = "0123" + >>> commA.time_string = "Thu, 01 Jan 1970 00:00:00 +0000" + >>> xml = commA.xml(shortname="com-1") + >>> commB = Comment() + >>> commB.from_xml(xml) + >>> attrs=['uuid','alt_id','in_reply_to','From','time_string','content_type','body'] + >>> for attr in attrs: # doctest: +ELLIPSIS + ... if getattr(commB, attr) != getattr(commA, attr): + ... estr = "Mismatch on %s: '%s' should be '%s'" + ... args = (attr, getattr(commB, attr), getattr(commA, attr)) + ... print estr % args + Mismatch on uuid: '...' should be '0123' + Mismatch on alt_id: '0123' should be 'None' + >>> print commB.alt_id + 0123 + >>> commA.From + >>> commB.From + """ + if type(xml_string) == types.UnicodeType: + xml_string = xml_string.strip().encode("unicode_escape") + comment = ElementTree.XML(xml_string) + if comment.tag != "comment": + raise InvalidXML(comment, "root element must be <comment>") + tags=['uuid','alt-id','in-reply-to','from','date','content-type','body'] + uuid = None + body = None + for child in comment.getchildren(): + if child.tag == "short-name": + pass + elif child.tag in tags: + if child.text == None or len(child.text) == 0: + text = settings_object.EMPTY + else: + text = xml.sax.saxutils.unescape(child.text) + text = unicode(text).decode("unicode_escape").strip() + if child.tag == "uuid": + uuid = text + continue # don't set the bug's uuid tag. + if child.tag == "body": + body = text + continue # don't set the bug's body yet. + elif child.tag == 'from': + attr_name = "From" + elif child.tag == 'date': + attr_name = 'time_string' + else: + attr_name = child.tag.replace('-','_') + setattr(self, attr_name, text) + elif verbose == True: + print >> sys.stderr, "Ignoring unknown tag %s in %s" \ + % (child.tag, comment.tag) + if self.alt_id == None and uuid not in [None, self.uuid]: + self.alt_id = uuid + if body != None: + if self.content_type.startswith("text/"): + self.body = body+"\n" # restore trailing newline + else: + self.body = base64.decodestring(body) + def string(self, indent=0, shortname=None): """ >>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n") @@ -287,13 +394,10 @@ class Comment(Tree, settings_object.SavedSettingsObject): lines.append("From: %s" % (self._setting_attr_string("From"))) lines.append("Date: %s" % self.time_string) lines.append("") - #lines.append(textwrap.fill(self.body or "", - # width=(79-indent))) if self.content_type.startswith("text/"): lines.extend((self.body or "").splitlines()) else: lines.append("Content type %s not printable. Try XML output instead" % self.content_type) - # some comments shouldn't be wrapped... istring = ' '*indent sep = '\n' + istring @@ -335,9 +439,6 @@ class Comment(Tree, settings_object.SavedSettingsObject): def save(self): assert self.body != None, "Can't save blank comment" - #if self.in_reply_to == None: - # raise Exception, str(self)+'\n'+str(self.settings)+'\n'+str(self._settings_loaded) - #assert self.in_reply_to != None, "Comment must be a reply to something" self.save_settings() self._set_comment_body(new=self.body, force=True) @@ -362,7 +463,6 @@ class Comment(Tree, settings_object.SavedSettingsObject): """ reply = Comment(self.bug, body=body) self.add_reply(reply) - #raise Exception, "new reply added (%s),\n%s\n%s\n\t--%s--" % (body, self, reply, reply.in_reply_to) return reply def string_thread(self, string_method_name="string", name_map={}, diff --git a/libbe/config.py b/libbe/config.py index 7f600a5..fafb4f0 100644 --- a/libbe/config.py +++ b/libbe/config.py @@ -1,6 +1,5 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/diff.py b/libbe/diff.py index a349e14..13efc9f 100644 --- a/libbe/diff.py +++ b/libbe/diff.py @@ -1,6 +1,5 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/editor.py b/libbe/editor.py index 6638ed9..4bab0fa 100644 --- a/libbe/editor.py +++ b/libbe/editor.py @@ -1,6 +1,5 @@ # Bugs Everywhere, a distributed bugtracker # Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/encoding.py b/libbe/encoding.py index bdac98e..84c360a 100644 --- a/libbe/encoding.py +++ b/libbe/encoding.py @@ -1,6 +1,5 @@ # Bugs Everywhere, a distributed bugtracker # Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/mapfile.py b/libbe/mapfile.py index 9ff2215..b183bfe 100644 --- a/libbe/mapfile.py +++ b/libbe/mapfile.py @@ -1,6 +1,5 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # W. Trevor King <wking@drexel.edu> -# <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 @@ -68,9 +67,9 @@ def generate(map): assert(':' not in key) assert(len(key) > 0) except AssertionError: - raise IllegalKey(key.encode('string_escape')) + raise IllegalKey(unicode(key).encode('unicode_escape')) if "\n" in map[key]: - raise IllegalValue(map[key].encode('string_escape')) + raise IllegalValue(unicode(map[key]).encode('unicode_escape')) lines = [] for key in keys: diff --git a/libbe/plugin.py b/libbe/plugin.py index 3c7c753..a21ba91 100644 --- a/libbe/plugin.py +++ b/libbe/plugin.py @@ -1,7 +1,6 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # Marien Zwart <marienz@gentoo.org> # W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/rcs.py b/libbe/rcs.py index 2c416f4..844920a 100644 --- a/libbe/rcs.py +++ b/libbe/rcs.py @@ -3,7 +3,6 @@ # Ben Finney <ben+python@benfinney.id.au> # Chris Ball <cjb@laptop.org> # W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/settings_object.py b/libbe/settings_object.py index 7326d1b..1dadd0a 100644 --- a/libbe/settings_object.py +++ b/libbe/settings_object.py @@ -125,17 +125,17 @@ def versioned_property(name, doc, required_saved_properties.append(name) def decorator(funcs): fulldoc = doc - if default != None: + if default != None or generator == None: defaulting = defaulting_property(default=default, null=EMPTY, default_mutable=mutable) - fulldoc += "\n\nThis property defaults to %s" % default + fulldoc += "\n\nThis property defaults to %s." % default if generator != None: cached = cached_property(generator=generator, initVal=EMPTY, mutable=mutable) - fulldoc += "\n\nThis property is generated with %s" % generator + fulldoc += "\n\nThis property is generated with %s." % generator if check_fn != None: fn_checked = fn_checked_property(value_allowed_fn=check_fn) - fulldoc += "\n\nThis property is checked with %s" % check_fn + fulldoc += "\n\nThis property is checked with %s." % check_fn if allowed != None: checked = checked_property(allowed=allowed) fulldoc += "\n\nThe allowed values for this property are: %s." \ @@ -145,7 +145,7 @@ def versioned_property(name, doc, settings = settings_property(name=name, null=UNPRIMED) docp = doc_property(doc=fulldoc) deco = hooked(primed(settings(docp(funcs)))) - if default != None: + if default != None or generator == None: deco = defaulting(deco) if generator != None: deco = cached(deco) @@ -235,7 +235,7 @@ class SavedSettingsObjectTests(unittest.TestCase): # access missing setting self.failUnless(t._settings_loaded == False, t._settings_loaded) self.failUnless(len(t.settings) == 0, len(t.settings)) - self.failUnless(t.content_type == EMPTY, t.content_type) + self.failUnless(t.content_type == None, t.content_type) # accessing t.content_type triggers the priming, which runs # t._setup_saved_settings, which fills out t.settings with # EMPTY data. t._settings_loaded is still false though, since @@ -250,16 +250,16 @@ class SavedSettingsObjectTests(unittest.TestCase): self.failUnless(t._settings_loaded == True, t._settings_loaded) self.failUnless(t.settings["Content-type"] == EMPTY, t.settings["Content-type"]) - self.failUnless(t.content_type == EMPTY, t.content_type) + self.failUnless(t.content_type == None, t.content_type) self.failUnless(len(t.settings) == 1, len(t.settings)) self.failUnless(t.settings["Content-type"] == EMPTY, t.settings["Content-type"]) # now we set a value - t.content_type = None - self.failUnless(t.settings["Content-type"] == None, + t.content_type = 5 + self.failUnless(t.settings["Content-type"] == 5, t.settings["Content-type"]) - self.failUnless(t.content_type == None, t.content_type) - self.failUnless(t.settings["Content-type"] == None, + self.failUnless(t.content_type == 5, t.content_type) + self.failUnless(t.settings["Content-type"] == 5, t.settings["Content-type"]) # now we set another value t.content_type = "text/plain" @@ -273,7 +273,7 @@ class SavedSettingsObjectTests(unittest.TestCase): self.failUnless(t._settings_loaded == True, t._settings_loaded) self.failUnless(t.settings["Content-type"] == EMPTY, t.settings["Content-type"]) - self.failUnless(t.content_type == EMPTY, t.content_type) + self.failUnless(t.content_type == None, t.content_type) self.failUnless(len(t.settings) == 1, len(t.settings)) self.failUnless(t.settings["Content-type"] == EMPTY, t.settings["Content-type"]) @@ -376,7 +376,7 @@ class SavedSettingsObjectTests(unittest.TestCase): t.load_settings() self.failUnless(SAVES == [], SAVES) self.failUnless(t._settings_loaded == True, t._settings_loaded) - self.failUnless(t.list_type == EMPTY, t.list_type) + self.failUnless(t.list_type == None, t.list_type) self.failUnless(SAVES == [ "'None' -> '<class 'libbe.settings_object.EMPTY'>'" ], SAVES) diff --git a/libbe/utility.py b/libbe/utility.py index 8a0f318..e16b94a 100644 --- a/libbe/utility.py +++ b/libbe/utility.py @@ -1,6 +1,5 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # W. Trevor King <wking@drexel.edu> -# <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 @@ -21,6 +20,7 @@ import os import shutil import tempfile import time +import types import doctest @@ -77,17 +77,34 @@ def time_to_str(time_val): return time.strftime(RFC_2822_TIME_FMT, time.gmtime(time_val)) def str_to_time(str_time): - """Convert an RFC 2822-fomatted string into a time falue. + """Convert an RFC 2822-fomatted string into a time value. >>> str_to_time("Thu, 01 Jan 1970 00:00:00 +0000") 0 >>> q = time.time() >>> str_to_time(time_to_str(q)) == int(q) True + >>> str_to_time("Thu, 01 Jan 1970 00:00:00 -1000") + 36000 """ - return calendar.timegm(time.strptime(str_time, RFC_2822_TIME_FMT)) + timezone_str = str_time[-5:] + if timezone_str != "+0000": + str_time = str_time.replace(timezone_str, "+0000") + time_val = calendar.timegm(time.strptime(str_time, RFC_2822_TIME_FMT)) + timesign = -int(timezone_str[0]+"1") # "+" -> time_val ahead of GMT + timezone_tuple = time.strptime(timezone_str[1:], "%H%M") + timezone = timezone_tuple.tm_hour*3600 + timezone_tuple.tm_min*60 + return time_val + timesign*timezone def handy_time(time_val): return time.strftime("%a, %d %b %Y %H:%M", time.localtime(time_val)) +def time_to_gmtime(str_time): + """Convert an RFC 2822-fomatted string to a GMT string. + >>> time_to_gmtime("Thu, 01 Jan 1970 00:00:00 -1000") + 'Thu, 01 Jan 1970 10:00:00 +0000' + """ + time_val = str_to_time(str_time) + return time_to_str(time_val) + suite = doctest.DocTestSuite() |