##// END OF EJS Templates
move hgweb.templatepath into templater
Vadim Gelfer -
r1897:58b6784c default
parent child Browse files
Show More
@@ -1,1083 +1,1077 b''
1 # hgweb.py - web interface to a mercurial repository
1 # hgweb.py - web interface to a mercurial repository
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 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005 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, cgi, sys
9 import os, cgi, sys
10 import mimetypes
10 import mimetypes
11 from demandload import demandload
11 from demandload import demandload
12 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
12 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
13 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
13 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
14 demandload(globals(), "mimetypes templater")
14 demandload(globals(), "mimetypes templater")
15 from node import *
15 from node import *
16 from i18n import gettext as _
16 from i18n import gettext as _
17
17
18 def templatepath():
19 for f in "templates", "../templates":
20 p = os.path.join(os.path.dirname(__file__), f)
21 if os.path.isdir(p):
22 return os.path.normpath(p)
23
24 def up(p):
18 def up(p):
25 if p[0] != "/":
19 if p[0] != "/":
26 p = "/" + p
20 p = "/" + p
27 if p[-1] == "/":
21 if p[-1] == "/":
28 p = p[:-1]
22 p = p[:-1]
29 up = os.path.dirname(p)
23 up = os.path.dirname(p)
30 if up == "/":
24 if up == "/":
31 return "/"
25 return "/"
32 return up + "/"
26 return up + "/"
33
27
34 def get_mtime(repo_path):
28 def get_mtime(repo_path):
35 hg_path = os.path.join(repo_path, ".hg")
29 hg_path = os.path.join(repo_path, ".hg")
36 cl_path = os.path.join(hg_path, "00changelog.i")
30 cl_path = os.path.join(hg_path, "00changelog.i")
37 if os.path.exists(os.path.join(cl_path)):
31 if os.path.exists(os.path.join(cl_path)):
38 return os.stat(cl_path).st_mtime
32 return os.stat(cl_path).st_mtime
39 else:
33 else:
40 return os.stat(hg_path).st_mtime
34 return os.stat(hg_path).st_mtime
41
35
42 def staticfile(directory, fname):
36 def staticfile(directory, fname):
43 fname = os.path.realpath(os.path.join(directory, fname))
37 fname = os.path.realpath(os.path.join(directory, fname))
44
38
45 try:
39 try:
46 # the static dir should be a substring in the real
40 # the static dir should be a substring in the real
47 # file path, if it is not, we have something strange
41 # file path, if it is not, we have something strange
48 # going on => security breach attempt?
42 # going on => security breach attempt?
49 #
43 #
50 # This will either:
44 # This will either:
51 # 1) find the `static' path at index 0 = success
45 # 1) find the `static' path at index 0 = success
52 # 2) find the `static' path at other index = error
46 # 2) find the `static' path at other index = error
53 # 3) not find the `static' path = ValueError generated
47 # 3) not find the `static' path = ValueError generated
54 if fname.index(directory) != 0:
48 if fname.index(directory) != 0:
55 # generate ValueError manually
49 # generate ValueError manually
56 raise ValueError()
50 raise ValueError()
57
51
58 os.stat(fname)
52 os.stat(fname)
59
53
60 ct = mimetypes.guess_type(fname)[0] or "text/plain"
54 ct = mimetypes.guess_type(fname)[0] or "text/plain"
61 return "Content-type: %s\n\n%s" % (ct, file(fname).read())
55 return "Content-type: %s\n\n%s" % (ct, file(fname).read())
62 except ValueError:
56 except ValueError:
63 # security breach attempt
57 # security breach attempt
64 return ""
58 return ""
65 except OSError, e:
59 except OSError, e:
66 if e.errno == errno.ENOENT:
60 if e.errno == errno.ENOENT:
67 return ""
61 return ""
68
62
69 class hgrequest(object):
63 class hgrequest(object):
70 def __init__(self, inp=None, out=None, env=None):
64 def __init__(self, inp=None, out=None, env=None):
71 self.inp = inp or sys.stdin
65 self.inp = inp or sys.stdin
72 self.out = out or sys.stdout
66 self.out = out or sys.stdout
73 self.env = env or os.environ
67 self.env = env or os.environ
74 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
68 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
75
69
76 def write(self, *things):
70 def write(self, *things):
77 for thing in things:
71 for thing in things:
78 if hasattr(thing, "__iter__"):
72 if hasattr(thing, "__iter__"):
79 for part in thing:
73 for part in thing:
80 self.write(part)
74 self.write(part)
81 else:
75 else:
82 try:
76 try:
83 self.out.write(str(thing))
77 self.out.write(str(thing))
84 except socket.error, inst:
78 except socket.error, inst:
85 if inst[0] != errno.ECONNRESET:
79 if inst[0] != errno.ECONNRESET:
86 raise
80 raise
87
81
88 def header(self, headers=[('Content-type','text/html')]):
82 def header(self, headers=[('Content-type','text/html')]):
89 for header in headers:
83 for header in headers:
90 self.out.write("%s: %s\r\n" % header)
84 self.out.write("%s: %s\r\n" % header)
91 self.out.write("\r\n")
85 self.out.write("\r\n")
92
86
93 def httphdr(self, type, file="", size=0):
87 def httphdr(self, type, file="", size=0):
94
88
95 headers = [('Content-type', type)]
89 headers = [('Content-type', type)]
96 if file:
90 if file:
97 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
91 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
98 if size > 0:
92 if size > 0:
99 headers.append(('Content-length', str(size)))
93 headers.append(('Content-length', str(size)))
100 self.header(headers)
94 self.header(headers)
101
95
102 class hgweb(object):
96 class hgweb(object):
103 def __init__(self, repo, name=None):
97 def __init__(self, repo, name=None):
104 if type(repo) == type(""):
98 if type(repo) == type(""):
105 self.repo = hg.repository(ui.ui(), repo)
99 self.repo = hg.repository(ui.ui(), repo)
106 else:
100 else:
107 self.repo = repo
101 self.repo = repo
108
102
109 self.mtime = -1
103 self.mtime = -1
110 self.reponame = name
104 self.reponame = name
111 self.archives = 'zip', 'gz', 'bz2'
105 self.archives = 'zip', 'gz', 'bz2'
112
106
113 def refresh(self):
107 def refresh(self):
114 mtime = get_mtime(self.repo.root)
108 mtime = get_mtime(self.repo.root)
115 if mtime != self.mtime:
109 if mtime != self.mtime:
116 self.mtime = mtime
110 self.mtime = mtime
117 self.repo = hg.repository(self.repo.ui, self.repo.root)
111 self.repo = hg.repository(self.repo.ui, self.repo.root)
118 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
112 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
119 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
113 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
120 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
114 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
121
115
122 def archivelist(self, nodeid):
116 def archivelist(self, nodeid):
123 for i in self.archives:
117 for i in self.archives:
124 if self.repo.ui.configbool("web", "allow" + i, False):
118 if self.repo.ui.configbool("web", "allow" + i, False):
125 yield {"type" : i, "node" : nodeid}
119 yield {"type" : i, "node" : nodeid}
126
120
127 def listfiles(self, files, mf):
121 def listfiles(self, files, mf):
128 for f in files[:self.maxfiles]:
122 for f in files[:self.maxfiles]:
129 yield self.t("filenodelink", node=hex(mf[f]), file=f)
123 yield self.t("filenodelink", node=hex(mf[f]), file=f)
130 if len(files) > self.maxfiles:
124 if len(files) > self.maxfiles:
131 yield self.t("fileellipses")
125 yield self.t("fileellipses")
132
126
133 def listfilediffs(self, files, changeset):
127 def listfilediffs(self, files, changeset):
134 for f in files[:self.maxfiles]:
128 for f in files[:self.maxfiles]:
135 yield self.t("filedifflink", node=hex(changeset), file=f)
129 yield self.t("filedifflink", node=hex(changeset), file=f)
136 if len(files) > self.maxfiles:
130 if len(files) > self.maxfiles:
137 yield self.t("fileellipses")
131 yield self.t("fileellipses")
138
132
139 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
133 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
140 if not rev:
134 if not rev:
141 rev = lambda x: ""
135 rev = lambda x: ""
142 siblings = [s for s in siblings if s != nullid]
136 siblings = [s for s in siblings if s != nullid]
143 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
137 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
144 return
138 return
145 for s in siblings:
139 for s in siblings:
146 yield dict(node=hex(s), rev=rev(s), **args)
140 yield dict(node=hex(s), rev=rev(s), **args)
147
141
148 def renamelink(self, fl, node):
142 def renamelink(self, fl, node):
149 r = fl.renamed(node)
143 r = fl.renamed(node)
150 if r:
144 if r:
151 return [dict(file=r[0], node=hex(r[1]))]
145 return [dict(file=r[0], node=hex(r[1]))]
152 return []
146 return []
153
147
154 def showtag(self, t1, node=nullid, **args):
148 def showtag(self, t1, node=nullid, **args):
155 for t in self.repo.nodetags(node):
149 for t in self.repo.nodetags(node):
156 yield self.t(t1, tag=t, **args)
150 yield self.t(t1, tag=t, **args)
157
151
158 def diff(self, node1, node2, files):
152 def diff(self, node1, node2, files):
159 def filterfiles(filters, files):
153 def filterfiles(filters, files):
160 l = [x for x in files if x in filters]
154 l = [x for x in files if x in filters]
161
155
162 for t in filters:
156 for t in filters:
163 if t and t[-1] != os.sep:
157 if t and t[-1] != os.sep:
164 t += os.sep
158 t += os.sep
165 l += [x for x in files if x.startswith(t)]
159 l += [x for x in files if x.startswith(t)]
166 return l
160 return l
167
161
168 parity = [0]
162 parity = [0]
169 def diffblock(diff, f, fn):
163 def diffblock(diff, f, fn):
170 yield self.t("diffblock",
164 yield self.t("diffblock",
171 lines=prettyprintlines(diff),
165 lines=prettyprintlines(diff),
172 parity=parity[0],
166 parity=parity[0],
173 file=f,
167 file=f,
174 filenode=hex(fn or nullid))
168 filenode=hex(fn or nullid))
175 parity[0] = 1 - parity[0]
169 parity[0] = 1 - parity[0]
176
170
177 def prettyprintlines(diff):
171 def prettyprintlines(diff):
178 for l in diff.splitlines(1):
172 for l in diff.splitlines(1):
179 if l.startswith('+'):
173 if l.startswith('+'):
180 yield self.t("difflineplus", line=l)
174 yield self.t("difflineplus", line=l)
181 elif l.startswith('-'):
175 elif l.startswith('-'):
182 yield self.t("difflineminus", line=l)
176 yield self.t("difflineminus", line=l)
183 elif l.startswith('@'):
177 elif l.startswith('@'):
184 yield self.t("difflineat", line=l)
178 yield self.t("difflineat", line=l)
185 else:
179 else:
186 yield self.t("diffline", line=l)
180 yield self.t("diffline", line=l)
187
181
188 r = self.repo
182 r = self.repo
189 cl = r.changelog
183 cl = r.changelog
190 mf = r.manifest
184 mf = r.manifest
191 change1 = cl.read(node1)
185 change1 = cl.read(node1)
192 change2 = cl.read(node2)
186 change2 = cl.read(node2)
193 mmap1 = mf.read(change1[0])
187 mmap1 = mf.read(change1[0])
194 mmap2 = mf.read(change2[0])
188 mmap2 = mf.read(change2[0])
195 date1 = util.datestr(change1[2])
189 date1 = util.datestr(change1[2])
196 date2 = util.datestr(change2[2])
190 date2 = util.datestr(change2[2])
197
191
198 modified, added, removed, deleted, unknown = r.changes(node1, node2)
192 modified, added, removed, deleted, unknown = r.changes(node1, node2)
199 if files:
193 if files:
200 modified, added, removed = map(lambda x: filterfiles(files, x),
194 modified, added, removed = map(lambda x: filterfiles(files, x),
201 (modified, added, removed))
195 (modified, added, removed))
202
196
203 diffopts = self.repo.ui.diffopts()
197 diffopts = self.repo.ui.diffopts()
204 showfunc = diffopts['showfunc']
198 showfunc = diffopts['showfunc']
205 ignorews = diffopts['ignorews']
199 ignorews = diffopts['ignorews']
206 for f in modified:
200 for f in modified:
207 to = r.file(f).read(mmap1[f])
201 to = r.file(f).read(mmap1[f])
208 tn = r.file(f).read(mmap2[f])
202 tn = r.file(f).read(mmap2[f])
209 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
203 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
210 showfunc=showfunc, ignorews=ignorews), f, tn)
204 showfunc=showfunc, ignorews=ignorews), f, tn)
211 for f in added:
205 for f in added:
212 to = None
206 to = None
213 tn = r.file(f).read(mmap2[f])
207 tn = r.file(f).read(mmap2[f])
214 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
208 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
215 showfunc=showfunc, ignorews=ignorews), f, tn)
209 showfunc=showfunc, ignorews=ignorews), f, tn)
216 for f in removed:
210 for f in removed:
217 to = r.file(f).read(mmap1[f])
211 to = r.file(f).read(mmap1[f])
218 tn = None
212 tn = None
219 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
213 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
220 showfunc=showfunc, ignorews=ignorews), f, tn)
214 showfunc=showfunc, ignorews=ignorews), f, tn)
221
215
222 def changelog(self, pos):
216 def changelog(self, pos):
223 def changenav(**map):
217 def changenav(**map):
224 def seq(factor, maxchanges=None):
218 def seq(factor, maxchanges=None):
225 if maxchanges:
219 if maxchanges:
226 yield maxchanges
220 yield maxchanges
227 if maxchanges >= 20 and maxchanges <= 40:
221 if maxchanges >= 20 and maxchanges <= 40:
228 yield 50
222 yield 50
229 else:
223 else:
230 yield 1 * factor
224 yield 1 * factor
231 yield 3 * factor
225 yield 3 * factor
232 for f in seq(factor * 10):
226 for f in seq(factor * 10):
233 yield f
227 yield f
234
228
235 l = []
229 l = []
236 last = 0
230 last = 0
237 for f in seq(1, self.maxchanges):
231 for f in seq(1, self.maxchanges):
238 if f < self.maxchanges or f <= last:
232 if f < self.maxchanges or f <= last:
239 continue
233 continue
240 if f > count:
234 if f > count:
241 break
235 break
242 last = f
236 last = f
243 r = "%d" % f
237 r = "%d" % f
244 if pos + f < count:
238 if pos + f < count:
245 l.append(("+" + r, pos + f))
239 l.append(("+" + r, pos + f))
246 if pos - f >= 0:
240 if pos - f >= 0:
247 l.insert(0, ("-" + r, pos - f))
241 l.insert(0, ("-" + r, pos - f))
248
242
249 yield {"rev": 0, "label": "(0)"}
243 yield {"rev": 0, "label": "(0)"}
250
244
251 for label, rev in l:
245 for label, rev in l:
252 yield {"label": label, "rev": rev}
246 yield {"label": label, "rev": rev}
253
247
254 yield {"label": "tip", "rev": "tip"}
248 yield {"label": "tip", "rev": "tip"}
255
249
256 def changelist(**map):
250 def changelist(**map):
257 parity = (start - end) & 1
251 parity = (start - end) & 1
258 cl = self.repo.changelog
252 cl = self.repo.changelog
259 l = [] # build a list in forward order for efficiency
253 l = [] # build a list in forward order for efficiency
260 for i in range(start, end):
254 for i in range(start, end):
261 n = cl.node(i)
255 n = cl.node(i)
262 changes = cl.read(n)
256 changes = cl.read(n)
263 hn = hex(n)
257 hn = hex(n)
264
258
265 l.insert(0, {"parity": parity,
259 l.insert(0, {"parity": parity,
266 "author": changes[1],
260 "author": changes[1],
267 "parent": self.siblings(cl.parents(n), cl.rev,
261 "parent": self.siblings(cl.parents(n), cl.rev,
268 cl.rev(n) - 1),
262 cl.rev(n) - 1),
269 "child": self.siblings(cl.children(n), cl.rev,
263 "child": self.siblings(cl.children(n), cl.rev,
270 cl.rev(n) + 1),
264 cl.rev(n) + 1),
271 "changelogtag": self.showtag("changelogtag",n),
265 "changelogtag": self.showtag("changelogtag",n),
272 "manifest": hex(changes[0]),
266 "manifest": hex(changes[0]),
273 "desc": changes[4],
267 "desc": changes[4],
274 "date": changes[2],
268 "date": changes[2],
275 "files": self.listfilediffs(changes[3], n),
269 "files": self.listfilediffs(changes[3], n),
276 "rev": i,
270 "rev": i,
277 "node": hn})
271 "node": hn})
278 parity = 1 - parity
272 parity = 1 - parity
279
273
280 for e in l:
274 for e in l:
281 yield e
275 yield e
282
276
283 cl = self.repo.changelog
277 cl = self.repo.changelog
284 mf = cl.read(cl.tip())[0]
278 mf = cl.read(cl.tip())[0]
285 count = cl.count()
279 count = cl.count()
286 start = max(0, pos - self.maxchanges + 1)
280 start = max(0, pos - self.maxchanges + 1)
287 end = min(count, start + self.maxchanges)
281 end = min(count, start + self.maxchanges)
288 pos = end - 1
282 pos = end - 1
289
283
290 yield self.t('changelog',
284 yield self.t('changelog',
291 changenav=changenav,
285 changenav=changenav,
292 manifest=hex(mf),
286 manifest=hex(mf),
293 rev=pos, changesets=count, entries=changelist)
287 rev=pos, changesets=count, entries=changelist)
294
288
295 def search(self, query):
289 def search(self, query):
296
290
297 def changelist(**map):
291 def changelist(**map):
298 cl = self.repo.changelog
292 cl = self.repo.changelog
299 count = 0
293 count = 0
300 qw = query.lower().split()
294 qw = query.lower().split()
301
295
302 def revgen():
296 def revgen():
303 for i in range(cl.count() - 1, 0, -100):
297 for i in range(cl.count() - 1, 0, -100):
304 l = []
298 l = []
305 for j in range(max(0, i - 100), i):
299 for j in range(max(0, i - 100), i):
306 n = cl.node(j)
300 n = cl.node(j)
307 changes = cl.read(n)
301 changes = cl.read(n)
308 l.append((n, j, changes))
302 l.append((n, j, changes))
309 l.reverse()
303 l.reverse()
310 for e in l:
304 for e in l:
311 yield e
305 yield e
312
306
313 for n, i, changes in revgen():
307 for n, i, changes in revgen():
314 miss = 0
308 miss = 0
315 for q in qw:
309 for q in qw:
316 if not (q in changes[1].lower() or
310 if not (q in changes[1].lower() or
317 q in changes[4].lower() or
311 q in changes[4].lower() or
318 q in " ".join(changes[3][:20]).lower()):
312 q in " ".join(changes[3][:20]).lower()):
319 miss = 1
313 miss = 1
320 break
314 break
321 if miss:
315 if miss:
322 continue
316 continue
323
317
324 count += 1
318 count += 1
325 hn = hex(n)
319 hn = hex(n)
326
320
327 yield self.t('searchentry',
321 yield self.t('searchentry',
328 parity=count & 1,
322 parity=count & 1,
329 author=changes[1],
323 author=changes[1],
330 parent=self.siblings(cl.parents(n), cl.rev),
324 parent=self.siblings(cl.parents(n), cl.rev),
331 child=self.siblings(cl.children(n), cl.rev),
325 child=self.siblings(cl.children(n), cl.rev),
332 changelogtag=self.showtag("changelogtag",n),
326 changelogtag=self.showtag("changelogtag",n),
333 manifest=hex(changes[0]),
327 manifest=hex(changes[0]),
334 desc=changes[4],
328 desc=changes[4],
335 date=changes[2],
329 date=changes[2],
336 files=self.listfilediffs(changes[3], n),
330 files=self.listfilediffs(changes[3], n),
337 rev=i,
331 rev=i,
338 node=hn)
332 node=hn)
339
333
340 if count >= self.maxchanges:
334 if count >= self.maxchanges:
341 break
335 break
342
336
343 cl = self.repo.changelog
337 cl = self.repo.changelog
344 mf = cl.read(cl.tip())[0]
338 mf = cl.read(cl.tip())[0]
345
339
346 yield self.t('search',
340 yield self.t('search',
347 query=query,
341 query=query,
348 manifest=hex(mf),
342 manifest=hex(mf),
349 entries=changelist)
343 entries=changelist)
350
344
351 def changeset(self, nodeid):
345 def changeset(self, nodeid):
352 cl = self.repo.changelog
346 cl = self.repo.changelog
353 n = self.repo.lookup(nodeid)
347 n = self.repo.lookup(nodeid)
354 nodeid = hex(n)
348 nodeid = hex(n)
355 changes = cl.read(n)
349 changes = cl.read(n)
356 p1 = cl.parents(n)[0]
350 p1 = cl.parents(n)[0]
357
351
358 files = []
352 files = []
359 mf = self.repo.manifest.read(changes[0])
353 mf = self.repo.manifest.read(changes[0])
360 for f in changes[3]:
354 for f in changes[3]:
361 files.append(self.t("filenodelink",
355 files.append(self.t("filenodelink",
362 filenode=hex(mf.get(f, nullid)), file=f))
356 filenode=hex(mf.get(f, nullid)), file=f))
363
357
364 def diff(**map):
358 def diff(**map):
365 yield self.diff(p1, n, None)
359 yield self.diff(p1, n, None)
366
360
367 yield self.t('changeset',
361 yield self.t('changeset',
368 diff=diff,
362 diff=diff,
369 rev=cl.rev(n),
363 rev=cl.rev(n),
370 node=nodeid,
364 node=nodeid,
371 parent=self.siblings(cl.parents(n), cl.rev),
365 parent=self.siblings(cl.parents(n), cl.rev),
372 child=self.siblings(cl.children(n), cl.rev),
366 child=self.siblings(cl.children(n), cl.rev),
373 changesettag=self.showtag("changesettag",n),
367 changesettag=self.showtag("changesettag",n),
374 manifest=hex(changes[0]),
368 manifest=hex(changes[0]),
375 author=changes[1],
369 author=changes[1],
376 desc=changes[4],
370 desc=changes[4],
377 date=changes[2],
371 date=changes[2],
378 files=files,
372 files=files,
379 archives=self.archivelist(nodeid))
373 archives=self.archivelist(nodeid))
380
374
381 def filelog(self, f, filenode):
375 def filelog(self, f, filenode):
382 cl = self.repo.changelog
376 cl = self.repo.changelog
383 fl = self.repo.file(f)
377 fl = self.repo.file(f)
384 filenode = hex(fl.lookup(filenode))
378 filenode = hex(fl.lookup(filenode))
385 count = fl.count()
379 count = fl.count()
386
380
387 def entries(**map):
381 def entries(**map):
388 l = []
382 l = []
389 parity = (count - 1) & 1
383 parity = (count - 1) & 1
390
384
391 for i in range(count):
385 for i in range(count):
392 n = fl.node(i)
386 n = fl.node(i)
393 lr = fl.linkrev(n)
387 lr = fl.linkrev(n)
394 cn = cl.node(lr)
388 cn = cl.node(lr)
395 cs = cl.read(cl.node(lr))
389 cs = cl.read(cl.node(lr))
396
390
397 l.insert(0, {"parity": parity,
391 l.insert(0, {"parity": parity,
398 "filenode": hex(n),
392 "filenode": hex(n),
399 "filerev": i,
393 "filerev": i,
400 "file": f,
394 "file": f,
401 "node": hex(cn),
395 "node": hex(cn),
402 "author": cs[1],
396 "author": cs[1],
403 "date": cs[2],
397 "date": cs[2],
404 "rename": self.renamelink(fl, n),
398 "rename": self.renamelink(fl, n),
405 "parent": self.siblings(fl.parents(n),
399 "parent": self.siblings(fl.parents(n),
406 fl.rev, file=f),
400 fl.rev, file=f),
407 "child": self.siblings(fl.children(n),
401 "child": self.siblings(fl.children(n),
408 fl.rev, file=f),
402 fl.rev, file=f),
409 "desc": cs[4]})
403 "desc": cs[4]})
410 parity = 1 - parity
404 parity = 1 - parity
411
405
412 for e in l:
406 for e in l:
413 yield e
407 yield e
414
408
415 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
409 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
416
410
417 def filerevision(self, f, node):
411 def filerevision(self, f, node):
418 fl = self.repo.file(f)
412 fl = self.repo.file(f)
419 n = fl.lookup(node)
413 n = fl.lookup(node)
420 node = hex(n)
414 node = hex(n)
421 text = fl.read(n)
415 text = fl.read(n)
422 changerev = fl.linkrev(n)
416 changerev = fl.linkrev(n)
423 cl = self.repo.changelog
417 cl = self.repo.changelog
424 cn = cl.node(changerev)
418 cn = cl.node(changerev)
425 cs = cl.read(cn)
419 cs = cl.read(cn)
426 mfn = cs[0]
420 mfn = cs[0]
427
421
428 mt = mimetypes.guess_type(f)[0]
422 mt = mimetypes.guess_type(f)[0]
429 rawtext = text
423 rawtext = text
430 if util.binary(text):
424 if util.binary(text):
431 text = "(binary:%s)" % mt
425 text = "(binary:%s)" % mt
432
426
433 def lines():
427 def lines():
434 for l, t in enumerate(text.splitlines(1)):
428 for l, t in enumerate(text.splitlines(1)):
435 yield {"line": t,
429 yield {"line": t,
436 "linenumber": "% 6d" % (l + 1),
430 "linenumber": "% 6d" % (l + 1),
437 "parity": l & 1}
431 "parity": l & 1}
438
432
439 yield self.t("filerevision",
433 yield self.t("filerevision",
440 file=f,
434 file=f,
441 filenode=node,
435 filenode=node,
442 path=up(f),
436 path=up(f),
443 text=lines(),
437 text=lines(),
444 raw=rawtext,
438 raw=rawtext,
445 mimetype=mt,
439 mimetype=mt,
446 rev=changerev,
440 rev=changerev,
447 node=hex(cn),
441 node=hex(cn),
448 manifest=hex(mfn),
442 manifest=hex(mfn),
449 author=cs[1],
443 author=cs[1],
450 date=cs[2],
444 date=cs[2],
451 parent=self.siblings(fl.parents(n), fl.rev, file=f),
445 parent=self.siblings(fl.parents(n), fl.rev, file=f),
452 child=self.siblings(fl.children(n), fl.rev, file=f),
446 child=self.siblings(fl.children(n), fl.rev, file=f),
453 rename=self.renamelink(fl, n),
447 rename=self.renamelink(fl, n),
454 permissions=self.repo.manifest.readflags(mfn)[f])
448 permissions=self.repo.manifest.readflags(mfn)[f])
455
449
456 def fileannotate(self, f, node):
450 def fileannotate(self, f, node):
457 bcache = {}
451 bcache = {}
458 ncache = {}
452 ncache = {}
459 fl = self.repo.file(f)
453 fl = self.repo.file(f)
460 n = fl.lookup(node)
454 n = fl.lookup(node)
461 node = hex(n)
455 node = hex(n)
462 changerev = fl.linkrev(n)
456 changerev = fl.linkrev(n)
463
457
464 cl = self.repo.changelog
458 cl = self.repo.changelog
465 cn = cl.node(changerev)
459 cn = cl.node(changerev)
466 cs = cl.read(cn)
460 cs = cl.read(cn)
467 mfn = cs[0]
461 mfn = cs[0]
468
462
469 def annotate(**map):
463 def annotate(**map):
470 parity = 1
464 parity = 1
471 last = None
465 last = None
472 for r, l in fl.annotate(n):
466 for r, l in fl.annotate(n):
473 try:
467 try:
474 cnode = ncache[r]
468 cnode = ncache[r]
475 except KeyError:
469 except KeyError:
476 cnode = ncache[r] = self.repo.changelog.node(r)
470 cnode = ncache[r] = self.repo.changelog.node(r)
477
471
478 try:
472 try:
479 name = bcache[r]
473 name = bcache[r]
480 except KeyError:
474 except KeyError:
481 cl = self.repo.changelog.read(cnode)
475 cl = self.repo.changelog.read(cnode)
482 bcache[r] = name = self.repo.ui.shortuser(cl[1])
476 bcache[r] = name = self.repo.ui.shortuser(cl[1])
483
477
484 if last != cnode:
478 if last != cnode:
485 parity = 1 - parity
479 parity = 1 - parity
486 last = cnode
480 last = cnode
487
481
488 yield {"parity": parity,
482 yield {"parity": parity,
489 "node": hex(cnode),
483 "node": hex(cnode),
490 "rev": r,
484 "rev": r,
491 "author": name,
485 "author": name,
492 "file": f,
486 "file": f,
493 "line": l}
487 "line": l}
494
488
495 yield self.t("fileannotate",
489 yield self.t("fileannotate",
496 file=f,
490 file=f,
497 filenode=node,
491 filenode=node,
498 annotate=annotate,
492 annotate=annotate,
499 path=up(f),
493 path=up(f),
500 rev=changerev,
494 rev=changerev,
501 node=hex(cn),
495 node=hex(cn),
502 manifest=hex(mfn),
496 manifest=hex(mfn),
503 author=cs[1],
497 author=cs[1],
504 date=cs[2],
498 date=cs[2],
505 rename=self.renamelink(fl, n),
499 rename=self.renamelink(fl, n),
506 parent=self.siblings(fl.parents(n), fl.rev, file=f),
500 parent=self.siblings(fl.parents(n), fl.rev, file=f),
507 child=self.siblings(fl.children(n), fl.rev, file=f),
501 child=self.siblings(fl.children(n), fl.rev, file=f),
508 permissions=self.repo.manifest.readflags(mfn)[f])
502 permissions=self.repo.manifest.readflags(mfn)[f])
509
503
510 def manifest(self, mnode, path):
504 def manifest(self, mnode, path):
511 man = self.repo.manifest
505 man = self.repo.manifest
512 mn = man.lookup(mnode)
506 mn = man.lookup(mnode)
513 mnode = hex(mn)
507 mnode = hex(mn)
514 mf = man.read(mn)
508 mf = man.read(mn)
515 rev = man.rev(mn)
509 rev = man.rev(mn)
516 node = self.repo.changelog.node(rev)
510 node = self.repo.changelog.node(rev)
517 mff = man.readflags(mn)
511 mff = man.readflags(mn)
518
512
519 files = {}
513 files = {}
520
514
521 p = path[1:]
515 p = path[1:]
522 if p and p[-1] != "/":
516 if p and p[-1] != "/":
523 p += "/"
517 p += "/"
524 l = len(p)
518 l = len(p)
525
519
526 for f,n in mf.items():
520 for f,n in mf.items():
527 if f[:l] != p:
521 if f[:l] != p:
528 continue
522 continue
529 remain = f[l:]
523 remain = f[l:]
530 if "/" in remain:
524 if "/" in remain:
531 short = remain[:remain.find("/") + 1] # bleah
525 short = remain[:remain.find("/") + 1] # bleah
532 files[short] = (f, None)
526 files[short] = (f, None)
533 else:
527 else:
534 short = os.path.basename(remain)
528 short = os.path.basename(remain)
535 files[short] = (f, n)
529 files[short] = (f, n)
536
530
537 def filelist(**map):
531 def filelist(**map):
538 parity = 0
532 parity = 0
539 fl = files.keys()
533 fl = files.keys()
540 fl.sort()
534 fl.sort()
541 for f in fl:
535 for f in fl:
542 full, fnode = files[f]
536 full, fnode = files[f]
543 if not fnode:
537 if not fnode:
544 continue
538 continue
545
539
546 yield {"file": full,
540 yield {"file": full,
547 "manifest": mnode,
541 "manifest": mnode,
548 "filenode": hex(fnode),
542 "filenode": hex(fnode),
549 "parity": parity,
543 "parity": parity,
550 "basename": f,
544 "basename": f,
551 "permissions": mff[full]}
545 "permissions": mff[full]}
552 parity = 1 - parity
546 parity = 1 - parity
553
547
554 def dirlist(**map):
548 def dirlist(**map):
555 parity = 0
549 parity = 0
556 fl = files.keys()
550 fl = files.keys()
557 fl.sort()
551 fl.sort()
558 for f in fl:
552 for f in fl:
559 full, fnode = files[f]
553 full, fnode = files[f]
560 if fnode:
554 if fnode:
561 continue
555 continue
562
556
563 yield {"parity": parity,
557 yield {"parity": parity,
564 "path": os.path.join(path, f),
558 "path": os.path.join(path, f),
565 "manifest": mnode,
559 "manifest": mnode,
566 "basename": f[:-1]}
560 "basename": f[:-1]}
567 parity = 1 - parity
561 parity = 1 - parity
568
562
569 yield self.t("manifest",
563 yield self.t("manifest",
570 manifest=mnode,
564 manifest=mnode,
571 rev=rev,
565 rev=rev,
572 node=hex(node),
566 node=hex(node),
573 path=path,
567 path=path,
574 up=up(path),
568 up=up(path),
575 fentries=filelist,
569 fentries=filelist,
576 dentries=dirlist,
570 dentries=dirlist,
577 archives=self.archivelist(hex(node)))
571 archives=self.archivelist(hex(node)))
578
572
579 def tags(self):
573 def tags(self):
580 cl = self.repo.changelog
574 cl = self.repo.changelog
581 mf = cl.read(cl.tip())[0]
575 mf = cl.read(cl.tip())[0]
582
576
583 i = self.repo.tagslist()
577 i = self.repo.tagslist()
584 i.reverse()
578 i.reverse()
585
579
586 def entries(notip=False, **map):
580 def entries(notip=False, **map):
587 parity = 0
581 parity = 0
588 for k,n in i:
582 for k,n in i:
589 if notip and k == "tip": continue
583 if notip and k == "tip": continue
590 yield {"parity": parity,
584 yield {"parity": parity,
591 "tag": k,
585 "tag": k,
592 "tagmanifest": hex(cl.read(n)[0]),
586 "tagmanifest": hex(cl.read(n)[0]),
593 "date": cl.read(n)[2],
587 "date": cl.read(n)[2],
594 "node": hex(n)}
588 "node": hex(n)}
595 parity = 1 - parity
589 parity = 1 - parity
596
590
597 yield self.t("tags",
591 yield self.t("tags",
598 manifest=hex(mf),
592 manifest=hex(mf),
599 entries=lambda **x: entries(False, **x),
593 entries=lambda **x: entries(False, **x),
600 entriesnotip=lambda **x: entries(True, **x))
594 entriesnotip=lambda **x: entries(True, **x))
601
595
602 def summary(self):
596 def summary(self):
603 cl = self.repo.changelog
597 cl = self.repo.changelog
604 mf = cl.read(cl.tip())[0]
598 mf = cl.read(cl.tip())[0]
605
599
606 i = self.repo.tagslist()
600 i = self.repo.tagslist()
607 i.reverse()
601 i.reverse()
608
602
609 def tagentries(**map):
603 def tagentries(**map):
610 parity = 0
604 parity = 0
611 count = 0
605 count = 0
612 for k,n in i:
606 for k,n in i:
613 if k == "tip": # skip tip
607 if k == "tip": # skip tip
614 continue;
608 continue;
615
609
616 count += 1
610 count += 1
617 if count > 10: # limit to 10 tags
611 if count > 10: # limit to 10 tags
618 break;
612 break;
619
613
620 c = cl.read(n)
614 c = cl.read(n)
621 m = c[0]
615 m = c[0]
622 t = c[2]
616 t = c[2]
623
617
624 yield self.t("tagentry",
618 yield self.t("tagentry",
625 parity = parity,
619 parity = parity,
626 tag = k,
620 tag = k,
627 node = hex(n),
621 node = hex(n),
628 date = t,
622 date = t,
629 tagmanifest = hex(m))
623 tagmanifest = hex(m))
630 parity = 1 - parity
624 parity = 1 - parity
631
625
632 def changelist(**map):
626 def changelist(**map):
633 parity = 0
627 parity = 0
634 cl = self.repo.changelog
628 cl = self.repo.changelog
635 l = [] # build a list in forward order for efficiency
629 l = [] # build a list in forward order for efficiency
636 for i in range(start, end):
630 for i in range(start, end):
637 n = cl.node(i)
631 n = cl.node(i)
638 changes = cl.read(n)
632 changes = cl.read(n)
639 hn = hex(n)
633 hn = hex(n)
640 t = changes[2]
634 t = changes[2]
641
635
642 l.insert(0, self.t(
636 l.insert(0, self.t(
643 'shortlogentry',
637 'shortlogentry',
644 parity = parity,
638 parity = parity,
645 author = changes[1],
639 author = changes[1],
646 manifest = hex(changes[0]),
640 manifest = hex(changes[0]),
647 desc = changes[4],
641 desc = changes[4],
648 date = t,
642 date = t,
649 rev = i,
643 rev = i,
650 node = hn))
644 node = hn))
651 parity = 1 - parity
645 parity = 1 - parity
652
646
653 yield l
647 yield l
654
648
655 cl = self.repo.changelog
649 cl = self.repo.changelog
656 mf = cl.read(cl.tip())[0]
650 mf = cl.read(cl.tip())[0]
657 count = cl.count()
651 count = cl.count()
658 start = max(0, count - self.maxchanges)
652 start = max(0, count - self.maxchanges)
659 end = min(count, start + self.maxchanges)
653 end = min(count, start + self.maxchanges)
660 pos = end - 1
654 pos = end - 1
661
655
662 yield self.t("summary",
656 yield self.t("summary",
663 desc = self.repo.ui.config("web", "description", "unknown"),
657 desc = self.repo.ui.config("web", "description", "unknown"),
664 owner = (self.repo.ui.config("ui", "username") or # preferred
658 owner = (self.repo.ui.config("ui", "username") or # preferred
665 self.repo.ui.config("web", "contact") or # deprecated
659 self.repo.ui.config("web", "contact") or # deprecated
666 self.repo.ui.config("web", "author", "unknown")), # also
660 self.repo.ui.config("web", "author", "unknown")), # also
667 lastchange = (0, 0), # FIXME
661 lastchange = (0, 0), # FIXME
668 manifest = hex(mf),
662 manifest = hex(mf),
669 tags = tagentries,
663 tags = tagentries,
670 shortlog = changelist)
664 shortlog = changelist)
671
665
672 def filediff(self, file, changeset):
666 def filediff(self, file, changeset):
673 cl = self.repo.changelog
667 cl = self.repo.changelog
674 n = self.repo.lookup(changeset)
668 n = self.repo.lookup(changeset)
675 changeset = hex(n)
669 changeset = hex(n)
676 p1 = cl.parents(n)[0]
670 p1 = cl.parents(n)[0]
677 cs = cl.read(n)
671 cs = cl.read(n)
678 mf = self.repo.manifest.read(cs[0])
672 mf = self.repo.manifest.read(cs[0])
679
673
680 def diff(**map):
674 def diff(**map):
681 yield self.diff(p1, n, file)
675 yield self.diff(p1, n, file)
682
676
683 yield self.t("filediff",
677 yield self.t("filediff",
684 file=file,
678 file=file,
685 filenode=hex(mf.get(file, nullid)),
679 filenode=hex(mf.get(file, nullid)),
686 node=changeset,
680 node=changeset,
687 rev=self.repo.changelog.rev(n),
681 rev=self.repo.changelog.rev(n),
688 parent=self.siblings(cl.parents(n), cl.rev),
682 parent=self.siblings(cl.parents(n), cl.rev),
689 child=self.siblings(cl.children(n), cl.rev),
683 child=self.siblings(cl.children(n), cl.rev),
690 diff=diff)
684 diff=diff)
691
685
692 def archive(self, req, cnode, type):
686 def archive(self, req, cnode, type):
693 cs = self.repo.changelog.read(cnode)
687 cs = self.repo.changelog.read(cnode)
694 mnode = cs[0]
688 mnode = cs[0]
695 mf = self.repo.manifest.read(mnode)
689 mf = self.repo.manifest.read(mnode)
696 rev = self.repo.manifest.rev(mnode)
690 rev = self.repo.manifest.rev(mnode)
697 reponame = re.sub(r"\W+", "-", self.reponame)
691 reponame = re.sub(r"\W+", "-", self.reponame)
698 name = "%s-%s/" % (reponame, short(cnode))
692 name = "%s-%s/" % (reponame, short(cnode))
699
693
700 files = mf.keys()
694 files = mf.keys()
701 files.sort()
695 files.sort()
702
696
703 if type == 'zip':
697 if type == 'zip':
704 tmp = tempfile.mkstemp()[1]
698 tmp = tempfile.mkstemp()[1]
705 try:
699 try:
706 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
700 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
707
701
708 for f in files:
702 for f in files:
709 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
703 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
710 zf.close()
704 zf.close()
711
705
712 f = open(tmp, 'r')
706 f = open(tmp, 'r')
713 req.httphdr('application/zip', name[:-1] + '.zip',
707 req.httphdr('application/zip', name[:-1] + '.zip',
714 os.path.getsize(tmp))
708 os.path.getsize(tmp))
715 req.write(f.read())
709 req.write(f.read())
716 f.close()
710 f.close()
717 finally:
711 finally:
718 os.unlink(tmp)
712 os.unlink(tmp)
719
713
720 else:
714 else:
721 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
715 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
722 mff = self.repo.manifest.readflags(mnode)
716 mff = self.repo.manifest.readflags(mnode)
723 mtime = int(time.time())
717 mtime = int(time.time())
724
718
725 if type == "gz":
719 if type == "gz":
726 encoding = "gzip"
720 encoding = "gzip"
727 else:
721 else:
728 encoding = "x-bzip2"
722 encoding = "x-bzip2"
729 req.header([('Content-type', 'application/x-tar'),
723 req.header([('Content-type', 'application/x-tar'),
730 ('Content-disposition', 'attachment; filename=%s%s%s' %
724 ('Content-disposition', 'attachment; filename=%s%s%s' %
731 (name[:-1], '.tar.', type)),
725 (name[:-1], '.tar.', type)),
732 ('Content-encoding', encoding)])
726 ('Content-encoding', encoding)])
733 for fname in files:
727 for fname in files:
734 rcont = self.repo.file(fname).read(mf[fname])
728 rcont = self.repo.file(fname).read(mf[fname])
735 finfo = tarfile.TarInfo(name + fname)
729 finfo = tarfile.TarInfo(name + fname)
736 finfo.mtime = mtime
730 finfo.mtime = mtime
737 finfo.size = len(rcont)
731 finfo.size = len(rcont)
738 finfo.mode = mff[fname] and 0755 or 0644
732 finfo.mode = mff[fname] and 0755 or 0644
739 tf.addfile(finfo, StringIO.StringIO(rcont))
733 tf.addfile(finfo, StringIO.StringIO(rcont))
740 tf.close()
734 tf.close()
741
735
742 # add tags to things
736 # add tags to things
743 # tags -> list of changesets corresponding to tags
737 # tags -> list of changesets corresponding to tags
744 # find tag, changeset, file
738 # find tag, changeset, file
745
739
746 def run(self, req=hgrequest()):
740 def run(self, req=hgrequest()):
747 def clean(path):
741 def clean(path):
748 p = os.path.normpath(path)
742 p = os.path.normpath(path)
749 if p[:2] == "..":
743 if p[:2] == "..":
750 raise "suspicious path"
744 raise "suspicious path"
751 return p
745 return p
752
746
753 def header(**map):
747 def header(**map):
754 yield self.t("header", **map)
748 yield self.t("header", **map)
755
749
756 def footer(**map):
750 def footer(**map):
757 yield self.t("footer", **map)
751 yield self.t("footer", **map)
758
752
759 def expand_form(form):
753 def expand_form(form):
760 shortcuts = {
754 shortcuts = {
761 'cl': [('cmd', ['changelog']), ('rev', None)],
755 'cl': [('cmd', ['changelog']), ('rev', None)],
762 'cs': [('cmd', ['changeset']), ('node', None)],
756 'cs': [('cmd', ['changeset']), ('node', None)],
763 'f': [('cmd', ['file']), ('filenode', None)],
757 'f': [('cmd', ['file']), ('filenode', None)],
764 'fl': [('cmd', ['filelog']), ('filenode', None)],
758 'fl': [('cmd', ['filelog']), ('filenode', None)],
765 'fd': [('cmd', ['filediff']), ('node', None)],
759 'fd': [('cmd', ['filediff']), ('node', None)],
766 'fa': [('cmd', ['annotate']), ('filenode', None)],
760 'fa': [('cmd', ['annotate']), ('filenode', None)],
767 'mf': [('cmd', ['manifest']), ('manifest', None)],
761 'mf': [('cmd', ['manifest']), ('manifest', None)],
768 'ca': [('cmd', ['archive']), ('node', None)],
762 'ca': [('cmd', ['archive']), ('node', None)],
769 'tags': [('cmd', ['tags'])],
763 'tags': [('cmd', ['tags'])],
770 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
764 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
771 'static': [('cmd', ['static']), ('file', None)]
765 'static': [('cmd', ['static']), ('file', None)]
772 }
766 }
773
767
774 for k in shortcuts.iterkeys():
768 for k in shortcuts.iterkeys():
775 if form.has_key(k):
769 if form.has_key(k):
776 for name, value in shortcuts[k]:
770 for name, value in shortcuts[k]:
777 if value is None:
771 if value is None:
778 value = form[k]
772 value = form[k]
779 form[name] = value
773 form[name] = value
780 del form[k]
774 del form[k]
781
775
782 self.refresh()
776 self.refresh()
783
777
784 expand_form(req.form)
778 expand_form(req.form)
785
779
786 t = self.repo.ui.config("web", "templates", templatepath())
780 t = self.repo.ui.config("web", "templates", templater.templatepath())
787 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
781 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
788 m = os.path.join(t, "map")
782 m = os.path.join(t, "map")
789 style = self.repo.ui.config("web", "style", "")
783 style = self.repo.ui.config("web", "style", "")
790 if req.form.has_key('style'):
784 if req.form.has_key('style'):
791 style = req.form['style'][0]
785 style = req.form['style'][0]
792 if style:
786 if style:
793 b = os.path.basename("map-" + style)
787 b = os.path.basename("map-" + style)
794 p = os.path.join(t, b)
788 p = os.path.join(t, b)
795 if os.path.isfile(p):
789 if os.path.isfile(p):
796 m = p
790 m = p
797
791
798 port = req.env["SERVER_PORT"]
792 port = req.env["SERVER_PORT"]
799 port = port != "80" and (":" + port) or ""
793 port = port != "80" and (":" + port) or ""
800 uri = req.env["REQUEST_URI"]
794 uri = req.env["REQUEST_URI"]
801 if "?" in uri:
795 if "?" in uri:
802 uri = uri.split("?")[0]
796 uri = uri.split("?")[0]
803 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
797 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
804 if not self.reponame:
798 if not self.reponame:
805 self.reponame = (self.repo.ui.config("web", "name")
799 self.reponame = (self.repo.ui.config("web", "name")
806 or uri.strip('/') or self.repo.root)
800 or uri.strip('/') or self.repo.root)
807
801
808 self.t = templater.templater(m, templater.common_filters,
802 self.t = templater.templater(m, templater.common_filters,
809 {"url": url,
803 {"url": url,
810 "repo": self.reponame,
804 "repo": self.reponame,
811 "header": header,
805 "header": header,
812 "footer": footer,
806 "footer": footer,
813 })
807 })
814
808
815 if not req.form.has_key('cmd'):
809 if not req.form.has_key('cmd'):
816 req.form['cmd'] = [self.t.cache['default'],]
810 req.form['cmd'] = [self.t.cache['default'],]
817
811
818 if req.form['cmd'][0] == 'changelog':
812 if req.form['cmd'][0] == 'changelog':
819 c = self.repo.changelog.count() - 1
813 c = self.repo.changelog.count() - 1
820 hi = c
814 hi = c
821 if req.form.has_key('rev'):
815 if req.form.has_key('rev'):
822 hi = req.form['rev'][0]
816 hi = req.form['rev'][0]
823 try:
817 try:
824 hi = self.repo.changelog.rev(self.repo.lookup(hi))
818 hi = self.repo.changelog.rev(self.repo.lookup(hi))
825 except hg.RepoError:
819 except hg.RepoError:
826 req.write(self.search(hi))
820 req.write(self.search(hi))
827 return
821 return
828
822
829 req.write(self.changelog(hi))
823 req.write(self.changelog(hi))
830
824
831 elif req.form['cmd'][0] == 'changeset':
825 elif req.form['cmd'][0] == 'changeset':
832 req.write(self.changeset(req.form['node'][0]))
826 req.write(self.changeset(req.form['node'][0]))
833
827
834 elif req.form['cmd'][0] == 'manifest':
828 elif req.form['cmd'][0] == 'manifest':
835 req.write(self.manifest(req.form['manifest'][0],
829 req.write(self.manifest(req.form['manifest'][0],
836 clean(req.form['path'][0])))
830 clean(req.form['path'][0])))
837
831
838 elif req.form['cmd'][0] == 'tags':
832 elif req.form['cmd'][0] == 'tags':
839 req.write(self.tags())
833 req.write(self.tags())
840
834
841 elif req.form['cmd'][0] == 'summary':
835 elif req.form['cmd'][0] == 'summary':
842 req.write(self.summary())
836 req.write(self.summary())
843
837
844 elif req.form['cmd'][0] == 'filediff':
838 elif req.form['cmd'][0] == 'filediff':
845 req.write(self.filediff(clean(req.form['file'][0]),
839 req.write(self.filediff(clean(req.form['file'][0]),
846 req.form['node'][0]))
840 req.form['node'][0]))
847
841
848 elif req.form['cmd'][0] == 'file':
842 elif req.form['cmd'][0] == 'file':
849 req.write(self.filerevision(clean(req.form['file'][0]),
843 req.write(self.filerevision(clean(req.form['file'][0]),
850 req.form['filenode'][0]))
844 req.form['filenode'][0]))
851
845
852 elif req.form['cmd'][0] == 'annotate':
846 elif req.form['cmd'][0] == 'annotate':
853 req.write(self.fileannotate(clean(req.form['file'][0]),
847 req.write(self.fileannotate(clean(req.form['file'][0]),
854 req.form['filenode'][0]))
848 req.form['filenode'][0]))
855
849
856 elif req.form['cmd'][0] == 'filelog':
850 elif req.form['cmd'][0] == 'filelog':
857 req.write(self.filelog(clean(req.form['file'][0]),
851 req.write(self.filelog(clean(req.form['file'][0]),
858 req.form['filenode'][0]))
852 req.form['filenode'][0]))
859
853
860 elif req.form['cmd'][0] == 'heads':
854 elif req.form['cmd'][0] == 'heads':
861 req.httphdr("application/mercurial-0.1")
855 req.httphdr("application/mercurial-0.1")
862 h = self.repo.heads()
856 h = self.repo.heads()
863 req.write(" ".join(map(hex, h)) + "\n")
857 req.write(" ".join(map(hex, h)) + "\n")
864
858
865 elif req.form['cmd'][0] == 'branches':
859 elif req.form['cmd'][0] == 'branches':
866 req.httphdr("application/mercurial-0.1")
860 req.httphdr("application/mercurial-0.1")
867 nodes = []
861 nodes = []
868 if req.form.has_key('nodes'):
862 if req.form.has_key('nodes'):
869 nodes = map(bin, req.form['nodes'][0].split(" "))
863 nodes = map(bin, req.form['nodes'][0].split(" "))
870 for b in self.repo.branches(nodes):
864 for b in self.repo.branches(nodes):
871 req.write(" ".join(map(hex, b)) + "\n")
865 req.write(" ".join(map(hex, b)) + "\n")
872
866
873 elif req.form['cmd'][0] == 'between':
867 elif req.form['cmd'][0] == 'between':
874 req.httphdr("application/mercurial-0.1")
868 req.httphdr("application/mercurial-0.1")
875 nodes = []
869 nodes = []
876 if req.form.has_key('pairs'):
870 if req.form.has_key('pairs'):
877 pairs = [map(bin, p.split("-"))
871 pairs = [map(bin, p.split("-"))
878 for p in req.form['pairs'][0].split(" ")]
872 for p in req.form['pairs'][0].split(" ")]
879 for b in self.repo.between(pairs):
873 for b in self.repo.between(pairs):
880 req.write(" ".join(map(hex, b)) + "\n")
874 req.write(" ".join(map(hex, b)) + "\n")
881
875
882 elif req.form['cmd'][0] == 'changegroup':
876 elif req.form['cmd'][0] == 'changegroup':
883 req.httphdr("application/mercurial-0.1")
877 req.httphdr("application/mercurial-0.1")
884 nodes = []
878 nodes = []
885 if not self.allowpull:
879 if not self.allowpull:
886 return
880 return
887
881
888 if req.form.has_key('roots'):
882 if req.form.has_key('roots'):
889 nodes = map(bin, req.form['roots'][0].split(" "))
883 nodes = map(bin, req.form['roots'][0].split(" "))
890
884
891 z = zlib.compressobj()
885 z = zlib.compressobj()
892 f = self.repo.changegroup(nodes, 'serve')
886 f = self.repo.changegroup(nodes, 'serve')
893 while 1:
887 while 1:
894 chunk = f.read(4096)
888 chunk = f.read(4096)
895 if not chunk:
889 if not chunk:
896 break
890 break
897 req.write(z.compress(chunk))
891 req.write(z.compress(chunk))
898
892
899 req.write(z.flush())
893 req.write(z.flush())
900
894
901 elif req.form['cmd'][0] == 'archive':
895 elif req.form['cmd'][0] == 'archive':
902 changeset = self.repo.lookup(req.form['node'][0])
896 changeset = self.repo.lookup(req.form['node'][0])
903 type = req.form['type'][0]
897 type = req.form['type'][0]
904 if (type in self.archives and
898 if (type in self.archives and
905 self.repo.ui.configbool("web", "allow" + type, False)):
899 self.repo.ui.configbool("web", "allow" + type, False)):
906 self.archive(req, changeset, type)
900 self.archive(req, changeset, type)
907 return
901 return
908
902
909 req.write(self.t("error"))
903 req.write(self.t("error"))
910
904
911 elif req.form['cmd'][0] == 'static':
905 elif req.form['cmd'][0] == 'static':
912 fname = req.form['file'][0]
906 fname = req.form['file'][0]
913 req.write(staticfile(static, fname)
907 req.write(staticfile(static, fname)
914 or self.t("error", error="%r not found" % fname))
908 or self.t("error", error="%r not found" % fname))
915
909
916 else:
910 else:
917 req.write(self.t("error"))
911 req.write(self.t("error"))
918
912
919 def create_server(repo):
913 def create_server(repo):
920
914
921 def openlog(opt, default):
915 def openlog(opt, default):
922 if opt and opt != '-':
916 if opt and opt != '-':
923 return open(opt, 'w')
917 return open(opt, 'w')
924 return default
918 return default
925
919
926 address = repo.ui.config("web", "address", "")
920 address = repo.ui.config("web", "address", "")
927 port = int(repo.ui.config("web", "port", 8000))
921 port = int(repo.ui.config("web", "port", 8000))
928 use_ipv6 = repo.ui.configbool("web", "ipv6")
922 use_ipv6 = repo.ui.configbool("web", "ipv6")
929 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
923 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
930 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
924 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
931
925
932 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
926 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
933 address_family = getattr(socket, 'AF_INET6', None)
927 address_family = getattr(socket, 'AF_INET6', None)
934
928
935 def __init__(self, *args, **kwargs):
929 def __init__(self, *args, **kwargs):
936 if self.address_family is None:
930 if self.address_family is None:
937 raise hg.RepoError(_('IPv6 not available on this system'))
931 raise hg.RepoError(_('IPv6 not available on this system'))
938 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
932 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
939
933
940 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
934 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
941 def log_error(self, format, *args):
935 def log_error(self, format, *args):
942 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
936 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
943 self.log_date_time_string(),
937 self.log_date_time_string(),
944 format % args))
938 format % args))
945
939
946 def log_message(self, format, *args):
940 def log_message(self, format, *args):
947 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
941 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
948 self.log_date_time_string(),
942 self.log_date_time_string(),
949 format % args))
943 format % args))
950
944
951 def do_POST(self):
945 def do_POST(self):
952 try:
946 try:
953 self.do_hgweb()
947 self.do_hgweb()
954 except socket.error, inst:
948 except socket.error, inst:
955 if inst[0] != errno.EPIPE:
949 if inst[0] != errno.EPIPE:
956 raise
950 raise
957
951
958 def do_GET(self):
952 def do_GET(self):
959 self.do_POST()
953 self.do_POST()
960
954
961 def do_hgweb(self):
955 def do_hgweb(self):
962 query = ""
956 query = ""
963 p = self.path.find("?")
957 p = self.path.find("?")
964 if p:
958 if p:
965 query = self.path[p + 1:]
959 query = self.path[p + 1:]
966 query = query.replace('+', ' ')
960 query = query.replace('+', ' ')
967
961
968 env = {}
962 env = {}
969 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
963 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
970 env['REQUEST_METHOD'] = self.command
964 env['REQUEST_METHOD'] = self.command
971 env['SERVER_NAME'] = self.server.server_name
965 env['SERVER_NAME'] = self.server.server_name
972 env['SERVER_PORT'] = str(self.server.server_port)
966 env['SERVER_PORT'] = str(self.server.server_port)
973 env['REQUEST_URI'] = "/"
967 env['REQUEST_URI'] = "/"
974 if query:
968 if query:
975 env['QUERY_STRING'] = query
969 env['QUERY_STRING'] = query
976 host = self.address_string()
970 host = self.address_string()
977 if host != self.client_address[0]:
971 if host != self.client_address[0]:
978 env['REMOTE_HOST'] = host
972 env['REMOTE_HOST'] = host
979 env['REMOTE_ADDR'] = self.client_address[0]
973 env['REMOTE_ADDR'] = self.client_address[0]
980
974
981 if self.headers.typeheader is None:
975 if self.headers.typeheader is None:
982 env['CONTENT_TYPE'] = self.headers.type
976 env['CONTENT_TYPE'] = self.headers.type
983 else:
977 else:
984 env['CONTENT_TYPE'] = self.headers.typeheader
978 env['CONTENT_TYPE'] = self.headers.typeheader
985 length = self.headers.getheader('content-length')
979 length = self.headers.getheader('content-length')
986 if length:
980 if length:
987 env['CONTENT_LENGTH'] = length
981 env['CONTENT_LENGTH'] = length
988 accept = []
982 accept = []
989 for line in self.headers.getallmatchingheaders('accept'):
983 for line in self.headers.getallmatchingheaders('accept'):
990 if line[:1] in "\t\n\r ":
984 if line[:1] in "\t\n\r ":
991 accept.append(line.strip())
985 accept.append(line.strip())
992 else:
986 else:
993 accept = accept + line[7:].split(',')
987 accept = accept + line[7:].split(',')
994 env['HTTP_ACCEPT'] = ','.join(accept)
988 env['HTTP_ACCEPT'] = ','.join(accept)
995
989
996 req = hgrequest(self.rfile, self.wfile, env)
990 req = hgrequest(self.rfile, self.wfile, env)
997 self.send_response(200, "Script output follows")
991 self.send_response(200, "Script output follows")
998 hg.run(req)
992 hg.run(req)
999
993
1000 hg = hgweb(repo)
994 hg = hgweb(repo)
1001 if use_ipv6:
995 if use_ipv6:
1002 return IPv6HTTPServer((address, port), hgwebhandler)
996 return IPv6HTTPServer((address, port), hgwebhandler)
1003 else:
997 else:
1004 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
998 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
1005
999
1006 # This is a stopgap
1000 # This is a stopgap
1007 class hgwebdir(object):
1001 class hgwebdir(object):
1008 def __init__(self, config):
1002 def __init__(self, config):
1009 def cleannames(items):
1003 def cleannames(items):
1010 return [(name.strip('/'), path) for name, path in items]
1004 return [(name.strip('/'), path) for name, path in items]
1011
1005
1012 if type(config) == type([]):
1006 if type(config) == type([]):
1013 self.repos = cleannames(config)
1007 self.repos = cleannames(config)
1014 elif type(config) == type({}):
1008 elif type(config) == type({}):
1015 self.repos = cleannames(config.items())
1009 self.repos = cleannames(config.items())
1016 self.repos.sort()
1010 self.repos.sort()
1017 else:
1011 else:
1018 cp = ConfigParser.SafeConfigParser()
1012 cp = ConfigParser.SafeConfigParser()
1019 cp.read(config)
1013 cp.read(config)
1020 self.repos = cleannames(cp.items("paths"))
1014 self.repos = cleannames(cp.items("paths"))
1021 self.repos.sort()
1015 self.repos.sort()
1022
1016
1023 def run(self, req=hgrequest()):
1017 def run(self, req=hgrequest()):
1024 def header(**map):
1018 def header(**map):
1025 yield tmpl("header", **map)
1019 yield tmpl("header", **map)
1026
1020
1027 def footer(**map):
1021 def footer(**map):
1028 yield tmpl("footer", **map)
1022 yield tmpl("footer", **map)
1029
1023
1030 m = os.path.join(templatepath(), "map")
1024 m = os.path.join(templater.templatepath(), "map")
1031 tmpl = templater(m, common_filters,
1025 tmpl = templater(m, common_filters,
1032 {"header": header, "footer": footer})
1026 {"header": header, "footer": footer})
1033
1027
1034 def entries(**map):
1028 def entries(**map):
1035 parity = 0
1029 parity = 0
1036 for name, path in self.repos:
1030 for name, path in self.repos:
1037 u = ui.ui()
1031 u = ui.ui()
1038 try:
1032 try:
1039 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
1033 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
1040 except IOError:
1034 except IOError:
1041 pass
1035 pass
1042 get = u.config
1036 get = u.config
1043
1037
1044 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
1038 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
1045 .replace("//", "/"))
1039 .replace("//", "/"))
1046
1040
1047 # update time with local timezone
1041 # update time with local timezone
1048 try:
1042 try:
1049 d = (get_mtime(path), util.makedate()[1])
1043 d = (get_mtime(path), util.makedate()[1])
1050 except OSError:
1044 except OSError:
1051 continue
1045 continue
1052
1046
1053 yield dict(contact=(get("ui", "username") or # preferred
1047 yield dict(contact=(get("ui", "username") or # preferred
1054 get("web", "contact") or # deprecated
1048 get("web", "contact") or # deprecated
1055 get("web", "author", "unknown")), # also
1049 get("web", "author", "unknown")), # also
1056 name=get("web", "name", name),
1050 name=get("web", "name", name),
1057 url=url,
1051 url=url,
1058 parity=parity,
1052 parity=parity,
1059 shortdesc=get("web", "description", "unknown"),
1053 shortdesc=get("web", "description", "unknown"),
1060 lastupdate=d)
1054 lastupdate=d)
1061
1055
1062 parity = 1 - parity
1056 parity = 1 - parity
1063
1057
1064 virtual = req.env.get("PATH_INFO", "").strip('/')
1058 virtual = req.env.get("PATH_INFO", "").strip('/')
1065 if virtual:
1059 if virtual:
1066 real = dict(self.repos).get(virtual)
1060 real = dict(self.repos).get(virtual)
1067 if real:
1061 if real:
1068 try:
1062 try:
1069 hgweb(real).run(req)
1063 hgweb(real).run(req)
1070 except IOError, inst:
1064 except IOError, inst:
1071 req.write(tmpl("error", error=inst.strerror))
1065 req.write(tmpl("error", error=inst.strerror))
1072 except hg.RepoError, inst:
1066 except hg.RepoError, inst:
1073 req.write(tmpl("error", error=str(inst)))
1067 req.write(tmpl("error", error=str(inst)))
1074 else:
1068 else:
1075 req.write(tmpl("notfound", repo=virtual))
1069 req.write(tmpl("notfound", repo=virtual))
1076 else:
1070 else:
1077 if req.form.has_key('static'):
1071 if req.form.has_key('static'):
1078 static = os.path.join(templatepath(), "static")
1072 static = os.path.join(templater.templatepath(), "static")
1079 fname = req.form['static'][0]
1073 fname = req.form['static'][0]
1080 req.write(staticfile(static, fname)
1074 req.write(staticfile(static, fname)
1081 or tmpl("error", error="%r not found" % fname))
1075 or tmpl("error", error="%r not found" % fname))
1082 else:
1076 else:
1083 req.write(tmpl("index", entries=entries))
1077 req.write(tmpl("index", entries=entries))
@@ -1,107 +1,113 b''
1 from demandload import demandload
1 from demandload import demandload
2 demandload(globals(), "cgi os re time urllib util")
2 demandload(globals(), "cgi os re time urllib util")
3
3
4 class templater(object):
4 class templater(object):
5 def __init__(self, mapfile, filters={}, defaults={}):
5 def __init__(self, mapfile, filters={}, defaults={}):
6 self.cache = {}
6 self.cache = {}
7 self.map = {}
7 self.map = {}
8 self.base = os.path.dirname(mapfile)
8 self.base = os.path.dirname(mapfile)
9 self.filters = filters
9 self.filters = filters
10 self.defaults = defaults
10 self.defaults = defaults
11
11
12 for l in file(mapfile):
12 for l in file(mapfile):
13 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
13 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
14 if m:
14 if m:
15 self.cache[m.group(1)] = m.group(2)
15 self.cache[m.group(1)] = m.group(2)
16 else:
16 else:
17 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
17 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
18 if m:
18 if m:
19 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
19 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
20 else:
20 else:
21 raise LookupError(_("unknown map entry '%s'") % l)
21 raise LookupError(_("unknown map entry '%s'") % l)
22
22
23 def __call__(self, t, **map):
23 def __call__(self, t, **map):
24 m = self.defaults.copy()
24 m = self.defaults.copy()
25 m.update(map)
25 m.update(map)
26 try:
26 try:
27 tmpl = self.cache[t]
27 tmpl = self.cache[t]
28 except KeyError:
28 except KeyError:
29 tmpl = self.cache[t] = file(self.map[t]).read()
29 tmpl = self.cache[t] = file(self.map[t]).read()
30 return self.template(tmpl, self.filters, **m)
30 return self.template(tmpl, self.filters, **m)
31
31
32 def template(self, tmpl, filters={}, **map):
32 def template(self, tmpl, filters={}, **map):
33 while tmpl:
33 while tmpl:
34 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
34 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
35 if m:
35 if m:
36 yield tmpl[:m.start(0)]
36 yield tmpl[:m.start(0)]
37 v = map.get(m.group(1), "")
37 v = map.get(m.group(1), "")
38 v = callable(v) and v(**map) or v
38 v = callable(v) and v(**map) or v
39
39
40 format = m.group(2)
40 format = m.group(2)
41 fl = m.group(4)
41 fl = m.group(4)
42
42
43 if format:
43 if format:
44 q = v.__iter__
44 q = v.__iter__
45 for i in q():
45 for i in q():
46 lm = map.copy()
46 lm = map.copy()
47 lm.update(i)
47 lm.update(i)
48 yield self(format[1:], **lm)
48 yield self(format[1:], **lm)
49
49
50 v = ""
50 v = ""
51
51
52 elif fl:
52 elif fl:
53 for f in fl.split("|")[1:]:
53 for f in fl.split("|")[1:]:
54 v = filters[f](v)
54 v = filters[f](v)
55
55
56 yield v
56 yield v
57 tmpl = tmpl[m.end(0):]
57 tmpl = tmpl[m.end(0):]
58 else:
58 else:
59 yield tmpl
59 yield tmpl
60 return
60 return
61
61
62 def age(x):
62 def age(x):
63 def plural(t, c):
63 def plural(t, c):
64 if c == 1:
64 if c == 1:
65 return t
65 return t
66 return t + "s"
66 return t + "s"
67 def fmt(t, c):
67 def fmt(t, c):
68 return "%d %s" % (c, plural(t, c))
68 return "%d %s" % (c, plural(t, c))
69
69
70 now = time.time()
70 now = time.time()
71 then = x[0]
71 then = x[0]
72 delta = max(1, int(now - then))
72 delta = max(1, int(now - then))
73
73
74 scales = [["second", 1],
74 scales = [["second", 1],
75 ["minute", 60],
75 ["minute", 60],
76 ["hour", 3600],
76 ["hour", 3600],
77 ["day", 3600 * 24],
77 ["day", 3600 * 24],
78 ["week", 3600 * 24 * 7],
78 ["week", 3600 * 24 * 7],
79 ["month", 3600 * 24 * 30],
79 ["month", 3600 * 24 * 30],
80 ["year", 3600 * 24 * 365]]
80 ["year", 3600 * 24 * 365]]
81
81
82 scales.reverse()
82 scales.reverse()
83
83
84 for t, s in scales:
84 for t, s in scales:
85 n = delta / s
85 n = delta / s
86 if n >= 2 or s == 1:
86 if n >= 2 or s == 1:
87 return fmt(t, n)
87 return fmt(t, n)
88
88
89 def nl2br(text):
89 def nl2br(text):
90 return text.replace('\n', '<br/>\n')
90 return text.replace('\n', '<br/>\n')
91
91
92 def obfuscate(text):
92 def obfuscate(text):
93 return ''.join(['&#%d;' % ord(c) for c in text])
93 return ''.join(['&#%d;' % ord(c) for c in text])
94
94
95 common_filters = {
95 common_filters = {
96 "escape": lambda x: cgi.escape(x, True),
96 "escape": lambda x: cgi.escape(x, True),
97 "urlescape": urllib.quote,
97 "urlescape": urllib.quote,
98 "strip": lambda x: x.strip(),
98 "strip": lambda x: x.strip(),
99 "age": age,
99 "age": age,
100 "date": lambda x: util.datestr(x),
100 "date": lambda x: util.datestr(x),
101 "addbreaks": nl2br,
101 "addbreaks": nl2br,
102 "obfuscate": obfuscate,
102 "obfuscate": obfuscate,
103 "short": (lambda x: x[:12]),
103 "short": (lambda x: x[:12]),
104 "firstline": (lambda x: x.splitlines(1)[0]),
104 "firstline": (lambda x: x.splitlines(1)[0]),
105 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
105 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
106 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
106 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
107 }
107 }
108
109 def templatepath():
110 for f in "templates", "../templates":
111 p = os.path.join(os.path.dirname(__file__), f)
112 if os.path.isdir(p):
113 return os.path.normpath(p)
General Comments 0
You need to be logged in to leave comments. Login now