review/web_ui.py @ 757e193ec1b1

web: comments, styling, and multiline comments, OH MY
author Steve Losh <steve@stevelosh.com>
date Sun, 13 Jun 2010 04:42:29 -0400
parents 7521b06e6b18
children 647c0ea1bce7
from __future__ import with_statement

"""The review extension's web UI."""

import sys, os
from hashlib import md5

from mercurial import commands, templatefilters
from mercurial.node import short
from mercurial.util import email

import api

def unbundle():
    package_path = os.path.split(os.path.realpath(__file__))[0]
    template_path = os.path.join(package_path, 'web_templates')
    media_path = os.path.join(package_path, 'web_media')
    top_path = os.path.split(package_path)[0]
    bundled_path = os.path.join(top_path, 'bundled')
    flask_path = os.path.join(bundled_path, 'flask')
    jinja2_path = os.path.join(bundled_path, 'jinja2')
    werkzeug_path = os.path.join(bundled_path, 'werkzeug')
    simplejson_path = os.path.join(bundled_path, 'simplejson')

    sys.path.insert(0, flask_path)
    sys.path.insert(0, werkzeug_path)
    sys.path.insert(0, jinja2_path)
    sys.path.insert(0, simplejson_path)

unbundle()

from flask import Flask
from flask import abort, redirect, render_template, request
app = Flask(__name__)

LOG_PAGE_LEN = 1000000

def _item_gravatar(item):
    return 'http://www.gravatar.com/avatar/%s/' % md5(email(item.author)).hexdigest()

def _line_type(line):
    return 'rem' if line[0] == '-' else 'add' if line[0] == '+' else 'con'

def _categorize_signoffs(signoffs):
    return { 'yes': len(filter(lambda s: s.opinion == 'yes', signoffs)),
             'no': len(filter(lambda s: s.opinion == 'no', signoffs)),
             'neutral': len(filter(lambda s: s.opinion == '', signoffs)),}
utils = {
    'node_short': short,
    'basename': os.path.basename,
    'md5': md5,
    'email': email,
    'templatefilters': templatefilters,
    'len': len,
    'item_gravatar': _item_gravatar,
    'line_type': _line_type,
    'categorize_signoffs': _categorize_signoffs,
    'map': map,
    'str': str,
}

datastore = None
site_read_only = False

def _render(template, **kwargs):
    return render_template(template, read_only=site_read_only, utils=utils,
        datastore=datastore, **kwargs)


@app.route('/')
def index():
    rev_max = datastore.target['tip'].rev()
    rev_min = rev_max - LOG_PAGE_LEN if rev_max >= LOG_PAGE_LEN else 0
    rcsets = [datastore[r] for r in xrange(rev_max, rev_min, -1)]
    return _render('index.html', title='', rcsets=rcsets)


def _handle_signoff(revhash):
    signoff = request.form.get('signoff', None)

    if signoff not in ['yes', 'no', 'neutral']:
        abort(400)

    if signoff == 'neutral':
        signoff = ''

    body = request.form.get('new-signoff-body', '')
    rcset = datastore[revhash]
    rcset.add_signoff(body, signoff, force=True)

    return redirect("/changeset/%s/" % revhash)

def _handle_comment(revhash):
    filename = request.form.get('filename', '')
    lines = str(request.form.get('lines', ''))
    if lines:
        lines = filter(None, [l.strip() for l in lines.split(',')])
    body = request.form['new-comment-body']
    
    if body:
        rcset = datastore[revhash]
        rcset.add_comment(body, filename, lines)
    
    return redirect("/changeset/%s/" % revhash)

@app.route('/changeset/<revhash>/', methods=['GET', 'POST'])
def changeset(revhash):
    if request.method == 'POST' and not site_read_only:
        signoff = request.form.get('signoff', None)
        if signoff:
            return _handle_signoff(revhash)
        else:
            return _handle_comment(revhash)
    
    rcset = datastore[revhash]
    rev = rcset.target[revhash]
    
    cu_signoffs = rcset.signoffs_for_current_user()
    cu_signoff = cu_signoffs[0] if cu_signoffs else None
    
    return _render('changeset.html',
        title='%s:%s' % (rev.rev(), short(rev.node())),
        rcset=rcset, rev=rev, cu_signoff=cu_signoff
    )


@app.route('/pull/', methods=['POST'])
def pull():
    if not site_read_only:
        path = request.form['path']
        commands.pull(datastore.repo.ui, datastore.repo, path, update=True)
    return redirect('/')

@app.route('/push/', methods=['POST'])
def push():
    if not site_read_only:
        path = request.form['path']
        commands.push(datastore.repo.ui, datastore.repo, path)
    return redirect('/')


def load_interface(ui, repo, read_only=False, open=False,
        address='127.0.0.1', port=8080):
    if open:
        import webbrowser
        webbrowser.open('http://localhost:%d/' % port)
        
    global datastore, site_read_only
    datastore = api.ReviewDatastore(ui, repo)
    site_read_only = read_only

    app.debug = ui.debugflag
    if app.debug:
        from flaskext.lesscss import lesscss
        lesscss(app)
    app.run(host=address, port=port)