changeset 1920:b7cc0f323a4c

merge with crew.
author Vadim Gelfer <vadim.gelfer@gmail.com>
date Sun, 12 Mar 2006 16:21:59 -0800
parents 8f565af14095 (diff) d7c038e805e9 (current diff)
children 3f6be77eda58 7ae177a70f54 2f500a4b6e99
files PKG-INFO contrib/hbisect.py doc/hg.1.txt mercurial/commands.py mercurial/hgweb.py mercurial/ui.py mercurial/util.py
diffstat 14 files changed, 1013 insertions(+), 288 deletions(-) [+]
line wrap: on
line diff
--- a/doc/hgrc.5.txt	Mon Mar 13 00:02:33 2006 +0100
+++ b/doc/hgrc.5.txt	Sun Mar 12 16:21:59 2006 -0800
@@ -238,6 +238,10 @@
     The editor to use during a commit.  Default is $EDITOR or "vi".
   interactive;;
     Allow to prompt the user.  True or False.  Default is True.
+  logtemplate;;
+    Template string for commands that print changesets.
+  style;;
+    Name of style to use for command output.
   merge;;
     The conflict resolution program to use during a manual merge.
     Default is "hgmerge".
--- a/mercurial/commands.py	Mon Mar 13 00:02:33 2006 +0100
+++ b/mercurial/commands.py	Sun Mar 12 16:21:59 2006 -0800
@@ -9,7 +9,7 @@
 from node import *
 from i18n import gettext as _
 demandload(globals(), "os re sys signal shutil imp urllib pdb")
-demandload(globals(), "fancyopts ui hg util lock revlog")
+demandload(globals(), "fancyopts ui hg util lock revlog templater")
 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
 demandload(globals(), "errno socket version struct atexit sets bz2")
 
@@ -339,63 +339,265 @@
         user = revcache[rev] = ui.shortuser(name)
     return user
 
-def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
-    """show a single changeset or file revision"""
-    log = repo.changelog
-    if changenode is None:
-        changenode = log.node(rev)
-    elif not rev:
-        rev = log.rev(changenode)
-
-    if ui.quiet:
-        ui.write("%d:%s\n" % (rev, short(changenode)))
-        return
-
-    changes = log.read(changenode)
-    date = util.datestr(changes[2])
-
-    parents = [(log.rev(p), ui.verbose and hex(p) or short(p))
-               for p in log.parents(changenode)
-               if ui.debugflag or p != nullid]
-    if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
-        parents = []
-
-    if ui.verbose:
-        ui.write(_("changeset:   %d:%s\n") % (rev, hex(changenode)))
+class changeset_templater(object):
+    '''use templater module to format changeset information.'''
+
+    def __init__(self, ui, repo, mapfile):
+        self.t = templater.templater(mapfile, templater.common_filters,
+                                     cache={'parent': '{rev}:{node|short} ',
+                                            'manifest': '{rev}:{node|short}'})
+        self.ui = ui
+        self.repo = repo
+
+    def use_template(self, t):
+        '''set template string to use'''
+        self.t.cache['changeset'] = t
+
+    def write(self, thing):
+        '''write expanded template.
+        uses in-order recursive traverse of iterators.'''
+        for t in thing:
+            if hasattr(t, '__iter__'):
+                self.write(t)
+            else:
+                self.ui.write(t)
+
+    def show(self, rev=0, changenode=None, brinfo=None):
+        '''show a single changeset or file revision'''
+        log = self.repo.changelog
+        if changenode is None:
+            changenode = log.node(rev)
+        elif not rev:
+            rev = log.rev(changenode)
+
+        changes = log.read(changenode)
+
+        def showlist(name, values, plural=None, **args):
+            '''expand set of values.
+            name is name of key in template map.
+            values is list of strings or dicts.
+            plural is plural of name, if not simply name + 's'.
+
+            expansion works like this, given name 'foo'.
+
+            if values is empty, expand 'no_foos'.
+
+            if 'foo' not in template map, return values as a string,
+            joined by space.
+
+            expand 'start_foos'.
+
+            for each value, expand 'foo'. if 'last_foo' in template
+            map, expand it instead of 'foo' for last key.
+
+            expand 'end_foos'.
+            '''
+            if plural: names = plural
+            else: names = name + 's'
+            if not values:
+                noname = 'no_' + names
+                if noname in self.t:
+                    yield self.t(noname, **args)
+                return
+            if name not in self.t:
+                if isinstance(values[0], str):
+                    yield ' '.join(values)
+                else:
+                    for v in values:
+                        yield dict(v, **args)
+                return
+            startname = 'start_' + names
+            if startname in self.t:
+                yield self.t(startname, **args)
+            vargs = args.copy()
+            def one(v, tag=name):
+                try:
+                    vargs.update(v)
+                except ValueError:
+                    vargs.update([(name, v)])
+                return self.t(tag, **vargs)
+            lastname = 'last_' + name
+            if lastname in self.t:
+                last = values.pop()
+            else:
+                last = None
+            for v in values:
+                yield one(v)
+            if last is not None:
+                yield one(last, tag=lastname)
+            endname = 'end_' + names
+            if endname in self.t:
+                yield self.t(endname, **args)
+
+        if brinfo:
+            def showbranches(**args):
+                if changenode in brinfo:
+                    for x in showlist('branch', brinfo[changenode],
+                                      plural='branches', **args):
+                        yield x
+        else:
+            showbranches = ''
+
+        if self.ui.debugflag:
+            def showmanifest(**args):
+                args = args.copy()
+                args.update(rev=self.repo.manifest.rev(changes[0]),
+                            node=hex(changes[0]))
+                yield self.t('manifest', **args)
+        else:
+            showmanifest = ''
+
+        def showparents(**args):
+            parents = [[('rev', log.rev(p)), ('node', hex(p))]
+                       for p in log.parents(changenode)
+                       if self.ui.debugflag or p != nullid]
+            if (not self.ui.debugflag and len(parents) == 1 and
+                parents[0][0][1] == rev - 1):
+                return
+            for x in showlist('parent', parents, **args):
+                yield x
+
+        def showtags(**args):
+            for x in showlist('tag', self.repo.nodetags(changenode), **args):
+                yield x
+
+        if self.ui.debugflag:
+            files = self.repo.changes(log.parents(changenode)[0], changenode)
+            def showfiles(**args):
+                for x in showlist('file', files[0], **args): yield x
+            def showadds(**args):
+                for x in showlist('file_add', files[1], **args): yield x
+            def showdels(**args):
+                for x in showlist('file_del', files[2], **args): yield x
+        else:
+            def showfiles(**args):
+                for x in showlist('file', changes[3], **args): yield x
+            showadds = ''
+            showdels = ''
+
+        props = {
+            'author': changes[1],
+            'branches': showbranches,
+            'date': changes[2],
+            'desc': changes[4],
+            'file_adds': showadds,
+            'file_dels': showdels,
+            'files': showfiles,
+            'manifest': showmanifest,
+            'node': hex(changenode),
+            'parents': showparents,
+            'rev': rev,
+            'tags': showtags,
+            }
+
+        try:
+            if self.ui.debugflag and 'changeset_debug' in self.t:
+                key = 'changeset_debug'
+            elif self.ui.quiet and 'changeset_quiet' in self.t:
+                key = 'changeset_quiet'
+            elif self.ui.verbose and 'changeset_verbose' in self.t:
+                key = 'changeset_verbose'
+            else:
+                key = 'changeset'
+            self.write(self.t(key, **props))
+        except KeyError, inst:
+            raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
+                                                           inst.args[0]))
+        except SyntaxError, inst:
+            raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
+
+class changeset_printer(object):
+    '''show changeset information when templating not requested.'''
+
+    def __init__(self, ui, repo):
+        self.ui = ui
+        self.repo = repo
+
+    def show(self, rev=0, changenode=None, brinfo=None):
+        '''show a single changeset or file revision'''
+        log = self.repo.changelog
+        if changenode is None:
+            changenode = log.node(rev)
+        elif not rev:
+            rev = log.rev(changenode)
+
+        if self.ui.quiet:
+            self.ui.write("%d:%s\n" % (rev, short(changenode)))
+            return
+
+        changes = log.read(changenode)
+        date = util.datestr(changes[2])
+
+        parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p))
+                   for p in log.parents(changenode)
+                   if self.ui.debugflag or p != nullid]
+        if (not self.ui.debugflag and len(parents) == 1 and
+            parents[0][0] == rev-1):
+            parents = []
+
+        if self.ui.verbose:
+            self.ui.write(_("changeset:   %d:%s\n") % (rev, hex(changenode)))
+        else:
+            self.ui.write(_("changeset:   %d:%s\n") % (rev, short(changenode)))
+
+        for tag in self.repo.nodetags(changenode):
+            self.ui.status(_("tag:         %s\n") % tag)
+        for parent in parents:
+            self.ui.write(_("parent:      %d:%s\n") % parent)
+
+        if brinfo and changenode in brinfo:
+            br = brinfo[changenode]
+            self.ui.write(_("branch:      %s\n") % " ".join(br))
+
+        self.ui.debug(_("manifest:    %d:%s\n") %
+                      (self.repo.manifest.rev(changes[0]), hex(changes[0])))
+        self.ui.status(_("user:        %s\n") % changes[1])
+        self.ui.status(_("date:        %s\n") % date)
+
+        if self.ui.debugflag:
+            files = self.repo.changes(log.parents(changenode)[0], changenode)
+            for key, value in zip([_("files:"), _("files+:"), _("files-:")],
+                                  files):
+                if value:
+                    self.ui.note("%-12s %s\n" % (key, " ".join(value)))
+        else:
+            self.ui.note(_("files:       %s\n") % " ".join(changes[3]))
+
+        description = changes[4].strip()
+        if description:
+            if self.ui.verbose:
+                self.ui.status(_("description:\n"))
+                self.ui.status(description)
+                self.ui.status("\n\n")
+            else:
+                self.ui.status(_("summary:     %s\n") %
+                               description.splitlines()[0])
+        self.ui.status("\n")
+
+def show_changeset(ui, repo, opts):
+    '''show one changeset.  uses template or regular display.  caller
+    can pass in 'style' and 'template' options in opts.'''
+
+    tmpl = opts.get('template')
+    if tmpl:
+        tmpl = templater.parsestring(tmpl, quoted=False)
     else:
-        ui.write(_("changeset:   %d:%s\n") % (rev, short(changenode)))
-
-    for tag in repo.nodetags(changenode):
-        ui.status(_("tag:         %s\n") % tag)
-    for parent in parents:
-        ui.write(_("parent:      %d:%s\n") % parent)
-
-    if brinfo and changenode in brinfo:
-        br = brinfo[changenode]
-        ui.write(_("branch:      %s\n") % " ".join(br))
-
-    ui.debug(_("manifest:    %d:%s\n") % (repo.manifest.rev(changes[0]),
-                                      hex(changes[0])))
-    ui.status(_("user:        %s\n") % changes[1])
-    ui.status(_("date:        %s\n") % date)
-
-    if ui.debugflag:
-        files = repo.changes(log.parents(changenode)[0], changenode)
-        for key, value in zip([_("files:"), _("files+:"), _("files-:")], files):
-            if value:
-                ui.note("%-12s %s\n" % (key, " ".join(value)))
-    else:
-        ui.note(_("files:       %s\n") % " ".join(changes[3]))
-
-    description = changes[4].strip()
-    if description:
-        if ui.verbose:
-            ui.status(_("description:\n"))
-            ui.status(description)
-            ui.status("\n\n")
-        else:
-            ui.status(_("summary:     %s\n") % description.splitlines()[0])
-    ui.status("\n")
+        tmpl = ui.config('ui', 'logtemplate')
+        if tmpl: tmpl = templater.parsestring(tmpl)
+    mapfile = opts.get('style') or ui.config('ui', 'style')
+    if tmpl or mapfile:
+        if mapfile:
+            if not os.path.isfile(mapfile):
+                mapname = templater.templatepath('map-cmdline.' + mapfile)
+                if not mapname: mapname = templater.templatepath(mapfile)
+                if mapname: mapfile = mapname
+        try:
+            t = changeset_templater(ui, repo, mapfile)
+        except SyntaxError, inst:
+            raise util.Abort(inst.args[0])
+        if tmpl: t.use_template(tmpl)
+        return t
+    return changeset_printer(ui, repo)
 
 def show_version(ui):
     """output version and copyright information"""
@@ -1425,8 +1627,9 @@
     br = None
     if opts['branches']:
         br = repo.branchlookup(heads)
+    displayer = show_changeset(ui, repo, opts)
     for n in heads:
-        show_changeset(ui, repo, changenode=n, brinfo=br)
+        displayer.show(changenode=n, brinfo=br)
 
 def identify(ui, repo):
     """print information about the working copy
@@ -1553,11 +1756,12 @@
     o = other.changelog.nodesbetween(o)[0]
     if opts['newest_first']:
         o.reverse()
+    displayer = show_changeset(ui, other, opts)
     for n in o:
         parents = [p for p in other.changelog.parents(n) if p != nullid]
         if opts['no_merges'] and len(parents) == 2:
             continue
-        show_changeset(ui, other, changenode=n)
+        displayer.show(changenode=n)
         if opts['patch']:
             prev = (parents and parents[0]) or nullid
             dodiff(ui, ui, other, prev, n)
@@ -1654,9 +1858,11 @@
         limit = sys.maxint
     count = 0
 
+    displayer = show_changeset(ui, repo, opts)
     for st, rev, fns in changeiter:
         if st == 'window':
             du = dui(ui)
+            displayer.ui = du
         elif st == 'add':
             du.bump(rev)
             changenode = repo.changelog.node(rev)
@@ -1683,7 +1889,7 @@
             if opts['branches']:
                 br = repo.branchlookup([repo.changelog.node(rev)])
 
-            show_changeset(du, repo, rev, brinfo=br)
+            displayer.show(rev, brinfo=br)
             if opts['patch']:
                 prev = (parents and parents[0]) or nullid
                 dodiff(du, du, repo, prev, changenode, match=matchfn)
@@ -1736,17 +1942,18 @@
     o = repo.changelog.nodesbetween(o)[0]
     if opts['newest_first']:
         o.reverse()
+    displayer = show_changeset(ui, repo, opts)
     for n in o:
         parents = [p for p in repo.changelog.parents(n) if p != nullid]
         if opts['no_merges'] and len(parents) == 2:
             continue
-        show_changeset(ui, repo, changenode=n)
+        displayer.show(changenode=n)
         if opts['patch']:
             prev = (parents and parents[0]) or nullid
             dodiff(ui, ui, repo, prev, n)
             ui.write("\n")
 
-def parents(ui, repo, rev=None, branches=None):
+def parents(ui, repo, rev=None, branches=None, **opts):
     """show the parents of the working dir or revision
 
     Print the working directory's parent revisions.
@@ -1759,9 +1966,10 @@
     br = None
     if branches is not None:
         br = repo.branchlookup(p)
+    displayer = show_changeset(ui, repo, opts)
     for n in p:
         if n != nullid:
-            show_changeset(ui, repo, changenode=n, brinfo=br)
+            displayer.show(changenode=n, brinfo=br)
 
 def paths(ui, repo, search=None):
     """show definition of symbolic path names
@@ -2272,7 +2480,7 @@
     br = None
     if opts['branches']:
         br = repo.branchlookup([n])
-    show_changeset(ui, repo, changenode=n, brinfo=br)
+    show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
     if opts['patch']:
         dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
 
@@ -2317,7 +2525,7 @@
     repo.undo()
 
 def update(ui, repo, node=None, merge=False, clean=False, force=None,
-           branch=None):
+           branch=None, **opts):
     """update or merge working directory
 
     Update the working directory to the specified revision.
@@ -2344,7 +2552,7 @@
         if len(found) > 1:
             ui.warn(_("Found multiple heads for %s\n") % branch)
             for x in found:
-                show_changeset(ui, repo, changenode=x, brinfo=br)
+                show_changeset(ui, repo, opts).show(changenode=x, brinfo=br)
             return 1
         if len(found) == 1:
             node = found[0]
@@ -2488,7 +2696,9 @@
     "heads":
         (heads,
          [('b', 'branches', None, _('show branches')),
-          ('r', 'rev', '', _('show only heads which are descendants of rev'))],
+          ('', 'style', '', _('display using template map file')),
+          ('r', 'rev', '', _('show only heads which are descendants of rev')),
+          ('', 'template', '', _('display with template'))],
          _('hg heads [-b] [-r <rev>]')),
     "help": (help_, [], _('hg help [COMMAND]')),
     "identify|id": (identify, [], _('hg identify')),
@@ -2503,8 +2713,10 @@
          _('hg import [-p NUM] [-b BASE] [-f] PATCH...')),
     "incoming|in": (incoming,
          [('M', 'no-merges', None, _('do not show merges')),
+          ('', 'style', '', _('display using template map file')),
+          ('n', 'newest-first', None, _('show newest record first')),
           ('p', 'patch', None, _('show patch')),
-          ('n', 'newest-first', None, _('show newest record first'))],
+          ('', 'template', '', _('display with template'))],
          _('hg incoming [-p] [-n] [-M] [SOURCE]')),
     "^init": (init, [], _('hg init [DEST]')),
     "locate":
@@ -2524,8 +2736,10 @@
           ('l', 'limit', '', _('limit number of changes displayed')),
           ('r', 'rev', [], _('show the specified revision or range')),
           ('M', 'no-merges', None, _('do not show merges')),
+          ('', 'style', '', _('display using template map file')),
           ('m', 'only-merges', None, _('show only merges')),
           ('p', 'patch', None, _('show patch')),
+          ('', 'template', '', _('display with template')),
           ('I', 'include', [], _('include names matching the given patterns')),
           ('X', 'exclude', [], _('exclude names matching the given patterns'))],
          _('hg log [OPTION]... [FILE]')),
@@ -2533,11 +2747,15 @@
     "outgoing|out": (outgoing,
          [('M', 'no-merges', None, _('do not show merges')),
           ('p', 'patch', None, _('show patch')),
-          ('n', 'newest-first', None, _('show newest record first'))],
+          ('', 'style', '', _('display using template map file')),
+          ('n', 'newest-first', None, _('show newest record first')),
+          ('', 'template', '', _('display with template'))],
          _('hg outgoing [-M] [-p] [-n] [DEST]')),
     "^parents":
         (parents,
-         [('b', 'branches', None, _('show branches'))],
+         [('b', 'branches', None, _('show branches')),
+          ('', 'style', '', _('display using template map file')),
+          ('', 'template', '', _('display with template'))],
          _('hg parents [-b] [REV]')),
     "paths": (paths, [], _('hg paths [NAME]')),
     "^pull":
@@ -2629,7 +2847,9 @@
     "tip":
         (tip,
          [('b', 'branches', None, _('show branches')),
-          ('p', 'patch', None, _('show patch'))],
+          ('', 'style', '', _('display using template map file')),
+          ('p', 'patch', None, _('show patch')),
+          ('', 'template', '', _('display with template'))],
          _('hg tip [-b] [-p]')),
     "unbundle":
         (unbundle,
@@ -2640,9 +2860,11 @@
     "^update|up|checkout|co":
         (update,
          [('b', 'branch', '', _('checkout the head of a specific branch')),
+          ('', 'style', '', _('display using template map file')),
           ('m', 'merge', None, _('allow merging of branches')),
           ('C', 'clean', None, _('overwrite locally modified files')),
-          ('f', 'force', None, _('force a merge with outstanding changes'))],
+          ('f', 'force', None, _('force a merge with outstanding changes')),
+          ('', 'template', '', _('display with template'))],
          _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
     "verify": (verify, [], _('hg verify')),
     "version": (show_version, [], _('hg version')),
@@ -2909,7 +3131,7 @@
             help_(u, 'shortlist')
         sys.exit(-1)
     except AmbiguousCommand, inst:
-        u.warn(_("hg: command '%s' is ambiguous:\n    %s\n") % 
+        u.warn(_("hg: command '%s' is ambiguous:\n    %s\n") %
                 (inst.args[0], " ".join(inst.args[1])))
         sys.exit(1)
     except UnknownCommand, inst:
--- a/mercurial/hgweb.py	Mon Mar 13 00:02:33 2006 +0100
+++ b/mercurial/hgweb.py	Sun Mar 12 16:21:59 2006 -0800
@@ -6,58 +6,15 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import os, cgi, sys, urllib
+import os, cgi, sys
 import mimetypes
 from demandload import demandload
 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
-demandload(globals(), "mimetypes")
+demandload(globals(), "mimetypes templater")
 from node import *
 from i18n import gettext as _
 
-def templatepath():
-    for f in "templates", "../templates":
-        p = os.path.join(os.path.dirname(__file__), f)
-        if os.path.isdir(p):
-            return os.path.normpath(p)
-    else:
-       # executable version (py2exe) doesn't support __file__
-        if hasattr(sys, 'frozen'):
-            return os.path.join(sys.prefix, "templates")
-
-def age(x):
-    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()
-    then = x[0]
-    delta = max(1, int(now - then))
-
-    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 >= 2 or s == 1:
-            return fmt(t, n)
-
-def nl2br(text):
-    return text.replace('\n', '<br/>\n')
-
-def obfuscate(text):
-    return ''.join(['&#%d;' % ord(c) for c in text])
-
 def up(p):
     if p[0] != "/":
         p = "/" + p
@@ -133,78 +90,6 @@
             headers.append(('Content-length', str(size)))
         self.header(headers)
 
-class templater(object):
-    def __init__(self, mapfile, filters={}, defaults={}):
-        self.cache = {}
-        self.map = {}
-        self.base = os.path.dirname(mapfile)
-        self.filters = filters
-        self.defaults = defaults
-
-        for l in file(mapfile):
-            m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
-            if m:
-                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:
-                    raise LookupError(_("unknown map entry '%s'") % l)
-
-    def __call__(self, t, **map):
-        m = self.defaults.copy()
-        m.update(map)
-        try:
-            tmpl = self.cache[t]
-        except KeyError:
-            tmpl = self.cache[t] = file(self.map[t]).read()
-        return self.template(tmpl, self.filters, **m)
-
-    def template(self, tmpl, filters={}, **map):
-        while tmpl:
-            m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
-            if m:
-                yield tmpl[:m.start(0)]
-                v = map.get(m.group(1), "")
-                v = callable(v) and v(**map) or v
-
-                format = m.group(2)
-                fl = m.group(4)
-
-                if format:
-                    q = v.__iter__
-                    for i in q():
-                        lm = map.copy()
-                        lm.update(i)
-                        yield self(format[1:], **lm)
-
-                    v = ""
-
-                elif fl:
-                    for f in fl.split("|")[1:]:
-                        v = filters[f](v)
-
-                yield v
-                tmpl = tmpl[m.end(0):]
-            else:
-                yield tmpl
-                return
-
-common_filters = {
-    "escape": lambda x: cgi.escape(x, True),
-    "urlescape": urllib.quote,
-    "strip": lambda x: x.strip(),
-    "age": age,
-    "date": lambda x: util.datestr(x),
-    "addbreaks": nl2br,
-    "obfuscate": obfuscate,
-    "short": (lambda x: x[:12]),
-    "firstline": (lambda x: x.splitlines(1)[0]),
-    "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
-    "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
-    }
-
 class hgweb(object):
     def __init__(self, repo, name=None):
         if type(repo) == type(""):
@@ -889,7 +774,7 @@
 
         expand_form(req.form)
 
-        t = self.repo.ui.config("web", "templates", templatepath())
+        t = self.repo.ui.config("web", "templates", templater.templatepath())
         static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
         m = os.path.join(t, "map")
         style = self.repo.ui.config("web", "style", "")
@@ -911,12 +796,12 @@
             self.reponame = (self.repo.ui.config("web", "name")
                              or uri.strip('/') or self.repo.root)
 
-        self.t = templater(m, common_filters,
-                           {"url": url,
-                            "repo": self.reponame,
-                            "header": header,
-                            "footer": footer,
-                           })
+        self.t = templater.templater(m, templater.common_filters,
+                                     {"url": url,
+                                      "repo": self.reponame,
+                                      "header": header,
+                                      "footer": footer,
+                                      })
 
         if not req.form.has_key('cmd'):
             req.form['cmd'] = [self.t.cache['default'],]
@@ -1143,9 +1028,9 @@
         def footer(**map):
             yield tmpl("footer", **map)
 
-        m = os.path.join(templatepath(), "map")
-        tmpl = templater(m, common_filters,
-                         {"header": header, "footer": footer})
+        m = os.path.join(templater.templatepath(), "map")
+        tmpl = templater.templater(m, templater.common_filters,
+                                   {"header": header, "footer": footer})
 
         def entries(**map):
             parity = 0
@@ -1191,7 +1076,7 @@
                 req.write(tmpl("notfound", repo=virtual))
         else:
             if req.form.has_key('static'):
-                static = os.path.join(templatepath(), "static")
+                static = os.path.join(templater.templatepath(), "static")
                 fname = req.form['static'][0]
                 req.write(staticfile(static, fname)
                           or tmpl("error", error="%r not found" % fname))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/templater.py	Sun Mar 12 16:21:59 2006 -0800
@@ -0,0 +1,237 @@
+# templater.py - template expansion for output
+#
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+import re
+from demandload import demandload
+from i18n import gettext as _
+demandload(globals(), "cStringIO cgi os time urllib util")
+
+esctable = {
+    '\\': '\\',
+    'r': '\r',
+    't': '\t',
+    'n': '\n',
+    'v': '\v',
+    }
+
+def parsestring(s, quoted=True):
+    '''parse a string using simple c-like syntax.
+    string must be in quotes if quoted is True.'''
+    fp = cStringIO.StringIO()
+    if quoted:
+        first = s[0]
+        if len(s) < 2: raise SyntaxError(_('string too short'))
+        if first not in "'\"": raise SyntaxError(_('invalid quote'))
+        if s[-1] != first: raise SyntaxError(_('unmatched quotes'))
+        s = s[1:-1]
+    escape = False
+    for c in s:
+        if escape:
+            fp.write(esctable.get(c, c))
+            escape = False
+        elif c == '\\': escape = True
+        elif quoted and c == first: raise SyntaxError(_('string ends early'))
+        else: fp.write(c)
+    if escape: raise SyntaxError(_('unterminated escape'))
+    return fp.getvalue()
+
+class templater(object):
+    '''template expansion engine.
+
+    template expansion works like this. a map file contains key=value
+    pairs. if value is quoted, it is treated as string. otherwise, it
+    is treated as name of template file.
+
+    templater is asked to expand a key in map. it looks up key, and
+    looks for atrings like this: {foo}. it expands {foo} by looking up
+    foo in map, and substituting it. expansion is recursive: it stops
+    when there is no more {foo} to replace.
+
+    expansion also allows formatting and filtering.
+
+    format uses key to expand each item in list. syntax is
+    {key%format}.
+
+    filter uses function to transform value. syntax is
+    {key|filter1|filter2|...}.'''
+
+    def __init__(self, mapfile, filters={}, cache={}):
+        '''set up template engine.
+        mapfile is name of file to read map definitions from.
+        filters is dict of functions. each transforms a value into another.
+        defaults is dict of default map definitions.'''
+        self.mapfile = mapfile or 'template'
+        self.cache = {}
+        self.map = {}
+        self.base = (mapfile and os.path.dirname(mapfile)) or ''
+        self.filters = filters
+        self.defaults = {}
+        self.cache = cache
+
+        if not mapfile:
+            return
+        i = 0
+        for l in file(mapfile):
+            l = l.strip()
+            i += 1
+            if not l or l[0] in '#;': continue
+            m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
+            if m:
+                key, val = m.groups()
+                if val[0] in "'\"":
+                    try:
+                        self.cache[key] = parsestring(val)
+                    except SyntaxError, inst:
+                        raise SyntaxError('%s:%s: %s' %
+                                          (mapfile, i, inst.args[0]))
+                else:
+                    self.map[key] = os.path.join(self.base, val)
+            else:
+                raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
+
+    def __contains__(self, key):
+        return key in self.cache
+
+    def __call__(self, t, **map):
+        '''perform expansion.
+        t is name of map element to expand.
+        map is added elements to use during expansion.'''
+        m = self.defaults.copy()
+        m.update(map)
+        try:
+            tmpl = self.cache[t]
+        except KeyError:
+            try:
+                tmpl = self.cache[t] = file(self.map[t]).read()
+            except IOError, inst:
+                raise IOError(inst.args[0], _('template file %s: %s') %
+                              (self.map[t], inst.args[1]))
+        return self.template(tmpl, self.filters, **m)
+
+    template_re = re.compile(r"[#{]([a-zA-Z_][a-zA-Z0-9_]*)"
+                             r"((%[a-zA-Z_][a-zA-Z0-9_]*)*)"
+                             r"((\|[a-zA-Z_][a-zA-Z0-9_]*)*)[#}]")
+
+    def template(self, tmpl, filters={}, **map):
+        lm = map.copy()
+        while tmpl:
+            m = self.template_re.search(tmpl)
+            if m:
+                start, end = m.span(0)
+                s, e = tmpl[start], tmpl[end - 1]
+                key = m.group(1)
+                if ((s == '#' and e != '#') or (s == '{' and e != '}')):
+                    raise SyntaxError(_("'%s'/'%s' mismatch expanding '%s'") %
+                                      (s, e, key))
+                if start:
+                    yield tmpl[:start]
+                v = map.get(key, "")
+                v = callable(v) and v(**map) or v
+
+                format = m.group(2)
+                fl = m.group(4)
+
+                if format:
+                    q = v.__iter__
+                    for i in q():
+                        lm.update(i)
+                        yield self(format[1:], **lm)
+
+                    v = ""
+
+                elif fl:
+                    for f in fl.split("|")[1:]:
+                        v = filters[f](v)
+
+                yield v
+                tmpl = tmpl[end:]
+            else:
+                yield tmpl
+                break
+
+agescales = [("second", 1),
+             ("minute", 60),
+             ("hour", 3600),
+             ("day", 3600 * 24),
+             ("week", 3600 * 24 * 7),
+             ("month", 3600 * 24 * 30),
+             ("year", 3600 * 24 * 365)]
+
+agescales.reverse()
+
+def age(date):
+    '''turn a (timestamp, tzoff) tuple into an age string.'''
+
+    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()
+    then = date[0]
+    delta = max(1, int(now - then))
+
+    for t, s in agescales:
+        n = delta / s
+        if n >= 2 or s == 1:
+            return fmt(t, n)
+
+def isodate(date):
+    '''turn a (timestamp, tzoff) tuple into an iso 8631 date.'''
+    return util.datestr(date, format='%Y-%m-%d %H:%M')
+
+def nl2br(text):
+    '''replace raw newlines with xhtml line breaks.'''
+    return text.replace('\n', '<br/>\n')
+
+def obfuscate(text):
+    return ''.join(['&#%d;' % ord(c) for c in text])
+
+def domain(author):
+    '''get domain of author, or empty string if none.'''
+    f = author.find('@')
+    if f == -1: return ''
+    author = author[f+1:]
+    f = author.find('>')
+    if f >= 0: author = author[:f]
+    return author
+
+def person(author):
+    '''get name of author, or else username.'''
+    f = author.find('<')
+    if f == -1: return util.shortuser(author)
+    return author[:f].rstrip()
+
+common_filters = {
+    "addbreaks": nl2br,
+    "age": age,
+    "date": lambda x: util.datestr(x),
+    "domain": domain,
+    "escape": lambda x: cgi.escape(x, True),
+    "firstline": lambda x: x.splitlines(1)[0].rstrip('\r\n'),
+    "isodate": isodate,
+    "obfuscate": obfuscate,
+    "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
+    "person": person,
+    "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
+    "short": lambda x: x[:12],
+    "strip": lambda x: x.strip(),
+    "urlescape": lambda x: urllib.quote(x),
+    "user": lambda x: util.shortuser(x),
+    }
+
+def templatepath(name=None):
+    '''return location of template file or directory (if no name).
+    returns None if not found.'''
+    for f in 'templates', '../templates':
+        fl = f.split('/')
+        if name: fl.append(name)
+        p = os.path.join(os.path.dirname(__file__), *fl)
+        if (name and os.path.exists(p)) or os.path.isdir(p):
+            return os.path.normpath(p)
--- a/mercurial/ui.py	Mon Mar 13 00:02:33 2006 +0100
+++ b/mercurial/ui.py	Sun Mar 12 16:21:59 2006 -0800
@@ -150,13 +150,7 @@
 
     def shortuser(self, user):
         """Return a short representation of a user name or email address."""
-        if not self.verbose:
-            f = user.find('@')
-            if f >= 0:
-                user = user[:f]
-            f = user.find('<')
-            if f >= 0:
-                user = user[f+1:]
+        if not self.verbose: user = util.shortuser(user)
         return user
 
     def expandpath(self, loc):
--- a/mercurial/util.py	Mon Mar 13 00:02:33 2006 +0100
+++ b/mercurial/util.py	Sun Mar 12 16:21:59 2006 -0800
@@ -746,6 +746,16 @@
              -tz / 3600,
              ((-tz % 3600) / 60)))
 
