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