1f2f3cb23ac3

Add real date and time support to the API, and use it in the UI.
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Sat, 10 Oct 2009 16:58:14 -0400
parents f740af183dca
children 44d1d0012482
branches/tags (none)
files review/api.py review/extension_ui.py review/messages.py review/templates.py

Changes

--- a/review/api.py	Sat Oct 10 11:05:05 2009 -0400
+++ b/review/api.py	Sat Oct 10 16:58:14 2009 -0400
@@ -2,12 +2,10 @@
 
 """The API for interacting with code review data."""
 
-import os
+import datetime, operator, os
 import messages, templates
-from datetime import datetime
-from mercurial import cmdutil, hg, patch
+from mercurial import cmdutil, hg, patch, util
 from mercurial.node import hex
-from mercurial.util import sha1
 
 
 class PreexistingDatastore(Exception):
@@ -98,6 +96,26 @@
     data['message'] = message
     return data
 
+def _datetime_from_hgdate(hgdate):
+    """Return a datetime.datetime for the given Mecurial-style date tuple.
+    
+    It will have NO timezone information -- the date and time are what a clock
+    next to the current computer would have read at the instant represented
+    by the Mercurial-style date!
+    
+    """
+    offset = abs(hgdate[1] - util.makedate()[1])
+    later = offset < 0
+    offset = datetime.timedelta(seconds=offset)
+    
+    conversion_format = '%Y-%m-%d %H:%M:%S'
+    bare = util.datestr(hgdate, format=conversion_format)
+    bare_datetime = datetime.datetime.strptime(bare, conversion_format)
+    if later:
+        return bare_datetime + offset
+    else:
+        return bare_datetime - offset
+
 
 def sanitize_path(p, repo=None):
     """Sanitize a (platform-specific) path.
@@ -217,12 +235,16 @@
                 data = _parse_data(self.repo['tip'][fn].data())
                 data['lines'] = data['lines'].split(',')
                 data['lines'] = map(int, filter(None, data['lines']))
+                data['hgdate'] = util.parsedate(data['hgdate'])
                 self.comments.append(ReviewComment(**data))
+            self.comments.sort(key=operator.attrgetter('local_datetime'))
             
             self.signoffs = []
             for fn in signofffns:
                 data = _parse_data(self.repo['tip'][fn].data())
+                data['hgdate'] = util.parsedate(data['hgdate'])
                 self.signoffs.append(ReviewSignoff(**data))
+            self.signoffs.sort(key=operator.attrgetter('local_datetime'))
         else:
             self.comments = []
             self.signoffs = []
@@ -253,7 +275,7 @@
                 raise SignoffExists
             existing[0]._delete(self.ui, self.repo)
         
-        signoff = ReviewSignoff(self.ui.username(), datetime.utcnow(),
+        signoff = ReviewSignoff(self.ui.username(), util.makedate(),
             self.node, opinion, message)
         signoff._commit(self.ui, self.repo)
     
@@ -274,7 +296,7 @@
         if filename and filename not in self.target[self.node].files():
             raise FileNotInChangeset(filename)
         
-        comment = ReviewComment(self.ui.username(), datetime.utcnow(),
+        comment = ReviewComment(self.ui.username(), util.makedate(),
             self.node, filename, lines, message)
         comment._commit(self.ui, self.repo)
     
@@ -402,7 +424,7 @@
             os.mkdir(path)
         
         data = self._render_data()
-        filename = sha1(data).hexdigest()
+        filename = util.sha1(data).hexdigest()
         objectpath = os.path.join(path, filename)
         
         with open(objectpath, 'w') as objectfile:
@@ -418,7 +440,7 @@
             raise CannotDeleteObject
         
         data = self._render_data()
-        filename = sha1(data).hexdigest()
+        filename = util.sha1(data).hexdigest()
         objectpath = os.path.join(repo.root, self.node, self.container, filename)
         
         os.remove(objectpath)
@@ -426,6 +448,9 @@
         cmdutil.commit(ui, repo, _commitfunc, [objectpath],
             { 'message': self.delete_message % self.node, 'addremove': True, })
     
+    @property
+    def local_datetime(self):
+        return _datetime_from_hgdate(self.hgdate)
 
 class ReviewComment(_ReviewObject):
     """A single review comment.
@@ -436,17 +461,26 @@
     
         comment = rcset.comments[0]
         comment.author
-        comment.datetime
+        comment.hgdate
         comment.node
         comment.filename
         comment.lines
         comment.message
+        comment.local_datetime
     
-    Each item is a string, except for datetime (a Python datetime.datetime
-    object) and lines (a list of ints).
+    Each item is a string, except for lines, hgdate, and local_datetime.
+    
+    lines is a list of ints.
+    
+    hgdate is a tuple of (seconds from the epoch, seconds offset from UTC),
+    which is the format Mercurial itself uses internally.
+    
+    local_datetime is a datetime object representing what a clock on the wall
+    next to the current computer would have read at the instant the comment
+    was added.
     
     """
-    def __init__(self, author, datetime, node, filename, lines, message, **extra):
+    def __init__(self, author, hgdate, node, filename, lines, message, **extra):
         """Initialize a ReviewComment.
         
         You shouldn't need to create these directly -- use a ReviewChangeset