+def shortuser(user):
+    """Return a short representation of a user name or email address."""
+    f = user.find('@')
+    if f >= 0:
+        user = user[:f]
+    f = user.find('<')
+    if f >= 0:
+        user = user[f+1:]
+    return user
+
 def walkrepos(path):
     '''yield every hg repository under path, recursively.'''
     def errhandler(err):
--- a/templates/map	Mon Mar 13 00:02:33 2006 +0100
+++ b/templates/map	Sun Mar 12 16:21:59 2006 -0800
@@ -1,50 +1,50 @@
-default = "changelog"
+default = 'changelog'
 header = header.tmpl
 footer = footer.tmpl
 search = search.tmpl
 changelog = changelog.tmpl
-naventry = "<a href="?cl=#rev#">#label|escape#</a> "
-filedifflink = "<a href="?fd=#node|short#;file=#file|urlescape#">#file|escape#</a> "
-filenodelink = "<a href="?f=#filenode|short#;file=#file|urlescape#">#file|escape#</a> "
-fileellipses = "..."
+naventry = '<a href="?cl=#rev#">#label|escape#</a> '
+filedifflink = '<a href="?fd=#node|short#;file=#file|urlescape#">#file|escape#</a> '
+filenodelink = '<a href="?f=#filenode|short#;file=#file|urlescape#">#file|escape#</a> '
+fileellipses = '...'
 changelogentry = changelogentry.tmpl
 searchentry = changelogentry.tmpl
 changeset = changeset.tmpl
 manifest = manifest.tmpl
-manifestdirentry = "<tr class="parity#parity#"><td><tt>drwxr-xr-x</tt>&nbsp;<td><a href="?cmd=manifest;manifest=#manifest#;path=#path|urlescape#">#basename|escape#/</a>"
-manifestfileentry = "<tr class="parity#parity#"><td><tt>#permissions|permissions#</tt>&nbsp;<td><a href="?f=#filenode|short#;file=#file|urlescape#">#basename|escape#</a>"
+manifestdirentry = '<tr class="parity#parity#"><td><tt>drwxr-xr-x</tt>&nbsp;<td><a href="?cmd=manifest;manifest=#manifest#;path=#path|urlescape#">#basename|escape#/</a>'
+manifestfileentry = '<tr class="parity#parity#"><td><tt>#permissions|permissions#</tt>&nbsp;<td><a href="?f=#filenode|short#;file=#file|urlescape#">#basename|escape#</a>'
 filerevision = filerevision.tmpl
 fileannotate = fileannotate.tmpl
 filediff = filediff.tmpl
 filelog = filelog.tmpl
-fileline = "<div class="parity#parity#"><span class="lineno">#linenumber#</span>#line|escape#</div>"
+fileline = '<div class="parity#parity#"><span class="lineno">#linenumber#</span>#line|escape#</div>'
 filelogentry = filelogentry.tmpl
-annotateline = "<tr class="parity#parity#"><td class="annotate"><a href="?cs=#node|short#">#author|obfuscate#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>"
-difflineplus = "<span class="plusline">#line|escape#</span>"
-difflineminus = "<span class="minusline">#line|escape#</span>"
-difflineat = "<span class="atline">#line|escape#</span>"
-diffline = "#line|escape#"
-changelogparent = "<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cs=#node|short#">#node|short#</a></td></tr>"
-changesetparent = "<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cs=#node|short#">#node|short#</a></td></tr>"
-filerevparent = "<tr><td class="metatag">parent:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>"
-filerename = "<tr><td class="metatag">parent:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#file|escape#@#node|short#</a></td></tr>"
-filelogrename = "<tr><td align="right">base:&nbsp;</td><td><a href="?f=#node|short#;file=#file|urlescape#">#file|escape#@#node|short#</a></td></tr>"
-fileannotateparent = "<tr><td class="metatag">parent:</td><td><a href="?fa=#filenode|short#;file=#file|urlescape#">#node|short#</a></td></tr>"
-changesetchild = "<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>"
-changelogchild = "<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>"
-filerevchild = "<tr><td class="metatag">child:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>"
-fileannotatechild = "<tr><td class="metatag">child:</td><td><a href="?fa=#filenode|short#;file=#file|urlescape#">#node|short#</a></td></tr>"
+annotateline = '<tr class="parity#parity#"><td class="annotate"><a href="?cs=#node|short#">#author|obfuscate#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>'
+difflineplus = '<span class="plusline">#line|escape#</span>'
+difflineminus = '<span class="minusline">#line|escape#</span>'
+difflineat = '<span class="atline">#line|escape#</span>'
+diffline = '#line|escape#'
+changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
+changesetparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
+filerevparent = '<tr><td class="metatag">parent:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
+filerename = '<tr><td class="metatag">parent:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#file|escape#@#node|short#</a></td></tr>'
+filelogrename = '<tr><td align="right">base:&nbsp;</td><td><a href="?f=#node|short#;file=#file|urlescape#">#file|escape#@#node|short#</a></td></tr>'
+fileannotateparent = '<tr><td class="metatag">parent:</td><td><a href="?fa=#filenode|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
+changesetchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
+changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
+filerevchild = '<tr><td class="metatag">child:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
+fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="?fa=#filenode|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
 tags = tags.tmpl
