1. mdelagra
  2. mercurial-reviewboard

Commits

mdelagra  committed 0371ddb

moving hg_reviewboard and reviewboard into top level modules plus related changes

  • Participants
  • Parent commits ece014f
  • Branches plugin_repackage

Comments (0)

Files changed (82)

File TESTING

View file
 
 Install Python Mock, nose and Mercurial and run:
 
-    nosetests
+    PYTHONPATH=. nosetests
 
 * WITH VIRTUALENV
 
 In order to set up the plugin tests for the first time, install virtualenv
 and run:
 
-    python mercurial_reviewboard/tests/virtualenv/bootstrap.py ENV --no-site-packages
+    python tests/virtualenv/bootstrap.py ENV --no-site-packages
 
 In order to run the tests, run:
 
-    ENV/bin/nosetests
+    PYTHONPATH=. ENV/bin/nosetests
     
 
 TEST DATA
     |
     +-<python scripts for unit and integration tests>
     
-There is a package-level setup function in mercurial_reviewboard.tests.__init__
+There is a package-level setup function in tests.__init__
 that will automatically recreate the repos directory for every test run.
 Some tests use the diffs/ and repos/ data to exercise various aspects of the
-tool.  When creating a new diff or repo for testing, place a shell script in the 
-scripts directory with the appropriate logic and commit the script and 
+tool.  When creating a new diff or repo for testing, place a shell script in 
+the scripts directory with the appropriate logic and commit the script and 
 the resulting diff or repo tar.

File __init__.py

View file
-from mercurial_reviewboard import *
+from hg_reviewboard import * # plugin logic

File hg_reviewboard.py

View file
+'''post changesets to a reviewboard server'''
+
+import os, errno, re, sys
+import cStringIO
+import operator
+import webbrowser
+
+from mercurial import cmdutil, hg, ui, mdiff, patch, util
+from mercurial.i18n import _
+
+from reviewboard import ReviewBoard, ReviewBoardError
+
+__version__ = '3.1.0'
+
+def postreview(ui, repo, rev='.', **opts):
+    '''post a changeset to a Review Board server
+
+This command creates a new review request on a Review Board server, or updates
+an existing review request, based on a changeset in the repository. If no
+revision number is specified the parent revision of the working directory is
+used.
+
+By default, the diff uploaded to the server is based on the parent of the
+revision to be reviewed. A different parent may be specified using the
+--parent option.  Alternatively you may specify --outgoingchanges to calculate
+the parent based on the outgoing changesets or --branch to choose the parent
+revision of the branch.
+
+If the parent revision is not available to the Review Board server (e.g. it
+exists in your local repository but not in the one that Review Board has
+access to) you must tell postreview how to determine the base revision
+to use for a parent diff. The --outgoing, --outgoingrepo or --master options
+may be used for this purpose. The --outgoing option is the simplest of these;
+it assumes that the upstream repository specified in .hg/hgrc is the same as
+the one known to Review Board. The other two options offer more control if
+this is not the case.
+'''
+
+    ui.status('postreview plugin, version %s\n' % __version__)
+    
+    if not ui.config('reviewboard', 'server'):
+        raise util.Abort(
+                _('please specify a reviewboard server in your .hgrc file') )
+    
+    check_parent_options(opts)
+
+    c = repo.changectx(rev)
+
+    rparent = find_rparent(ui, repo, c, opts)        
+    parent  = find_parent(ui, repo, c, rparent, opts)
+
+    diff, parentdiff = create_review_data(ui, repo, c, parent, rparent)
+
+    send_review(ui, repo, c, parent, diff, parentdiff, opts)
+    
+def find_rparent(ui, repo, c, opts):
+    outgoing = opts.get('outgoing')
+    outgoingrepo = opts.get('outgoingrepo')
+    master = opts.get('master')
+
+    if master:
+        rparent = repo[master]
+    elif outgoingrepo:
+        rparent = remoteparent(ui, repo, c, upstream=outgoingrepo)
+    elif outgoing:
+        rparent = remoteparent(ui, repo, c)
+    else:
+        rparent = None
+    return rparent
+
+def find_parent(ui, repo, c, rparent, opts):
+    parent = opts.get('parent')
+    outgoingchanges = opts.get('outgoingchanges')
+    branch = opts.get('branch')
+    
+    if outgoingchanges:
+        parent = rparent
+    elif parent:
+        parent = repo[parent]
+    elif branch:
+        parent = find_branch_parent(ui, c)
+    else:
+        parent = c.parents()[0]
+    return parent
+
+def create_review_data(ui, repo, c, parent, rparent):
+    'Returns a tuple of the diff and parent diff for the review.'
+    diff = getdiff(ui, repo, c, parent)
+    ui.debug('\n=== Diff from parent to rev ===\n')
+    ui.debug(diff + '\n')
+
+    if rparent and parent != rparent:
+        parentdiff = getdiff(ui, repo, parent, rparent)
+        ui.debug('\n=== Diff from rparent to parent ===\n')
+        ui.debug(parentdiff + '\n')
+    else:
+        parentdiff = ''
+    return diff, parentdiff
+    
+    
+def send_review(ui, repo, c, parentc, diff, parentdiff, opts):
+    
+    fields = createfields(ui, repo, c, parentc, opts)
+
+    request_id = opts['existing']
+    if request_id:
+        update_review(request_id, ui, fields, diff, parentdiff, opts)
+    else:
+        request_id = new_review(ui, fields, diff, parentdiff, 
+                                   opts)
+
+    request_url = '%s/%s/%s/' % (ui.config('reviewboard', 'server'), 
+                                 "r", request_id)
+
+    if not request_url.startswith('http'):
+        request_url = 'http://%s' % request_url
+
+    msg = 'review request draft saved: %s\n'
+    if opts['publish']:
+        msg = 'review request published: %s\n'
+    ui.status(msg % request_url)
+    
+    if ui.configbool('reviewboard', 'launch_webbrowser'):
+        ui.status('browser launched\n')
+        webbrowser.open(request_url)
+    
+def getdiff(ui, repo, r, parent):
+    '''return diff for the specified revision'''
+    output = ""
+    for chunk in patch.diff(repo, parent.node(), r.node()):
+        output += chunk
+    return output
+
+def getreviewboard(ui, opts):
+    
+    '''We are going to fetch the setting string from hg prefs, there we can set
+    our own proxy, or specify 'none' to pass an empty dictionary to urllib2
+    which overides the default autodetection when we want to force no proxy'''
+    http_proxy = ui.config('reviewboard', 'http_proxy' )
+    if http_proxy:
+        if http_proxy == 'none':
+            proxy = {}
+        else:
+            proxy = { 'http':http_proxy }
+    else:
+        proxy=None
+    
+    server = ui.config('reviewboard', 'server')
+    
+    reviewboard = ReviewBoard(server, proxy)
+    ui.status('reviewboard:\t%s\n' % server)
+    ui.status('\n')
+    username = opts.get('username') or ui.config('reviewboard', 'user')
+    if username:
+        ui.status('username: %s\n' % username)
+    password = opts.get('password') or ui.config('reviewboard', 'password')
+    if password:
+        ui.status('password: %s\n' % '**********')
+
+    try:
+        reviewboard.login(username, password)
+    except ReviewBoardError, msg:
+        raise util.Abort(_(msg))
+    
+    return reviewboard
+
+def update_review(request_id, ui, fields, diff, parentdiff, opts):
+    reviewboard = getreviewboard(ui, opts)
+    try:
+        reviewboard.update_request(request_id, fields, diff, parentdiff)
+        if opts['publish']:
+            reviewboard.publish(request_id)
+    except ReviewBoardError, msg:
+        raise util.Abort(_(msg))
+    
+def new_review(ui, fields, diff, parentdiff, opts):
+    reviewboard = getreviewboard(ui, opts)
+    
+    repo_id = find_reviewboard_repo_id(ui, reviewboard, opts)
+
+    try:
+        request_id = reviewboard.new_request(repo_id, fields, diff, parentdiff)
+        if opts['publish']:
+            reviewboard.publish(request_id)
+    except ReviewBoardError, msg:
+        raise util.Abort(_(msg))
+    
+    return request_id
+
+def find_reviewboard_repo_id(ui, reviewboard, opts):
+    if opts.get('repoid'):
+        return int(opts.get('repoid'))
+    elif ui.config('reviewboard','repoid'):
+        return int(ui.config('reviewboard','repoid'))
+    
+    try:
+        repositories = reviewboard.repositories()
+    except ReviewBoardError, msg:
+        raise util.Abort(_(msg))
+
+    if not repositories:
+        raise util.Abort(_('no repositories configured at %s' % server))
+
+    repositories = sorted(repositories, key=operator.itemgetter('name'),
+                          cmp=lambda x, y: cmp(x.lower(), y.lower()))
+    
+    remotepath = expandpath(ui, opts['outgoingrepo']).lower()
+    repo_id = None
+    for r in repositories:
+        if r['tool'] != 'Mercurial':
+            continue
+        if r['path'].lower() == remotepath:
+            repo_id = r['id']
+            ui.status('Using repository: %s\n' % r['name'])
+    if repo_id == None:
+        ui.status('Repositories:\n')
+        repo_ids = set()
+        for r in repositories:
+            if r['tool'] != 'Mercurial':
+                continue
+            ui.status('[%s] %s\n' % (r['id'], r['name']) )
+            repo_ids.add(str(r['id']))
+        if len(repositories) > 1:
+            repo_id = ui.prompt('repository id:', 0)
+            if not repo_id in repo_ids:
+                raise util.Abort(_('invalid repository ID: %s') % repo_id)
+        else:
+            repo_id = repositories[0]['id']
+            ui.status('repository id: %s\n' % repo_id)
+    return repo_id
+
+def createfields(ui, repo, c, parentc, opts):
+    fields = {}
+    
+    all_contexts = find_contexts(repo, parentc, c)
+
+    changesets_string = 'changesets:\n'
+    changesets_string += \
+        ''.join(['\t%s:%s "%s"\n' % (ctx.rev(), ctx, ctx.description()) \
+                 for ctx in all_contexts])
+    if opts['branch']:
+        branch_msg = "review of branch: %s\n\n" % (c.branch())
+        changesets_string = branch_msg + changesets_string
+    ui.status(changesets_string + '\n')
+
+    interactive = opts['interactive']
+    request_id = opts['existing']
+    # Don't clobber the summary and description for an existing request
+    # unless specifically asked for    
+    if opts['update'] or not request_id:
+        
+        # summary
+        default_summary = c.description().splitlines()[0]
+        if interactive:
+            ui.status('default summary: %s\n' % default_summary)
+            ui.status('enter summary (or return for default):\n') 
+            summary = readline().strip()
+            if summary:
+                fields['summary'] = summary
+            else:
+                fields['summary'] = default_summary
+        else:
+            fields['summary'] = default_summary
+
+        # description
+        if interactive:
+            ui.status('enter description:\n')
+            description = readline().strip()
+            ui.status('append changesets to description? (Y/n):\n')
+            choice = readline().strip()
+            if choice != 'n':
+                if description:
+                    description += '\n\n'
+                description += changesets_string
+        else:
+            description = changesets_string
+        fields['description'] = description 
+
+    for field in ('target_groups', 'target_people'):
+        if opts.get(field):
+            value = opts.get(field)
+        else:
+            value = ui.config('reviewboard', field)
+        if value:
+            fields[field] = value 
+    
+    return fields
+
+def remoteparent(ui, repo, ctx, upstream=None):
+    remotepath = expandpath(ui, upstream)
+    remoterepo = hg.repository(ui, remotepath)
+    out = repo.findoutgoing(remoterepo)
+    for o in out:
+        orev = repo[o]
+        a, b, c = repo.changelog.nodesbetween([orev.node()], [ctx.node()])
+        if a:
+            return orev.parents()[0]
+
+def expandpath(ui, upstream):
+    if upstream:
+        return ui.expandpath(upstream)
+    else:
+        return ui.expandpath('default-push', 'default')
+
+def check_parent_options(opts):
+    usep = bool(opts['parent'])
+    useg = bool(opts['outgoingchanges'])
+    useb = bool(opts['branch'])
+    
+    if (usep or useg or useb) and not (usep ^ useg ^ useb):
+        raise util.Abort(_(
+           "you cannot combine the --parent, --outgoingchanges "
+           "and --branch options"))
+        
+def find_branch_parent(ui, ctx):
+    '''Find the parent revision of the 'ctx' branch.'''
+    branchname = ctx.branch()
+    
+    getparent = lambda ctx: ctx.parents()[0]
+    
+    currctx = ctx
+    while getparent(currctx) and currctx.branch() == branchname:
+        currctx = getparent(currctx)
+        ui.debug('currctx rev: %s; branch: %s\n' % (currctx.rev(), 
+                                            currctx.branch()))
+    
+    return currctx
+  
+def find_contexts(repo, parentctx, ctx):
+    'Find all context between the contexts, excluding the parent context.'
+    contexts = []
+    for node in repo.changelog.nodesbetween([parentctx.node()],[ctx.node()])[0]:
+        if node != parentctx.node():
+            contexts.append(repo[node])
+    contexts.reverse()
+    return contexts
+
+def readline():
+    line = sys.stdin.readline()
+    return line
+
+cmdtable = {
+    "postreview":
+        (postreview,
+        [
+        ('o', 'outgoing', False,
+         _('use upstream repository to determine the parent diff base')),
+        ('O', 'outgoingrepo', '',
+         _('use specified repository to determine the parent diff base')),
+        ('i', 'repoid', '',
+         _('specify repository id on reviewboard server')),
+        ('m', 'master', '',
+         _('use specified revision as the parent diff base')),
+        ('e', 'existing', '', _('existing request ID to update')),
+        ('u', 'update', False, _('update the fields of an existing request')),
+        ('p', 'publish', None, _('publish request immediately')),
+        ('', 'parent', '', _('parent revision for the uploaded diff')),
+        ('g', 'outgoingchanges', False, 
+            _('create diff with all outgoing changes')),
+        ('b', 'branch', False, 
+            _('create diff of all revisions on the branch')),
+        ('I', 'interactive', False, 
+            _('override the default summary and description')),
+        ('U', 'target_people', '', 
+            _('comma separated list of people needed to review the code')),
+        ('G', 'target_groups', '', 
+            _('comma separated list of groups needed to review the code')),
+        ('', 'username', '', _('username for the ReviewBoard site')),
+        ('', 'password', '', _('password for the ReviewBoard site')),
+        ],
+        _('hg postreview [OPTION]... [REVISION]')),
+}

