changeset 138:c77a679e9cfa

Revamped templated hgweb
author mpm@selenic.com
date Mon, 23 May 2005 16:00:02 -0800
parents b45b1b00fc9e
children 17e66e1a0382
files mercurial/hgweb.py templates/changelog.tmpl templates/changelogentry.tmpl templates/changeset.tmpl templates/fileannotate.tmpl templates/filediff.tmpl templates/filelog.tmpl templates/filelogentry.tmpl templates/filerevision.tmpl templates/manifest.tmpl templates/map templates/template-vars.txt
diffstat 20 files changed, 730 insertions(+), 409 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/hgweb.py	Sun May 22 08:13:38 2005 -0800
+++ b/mercurial/hgweb.py	Mon May 23 16:00:02 2005 -0800
@@ -11,16 +11,45 @@
 cgitb.enable()
 
 import os, cgi, time, re, difflib, sys, zlib
-from mercurial import hg, mdiff
+from mercurial.hg import *
+
+def age(t):
+    def plural(t, c):
+        if c == 1: return t
+        return t + "s"
+    def fmt(t, c):
+        return "%d %s" % (c, plural(t, c))
+
+    now = time.time()
+    delta = max(1, int(now - t))
+
+    scales = [["second", 1],
+              ["minute", 60],
+              ["hour", 3600],
+              ["day", 3600 * 24],
+              ["week", 3600 * 24 * 7],
+              ["month", 3600 * 24 * 30],
+              ["year", 3600 * 24 * 365]]
+
+    scales.reverse()
+
+    for t, s in scales:
+        n = delta / s
+        if n >= 1: return fmt(t, n)
 
 def nl2br(text):
-    return re.sub('\n', '<br />', text)
+    return text.replace('\n', '<br/>')
 
 def obfuscate(text):
-    l = []
-    for c in text:
-        l.append('&#%d;' % ord(c))
-    return ''.join(l)
+    return ''.join([ '&#%d' % ord(c) for c in text ])
+
+def up(p):
+    if p[0] != "/": p = "/" + p
+    if p[-1] == "/": p = p[:-1]
+    up = os.path.dirname(p)
+    if up == "/":
+        return "/"
+    return up + "/"
 
 def httphdr(type):
     print 'Content-type: %s\n' % type
@@ -33,365 +62,451 @@
         else:
             sys.stdout.write(str(thing))
 
-class template:
-    def __init__(self, tmpl_dir):
-        self.tmpl_dir = tmpl_dir
-    def do_page(self, tmpl_fn, **map):
-        txt = file(os.path.join(self.tmpl_dir, tmpl_fn)).read()
-        while txt:
-            m = re.search(r"#([a-zA-Z0-9]+)#", txt)
+def template(tmpl, **map):
+    while tmpl:
+        m = re.search(r"#([a-zA-Z0-9]+)#", tmpl)
+        if m:
+            yield tmpl[:m.start(0)]
+            v = map.get(m.group(1), "")
+            yield callable(v) and v() or v
+            tmpl = tmpl[m.end(0):]
+        else:
+            yield tmpl
+            return
+
+class templater:
+    def __init__(self, mapfile):
+        self.cache = {}
+        self.map = {}
+        self.base = os.path.dirname(mapfile)
+        
+        for l in file(mapfile):
+            m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
             if m:
-                yield txt[:m.start(0)]
-                v = map.get(m.group(1), "")
-                if callable(v):
-                   for y in v(**map): yield y
+                self.cache[m.group(1)] = m.group(2)
+            else:
+                m = re.match(r'(\S+)\s*=\s*(\S+)', l)
+                if m:
+                    self.map[m.group(1)] = os.path.join(self.base, m.group(2))
                 else:
-                   yield v
-                txt = txt[m.end(0):]
-            else:
-                yield txt
-                txt = ''
+                    raise "unknown map entry '%s'"  % l
 
-class page:
-    def __init__(self, tmpl_dir = "", type="text/html", title="Mercurial Web", 
-            charset="ISO-8859-1"):
-        self.tmpl = template(tmpl_dir)
+    def __call__(self, t, **map):
+        try:
+            tmpl = self.cache[t]
+        except KeyError:
+            tmpl = self.cache[t] = file(self.map[t]).read()
+        return template(tmpl, **map)
+        
+class hgweb:
+    maxchanges = 20
+    maxfiles = 10
 
-        print 'Content-type: %s; charset=%s\n' % (type, charset)
-        write(self.tmpl.do_page('htmlstart.tmpl', title = title))
+    def __init__(self, path, name, templatemap):
+        self.reponame = name
+        self.repo = repository(ui(), path)
+        self.t = templater(templatemap)
 
-    def endpage(self):
-        print '</BODY>'
-        print '</HTML>'
+    def date(self, cs):
+        return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
+
+    def listfiles(self, files, mf):
+        for f in files[:self.maxfiles]:
+            yield self.t("filenodelink", node = hex(mf[f]), file = f)
+        if len(files) > self.maxfiles:
+            yield self.t("fileellipses")
+
+    def listfilediffs(self, files, changeset):
+        for f in files[:self.maxfiles]:
+            yield self.t("filedifflink", node = hex(changeset), file = f)
+        if len(files) > self.maxfiles:
+            yield self.t("fileellipses")
+
+    def diff(self, node1, node2, files):
+        def filterfiles(list, files):
+            l = [ x for x in list if x in files ]
+            
+            for f in files:
+                if f[-1] != os.sep: f += os.sep
+                l += [ x for x in list if x.startswith(f) ]
+            return l
 
