##// END OF EJS Templates
indentation cleanup
Peter Arrenbrecht -
r8389:4b798b10 default
parent child Browse files
Show More
@@ -1,326 +1,326 b''
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
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, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2, incorporated herein by reference.
7 # GNU General Public License version 2, incorporated herein by reference.
8
8
9 import os, time
9 import os, time
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial import ui, hg, util, templater
11 from mercurial import ui, hg, util, templater
12 from mercurial import error, encoding
12 from mercurial import error, encoding
13 from common import ErrorResponse, get_mtime, staticfile, paritygen,\
13 from common import ErrorResponse, get_mtime, staticfile, paritygen,\
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from hgweb_mod import hgweb
15 from hgweb_mod import hgweb
16 from request import wsgirequest
16 from request import wsgirequest
17 import webutil
17 import webutil
18
18
19 def cleannames(items):
19 def cleannames(items):
20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
21
21
22 class hgwebdir(object):
22 class hgwebdir(object):
23 refreshinterval = 20
23 refreshinterval = 20
24
24
25 def __init__(self, conf, baseui=None):
25 def __init__(self, conf, baseui=None):
26 self.conf = conf
26 self.conf = conf
27 self.baseui = baseui
27 self.baseui = baseui
28 self.lastrefresh = 0
28 self.lastrefresh = 0
29 self.refresh()
29 self.refresh()
30
30
31 def refresh(self):
31 def refresh(self):
32 if self.lastrefresh + self.refreshinterval > time.time():
32 if self.lastrefresh + self.refreshinterval > time.time():
33 return
33 return
34
34
35 if self.baseui:
35 if self.baseui:
36 self.ui = self.baseui.copy()
36 self.ui = self.baseui.copy()
37 else:
37 else:
38 self.ui = ui.ui()
38 self.ui = ui.ui()
39 self.ui.setconfig('ui', 'report_untrusted', 'off')
39 self.ui.setconfig('ui', 'report_untrusted', 'off')
40 self.ui.setconfig('ui', 'interactive', 'off')
40 self.ui.setconfig('ui', 'interactive', 'off')
41
41
42 if isinstance(self.conf, (list, tuple)):
42 if isinstance(self.conf, (list, tuple)):
43 self.repos = cleannames(conf)
43 self.repos = cleannames(conf)
44 elif isinstance(self.conf, dict):
44 elif isinstance(self.conf, dict):
45 self.repos = sorted(cleannames(self.conf.items()))
45 self.repos = sorted(cleannames(self.conf.items()))
46 else:
46 else:
47 self.ui.readconfig(self.conf, remap={'paths': 'hgweb-paths'}, trust=True)
47 self.ui.readconfig(self.conf, remap={'paths': 'hgweb-paths'}, trust=True)
48 self.repos = []
48 self.repos = []
49
49
50 self.motd = self.ui.config('web', 'motd')
50 self.motd = self.ui.config('web', 'motd')
51 self.style = self.ui.config('web', 'style', 'paper')
51 self.style = self.ui.config('web', 'style', 'paper')
52 self.stripecount = self.ui.config('web', 'stripes', 1)
52 self.stripecount = self.ui.config('web', 'stripes', 1)
53 if self.stripecount:
53 if self.stripecount:
54 self.stripecount = int(self.stripecount)
54 self.stripecount = int(self.stripecount)
55 self._baseurl = self.ui.config('web', 'baseurl')
55 self._baseurl = self.ui.config('web', 'baseurl')
56
56
57 if self.repos:
57 if self.repos:
58 return
58 return
59
59
60 for prefix, root in cleannames(self.ui.configitems('hgweb-paths')):
60 for prefix, root in cleannames(self.ui.configitems('hgweb-paths')):
61 roothead, roottail = os.path.split(root)
61 roothead, roottail = os.path.split(root)
62 # "foo = /bar/*" makes every subrepo of /bar/ to be
62 # "foo = /bar/*" makes every subrepo of /bar/ to be
63 # mounted as foo/subrepo
63 # mounted as foo/subrepo
64 # and "foo = /bar/**" also recurses into the subdirectories,
64 # and "foo = /bar/**" also recurses into the subdirectories,
65 # remember to use it without working dir.
65 # remember to use it without working dir.
66 try:
66 try:
67 recurse = {'*': False, '**': True}[roottail]
67 recurse = {'*': False, '**': True}[roottail]
68 except KeyError:
68 except KeyError:
69 self.repos.append((prefix, root))
69 self.repos.append((prefix, root))
70 continue
70 continue
71 roothead = os.path.normpath(roothead)
71 roothead = os.path.normpath(roothead)
72 for path in util.walkrepos(roothead, followsym=True,
72 for path in util.walkrepos(roothead, followsym=True,
73 recurse=recurse):
73 recurse=recurse):
74 path = os.path.normpath(path)
74 path = os.path.normpath(path)
75 name = util.pconvert(path[len(roothead):]).strip('/')
75 name = util.pconvert(path[len(roothead):]).strip('/')
76 if prefix:
76 if prefix:
77 name = prefix + '/' + name
77 name = prefix + '/' + name
78 self.repos.append((name, path))
78 self.repos.append((name, path))
79
79
80 for prefix, root in self.ui.configitems('collections'):
80 for prefix, root in self.ui.configitems('collections'):
81 for path in util.walkrepos(root, followsym=True):
81 for path in util.walkrepos(root, followsym=True):
82 repo = os.path.normpath(path)
82 repo = os.path.normpath(path)
83 name = repo
83 name = repo
84 if name.startswith(prefix):
84 if name.startswith(prefix):
85 name = name[len(prefix):]
85 name = name[len(prefix):]
86 self.repos.append((name.lstrip(os.sep), repo))
86 self.repos.append((name.lstrip(os.sep), repo))
87
87
88 self.repos.sort()
88 self.repos.sort()
89 self.lastrefresh = time.time()
89 self.lastrefresh = time.time()
90
90
91 def run(self):
91 def run(self):
92 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
92 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
93 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
93 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
94 import mercurial.hgweb.wsgicgi as wsgicgi
94 import mercurial.hgweb.wsgicgi as wsgicgi
95 wsgicgi.launch(self)
95 wsgicgi.launch(self)
96
96
97 def __call__(self, env, respond):
97 def __call__(self, env, respond):
98 req = wsgirequest(env, respond)
98 req = wsgirequest(env, respond)
99 return self.run_wsgi(req)
99 return self.run_wsgi(req)
100
100
101 def read_allowed(self, ui, req):
101 def read_allowed(self, ui, req):
102 """Check allow_read and deny_read config options of a repo's ui object
102 """Check allow_read and deny_read config options of a repo's ui object
103 to determine user permissions. By default, with neither option set (or
103 to determine user permissions. By default, with neither option set (or
104 both empty), allow all users to read the repo. There are two ways a
104 both empty), allow all users to read the repo. There are two ways a
105 user can be denied read access: (1) deny_read is not empty, and the
105 user can be denied read access: (1) deny_read is not empty, and the
106 user is unauthenticated or deny_read contains user (or *), and (2)
106 user is unauthenticated or deny_read contains user (or *), and (2)
107 allow_read is not empty and the user is not in allow_read. Return True
107 allow_read is not empty and the user is not in allow_read. Return True
108 if user is allowed to read the repo, else return False."""
108 if user is allowed to read the repo, else return False."""
109
109
110 user = req.env.get('REMOTE_USER')
110 user = req.env.get('REMOTE_USER')
111
111
112 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
112 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
113 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
113 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
114 return False
114 return False
115
115
116 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
116 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
117 # by default, allow reading if no allow_read option has been set
117 # by default, allow reading if no allow_read option has been set
118 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
118 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
119 return True
119 return True
120
120
121 return False
121 return False
122
122
123 def run_wsgi(self, req):
123 def run_wsgi(self, req):
124 try:
124 try:
125 try:
125 try:
126 self.refresh()
126 self.refresh()
127
127
128 virtual = req.env.get("PATH_INFO", "").strip('/')
128 virtual = req.env.get("PATH_INFO", "").strip('/')
129 tmpl = self.templater(req)
129 tmpl = self.templater(req)
130 ctype = tmpl('mimetype', encoding=encoding.encoding)
130 ctype = tmpl('mimetype', encoding=encoding.encoding)
131 ctype = templater.stringify(ctype)
131 ctype = templater.stringify(ctype)
132
132
133 # a static file
133 # a static file
134 if virtual.startswith('static/') or 'static' in req.form:
134 if virtual.startswith('static/') or 'static' in req.form:
135 if virtual.startswith('static/'):
135 if virtual.startswith('static/'):
136 fname = virtual[7:]
136 fname = virtual[7:]
137 else:
137 else:
138 fname = req.form['static'][0]
138 fname = req.form['static'][0]
139 static = templater.templatepath('static')
139 static = templater.templatepath('static')
140 return (staticfile(static, fname, req),)
140 return (staticfile(static, fname, req),)
141
141
142 # top-level index
142 # top-level index
143 elif not virtual:
143 elif not virtual:
144 req.respond(HTTP_OK, ctype)
144 req.respond(HTTP_OK, ctype)
145 return self.makeindex(req, tmpl)
145 return self.makeindex(req, tmpl)
146
146
147 # nested indexes and hgwebs
147 # nested indexes and hgwebs
148
148
149 repos = dict(self.repos)
149 repos = dict(self.repos)
150 while virtual:
150 while virtual:
151 real = repos.get(virtual)
151 real = repos.get(virtual)
152 if real:
152 if real:
153 req.env['REPO_NAME'] = virtual
153 req.env['REPO_NAME'] = virtual
154 try:
154 try:
155 repo = hg.repository(self.ui, real)
155 repo = hg.repository(self.ui, real)
156 return hgweb(repo).run_wsgi(req)
156 return hgweb(repo).run_wsgi(req)
157 except IOError, inst:
157 except IOError, inst:
158 msg = inst.strerror
158 msg = inst.strerror
159 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
159 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
160 except error.RepoError, inst:
160 except error.RepoError, inst:
161 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
161 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
162
162
163 # browse subdirectories
163 # browse subdirectories
164 subdir = virtual + '/'
164 subdir = virtual + '/'
165 if [r for r in repos if r.startswith(subdir)]:
165 if [r for r in repos if r.startswith(subdir)]:
166 req.respond(HTTP_OK, ctype)
166 req.respond(HTTP_OK, ctype)
167 return self.makeindex(req, tmpl, subdir)
167 return self.makeindex(req, tmpl, subdir)
168
168
169 up = virtual.rfind('/')
169 up = virtual.rfind('/')
170 if up < 0:
170 if up < 0:
171 break
171 break
172 virtual = virtual[:up]
172 virtual = virtual[:up]
173
173
174 # prefixes not found
174 # prefixes not found
175 req.respond(HTTP_NOT_FOUND, ctype)
175 req.respond(HTTP_NOT_FOUND, ctype)
176 return tmpl("notfound", repo=virtual)
176 return tmpl("notfound", repo=virtual)
177
177
178 except ErrorResponse, err:
178 except ErrorResponse, err:
179 req.respond(err, ctype)
179 req.respond(err, ctype)
180 return tmpl('error', error=err.message or '')
180 return tmpl('error', error=err.message or '')
181 finally:
181 finally:
182 tmpl = None
182 tmpl = None
183
183
184 def makeindex(self, req, tmpl, subdir=""):
184 def makeindex(self, req, tmpl, subdir=""):
185
185
186 def archivelist(ui, nodeid, url):
186 def archivelist(ui, nodeid, url):
187 allowed = ui.configlist("web", "allow_archive", untrusted=True)
187 allowed = ui.configlist("web", "allow_archive", untrusted=True)
188 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
188 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
189 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
189 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
190 untrusted=True):
190 untrusted=True):
191 yield {"type" : i[0], "extension": i[1],
191 yield {"type" : i[0], "extension": i[1],
192 "node": nodeid, "url": url}
192 "node": nodeid, "url": url}
193
193
194 sortdefault = 'name', False
194 sortdefault = 'name', False
195 def entries(sortcolumn="", descending=False, subdir="", **map):
195 def entries(sortcolumn="", descending=False, subdir="", **map):
196 rows = []
196 rows = []
197 parity = paritygen(self.stripecount)
197 parity = paritygen(self.stripecount)
198 for name, path in self.repos:
198 for name, path in self.repos:
199 if not name.startswith(subdir):
199 if not name.startswith(subdir):
200 continue
200 continue
201 name = name[len(subdir):]
201 name = name[len(subdir):]
202
202
203 u = self.ui.copy()
203 u = self.ui.copy()
204 try:
204 try:
205 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
205 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
206 except Exception, e:
206 except Exception, e:
207 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
207 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
208 continue
208 continue
209 def get(section, name, default=None):
209 def get(section, name, default=None):
210 return u.config(section, name, default, untrusted=True)
210 return u.config(section, name, default, untrusted=True)
211
211
212 if u.configbool("web", "hidden", untrusted=True):
212 if u.configbool("web", "hidden", untrusted=True):
213 continue
213 continue
214
214
215 if not self.read_allowed(u, req):
215 if not self.read_allowed(u, req):
216 continue
216 continue
217
217
218 parts = [name]
218 parts = [name]
219 if 'PATH_INFO' in req.env:
219 if 'PATH_INFO' in req.env:
220 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
220 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
221 if req.env['SCRIPT_NAME']:
221 if req.env['SCRIPT_NAME']:
222 parts.insert(0, req.env['SCRIPT_NAME'])
222 parts.insert(0, req.env['SCRIPT_NAME'])
223 url = ('/'.join(parts).replace("//", "/")) + '/'
223 url = ('/'.join(parts).replace("//", "/")) + '/'
224
224
225 # update time with local timezone
225 # update time with local timezone
226 try:
226 try:
227 d = (get_mtime(path), util.makedate()[1])
227 d = (get_mtime(path), util.makedate()[1])
228 except OSError:
228 except OSError:
229 continue
229 continue
230
230
231 contact = get_contact(get)
231 contact = get_contact(get)
232 description = get("web", "description", "")
232 description = get("web", "description", "")
233 name = get("web", "name", name)
233 name = get("web", "name", name)
234 row = dict(contact=contact or "unknown",
234 row = dict(contact=contact or "unknown",
235 contact_sort=contact.upper() or "unknown",
235 contact_sort=contact.upper() or "unknown",
236 name=name,
236 name=name,
237 name_sort=name,
237 name_sort=name,
238 url=url,
238 url=url,
239 description=description or "unknown",
239 description=description or "unknown",
240 description_sort=description.upper() or "unknown",
240 description_sort=description.upper() or "unknown",
241 lastchange=d,
241 lastchange=d,
242 lastchange_sort=d[1]-d[0],
242 lastchange_sort=d[1]-d[0],
243 archives=archivelist(u, "tip", url))
243 archives=archivelist(u, "tip", url))
244 if (not sortcolumn or (sortcolumn, descending) == sortdefault):
244 if (not sortcolumn or (sortcolumn, descending) == sortdefault):
245 # fast path for unsorted output
245 # fast path for unsorted output
246 row['parity'] = parity.next()
246 row['parity'] = parity.next()
247 yield row
247 yield row
248 else:
248 else:
249 rows.append((row["%s_sort" % sortcolumn], row))
249 rows.append((row["%s_sort" % sortcolumn], row))
250 if rows:
250 if rows:
251 rows.sort()
251 rows.sort()
252 if descending:
252 if descending:
253 rows.reverse()
253 rows.reverse()
254 for key, row in rows:
254 for key, row in rows:
255 row['parity'] = parity.next()
255 row['parity'] = parity.next()
256 yield row
256 yield row
257
257
258 self.refresh()
258 self.refresh()
259 sortable = ["name", "description", "contact", "lastchange"]
259 sortable = ["name", "description", "contact", "lastchange"]
260 sortcolumn, descending = sortdefault
260 sortcolumn, descending = sortdefault
261 if 'sort' in req.form:
261 if 'sort' in req.form:
262 sortcolumn = req.form['sort'][0]
262 sortcolumn = req.form['sort'][0]
263 descending = sortcolumn.startswith('-')
263 descending = sortcolumn.startswith('-')
264 if descending:
264 if descending:
265 sortcolumn = sortcolumn[1:]
265 sortcolumn = sortcolumn[1:]
266 if sortcolumn not in sortable:
266 if sortcolumn not in sortable:
267 sortcolumn = ""
267 sortcolumn = ""
268
268
269 sort = [("sort_%s" % column,
269 sort = [("sort_%s" % column,
270 "%s%s" % ((not descending and column == sortcolumn)
270 "%s%s" % ((not descending and column == sortcolumn)
271 and "-" or "", column))
271 and "-" or "", column))
272 for column in sortable]
272 for column in sortable]
273
273
274 self.refresh()
274 self.refresh()
275 if self._baseurl is not None:
275 if self._baseurl is not None:
276 req.env['SCRIPT_NAME'] = self._baseurl
276 req.env['SCRIPT_NAME'] = self._baseurl
277
277
278 return tmpl("index", entries=entries, subdir=subdir,
278 return tmpl("index", entries=entries, subdir=subdir,
279 sortcolumn=sortcolumn, descending=descending,
279 sortcolumn=sortcolumn, descending=descending,
280 **dict(sort))
280 **dict(sort))
281
281
282 def templater(self, req):
282 def templater(self, req):
283
283
284 def header(**map):
284 def header(**map):
285 yield tmpl('header', encoding=encoding.encoding, **map)
285 yield tmpl('header', encoding=encoding.encoding, **map)
286
286
287 def footer(**map):
287 def footer(**map):
288 yield tmpl("footer", **map)
288 yield tmpl("footer", **map)
289
289
290 def motd(**map):
290 def motd(**map):
291 if self.motd is not None:
291 if self.motd is not None:
292 yield self.motd
292 yield self.motd
293 else:
293 else:
294 yield config('web', 'motd', '')
294 yield config('web', 'motd', '')
295
295
296 def config(section, name, default=None, untrusted=True):
296 def config(section, name, default=None, untrusted=True):
297 return self.ui.config(section, name, default, untrusted)
297 return self.ui.config(section, name, default, untrusted)
298
298
299 if self._baseurl is not None:
299 if self._baseurl is not None:
300 req.env['SCRIPT_NAME'] = self._baseurl
300 req.env['SCRIPT_NAME'] = self._baseurl
301
301
302 url = req.env.get('SCRIPT_NAME', '')
302 url = req.env.get('SCRIPT_NAME', '')
303 if not url.endswith('/'):
303 if not url.endswith('/'):
304 url += '/'
304 url += '/'
305
305
306 vars = {}
306 vars = {}
307 style = self.style
307 style = self.style
308 if 'style' in req.form:
308 if 'style' in req.form:
309 vars['style'] = style = req.form['style'][0]
309 vars['style'] = style = req.form['style'][0]
310 start = url[-1] == '?' and '&' or '?'
310 start = url[-1] == '?' and '&' or '?'
311 sessionvars = webutil.sessionvars(vars, start)
311 sessionvars = webutil.sessionvars(vars, start)
312
312
313 staticurl = config('web', 'staticurl') or url + 'static/'
313 staticurl = config('web', 'staticurl') or url + 'static/'
314 if not staticurl.endswith('/'):
314 if not staticurl.endswith('/'):
315 staticurl += '/'
315 staticurl += '/'
316
316
317 style = 'style' in req.form and req.form['style'][0] or self.style
317 style = 'style' in req.form and req.form['style'][0] or self.style
318 mapfile = templater.stylemap(style)
318 mapfile = templater.stylemap(style)
319 tmpl = templater.templater(mapfile,
319 tmpl = templater.templater(mapfile,
320 defaults={"header": header,
320 defaults={"header": header,
321 "footer": footer,
321 "footer": footer,
322 "motd": motd,
322 "motd": motd,
323 "url": url,
323 "url": url,
324 "staticurl": staticurl,
324 "staticurl": staticurl,
325 "sessionvars": sessionvars})
325 "sessionvars": sessionvars})
326 return tmpl
326 return tmpl
@@ -1,209 +1,209 b''
1 # template-filters.py - common template expansion filters
1 # template-filters.py - common template expansion filters
2 #
2 #
3 # Copyright 2005-2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2008 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 import cgi, re, os, time, urllib, textwrap
8 import cgi, re, os, time, urllib, textwrap
9 import util, templater, encoding
9 import util, templater, encoding
10
10
11 def stringify(thing):
11 def stringify(thing):
12 '''turn nested template iterator into string.'''
12 '''turn nested template iterator into string.'''
13 if hasattr(thing, '__iter__') and not isinstance(thing, str):
13 if hasattr(thing, '__iter__') and not isinstance(thing, str):
14 return "".join([stringify(t) for t in thing if t is not None])
14 return "".join([stringify(t) for t in thing if t is not None])
15 return str(thing)
15 return str(thing)
16
16
17 agescales = [("second", 1),
17 agescales = [("second", 1),
18 ("minute", 60),
18 ("minute", 60),
19 ("hour", 3600),
19 ("hour", 3600),
20 ("day", 3600 * 24),
20 ("day", 3600 * 24),
21 ("week", 3600 * 24 * 7),
21 ("week", 3600 * 24 * 7),
22 ("month", 3600 * 24 * 30),
22 ("month", 3600 * 24 * 30),
23 ("year", 3600 * 24 * 365)]
23 ("year", 3600 * 24 * 365)]
24
24
25 agescales.reverse()
25 agescales.reverse()
26
26
27 def age(date):
27 def age(date):
28 '''turn a (timestamp, tzoff) tuple into an age string.'''
28 '''turn a (timestamp, tzoff) tuple into an age string.'''
29
29
30 def plural(t, c):
30 def plural(t, c):
31 if c == 1:
31 if c == 1:
32 return t
32 return t
33 return t + "s"
33 return t + "s"
34 def fmt(t, c):
34 def fmt(t, c):
35 return "%d %s" % (c, plural(t, c))
35 return "%d %s" % (c, plural(t, c))
36
36
37 now = time.time()
37 now = time.time()
38 then = date[0]
38 then = date[0]
39 if then > now:
39 if then > now:
40 return 'in the future'
40 return 'in the future'
41
41
42 delta = max(1, int(now - then))
42 delta = max(1, int(now - then))
43 for t, s in agescales:
43 for t, s in agescales:
44 n = delta / s
44 n = delta / s
45 if n >= 2 or s == 1:
45 if n >= 2 or s == 1:
46 return fmt(t, n)
46 return fmt(t, n)
47
47
48 para_re = None
48 para_re = None
49 space_re = None
49 space_re = None
50
50
51 def fill(text, width):
51 def fill(text, width):
52 '''fill many paragraphs.'''
52 '''fill many paragraphs.'''
53 global para_re, space_re
53 global para_re, space_re
54 if para_re is None:
54 if para_re is None:
55 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
55 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
56 space_re = re.compile(r' +')
56 space_re = re.compile(r' +')
57
57
58 def findparas():
58 def findparas():
59 start = 0
59 start = 0
60 while True:
60 while True:
61 m = para_re.search(text, start)
61 m = para_re.search(text, start)
62 if not m:
62 if not m:
63 w = len(text)
63 w = len(text)
64 while w > start and text[w-1].isspace(): w -= 1
64 while w > start and text[w-1].isspace(): w -= 1
65 yield text[start:w], text[w:]
65 yield text[start:w], text[w:]
66 break
66 break
67 yield text[start:m.start(0)], m.group(1)
67 yield text[start:m.start(0)], m.group(1)
68 start = m.end(1)
68 start = m.end(1)
69
69
70 return "".join([space_re.sub(' ', textwrap.fill(para, width)) + rest
70 return "".join([space_re.sub(' ', textwrap.fill(para, width)) + rest
71 for para, rest in findparas()])
71 for para, rest in findparas()])
72
72
73 def firstline(text):
73 def firstline(text):
74 '''return the first line of text'''
74 '''return the first line of text'''
75 try:
75 try:
76 return text.splitlines(1)[0].rstrip('\r\n')
76 return text.splitlines(1)[0].rstrip('\r\n')
77 except IndexError:
77 except IndexError:
78 return ''
78 return ''
79
79
80 def nl2br(text):
80 def nl2br(text):
81 '''replace raw newlines with xhtml line breaks.'''
81 '''replace raw newlines with xhtml line breaks.'''
82 return text.replace('\n', '<br/>\n')
82 return text.replace('\n', '<br/>\n')
83
83
84 def obfuscate(text):
84 def obfuscate(text):
85 text = unicode(text, encoding.encoding, 'replace')
85 text = unicode(text, encoding.encoding, 'replace')
86 return ''.join(['&#%d;' % ord(c) for c in text])
86 return ''.join(['&#%d;' % ord(c) for c in text])
87
87
88 def domain(author):
88 def domain(author):
89 '''get domain of author, or empty string if none.'''
89 '''get domain of author, or empty string if none.'''
90 f = author.find('@')
90 f = author.find('@')
91 if f == -1: return ''
91 if f == -1: return ''
92 author = author[f+1:]
92 author = author[f+1:]
93 f = author.find('>')
93 f = author.find('>')
94 if f >= 0: author = author[:f]
94 if f >= 0: author = author[:f]
95 return author
95 return author
96
96
97 def person(author):
97 def person(author):
98 '''get name of author, or else username.'''
98 '''get name of author, or else username.'''
99 f = author.find('<')
99 f = author.find('<')
100 if f == -1: return util.shortuser(author)
100 if f == -1: return util.shortuser(author)
101 return author[:f].rstrip()
101 return author[:f].rstrip()
102
102
103 def indent(text, prefix):
103 def indent(text, prefix):
104 '''indent each non-empty line of text after first with prefix.'''
104 '''indent each non-empty line of text after first with prefix.'''
105 lines = text.splitlines()
105 lines = text.splitlines()
106 num_lines = len(lines)
106 num_lines = len(lines)
107 def indenter():
107 def indenter():
108 for i in xrange(num_lines):
108 for i in xrange(num_lines):
109 l = lines[i]
109 l = lines[i]
110 if i and l.strip():
110 if i and l.strip():
111 yield prefix
111 yield prefix
112 yield l
112 yield l
113 if i < num_lines - 1 or text.endswith('\n'):
113 if i < num_lines - 1 or text.endswith('\n'):
114 yield '\n'
114 yield '\n'
115 return "".join(indenter())
115 return "".join(indenter())
116
116
117 def permissions(flags):
117 def permissions(flags):
118 if "l" in flags:
118 if "l" in flags:
119 return "lrwxrwxrwx"
119 return "lrwxrwxrwx"
120 if "x" in flags:
120 if "x" in flags:
121 return "-rwxr-xr-x"
121 return "-rwxr-xr-x"
122 return "-rw-r--r--"
122 return "-rw-r--r--"
123
123
124 def xmlescape(text):
124 def xmlescape(text):
125 text = (text
125 text = (text
126 .replace('&', '&amp;')
126 .replace('&', '&amp;')
127 .replace('<', '&lt;')
127 .replace('<', '&lt;')
128 .replace('>', '&gt;')
128 .replace('>', '&gt;')
129 .replace('"', '&quot;')
129 .replace('"', '&quot;')
130 .replace("'", '&#39;')) # &apos; invalid in HTML
130 .replace("'", '&#39;')) # &apos; invalid in HTML
131 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
131 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
132
132
133 _escapes = [
133 _escapes = [
134 ('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'), ('\n', '\\n'),
134 ('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'), ('\n', '\\n'),
135 ('\r', '\\r'), ('\f', '\\f'), ('\b', '\\b'),
135 ('\r', '\\r'), ('\f', '\\f'), ('\b', '\\b'),
136 ]
136 ]
137
137
138 def jsonescape(s):
138 def jsonescape(s):
139 for k, v in _escapes:
139 for k, v in _escapes:
140 s = s.replace(k, v)
140 s = s.replace(k, v)
141 return s
141 return s
142
142
143 def json(obj):
143 def json(obj):
144 if obj is None or obj is False or obj is True:
144 if obj is None or obj is False or obj is True:
145 return {None: 'null', False: 'false', True: 'true'}[obj]
145 return {None: 'null', False: 'false', True: 'true'}[obj]
146 elif isinstance(obj, int) or isinstance(obj, float):
146 elif isinstance(obj, int) or isinstance(obj, float):
147 return str(obj)
147 return str(obj)
148 elif isinstance(obj, str):
148 elif isinstance(obj, str):
149 return '"%s"' % jsonescape(obj)
149 return '"%s"' % jsonescape(obj)
150 elif isinstance(obj, unicode):
150 elif isinstance(obj, unicode):
151 return json(obj.encode('utf-8'))
151 return json(obj.encode('utf-8'))
152 elif hasattr(obj, 'keys'):
152 elif hasattr(obj, 'keys'):
153 out = []
153 out = []
154 for k, v in obj.iteritems():
154 for k, v in obj.iteritems():
155 s = '%s: %s' % (json(k), json(v))
155 s = '%s: %s' % (json(k), json(v))
156 out.append(s)
156 out.append(s)
157 return '{' + ', '.join(out) + '}'
157 return '{' + ', '.join(out) + '}'
158 elif hasattr(obj, '__iter__'):
158 elif hasattr(obj, '__iter__'):
159 out = []
159 out = []
160 for i in obj:
160 for i in obj:
161 out.append(json(i))
161 out.append(json(i))
162 return '[' + ', '.join(out) + ']'
162 return '[' + ', '.join(out) + ']'
163 else:
163 else:
164 raise TypeError('cannot encode type %s' % obj.__class__.__name__)
164 raise TypeError('cannot encode type %s' % obj.__class__.__name__)
165
165
166 def stripdir(text):
166 def stripdir(text):
167 '''Treat the text as path and strip a directory level, if possible.'''
167 '''Treat the text as path and strip a directory level, if possible.'''
168 dir = os.path.dirname(text)
168 dir = os.path.dirname(text)
169 if dir == "":
169 if dir == "":
170 return os.path.basename(text)
170 return os.path.basename(text)
171 else:
171 else:
172 return dir
172 return dir
173
173
174 def nonempty(str):
174 def nonempty(str):
175 return str or "(none)"
175 return str or "(none)"
176
176
177 filters = {
177 filters = {
178 "addbreaks": nl2br,
178 "addbreaks": nl2br,
179 "basename": os.path.basename,
179 "basename": os.path.basename,
180 "stripdir": stripdir,
180 "stripdir": stripdir,
181 "age": age,
181 "age": age,
182 "date": lambda x: util.datestr(x),
182 "date": lambda x: util.datestr(x),
183 "domain": domain,
183 "domain": domain,
184 "email": util.email,
184 "email": util.email,
185 "escape": lambda x: cgi.escape(x, True),
185 "escape": lambda x: cgi.escape(x, True),
186 "fill68": lambda x: fill(x, width=68),
186 "fill68": lambda x: fill(x, width=68),
187 "fill76": lambda x: fill(x, width=76),
187 "fill76": lambda x: fill(x, width=76),
188 "firstline": firstline,
188 "firstline": firstline,
189 "tabindent": lambda x: indent(x, '\t'),
189 "tabindent": lambda x: indent(x, '\t'),
190 "hgdate": lambda x: "%d %d" % x,
190 "hgdate": lambda x: "%d %d" % x,
191 "isodate": lambda x: util.datestr(x, '%Y-%m-%d %H:%M %1%2'),
191 "isodate": lambda x: util.datestr(x, '%Y-%m-%d %H:%M %1%2'),
192 "isodatesec": lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2'),
192 "isodatesec": lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2'),
193 "json": json,
193 "json": json,
194 "jsonescape": jsonescape,
194 "jsonescape": jsonescape,
195 "nonempty": nonempty,
195 "nonempty": nonempty,
196 "obfuscate": obfuscate,
196 "obfuscate": obfuscate,
197 "permissions": permissions,
197 "permissions": permissions,
198 "person": person,
198 "person": person,
199 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2"),
199 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2"),
200 "rfc3339date": lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S%1:%2"),
200 "rfc3339date": lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S%1:%2"),
201 "short": lambda x: x[:12],
201 "short": lambda x: x[:12],
202 "shortdate": util.shortdate,
202 "shortdate": util.shortdate,
203 "stringify": stringify,
203 "stringify": stringify,
204 "strip": lambda x: x.strip(),
204 "strip": lambda x: x.strip(),
205 "urlescape": lambda x: urllib.quote(x),
205 "urlescape": lambda x: urllib.quote(x),
206 "user": lambda x: util.shortuser(x),
206 "user": lambda x: util.shortuser(x),
207 "stringescape": lambda x: x.encode('string_escape'),
207 "stringescape": lambda x: x.encode('string_escape'),
208 "xmlescape": xmlescape,
208 "xmlescape": xmlescape,
209 }
209 }
General Comments 0
You need to be logged in to leave comments. Login now