##// END OF EJS Templates
hgweb: minor improvements for new web style...
Matt Mackall -
r6434:62e0bb41 default
parent child Browse files
Show More
@@ -1,572 +1,576 b''
1 #
1 #
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import os, mimetypes, re
8 import os, mimetypes, re
9 import webutil
9 import webutil
10 from mercurial import revlog, archival
10 from mercurial import revlog, archival
11 from mercurial.node import short, hex, nullid
11 from mercurial.node import short, hex, nullid
12 from mercurial.util import binary
12 from mercurial.util import binary
13 from mercurial.repo import RepoError
13 from mercurial.repo import RepoError
14 from common import paritygen, staticfile, get_contact, ErrorResponse
14 from common import paritygen, staticfile, get_contact, ErrorResponse
15 from common import HTTP_OK, HTTP_NOT_FOUND
15 from common import HTTP_OK, HTTP_NOT_FOUND
16
16
17 # __all__ is populated with the allowed commands. Be sure to add to it if
17 # __all__ is populated with the allowed commands. Be sure to add to it if
18 # you're adding a new command, or the new command won't work.
18 # you're adding a new command, or the new command won't work.
19
19
20 __all__ = [
20 __all__ = [
21 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
21 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
22 'manifest', 'tags', 'summary', 'filediff', 'diff', 'annotate', 'filelog',
22 'manifest', 'tags', 'summary', 'filediff', 'diff', 'annotate', 'filelog',
23 'archive', 'static',
23 'archive', 'static',
24 ]
24 ]
25
25
26 def log(web, req, tmpl):
26 def log(web, req, tmpl):
27 if 'file' in req.form and req.form['file'][0]:
27 if 'file' in req.form and req.form['file'][0]:
28 return filelog(web, req, tmpl)
28 return filelog(web, req, tmpl)
29 else:
29 else:
30 return changelog(web, req, tmpl)
30 return changelog(web, req, tmpl)
31
31
32 def rawfile(web, req, tmpl):
32 def rawfile(web, req, tmpl):
33 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
33 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
34 if not path:
34 if not path:
35 content = manifest(web, req, tmpl)
35 content = manifest(web, req, tmpl)
36 req.respond(HTTP_OK, web.ctype)
36 req.respond(HTTP_OK, web.ctype)
37 return content
37 return content
38
38
39 try:
39 try:
40 fctx = webutil.filectx(web.repo, req)
40 fctx = webutil.filectx(web.repo, req)
41 except revlog.LookupError, inst:
41 except revlog.LookupError, inst:
42 try:
42 try:
43 content = manifest(web, req, tmpl)
43 content = manifest(web, req, tmpl)
44 req.respond(HTTP_OK, web.ctype)
44 req.respond(HTTP_OK, web.ctype)
45 return content
45 return content
46 except ErrorResponse:
46 except ErrorResponse:
47 raise inst
47 raise inst
48
48
49 path = fctx.path()
49 path = fctx.path()
50 text = fctx.data()
50 text = fctx.data()
51 mt = mimetypes.guess_type(path)[0]
51 mt = mimetypes.guess_type(path)[0]
52 if mt is None or binary(text):
52 if mt is None or binary(text):
53 mt = mt or 'application/octet-stream'
53 mt = mt or 'application/octet-stream'
54
54
55 req.respond(HTTP_OK, mt, path, len(text))
55 req.respond(HTTP_OK, mt, path, len(text))
56 return [text]
56 return [text]
57
57
58 def _filerevision(web, tmpl, fctx):
58 def _filerevision(web, tmpl, fctx):
59 f = fctx.path()
59 f = fctx.path()
60 text = fctx.data()
60 text = fctx.data()
61 fl = fctx.filelog()
61 fl = fctx.filelog()
62 n = fctx.filenode()
62 n = fctx.filenode()
63 parity = paritygen(web.stripecount)
63 parity = paritygen(web.stripecount)
64
64
65 if binary(text):
65 if binary(text):
66 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
66 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
67 text = '(binary:%s)' % mt
67 text = '(binary:%s)' % mt
68
68
69 def lines():
69 def lines():
70 for lineno, t in enumerate(text.splitlines(1)):
70 for lineno, t in enumerate(text.splitlines(1)):
71 yield {"line": t,
71 yield {"line": t,
72 "lineid": "l%d" % (lineno + 1),
72 "lineid": "l%d" % (lineno + 1),
73 "linenumber": "% 6d" % (lineno + 1),
73 "linenumber": "% 6d" % (lineno + 1),
74 "parity": parity.next()}
74 "parity": parity.next()}
75
75
76 return tmpl("filerevision",
76 return tmpl("filerevision",
77 file=f,
77 file=f,
78 path=webutil.up(f),
78 path=webutil.up(f),
79 text=lines(),
79 text=lines(),
80 rev=fctx.rev(),
80 rev=fctx.rev(),
81 node=hex(fctx.node()),
81 node=hex(fctx.node()),
82 author=fctx.user(),
82 author=fctx.user(),
83 date=fctx.date(),
83 date=fctx.date(),
84 desc=fctx.description(),
84 desc=fctx.description(),
85 branch=webutil.nodebranchnodefault(fctx),
85 branch=webutil.nodebranchnodefault(fctx),
86 parent=webutil.siblings(fctx.parents()),
86 parent=webutil.siblings(fctx.parents()),
87 child=webutil.siblings(fctx.children()),
87 child=webutil.siblings(fctx.children()),
88 rename=webutil.renamelink(fl, n),
88 rename=webutil.renamelink(fctx),
89 permissions=fctx.manifest().flags(f))
89 permissions=fctx.manifest().flags(f))
90
90
91 def file(web, req, tmpl):
91 def file(web, req, tmpl):
92 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
92 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
93 if path:
93 if path:
94 try:
94 try:
95 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
95 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
96 except revlog.LookupError, inst:
96 except revlog.LookupError, inst:
97 pass
97 pass
98
98
99 try:
99 try:
100 return manifest(web, req, tmpl)
100 return manifest(web, req, tmpl)
101 except ErrorResponse:
101 except ErrorResponse:
102 raise inst
102 raise inst
103
103
104 def _search(web, tmpl, query):
104 def _search(web, tmpl, query):
105
105
106 def changelist(**map):
106 def changelist(**map):
107 cl = web.repo.changelog
107 cl = web.repo.changelog
108 count = 0
108 count = 0
109 qw = query.lower().split()
109 qw = query.lower().split()
110
110
111 def revgen():
111 def revgen():
112 for i in xrange(cl.count() - 1, 0, -100):
112 for i in xrange(cl.count() - 1, 0, -100):
113 l = []
113 l = []
114 for j in xrange(max(0, i - 100), i + 1):
114 for j in xrange(max(0, i - 100), i + 1):
115 ctx = web.repo.changectx(j)
115 ctx = web.repo.changectx(j)
116 l.append(ctx)
116 l.append(ctx)
117 l.reverse()
117 l.reverse()
118 for e in l:
118 for e in l:
119 yield e
119 yield e
120
120
121 for ctx in revgen():
121 for ctx in revgen():
122 miss = 0
122 miss = 0
123 for q in qw:
123 for q in qw:
124 if not (q in ctx.user().lower() or
124 if not (q in ctx.user().lower() or
125 q in ctx.description().lower() or
125 q in ctx.description().lower() or
126 q in " ".join(ctx.files()).lower()):
126 q in " ".join(ctx.files()).lower()):
127 miss = 1
127 miss = 1
128 break
128 break
129 if miss:
129 if miss:
130 continue
130 continue
131
131
132 count = 1
132 count = 1
133 n = ctx.node()
133 n = ctx.node()
134 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
134 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
135
135
136 yield tmpl('searchentry',
136 yield tmpl('searchentry',
137 parity=parity.next(),
137 parity=parity.next(),
138 author=ctx.user(),
138 author=ctx.user(),
139 parent=webutil.siblings(ctx.parents()),
139 parent=webutil.siblings(ctx.parents()),
140 child=webutil.siblings(ctx.children()),
140 child=webutil.siblings(ctx.children()),
141 changelogtag=showtags,
141 changelogtag=showtags,
142 desc=ctx.description(),
142 desc=ctx.description(),
143 date=ctx.date(),
143 date=ctx.date(),
144 files=web.listfilediffs(tmpl, ctx.files(), n),
144 files=web.listfilediffs(tmpl, ctx.files(), n),
145 rev=ctx.rev(),
145 rev=ctx.rev(),
146 node=hex(n),
146 node=hex(n),
147 tags=webutil.nodetagsdict(web.repo, n),
147 tags=webutil.nodetagsdict(web.repo, n),
148 inbranch=webutil.nodeinbranch(web.repo, ctx),
148 inbranch=webutil.nodeinbranch(web.repo, ctx),
149 branches=webutil.nodebranchdict(web.repo, ctx))
149 branches=webutil.nodebranchdict(web.repo, ctx))
150
150
151 if count >= web.maxchanges:
151 if count >= web.maxchanges:
152 break
152 break
153
153
154 cl = web.repo.changelog
154 cl = web.repo.changelog
155 parity = paritygen(web.stripecount)
155 parity = paritygen(web.stripecount)
156
156
157 return tmpl('search',
157 return tmpl('search',
158 query=query,
158 query=query,
159 node=hex(cl.tip()),
159 node=hex(cl.tip()),
160 entries=changelist,
160 entries=changelist,
161 archives=web.archivelist("tip"))
161 archives=web.archivelist("tip"))
162
162
163 def changelog(web, req, tmpl, shortlog = False):
163 def changelog(web, req, tmpl, shortlog = False):
164 if 'node' in req.form:
164 if 'node' in req.form:
165 ctx = webutil.changectx(web.repo, req)
165 ctx = webutil.changectx(web.repo, req)
166 else:
166 else:
167 if 'rev' in req.form:
167 if 'rev' in req.form:
168 hi = req.form['rev'][0]
168 hi = req.form['rev'][0]
169 else:
169 else:
170 hi = web.repo.changelog.count() - 1
170 hi = web.repo.changelog.count() - 1
171 try:
171 try:
172 ctx = web.repo.changectx(hi)
172 ctx = web.repo.changectx(hi)
173 except RepoError:
173 except RepoError:
174 return _search(web, tmpl, hi) # XXX redirect to 404 page?
174 return _search(web, tmpl, hi) # XXX redirect to 404 page?
175
175
176 def changelist(limit=0, **map):
176 def changelist(limit=0, **map):
177 cl = web.repo.changelog
177 cl = web.repo.changelog
178 l = [] # build a list in forward order for efficiency
178 l = [] # build a list in forward order for efficiency
179 for i in xrange(start, end):
179 for i in xrange(start, end):
180 ctx = web.repo.changectx(i)
180 ctx = web.repo.changectx(i)
181 n = ctx.node()
181 n = ctx.node()
182 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
182 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
183
183
184 l.insert(0, {"parity": parity.next(),
184 l.insert(0, {"parity": parity.next(),
185 "author": ctx.user(),
185 "author": ctx.user(),
186 "parent": webutil.siblings(ctx.parents(), i - 1),
186 "parent": webutil.siblings(ctx.parents(), i - 1),
187 "child": webutil.siblings(ctx.children(), i + 1),
187 "child": webutil.siblings(ctx.children(), i + 1),
188 "changelogtag": showtags,
188 "changelogtag": showtags,
189 "desc": ctx.description(),
189 "desc": ctx.description(),
190 "date": ctx.date(),
190 "date": ctx.date(),
191 "files": web.listfilediffs(tmpl, ctx.files(), n),
191 "files": web.listfilediffs(tmpl, ctx.files(), n),
192 "rev": i,
192 "rev": i,
193 "node": hex(n),
193 "node": hex(n),
194 "tags": webutil.nodetagsdict(web.repo, n),
194 "tags": webutil.nodetagsdict(web.repo, n),
195 "inbranch": webutil.nodeinbranch(web.repo, ctx),
195 "inbranch": webutil.nodeinbranch(web.repo, ctx),
196 "branches": webutil.nodebranchdict(web.repo, ctx)
196 "branches": webutil.nodebranchdict(web.repo, ctx)
197 })
197 })
198
198
199 if limit > 0:
199 if limit > 0:
200 l = l[:limit]
200 l = l[:limit]
201
201
202 for e in l:
202 for e in l:
203 yield e
203 yield e
204
204
205 maxchanges = shortlog and web.maxshortchanges or web.maxchanges
205 maxchanges = shortlog and web.maxshortchanges or web.maxchanges
206 cl = web.repo.changelog
206 cl = web.repo.changelog
207 count = cl.count()
207 count = cl.count()
208 pos = ctx.rev()
208 pos = ctx.rev()
209 start = max(0, pos - maxchanges + 1)
209 start = max(0, pos - maxchanges + 1)
210 end = min(count, start + maxchanges)
210 end = min(count, start + maxchanges)
211 pos = end - 1
211 pos = end - 1
212 parity = paritygen(web.stripecount, offset=start-end)
212 parity = paritygen(web.stripecount, offset=start-end)
213
213
214 changenav = webutil.revnavgen(pos, maxchanges, count, web.repo.changectx)
214 changenav = webutil.revnavgen(pos, maxchanges, count, web.repo.changectx)
215
215
216 return tmpl(shortlog and 'shortlog' or 'changelog',
216 return tmpl(shortlog and 'shortlog' or 'changelog',
217 changenav=changenav,
217 changenav=changenav,
218 node=hex(cl.tip()),
218 node=hex(ctx.node()),
219 rev=pos, changesets=count,
219 rev=pos, changesets=count,
220 entries=lambda **x: changelist(limit=0,**x),
220 entries=lambda **x: changelist(limit=0,**x),
221 latestentry=lambda **x: changelist(limit=1,**x),
221 latestentry=lambda **x: changelist(limit=1,**x),
222 archives=web.archivelist("tip"))
222 archives=web.archivelist("tip"))
223
223
224 def shortlog(web, req, tmpl):
224 def shortlog(web, req, tmpl):
225 return changelog(web, req, tmpl, shortlog = True)
225 return changelog(web, req, tmpl, shortlog = True)
226
226
227 def changeset(web, req, tmpl):
227 def changeset(web, req, tmpl):
228 ctx = webutil.changectx(web.repo, req)
228 ctx = webutil.changectx(web.repo, req)
229 n = ctx.node()
229 n = ctx.node()
230 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', n)
230 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', n)
231 parents = ctx.parents()
231 parents = ctx.parents()
232 p1 = parents[0].node()
232 p1 = parents[0].node()
233
233
234 files = []
234 files = []
235 parity = paritygen(web.stripecount)
235 parity = paritygen(web.stripecount)
236 for f in ctx.files():
236 for f in ctx.files():
237 files.append(tmpl("filenodelink",
237 files.append(tmpl("filenodelink",
238 node=hex(n), file=f,
238 node=hex(n), file=f,
239 parity=parity.next()))
239 parity=parity.next()))
240
240
241 diffs = web.diff(tmpl, p1, n, None)
241 diffs = web.diff(tmpl, p1, n, None)
242 return tmpl('changeset',
242 return tmpl('changeset',
243 diff=diffs,
243 diff=diffs,
244 rev=ctx.rev(),
244 rev=ctx.rev(),
245 node=hex(n),
245 node=hex(n),
246 parent=webutil.siblings(parents),
246 parent=webutil.siblings(parents),
247 child=webutil.siblings(ctx.children()),
247 child=webutil.siblings(ctx.children()),
248 changesettag=showtags,
248 changesettag=showtags,
249 author=ctx.user(),
249 author=ctx.user(),
250 desc=ctx.description(),
250 desc=ctx.description(),
251 date=ctx.date(),
251 date=ctx.date(),
252 files=files,
252 files=files,
253 archives=web.archivelist(hex(n)),
253 archives=web.archivelist(hex(n)),
254 tags=webutil.nodetagsdict(web.repo, n),
254 tags=webutil.nodetagsdict(web.repo, n),
255 branch=webutil.nodebranchnodefault(ctx),
255 branch=webutil.nodebranchnodefault(ctx),
256 inbranch=webutil.nodeinbranch(web.repo, ctx),
256 inbranch=webutil.nodeinbranch(web.repo, ctx),
257 branches=webutil.nodebranchdict(web.repo, ctx))
257 branches=webutil.nodebranchdict(web.repo, ctx))
258
258
259 rev = changeset
259 rev = changeset
260
260
261 def manifest(web, req, tmpl):
261 def manifest(web, req, tmpl):
262 ctx = webutil.changectx(web.repo, req)
262 ctx = webutil.changectx(web.repo, req)
263 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
263 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
264 mf = ctx.manifest()
264 mf = ctx.manifest()
265 node = ctx.node()
265 node = ctx.node()
266
266
267 files = {}
267 files = {}
268 parity = paritygen(web.stripecount)
268 parity = paritygen(web.stripecount)
269
269
270 if path and path[-1] != "/":
270 if path and path[-1] != "/":
271 path += "/"
271 path += "/"
272 l = len(path)
272 l = len(path)
273 abspath = "/" + path
273 abspath = "/" + path
274
274
275 for f, n in mf.items():
275 for f, n in mf.items():
276 if f[:l] != path:
276 if f[:l] != path:
277 continue
277 continue
278 remain = f[l:]
278 remain = f[l:]
279 if "/" in remain:
279 if "/" in remain:
280 short = remain[:remain.index("/") + 1] # bleah
280 short = remain[:remain.index("/") + 1] # bleah
281 files[short] = (f, None)
281 files[short] = (f, None)
282 else:
282 else:
283 short = os.path.basename(remain)
283 short = os.path.basename(remain)
284 files[short] = (f, n)
284 files[short] = (f, n)
285
285
286 if not files:
286 if not files:
287 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
287 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
288
288
289 def filelist(**map):
289 def filelist(**map):
290 fl = files.keys()
290 fl = files.keys()
291 fl.sort()
291 fl.sort()
292 for f in fl:
292 for f in fl:
293 full, fnode = files[f]
293 full, fnode = files[f]
294 if not fnode:
294 if not fnode:
295 continue
295 continue
296
296
297 fctx = ctx.filectx(full)
297 fctx = ctx.filectx(full)
298 yield {"file": full,
298 yield {"file": full,
299 "parity": parity.next(),
299 "parity": parity.next(),
300 "basename": f,
300 "basename": f,
301 "date": fctx.changectx().date(),
301 "date": fctx.changectx().date(),
302 "size": fctx.size(),
302 "size": fctx.size(),
303 "permissions": mf.flags(full)}
303 "permissions": mf.flags(full)}
304
304
305 def dirlist(**map):
305 def dirlist(**map):
306 fl = files.keys()
306 fl = files.keys()
307 fl.sort()
307 fl.sort()
308 for f in fl:
308 for f in fl:
309 full, fnode = files[f]
309 full, fnode = files[f]
310 if fnode:
310 if fnode:
311 continue
311 continue
312
312
313 yield {"parity": parity.next(),
313 yield {"parity": parity.next(),
314 "path": "%s%s" % (abspath, f),
314 "path": "%s%s" % (abspath, f),
315 "basename": f[:-1]}
315 "basename": f[:-1]}
316
316
317 return tmpl("manifest",
317 return tmpl("manifest",
318 rev=ctx.rev(),
318 rev=ctx.rev(),
319 node=hex(node),
319 node=hex(node),
320 path=abspath,
320 path=abspath,
321 up=webutil.up(abspath),
321 up=webutil.up(abspath),
322 upparity=parity.next(),
322 upparity=parity.next(),
323 fentries=filelist,
323 fentries=filelist,
324 dentries=dirlist,
324 dentries=dirlist,
325 archives=web.archivelist(hex(node)),
325 archives=web.archivelist(hex(node)),
326 tags=webutil.nodetagsdict(web.repo, node),
326 tags=webutil.nodetagsdict(web.repo, node),
327 inbranch=webutil.nodeinbranch(web.repo, ctx),
327 inbranch=webutil.nodeinbranch(web.repo, ctx),
328 branches=webutil.nodebranchdict(web.repo, ctx))
328 branches=webutil.nodebranchdict(web.repo, ctx))
329
329
330 def tags(web, req, tmpl):
330 def tags(web, req, tmpl):
331 i = web.repo.tagslist()
331 i = web.repo.tagslist()
332 i.reverse()
332 i.reverse()
333 parity = paritygen(web.stripecount)
333 parity = paritygen(web.stripecount)
334
334
335 def entries(notip=False,limit=0, **map):
335 def entries(notip=False,limit=0, **map):
336 count = 0
336 count = 0
337 for k, n in i:
337 for k, n in i:
338 if notip and k == "tip":
338 if notip and k == "tip":
339 continue
339 continue
340 if limit > 0 and count >= limit:
340 if limit > 0 and count >= limit:
341 continue
341 continue
342 count = count + 1
342 count = count + 1
343 yield {"parity": parity.next(),
343 yield {"parity": parity.next(),
344 "tag": k,
344 "tag": k,
345 "date": web.repo.changectx(n).date(),
345 "date": web.repo.changectx(n).date(),
346 "node": hex(n)}
346 "node": hex(n)}
347
347
348 return tmpl("tags",
348 return tmpl("tags",
349 node=hex(web.repo.changelog.tip()),
349 node=hex(web.repo.changelog.tip()),
350 entries=lambda **x: entries(False,0, **x),
350 entries=lambda **x: entries(False,0, **x),
351 entriesnotip=lambda **x: entries(True,0, **x),
351 entriesnotip=lambda **x: entries(True,0, **x),
352 latestentry=lambda **x: entries(True,1, **x))
352 latestentry=lambda **x: entries(True,1, **x))
353
353
354 def summary(web, req, tmpl):
354 def summary(web, req, tmpl):
355 i = web.repo.tagslist()
355 i = web.repo.tagslist()
356 i.reverse()
356 i.reverse()
357
357
358 def tagentries(**map):
358 def tagentries(**map):
359 parity = paritygen(web.stripecount)
359 parity = paritygen(web.stripecount)
360 count = 0
360 count = 0
361 for k, n in i:
361 for k, n in i:
362 if k == "tip": # skip tip
362 if k == "tip": # skip tip
363 continue
363 continue
364
364
365 count = 1
365 count = 1
366 if count > 10: # limit to 10 tags
366 if count > 10: # limit to 10 tags
367 break
367 break
368
368
369 yield tmpl("tagentry",
369 yield tmpl("tagentry",
370 parity=parity.next(),
370 parity=parity.next(),
371 tag=k,
371 tag=k,
372 node=hex(n),
372 node=hex(n),
373 date=web.repo.changectx(n).date())
373 date=web.repo.changectx(n).date())
374
374
375 def branches(**map):
375 def branches(**map):
376 parity = paritygen(web.stripecount)
376 parity = paritygen(web.stripecount)
377
377
378 b = web.repo.branchtags()
378 b = web.repo.branchtags()
379 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.items()]
379 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.items()]
380 l.sort()
380 l.sort()
381
381
382 for r,n,t in l:
382 for r,n,t in l:
383 ctx = web.repo.changectx(n)
383 ctx = web.repo.changectx(n)
384 yield {'parity': parity.next(),
384 yield {'parity': parity.next(),
385 'branch': t,
385 'branch': t,
386 'node': hex(n),
386 'node': hex(n),
387 'date': ctx.date()}
387 'date': ctx.date()}
388
388
389 def changelist(**map):
389 def changelist(**map):
390 parity = paritygen(web.stripecount, offset=start-end)
390 parity = paritygen(web.stripecount, offset=start-end)
391 l = [] # build a list in forward order for efficiency
391 l = [] # build a list in forward order for efficiency
392 for i in xrange(start, end):
392 for i in xrange(start, end):
393 ctx = web.repo.changectx(i)
393 ctx = web.repo.changectx(i)
394 n = ctx.node()
394 n = ctx.node()
395 hn = hex(n)
395 hn = hex(n)
396
396
397 l.insert(0, tmpl(
397 l.insert(0, tmpl(
398 'shortlogentry',
398 'shortlogentry',
399 parity=parity.next(),
399 parity=parity.next(),
400 author=ctx.user(),
400 author=ctx.user(),
401 desc=ctx.description(),
401 desc=ctx.description(),
402 date=ctx.date(),
402 date=ctx.date(),
403 rev=i,
403 rev=i,
404 node=hn,
404 node=hn,
405 tags=webutil.nodetagsdict(web.repo, n),
405 tags=webutil.nodetagsdict(web.repo, n),
406 inbranch=webutil.nodeinbranch(web.repo, ctx),
406 inbranch=webutil.nodeinbranch(web.repo, ctx),
407 branches=webutil.nodebranchdict(web.repo, ctx)))
407 branches=webutil.nodebranchdict(web.repo, ctx)))
408
408
409 yield l
409 yield l
410
410
411 cl = web.repo.changelog
411 cl = web.repo.changelog
412 count = cl.count()
412 count = cl.count()
413 start = max(0, count - web.maxchanges)
413 start = max(0, count - web.maxchanges)
414 end = min(count, start + web.maxchanges)
414 end = min(count, start + web.maxchanges)
415
415
416 return tmpl("summary",
416 return tmpl("summary",
417 desc=web.config("web", "description", "unknown"),
417 desc=web.config("web", "description", "unknown"),
418 owner=get_contact(web.config) or "unknown",
418 owner=get_contact(web.config) or "unknown",
419 lastchange=cl.read(cl.tip())[2],
419 lastchange=cl.read(cl.tip())[2],
420 tags=tagentries,
420 tags=tagentries,
421 branches=branches,
421 branches=branches,
422 shortlog=changelist,
422 shortlog=changelist,
423 node=hex(cl.tip()),
423 node=hex(cl.tip()),
424 archives=web.archivelist("tip"))
424 archives=web.archivelist("tip"))
425
425
426 def filediff(web, req, tmpl):
426 def filediff(web, req, tmpl):
427 fctx = webutil.filectx(web.repo, req)
427 fctx = webutil.filectx(web.repo, req)
428 n = fctx.node()
428 n = fctx.node()
429 path = fctx.path()
429 path = fctx.path()
430 parents = fctx.parents()
430 parents = fctx.parents()
431 p1 = parents and parents[0].node() or nullid
431 p1 = parents and parents[0].node() or nullid
432
432
433 diffs = web.diff(tmpl, p1, n, [path])
433 diffs = web.diff(tmpl, p1, n, [path])
434 return tmpl("filediff",
434 return tmpl("filediff",
435 file=path,
435 file=path,
436 node=hex(n),
436 node=hex(n),
437 rev=fctx.rev(),
437 rev=fctx.rev(),
438 date=fctx.date(),
439 desc=fctx.description(),
440 author=fctx.user(),
441 rename=self.renamelink(fctx),
438 branch=webutil.nodebranchnodefault(fctx),
442 branch=webutil.nodebranchnodefault(fctx),
439 parent=webutil.siblings(parents),
443 parent=webutil.siblings(parents),
440 child=webutil.siblings(fctx.children()),
444 child=webutil.siblings(fctx.children()),
441 diff=diffs)
445 diff=diffs)
442
446
443 diff = filediff
447 diff = filediff
444
448
445 def annotate(web, req, tmpl):
449 def annotate(web, req, tmpl):
446 fctx = webutil.filectx(web.repo, req)
450 fctx = webutil.filectx(web.repo, req)
447 f = fctx.path()
451 f = fctx.path()
448 n = fctx.filenode()
452 n = fctx.filenode()
449 fl = fctx.filelog()
453 fl = fctx.filelog()
450 parity = paritygen(web.stripecount)
454 parity = paritygen(web.stripecount)
451
455
452 def annotate(**map):
456 def annotate(**map):
453 last = None
457 last = None
454 if binary(fctx.data()):
458 if binary(fctx.data()):
455 mt = (mimetypes.guess_type(fctx.path())[0]
459 mt = (mimetypes.guess_type(fctx.path())[0]
456 or 'application/octet-stream')
460 or 'application/octet-stream')
457 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
461 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
458 '(binary:%s)' % mt)])
462 '(binary:%s)' % mt)])
459 else:
463 else:
460 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
464 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
461 for lineno, ((f, targetline), l) in lines:
465 for lineno, ((f, targetline), l) in lines:
462 fnode = f.filenode()
466 fnode = f.filenode()
463 name = web.repo.ui.shortuser(f.user())
467 name = web.repo.ui.shortuser(f.user())
464
468
465 if last != fnode:
469 if last != fnode:
466 last = fnode
470 last = fnode
467
471
468 yield {"parity": parity.next(),
472 yield {"parity": parity.next(),
469 "node": hex(f.node()),
473 "node": hex(f.node()),
470 "rev": f.rev(),
474 "rev": f.rev(),
471 "author": name,
475 "author": name,
472 "file": f.path(),
476 "file": f.path(),
473 "targetline": targetline,
477 "targetline": targetline,
474 "line": l,
478 "line": l,
475 "lineid": "l%d" % (lineno + 1),
479 "lineid": "l%d" % (lineno + 1),
476 "linenumber": "% 6d" % (lineno + 1)}
480 "linenumber": "% 6d" % (lineno + 1)}
477
481
478 return tmpl("fileannotate",
482 return tmpl("fileannotate",
479 file=f,
483 file=f,
480 annotate=annotate,
484 annotate=annotate,
481 path=webutil.up(f),
485 path=webutil.up(f),
482 rev=fctx.rev(),
486 rev=fctx.rev(),
483 node=hex(fctx.node()),
487 node=hex(fctx.node()),
484 author=fctx.user(),
488 author=fctx.user(),
485 date=fctx.date(),
489 date=fctx.date(),
486 desc=fctx.description(),
490 desc=fctx.description(),
487 rename=webutil.renamelink(fl, n),
491 rename=webutil.renamelink(fctx),
488 branch=webutil.nodebranchnodefault(fctx),
492 branch=webutil.nodebranchnodefault(fctx),
489 parent=webutil.siblings(fctx.parents()),
493 parent=webutil.siblings(fctx.parents()),
490 child=webutil.siblings(fctx.children()),
494 child=webutil.siblings(fctx.children()),
491 permissions=fctx.manifest().flags(f))
495 permissions=fctx.manifest().flags(f))
492
496
493 def filelog(web, req, tmpl):
497 def filelog(web, req, tmpl):
494 fctx = webutil.filectx(web.repo, req)
498 fctx = webutil.filectx(web.repo, req)
495 f = fctx.path()
499 f = fctx.path()
496 fl = fctx.filelog()
500 fl = fctx.filelog()
497 count = fl.count()
501 count = fl.count()
498 pagelen = web.maxshortchanges
502 pagelen = web.maxshortchanges
499 pos = fctx.filerev()
503 pos = fctx.filerev()
500 start = max(0, pos - pagelen + 1)
504 start = max(0, pos - pagelen + 1)
501 end = min(count, start + pagelen)
505 end = min(count, start + pagelen)
502 pos = end - 1
506 pos = end - 1
503 parity = paritygen(web.stripecount, offset=start-end)
507 parity = paritygen(web.stripecount, offset=start-end)
504
508
505 def entries(limit=0, **map):
509 def entries(limit=0, **map):
506 l = []
510 l = []
507
511
508 for i in xrange(start, end):
512 for i in xrange(start, end):
509 ctx = fctx.filectx(i)
513 ctx = fctx.filectx(i)
510 n = fl.node(i)
514 n = fl.node(i)
511
515
512 l.insert(0, {"parity": parity.next(),
516 l.insert(0, {"parity": parity.next(),
513 "filerev": i,
517 "filerev": i,
514 "file": f,
518 "file": f,
515 "node": hex(ctx.node()),
519 "node": hex(ctx.node()),
516 "author": ctx.user(),
520 "author": ctx.user(),
517 "date": ctx.date(),
521 "date": ctx.date(),
518 "rename": webutil.renamelink(fl, n),
522 "rename": webutil.renamelink(fctx),
519 "parent": webutil.siblings(fctx.parents()),
523 "parent": webutil.siblings(fctx.parents()),
520 "child": webutil.siblings(fctx.children()),
524 "child": webutil.siblings(fctx.children()),
521 "desc": ctx.description()})
525 "desc": ctx.description()})
522
526
523 if limit > 0:
527 if limit > 0:
524 l = l[:limit]
528 l = l[:limit]
525
529
526 for e in l:
530 for e in l:
527 yield e
531 yield e
528
532
529 nodefunc = lambda x: fctx.filectx(fileid=x)
533 nodefunc = lambda x: fctx.filectx(fileid=x)
530 nav = webutil.revnavgen(pos, pagelen, count, nodefunc)
534 nav = webutil.revnavgen(pos, pagelen, count, nodefunc)
531 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
535 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
532 entries=lambda **x: entries(limit=0, **x),
536 entries=lambda **x: entries(limit=0, **x),
533 latestentry=lambda **x: entries(limit=1, **x))
537 latestentry=lambda **x: entries(limit=1, **x))
534
538
535
539
536 def archive(web, req, tmpl):
540 def archive(web, req, tmpl):
537 type_ = req.form['type'][0]
541 type_ = req.form['type'][0]
538 allowed = web.configlist("web", "allow_archive")
542 allowed = web.configlist("web", "allow_archive")
539 key = req.form['node'][0]
543 key = req.form['node'][0]
540
544
541 if not (type_ in web.archives and (type_ in allowed or
545 if not (type_ in web.archives and (type_ in allowed or
542 web.configbool("web", "allow" + type_, False))):
546 web.configbool("web", "allow" + type_, False))):
543 msg = 'Unsupported archive type: %s' % type_
547 msg = 'Unsupported archive type: %s' % type_
544 raise ErrorResponse(HTTP_NOT_FOUND, msg)
548 raise ErrorResponse(HTTP_NOT_FOUND, msg)
545
549
546 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
550 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
547 cnode = web.repo.lookup(key)
551 cnode = web.repo.lookup(key)
548 arch_version = key
552 arch_version = key
549 if cnode == key or key == 'tip':
553 if cnode == key or key == 'tip':
550 arch_version = short(cnode)
554 arch_version = short(cnode)
551 name = "%s-%s" % (reponame, arch_version)
555 name = "%s-%s" % (reponame, arch_version)
552 mimetype, artype, extension, encoding = web.archive_specs[type_]
556 mimetype, artype, extension, encoding = web.archive_specs[type_]
553 headers = [
557 headers = [
554 ('Content-Type', mimetype),
558 ('Content-Type', mimetype),
555 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
559 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
556 ]
560 ]
557 if encoding:
561 if encoding:
558 headers.append(('Content-Encoding', encoding))
562 headers.append(('Content-Encoding', encoding))
559 req.header(headers)
563 req.header(headers)
560 req.respond(HTTP_OK)
564 req.respond(HTTP_OK)
561 archival.archive(web.repo, req, cnode, artype, prefix=name)
565 archival.archive(web.repo, req, cnode, artype, prefix=name)
562 return []
566 return []
563
567
564
568
565 def static(web, req, tmpl):
569 def static(web, req, tmpl):
566 fname = req.form['file'][0]
570 fname = req.form['file'][0]
567 # a repo owner may set web.static in .hg/hgrc to get any file
571 # a repo owner may set web.static in .hg/hgrc to get any file
568 # readable by the user running the CGI script
572 # readable by the user running the CGI script
569 static = web.config("web", "static",
573 static = web.config("web", "static",
570 os.path.join(web.templatepath, "static"),
574 os.path.join(web.templatepath, "static"),
571 untrusted=False)
575 untrusted=False)
572 return [staticfile(static, fname, req)]
576 return [staticfile(static, fname, req)]
@@ -1,143 +1,143 b''
1 # hgweb/webutil.py - utility library for the web interface.
1 # hgweb/webutil.py - utility library for the web interface.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os
9 import os
10 from mercurial.node import hex, nullid
10 from mercurial.node import hex, nullid
11 from mercurial.repo import RepoError
11 from mercurial.repo import RepoError
12 from mercurial import util
12 from mercurial import util
13
13
14 def up(p):
14 def up(p):
15 if p[0] != "/":
15 if p[0] != "/":
16 p = "/" + p
16 p = "/" + p
17 if p[-1] == "/":
17 if p[-1] == "/":
18 p = p[:-1]
18 p = p[:-1]
19 up = os.path.dirname(p)
19 up = os.path.dirname(p)
20 if up == "/":
20 if up == "/":
21 return "/"
21 return "/"
22 return up + "/"
22 return up + "/"
23
23
24 def revnavgen(pos, pagelen, limit, nodefunc):
24 def revnavgen(pos, pagelen, limit, nodefunc):
25 def seq(factor, limit=None):
25 def seq(factor, limit=None):
26 if limit:
26 if limit:
27 yield limit
27 yield limit
28 if limit >= 20 and limit <= 40:
28 if limit >= 20 and limit <= 40:
29 yield 50
29 yield 50
30 else:
30 else:
31 yield 1 * factor
31 yield 1 * factor
32 yield 3 * factor
32 yield 3 * factor
33 for f in seq(factor * 10):
33 for f in seq(factor * 10):
34 yield f
34 yield f
35
35
36 def nav(**map):
36 def nav(**map):
37 l = []
37 l = []
38 last = 0
38 last = 0
39 for f in seq(1, pagelen):
39 for f in seq(1, pagelen):
40 if f < pagelen or f <= last:
40 if f < pagelen or f <= last:
41 continue
41 continue
42 if f > limit:
42 if f > limit:
43 break
43 break
44 last = f
44 last = f
45 if pos + f < limit:
45 if pos + f < limit:
46 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
46 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
47 if pos - f >= 0:
47 if pos - f >= 0:
48 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
48 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
49
49
50 try:
50 try:
51 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
51 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
52
52
53 for label, node in l:
53 for label, node in l:
54 yield {"label": label, "node": node}
54 yield {"label": label, "node": node}
55
55
56 yield {"label": "tip", "node": "tip"}
56 yield {"label": "tip", "node": "tip"}
57 except RepoError:
57 except RepoError:
58 pass
58 pass
59
59
60 return nav
60 return nav
61
61
62 def siblings(siblings=[], hiderev=None, **args):
62 def siblings(siblings=[], hiderev=None, **args):
63 siblings = [s for s in siblings if s.node() != nullid]
63 siblings = [s for s in siblings if s.node() != nullid]
64 if len(siblings) == 1 and siblings[0].rev() == hiderev:
64 if len(siblings) == 1 and siblings[0].rev() == hiderev:
65 return
65 return
66 for s in siblings:
66 for s in siblings:
67 d = {'node': hex(s.node()), 'rev': s.rev()}
67 d = {'node': hex(s.node()), 'rev': s.rev()}
68 if hasattr(s, 'path'):
68 if hasattr(s, 'path'):
69 d['file'] = s.path()
69 d['file'] = s.path()
70 d.update(args)
70 d.update(args)
71 yield d
71 yield d
72
72
73 def renamelink(fl, node):
73 def renamelink(fctx):
74 r = fl.renamed(node)
74 r = fctx.renamed(node)
75 if r:
75 if r:
76 return [dict(file=r[0], node=hex(r[1]))]
76 return [dict(file=r[0], node=hex(r[1]))]
77 return []
77 return []
78
78
79 def nodetagsdict(repo, node):
79 def nodetagsdict(repo, node):
80 return [{"name": i} for i in repo.nodetags(node)]
80 return [{"name": i} for i in repo.nodetags(node)]
81
81
82 def nodebranchdict(repo, ctx):
82 def nodebranchdict(repo, ctx):
83 branches = []
83 branches = []
84 branch = ctx.branch()
84 branch = ctx.branch()
85 # If this is an empty repo, ctx.node() == nullid,
85 # If this is an empty repo, ctx.node() == nullid,
86 # ctx.branch() == 'default', but branchtags() is
86 # ctx.branch() == 'default', but branchtags() is
87 # an empty dict. Using dict.get avoids a traceback.
87 # an empty dict. Using dict.get avoids a traceback.
88 if repo.branchtags().get(branch) == ctx.node():
88 if repo.branchtags().get(branch) == ctx.node():
89 branches.append({"name": branch})
89 branches.append({"name": branch})
90 return branches
90 return branches
91
91
92 def nodeinbranch(repo, ctx):
92 def nodeinbranch(repo, ctx):
93 branches = []
93 branches = []
94 branch = ctx.branch()
94 branch = ctx.branch()
95 if branch != 'default' and repo.branchtags().get(branch) != ctx.node():
95 if branch != 'default' and repo.branchtags().get(branch) != ctx.node():
96 branches.append({"name": branch})
96 branches.append({"name": branch})
97 return branches
97 return branches
98
98
99 def nodebranchnodefault(ctx):
99 def nodebranchnodefault(ctx):
100 branches = []
100 branches = []
101 branch = ctx.branch()
101 branch = ctx.branch()
102 if branch != 'default':
102 if branch != 'default':
103 branches.append({"name": branch})
103 branches.append({"name": branch})
104 return branches
104 return branches
105
105
106 def showtag(repo, tmpl, t1, node=nullid, **args):
106 def showtag(repo, tmpl, t1, node=nullid, **args):
107 for t in repo.nodetags(node):
107 for t in repo.nodetags(node):
108 yield tmpl(t1, tag=t, **args)
108 yield tmpl(t1, tag=t, **args)
109
109
110 def cleanpath(repo, path):
110 def cleanpath(repo, path):
111 path = path.lstrip('/')
111 path = path.lstrip('/')
112 return util.canonpath(repo.root, '', path)
112 return util.canonpath(repo.root, '', path)
113
113
114 def changectx(repo, req):
114 def changectx(repo, req):
115 if 'node' in req.form:
115 if 'node' in req.form:
116 changeid = req.form['node'][0]
116 changeid = req.form['node'][0]
117 elif 'manifest' in req.form:
117 elif 'manifest' in req.form:
118 changeid = req.form['manifest'][0]
118 changeid = req.form['manifest'][0]
119 else:
119 else:
120 changeid = repo.changelog.count() - 1
120 changeid = repo.changelog.count() - 1
121
121
122 try:
122 try:
123 ctx = repo.changectx(changeid)
123 ctx = repo.changectx(changeid)
124 except RepoError:
124 except RepoError:
125 man = repo.manifest
125 man = repo.manifest
126 mn = man.lookup(changeid)
126 mn = man.lookup(changeid)
127 ctx = repo.changectx(man.linkrev(mn))
127 ctx = repo.changectx(man.linkrev(mn))
128
128
129 return ctx
129 return ctx
130
130
131 def filectx(repo, req):
131 def filectx(repo, req):
132 path = cleanpath(repo, req.form['file'][0])
132 path = cleanpath(repo, req.form['file'][0])
133 if 'node' in req.form:
133 if 'node' in req.form:
134 changeid = req.form['node'][0]
134 changeid = req.form['node'][0]
135 else:
135 else:
136 changeid = req.form['filenode'][0]
136 changeid = req.form['filenode'][0]
137 try:
137 try:
138 ctx = repo.changectx(changeid)
138 ctx = repo.changectx(changeid)
139 fctx = ctx.filectx(path)
139 fctx = ctx.filectx(path)
140 except RepoError:
140 except RepoError:
141 fctx = repo.filectx(path, fileid=changeid)
141 fctx = repo.filectx(path, fileid=changeid)
142
142
143 return fctx
143 return fctx
@@ -1,150 +1,150 b''
1 # templater.py - template expansion for output
1 # templater.py - template expansion for output
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 import re, sys, os
9 import re, sys, os
10 from mercurial import util
10 from mercurial import util
11
11
12 def parsestring(s, quoted=True):
12 def parsestring(s, quoted=True):
13 '''parse a string using simple c-like syntax.
13 '''parse a string using simple c-like syntax.
14 string must be in quotes if quoted is True.'''
14 string must be in quotes if quoted is True.'''
15 if quoted:
15 if quoted:
16 if len(s) < 2 or s[0] != s[-1]:
16 if len(s) < 2 or s[0] != s[-1]:
17 raise SyntaxError(_('unmatched quotes'))
17 raise SyntaxError(_('unmatched quotes'))
18 return s[1:-1].decode('string_escape')
18 return s[1:-1].decode('string_escape')
19
19
20 return s.decode('string_escape')
20 return s.decode('string_escape')
21
21
22 class templater(object):
22 class templater(object):
23 '''template expansion engine.
23 '''template expansion engine.
24
24
25 template expansion works like this. a map file contains key=value
25 template expansion works like this. a map file contains key=value
26 pairs. if value is quoted, it is treated as string. otherwise, it
26 pairs. if value is quoted, it is treated as string. otherwise, it
27 is treated as name of template file.
27 is treated as name of template file.
28
28
29 templater is asked to expand a key in map. it looks up key, and
29 templater is asked to expand a key in map. it looks up key, and
30 looks for strings like this: {foo}. it expands {foo} by looking up
30 looks for strings like this: {foo}. it expands {foo} by looking up
31 foo in map, and substituting it. expansion is recursive: it stops
31 foo in map, and substituting it. expansion is recursive: it stops
32 when there is no more {foo} to replace.
32 when there is no more {foo} to replace.
33
33
34 expansion also allows formatting and filtering.
34 expansion also allows formatting and filtering.
35
35
36 format uses key to expand each item in list. syntax is
36 format uses key to expand each item in list. syntax is
37 {key%format}.
37 {key%format}.
38
38
39 filter uses function to transform value. syntax is
39 filter uses function to transform value. syntax is
40 {key|filter1|filter2|...}.'''
40 {key|filter1|filter2|...}.'''
41
41
42 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))"
42 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))"
43 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]")
43 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]")
44
44
45 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
45 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
46 '''set up template engine.
46 '''set up template engine.
47 mapfile is name of file to read map definitions from.
47 mapfile is name of file to read map definitions from.
48 filters is dict of functions. each transforms a value into another.
48 filters is dict of functions. each transforms a value into another.
49 defaults is dict of default map definitions.'''
49 defaults is dict of default map definitions.'''
50 self.mapfile = mapfile or 'template'
50 self.mapfile = mapfile or 'template'
51 self.cache = cache.copy()
51 self.cache = cache.copy()
52 self.map = {}
52 self.map = {}
53 self.base = (mapfile and os.path.dirname(mapfile)) or ''
53 self.base = (mapfile and os.path.dirname(mapfile)) or ''
54 self.filters = filters
54 self.filters = filters
55 self.defaults = defaults
55 self.defaults = defaults
56
56
57 if not mapfile:
57 if not mapfile:
58 return
58 return
59 if not os.path.exists(mapfile):
59 if not os.path.exists(mapfile):
60 raise util.Abort(_('style not found: %s') % mapfile)
60 raise util.Abort(_('style not found: %s') % mapfile)
61
61
62 i = 0
62 i = 0
63 for l in file(mapfile):
63 for l in file(mapfile):
64 l = l.strip()
64 l = l.strip()
65 i += 1
65 i += 1
66 if not l or l[0] in '#;': continue
66 if not l or l[0] in '#;': continue
67 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
67 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
68 if m:
68 if m:
69 key, val = m.groups()
69 key, val = m.groups()
70 if val[0] in "'\"":
70 if val[0] in "'\"":
71 try:
71 try:
72 self.cache[key] = parsestring(val)
72 self.cache[key] = parsestring(val)
73 except SyntaxError, inst:
73 except SyntaxError, inst:
74 raise SyntaxError('%s:%s: %s' %
74 raise SyntaxError('%s:%s: %s' %
75 (mapfile, i, inst.args[0]))
75 (mapfile, i, inst.args[0]))
76 else:
76 else:
77 self.map[key] = os.path.join(self.base, val)
77 self.map[key] = os.path.join(self.base, val)
78 else:
78 else:
79 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
79 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
80
80
81 def __contains__(self, key):
81 def __contains__(self, key):
82 return key in self.cache or key in self.map
82 return key in self.cache or key in self.map
83
83
84 def __call__(self, t, **map):
84 def __call__(self, t, **map):
85 '''perform expansion.
85 '''perform expansion.
86 t is name of map element to expand.
86 t is name of map element to expand.
87 map is added elements to use during expansion.'''
87 map is added elements to use during expansion.'''
88 if not t in self.cache:
88 if not t in self.cache:
89 try:
89 try:
90 self.cache[t] = file(self.map[t]).read()
90 self.cache[t] = file(self.map[t]).read()
91 except IOError, inst:
91 except IOError, inst:
92 raise IOError(inst.args[0], _('template file %s: %s') %
92 raise IOError(inst.args[0], _('template file %s: %s') %
93 (self.map[t], inst.args[1]))
93 (self.map[t], inst.args[1]))
94 tmpl = self.cache[t]
94 tmpl = self.cache[t]
95
95
96 while tmpl:
96 while tmpl:
97 m = self.template_re.search(tmpl)
97 m = self.template_re.search(tmpl)
98 if not m:
98 if not m:
99 yield tmpl
99 yield tmpl
100 break
100 break
101
101
102 start, end = m.span(0)
102 start, end = m.span(0)
103 key, format, fl = m.groups()
103 key, format, fl = m.groups()
104
104
105 if start:
105 if start:
106 yield tmpl[:start]
106 yield tmpl[:start]
107 tmpl = tmpl[end:]
107 tmpl = tmpl[end:]
108
108
109 if key in map:
109 if key in map:
110 v = map[key]
110 v = map[key]
111 else:
111 else:
112 v = self.defaults.get(key, "")
112 v = self.defaults.get(key, "")
113 if callable(v):
113 if callable(v):
114 v = v(**map)
114 v = v(**map)
115 if format:
115 if format:
116 if not hasattr(v, '__iter__'):
116 if not hasattr(v, '__iter__'):
117 raise SyntaxError(_("Error expanding '%s%s'")
117 raise SyntaxError(_("Error expanding '%s%%%s'")
118 % (key, format))
118 % (key, format))
119 lm = map.copy()
119 lm = map.copy()
120 for i in v:
120 for i in v:
121 lm.update(i)
121 lm.update(i)
122 yield self(format, **lm)
122 yield self(format, **lm)
123 else:
123 else:
124 if fl:
124 if fl:
125 for f in fl.split("|")[1:]:
125 for f in fl.split("|")[1:]:
126 v = self.filters[f](v)
126 v = self.filters[f](v)
127 yield v
127 yield v
128
128
129 def templatepath(name=None):
129 def templatepath(name=None):
130 '''return location of template file or directory (if no name).
130 '''return location of template file or directory (if no name).
131 returns None if not found.'''
131 returns None if not found.'''
132
132
133 # executable version (py2exe) doesn't support __file__
133 # executable version (py2exe) doesn't support __file__
134 if hasattr(sys, 'frozen'):
134 if hasattr(sys, 'frozen'):
135 module = sys.executable
135 module = sys.executable
136 else:
136 else:
137 module = __file__
137 module = __file__
138 for f in 'templates', '../templates':
138 for f in 'templates', '../templates':
139 fl = f.split('/')
139 fl = f.split('/')
140 if name: fl.append(name)
140 if name: fl.append(name)
141 p = os.path.join(os.path.dirname(module), *fl)
141 p = os.path.join(os.path.dirname(module), *fl)
142 if (name and os.path.exists(p)) or os.path.isdir(p):
142 if (name and os.path.exists(p)) or os.path.isdir(p):
143 return os.path.normpath(p)
143 return os.path.normpath(p)
144
144
145 def stringify(thing):
145 def stringify(thing):
146 '''turn nested template iterator into string.'''
146 '''turn nested template iterator into string.'''
147 if hasattr(thing, '__iter__'):
147 if hasattr(thing, '__iter__'):
148 return "".join([stringify(t) for t in thing if t is not None])
148 return "".join([stringify(t) for t in thing if t is not None])
149 return str(thing)
149 return str(thing)
150
150
General Comments 0
You need to be logged in to leave comments. Login now