##// END OF EJS Templates
hgwebdir: split out templater creation
Dirkjan Ochtman -
r5602:d676d0f3 default
parent child Browse files
Show More
@@ -1,261 +1,268 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 def header(**map):
74 header_file = cStringIO.StringIO(
75 ''.join(tmpl("header", encoding=util._encoding, **map)))
76 msg = mimetools.Message(header_file, 0)
77 req.header(msg.items())
78 yield header_file.read()
79
80 def footer(**map):
81 yield tmpl("footer", **map)
82
83 def motd(**map):
84 if self.motd is not None:
85 yield self.motd
86 else:
87 yield config('web', 'motd', '')
88
89 def config(section, name, default=None, untrusted=True):
90 return self.parentui.config(section, name, default, untrusted)
91
92 url = req.env.get('SCRIPT_NAME', '')
93 if not url.endswith('/'):
94 url += '/'
95
96 staticurl = config('web', 'staticurl') or url + 'static/'
97 if not staticurl.endswith('/'):
98 staticurl += '/'
99
100 style = self.style
101 if style is None:
102 style = config('web', 'style', '')
103 if req.form.has_key('style'):
104 style = req.form['style'][0]
105 if self.stripecount is None:
106 self.stripecount = int(config('web', 'stripes', 1))
107 mapfile = style_map(templater.templatepath(), style)
108 tmpl = templater.templater(mapfile, templater.common_filters,
109 defaults={"header": header,
110 "footer": footer,
111 "motd": motd,
112 "url": url,
113 "staticurl": staticurl})
114
73
115 try:
74 try:
116 try:
75 try:
117 virtual = req.env.get("PATH_INFO", "").strip('/')
76 virtual = req.env.get("PATH_INFO", "").strip('/')
118 if virtual.startswith('static/'):
77 if virtual.startswith('static/'):
119 static = os.path.join(templater.templatepath(), 'static')
78 static = os.path.join(templater.templatepath(), 'static')
120 fname = virtual[7:]
79 fname = virtual[7:]
121 req.write(staticfile(static, fname, req))
80 req.write(staticfile(static, fname, req))
122 elif virtual:
81 elif virtual:
123 repos = dict(self.repos)
82 repos = dict(self.repos)
124 while virtual:
83 while virtual:
125 real = repos.get(virtual)
84 real = repos.get(virtual)
126 if real:
85 if real:
127 req.env['REPO_NAME'] = virtual
86 req.env['REPO_NAME'] = virtual
128 try:
87 try:
129 repo = hg.repository(self.parentui, real)
88 repo = hg.repository(self.parentui, real)
130 hgweb(repo).run_wsgi(req)
89 hgweb(repo).run_wsgi(req)
131 return
90 return
132 except IOError, inst:
91 except IOError, inst:
133 raise ErrorResponse(500, inst.strerror)
92 raise ErrorResponse(500, inst.strerror)
134 except hg.RepoError, inst:
93 except hg.RepoError, inst:
135 raise ErrorResponse(500, str(inst))
94 raise ErrorResponse(500, str(inst))
136
95
137 # browse subdirectories
96 # browse subdirectories
138 subdir = virtual + '/'
97 subdir = virtual + '/'
139 if [r for r in repos if r.startswith(subdir)]:
98 if [r for r in repos if r.startswith(subdir)]:
99 tmpl = self.templater(req)
140 self.makeindex(req, tmpl, subdir)
100 self.makeindex(req, tmpl, subdir)
141 return
101 return
142
102
143 up = virtual.rfind('/')
103 up = virtual.rfind('/')
144 if up < 0:
104 if up < 0:
145 break
105 break
146 virtual = virtual[:up]
106 virtual = virtual[:up]
147
107
108 tmpl = self.templater(req)
148 req.respond(404, tmpl("notfound", repo=virtual))
109 req.respond(404, tmpl("notfound", repo=virtual))
149 else:
110 else:
150 if req.form.has_key('static'):
111 if req.form.has_key('static'):
151 static = os.path.join(templater.templatepath(), "static")
112 static = os.path.join(templater.templatepath(), "static")
152 fname = req.form['static'][0]
113 fname = req.form['static'][0]
153 req.write(staticfile(static, fname, req))
114 req.write(staticfile(static, fname, req))
154 else:
115 else:
116 tmpl = self.templater(req)
155 self.makeindex(req, tmpl)
117 self.makeindex(req, tmpl)
156 except ErrorResponse, err:
118 except ErrorResponse, err:
157 req.respond(err.code, tmpl('error', error=err.message or ''))
119 req.respond(err.code, tmpl('error', error=err.message or ''))
158 finally:
120 finally:
159 tmpl = None
121 tmpl = None
160
122
161 def makeindex(self, req, tmpl, subdir=""):
123 def makeindex(self, req, tmpl, subdir=""):
162
124
163 def archivelist(ui, nodeid, url):
125 def archivelist(ui, nodeid, url):
164 allowed = ui.configlist("web", "allow_archive", untrusted=True)
126 allowed = ui.configlist("web", "allow_archive", untrusted=True)
165 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
127 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
166 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
128 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
167 untrusted=True):
129 untrusted=True):
168 yield {"type" : i[0], "extension": i[1],
130 yield {"type" : i[0], "extension": i[1],
169 "node": nodeid, "url": url}
131 "node": nodeid, "url": url}
170
132
171 def entries(sortcolumn="", descending=False, subdir="", **map):
133 def entries(sortcolumn="", descending=False, subdir="", **map):
172 def sessionvars(**map):
134 def sessionvars(**map):
173 fields = []
135 fields = []
174 if req.form.has_key('style'):
136 if req.form.has_key('style'):
175 style = req.form['style'][0]
137 style = req.form['style'][0]
176 if style != get('web', 'style', ''):
138 if style != get('web', 'style', ''):
177 fields.append(('style', style))
139 fields.append(('style', style))
178
140
179 separator = url[-1] == '?' and ';' or '?'
141 separator = url[-1] == '?' and ';' or '?'
180 for name, value in fields:
142 for name, value in fields:
181 yield dict(name=name, value=value, separator=separator)
143 yield dict(name=name, value=value, separator=separator)
182 separator = ';'
144 separator = ';'
183
145
184 rows = []
146 rows = []
185 parity = paritygen(self.stripecount)
147 parity = paritygen(self.stripecount)
186 for name, path in self.repos:
148 for name, path in self.repos:
187 if not name.startswith(subdir):
149 if not name.startswith(subdir):
188 continue
150 continue
189 name = name[len(subdir):]
151 name = name[len(subdir):]
190
152
191 u = ui.ui(parentui=self.parentui)
153 u = ui.ui(parentui=self.parentui)
192 try:
154 try:
193 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
155 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
194 except Exception, e:
156 except Exception, e:
195 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
157 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
196 continue
158 continue
197 def get(section, name, default=None):
159 def get(section, name, default=None):
198 return u.config(section, name, default, untrusted=True)
160 return u.config(section, name, default, untrusted=True)
199
161
200 if u.configbool("web", "hidden", untrusted=True):
162 if u.configbool("web", "hidden", untrusted=True):
201 continue
163 continue
202
164
203 parts = [req.env['PATH_INFO'], name]
165 parts = [req.env['PATH_INFO'], name]
204 if req.env['SCRIPT_NAME']:
166 if req.env['SCRIPT_NAME']:
205 parts.insert(0, req.env['SCRIPT_NAME'])
167 parts.insert(0, req.env['SCRIPT_NAME'])
206 url = ('/'.join(parts).replace("//", "/")) + '/'
168 url = ('/'.join(parts).replace("//", "/")) + '/'
207
169
208 # update time with local timezone
170 # update time with local timezone
209 try:
171 try:
210 d = (get_mtime(path), util.makedate()[1])
172 d = (get_mtime(path), util.makedate()[1])
211 except OSError:
173 except OSError:
212 continue
174 continue
213
175
214 contact = (get("ui", "username") or # preferred
176 contact = (get("ui", "username") or # preferred
215 get("web", "contact") or # deprecated
177 get("web", "contact") or # deprecated
216 get("web", "author", "")) # also
178 get("web", "author", "")) # also
217 description = get("web", "description", "")
179 description = get("web", "description", "")
218 name = get("web", "name", name)
180 name = get("web", "name", name)
219 row = dict(contact=contact or "unknown",
181 row = dict(contact=contact or "unknown",
220 contact_sort=contact.upper() or "unknown",
182 contact_sort=contact.upper() or "unknown",
221 name=name,
183 name=name,
222 name_sort=name,
184 name_sort=name,
223 url=url,
185 url=url,
224 description=description or "unknown",
186 description=description or "unknown",
225 description_sort=description.upper() or "unknown",
187 description_sort=description.upper() or "unknown",
226 lastchange=d,
188 lastchange=d,
227 lastchange_sort=d[1]-d[0],
189 lastchange_sort=d[1]-d[0],
228 sessionvars=sessionvars,
190 sessionvars=sessionvars,
229 archives=archivelist(u, "tip", url))
191 archives=archivelist(u, "tip", url))
230 if (not sortcolumn
192 if (not sortcolumn
231 or (sortcolumn, descending) == self.repos_sorted):
193 or (sortcolumn, descending) == self.repos_sorted):
232 # fast path for unsorted output
194 # fast path for unsorted output
233 row['parity'] = parity.next()
195 row['parity'] = parity.next()
234 yield row
196 yield row
235 else:
197 else:
236 rows.append((row["%s_sort" % sortcolumn], row))
198 rows.append((row["%s_sort" % sortcolumn], row))
237 if rows:
199 if rows:
238 rows.sort()
200 rows.sort()
239 if descending:
201 if descending:
240 rows.reverse()
202 rows.reverse()
241 for key, row in rows:
203 for key, row in rows:
242 row['parity'] = parity.next()
204 row['parity'] = parity.next()
243 yield row
205 yield row
244
206
245 sortable = ["name", "description", "contact", "lastchange"]
207 sortable = ["name", "description", "contact", "lastchange"]
246 sortcolumn, descending = self.repos_sorted
208 sortcolumn, descending = self.repos_sorted
247 if req.form.has_key('sort'):
209 if req.form.has_key('sort'):
248 sortcolumn = req.form['sort'][0]
210 sortcolumn = req.form['sort'][0]
249 descending = sortcolumn.startswith('-')
211 descending = sortcolumn.startswith('-')
250 if descending:
212 if descending:
251 sortcolumn = sortcolumn[1:]
213 sortcolumn = sortcolumn[1:]
252 if sortcolumn not in sortable:
214 if sortcolumn not in sortable:
253 sortcolumn = ""
215 sortcolumn = ""
254
216
255 sort = [("sort_%s" % column,
217 sort = [("sort_%s" % column,
256 "%s%s" % ((not descending and column == sortcolumn)
218 "%s%s" % ((not descending and column == sortcolumn)
257 and "-" or "", column))
219 and "-" or "", column))
258 for column in sortable]
220 for column in sortable]
259 req.write(tmpl("index", entries=entries, subdir=subdir,
221 req.write(tmpl("index", entries=entries, subdir=subdir,
260 sortcolumn=sortcolumn, descending=descending,
222 sortcolumn=sortcolumn, descending=descending,
261 **dict(sort)))
223 **dict(sort)))
224
225 def templater(self, req):
226
227 def header(**map):
228 header_file = cStringIO.StringIO(
229 ''.join(tmpl("header", encoding=util._encoding, **map)))
230 msg = mimetools.Message(header_file, 0)
231 req.header(msg.items())
232 yield header_file.read()
233
234 def footer(**map):
235 yield tmpl("footer", **map)
236
237 def motd(**map):
238 if self.motd is not None:
239 yield self.motd
240 else:
241 yield config('web', 'motd', '')
242
243 def config(section, name, default=None, untrusted=True):
244 return self.parentui.config(section, name, default, untrusted)
245
246 url = req.env.get('SCRIPT_NAME', '')
247 if not url.endswith('/'):
248 url += '/'
249
250 staticurl = config('web', 'staticurl') or url + 'static/'
251 if not staticurl.endswith('/'):
252 staticurl += '/'
253
254 style = self.style
255 if style is None:
256 style = config('web', 'style', '')
257 if req.form.has_key('style'):
258 style = req.form['style'][0]
259 if self.stripecount is None:
260 self.stripecount = int(config('web', 'stripes', 1))
261 mapfile = style_map(templater.templatepath(), style)
262 tmpl = templater.templater(mapfile, templater.common_filters,
263 defaults={"header": header,
264 "footer": footer,
265 "motd": motd,
266 "url": url,
267 "staticurl": staticurl})
268 return tmpl
General Comments 0
You need to be logged in to leave comments. Login now