--- a/review/api.py Wed Oct 07 19:08:50 2009 -0400
+++ b/review/api.py Wed Oct 07 23:38:30 2009 -0400
@@ -1,7 +1,6 @@
from __future__ import with_statement
-'''The review data structures.
-'''
+"""The data structures used by hg-review."""
import os
import messages, templates
@@ -19,15 +18,15 @@
class SignoffExists(Exception):
- '''Raised when trying to signoff twice without forcing.'''
+ """Raised when trying to signoff twice without forcing."""
pass
class CannotDeleteObject(Exception):
- '''Raised when trying to delete an object that does not support deletion.'''
+ """Raised when trying to delete an object that does not support deletion."""
pass
class FileNotInChangeset(Exception):
- '''Raised when trying to add a comment on a file not in the changeset.'''
+ """Raised when trying to add a comment on a file not in the changeset."""
def __init__(self, filename):
super(FileNotInChangeset, self).__init__()
self.filename = filename
@@ -35,17 +34,38 @@
def _split_path_dammit(p):
+ """Take a file path (from the current platform) and split it. Really.
+
+ os.path doesn't seem to have an easy way to say "Split this path into a
+ list of pieces."
+
+ >>> _split_path_dammit('')
+ []
+ >>> _split_path_dammit('one')
+ ['one']
+ >>> _split_path_dammit('one/two/three')
+ ['one', 'two', 'three']
+ >>> _split_path_dammit('one/two/three/')
+ ['one', 'two', 'three']
+ >>> _split_path_dammit('one/two/three.py')
+ ['one', 'two', 'three.py']
+
+ """
def _spd(p):
p, i = os.path.split(p)
- while i:
+ while i or p:
yield i
p, i = os.path.split(p)
- return list(_spd(p))[::-1]
+ return filter(None, list(_spd(p)))[::-1]
def _parse_hgrf(repo):
- """Parse the .hgreview file and return the data inside."""
+ """Parse the .hgreview file and return the data inside.
+ The .hgreview file will be pulled from the tip revision of the given
+ repository. If it is not committed it will not be found!
+
+ """
data = {}
hgrd = repo['tip']['.hgreview'].data().split('\n')
lines = [line for line in hgrd if line.strip()]
@@ -59,12 +79,16 @@
return data
def _commitfunc(ui, repo, message, match, opts):
+ """A function used by the guts of Mercurial.
+
+ Mercurial needs a "commit function" parameter when using cmdutil.commit.
+ This is a simple function for *only* that purpose.
+
+ """
return repo.commit(message, opts.get('user'), opts.get('date'), match)
-def _match(start):
- return lambda fn: fn.startswith(start)
-
def _parse_data(data):
+ """Parse the data (string) of a stored _ReviewObject and return a dict."""
meta, _, message = data.partition('\n\n')
data = {}
@@ -76,14 +100,26 @@
def sanitize_path(p, repo=None):
- '''Take a path specific to the current platform and convert it.'''
+ """Sanitize a (platform-specific) path.
+
+ If no repository is given, the path's separators will be replaced with
+ forward slashes (the form Mercurial uses internally).
+
+ If a repository is given, the result will be relative to the root of the
+ repository. This is useful for turning relative paths into normalized
+ paths that can be used to look up files from a changectx.
+
+ This function is idempotent. If you sanitize a path multiple times
+ against the same repository the result will not change.
+
+ """
if repo:
p = os.path.relpath(os.path.realpath(p), start=repo.root)
return '/'.join(_split_path_dammit(p))
class ReviewDatastore(object):
- '''The data store for all the reviews so far.'''
+ """The data store for all the reviews so far."""
def __init__(self, ui, repo, lpath=None, rpath=None, create=False):
self.ui = ui
@@ -111,13 +147,13 @@
repo.add(['.hgreview'])
def __getitem__(self, rev):
- '''Return a ReviewChangeset for the given revision.'''
+ """Return a ReviewChangeset for the given revision."""
node = hex(self.target[rev].node())
return ReviewChangeset(self.ui, self.repo, self.target, node)
class ReviewChangeset(object):
- '''The review data about one changeset in the target repository.'''
+ """The review data about one changeset in the target repository."""
def __init__(self, ui, repo, target, node):
self.repo = repo
self.target = target
@@ -125,6 +161,8 @@
self.node = node
if '%s/.exists' % self.node in self.repo['tip']:
+ _match = lambda p: lambda fn: fn.startswith(p)
+
relevant = filter(_match(node), self.repo['tip'])
commentfns = filter(_match('%s/comments' % node), relevant)
signofffns = filter(_match('%s/signoffs' % node), relevant)
@@ -155,7 +193,7 @@
'addremove': True, })
def add_signoff(self, message, opinion='', force=False):
- '''Add (and commit) a signoff for the given revision.'''
+ """Add (and commit) a signoff for the given revision."""
existing = filter(lambda s: s.author == self.ui.username(), self.signoffs)
if existing:
@@ -168,7 +206,7 @@
signoff.commit(self.ui, self.repo)
def add_comment(self, message, filename='', lines=[]):
- '''Add (and commit) a comment for the given file and lines.'''
+ """Add (and commit) a comment for the given file and lines."""
if filename and filename not in self.target[self.node].files():
raise FileNotInChangeset(filename)
@@ -177,7 +215,7 @@
comment.commit(self.ui, self.repo)
def full_diffs(self, filenames=None, opts={}):
- '''Return diffs of the given files.'''
+ """Return diffs of the given files."""
target_files = self.target[self.node].files()
if not filenames:
@@ -206,14 +244,14 @@
class _ReviewObject(object):
- '''Some kind of object.'''
+ """Some kind of object."""
def __init__(self, container, commit_message, delete_message=None):
self.container = container
self.commit_message = commit_message
self.delete_message = delete_message
def commit(self, ui, repo):
- '''Write and commit this object to the given repo.'''
+ """Write and commit this object to the given repo."""
path = os.path.join(repo.root, self.node, self.container)
if not os.path.exists(path):
@@ -230,7 +268,7 @@
{ 'message': self.commit_message % self.node, 'addremove': True, })
def delete(self, ui, repo):
- '''Delete and commit this object in the given repo.'''
+ """Delete and commit this object in the given repo."""
if not self.delete_message:
raise CannotDeleteObject
@@ -246,7 +284,7 @@
class ReviewComment(_ReviewObject):
- '''A single review comment.'''
+ """A single review comment."""
def __init__(self, author, datetime, node, filename, lines, message, **extra):
super(ReviewComment, self).__init__(
container='comments', commit_message=messages.COMMIT_COMMENT,
@@ -277,7 +315,7 @@
class ReviewSignoff(_ReviewObject):
- '''A single review signoff.'''
+ """A single review signoff."""
def __init__(self, author, datetime, node, opinion, message, **extra):
super(ReviewSignoff, self).__init__(
container='signoffs', commit_message=messages.COMMIT_SIGNOFF,