@@ -462,7 +496,7 @@
             container='comments', commit_message=messages.COMMIT_COMMENT,
         )
         self.author = author
-        self.datetime = datetime
+        self.hgdate = hgdate
         self.node = node
         self.filename = filename
         self.lines = lines
@@ -471,13 +505,13 @@
     def _render_data(self):
         """Render the data of this comment into a string for writing to disk.
         
-        You probably don't need to call this directly, the add_signoff method
+        You probably don't need to call this directly, the add_comment method
         of a ReviewChangeset will handle it for you.
         
         """
-        datetime = str(self.datetime)
+        rendered_date = util.datestr(self.hgdate)
         lines = ','.join(self.lines)
-        return templates.COMMENT_FILE_TEMPLATE % ( self.author, datetime,
+        return templates.COMMENT_FILE_TEMPLATE % ( self.author, rendered_date,
             self.node, self.filename, lines, self.message )
     
     def __str__(self):
@@ -503,15 +537,22 @@
     
         signoff = rcset.comments[0]
         signoff.author
-        signoff.datetime
+        signoff.hgdate
         signoff.node
         signoff.opinion
         signoff.message
     
-    Each item is a string.
+    Each item is a string, except for hgdate and local_datetime.
+    
+    hgdate is a tuple of (seconds from the epoch, seconds offset from UTC),
+    which is the format Mercurial itself uses internally.
+    
+    local_datetime is a datetime object representing what a clock on the wall
+    next to the current computer would have read at the instant the signoff
+    was added.
     
     """
-    def __init__(self, author, datetime, node, opinion, message, **extra):
+    def __init__(self, author, hgdate, node, opinion, message, **extra):
         """Initialize a ReviewSignoff.
         
         You shouldn't need to create these directly -- use a ReviewChangeset
@@ -528,7 +569,7 @@
             delete_message=messages.DELETE_SIGNOFF,
         )
         self.author = author
-        self.datetime = datetime
+        self.hgdate = hgdate
         self.node = node
         self.opinion = opinion
         self.message = message
@@ -540,7 +581,7 @@
         of a ReviewChangeset will handle it for you.
         
         """
-        datetime = str(self.datetime)
-        return templates.SIGNOFF_FILE_TEMPLATE % ( self.author, datetime,
+        rendered_date = util.datestr(self.hgdate)
+        return templates.SIGNOFF_FILE_TEMPLATE % ( self.author, rendered_date,
             self.node, self.opinion, self.message )
     
--- a/review/extension_ui.py	Sat Oct 10 11:05:05 2009 -0400
+++ b/review/extension_ui.py	Sat Oct 10 16:58:14 2009 -0400
@@ -8,7 +8,7 @@
 import operator, os
 import messages
 from api import *
-from mercurial import util
+from mercurial import templatefilters, util
 from mercurial.node import short
 
 
@@ -99,9 +99,19 @@
     
     def _print_comment(comment, before='', after=''):
         ui.write(before)
-        ui.write(messages.REVIEW_LOG_COMMENT_AUTHOR % comment.author)
+        
+        author = templatefilters.person(comment.author)
+        author_part = messages.REVIEW_LOG_COMMENT_AUTHOR % author
+        
+        age = templatefilters.age(comment.hgdate)
+        age_part = messages.REVIEW_LOG_COMMENT_AGE % age 
+        
+        spacing = ' ' * (80 - (len(author_part) + len(age_part)))
+        
+        ui.write(author_part + spacing + age_part + '\n')
         for line in comment.message.splitlines():
             ui.write(messages.REVIEW_LOG_COMMENT_LINE % line)
+        
         ui.write(after)
     
     review_level_comments = filter(lambda c: not c.filename, rcset.comments)
--- a/review/messages.py	Sat Oct 10 11:05:05 2009 -0400
+++ b/review/messages.py	Sat Oct 10 16:58:14 2009 -0400
@@ -71,9 +71,8 @@
 
 REVIEW_LOG_FILE_HEADER = """changes in %s"""
 
-REVIEW_LOG_COMMENT_AUTHOR = """\
-# %s said:
-"""
+REVIEW_LOG_COMMENT_AUTHOR = """# %s said:"""
+REVIEW_LOG_COMMENT_AGE = """(%s ago)"""
 
 REVIEW_LOG_COMMENT_LINE = """\
 #     %s
--- a/review/templates.py	Sat Oct 10 11:05:05 2009 -0400
+++ b/review/templates.py	Sat Oct 10 16:58:14 2009 -0400
@@ -2,7 +2,7 @@
 
 COMMENT_FILE_TEMPLATE = """\
 author:%s
-datetime:%s
+hgdate:%s
 node:%s
 filename:%s
 lines:%s
@@ -11,7 +11,7 @@
 
 SIGNOFF_FILE_TEMPLATE = """\
 author:%s
-datetime:%s
+hgdate:%s
 node:%s
 opinion:%s