-    def show_diff(self, a, b, fn):
-        a = a.splitlines(1)
-        b = b.splitlines(1)
-        l = difflib.unified_diff(a, b, fn, fn)
-        print '<pre>'
-        for line in l:
-            line = cgi.escape(line[:-1])
-            if line.startswith('+'):
-                print '<span class="plusline">%s</span>' % (line, )
-            elif line.startswith('-'):
-                print '<span class="minusline">%s</span>' % (line, )
-            elif line.startswith('@'):
-                print '<span class="atline">%s</span>' % (line, )
-            else:
-                print line
-        print '</pre>'
+        def prettyprint(diff):
+            for l in diff.splitlines(1):
+                line = cgi.escape(l)
+                if line.startswith('+'):
+                    yield self.t("difflineplus", line = line)
+                elif line.startswith('-'):
+                    yield self.t("difflineminus", line = line)
+                elif line.startswith('@'):
+                    yield self.t("difflineat", line = line)
+                else:
+                    yield self.t("diffline", line = line)
 
-class errpage(page):
-    def __init__(self, tmpl_dir):
-        page.__init__(self, tmpl_dir, title="Mercurial Web Error Page")
+        r = self.repo
+        cl = r.changelog
+        mf = r.manifest
+        change1 = cl.read(node1)
+        change2 = cl.read(node2)
+        mmap1 = mf.read(change1[0])
+        mmap2 = mf.read(change2[0])
+        date1 = self.date(change1)
+        date2 = self.date(change2)
 
-class change_list(page):
-    def __init__(self, repo, tmpl_dir, reponame, numchanges = 50):
-        page.__init__(self, tmpl_dir)
-        self.repo = repo
-        self.numchanges = numchanges
-        write(self.tmpl.do_page('changestitle.tmpl', reponame=reponame))
+        c, a, d = r.diffrevs(node1, node2)
+        c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
 
-    def content(self, hi=None):
-        cl = []
-        count = self.repo.changelog.count()
-        if not hi:
-            hi = count
-        elif hi < self.numchanges:
-            hi = self.numchanges
-
-        start = 0
-        if hi - self.numchanges >= 0:
-            start = hi - self.numchanges
+        for f in c:
+            to = r.file(f).read(mmap1[f])
+            tn = r.file(f).read(mmap2[f])
+            yield prettyprint(mdiff.unidiff(to, date1, tn, date2, f))
+        for f in a:
+            to = ""
+            tn = r.file(f).read(mmap2[f])
+            yield prettyprint(mdiff.unidiff(to, date1, tn, date2, f))
+        for f in d:
+            to = r.file(f).read(mmap1[f])
+            tn = ""
+            yield prettyprint(mdiff.unidiff(to, date1, tn, date2, f))
 
-        nav = "Displaying Revisions: %d-%d" % (start, hi-1)
-        if start != 0:
-            nav = ('<a href="?cmd=changes;hi=%d">Previous %d</a>&nbsp;&nbsp;' \
-                    % (start, self.numchanges)) + nav
-        if hi != count:
-            if hi + self.numchanges <= count:
-                nav += '&nbsp;&nbsp;<a href="?cmd=changes;hi=%d">Next %d</a>' \
-                        % (hi + self.numchanges, self.numchanges)
-            else:
-                nav += '&nbsp;&nbsp;<a href="?cmd=changes">Next %d</a>' % \
-                        self.numchanges
+    def changelog(self, pos=None):
+        def changenav():
+            def seq(factor = 1):
+                yield 1 * factor
+                yield 2 * factor
+                yield 5 * factor
+                for f in seq(factor * 10):
+                    yield f
+                    
+            linear = range(0, count - 2, self.maxchanges)[0:8]
+
+            for i in linear:
+                yield self.t("naventry", rev = max(i, 1))
 
-        print '<center>%s</center>' % nav
+            for s in seq():
+                if s > count - 2: break
+                if s > linear[-1]:
+                    yield self.t("naventry", rev = s)
+                    
+            yield self.t("naventry", rev = count - 1)
 
-        for i in xrange(start, hi):
-            n = self.repo.changelog.node(i)
-            cl.append((n, self.repo.changelog.read(n)))
-        cl.reverse()
+        def changelist():
+            cl = self.repo.changelog
+            l = [] # build a list in forward order for efficiency
+            for i in range(start, end + 1):
+                n = cl.node(i)
+                changes = cl.read(n)
+                hn = hex(n)
+                p1, p2 = cl.parents(n)
+                t = float(changes[2].split(' ')[0])
 
-        print '<table summary="" width="100%" align="center">'
-        for n, ch in cl:
-            print '<tr><td>'
-            self.change_table(n, ch)
-            print '</td></tr>'
-        print '</table>'
+                l.insert(0, self.t(
+                    'changelogentry',
+                    author = obfuscate(changes[1]),
+                    shortdesc = cgi.escape(changes[4].splitlines()[0]),
+                    age = age(t),
+                    p1 = hex(p1), p2 = hex(p2),
+                    p1rev = cl.rev(p1), p2rev = cl.rev(p2),
+                    manifest = hex(changes[0]),
+                    desc = nl2br(cgi.escape(changes[4])),
+                    date = time.asctime(time.gmtime(t)),
+                    files = self.listfilediffs(changes[3], n),
+                    rev = i,
+                    node = hn))
+
+            yield l
 
-        print '<center>%s</center>' % nav
+        count = self.repo.changelog.count()
+        pos = pos or count - 1
+        end = min(pos, count - 1)
+        start = max(0, pos - self.maxchanges)
+        end = min(count - 1, start + self.maxchanges)
+
+        yield self.t('changelog', repo = self.reponame, changenav = changenav,
+                     rev = pos, changesets = count, changelist = changelist)
 
-    def change_table(self, nodeid, changes):
-        hn = hg.hex(nodeid)
-        i = self.repo.changelog.rev(nodeid)
-        (h1, h2) = [ hg.hex(x) for x in self.repo.changelog.parents(nodeid) ]
-        datestr = time.asctime(time.gmtime(float(changes[2].split(' ')[0])))
+    def changeset(self, nodeid):
+        n = bin(nodeid)
+        cl = self.repo.changelog
+        changes = cl.read(n)
+        p1, p2 = cl.parents(n)
+        p1rev, p2rev = cl.rev(p1), cl.rev(p2)
+        t = float(changes[2].split(' ')[0])
+        
         files = []
