Mercurial > public > mercurial-scm > hg
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: |