-tagentry = "<li class="tagEntry parity#parity#"><span class="node">#node#</span> <a href="?cs=#node|short#">#tag|escape#</a></li>"
-diffblock = "<pre class="parity#parity#">#lines#</pre>"
-changelogtag = "<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>"
-changesettag = "<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>"
-filediffparent = "<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cs=#node|short#">#node|short#</a></td></tr>"
-filelogparent = "<tr><td align="right">parent #rev#:&nbsp;</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>"
-filediffchild = "<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>"
-filelogchild = "<tr><td align="right">child #rev#:&nbsp;</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>"
-indexentry = "<tr class="parity#parity#"><td><a href="#url#">#name|escape#</a></td><td>#shortdesc#</td><td>#contact|obfuscate#</td><td>#lastupdate|age# ago</td><td><a href="#url#?cl=tip;style=rss">RSS</a></td></tr>"
+tagentry = '<li class="tagEntry parity#parity#"><span class="node">#node#</span> <a href="?cs=#node|short#">#tag|escape#</a></li>'
+diffblock = '<pre class="parity#parity#">#lines#</pre>'
+changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
+changesettag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
+filediffparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
+filelogparent = '<tr><td align="right">parent #rev#:&nbsp;</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
+filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
+filelogchild = '<tr><td align="right">child #rev#:&nbsp;</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
+indexentry = '<tr class="parity#parity#"><td><a href="#url#">#name|escape#</a></td><td>#shortdesc#</td><td>#contact|obfuscate#</td><td>#lastupdate|age# ago</td><td><a href="#url#?cl=tip;style=rss">RSS</a></td></tr>'
 index = index.tmpl
-archiveentry = "<a href="?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> "
+archiveentry = '<a href="?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> '
 notfound = notfound.tmpl
 error = error.tmpl
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/map-cmdline.compact	Sun Mar 12 16:21:59 2006 -0800
@@ -0,0 +1,8 @@
+changeset = '{rev}{tags}{parents}   {node|short}   {date|isodate}   {author|user}\n  {desc|firstline|strip}\n\n'
+changeset_quiet = '{rev}:{node|short}\n'
+start_tags = '['
+tag = '{tag},'
+last_tag = '{tag}]'
+start_parents = ':'
+parent = '{rev},'
+last_parent = '{rev}'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/map-cmdline.default	Sun Mar 12 16:21:59 2006 -0800
@@ -0,0 +1,13 @@
+changeset = 'changeset:   {rev}:{node|short}\n{tags}{short_parents}user:        {author}\ndate:        {date|date}\nsummary:     {desc|firstline}\n\n'
+changeset_quiet = '{rev}:{node|short}\n'
+changeset_verbose = 'changeset:   {rev}:{node}\n{tags}{parents}{manifest}user:        {author}\ndate:        {date|date}\nfiles:       {files}\n{file_adds}{file_dels}description:\n{desc|strip}\n\n\n'
+start_file_adds = 'files+:     '
+file_add = ' {file_add}'
+end_file_adds = '\n'
+start_file_dels = 'files-:     '
+file_del = ' {file_del}'
+end_file_dels = '\n'
+short_parent = 'parent:      {rev}:{node|short}\n'
+parent = 'parent:      {rev}:{node}\n'
+manifest = 'manifest:    {rev}:{node}\n'
+tag = 'tag:         {tag}\n'
--- a/templates/map-gitweb	Mon Mar 13 00:02:33 2006 +0100
+++ b/templates/map-gitweb	Sun Mar 12 16:21:59 2006 -0800
@@ -1,50 +1,50 @@
-default = "summary"
+default = 'summary'
 header = header-gitweb.tmpl
 footer = footer-gitweb.tmpl
 search = search-gitweb.tmpl
 changelog = changelog-gitweb.tmpl
 summary = summary-gitweb.tmpl
 error = error-gitweb.tmpl
-naventry = "<a href="?cmd=changelog;rev=#rev#;style=gitweb">#label|escape#</a> "
-navshortentry = "<a href="?cmd=shortlog;rev=#rev#;style=gitweb">#label|escape#</a> "
-filedifflink = "<a href="?cmd=filediff;node=#node#;file=#file|urlescape#;style=gitweb">#file|escape#</a> "
-filenodelink = "<tr class="light"><td><a class="list" href="">#file|escape#</a></td><td></td><td class="link"><a href="?cmd=file;filenode=#filenode#;file=#file|urlescape#;style=gitweb">file</a> | <!-- FIXME: <a href="?fd=#filenode|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?cmd=filelog;filenode=#filenode|short#;file=#file|urlescape#;style=gitweb">revisions</a></td></tr>"
-fileellipses = "..."
+naventry = '<a href="?cmd=changelog;rev=#rev#;style=gitweb">#label|escape#</a> '
+navshortentry = '<a href="?cmd=shortlog;rev=#rev#;style=gitweb">#label|escape#</a> '
+filedifflink = '<a href="?cmd=filediff;node=#node#;file=#file|urlescape#;style=gitweb">#file|escape#</a> '
+filenodelink = '<tr class="light"><td><a class="list" href="">#file|escape#</a></td><td></td><td class="link"><a href="?cmd=file;filenode=#filenode#;file=#file|urlescape#;style=gitweb">file</a> | <!-- FIXME: <a href="?fd=#filenode|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?cmd=filelog;filenode=#filenode|short#;file=#file|urlescape#;style=gitweb">revisions</a></td></tr>'
+fileellipses = '...'
 changelogentry = changelogentry-gitweb.tmpl
 searchentry = changelogentry-gitweb.tmpl
 changeset = changeset-gitweb.tmpl
 manifest = manifest-gitweb.tmpl
-manifestdirentry = "<tr class="parity#parity#"><td style="font-family:monospace">drwxr-xr-x</td><td><a href="?mf=#manifest|short#;path=#path|urlescape#;style=gitweb">#basename|escape#/</a></td><td class="link"><a href="?mf=#manifest|short#;path=#path|urlescape#;style=gitweb">manifest</a></td></tr>"
-manifestfileentry = "<tr class="parity#parity#"><td style="font-family:monospace">#permissions|permissions#</td><td class="list"><a class="list" href="?f=#filenode|short#;file=#file|urlescape#;style=gitweb">#basename|escape#</a></td><td class="link"><a href="?f=#filenode|short#;file=#file|urlescape#;style=gitweb">file</a> | <a href="?fl=#filenode|short#;file=#file|urlescape#;style=gitweb">revisions</a> | <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a></td></tr>"
+manifestdirentry = '<tr class="parity#parity#"><td style="font-family:monospace">drwxr-xr-x</td><td><a href="?mf=#manifest|short#;path=#path|urlescape#;style=gitweb">#basename|escape#/</a></td><td class="link"><a href="?mf=#manifest|short#;path=#path|urlescape#;style=gitweb">manifest</a></td></tr>'
+manifestfileentry = '<tr class="parity#parity#"><td style="font-family:monospace">#permissions|permissions#</td><td class="list"><a class="list" href="?f=#filenode|short#;file=#file|urlescape#;style=gitweb">#basename|escape#</a></td><td class="link"><a href="?f=#filenode|short#;file=#file|urlescape#;style=gitweb">file</a> | <a href="?fl=#filenode|short#;file=#file|urlescape#;style=gitweb">revisions</a> | <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a></td></tr>'
 filerevision = filerevision-gitweb.tmpl
 fileannotate = fileannotate-gitweb.tmpl
 filelog = filelog-gitweb.tmpl