+        mf = self.repo.manifest.read(changes[0])
         for f in changes[3]:
-            files.append('<a href="?cmd=file;cs=%s;fn=%s">%s</a>&nbsp;&nbsp;' \
-                % (hn, f, cgi.escape(f)))
-        write(self.tmpl.do_page('change_table.tmpl',
-                author=obfuscate(changes[1]),
-                desc=nl2br(cgi.escape(changes[4])), date=datestr, 
-                files=' '.join(files), revnum=i, revnode=hn))
+            files.append(self.t("filenodelink",
+                                filenode = hex(mf[f]), file = f))
+
+        def diff():
+            yield self.diff(p1, n, changes[3])
 
-class checkin(page):
-    def __init__(self, repo, tmpl_dir, nodestr):
-        page.__init__(self, tmpl_dir)
-        self.repo = repo
-        self.node = hg.bin(nodestr)
-        self.nodestr = nodestr
-        print '<h3>Checkin: %s</h3>' % nodestr
+        yield self.t('changeset',
+                     diff = diff,
+                     rev = cl.rev(n),
+                     node = nodeid,
+                     shortdesc = cgi.escape(changes[4].splitlines()[0]),
+                     p1 = hex(p1), p2 = hex(p2),
+                     p1rev = cl.rev(p1), p2rev = cl.rev(p2),
+                     manifest = hex(changes[0]),
+                     author = obfuscate(changes[1]),
+                     desc = nl2br(cgi.escape(changes[4])),
+                     date = time.asctime(time.gmtime(t)),
+                     files = files)
 
-    def content(self):
-        changes = self.repo.changelog.read(self.node)
-        i = self.repo.changelog.rev(self.node)
-        parents = self.repo.changelog.parents(self.node)
-        (h1, h2) = [ hg.hex(x) for x in parents ]
-        (i1, i2) = [ self.repo.changelog.rev(x) for x in parents ]
-        datestr = time.asctime(time.gmtime(float(changes[2].split(' ')[0])))
-        mf = self.repo.manifest.read(changes[0])
-        files = []
-        for f in changes[3]:
-            files.append('<a href="?cmd=file;nd=%s;fn=%s">%s</a>&nbsp;&nbsp;' \
-                % (hg.hex(mf[f]), f, cgi.escape(f)))
-        p2link = h2
-        if i2 != -1:
-            p2link = '<a href="?cmd=chkin;nd=%s">%s</a>' % (h2, h2)
+    def filelog(self, f, filenode):
+        cl = self.repo.changelog
+        fl = self.repo.file(f)
+        count = fl.count()
+
+        def entries():
+            l = []
+            for i in range(count):
+
+                n = fl.node(i)
+                lr = fl.linkrev(n)
+                cn = cl.node(lr)
+                cs = cl.read(cl.node(lr))
+                p1, p2 = fl.parents(n)
+                t = float(cs[2].split(' ')[0])
 
-        write(self.tmpl.do_page('checkin.tmpl', revnum=i, revnode=self.nodestr,
-                p1num=i1, p1node=h1, p2num=i2, p2node=h2, p2link=p2link,
-                mfnum=self.repo.manifest.rev(changes[0]), 
-                mfnode=hg.hex(changes[0]), author=obfuscate(changes[1]),
-                desc=nl2br(cgi.escape(changes[4])), date=datestr,
-                files=' '.join(files)))
+                l.insert(0, self.t("filelogentry",
+                                   filenode = hex(n),
+                                   filerev = i,
+                                   file = f,
+                                   node = hex(cn),
+                                   author = obfuscate(cs[1]),
+                                   age = age(t),
+                                   date = time.asctime(time.gmtime(t)),
+                                   shortdesc = cgi.escape(cs[4].splitlines()[0]),
+                                   p1 = hex(p1), p2 = hex(p2),
+                                   p1rev = fl.rev(p1), p2rev = fl.rev(p2)))
+
+            yield l
+
+        yield self.t("filelog",
+                     file = f,
+                     filenode = filenode,
+                     entries = entries)
 
-        (c, a, d) = self.repo.diffrevs(parents[0], self.node)
-        change = self.repo.changelog.read(parents[0])
-        mf2 = self.repo.manifest.read(change[0])
-        for f in c:
-            self.show_diff(self.repo.file(f).read(mf2[f]), \
-                    self.repo.file(f).read(mf[f]), f)
-        for f in a:
-            self.show_diff('', self.repo.file(f).read(mf[f]), f)
-        for f in d:
-            self.show_diff(self.repo.file(f).read(mf2[f]), '', f)
+    def filerevision(self, f, node):
+        fl = self.repo.file(f)
+        n = bin(node)
+        text = cgi.escape(fl.read(n))
+        changerev = fl.linkrev(n)
+        cl = self.repo.changelog
+        cn = cl.node(changerev)
+        cs = cl.read(cn)
+        p1, p2 = fl.parents(n)
+        t = float(cs[2].split(' ')[0])
+        mfn = cs[0]
+        
+        yield self.t("filerevision", file = f,
+                     filenode = node,
+                     path = up(f),
+                     text = text,
+                     rev = changerev,
+                     node = hex(cn),
+                     manifest = hex(mfn),
+                     author = obfuscate(cs[1]),
+                     age = age(t),
+                     date = time.asctime(time.gmtime(t)),
+                     shortdesc = cgi.escape(cs[4].splitlines()[0]),
+                     p1 = hex(p1), p2 = hex(p2),
+                     p1rev = fl.rev(p1), p2rev = fl.rev(p2))
+
 