File mercurial_reviewboard/__init__.py

-'''post changesets to a reviewboard server'''
-
-import os, errno, re, sys
-import cStringIO
-import operator
-import webbrowser
-
-from mercurial import cmdutil, hg, ui, mdiff, patch, util
-from mercurial.i18n import _
-
-from reviewboard import ReviewBoard, ReviewBoardError
-
-__version__ = '3.1.0'
-
-def postreview(ui, repo, rev='.', **opts):
-    '''post a changeset to a Review Board server
-
-This command creates a new review request on a Review Board server, or updates
-an existing review request, based on a changeset in the repository. If no
-revision number is specified the parent revision of the working directory is
-used.
-
-By default, the diff uploaded to the server is based on the parent of the
-revision to be reviewed. A different parent may be specified using the
---parent option.  Alternatively you may specify --outgoingchanges to calculate
-the parent based on the outgoing changesets or --branch to choose the parent
-revision of the branch.
-
-If the parent revision is not available to the Review Board server (e.g. it
-exists in your local repository but not in the one that Review Board has
-access to) you must tell postreview how to determine the base revision
-to use for a parent diff. The --outgoing, --outgoingrepo or --master options
-may be used for this purpose. The --outgoing option is the simplest of these;
-it assumes that the upstream repository specified in .hg/hgrc is the same as
-the one known to Review Board. The other two options offer more control if
-this is not the case.
-'''
-
-    ui.status('postreview plugin, version %s\n' % __version__)
-    
-    if not ui.config('reviewboard', 'server'):
-        raise util.Abort(
-                _('please specify a reviewboard server in your .hgrc file') )
-    
-    check_parent_options(opts)
-
-    c = repo.changectx(rev)
-
-    rparent = find_rparent(ui, repo, c, opts)        
-    parent  = find_parent(ui, repo, c, rparent, opts)
-
-    diff, parentdiff = create_review_data(ui, repo, c, parent, rparent)
-
-    send_review(ui, repo, c, parent, diff, parentdiff, opts)
-    
-def find_rparent(ui, repo, c, opts):
-    outgoing = opts.get('outgoing')
-    outgoingrepo = opts.get('outgoingrepo')
-    master = opts.get('master')
-
-    if master:
-        rparent = repo[master]
-    elif outgoingrepo:
-        rparent = remoteparent(ui, repo, c, upstream=outgoingrepo)
-    elif outgoing:
-        rparent = remoteparent(ui, repo, c)
-    else:
-        rparent = None
-    return rparent
-
-def find_parent(ui, repo, c, rparent, opts):
-    parent = opts.get('parent')
-    outgoingchanges = opts.get('outgoingchanges')
-    branch = opts.get('branch')
-    
-    if outgoingchanges:
-        parent = rparent
-    elif parent:
-        parent = repo[parent]
-    elif branch:
-        parent = find_branch_parent(ui, c)
-    else:
-        parent = c.parents()[0]
-    return parent
-
-def create_review_data(ui, repo, c, parent, rparent):
-    'Returns a tuple of the diff and parent diff for the review.'
-    diff = getdiff(ui, repo, c, parent)
-    ui.debug('\n=== Diff from parent to rev ===\n')
-    ui.debug(diff + '\n')
-
-    if rparent and parent != rparent:
-        parentdiff = getdiff(ui, repo, parent, rparent)
-        ui.debug('\n=== Diff from rparent to parent ===\n')
-        ui.debug(parentdiff + '\n')
-    else:
-        parentdiff = ''
-    return diff, parentdiff
-    
-    
-def send_review(ui, repo, c, parentc, diff, parentdiff, opts):
-    
-    fields = createfields(ui, repo, c, parentc, opts)
-
-    request_id = opts['existing']
-    if request_id:
-        update_review(request_id, ui, fields, diff, parentdiff, opts)
-    else:
-        request_id = new_review(ui, fields, diff, parentdiff, 
-                                   opts)
-
-    request_url = '%s/%s/%s/' % (ui.config('reviewboard', 'server'), 
-                                 "r", request_id)
-
-    if not request_url.startswith('http'):
-        request_url = 'http://%s' % request_url
-
-    msg = 'review request draft saved: %s\n'
-    if opts['publish']:
-        msg = 'review request published: %s\n'
-    ui.status(msg % request_url)
-    
-    if ui.configbool('reviewboard', 'launch_webbrowser'):
-        ui.status('browser launched\n')
-        webbrowser.open(request_url)
-    
-def getdiff(ui, repo, r, parent):
-    '''return diff for the specified revision'''
-    output = ""
-    for chunk in patch.diff(repo, parent.node(), r.node()):
-        output += chunk
-    return output
-
-def getreviewboard(ui, opts):
-    
-    '''We are going to fetch the setting string from hg prefs, there we can set
-    our own proxy, or specify 'none' to pass an empty dictionary to urllib2
-    which overides the default autodetection when we want to force no proxy'''
-    http_proxy = ui.config('reviewboard', 'http_proxy' )
-    if http_proxy:
-        if http_proxy == 'none':
-            proxy = {}
-        else:
-            proxy = { 'http':http_proxy }
-    else:
-        proxy=None
-    
-    server = ui.config('reviewboard', 'server')
-    
-    reviewboard = ReviewBoard(server, proxy)
-    ui.status('reviewboard:\t%s\n' % server)
-    ui.status('\n')
-    username = opts.get('username') or ui.config('reviewboard', 'user')
-    if username:
-        ui.status('username: %s\n' % username)
-    password = opts.get('password') or ui.config('reviewboard', 'password')
-    if password:
-        ui.status('password: %s\n' % '**********')
-
-    try:
-        reviewboard.login(username, password)
-    except ReviewBoardError, msg:
-        raise util.Abort(_(msg))
-    
-    return reviewboard
-
-def update_review(request_id, ui, fields, diff, parentdiff, opts):
-    reviewboard = getreviewboard(ui, opts)
-    try:
-        reviewboard.update_request(request_id, fields, diff, parentdiff)
-        if opts['publish']:
-            reviewboard.publish(request_id)
-    except ReviewBoardError, msg:
-        raise util.Abort(_(msg))
-    
-def new_review(ui, fields, diff, parentdiff, opts):
-    reviewboard = getreviewboard(ui, opts)
-    
-    repo_id = find_reviewboard_repo_id(ui, reviewboard, opts)
-
-    try:
-        request_id = reviewboard.new_request(repo_id, fields, diff, parentdiff)
-        if opts['publish']:
-            reviewboard.publish(request_id)
-    except ReviewBoardError, msg:
-        raise util.Abort(_(msg))
-    
-    return request_id
-
-def find_reviewboard_repo_id(ui, reviewboard, opts):
-    if opts.get('repoid'):
-        return int(opts.get('repoid'))
-    elif ui.config('reviewboard','repoid'):
-        return int(ui.config('reviewboard','repoid'))
-    
-    try:
-        repositories = reviewboard.repositories()
-    except ReviewBoardError, msg:
-        raise util.Abort(_(msg))
-
-    if not repositories:
-        raise util.Abort(_('no repositories configured at %s' % server))
-
-    repositories = sorted(repositories, key=operator.itemgetter('name'),
-                          cmp=lambda x, y: cmp(x.lower(), y.lower()))
-    
-    remotepath = expandpath(ui, opts['outgoingrepo']).lower()
-    repo_id = None
-    for r in repositories:
-        if r['tool'] != 'Mercurial':
-            continue
-        if r['path'].lower() == remotepath:
-            repo_id = r['id']
-            ui.status('Using repository: %s\n' % r['name'])
-    if repo_id == None:
-        ui.status('Repositories:\n')
-        repo_ids = set()
-        for r in repositories:
-            if r['tool'] != 'Mercurial':
-                continue
-            ui.status('[%s] %s\n' % (r['id'], r['name']) )
-            repo_ids.add(str(r['id']))
-        if len(repositories) > 1:
-            repo_id = ui.prompt('repository id:', 0)
-            if not repo_id in repo_ids:
-                raise util.Abort(_('invalid repository ID: %s') % repo_id)
-        else:
-            repo_id = repositories[0]['id']
-            ui.status('repository id: %s\n' % repo_id)
-    return repo_id
-
-def createfields(ui, repo, c, parentc, opts):
-    fields = {}
-    
-    all_contexts = find_contexts(repo, parentc, c)
-
-    changesets_string = 'changesets:\n'
-    changesets_string += \
-        ''.join(['\t%s:%s "%s"\n' % (ctx.rev(), ctx, ctx.description()) \
-                 for ctx in all_contexts])
-    if opts['branch']:
-        branch_msg = "review of branch: %s\n\n" % (c.branch())
-        changesets_string = branch_msg + changesets_string
-    ui.status(changesets_string + '\n')
-
-    interactive = opts['interactive']
-    request_id = opts['existing']
-    # Don't clobber the summary and description for an existing request
-    # unless specifically asked for    
-    if opts['update'] or not request_id:
-        
-        # summary
-        default_summary = c.description().splitlines()[0]
-        if interactive:
-            ui.status('default summary: %s\n' % default_summary)
-            ui.status('enter summary (or return for default):\n') 
-            summary = readline().strip()
-            if summary:
-                fields['summary'] = summary
-            else:
-                fields['summary'] = default_summary
-        else:
-            fields['summary'] = default_summary
-
-        # description
-        if interactive:
-            ui.status('enter description:\n')
-            description = readline().strip()
-            ui.status('append changesets to description? (Y/n):\n')
-            choice = readline().strip()
-            if choice != 'n':
-                if description:
-                    description += '\n\n'
-                description += changesets_string
-        else:
-            description = changesets_string
-        fields['description'] = description 
-
-    for field in ('target_groups', 'target_people'):
-        if opts.get(field):
-            value = opts.get(field)
-        else:
-            value = ui.config('reviewboard', field)
-        if value:
-            fields[field] = value 
-    
-    return fields
-
-def remoteparent(ui, repo, ctx, upstream=None):
-    remotepath = expandpath(ui, upstream)
-    remoterepo = hg.repository(ui, remotepath)
-    out = repo.findoutgoing(remoterepo)
-    for o in out:
-        orev = repo[o]
-        a, b, c = repo.changelog.nodesbetween([orev.node()], [ctx.node()])
-        if a:
-            return orev.parents()[0]
-
-def expandpath(ui, upstream):
-    if upstream:
-        return ui.expandpath(upstream)
-    else:
-        return ui.expandpath('default-push', 'default')
-
-def check_parent_options(opts):
-    usep = bool(opts['parent'])
-    useg = bool(opts['outgoingchanges'])
-    useb = bool(opts['branch'])
-    
-    if (usep or useg or useb) and not (usep ^ useg ^ useb):
-        raise util.Abort(_(
-           "you cannot combine the --parent, --outgoingchanges "
-           "and --branch options"))
-        
-def find_branch_parent(ui, ctx):
-    '''Find the parent revision of the 'ctx' branch.'''
-    branchname = ctx.branch()
-    
-    getparent = lambda ctx: ctx.parents()[0]
-    
-    currctx = ctx
-    while getparent(currctx) and currctx.branch() == branchname:
-        currctx = getparent(currctx)
-        ui.debug('currctx rev: %s; branch: %s\n' % (currctx.rev(), 
-                                            currctx.branch()))
-    
-    return currctx
-  
-def find_contexts(repo, parentctx, ctx):
-    'Find all context between the contexts, excluding the parent context.'
-    contexts = []
-    for node in repo.changelog.nodesbetween([parentctx.node()],[ctx.node()])[0]:
-        if node != parentctx.node():
-            contexts.append(repo[node])
-    contexts.reverse()
-    return contexts
-
-def readline():
-    line = sys.stdin.readline()
-    return line
-
-cmdtable = {
-    "postreview":
-        (postreview,
-        [
-        ('o', 'outgoing', False,
-         _('use upstream repository to determine the parent diff base')),
-        ('O', 'outgoingrepo', '',
-         _('use specified repository to determine the parent diff base')),
-        ('i', 'repoid', '',
-         _('specify repository id on reviewboard server')),
-        ('m', 'master', '',
-         _('use specified revision as the parent diff base')),
-        ('e', 'existing', '', _('existing request ID to update')),
-        ('u', 'update', False, _('update the fields of an existing request')),
-        ('p', 'publish', None, _('publish request immediately')),
-        ('', 'parent', '', _('parent revision for the uploaded diff')),
-        ('g', 'outgoingchanges', False, 
-            _('create diff with all outgoing changes')),
-        ('b', 'branch', False, 
-            _('create diff of all revisions on the branch')),
-        ('I', 'interactive', False, 
-            _('override the default summary and description')),
-        ('U', 'target_people', '', 
-            _('comma separated list of people needed to review the code')),
-        ('G', 'target_groups', '', 
-            _('comma separated list of groups needed to review the code')),
-        ('', 'username', '', _('username for the ReviewBoard site')),
-        ('', 'password', '', _('password for the ReviewBoard site')),
-        ],
-        _('hg postreview [OPTION]... [REVISION]')),
-}