-fileline = "<div style="font-family:monospace; white-space: pre;" class="parity#parity#"><span class="linenr">   #linenumber#</span> #line|escape#</div>"
-annotateline = "<tr style="font-family:monospace; white-space: pre;" class="parity#parity#"><td class="linenr" style="text-align: right;"><a href="?cs=#node|short#;style=gitweb">#author|obfuscate#@#rev#</a></td><td>#line|escape#</td></tr>"
-difflineplus = "<div class="pre" style="color:#008800;">#line|escape#</div>"
-difflineminus = "<div class="pre" style="color:#cc0000;">#line|escape#</div>"
-difflineat = "<div class="pre" style="color:#990099;">#line|escape#</div>"
-diffline = "<div class="pre">#line|escape#</div>"
-changelogparent = "<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>"
-changesetparent = "<tr><td>parent</td><td style="font-family:monospace"><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb">#node|short#</a></td></tr>"
-filerevparent = "<tr><td class="metatag">parent:</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>"
-filerename = "<tr><td class="metatag">parent:</td><td><a href="?f=#node|short#;file=#file|urlescape#;style=gitweb">#file|escape#@#node|short#</a></td></tr>"
-filelogrename = "| <a href="?f=#node|short#;file=#file|urlescape#;style=gitweb">base</a>"
-fileannotateparent = "<tr><td class="metatag">parent:</td><td><a href="?cmd=annotate;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>"
-changelogchild = "<tr><th class="child">child #rev#:</th><td class="child"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>"
-changesetchild = "<tr><td>child</td><td style="font-family:monospace"><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb">#node|short#</a></td></tr>"
-filerevchild = "<tr><td class="metatag">child:</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>"
-fileannotatechild = "<tr><td class="metatag">child:</td><td><a href="?cmd=annotate;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>"
+fileline = '<div style="font-family:monospace; white-space: pre;" class="parity#parity#"><span class="linenr">   #linenumber#</span> #line|escape#</div>'
+annotateline = '<tr style="font-family:monospace; white-space: pre;" class="parity#parity#"><td class="linenr" style="text-align: right;"><a href="?cs=#node|short#;style=gitweb">#author|obfuscate#@#rev#</a></td><td>#line|escape#</td></tr>'
+difflineplus = '<div class="pre" style="color:#008800;">#line|escape#</div>'
+difflineminus = '<div class="pre" style="color:#cc0000;">#line|escape#</div>'
+difflineat = '<div class="pre" style="color:#990099;">#line|escape#</div>'
+diffline = '<div class="pre">#line|escape#</div>'
+changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>'
+changesetparent = '<tr><td>parent</td><td style="font-family:monospace"><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb">#node|short#</a></td></tr>'
+filerevparent = '<tr><td class="metatag">parent:</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>'
+filerename = '<tr><td class="metatag">parent:</td><td><a href="?f=#node|short#;file=#file|urlescape#;style=gitweb">#file|escape#@#node|short#</a></td></tr>'
+filelogrename = '| <a href="?f=#node|short#;file=#file|urlescape#;style=gitweb">base</a>'
+fileannotateparent = '<tr><td class="metatag">parent:</td><td><a href="?cmd=annotate;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>'
+changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>'
+changesetchild = '<tr><td>child</td><td style="font-family:monospace"><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb">#node|short#</a></td></tr>'
+filerevchild = '<tr><td class="metatag">child:</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>'
+fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="?cmd=annotate;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>'
 tags = tags-gitweb.tmpl
-tagentry = "<tr class="parity#parity#"><td><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#tag|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> | <a href="?cmd=changelog;rev=#node|short#;style=gitweb">changelog</a> |  <a href="?mf=#tagmanifest|short#;path=/;style=gitweb">manifest</a></td></tr>"
-diffblock = "#lines#"
-changelogtag = "<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>"
-changesettag = "<tr><td>tag</td><td>#tag|escape#</td></tr>"
-filediffparent = "<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>"
-filelogparent = "<tr><td align="right">parent #rev#:&nbsp;</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>"
-filediffchild = "<tr><th class="child">child #rev#:</th><td class="child"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>"
-filelogchild = "<tr><td align="right">child #rev#:&nbsp;</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>"
+tagentry = '<tr class="parity#parity#"><td><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#tag|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> | <a href="?cmd=changelog;rev=#node|short#;style=gitweb">changelog</a> |  <a href="?mf=#tagmanifest|short#;path=/;style=gitweb">manifest</a></td></tr>'
+diffblock = '#lines#'
+changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
+changesettag = '<tr><td>tag</td><td>#tag|escape#</td></tr>'
+filediffparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>'
+filelogparent = '<tr><td align="right">parent #rev#:&nbsp;</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>'
+filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>'
+filelogchild = '<tr><td align="right">child #rev#:&nbsp;</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>'
 shortlog = shortlog-gitweb.tmpl
-shortlogentry = "<tr class="parity#parity#"><td><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> |  <a href="?cmd=manifest;manifest=#manifest|short#;path=/;style=gitweb">manifest</a></td></tr>"
-filelogentry = "<tr class="parity#parity#"><td><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><!-- FIXME: <a href="?fd=#node|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a> #rename%filelogrename#</td></tr>"
-archiveentry = " | <a href="?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> "
+shortlogentry = '<tr class="parity#parity#"><td><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> |  <a href="?cmd=manifest;manifest=#manifest|short#;path=/;style=gitweb">manifest</a></td></tr>'
+filelogentry = '<tr class="parity#parity#"><td><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><!-- FIXME: <a href="?fd=#node|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a> #rename%filelogrename#</td></tr>'
+archiveentry = ' | <a href="?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> '
--- a/templates/map-raw	Mon Mar 13 00:02:33 2006 +0100
+++ b/templates/map-raw	Sun Mar 12 16:21:59 2006 -0800
@@ -1,16 +1,16 @@
 header = header-raw.tmpl
-footer = ""
+footer = ''
 changeset = changeset-raw.tmpl
-difflineplus = "#line#"
-difflineminus = "#line#"
-difflineat = "#line#"
-diffline = "#line#"
-changesetparent = "# parent: #node#"
-changesetchild = "# child: #node#"
-filenodelink = ""
+difflineplus = '#line#'
+difflineminus = '#line#'
+difflineat = '#line#'
+diffline = '#line#'
+changesetparent = '# parent: #node#'
+changesetchild = '# child: #node#'
+filenodelink = ''
 filerevision = filerevision-raw.tmpl
-fileline = "#line#"
-diffblock = "#lines#"
+fileline = '#line#'
+diffblock = '#lines#'
 filediff = filediff-raw.tmpl
 fileannotate = fileannotate-raw.tmpl