-class filepage(page):
-    def __init__(self, repo, tmpl_dir, fn, node=None, cs=None):
-        page.__init__(self, tmpl_dir)
-        self.repo = repo
-        self.fn = fn
-        if cs: 
-            chng = self.repo.changelog.read(hg.bin(cs))
-            mf = self.repo.manifest.read(chng[0])
-            self.node = mf[self.fn]
-            self.nodestr = hg.hex(self.node)
-        else:
-            self.nodestr = node
-            self.node = hg.bin(node)
-        print '<div class="filename">%s (%s)</div>' % \
-                (cgi.escape(self.fn), self.nodestr, )
-        print '<a href="?cmd=hist;fn=%s">history</a><br />' % self.fn
-        print '<a href="?cmd=ann;fn=%s;nd=%s">annotate</a><br />' % \
-                (self.fn, self.nodestr)
+    def fileannotate(self, f, node):
+        bcache = {}
+        ncache = {}
+        fl = self.repo.file(f)
+        n = bin(node)
+        changerev = fl.linkrev(n)
+
+        cl = self.repo.changelog
+        cn = cl.node(changerev)
+        cs = cl.read(cn)
+        p1, p2 = fl.parents(n)
+        t = float(cs[2].split(' ')[0])
+        mfn = cs[0]
 
-    def content(self):
-        print '<pre>'
-        print cgi.escape(self.repo.file(self.fn).read(self.node))
-        print '</pre>'
+        def annotate():
+            for r, l in fl.annotate(n):
+                try:
+                    cnode = ncache[r]
+                except KeyError:
+                    cnode = ncache[r] = self.repo.changelog.node(r)
+                    
+                try:
+                    name = bcache[r]
+                except KeyError:
+                    cl = self.repo.changelog.read(cnode)
+                    name = cl[1]
+                    f = name.find('@')
+                    if f >= 0:
+                        name = name[:f]
+                    bcache[r] = name
 
-class annpage(page):
-    def __init__(self, repo, tmpl_dir, fn, node):
-        page.__init__(self, tmpl_dir)
-        self.repo = repo
-        self.fn = fn
-        self.nodestr = node
-        self.node = hg.bin(node)
-        print '<div class="annotation">Annotated: %s (%s)</div>' % \
-                (cgi.escape(self.fn), self.nodestr, )
+                yield self.t("annotateline",
+                             node = hex(cnode),
+                             rev = r,
+                             author = name,
+                             file = f,
+                             line = cgi.escape(l))
+
+        yield self.t("fileannotate",
+                     file = f,
+                     filenode = node,
+                     annotate = annotate,
+                     path = up(f),
+                     rev = changerev,
+                     node = hex(cn),
+                     manifest = hex(mfn),
+                     author = obfuscate(cs[1]),
+                     age = age(t),
+                     date = time.asctime(time.gmtime(t)),
+                     shortdesc = cgi.escape(cs[4].splitlines()[0]),
+                     p1 = hex(p1), p2 = hex(p2),
+                     p1rev = fl.rev(p1), p2rev = fl.rev(p2))
 
-    def content(self):
-        print '<pre>'
-        for n, l in self.repo.file(self.fn).annotate(self.node):
-            cnode = self.repo.changelog.lookup(n)
-            write(self.tmpl.do_page('annline.tmpl', cnode=hg.hex(cnode),
-                    cnum='% 6s' % n, fn=self.fn, line=cgi.escape(l[:-1])))
-        print '</pre>'
+    def manifest(self, mnode, path):
+        mf = self.repo.manifest.read(bin(mnode))
+        rev = self.repo.manifest.rev(bin(mnode))
+        node = self.repo.changelog.node(rev)
+
+        dirs = {}
+        files = {}
+        short = {}
 
-class mfpage(page):
-    def __init__(self, repo, tmpl_dir, node):
-        page.__init__(self, tmpl_dir)
-        self.repo = repo
-        self.nodestr = node
-        self.node = hg.bin(node)
+        p = path[1:]
+        l = len(p)
 
-    def content(self):
-        mf = self.repo.manifest.read(self.node)
-        fns = mf.keys()
-        fns.sort()
-        write(self.tmpl.do_page('mftitle.tmpl', node = self.nodestr))
-        for f in fns:
-            write(self.tmpl.do_page('mfentry.tmpl', fn=f, node=hg.hex(mf[f])))
+        for f,n in mf.items():
+            if f[:l] != p:
+                continue
+            remain = f[l:]
+            if "/" in remain:
+                short = remain[:remain.find("/") + 1] # bleah
+                dirs[short] = 1
+            else:
+                short = os.path.basename(remain)
+                files[short] = (f, n)
 
-class histpage(page):
-    def __init__(self, repo, tmpl_dir, fn):
-        page.__init__(self, tmpl_dir)
-        self.repo = repo
-        self.fn = fn
+        def dirlist():
+            dl = dirs.keys()
+            dl.sort()
+            
+            for d in dl:
+                yield self.t("manifestdirentry",
+                             path = os.path.join(path, d),
+                             manifest = mnode, basename = d[:-1])
 
-    def content(self):
-        print '<div class="filehist">File History: %s</div>' % self.fn
-        r = self.repo.file(self.fn)
-        print '<br />'
-        print '<table summary="" width="100%" align="center">'
-        for i in xrange(r.count()-1, -1, -1):
-            print '<tr><td>'
-            self.hist_ent(i, r)
-            print '</tr></td>'
-        print '</table>'
+        def filelist():
+            fl = files.keys()
+            fl.sort()
+            for f in fl:
+                full, fnode = files[f]
+                yield self.t("manifestfileentry",
+                             file = full, manifest = mnode, filenode = hex(fnode),
+                             basename = f)
+
+        yield self.t("manifest",
+                     manifest = mnode,
+                     rev = rev,
+                     node = hex(node),
+                     path = path,
+                     up = up(path),
+                     dirs = dirlist,
+                     files = filelist)
 