File mercurial_reviewboard/reviewboard.py

-# api code for the reviewboard extension, inspired/copied from reviewboard
-# post-review code.
-
-import cookielib
-import getpass
-import mimetools
-import os
-import urllib2
-import simplejson
-from urlparse import urljoin, urlparse
-
-class APIError(Exception):
-    pass
-
-class ReviewBoardError(Exception):
-    pass
-
-class ReviewBoardHTTPPasswordMgr(urllib2.HTTPPasswordMgr):
-    """
-    Adds HTTP authentication support for URLs.
-
-    Python 2.4's password manager has a bug in http authentication when the
-    target server uses a non-standard port.  This works around that bug on
-    Python 2.4 installs. This also allows post-review to prompt for passwords
-    in a consistent way.
-
-    See: http://bugs.python.org/issue974757
-    """
-    def __init__(self, reviewboard_url):
-        self.passwd  = {}
-        self.rb_url  = reviewboard_url
-        self.rb_user = None
-        self.rb_pass = None
-
-    def find_user_password(self, realm, uri):
-        if uri.startswith(self.rb_url):
-            if self.rb_user is None or self.rb_pass is None:
-                print "==> HTTP Authentication Required"
-                print 'Enter username and password for "%s" at %s' % \
-                    (realm, urlparse(uri)[1])
-                self.rb_user = raw_input('Username: ')
-                self.rb_pass = getpass.getpass('Password: ')
-
-            return self.rb_user, self.rb_pass
-        else:
-            # If this is an auth request for some other domain (since HTTP
-            # handlers are global), fall back to standard password management.
-            return urllib2.HTTPPasswordMgr.find_user_password(self, realm, uri)
-
-
-class ReviewBoard:
-    def __init__(self, url, proxy=None):
-        if not url.endswith('/'):
-            url = url + '/'
-        self.url       = url
-        if 'USERPROFILE' in os.environ:
-            homepath = os.path.join(os.environ["USERPROFILE"], "Local Settings",
-                                    "Application Data")
-        elif 'HOME' in os.environ:
-            homepath = os.environ["HOME"]
-        else:
-            homepath = ''
-        self.cookie_file = os.path.join(homepath, ".post-review-cookies.txt")
-        self._cj = cookielib.MozillaCookieJar(self.cookie_file)
-        password_mgr = ReviewBoardHTTPPasswordMgr(self.url)
-        self._opener = opener = urllib2.build_opener(
-                        urllib2.ProxyHandler(proxy),
-                        urllib2.UnknownHandler(),
-                        urllib2.HTTPHandler(),
-                        urllib2.HTTPDefaultErrorHandler(),
-                        urllib2.HTTPErrorProcessor(),
-                        urllib2.HTTPCookieProcessor(self._cj),
-                        urllib2.HTTPBasicAuthHandler(password_mgr)
-                        )
-        urllib2.install_opener(self._opener)
-        self._repositories = None
-        self._requests = None
-
-    def has_valid_cookie(self):
-        """
-        Load the user's cookie file and see if they have a valid
-        'rbsessionid' cookie for the current Review Board server.  Returns
-        true if so and false otherwise.
-        """
-        try:
-            parsed_url = urlparse(self.url)
-            host = parsed_url[1]
-            path = parsed_url[2] or '/'
-
-            # Cookie files don't store port numbers, unfortunately, so
-            # get rid of the port number if it's present.
-            host = host.split(":")[0]
-
-            print("Looking for '%s %s' cookie in %s" % \
-                  (host, path, self.cookie_file))
-            self._cj.load(self.cookie_file, ignore_expires=True)
-
-            try:
-                cookie = self._cj._cookies[host][path]['rbsessionid']
-
-                if not cookie.is_expired():
-                    print("Loaded valid cookie -- no login required")
-                    return True
-
-                print("Cookie file loaded, but cookie has expired")
-            except KeyError:
-                print("Cookie file loaded, but no cookie for this server")
-        except IOError, error:
-            print("Couldn't load cookie file: %s" % error)
-
-        return False
-
-    def login(self, username=None, password=None):
-        if not username and not password and self.has_valid_cookie():
-            return
-
-        if not username:
-            username = raw_input('Username: ')
-        if not password:
-            password = getpass.getpass('Password: ')
-
-        self._api_post('/api/json/accounts/login/', {
-            'username': username,
-            'password': password,
-        })
-
-    def repositories(self):
-        if not self._repositories:
-            rsp = self._api_post('/api/json/repositories/')
-            self._repositories = rsp['repositories']
-        return self._repositories
-
-    def requests(self):
-        if not self._requests:
-            rsp = self._api_post('/api/json/reviewrequests/all/')
-            self._requests = rsp['review_requests']
-        return self._requests
-
-    def users(self):
-        rsp = self._api_post('/api/json/users/')
-        self.users = rsp['users']
-        return self.users
-
-    def new_request(self, repo_id, fields={}, diff='', parentdiff=''):
-        repository_path = None
-        for r in self.repositories():
-            if r['id'] == int(repo_id):
-                repository_path = r['path']
-                break
-        if not repository_path:
-            raise ReviewBoardError, ("can't find repository with id: %s" % \
-                                        repo_id)
-
-        id = self._create_request(repository_path)
-
-        self._set_request_details(id, fields, diff, parentdiff)
-
-        return id
-
-    def update_request(self, id, fields={}, diff='', parentdiff=''):
-        request_id = None
-        for r in self.requests():
-            if r['id'] == int(id):
-                request_id = int(id)
-                break
-        if not request_id:
-            raise ReviewBoardError, ("can't find request with id: %s" % id)
-
-        self._set_request_details(request_id, fields, diff, parentdiff)
-
-        return request_id
-
-    def publish(self, id):
-        self._api_post('api/json/reviewrequests/%s/publish/' % id)
-
-    def _api_post(self, url, fields=None, files=None):
-        """
-        Performs an API call using HTTP POST at the specified path.
-        """
-        try:
-            return self._process_json( self._http_post(url, fields, files) )
-        except APIError, e:
-            rsp, = e.args
-
-            raise ReviewBoardError, ("%s (%s)" % \
-                                    (rsp["err"]["msg"], rsp["err"]["code"]) )
-
-    def _http_post(self, path, fields, files=None):
-        """
-        Performs an HTTP POST on the specified path.
-        """
-        if path.startswith('/'):
-            path = path[1:]
-        url = urljoin(self.url, path)
-        content_type, body = self._encode_multipart_formdata(fields, files)
-        headers = {
-            'Content-Type': content_type,
-            'Content-Length': str(len(body))
-        }
-
-        try:
-            r = urllib2.Request(url, body, headers)
-            data = urllib2.urlopen(r).read()
-            self._cj.save(self.cookie_file)
-            return data
-        except urllib2.URLError, e:
-            raise ReviewBoardError, ("Unable to access %s.\n%s" % \
-                    (url, e))
-        except urllib2.HTTPError, e:
-            raise ReviewBoardError, ("Unable to access %s (%s).\n%s" % \
-                    (url, e.code, e.read()))
-
-    def _process_json(self, data):
-        """
-        Loads in a JSON file and returns the data if successful. On failure,
-        APIError is raised.
-        """
-        rsp = simplejson.loads(data)
-
-        if rsp['stat'] == 'fail':
-            raise APIError, rsp
-
-        return rsp
-
-    def _encode_multipart_formdata(self, fields, files):
-        """
-        Encodes data for use in an HTTP POST.
-        """
-        BOUNDARY = mimetools.choose_boundary()
-        content = ""
-
-        fields = fields or {}
-        files = files or {}
-
-        for key in fields:
-            content += "--" + BOUNDARY + "\r\n"
-            content += "Content-Disposition: form-data; name=\"%s\"\r\n" % key
-            content += "\r\n"
-            content += fields[key] + "\r\n"
-
-        for key in files:
-            filename = files[key]['filename']
-            value = files[key]['content']
-            content += "--" + BOUNDARY + "\r\n"
-            content += "Content-Disposition: form-data; name=\"%s\"; " % key
-            content += "filename=\"%s\"\r\n" % filename
-            content += "\r\n"
-            content += value + "\r\n"
-
-        content += "--" + BOUNDARY + "--\r\n"
-        content += "\r\n"
-
-        content_type = "multipart/form-data; boundary=%s" % BOUNDARY
-
-        return content_type, content
-
-    def _create_request(self, repository_path):
-        data = { 'repository_path': repository_path }
-        rsp = self._api_post('/api/json/reviewrequests/new/', data)
-
-        return rsp['review_request']['id']
-
-    def _set_request_field(self, id, field, value):
-        self._api_post('/api/json/reviewrequests/%s/draft/set/' %
-                                id, { field: value })
-
-    def _upload_diff(self, id, diff, parentdiff=""):
-        data = {'path': {'filename': 'diff', 'content': diff}}
-        if parentdiff:
-            data['parent_diff_path'] = \
-                {'filename': 'parent_diff', 'content': parentdiff}
-        rsp = self._api_post('/api/json/reviewrequests/%s/diff/new/' % \
-                                id, {}, data)
-
-    def _set_fields(self, id, fields={}):
-        for field in fields:
-            self._set_request_field(id, field, fields[field])
-
-    def _set_request_details(self, id, fields, diff, parentdiff):
-        self._set_fields(id, fields)
-        if diff:
-            self._upload_diff(id, diff, parentdiff)

File mercurial_reviewboard/simplejson/__init__.py

-r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of
-JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
-interchange format.
-
-:mod:`simplejson` exposes an API familiar to users of the standard library
-:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
-version of the :mod:`json` library contained in Python 2.6, but maintains
-compatibility with Python 2.4 and Python 2.5 and (currently) has
-significant performance advantages, even without using the optional C
-extension for speedups.
-
-Encoding basic Python object hierarchies::
-
-    >>> import simplejson as json
-    >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
-    '["foo", {"bar": ["baz", null, 1.0, 2]}]'
-    >>> print json.dumps("\"foo\bar")
-    "\"foo\bar"
-    >>> print json.dumps(u'\u1234')
-    "\u1234"
-    >>> print json.dumps('\\')
-    "\\"
-    >>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)
-    {"a": 0, "b": 0, "c": 0}
-    >>> from StringIO import StringIO
-    >>> io = StringIO()
-    >>> json.dump(['streaming API'], io)
-    >>> io.getvalue()
-    '["streaming API"]'
-
-Compact encoding::
-
-    >>> import simplejson as json
-    >>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':'))
-    '[1,2,3,{"4":5,"6":7}]'
-
-Pretty printing::
-
-    >>> import simplejson as json
-    >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4)
-    >>> print '\n'.join([l.rstrip() for l in  s.splitlines()])
-    {
-        "4": 5,
-        "6": 7
-    }
-
-Decoding JSON::
-
-    >>> import simplejson as json
-    >>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}]
-    >>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj
-    True
-    >>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar'
-    True
-    >>> from StringIO import StringIO
-    >>> io = StringIO('["streaming API"]')
-    >>> json.load(io)[0] == 'streaming API'
-    True
-
-Specializing JSON object decoding::
-
-    >>> import simplejson as json
-    >>> def as_complex(dct):
-    ...     if '__complex__' in dct:
-    ...         return complex(dct['real'], dct['imag'])
-    ...     return dct
-    ...
-    >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
-    ...     object_hook=as_complex)
-    (1+2j)
-    >>> import decimal
-    >>> json.loads('1.1', parse_float=decimal.Decimal) == decimal.Decimal('1.1')
-    True
-
-Specializing JSON object encoding::
-
-    >>> import simplejson as json
-    >>> def encode_complex(obj):
-    ...     if isinstance(obj, complex):
-    ...         return [obj.real, obj.imag]
-    ...     raise TypeError(repr(o) + " is not JSON serializable")
-    ...
-    >>> json.dumps(2 + 1j, default=encode_complex)
-    '[2.0, 1.0]'
-    >>> json.JSONEncoder(default=encode_complex).encode(2 + 1j)
-    '[2.0, 1.0]'
-    >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
-    '[2.0, 1.0]'
-
-
-Using simplejson.tool from the shell to validate and pretty-print::
-
-    $ echo '{"json":"obj"}' | python -m simplejson.tool
-    {
-        "json": "obj"
-    }
-    $ echo '{ 1.2:3.4}' | python -m simplejson.tool
-    Expecting property name: line 1 column 2 (char 2)
-"""
-__version__ = '2.0.9'
-__all__ = [
-    'dump', 'dumps', 'load', 'loads',
-    'JSONDecoder', 'JSONEncoder',
-]
-
-__author__ = 'Bob Ippolito <bob@redivi.com>'
-
-from decoder import JSONDecoder
-from encoder import JSONEncoder
-
-_default_encoder = JSONEncoder(
-    skipkeys=False,
-    ensure_ascii=True,
-    check_circular=True,
-    allow_nan=True,
-    indent=None,
-    separators=None,
-    encoding='utf-8',
-    default=None,
-)
-
-def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
-        allow_nan=True, cls=None, indent=None, separators=None,
-        encoding='utf-8', default=None, **kw):
-    """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
-    ``.write()``-supporting file-like object).
-
-    If ``skipkeys`` is true then ``dict`` keys that are not basic types
-    (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
-    will be skipped instead of raising a ``TypeError``.
-
-    If ``ensure_ascii`` is false, then the some chunks written to ``fp``
-    may be ``unicode`` instances, subject to normal Python ``str`` to
-    ``unicode`` coercion rules. Unless ``fp.write()`` explicitly
-    understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
-    to cause an error.
-
-    If ``check_circular`` is false, then the circular reference check
-    for container types will be skipped and a circular reference will
-    result in an ``OverflowError`` (or worse).
-
-    If ``allow_nan`` is false, then it will be a ``ValueError`` to
-    serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
-    in strict compliance of the JSON specification, instead of using the
-    JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
-
-    If ``indent`` is a non-negative integer, then JSON array elements and object
-    members will be pretty-printed with that indent level. An indent level
-    of 0 will only insert newlines. ``None`` is the most compact representation.
-
-    If ``separators`` is an ``(item_separator, dict_separator)`` tuple
-    then it will be used instead of the default ``(', ', ': ')`` separators.
-    ``(',', ':')`` is the most compact JSON representation.
-
-    ``encoding`` is the character encoding for str instances, default is UTF-8.
-
-    ``default(obj)`` is a function that should return a serializable version
-    of obj or raise TypeError. The default simply raises TypeError.
-
-    To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
-    ``.default()`` method to serialize additional types), specify it with
-    the ``cls`` kwarg.
-
-    """
-    # cached encoder
-    if (not skipkeys and ensure_ascii and
-        check_circular and allow_nan and
-        cls is None and indent is None and separators is None and
-        encoding == 'utf-8' and default is None and not kw):
-        iterable = _default_encoder.iterencode(obj)
-    else:
-        if cls is None:
-            cls = JSONEncoder
-        iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
-            check_circular=check_circular, allow_nan=allow_nan, indent=indent,
-            separators=separators, encoding=encoding,
-            default=default, **kw).iterencode(obj)
-    # could accelerate with writelines in some versions of Python, at
-    # a debuggability cost
-    for chunk in iterable:
-        fp.write(chunk)
-
-
-def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
-        allow_nan=True, cls=None, indent=None, separators=None,
-        encoding='utf-8', default=None, **kw):
-    """Serialize ``obj`` to a JSON formatted ``str``.
-
-    If ``skipkeys`` is false then ``dict`` keys that are not basic types
-    (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
-    will be skipped instead of raising a ``TypeError``.
-
-    If ``ensure_ascii`` is false, then the return value will be a
-    ``unicode`` instance subject to normal Python ``str`` to ``unicode``
-    coercion rules instead of being escaped to an ASCII ``str``.
-
-    If ``check_circular`` is false, then the circular reference check
-    for container types will be skipped and a circular reference will
-    result in an ``OverflowError`` (or worse).
-
-    If ``allow_nan`` is false, then it will be a ``ValueError`` to
-    serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
-    strict compliance of the JSON specification, instead of using the
-    JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
-
-    If ``indent`` is a non-negative integer, then JSON array elements and
-    object members will be pretty-printed with that indent level. An indent
-    level of 0 will only insert newlines. ``None`` is the most compact
-    representation.
-
-    If ``separators`` is an ``(item_separator, dict_separator)`` tuple
-    then it will be used instead of the default ``(', ', ': ')`` separators.
-    ``(',', ':')`` is the most compact JSON representation.
-
-    ``encoding`` is the character encoding for str instances, default is UTF-8.
-
-    ``default(obj)`` is a function that should return a serializable version
-    of obj or raise TypeError. The default simply raises TypeError.
-
-    To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
-    ``.default()`` method to serialize additional types), specify it with
-    the ``cls`` kwarg.
-
-    """
-    # cached encoder
-    if (not skipkeys and ensure_ascii and
-        check_circular and allow_nan and
-        cls is None and indent is None and separators is None and
-        encoding == 'utf-8' and default is None and not kw):
-        return _default_encoder.encode(obj)
-    if cls is None:
-        cls = JSONEncoder
-    return cls(
-        skipkeys=skipkeys, ensure_ascii=ensure_ascii,
-        check_circular=check_circular, allow_nan=allow_nan, indent=indent,
-        separators=separators, encoding=encoding, default=default,
-        **kw).encode(obj)
-
-
-_default_decoder = JSONDecoder(encoding=None, object_hook=None)
-
-
-def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None,
-        parse_int=None, parse_constant=None, **kw):
-    """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
-    a JSON document) to a Python object.
-
-    If the contents of ``fp`` is encoded with an ASCII based encoding other
-    than utf-8 (e.g. latin-1), then an appropriate ``encoding`` name must
-    be specified. Encodings that are not ASCII based (such as UCS-2) are
-    not allowed, and should be wrapped with
-    ``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode``
-    object and passed to ``loads()``
-
-    ``object_hook`` is an optional function that will be called with the
-    result of any object literal decode (a ``dict``). The return value of
-    ``object_hook`` will be used instead of the ``dict``. This feature
-    can be used to implement custom decoders (e.g. JSON-RPC class hinting).
-
-    To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
-    kwarg.
-
-    """
-    return loads(fp.read(),
-        encoding=encoding, cls=cls, object_hook=object_hook,
-        parse_float=parse_float, parse_int=parse_int,
-        parse_constant=parse_constant, **kw)
-
-
-def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
-        parse_int=None, parse_constant=None, **kw):
-    """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
-    document) to a Python object.
-
-    If ``s`` is a ``str`` instance and is encoded with an ASCII based encoding
-    other than utf-8 (e.g. latin-1) then an appropriate ``encoding`` name
-    must be specified. Encodings that are not ASCII based (such as UCS-2)
-    are not allowed and should be decoded to ``unicode`` first.
-
-    ``object_hook`` is an optional function that will be called with the
-    result of any object literal decode (a ``dict``). The return value of
-    ``object_hook`` will be used instead of the ``dict``. This feature
-    can be used to implement custom decoders (e.g. JSON-RPC class hinting).
-
-    ``parse_float``, if specified, will be called with the string
-    of every JSON float to be decoded. By default this is equivalent to
-    float(num_str). This can be used to use another datatype or parser
-    for JSON floats (e.g. decimal.Decimal).
-
-    ``parse_int``, if specified, will be called with the string
-    of every JSON int to be decoded. By default this is equivalent to
-    int(num_str). This can be used to use another datatype or parser
-    for JSON integers (e.g. float).
-
-    ``parse_constant``, if specified, will be called with one of the
-    following strings: -Infinity, Infinity, NaN, null, true, false.
-    This can be used to raise an exception if invalid JSON numbers
-    are encountered.
-
-    To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
-    kwarg.
-
-    """
-    if (cls is None and encoding is None and object_hook is None and
-            parse_int is None and parse_float is None and
-            parse_constant is None and not kw):
-        return _default_decoder.decode(s)
-    if cls is None:
-        cls = JSONDecoder
-    if object_hook is not None:
-        kw['object_hook'] = object_hook
-    if parse_float is not None:
-        kw['parse_float'] = parse_float
-    if parse_int is not None:
-        kw['parse_int'] = parse_int
-    if parse_constant is not None:
-        kw['parse_constant'] = parse_constant
-    return cls(encoding=encoding, **kw).decode(s)

File mercurial_reviewboard/simplejson/decoder.py

-"""Implementation of JSONDecoder
-"""
-import re
-import sys
-import struct
-
-from scanner import make_scanner
-c_scanstring = None
-
-__all__ = ['JSONDecoder']
-
-FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
-
-def _floatconstants():
-    _BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
-    if sys.byteorder != 'big':
-        _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
-    nan, inf = struct.unpack('dd', _BYTES)
-    return nan, inf, -inf
-
-NaN, PosInf, NegInf = _floatconstants()
-
-
-def linecol(doc, pos):
-    lineno = doc.count('\n', 0, pos) + 1
-    if lineno == 1:
-        colno = pos
-    else:
-        colno = pos - doc.rindex('\n', 0, pos)
-    return lineno, colno
-
-
-def errmsg(msg, doc, pos, end=None):
-    # Note that this function is called from _speedups
-    lineno, colno = linecol(doc, pos)
-    if end is None:
-        #fmt = '{0}: line {1} column {2} (char {3})'
-        #return fmt.format(msg, lineno, colno, pos)
-        fmt = '%s: line %d column %d (char %d)'
-        return fmt % (msg, lineno, colno, pos)
-    endlineno, endcolno = linecol(doc, end)
-    #fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
-    #return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
-    fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
-    return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
-
-
-_CONSTANTS = {
-    '-Infinity': NegInf,
-    'Infinity': PosInf,
-    'NaN': NaN,
-}
-
-STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
-BACKSLASH = {
-    '"': u'"', '\\': u'\\', '/': u'/',
-    'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
-}
-
-DEFAULT_ENCODING = "utf-8"
-
-def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match):
-    """Scan the string s for a JSON string. End is the index of the
-    character in s after the quote that started the JSON string.
-    Unescapes all valid JSON string escape sequences and raises ValueError
-    on attempt to decode an invalid string. If strict is False then literal
-    control characters are allowed in the string.
-    
-    Returns a tuple of the decoded string and the index of the character in s
-    after the end quote."""
-    if encoding is None:
-        encoding = DEFAULT_ENCODING
-    chunks = []
-    _append = chunks.append
-    begin = end - 1
-    while 1:
-        chunk = _m(s, end)
-        if chunk is None:
-            raise ValueError(
-                errmsg("Unterminated string starting at", s, begin))
-        end = chunk.end()
-        content, terminator = chunk.groups()
-        # Content is contains zero or more unescaped string characters
-        if content:
-            if not isinstance(content, unicode):
-                content = unicode(content, encoding)
-            _append(content)
-        # Terminator is the end of string, a literal control character,
-        # or a backslash denoting that an escape sequence follows
-        if terminator == '"':
-            break
-        elif terminator != '\\':
-            if strict:
-                msg = "Invalid control character %r at" % (terminator,)
-                #msg = "Invalid control character {0!r} at".format(terminator)
-                raise ValueError(errmsg(msg, s, end))
-            else:
-                _append(terminator)
-                continue
-        try:
-            esc = s[end]
-        except IndexError:
-            raise ValueError(
-                errmsg("Unterminated string starting at", s, begin))
-        # If not a unicode escape sequence, must be in the lookup table
-        if esc != 'u':
-            try:
-                char = _b[esc]
-            except KeyError:
-                msg = "Invalid \\escape: " + repr(esc)
-                raise ValueError(errmsg(msg, s, end))
-            end += 1
-        else:
-            # Unicode escape sequence
-            esc = s[end + 1:end + 5]
-            next_end = end + 5
-            if len(esc) != 4:
-                msg = "Invalid \\uXXXX escape"
-                raise ValueError(errmsg(msg, s, end))
-            uni = int(esc, 16)
-            # Check for surrogate pair on UCS-4 systems
-            if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535:
-                msg = "Invalid \\uXXXX\\uXXXX surrogate pair"
-                if not s[end + 5:end + 7] == '\\u':
-                    raise ValueError(errmsg(msg, s, end))
-                esc2 = s[end + 7:end + 11]
-                if len(esc2) != 4:
-                    raise ValueError(errmsg(msg, s, end))
-                uni2 = int(esc2, 16)
-                uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
-                next_end += 6
-            char = unichr(uni)
-            end = next_end
-        # Append the unescaped character
-        _append(char)
-    return u''.join(chunks), end
-
-
-# Use speedup if available
-scanstring = c_scanstring or py_scanstring
-
-WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
-WHITESPACE_STR = ' \t\n\r'
-
-def JSONObject((s, end), encoding, strict, scan_once, object_hook, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
-    pairs = {}
-    # Use a slice to prevent IndexError from being raised, the following
-    # check will raise a more specific ValueError if the string is empty
-    nextchar = s[end:end + 1]
-    # Normally we expect nextchar == '"'
-    if nextchar != '"':
-        if nextchar in _ws:
-            end = _w(s, end).end()
-            nextchar = s[end:end + 1]
-        # Trivial empty object
-        if nextchar == '}':
-            return pairs, end + 1
-        elif nextchar != '"':
-            raise ValueError(errmsg("Expecting property name", s, end))
-    end += 1
-    while True:
-        key, end = scanstring(s, end, encoding, strict)
-
-        # To skip some function call overhead we optimize the fast paths where
-        # the JSON key separator is ": " or just ":".
-        if s[end:end + 1] != ':':
-            end = _w(s, end).end()
-            if s[end:end + 1] != ':':
-                raise ValueError(errmsg("Expecting : delimiter", s, end))
-
-        end += 1
-
-        try:
-            if s[end] in _ws:
-                end += 1
-                if s[end] in _ws:
-                    end = _w(s, end + 1).end()
-        except IndexError:
-            pass
-
-        try:
-            value, end = scan_once(s, end)
-        except StopIteration:
-            raise ValueError(errmsg("Expecting object", s, end))
-        pairs[key] = value
-
-        try:
-            nextchar = s[end]
-            if nextchar in _ws:
-                end = _w(s, end + 1).end()
-                nextchar = s[end]
-        except IndexError:
-            nextchar = ''
-        end += 1
-
-        if nextchar == '}':
-            break
-        elif nextchar != ',':
-            raise ValueError(errmsg("Expecting , delimiter", s, end - 1))
-
-        try:
-            nextchar = s[end]
-            if nextchar in _ws:
-                end += 1
-                nextchar = s[end]
-                if nextchar in _ws:
-                    end = _w(s, end + 1).end()
-                    nextchar = s[end]
-        except IndexError:
-            nextchar = ''
-
-        end += 1
-        if nextchar != '"':
-            raise ValueError(errmsg("Expecting property name", s, end - 1))
-
-    if object_hook is not None:
-        pairs = object_hook(pairs)
-    return pairs, end
-
-def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
-    values = []
-    nextchar = s[end:end + 1]
-    if nextchar in _ws:
-        end = _w(s, end + 1).end()
-        nextchar = s[end:end + 1]
-    # Look-ahead for trivial empty array
-    if nextchar == ']':
-        return values, end + 1
-    _append = values.append
-    while True:
-        try:
-            value, end = scan_once(s, end)
-        except StopIteration:
-            raise ValueError(errmsg("Expecting object", s, end))
-        _append(value)
-        nextchar = s[end:end + 1]
-        if nextchar in _ws:
-            end = _w(s, end + 1).end()
-            nextchar = s[end:end + 1]
-        end += 1
-        if nextchar == ']':
-            break
-        elif nextchar != ',':
-            raise ValueError(errmsg("Expecting , delimiter", s, end))
-
-        try:
-            if s[end] in _ws:
-                end += 1
-                if s[end] in _ws:
-                    end = _w(s, end + 1).end()
-        except IndexError:
-            pass
-
-    return values, end
-
-class JSONDecoder(object):
-    """Simple JSON <http://json.org> decoder
-
-    Performs the following translations in decoding by default:
-
-    +---------------+-------------------+
-    | JSON          | Python            |
-    +===============+===================+
-    | object        | dict              |
-    +---------------+-------------------+
-    | array         | list              |
-    +---------------+-------------------+
-    | string        | unicode           |
-    +---------------+-------------------+
-    | number (int)  | int, long         |
-    +---------------+-------------------+
-    | number (real) | float             |
-    +---------------+-------------------+
-    | true          | True              |
-    +---------------+-------------------+
-    | false         | False             |
-    +---------------+-------------------+
-    | null          | None              |
-    +---------------+-------------------+
-
-    It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
-    their corresponding ``float`` values, which is outside the JSON spec.
-
-    """
-
-    def __init__(self, encoding=None, object_hook=None, parse_float=None,
-            parse_int=None, parse_constant=None, strict=True):
-        """``encoding`` determines the encoding used to interpret any ``str``
-        objects decoded by this instance (utf-8 by default).  It has no
-        effect when decoding ``unicode`` objects.
-
-        Note that currently only encodings that are a superset of ASCII work,
-        strings of other encodings should be passed in as ``unicode``.
-
-        ``object_hook``, if specified, will be called with the result
-        of every JSON object decoded and its return value will be used in
-        place of the given ``dict``.  This can be used to provide custom
-        deserializations (e.g. to support JSON-RPC class hinting).
-
-        ``parse_float``, if specified, will be called with the string
-        of every JSON float to be decoded. By default this is equivalent to
-        float(num_str). This can be used to use another datatype or parser
-        for JSON floats (e.g. decimal.Decimal).
-
-        ``parse_int``, if specified, will be called with the string
-        of every JSON int to be decoded. By default this is equivalent to
-        int(num_str). This can be used to use another datatype or parser
-        for JSON integers (e.g. float).
-
-        ``parse_constant``, if specified, will be called with one of the
-        following strings: -Infinity, Infinity, NaN.
-        This can be used to raise an exception if invalid JSON numbers
-        are encountered.
-
-        """
-        self.encoding = encoding
-        self.object_hook = object_hook
-        self.parse_float = parse_float or float
-        self.parse_int = parse_int or int
-        self.parse_constant = parse_constant or _CONSTANTS.__getitem__
-        self.strict = strict
-        self.parse_object = JSONObject
-        self.parse_array = JSONArray
-        self.parse_string = scanstring
-        self.scan_once = make_scanner(self)
-
-    def decode(self, s, _w=WHITESPACE.match):
-        """Return the Python representation of ``s`` (a ``str`` or ``unicode``
-        instance containing a JSON document)
-
-        """
-        obj, end = self.raw_decode(s, idx=_w(s, 0).end())
-        end = _w(s, end).end()
-        if end != len(s):
-            raise ValueError(errmsg("Extra data", s, end, len(s)))
-        return obj
-
-    def raw_decode(self, s, idx=0):
-        """Decode a JSON document from ``s`` (a ``str`` or ``unicode`` beginning
-        with a JSON document) and return a 2-tuple of the Python
-        representation and the index in ``s`` where the document ended.
-
-        This can be used to decode a JSON document from a string that may
-        have extraneous data at the end.
-
-        """
-        try:
-            obj, end = self.scan_once(s, idx)
-        except StopIteration:
-            raise ValueError("No JSON object could be decoded")
-        return obj, end

File mercurial_reviewboard/simplejson/encoder.py

-"""Implementation of JSONEncoder
-"""
-import re
-
-c_encode_basestring_ascii = None
-c_make_encoder = None
-
-ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
-ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
-HAS_UTF8 = re.compile(r'[\x80-\xff]')
-ESCAPE_DCT = {
-    '\\': '\\\\',
-    '"': '\\"',
-    '\b': '\\b',
-    '\f': '\\f',
-    '\n': '\\n',
-    '\r': '\\r',
-    '\t': '\\t',
-}
-for i in range(0x20):
-    #ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
-    ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
-
-# Assume this produces an infinity on all machines (probably not guaranteed)
-INFINITY = float('1e66666')
-FLOAT_REPR = repr
-
-def encode_basestring(s):
-    """Return a JSON representation of a Python string
-
-    """
-    def replace(match):
-        return ESCAPE_DCT[match.group(0)]
-    return '"' + ESCAPE.sub(replace, s) + '"'
-
-
-def py_encode_basestring_ascii(s):
-    """Return an ASCII-only JSON representation of a Python string
-
-    """
-    if isinstance(s, str) and HAS_UTF8.search(s) is not None:
-        s = s.decode('utf-8')
-    def replace(match):
-        s = match.group(0)
-        try:
-            return ESCAPE_DCT[s]
-        except KeyError:
-            n = ord(s)
-            if n < 0x10000:
-                #return '\\u{0:04x}'.format(n)
-                return '\\u%04x' % (n,)
-            else:
-                # surrogate pair
-                n -= 0x10000
-                s1 = 0xd800 | ((n >> 10) & 0x3ff)
-                s2 = 0xdc00 | (n & 0x3ff)
-                #return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
-                return '\\u%04x\\u%04x' % (s1, s2)
-    return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
-
-
-encode_basestring_ascii = c_encode_basestring_ascii or py_encode_basestring_ascii
-
-class JSONEncoder(object):
-    """Extensible JSON <http://json.org> encoder for Python data structures.
-
-    Supports the following objects and types by default:
-
-    +-------------------+---------------+
-    | Python            | JSON          |
-    +===================+===============+
-    | dict              | object        |
-    +-------------------+---------------+
-    | list, tuple       | array         |
-    +-------------------+---------------+
-    | str, unicode      | string        |
-    +-------------------+---------------+
-    | int, long, float  | number        |
-    +-------------------+---------------+
-    | True              | true          |
-    +-------------------+---------------+
-    | False             | false         |
-    +-------------------+---------------+
-    | None              | null          |
-    +-------------------+---------------+
-
-    To extend this to recognize other objects, subclass and implement a
-    ``.default()`` method with another method that returns a serializable
-    object for ``o`` if possible, otherwise it should call the superclass
-    implementation (to raise ``TypeError``).
-
-    """
-    item_separator = ', '
-    key_separator = ': '
-    def __init__(self, skipkeys=False, ensure_ascii=True,
-            check_circular=True, allow_nan=True, sort_keys=False,
-            indent=None, separators=None, encoding='utf-8', default=None):
-        """Constructor for JSONEncoder, with sensible defaults.
-
-        If skipkeys is false, then it is a TypeError to attempt
-        encoding of keys that are not str, int, long, float or None.  If
-        skipkeys is True, such items are simply skipped.
-
-        If ensure_ascii is true, the output is guaranteed to be str
-        objects with all incoming unicode characters escaped.  If
-        ensure_ascii is false, the output will be unicode object.
-
-        If check_circular is true, then lists, dicts, and custom encoded
-        objects will be checked for circular references during encoding to
-        prevent an infinite recursion (which would cause an OverflowError).
-        Otherwise, no such check takes place.
-
-        If allow_nan is true, then NaN, Infinity, and -Infinity will be
-        encoded as such.  This behavior is not JSON specification compliant,
-        but is consistent with most JavaScript based encoders and decoders.
-        Otherwise, it will be a ValueError to encode such floats.
-
-        If sort_keys is true, then the output of dictionaries will be
-        sorted by key; this is useful for regression tests to ensure
-        that JSON serializations can be compared on a day-to-day basis.
-
-        If indent is a non-negative integer, then JSON array
-        elements and object members will be pretty-printed with that
-        indent level.  An indent level of 0 will only insert newlines.
-        None is the most compact representation.
-
-        If specified, separators should be a (item_separator, key_separator)
-        tuple.  The default is (', ', ': ').  To get the most compact JSON
-        representation you should specify (',', ':') to eliminate whitespace.
-
-        If specified, default is a function that gets called for objects
-        that can't otherwise be serialized.  It should return a JSON encodable
-        version of the object or raise a ``TypeError``.
-
-        If encoding is not None, then all input strings will be
-        transformed into unicode using that encoding prior to JSON-encoding.
-        The default is UTF-8.
-
-        """
-
-        self.skipkeys = skipkeys
-        self.ensure_ascii = ensure_ascii
-        self.check_circular = check_circular
-        self.allow_nan = allow_nan
-        self.sort_keys = sort_keys
-        self.indent = indent
-        if separators is not None:
-            self.item_separator, self.key_separator = separators
-        if default is not None:
-            self.default = default
-        self.encoding = encoding
-
-    def default(self, o):
-        """Implement this method in a subclass such that it returns
-        a serializable object for ``o``, or calls the base implementation
-        (to raise a ``TypeError``).
-
-        For example, to support arbitrary iterators, you could
-        implement default like this::
-
-            def default(self, o):
-                try:
-                    iterable = iter(o)
-                except TypeError:
-                    pass
-                else:
-                    return list(iterable)
-                return JSONEncoder.default(self, o)
-
-        """
-        raise TypeError(repr(o) + " is not JSON serializable")
-
-    def encode(self, o):
-        """Return a JSON string representation of a Python data structure.
-