-annotateline = "#author#@#rev#: #line#"
+annotateline = '#author#@#rev#: #line#'
--- a/templates/map-rss	Mon Mar 13 00:02:33 2006 +0100
+++ b/templates/map-rss	Sun Mar 12 16:21:59 2006 -0800
@@ -1,4 +1,4 @@
-default = "changelog"
+default = 'changelog'
 header = header-rss.tmpl
 changelog = changelog-rss.tmpl
 changelogentry = changelogentry-rss.tmpl
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-command-template	Sun Mar 12 16:21:59 2006 -0800
@@ -0,0 +1,91 @@
+#!/bin/sh
+
+hg init a
+cd a
+echo a > a
+hg add a
+echo line 1 > b
+echo line 2 >> b
+hg commit -l b -d '1111111111 0' -u 'User Name <user@hostname>'
+hg add b
+echo other 1 > c
+echo other 2 >> c
+echo >> c
+echo other 3 >> c
+hg commit -l c -d '1123456789 0' -u 'A. N. Other <other@place>'
+hg add c
+hg commit -m 'no person' -d '1134567890 0' -u 'other@place'
+echo c >> c
+hg commit -m 'no user, no domain' -d '1144567890 0' -u 'person'
+
+# make sure user/global hgrc does not affect tests
+echo '[ui]' > .hg/hgrc
+echo 'logtemplate =' >> .hg/hgrc
+echo 'style =' >> .hg/hgrc
+
+echo '# default style is like normal output'
+hg log > log.out
+hg log --style default > style.out
+diff log.out style.out
+hg log -v > log.out
+hg log -v --style default > style.out
+diff log.out style.out
+hg log --debug > log.out
+hg log --debug --style default > style.out
+diff log.out style.out
+
+echo '# compact style works'
+hg log --style compact
+hg log -v --style compact
+hg log --debug --style compact
+
+echo '# error if style not readable'
+touch q
+chmod 0 q
+hg log --style ./q
+
+echo '# error if no style'
+hg log --style notexist
+
+echo '# error if style missing key'
+echo 'q = q' > t
+hg log --style ./t
+
+echo '# error if include fails'
+echo 'changeset = q' >> t
+hg log --style ./t
+
+echo '# include works'
+rm -f q
+echo '{rev}' > q
+hg log --style ./t
+
+echo '# ui.style works'
+echo '[ui]' > .hg/hgrc
+echo 'style = t' >> .hg/hgrc
+hg log
+
+echo "# keys work"
+for key in author branches date desc file_adds file_dels files \
+        manifest node parents rev tags; do
+    for mode in '' --verbose --debug; do
+        hg log $mode --template "$key$mode: {$key}\n"
+    done
+done
+
+echo '# filters work'
+hg log --template '{author|domain}\n'
+hg log --template '{author|person}\n'
+hg log --template '{author|user}\n'
+hg log --template '{date|age}\n' > /dev/null || exit 1
+hg log --template '{date|date}\n'
+hg log --template '{date|isodate}\n'
+hg log --template '{date|rfc822date}\n'
+hg log --template '{desc|firstline}\n'
+hg log --template '{node|short}\n'
+
+echo '# error on syntax'
+echo 'x = "f' >> t
+hg log
+
+echo '# done'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-command-template.out	Sun Mar 12 16:21:59 2006 -0800
@@ -0,0 +1,261 @@
+# default style is like normal output
+18a19
+> files:       
+29a31
+> files:       
+43a46
+> files:       
+# compact style works
+3[tip]   2bacb094274c   2006-04-09 07:31 +0000   person
+  no user, no domain
+
+2   cdc488b3baa4   2005-12-14 13:44 +0000   other
+  no person
+
+1   55b647117689   2005-08-07 23:19 +0000   other
+  other 1
+
+0   debcd367d974   2005-03-18 01:58 +0000   user
+  line 1
+
+3[tip]   2bacb094274c   2006-04-09 07:31 +0000   person
+  no user, no domain
+
+2   cdc488b3baa4   2005-12-14 13:44 +0000   other
+  no person
+
+1   55b647117689   2005-08-07 23:19 +0000   other
+  other 1
+
+0   debcd367d974   2005-03-18 01:58 +0000   user
+  line 1
+
+3[tip]:2,-1   2bacb094274c   2006-04-09 07:31 +0000   person
+  no user, no domain
+
+2:1,-1   cdc488b3baa4   2005-12-14 13:44 +0000   other
+  no person
+
+1:0,-1   55b647117689   2005-08-07 23:19 +0000   other
+  other 1
+
+0:-1,-1   debcd367d974   2005-03-18 01:58 +0000   user
+  line 1
+
+# error if style not readable
+abort: Permission denied - ./q
+# error if no style
+abort: No such file or directory - notexist
+# error if style missing key
+abort: ./t: no key named 'changeset'
+# error if include fails
+abort: template file ./q: Permission denied
+# include works
+3
+2
+1
+0
+# ui.style works
+3
+2
+1
+0
+# keys work
+author: person
+author: other@place
+author: A. N. Other <other@place>
+author: User Name <user@hostname>
+author--verbose: person
+author--verbose: other@place
+author--verbose: A. N. Other <other@place>
+author--verbose: User Name <user@hostname>
+author--debug: person
+author--debug: other@place
+author--debug: A. N. Other <other@place>
+author--debug: User Name <user@hostname>
+branches: 
+branches: 
+branches: 
+branches: 
+branches--verbose: 
+branches--verbose: 
+branches--verbose: 
+branches--verbose: 
+branches--debug: 
+branches--debug: 
+branches--debug: 
+branches--debug: 
+date: 1144567890.00
+date: 1134567890.00
+date: 1123456789.00
+date: 1111111111.00
+date--verbose: 1144567890.00
+date--verbose: 1134567890.00
+date--verbose: 1123456789.00
+date--verbose: 1111111111.00
+date--debug: 1144567890.00
+date--debug: 1134567890.00
+date--debug: 1123456789.00
+date--debug: 1111111111.00
+desc: no user, no domain
+desc: no person
+desc: other 1
+other 2
+
+other 3
+
+desc: line 1
+line 2
+
+desc--verbose: no user, no domain
+desc--verbose: no person
+desc--verbose: other 1
+other 2
+
+other 3
+
+desc--verbose: line 1
+line 2
+
+desc--debug: no user, no domain
+desc--debug: no person
+desc--debug: other 1
+other 2
+
+other 3
+
+desc--debug: line 1
+line 2
+
+file_adds: 
+file_adds: 
+file_adds: 
+file_adds: 
+file_adds--verbose: 
+file_adds--verbose: 
+file_adds--verbose: 
+file_adds--verbose: 
+file_adds--debug: 
+file_adds--debug: c
+file_adds--debug: b
+file_adds--debug: a
+file_dels: 
+file_dels: 
+file_dels: 
+file_dels: 
+file_dels--verbose: 
+file_dels--verbose: 
+file_dels--verbose: 
+file_dels--verbose: 
+file_dels--debug: 
+file_dels--debug: 
+file_dels--debug: 
+file_dels--debug: 
+files: c
+files: c
+files: b
+files: a
+files--verbose: c
+files--verbose: c
+files--verbose: b
+files--verbose: a
+files--debug: c
+files--debug: 
+files--debug: 
+files--debug: 
+manifest: 
+manifest: 
+manifest: 
+manifest: 
+manifest--verbose: 
+manifest--verbose: 
+manifest--verbose: 
+manifest--verbose: 
+manifest--debug: 3:cb5a1327723b
+manifest--debug: 2:6e0e82995c35
+manifest--debug: 1:4e8d705b1e53
+manifest--debug: 0:a0c8bcbbb45c
+node: 2bacb094274c6ad120b419cab77a39e51b69cbaa
+node: cdc488b3baa4a2cf316d4d85a3a1f17c5e1695d8
+node: 55b64711768911f37c6d244b12785623aa64e7c3
+node: debcd367d97455db85bba7b583b14b166172de25
+node--verbose: 2bacb094274c6ad120b419cab77a39e51b69cbaa
+node--verbose: cdc488b3baa4a2cf316d4d85a3a1f17c5e1695d8
+node--verbose: 55b64711768911f37c6d244b12785623aa64e7c3
+node--verbose: debcd367d97455db85bba7b583b14b166172de25
+node--debug: 2bacb094274c6ad120b419cab77a39e51b69cbaa
+node--debug: cdc488b3baa4a2cf316d4d85a3a1f17c5e1695d8
+node--debug: 55b64711768911f37c6d244b12785623aa64e7c3
+node--debug: debcd367d97455db85bba7b583b14b166172de25
+parents: 
+parents: 
+parents: 
+parents: 
+parents--verbose: 
+parents--verbose: 
+parents--verbose: 
+parents--verbose: 
+parents--debug: 2:cdc488b3baa4 -1:000000000000 
+parents--debug: 1:55b647117689 -1:000000000000 
+parents--debug: 0:debcd367d974 -1:000000000000 
+parents--debug: -1:000000000000 -1:000000000000 
+rev: 3
+rev: 2
+rev: 1
+rev: 0
+rev--verbose: 3
+rev--verbose: 2
+rev--verbose: 1
+rev--verbose: 0
+rev--debug: 3
+rev--debug: 2
+rev--debug: 1
+rev--debug: 0
+tags: tip
+tags: 
+tags: 
+tags: 
+tags--verbose: tip
+tags--verbose: 
+tags--verbose: 
+tags--verbose: 
+tags--debug: tip
+tags--debug: 
+tags--debug: 
+tags--debug: 
+# filters work
+
+place
+place
+hostname
+person
+other
+A. N. Other
+User Name
+person
+other
+other
+user
+Sun Apr  9 07:31:30 2006 +0000
+Wed Dec 14 13:44:50 2005 +0000
+Sun Aug  7 23:19:49 2005 +0000
+Fri Mar 18 01:58:31 2005 +0000
+2006-04-09 07:31 +0000
+2005-12-14 13:44 +0000
+2005-08-07 23:19 +0000
+2005-03-18 01:58 +0000
+Sun, 09 Apr 2006 07:31:30 +0000
+Wed, 14 Dec 2005 13:44:50 +0000
+Sun, 07 Aug 2005 23:19:49 +0000
+Fri, 18 Mar 2005 01:58:31 +0000
+no user, no domain
+no person
+other 1
+line 1
+2bacb094274c
+cdc488b3baa4
+55b647117689
+debcd367d974
+# error on syntax
+abort: t:3: unmatched quotes
+# done