mercurial/hgweb.py
changeset 201 f918a6fa2572
parent 198 c88ef31fb5c0
child 215 9ff5a78d0c45
equal deleted inserted replaced
200:8450c18f2a45 201:f918a6fa2572
    12 
    12 
    13 import os, cgi, time, re, difflib, sys, zlib
    13 import os, cgi, time, re, difflib, sys, zlib
    14 from mercurial.hg import *
    14 from mercurial.hg import *
    15 
    15 
    16 def templatepath():
    16 def templatepath():
    17     for f in "templates/map", "../templates/map":
    17     for f in "templates", "../templates":
    18         p = os.path.join(os.path.dirname(__file__), f)
    18         p = os.path.join(os.path.dirname(__file__), f)
    19         if os.path.isfile(p): return p
    19         if os.path.isdir(p): return p
    20 
    20 
    21 def age(t):
    21 def age(t):
    22     def plural(t, c):
    22     def plural(t, c):
    23         if c == 1: return t
    23         if c == 1: return t
    24         return t + "s"
    24         return t + "s"
    41     for t, s in scales:
    41     for t, s in scales:
    42         n = delta / s
    42         n = delta / s
    43         if n >= 2 or s == 1: return fmt(t, n)
    43         if n >= 2 or s == 1: return fmt(t, n)
    44 
    44 
    45 def nl2br(text):
    45 def nl2br(text):
    46     return text.replace('\n', '<br/>')
    46     return text.replace('\n', '<br/>\n')
    47 
    47 
    48 def obfuscate(text):
    48 def obfuscate(text):
    49     return ''.join([ '&#%d' % ord(c) for c in text ])
    49     return ''.join([ '&#%d' % ord(c) for c in text ])
    50 
    50 
    51 def up(p):
    51 def up(p):
    65             for part in thing:
    65             for part in thing:
    66                 write(part)
    66                 write(part)
    67         else:
    67         else:
    68             sys.stdout.write(str(thing))
    68             sys.stdout.write(str(thing))
    69 
    69 
    70 def template(tmpl, **map):
    70 def template(tmpl, filters = {}, **map):
    71     while tmpl:
    71     while tmpl:
    72         m = re.search(r"#([a-zA-Z0-9]+)#", tmpl)
    72         m = re.search(r"#([a-zA-Z0-9]+)((\|[a-zA-Z0-9]+)*)#", tmpl)
    73         if m:
    73         if m:
    74             yield tmpl[:m.start(0)]
    74             yield tmpl[:m.start(0)]
    75             v = map.get(m.group(1), "")
    75             v = map.get(m.group(1), "")
    76             yield callable(v) and v() or v
    76             v = callable(v) and v() or v
       
    77 
       
    78             fl = m.group(2)
       
    79             if fl:
       
    80                 for f in fl.split("|")[1:]:
       
    81                     v = filters[f](v)
       
    82                 
       
    83             yield v
    77             tmpl = tmpl[m.end(0):]
    84             tmpl = tmpl[m.end(0):]
    78         else:
    85         else:
    79             yield tmpl
    86             yield tmpl
    80             return
    87             return
    81 
    88 
    82 class templater:
    89 class templater:
    83     def __init__(self, mapfile):
    90     def __init__(self, mapfile, filters = {}):
    84         self.cache = {}
    91         self.cache = {}
    85         self.map = {}
    92         self.map = {}
    86         self.base = os.path.dirname(mapfile)
    93         self.base = os.path.dirname(mapfile)
       
    94         self.filters = filters
    87         
    95         
    88         for l in file(mapfile):
    96         for l in file(mapfile):
    89             m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
    97             m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
    90             if m:
    98             if m:
    91                 self.cache[m.group(1)] = m.group(2)
    99                 self.cache[m.group(1)] = m.group(2)
    99     def __call__(self, t, **map):
   107     def __call__(self, t, **map):
   100         try:
   108         try:
   101             tmpl = self.cache[t]
   109             tmpl = self.cache[t]
   102         except KeyError:
   110         except KeyError:
   103             tmpl = self.cache[t] = file(self.map[t]).read()
   111             tmpl = self.cache[t] = file(self.map[t]).read()
   104         return template(tmpl, **map)
   112         return template(tmpl, self.filters, **map)
   105         
   113         
   106 class hgweb:
   114 class hgweb:
   107     maxchanges = 20
   115     maxchanges = 20
   108     maxfiles = 10
   116     maxfiles = 10
   109 
   117 
   110     def __init__(self, path, name, templatemap = ""):
   118     def __init__(self, path, name, templates = ""):
   111         templatemap = templatemap or templatepath()
   119         self.templates = templates or templatepath()
   112 
       
   113         self.reponame = name
   120         self.reponame = name
   114         self.repo = repository(ui(), path)
   121         self.repo = repository(ui(), path)
   115         self.t = templater(templatemap)
       
   116         self.viewonly = 0
   122         self.viewonly = 0
       
   123 
       
   124         self.filters = {
       
   125             "escape": cgi.escape,
       
   126             "age": age,
       
   127             "date": (lambda x: time.asctime(time.gmtime(x))),
       
   128             "addbreaks": nl2br,
       
   129             "obfuscate": obfuscate,
       
   130             "firstline": (lambda x: x.splitlines(1)[0]),
       
   131             }
   117 
   132 
   118     def date(self, cs):
   133     def date(self, cs):
   119         return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
   134         return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
   120 
   135 
   121     def listfiles(self, files, mf):
   136     def listfiles(self, files, mf):
   152                          filenode = hex(fn))
   167                          filenode = hex(fn))
   153             parity[0] = 1 - parity[0]
   168             parity[0] = 1 - parity[0]
   154             
   169             
   155         def prettyprintlines(diff):
   170         def prettyprintlines(diff):
   156             for l in diff.splitlines(1):
   171             for l in diff.splitlines(1):
   157                 line = cgi.escape(l)
   172                 if l.startswith('+'):
   158                 if line.startswith('+'):
   173                     yield self.t("difflineplus", line = l)
   159                     yield self.t("difflineplus", line = line)
   174                 elif l.startswith('-'):
   160                 elif line.startswith('-'):
   175                     yield self.t("difflineminus", line = l)
   161                     yield self.t("difflineminus", line = line)
   176                 elif l.startswith('@'):
   162                 elif line.startswith('@'):
   177                     yield self.t("difflineat", line = l)
   163                     yield self.t("difflineat", line = line)
       
   164                 else:
   178                 else:
   165                     yield self.t("diffline", line = line)
   179                     yield self.t("diffline", line = l)
   166 
   180 
   167         r = self.repo
   181         r = self.repo
   168         cl = r.changelog
   182         cl = r.changelog
   169         mf = r.manifest
   183         mf = r.manifest
   170         change1 = cl.read(node1)
   184         change1 = cl.read(node1)
   232                 t = float(changes[2].split(' ')[0])
   246                 t = float(changes[2].split(' ')[0])
   233 
   247 
   234                 l.insert(0, self.t(
   248                 l.insert(0, self.t(
   235                     'changelogentry',
   249                     'changelogentry',
   236                     parity = parity,
   250                     parity = parity,
   237                     author = obfuscate(changes[1]),
   251                     author = changes[1],
   238                     shortdesc = cgi.escape(changes[4].splitlines()[0]),
       
   239                     age = age(t),
       
   240                     parent1 = self.parent("changelogparent",
   252                     parent1 = self.parent("changelogparent",
   241                                           hex(p1), cl.rev(p1)),
   253                                           hex(p1), cl.rev(p1)),
   242                     parent2 = self.parent("changelogparent",
   254                     parent2 = self.parent("changelogparent",
   243                                           hex(p2), cl.rev(p2)),
   255                                           hex(p2), cl.rev(p2)),
   244                     p1 = hex(p1), p2 = hex(p2),
   256                     p1 = hex(p1), p2 = hex(p2),
   245                     p1rev = cl.rev(p1), p2rev = cl.rev(p2),
   257                     p1rev = cl.rev(p1), p2rev = cl.rev(p2),
   246                     manifest = hex(changes[0]),
   258                     manifest = hex(changes[0]),
   247                     desc = nl2br(cgi.escape(changes[4])),
   259                     desc = changes[4],
   248                     date = time.asctime(time.gmtime(t)),
   260                     date = t,
   249                     files = self.listfilediffs(changes[3], n),
   261                     files = self.listfilediffs(changes[3], n),
   250                     rev = i,
   262                     rev = i,
   251                     node = hn))
   263                     node = hn))
   252                 parity = 1 - parity
   264                 parity = 1 - parity
   253 
   265 
   290                      footer = self.footer(),
   302                      footer = self.footer(),
   291                      repo = self.reponame,
   303                      repo = self.reponame,
   292                      diff = diff,
   304                      diff = diff,
   293                      rev = cl.rev(n),
   305                      rev = cl.rev(n),
   294                      node = nodeid,
   306                      node = nodeid,
   295                      shortdesc = cgi.escape(changes[4].splitlines()[0]),
       
   296                      parent1 = self.parent("changesetparent",
   307                      parent1 = self.parent("changesetparent",
   297                                            hex(p1), cl.rev(p1)),
   308                                            hex(p1), cl.rev(p1)),
   298                      parent2 = self.parent("changesetparent",
   309                      parent2 = self.parent("changesetparent",
   299                                            hex(p2), cl.rev(p2)),
   310                                            hex(p2), cl.rev(p2)),
   300                      p1 = hex(p1), p2 = hex(p2),
   311                      p1 = hex(p1), p2 = hex(p2),
   301                      p1rev = cl.rev(p1), p2rev = cl.rev(p2),
   312                      p1rev = cl.rev(p1), p2rev = cl.rev(p2),
   302                      manifest = hex(changes[0]),
   313                      manifest = hex(changes[0]),
   303                      author = obfuscate(changes[1]),
   314                      author = changes[1],
   304                      desc = nl2br(cgi.escape(changes[4])),
   315                      desc = changes[4],
   305                      date = time.asctime(time.gmtime(t)),
   316                      date = t,
   306                      files = files)
   317                      files = files)
   307 
   318 
   308     def filelog(self, f, filenode):
   319     def filelog(self, f, filenode):
   309         cl = self.repo.changelog
   320         cl = self.repo.changelog
   310         fl = self.repo.file(f)
   321         fl = self.repo.file(f)
   327                                    parity = parity,
   338                                    parity = parity,
   328                                    filenode = hex(n),
   339                                    filenode = hex(n),
   329                                    filerev = i,
   340                                    filerev = i,
   330                                    file = f,
   341                                    file = f,
   331                                    node = hex(cn),
   342                                    node = hex(cn),
   332                                    author = obfuscate(cs[1]),
   343                                    author = cs[1],
   333                                    age = age(t),
   344                                    date = t,
   334                                    date = time.asctime(time.gmtime(t)),
   345                                    desc = cs[4],
   335                                    shortdesc = cgi.escape(cs[4].splitlines()[0]),
       
   336                                    p1 = hex(p1), p2 = hex(p2),
   346                                    p1 = hex(p1), p2 = hex(p2),
   337                                    p1rev = fl.rev(p1), p2rev = fl.rev(p2)))
   347                                    p1rev = fl.rev(p1), p2rev = fl.rev(p2)))
   338                 parity = 1 - parity
   348                 parity = 1 - parity
   339 
   349 
   340             yield l
   350             yield l
   348                      entries = entries)
   358                      entries = entries)
   349 
   359 
   350     def filerevision(self, f, node):
   360     def filerevision(self, f, node):
   351         fl = self.repo.file(f)
   361         fl = self.repo.file(f)
   352         n = bin(node)
   362         n = bin(node)
   353         text = cgi.escape(fl.read(n))
   363         text = fl.read(n)
   354         changerev = fl.linkrev(n)
   364         changerev = fl.linkrev(n)
   355         cl = self.repo.changelog
   365         cl = self.repo.changelog
   356         cn = cl.node(changerev)
   366         cn = cl.node(changerev)
   357         cs = cl.read(cn)
   367         cs = cl.read(cn)
   358         p1, p2 = fl.parents(n)
   368         p1, p2 = fl.parents(n)
   359         t = float(cs[2].split(' ')[0])
   369         t = float(cs[2].split(' ')[0])
   360         mfn = cs[0]
   370         mfn = cs[0]
   361 
   371 
   362         def lines():
   372         def lines():
   363             for l, t in enumerate(text.splitlines(1)):
   373             for l, t in enumerate(text.splitlines(1)):
   364                 yield self.t("fileline",
   374                 yield self.t("fileline", line = t,
   365                              line = t,
       
   366                              linenumber = "% 6d" % (l + 1),
   375                              linenumber = "% 6d" % (l + 1),
   367                              parity = l & 1)
   376                              parity = l & 1)
   368         
   377         
   369         yield self.t("filerevision", file = f,
   378         yield self.t("filerevision", file = f,
   370                      header = self.header(),
   379                      header = self.header(),
   374                      path = up(f),
   383                      path = up(f),
   375                      text = lines(),
   384                      text = lines(),
   376                      rev = changerev,
   385                      rev = changerev,
   377                      node = hex(cn),
   386                      node = hex(cn),
   378                      manifest = hex(mfn),
   387                      manifest = hex(mfn),
   379                      author = obfuscate(cs[1]),
   388                      author = cs[1],
   380                      age = age(t),
   389                      date = t,
   381                      date = time.asctime(time.gmtime(t)),
       
   382                      shortdesc = cgi.escape(cs[4].splitlines()[0]),
       
   383                      parent1 = self.parent("filerevparent",
   390                      parent1 = self.parent("filerevparent",
   384                                            hex(p1), fl.rev(p1), file=f),
   391                                            hex(p1), fl.rev(p1), file=f),
   385                      parent2 = self.parent("filerevparent",
   392                      parent2 = self.parent("filerevparent",
   386                                            hex(p2), fl.rev(p2), file=f),
   393                                            hex(p2), fl.rev(p2), file=f),
   387                      p1 = hex(p1), p2 = hex(p2),
   394                      p1 = hex(p1), p2 = hex(p2),
   388                      p1rev = fl.rev(p1), p2rev = fl.rev(p2))
   395                      p1rev = fl.rev(p1), p2rev = fl.rev(p2))
   389 
       
   390 
   396 
   391     def fileannotate(self, f, node):
   397     def fileannotate(self, f, node):
   392         bcache = {}
   398         bcache = {}
   393         ncache = {}
   399         ncache = {}
   394         fl = self.repo.file(f)
   400         fl = self.repo.file(f)
   429                              parity = parity,
   435                              parity = parity,
   430                              node = hex(cnode),
   436                              node = hex(cnode),
   431                              rev = r,
   437                              rev = r,
   432                              author = name,
   438                              author = name,
   433                              file = f,
   439                              file = f,
   434                              line = cgi.escape(l))
   440                              line = l)
   435 
   441 
   436         yield self.t("fileannotate",
   442         yield self.t("fileannotate",
   437                      header = self.header(),
   443                      header = self.header(),
   438                      footer = self.footer(),
   444                      footer = self.footer(),
   439                      repo = self.reponame,
   445                      repo = self.reponame,
   442                      annotate = annotate,
   448                      annotate = annotate,
   443                      path = up(f),
   449                      path = up(f),
   444                      rev = changerev,
   450                      rev = changerev,
   445                      node = hex(cn),
   451                      node = hex(cn),
   446                      manifest = hex(mfn),
   452                      manifest = hex(mfn),
   447                      author = obfuscate(cs[1]),
   453                      author = cs[1],
   448                      age = age(t),
   454                      date = t,
   449                      date = time.asctime(time.gmtime(t)),
       
   450                      shortdesc = cgi.escape(cs[4].splitlines()[0]),
       
   451                      parent1 = self.parent("fileannotateparent",
   455                      parent1 = self.parent("fileannotateparent",
   452                                            hex(p1), fl.rev(p1), file=f),
   456                                            hex(p1), fl.rev(p1), file=f),
   453                      parent2 = self.parent("fileannotateparent",
   457                      parent2 = self.parent("fileannotateparent",
   454                                            hex(p2), fl.rev(p2), file=f),
   458                                            hex(p2), fl.rev(p2), file=f),
   455                      p1 = hex(p1), p2 = hex(p2),
   459                      p1 = hex(p1), p2 = hex(p2),
   561     # find tag, changeset, file
   565     # find tag, changeset, file
   562 
   566 
   563     def run(self):
   567     def run(self):
   564         args = cgi.parse()
   568         args = cgi.parse()
   565 
   569 
       
   570         m = os.path.join(self.templates, "map")
       
   571         if args.has_key('style'):
       
   572             b = os.path.basename("map-" + args['style'][0])
       
   573             p = os.path.join(self.templates, b)
       
   574             if os.path.isfile(p): m = p
       
   575             
       
   576         self.t = templater(m, self.filters)
       
   577 
   566         if not args.has_key('cmd') or args['cmd'][0] == 'changelog':
   578         if not args.has_key('cmd') or args['cmd'][0] == 'changelog':
   567             hi = self.repo.changelog.count()
   579             hi = self.repo.changelog.count()
   568             if args.has_key('rev'):
   580             if args.has_key('rev'):
   569                 hi = args['rev'][0]
   581                 hi = args['rev'][0]
   570                 try:
   582                 try: