comparison mercurial/hgweb.py @ 201:f918a6fa2572

hgweb: add template filters, template style maps, and raw pages -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 hgweb: add template filters, template style maps, and raw pages Template filters: in templates, you can now specify a chain of filters like #desc|firstline|escape# #desc|escape|addbreaks# #date|age# to specify how you'd like raw text (or whatever) to be transformed. Template style maps: add ;style=foo to a URL and we'll use templates/map-foo if it exists. Raw output: Together, these two features make it east to implement raw downloadable files and patches. Simply link to the same page with style=raw and present the output as unfiltered text/plain with that template. manifest hash: 5954a648b3d6b4e6dc2dcd1975f96b4b0178da2a -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.0 (GNU/Linux) iD8DBQFCnUMyywK+sNU5EO8RAkKjAJ9h9JElSCbWBPUnL+koCSDxgo38AwCgrccM 0qwyKdh/fUNglICxSh3HBNA= =Svlo -----END PGP SIGNATURE-----
author mpm@selenic.com
date Tue, 31 May 2005 21:10:10 -0800
parents c88ef31fb5c0
children 9ff5a78d0c45
comparison
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: