##// END OF EJS Templates
hgwebdir: refactor inner loop
Dirkjan Ochtman -
r5603:74f65f44 default
parent child Browse files
Show More
@@ -1,268 +1,277 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
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, mimetools, cStringIO
9 import os, mimetools, cStringIO
10 from mercurial.i18n import gettext as _
10 from mercurial.i18n import gettext as _
11 from mercurial import ui, hg, util, templater
11 from mercurial import ui, hg, util, templater
12 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
12 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
13 from hgweb_mod import hgweb
13 from hgweb_mod import hgweb
14 from request import wsgirequest
14 from request import wsgirequest
15
15
16 # This is a stopgap
16 # This is a stopgap
17 class hgwebdir(object):
17 class hgwebdir(object):
18 def __init__(self, config, parentui=None):
18 def __init__(self, config, parentui=None):
19 def cleannames(items):
19 def cleannames(items):
20 return [(util.pconvert(name).strip('/'), path)
20 return [(util.pconvert(name).strip('/'), path)
21 for name, path in items]
21 for name, path in items]
22
22
23 self.parentui = parentui or ui.ui(report_untrusted=False,
23 self.parentui = parentui or ui.ui(report_untrusted=False,
24 interactive = False)
24 interactive = False)
25 self.motd = None
25 self.motd = None
26 self.style = None
26 self.style = None
27 self.stripecount = None
27 self.stripecount = None
28 self.repos_sorted = ('name', False)
28 self.repos_sorted = ('name', False)
29 if isinstance(config, (list, tuple)):
29 if isinstance(config, (list, tuple)):
30 self.repos = cleannames(config)
30 self.repos = cleannames(config)
31 self.repos_sorted = ('', False)
31 self.repos_sorted = ('', False)
32 elif isinstance(config, dict):
32 elif isinstance(config, dict):
33 self.repos = cleannames(config.items())
33 self.repos = cleannames(config.items())
34 self.repos.sort()
34 self.repos.sort()
35 else:
35 else:
36 if isinstance(config, util.configparser):
36 if isinstance(config, util.configparser):
37 cp = config
37 cp = config
38 else:
38 else:
39 cp = util.configparser()
39 cp = util.configparser()
40 cp.read(config)
40 cp.read(config)
41 self.repos = []
41 self.repos = []
42 if cp.has_section('web'):
42 if cp.has_section('web'):
43 if cp.has_option('web', 'motd'):
43 if cp.has_option('web', 'motd'):
44 self.motd = cp.get('web', 'motd')
44 self.motd = cp.get('web', 'motd')
45 if cp.has_option('web', 'style'):
45 if cp.has_option('web', 'style'):
46 self.style = cp.get('web', 'style')
46 self.style = cp.get('web', 'style')
47 if cp.has_option('web', 'stripes'):
47 if cp.has_option('web', 'stripes'):
48 self.stripecount = int(cp.get('web', 'stripes'))
48 self.stripecount = int(cp.get('web', 'stripes'))
49 if cp.has_section('paths'):
49 if cp.has_section('paths'):
50 self.repos.extend(cleannames(cp.items('paths')))
50 self.repos.extend(cleannames(cp.items('paths')))
51 if cp.has_section('collections'):
51 if cp.has_section('collections'):
52 for prefix, root in cp.items('collections'):
52 for prefix, root in cp.items('collections'):
53 for path in util.walkrepos(root):
53 for path in util.walkrepos(root):
54 repo = os.path.normpath(path)
54 repo = os.path.normpath(path)
55 name = repo
55 name = repo
56 if name.startswith(prefix):
56 if name.startswith(prefix):
57 name = name[len(prefix):]
57 name = name[len(prefix):]
58 self.repos.append((name.lstrip(os.sep), repo))
58 self.repos.append((name.lstrip(os.sep), repo))
59 self.repos.sort()
59 self.repos.sort()
60
60
61 def run(self):
61 def run(self):
62 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
62 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
63 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
63 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
64 import mercurial.hgweb.wsgicgi as wsgicgi
64 import mercurial.hgweb.wsgicgi as wsgicgi
65 wsgicgi.launch(self)
65 wsgicgi.launch(self)
66
66
67 def __call__(self, env, respond):
67 def __call__(self, env, respond):
68 req = wsgirequest(env, respond)
68 req = wsgirequest(env, respond)
69 self.run_wsgi(req)
69 self.run_wsgi(req)
70 return req
70 return req
71
71
72 def run_wsgi(self, req):
72 def run_wsgi(self, req):
73
73
74 try:
74 try:
75 try:
75 try:
76
76 virtual = req.env.get("PATH_INFO", "").strip('/')
77 virtual = req.env.get("PATH_INFO", "").strip('/')
77 if virtual.startswith('static/'):
78
79 # a static file
80 if virtual.startswith('static/') or 'static' in req.form:
78 static = os.path.join(templater.templatepath(), 'static')
81 static = os.path.join(templater.templatepath(), 'static')
79 fname = virtual[7:]
82 if virtual.startswith('static/'):
83 fname = virtual[7:]
84 else:
85 fname = req.form['static'][0]
80 req.write(staticfile(static, fname, req))
86 req.write(staticfile(static, fname, req))
81 elif virtual:
87 return
82 repos = dict(self.repos)
88
83 while virtual:
89 # top-level index
84 real = repos.get(virtual)
90 elif not virtual:
85 if real:
91 tmpl = self.templater(req)
86 req.env['REPO_NAME'] = virtual
92 self.makeindex(req, tmpl)
87 try:
93 return
88 repo = hg.repository(self.parentui, real)
89 hgweb(repo).run_wsgi(req)
90 return
91 except IOError, inst:
92 raise ErrorResponse(500, inst.strerror)
93 except hg.RepoError, inst:
94 raise ErrorResponse(500, str(inst))
95
94
96 # browse subdirectories
95 # nested indexes and hgwebs
97 subdir = virtual + '/'
96 repos = dict(self.repos)
98 if [r for r in repos if r.startswith(subdir)]:
97 while virtual:
99 tmpl = self.templater(req)
98 real = repos.get(virtual)
100 self.makeindex(req, tmpl, subdir)
99 if real:
100 req.env['REPO_NAME'] = virtual
101 try:
102 repo = hg.repository(self.parentui, real)
103 hgweb(repo).run_wsgi(req)
101 return
104 return
102
105 except IOError, inst:
103 up = virtual.rfind('/')
106 raise ErrorResponse(500, inst.strerror)
104 if up < 0:
107 except hg.RepoError, inst:
105 break
108 raise ErrorResponse(500, str(inst))
106 virtual = virtual[:up]
107
109
108 tmpl = self.templater(req)
110 # browse subdirectories
109 req.respond(404, tmpl("notfound", repo=virtual))
111 subdir = virtual + '/'
110 else:
112 if [r for r in repos if r.startswith(subdir)]:
111 if req.form.has_key('static'):
112 static = os.path.join(templater.templatepath(), "static")
113 fname = req.form['static'][0]
114 req.write(staticfile(static, fname, req))
115 else:
116 tmpl = self.templater(req)
113 tmpl = self.templater(req)
117 self.makeindex(req, tmpl)
114 self.makeindex(req, tmpl, subdir)
115 return
116
117 up = virtual.rfind('/')
118 if up < 0:
119 break
120 virtual = virtual[:up]
121
122 # prefixes not found
123 tmpl = self.templater(req)
124 req.respond(404, tmpl("notfound", repo=virtual))
125
118 except ErrorResponse, err:
126 except ErrorResponse, err:
127 tmpl = self.templater(req)
119 req.respond(err.code, tmpl('error', error=err.message or ''))
128 req.respond(err.code, tmpl('error', error=err.message or ''))
120 finally:
129 finally:
121 tmpl = None
130 tmpl = None
122
131
123 def makeindex(self, req, tmpl, subdir=""):
132 def makeindex(self, req, tmpl, subdir=""):
124
133
125 def archivelist(ui, nodeid, url):
134 def archivelist(ui, nodeid, url):
126 allowed = ui.configlist("web", "allow_archive", untrusted=True)
135 allowed = ui.configlist("web", "allow_archive", untrusted=True)
127 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
136 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
128 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
137 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
129 untrusted=True):
138 untrusted=True):
130 yield {"type" : i[0], "extension": i[1],
139 yield {"type" : i[0], "extension": i[1],
131 "node": nodeid, "url": url}
140 "node": nodeid, "url": url}
132
141
133 def entries(sortcolumn="", descending=False, subdir="", **map):
142 def entries(sortcolumn="", descending=False, subdir="", **map):
134 def sessionvars(**map):
143 def sessionvars(**map):
135 fields = []
144 fields = []
136 if req.form.has_key('style'):
145 if req.form.has_key('style'):
137 style = req.form['style'][0]
146 style = req.form['style'][0]
138 if style != get('web', 'style', ''):
147 if style != get('web', 'style', ''):
139 fields.append(('style', style))
148 fields.append(('style', style))
140
149
141 separator = url[-1] == '?' and ';' or '?'
150 separator = url[-1] == '?' and ';' or '?'
142 for name, value in fields:
151 for name, value in fields:
143 yield dict(name=name, value=value, separator=separator)
152 yield dict(name=name, value=value, separator=separator)
144 separator = ';'
153 separator = ';'
145
154
146 rows = []
155 rows = []
147 parity = paritygen(self.stripecount)
156 parity = paritygen(self.stripecount)
148 for name, path in self.repos:
157 for name, path in self.repos:
149 if not name.startswith(subdir):
158 if not name.startswith(subdir):
150 continue
159 continue
151 name = name[len(subdir):]
160 name = name[len(subdir):]
152
161
153 u = ui.ui(parentui=self.parentui)
162 u = ui.ui(parentui=self.parentui)
154 try:
163 try:
155 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
164 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
156 except Exception, e:
165 except Exception, e:
157 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
166 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
158 continue
167 continue
159 def get(section, name, default=None):
168 def get(section, name, default=None):
160 return u.config(section, name, default, untrusted=True)
169 return u.config(section, name, default, untrusted=True)
161
170
162 if u.configbool("web", "hidden", untrusted=True):
171 if u.configbool("web", "hidden", untrusted=True):
163 continue
172 continue
164
173
165 parts = [req.env['PATH_INFO'], name]
174 parts = [req.env['PATH_INFO'], name]
166 if req.env['SCRIPT_NAME']:
175 if req.env['SCRIPT_NAME']:
167 parts.insert(0, req.env['SCRIPT_NAME'])
176 parts.insert(0, req.env['SCRIPT_NAME'])
168 url = ('/'.join(parts).replace("//", "/")) + '/'
177 url = ('/'.join(parts).replace("//", "/")) + '/'
169
178
170 # update time with local timezone
179 # update time with local timezone
171 try:
180 try:
172 d = (get_mtime(path), util.makedate()[1])
181 d = (get_mtime(path), util.makedate()[1])
173 except OSError:
182 except OSError:
174 continue
183 continue
175
184
176 contact = (get("ui", "username") or # preferred
185 contact = (get("ui", "username") or # preferred
177 get("web", "contact") or # deprecated
186 get("web", "contact") or # deprecated
178 get("web", "author", "")) # also
187 get("web", "author", "")) # also
179 description = get("web", "description", "")
188 description = get("web", "description", "")
180 name = get("web", "name", name)
189 name = get("web", "name", name)
181 row = dict(contact=contact or "unknown",
190 row = dict(contact=contact or "unknown",
182 contact_sort=contact.upper() or "unknown",
191 contact_sort=contact.upper() or "unknown",
183 name=name,
192 name=name,
184 name_sort=name,
193 name_sort=name,
185 url=url,
194 url=url,
186 description=description or "unknown",
195 description=description or "unknown",
187 description_sort=description.upper() or "unknown",
196 description_sort=description.upper() or "unknown",
188 lastchange=d,
197 lastchange=d,
189 lastchange_sort=d[1]-d[0],
198 lastchange_sort=d[1]-d[0],
190 sessionvars=sessionvars,
199 sessionvars=sessionvars,
191 archives=archivelist(u, "tip", url))
200 archives=archivelist(u, "tip", url))
192 if (not sortcolumn
201 if (not sortcolumn
193 or (sortcolumn, descending) == self.repos_sorted):
202 or (sortcolumn, descending) == self.repos_sorted):
194 # fast path for unsorted output
203 # fast path for unsorted output
195 row['parity'] = parity.next()
204 row['parity'] = parity.next()
196 yield row
205 yield row
197 else:
206 else:
198 rows.append((row["%s_sort" % sortcolumn], row))
207 rows.append((row["%s_sort" % sortcolumn], row))
199 if rows:
208 if rows:
200 rows.sort()
209 rows.sort()
201 if descending:
210 if descending:
202 rows.reverse()
211 rows.reverse()
203 for key, row in rows:
212 for key, row in rows:
204 row['parity'] = parity.next()
213 row['parity'] = parity.next()
205 yield row
214 yield row
206
215
207 sortable = ["name", "description", "contact", "lastchange"]
216 sortable = ["name", "description", "contact", "lastchange"]
208 sortcolumn, descending = self.repos_sorted
217 sortcolumn, descending = self.repos_sorted
209 if req.form.has_key('sort'):
218 if req.form.has_key('sort'):
210 sortcolumn = req.form['sort'][0]
219 sortcolumn = req.form['sort'][0]
211 descending = sortcolumn.startswith('-')
220 descending = sortcolumn.startswith('-')
212 if descending:
221 if descending:
213 sortcolumn = sortcolumn[1:]
222 sortcolumn = sortcolumn[1:]
214 if sortcolumn not in sortable:
223 if sortcolumn not in sortable:
215 sortcolumn = ""
224 sortcolumn = ""
216
225
217 sort = [("sort_%s" % column,
226 sort = [("sort_%s" % column,
218 "%s%s" % ((not descending and column == sortcolumn)
227 "%s%s" % ((not descending and column == sortcolumn)
219 and "-" or "", column))
228 and "-" or "", column))
220 for column in sortable]
229 for column in sortable]
221 req.write(tmpl("index", entries=entries, subdir=subdir,
230 req.write(tmpl("index", entries=entries, subdir=subdir,
222 sortcolumn=sortcolumn, descending=descending,
231 sortcolumn=sortcolumn, descending=descending,
223 **dict(sort)))
232 **dict(sort)))
224
233
225 def templater(self, req):
234 def templater(self, req):
226
235
227 def header(**map):
236 def header(**map):
228 header_file = cStringIO.StringIO(
237 header_file = cStringIO.StringIO(
229 ''.join(tmpl("header", encoding=util._encoding, **map)))
238 ''.join(tmpl("header", encoding=util._encoding, **map)))
230 msg = mimetools.Message(header_file, 0)
239 msg = mimetools.Message(header_file, 0)
231 req.header(msg.items())
240 req.header(msg.items())
232 yield header_file.read()
241 yield header_file.read()
233
242
234 def footer(**map):
243 def footer(**map):
235 yield tmpl("footer", **map)
244 yield tmpl("footer", **map)
236
245
237 def motd(**map):
246 def motd(**map):
238 if self.motd is not None:
247 if self.motd is not None:
239 yield self.motd
248 yield self.motd
240 else:
249 else:
241 yield config('web', 'motd', '')
250 yield config('web', 'motd', '')
242
251
243 def config(section, name, default=None, untrusted=True):
252 def config(section, name, default=None, untrusted=True):
244 return self.parentui.config(section, name, default, untrusted)
253 return self.parentui.config(section, name, default, untrusted)
245
254
246 url = req.env.get('SCRIPT_NAME', '')
255 url = req.env.get('SCRIPT_NAME', '')
247 if not url.endswith('/'):
256 if not url.endswith('/'):
248 url += '/'
257 url += '/'
249
258
250 staticurl = config('web', 'staticurl') or url + 'static/'
259 staticurl = config('web', 'staticurl') or url + 'static/'
251 if not staticurl.endswith('/'):
260 if not staticurl.endswith('/'):
252 staticurl += '/'
261 staticurl += '/'
253
262
254 style = self.style
263 style = self.style
255 if style is None:
264 if style is None:
256 style = config('web', 'style', '')
265 style = config('web', 'style', '')
257 if req.form.has_key('style'):
266 if req.form.has_key('style'):
258 style = req.form['style'][0]
267 style = req.form['style'][0]
259 if self.stripecount is None:
268 if self.stripecount is None:
260 self.stripecount = int(config('web', 'stripes', 1))
269 self.stripecount = int(config('web', 'stripes', 1))
261 mapfile = style_map(templater.templatepath(), style)
270 mapfile = style_map(templater.templatepath(), style)
262 tmpl = templater.templater(mapfile, templater.common_filters,
271 tmpl = templater.templater(mapfile, templater.common_filters,
263 defaults={"header": header,
272 defaults={"header": header,
264 "footer": footer,
273 "footer": footer,
265 "motd": motd,
274 "motd": motd,
266 "url": url,
275 "url": url,
267 "staticurl": staticurl})
276 "staticurl": staticurl})
268 return tmpl
277 return tmpl
General Comments 0
You need to be logged in to leave comments. Login now