-    def hist_ent(self, i, r):
-        n = r.node(i)
-        (p1, p2) = r.parents(n)
-        (h, h1, h2) = map(hg.hex, (n, p1, p2))
-        (i1, i2) = map(r.rev, (p1, p2))
-        ci = r.linkrev(n)
-        cn = self.repo.changelog.node(ci)
-        cs = hg.hex(cn)
-        changes = self.repo.changelog.read(cn)
-        datestr = time.asctime(time.gmtime(float(changes[2].split(' ')[0])))
-        p2entry = ''
-        if i2 != -1:
-            p2entry = '&nbsp;&nbsp;%d:<a href="?cmd=file;nd=%s;fn=%s">%s</a>' \
-                    % (i2, h2, self.fn, h2 ),
-        write(self.tmpl.do_page('hist_ent.tmpl', author=obfuscate(changes[1]),
-                csnode=cs, desc=nl2br(cgi.escape(changes[4])), 
-                date = datestr, fn=self.fn, revnode=h, p1num = i1,
-                p1node=h1, p2entry=p2entry))
-                
-class hgweb:
-    repo_path = "."
-    numchanges = 50
-    tmpl_dir = "templates"
+    def filediff(self, file, changeset):
+        n = bin(changeset)
+        cl = self.repo.changelog
+        p1 = cl.parents(n)[0]
+        cs = cl.read(n)
+        mf = self.repo.manifest.read(cs[0])
+        
+        def diff():
+            yield self.diff(p1, n, file)
 
-    def __init__(self):
-        pass
+        yield self.t("filediff",
+                     file = file,
+                     filenode = hex(mf[file]),
+                     node = changeset,
+                     rev = self.repo.changelog.rev(n),
+                     p1 = hex(p1),
+                     p1rev = self.repo.changelog.rev(p1),
+                     diff = diff)
+                     
+    # header and footer, css
+    # add tags to things
+    # show parents
+    # diff between rev and parent in changeset and file
+    # manifest links
+    # browse at top
+    # tags -> list of changesets corresponding to tags
+    # find tag, changeset, file
 
     def run(self):
-
         args = cgi.parse()
 
-        ui = hg.ui()
-        repo = hg.repository(ui, self.repo_path)
+        if not args.has_key('cmd') or args['cmd'][0] == 'changelog':
+            hi = self.repo.changelog.count()
+            if args.has_key('pos'):
+                hi = int(args['pos'][0])
 
-        if not args.has_key('cmd') or args['cmd'][0] == 'changes':
-            page = change_list(repo, self.tmpl_dir, 'Mercurial', 
-                    self.numchanges)
-            hi = args.get('hi', ( repo.changelog.count(), ))
-            page.content(hi = int(hi[0]))
-            page.endpage()
+            write(self.changelog(hi))
             
-        elif args['cmd'][0] == 'chkin':
-            if not args.has_key('nd'):
-                page = errpage(self.tmpl_dir)
-                print '<div class="errmsg">No Node!</div>'
-            else:
-                page = checkin(repo, self.tmpl_dir, args['nd'][0])
-                page.content()
-            page.endpage()
+        elif args['cmd'][0] == 'changeset':
+            write(self.changeset(args['node'][0]))
+
+        elif args['cmd'][0] == 'manifest':
+            write(self.manifest(args['manifest'][0], args['path'][0]))
+
+        elif args['cmd'][0] == 'filediff':
+            write(self.filediff(args['file'][0], args['node'][0]))
 
         elif args['cmd'][0] == 'file':
-            if not (args.has_key('nd') and args.has_key('fn')) and \
-                    not (args.has_key('cs') and args.has_key('fn')):
-                page = errpage(self.tmpl_dir)
-                print '<div class="errmsg">Invalid Args!</div>'
-            else:
-                if args.has_key('nd'):
-                    page = filepage(repo, self.tmpl_dir, 
-                            args['fn'][0], node=args['nd'][0])
-                else:
-                    page = filepage(repo, self.tmpl_dir,
-                            args['fn'][0], cs=args['cs'][0])
-                page.content()
-            page.endpage()
+            write(self.filerevision(args['file'][0], args['filenode'][0]))
 
-        elif args['cmd'][0] == 'mf':
-            if not args.has_key('nd'):
-                page = errpage(self.tmpl_dir)
-                print '<div class="errmsg">No Node!</div>'
-            else:
-                page = mfpage(repo, self.tmpl_dir, args['nd'][0])
-                page.content()
-            page.endpage()
+        elif args['cmd'][0] == 'annotate':
+            write(self.fileannotate(args['file'][0], args['filenode'][0]))
 
-        elif args['cmd'][0] == 'hist':
-            if not args.has_key('fn'):
-                page = errpage(self.tmpl_dir)
-                print '<div class="errmsg">No Filename!</div>'
-            else:
-                page = histpage(repo, self.tmpl_dir, args['fn'][0])
-                page.content()
-            page.endpage()
-
-        elif args['cmd'][0] == 'ann':
-            if not args.has_key('fn'):
-                page = errpage(self.tmpl_dir)
-                print '<div class="errmsg">No Filename!</div>'
-            elif not args.has_key('nd'):
-                page = errpage(self.tmpl_dir)
-                print '<div class="errmsg">No Node!</div>'
-            else:
-                page = annpage(repo, self.tmpl_dir, args['fn'][0], 
-                        args['nd'][0])
-                page.content()
-            page.endpage()
+        elif args['cmd'][0] == 'filelog':
+            write(self.filelog(args['file'][0], args['filenode'][0]))
 
         elif args['cmd'][0] == 'branches':
             httphdr("text/plain")
             nodes = []
             if args.has_key('nodes'):
-                nodes = map(hg.bin, args['nodes'][0].split(" "))
-            for b in repo.branches(nodes):
-                print " ".join(map(hg.hex, b))
+                nodes = map(bin, args['nodes'][0].split(" "))
+            for b in self.repo.branches(nodes):
+                sys.stdout.write(" ".join(map(hex, b)) + "\n")
 
         elif args['cmd'][0] == 'between':
             httphdr("text/plain")
             nodes = []
             if args.has_key('pairs'):
-                pairs = [ map(hg.bin, p.split("-"))
+                pairs = [ map(bin, p.split("-"))
                           for p in args['pairs'][0].split(" ") ]
-            for b in repo.between(pairs):
-                print " ".join(map(hg.hex, b))
+            for b in self.repo.between(pairs):
+                sys.stdout.write(" ".join(map(hex, b)) + "\n")
 
         elif args['cmd'][0] == 'changegroup':
             httphdr("application/hg-changegroup")
             nodes = []
             if args.has_key('roots'):
-                nodes = map(hg.bin, args['roots'][0].split(" "))
+                nodes = map(bin, args['roots'][0].split(" "))
 
             z = zlib.compressobj()
-            for chunk in repo.changegroup(nodes):
+            for chunk in self.repo.changegroup(nodes):
                 sys.stdout.write(z.compress(chunk))
 
             sys.stdout.write(z.flush())
 
         else:
-            page = errpage(self.tmpl_dir)
-            print '<div class="errmsg">unknown command: %s</div>' % \
-                    cgi.escape(args['cmd'][0])
-            page.endpage()
+            write(self.t("error"))
 
 if __name__ == "__main__":
     hgweb().run()
--- a/templates/annline.tmpl	Sun May 22 08:13:38 2005 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-<a class="revnumlink" href="?cmd=chkin;nd=#cnode#">#cnum#</a>:<a class="annlinelink" href="?cmd=file;fn=#fn#;cs=#cnode#">#line#</a>
--- a/templates/change_table.tmpl	Sun May 22 08:13:38 2005 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-<table summary="" width="100%" border="1">
-    <tr>
-        <td valign="top" width="10%">author:</td>
-        <td valign="top" width="20%%">#author#</td>
-        <td valign="top" width="10%">description:</td>
-        <td width="60%">
-            <a href="?cmd=chkin;nd=#revnode#">#desc#</a>
-        </td>
-    </tr>
-    <tr>
-        <td>date:</td>
-        <td>#date# UTC</td>
-        <td valign="top">files:</td>
-        <td valign="top">#files#</td>
-    </tr>
-    <tr>
-        <td>revision:</td>
-        <td colspan="3">
-            #revnum#:<a href="?cmd=chkin;nd=#revnode#">#revnode#</a>
-        </td>
-    </tr>
-</table>
-<br />
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/changelog.tmpl	Mon May 23 16:00:02 2005 -0800
@@ -0,0 +1,16 @@
+Content-Type: text/html
+
+<html>
+<body>
+<h2>changelog for #repo#</h2>
+
+navigate: #changenav#<br>
+
+<table>
+#changelist#
+</table>
+
+navigate: #changenav#<br>
+
+</body>
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/changelogentry.tmpl	Mon May 23 16:00:02 2005 -0800
@@ -0,0 +1,26 @@
+<tr>
+ <td align=right width="15%"><b>#age# ago:</b></td>
+ <td><b>#shortdesc#</b></td</tr>
+<tr>
+ <td align=right>revision:</td>
+ <td><a href="?cmd=changeset;node=#node#">#rev#:#node#</a></td></tr>
+<tr>
+ <td align=right>parent:</td>
+ <td><a href="?cmd=changeset;node=#p1#">#p1rev#:#p1#</a></td></tr>
+<tr>
+ <td align=right>parent:</td>
+ <td><a href="?cmd=changeset;node=#p2#">#p2rev#:#p2#</a></td></tr>
+<tr>
+ <td align=right>manifest:</td>
+ <td><a href="?cmd=manifest;manifest=#manifest#;path=/">#rev#:#manifest#</a></td></tr>
+<tr>
+ <td align=right>author:</td>
+ <td>#author#</td></tr>
+<tr>
+ <td align=right>date:</td>
+ <td>#date#</td></tr>
+<tr>
+ <td align=right valign=top>files:</td>
+ <td>#files#</td></tr>
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/changeset.tmpl	Mon May 23 16:00:02 2005 -0800
@@ -0,0 +1,46 @@
+Content-type: text/html
+
+<html>
+<body>
+<a href="?cmd=changelog&pos=#rev#">changelog</a>
+<a href="?cmd=manifest;manifest=#manifest#;path=/">manifest</a>
+
+<h2>changeset: #shortdesc#</h2>
+
+<table>
+<tr>
+ <td align=right>revision:</td>
+ <td><a href="?cmd=changeset;node=#node#">#rev#:#node#</a></td></tr>
+<tr>
+ <td align=right>parent:</td>
+ <td><a href="?cmd=changeset;node=#p1#">#p1rev#:#p1#</a></td></tr>
+<tr>
+ <td align=right>parent:</td>
+ <td><a href="?cmd=changeset;node=#p2#">#p2rev#:#p2#</a></td></tr>
+<tr>
+ <td align=right>manifest:</td>
+ <td><a href="?cmd=manifest;manifest=#manifest#;path=/">#rev#:#manifest#</a></td></tr>
+<tr>
+ <td align=right>author:</td>
+ <td>#author#</td></tr>
+<tr>
+ <td align=right>date:</td>
+ <td>#date#</td></tr>
+<tr>
+ <td align=right valign=top>files:</td>
+ <td>#files#</td></tr>
+<tr>
+ <td align=right valign=top>description:</td>
+ <td>#desc#</td></tr>
+</table>
+
+<hr />
+
+<pre>
+#diff#
+</pre>
+
+</body>
+</html
+
+
--- a/templates/changestitle.tmpl	Sun May 22 08:13:38 2005 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-<h3>Changes For: #reponame#</h3>
--- a/templates/checkin.tmpl	Sun May 22 08:13:38 2005 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-<table summary="" width="100%" border="1">
-    <tr>
-        <td>revision:</td>
-        <td colspan="3">
-            #revnum#: <a href="?cmd=chkin;nd=#revnode#">#revnode#</a>
-        </td>
-    </tr>
-    <tr>
-        <td>parent(s):</td>
-        <td colspan="3">
-            #p1num#:
-            <a href="?cmd=chkin;nd=#p1node#">#p1node#</a>
-            &nbsp;&nbsp;#p2num#:#p2link#
-        </td>
-    </tr>
-    <tr>
-        <td>manifest:</td>
-        <td colspan="3">
-            #mfnum#: <a href="?cmd=mf;nd=#mfnode#">#mfnode#</a>
-        </td>
-    </tr>
-    <tr>
-        <td valign="top" width="10%">author:</td>
-        <td valign="top" width="20%%">#author#</td>
-        <td valign="top" width="10%">description:</td>
-        <td width="60%"><a href="?cmd=chkin;nd=#revnode#">#desc#</a></td>
-    </tr>
-    <tr>
-        <td valign="top">date:</td>
-        <td valign="top">#date# UTC</td>
-        <td valign="top">files:</td>
-        <td valign="top">#files#</td>
-    </tr>
-</table>
-<br />
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/fileannotate.tmpl	Mon May 23 16:00:02 2005 -0800
@@ -0,0 +1,42 @@
+Content-type: text/html
+
+<html>
+<body>
+
+<a href="?cmd=changelog&rev=#rev#">changelog</a>
+<a href="?cmd=changeset&node=#node#">changeset</a>
+<a href="?cmd=manifest&manifest=#manifest#;path=#path#">manifest</a>
+<a href="?cmd=file&file=#file#&filenode=#filenode#">file</a>
+<a href="?cmd=filelog&file=#file#;filenode=#filenode#">revisions</a>
+
+<h2>Annotate #file# (#filenode#)</h2>
+
+<table>
+<tr>
+ <td align=right>changeset:</td>
+ <td><a href="?cmd=changeset;node=#node#">#rev#:#node#</a></td></tr>
+<tr>
+ <td align=right>parent:</td>
+ <td><a href="?cmd=file;file=#file#;node=#p1#">#p1rev#:#p1#</a></td></tr>
+<tr>
+ <td align=right>parent:</td>
+ <td><a href="?cmd=file;file=#file#;node=#p2#">#p2rev#:#p2#</a></td></tr>
+<tr>
+ <td align=right>manifest:</td>
+ <td><a href="?cmd=manifest;manifest=#manifest#;path=/">#rev#:#manifest#</a></td></tr>
+<tr>
+ <td align=right>author:</td>
+ <td>#author#</td></tr>
+<tr>
+ <td align=right>date:</td>
+ <td>#date#</td></tr>
+</table>
+
+<hr />
+
+<table>
+#annotate#
+</table>
+
+</body>
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/filediff.tmpl	Mon May 23 16:00:02 2005 -0800
@@ -0,0 +1,30 @@
+Content-type: text/html
+
+<html>
+<body>
+<a href="?cmd=changelog&rev=#rev#">changelog</a>
+<a href="?cmd=changeset&node=#node#">changeset</a>
+<a href="?cmd=file&file=#file#&filenode=#filenode#">file</a>
+<a href="?cmd=filelog&file=#file#&filenode=#filenode#">revisions</a>
+<a href="?cmd=annotate&file=#file#&filenode=#filenode#">annotate</a>
+
+<h2>#file#</h2>
+
+<table>
+<tr>
+ <td align=right>revision:</td>
+ <td><a href="?cmd=changeset;node=#node#">#rev#:#node#</a></td></tr>
+<tr>
+ <td align=right>parent:</td>
+ <td><a href="?cmd=changeset;node=#p1#">#p1rev#:#p1#</a></td></tr>
+</table>
+
+<hr />
+<pre>
+#diff#
+</pre>
+
+</body>
+</html
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/filelog.tmpl	Mon May 23 16:00:02 2005 -0800
@@ -0,0 +1,17 @@
+Content-type: text/html
+
+<html>
+<body>
+
+<a href="?cmd=changelog">changelog</a>
+<a href="?cmd=file&file=#file#&filenode=#filenode#">file</a>
+<a href="?cmd=annotate&file=#file#&filenode=#filenode#">annotate</a>
+
+<h2>#file# revision history</h2>
+
+<table>
+#entries#
+</table>
+
+</body>
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/filelogentry.tmpl	Mon May 23 16:00:02 2005 -0800
@@ -0,0 +1,18 @@
+<tr>
+ <td align=right width="15%"><b>#age# ago:</b></td>
+ <td><b><a href="?cmd=changeset;node=#changeset#">#shortdesc#</a></b></td</tr>
+<tr>
+ <td align=right>revision:</td>
+ <td><a href="?cmd=file;file=#file#;filenode=#filenode#">#filerev#:#filenode#</a>
+<a href="?cmd=filediff;file=#file#;node=#node#">(diff)</a>
+<a href="?cmd=annotate;file=#file#;filenode=#filenode#">(annotate)</a>
+</td></tr>
+<tr>
+ <td align=right>author:</td>
+ <td>#author#</td></tr>
+<tr>
+ <td align=right>date:</td>
+ <td>#date#</td></tr>
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/filerevision.tmpl	Mon May 23 16:00:02 2005 -0800
@@ -0,0 +1,42 @@
+Content-type: text/html
+
+<html>
+<body>
+
+<a href="?cmd=changelog&rev=#rev#">changelog</a>
+<a href="?cmd=changeset&node=#node#">changeset</a>
+<a href="?cmd=manifest&manifest=#manifest#;path=#path#">manifest</a>
+<a href="?cmd=filelog&file=#file#;filenode=#filenode#">revisions</a>
+<a href="?cmd=annotate&file=#file#&filenode=#filenode#">annotate</a>
+
+<h2>#file# (revision #filenode#)</h2>
+
+<table>
+<tr>
+ <td align=right>changeset:</td>
+ <td><a href="?cmd=changeset;node=#node#">#rev#:#node#</a></td></tr>
+<tr>
+ <td align=right>parent:</td>
+ <td><a href="?cmd=file;file=#file#;node=#p1#">#p1rev#:#p1#</a></td></tr>
+<tr>
+ <td align=right>parent:</td>
+ <td><a href="?cmd=file;file=#file#;node=#p2#">#p2rev#:#p2#</a></td></tr>
+<tr>
+ <td align=right>manifest:</td>
+ <td><a href="?cmd=manifest;manifest=#manifest#;path=/">#rev#:#manifest#</a></td></tr>
+<tr>
+ <td align=right>author:</td>
+ <td>#author#</td></tr>
+<tr>
+ <td align=right>date:</td>
+ <td>#date#</td></tr>
+</table>
+
+<hr />
+
+<pre>
+#text#
+</pre>
+
+</body>
+</html>
\ No newline at end of file
--- a/templates/hist_ent.tmpl	Sun May 22 08:13:38 2005 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-<table summary="" width="100%" border="1">
-    <tr>
-        <td valign="top" width="10%">author:</td>
-        <td valign="top" width="20%%">#author#</td>
-        <td valign="top" width="10%">description:</td>
-        <td width="60%"><a href="?cmd=chkin;nd=#csnode#">#desc#</a></td>
-    </tr>
-    <tr>
-        <td>date:</td>
-        <td>#date# UTC</td>
-        <td>revision:</td>
-        <td><a href="?cmd=file;cs=#csnode#;fn=#fn#">#revnode#</a></td>
-    </tr>
-    <tr>
-        <td>parent(s):</td>
-        <td colspan="3">
-            #p1num#: <a href="?cmd=file;nd=#p1node#;fn=#fn#">#p1node#</a>
-            #p2entry#
-        </td>
-    </tr>
-</table>
-<br />
--- a/templates/htmlstart.tmpl	Sun May 22 08:13:38 2005 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
-<HTML>
-<!-- created by hgweb 0.1 - jake@edge2.net -->
-<HEAD><TITLE>#title#</TITLE>
-<style type="text/css">
-body { font-family: sans-serif; font-size: 12px; }
-table { font-size: 12px; }
-.errmsg { font-size: 200%; color: red; }
-.filename { font-size: 150%; color: purple; }
-.manifest { font-size: 150%; color: purple; }
-.filehist { font-size: 150%; color: purple; }
-.annotation { font-size: 150%; color: purple; }
-.plusline { color: green; }
-.minusline { color: red; }
-.atline { color: purple; }
-a.annlinelink { text-decoration: none; color: black; }
-a.revnumlink { text-decoration: none; color: black; }
-a.annlinelink:hover { text-decoration: none; color: blue; }
-a.revnumlink:hover { text-decoration: none; color: blue; }
-</style>
-</HEAD>
-<BODY>
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/manifest.tmpl	Mon May 23 16:00:02 2005 -0800
@@ -0,0 +1,19 @@
+Content-Type: text/html
+
+<html>
+<body>
+
+<a href="?cmd=changelog&rev=#rev#">changelog</a>
+<a href="?cmd=changeset&node=#node#">changeset</a>
+
+<h2>manifest: #path#</h2>
+<p>(#rev#:#manifest#)</p>
+
+<p>
+<a href="?cmd=manifest;manifest=#manifest#;path=#up#">[up]</a><br />
+#dirs#</p>
+
+<p>#files#</p>
+
+</body>
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/map	Mon May 23 16:00:02 2005 -0800
@@ -0,0 +1,20 @@
+changelog = changelog.tmpl
+naventry = "<a href="?cmd=changelog;pos=#rev#">#rev#</a> "
+filedifflink = "<a href="?cmd=filediff;node=#node#;file=#file#">#file#</a> "
+filenodelink = "<a href="?cmd=file;filenode=#filenode#;file=#file#">#file#</a> "
+fileellipses = "..."
+changelogentry = changelogentry.tmpl
+changeset = changeset.tmpl
+manifest = manifest.tmpl
+manifestdirentry = "<a href="?cmd=manifest;manifest=#manifest#;path=#path#">#basename#/</a><br />"
+manifestfileentry = "<a href="?cmd=file;filenode=#filenode#;file=#file#">#basename#</a><br /> "
+filerevision = filerevision.tmpl
+fileannotate = fileannotate.tmpl
+filediff = filediff.tmpl
+filelog = filelog.tmpl
+filelogentry = filelogentry.tmpl
+annotateline = "<tr><td align = right><a href="?cmd=changeset;node=#node#">#author#@#rev#</a>:</td><td><pre>#line#</pre></td></tr>"
+difflineplus = "<span class=plusline>#line#</span>"
+difflineminus = "<span class=minusline>#line#</span>"
+difflineat = "<span class=atline>#line#</span>"
+diffline = "#line#"
\ No newline at end of file
--- a/templates/mfentry.tmpl	Sun May 22 08:13:38 2005 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-<a href="?cmd=file;fn=#fn#;nd=#node#">#fn#</a><br />
--- a/templates/mftitle.tmpl	Sun May 22 08:13:38 2005 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-<div class="manifest">Manifest (#node#)</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/template-vars.txt	Mon May 23 16:00:02 2005 -0800
@@ -0,0 +1,38 @@
+repo          the name of the repo
+rev           a changeset.manifest revision
+node          a changeset node
+changesets    total number of changesets
+file          a filename
+filenode      a file node
+filerev       a file revision
+filerevs      total number of file revisions
+up            the directory of the relevant file
+path          a path in the manifest, starting with "/"
+basename      a short pathname
+manifest      a manifest node
+manifestrev   a manifest revision
+date          a date string
+age           age in hours, days, etc
+line          a line of text (escaped)
+desc          a description (escaped, with breaks)
+shortdesc         a short description (escaped)
+author        a name or email addressv(obfuscated)
+p1, p2        parent nodes
+p1rev, p2rev  parent revs
+
+header        the global page header
+footer        the global page footer
+
+files         a list of file links
+dirs          a set of directory links
+diff          a diff of one or more files
+annotate      an annotated file
+entries       the entries relevant to the page
+
+Templates and commands:
+  changelog(rev) - a page for browsing changesets
+    naventry - a link for jumping to a changeset number
+    filenodelink - jump to file diff
+    fileellipses - printed after maxfiles
+    changelogentry - an entry in the log
+  manifest - browse a manifest as a directory tree
\ No newline at end of file