##// END OF EJS Templates
hgweb: use config.config
Matt Mackall -
r8180:6fc30fe7 default
parent child Browse files
Show More
@@ -1,93 +1,97 b''
1 from i18n import _
1 from i18n import _
2 import re, error
2 import re, error
3
3
4 class sortdict(dict):
4 class sortdict(dict):
5 'a simple append-only sorted dictionary'
5 'a simple append-only sorted dictionary'
6 def __init__(self, data=None):
6 def __init__(self, data=None):
7 self._list = []
7 self._list = []
8 if data:
8 if data:
9 if hasattr(data, '_list'):
9 if hasattr(data, '_list'):
10 self._list = list(data._list)
10 self._list = list(data._list)
11 self.update(data)
11 self.update(data)
12 def copy(self):
12 def copy(self):
13 return sortdict(self)
13 return sortdict(self)
14 def __setitem__(self, key, val):
14 def __setitem__(self, key, val):
15 if key in self:
15 if key in self:
16 self._list.remove(key)
16 self._list.remove(key)
17 self._list.append(key)
17 self._list.append(key)
18 dict.__setitem__(self, key, val)
18 dict.__setitem__(self, key, val)
19 def __iter__(self):
19 def __iter__(self):
20 return self._list.__iter__()
20 return self._list.__iter__()
21 def update(self, src):
21 def update(self, src):
22 for k in src:
22 for k in src:
23 self[k] = src[k]
23 self[k] = src[k]
24 def items(self):
24 def items(self):
25 return [(k,self[k]) for k in self._list]
25 return [(k,self[k]) for k in self._list]
26
26
27 class config:
27 class config:
28 def __init__(self, data=None):
28 def __init__(self, data=None):
29 self._data = {}
29 self._data = {}
30 if data:
30 if data:
31 for k in data._data:
31 for k in data._data:
32 self._data[k] = data[k].copy()
32 self._data[k] = data[k].copy()
33 def copy(self):
33 def copy(self):
34 return config(self)
34 return config(self)
35 def __contains__(self, section):
35 def __contains__(self, section):
36 return section in self._data
36 return section in self._data
37 def update(self, src, sections=None):
37 def update(self, src, sections=None):
38 if not sections:
38 if not sections:
39 sections = src.sections()
39 sections = src.sections()
40 for s in sections:
40 for s in sections:
41 if s not in src:
41 if s not in src:
42 continue
42 continue
43 if s not in self:
43 if s not in self:
44 self._data[s] = sortdict()
44 self._data[s] = sortdict()
45 for k in src._data[s]:
45 for k in src._data[s]:
46 self._data[s][k] = src._data[s][k]
46 self._data[s][k] = src._data[s][k]
47 def get(self, section, item, default=None):
47 def get(self, section, item, default=None):
48 return self._data.get(section, {}).get(item, (default, ""))[0]
48 return self._data.get(section, {}).get(item, (default, ""))[0]
49 def getsource(self, section, item):
49 def getsource(self, section, item):
50 return self._data.get(section, {}).get(item, (None, ""))[1]
50 return self._data.get(section, {}).get(item, (None, ""))[1]
51 def sections(self):
51 def sections(self):
52 return sorted(self._data.keys())
52 return sorted(self._data.keys())
53 def items(self, section):
53 def items(self, section):
54 return [(k, v[0]) for k,v in self._data.get(section, {}).items()]
54 return [(k, v[0]) for k,v in self._data.get(section, {}).items()]
55 def set(self, section, item, value, source=""):
55 def set(self, section, item, value, source=""):
56 if section not in self:
56 if section not in self:
57 self._data[section] = sortdict()
57 self._data[section] = sortdict()
58 self._data[section][item] = (value, source)
58 self._data[section][item] = (value, source)
59
59
60 def read(self, path, fp):
60 def read(self, path, fp=None):
61 sectionre = re.compile(r'\[([^\[]+)\]')
61 sectionre = re.compile(r'\[([^\[]+)\]')
62 itemre = re.compile(r'([^=\s]+)\s*=\s*(.*)')
62 itemre = re.compile(r'([^=\s]+)\s*=\s*(.*)')
63 contre = re.compile(r'\s+(\S.*)')
63 contre = re.compile(r'\s+(\S.*)')
64 emptyre = re.compile(r'(;|#|\s*$)')
64 emptyre = re.compile(r'(;|#|\s*$)')
65 section = ""
65 section = ""
66 item = None
66 item = None
67 line = 0
67 line = 0
68 cont = 0
68 cont = 0
69
70 if not fp:
71 fp = open(path)
72
69 for l in fp:
73 for l in fp:
70 line += 1
74 line += 1
71 if cont:
75 if cont:
72 m = contre.match(l)
76 m = contre.match(l)
73 if m:
77 if m:
74 v = self.get(section, item) + "\n" + m.group(1)
78 v = self.get(section, item) + "\n" + m.group(1)
75 self.set(section, item, v, "%s:%d" % (path, line))
79 self.set(section, item, v, "%s:%d" % (path, line))
76 continue
80 continue
77 item = None
81 item = None
78 if emptyre.match(l):
82 if emptyre.match(l):
79 continue
83 continue
80 m = sectionre.match(l)
84 m = sectionre.match(l)
81 if m:
85 if m:
82 section = m.group(1)
86 section = m.group(1)
83 if section not in self:
87 if section not in self:
84 self._data[section] = sortdict()
88 self._data[section] = sortdict()
85 continue
89 continue
86 m = itemre.match(l)
90 m = itemre.match(l)
87 if m:
91 if m:
88 item = m.group(1)
92 item = m.group(1)
89 self.set(section, item, m.group(2), "%s:%d" % (path, line))
93 self.set(section, item, m.group(2), "%s:%d" % (path, line))
90 cont = 1
94 cont = 1
91 continue
95 continue
92 raise error.ConfigError(_('config error at %s:%d: \'%s\'')
96 raise error.ConfigError(_('config error at %s:%d: \'%s\'')
93 % (path, line, l.rstrip()))
97 % (path, line, l.rstrip()))
@@ -1,332 +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
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
9 import os
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial import ui, hg, util, templater, templatefilters, error, encoding
11 from mercurial import ui, hg, util, templater, templatefilters
12 from mercurial import config, error, encoding
12 from common import ErrorResponse, get_mtime, staticfile, paritygen,\
13 from common import ErrorResponse, get_mtime, staticfile, paritygen,\
13 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 from hgweb_mod import hgweb
15 from hgweb_mod import hgweb
15 from request import wsgirequest
16 from request import wsgirequest
16
17
17 # This is a stopgap
18 # This is a stopgap
18 class hgwebdir(object):
19 class hgwebdir(object):
19 def __init__(self, config, parentui=None):
20 def __init__(self, conf, parentui=None):
20 def cleannames(items):
21 def cleannames(items):
21 return [(util.pconvert(name).strip('/'), path)
22 return [(util.pconvert(name).strip('/'), path)
22 for name, path in items]
23 for name, path in items]
23
24
24 if parentui:
25 if parentui:
25 self.parentui = parentui
26 self.parentui = parentui
26 else:
27 else:
27 self.parentui = ui.ui()
28 self.parentui = ui.ui()
28 self.parentui.setconfig('ui', 'report_untrusted', 'off')
29 self.parentui.setconfig('ui', 'report_untrusted', 'off')
29 self.parentui.setconfig('ui', 'interactive', 'off')
30 self.parentui.setconfig('ui', 'interactive', 'off')
30
31
31 self.motd = None
32 self.motd = None
32 self.style = 'paper'
33 self.style = 'paper'
33 self.stripecount = None
34 self.stripecount = None
34 self.repos_sorted = ('name', False)
35 self.repos_sorted = ('name', False)
35 self._baseurl = None
36 self._baseurl = None
36 if isinstance(config, (list, tuple)):
37 if isinstance(conf, (list, tuple)):
37 self.repos = cleannames(config)
38 self.repos = cleannames(conf)
38 self.repos_sorted = ('', False)
39 self.repos_sorted = ('', False)
39 elif isinstance(config, dict):
40 elif isinstance(conf, dict):
40 self.repos = util.sort(cleannames(config.items()))
41 self.repos = util.sort(cleannames(conf.items()))
41 else:
42 else:
42 if isinstance(config, util.configparser):
43 if isinstance(conf, config.config):
43 cp = config
44 cp = conf
44 else:
45 else:
45 cp = util.configparser()
46 cp = config.config()
46 cp.read(config)
47 cp.read(conf)
47 self.repos = []
48 self.repos = []
48 if cp.has_section('web'):
49 if cp.has_option('web', 'motd'):
50 self.motd = cp.get('web', 'motd')
49 self.motd = cp.get('web', 'motd')
51 if cp.has_option('web', 'style'):
50 self.style = cp.get('web', 'style', 'paper')
52 self.style = cp.get('web', 'style')
51 self.stripecount = cp.get('web', 'stripes')
53 if cp.has_option('web', 'stripes'):
54 self.stripecount = int(cp.get('web', 'stripes'))
55 if cp.has_option('web', 'baseurl'):
56 self._baseurl = cp.get('web', 'baseurl')
52 self._baseurl = cp.get('web', 'baseurl')
57 if cp.has_section('paths'):
53 if 'paths' in cp:
58 paths = cleannames(cp.items('paths'))
54 paths = cleannames(cp.items('paths'))
59 for prefix, root in paths:
55 for prefix, root in paths:
60 roothead, roottail = os.path.split(root)
56 roothead, roottail = os.path.split(root)
61 # "foo = /bar/*" makes every subrepo of /bar/ to be
57 # "foo = /bar/*" makes every subrepo of /bar/ to be
62 # mounted as foo/subrepo
58 # mounted as foo/subrepo
63 # and "foo = /bar/**" does even recurse inside the
59 # and "foo = /bar/**" does even recurse inside the
64 # subdirectories, remember to use it without working dir.
60 # subdirectories, remember to use it without working dir.
65 try:
61 try:
66 recurse = {'*': False, '**': True}[roottail]
62 recurse = {'*': False, '**': True}[roottail]
67 except KeyError:
63 except KeyError:
68 self.repos.append((prefix, root))
64 self.repos.append((prefix, root))
69 continue
65 continue
70 roothead = os.path.normpath(roothead)
66 roothead = os.path.normpath(roothead)
71 for path in util.walkrepos(roothead, followsym=True,
67 for path in util.walkrepos(roothead, followsym=True,
72 recurse=recurse):
68 recurse=recurse):
73 path = os.path.normpath(path)
69 path = os.path.normpath(path)
74 name = util.pconvert(path[len(roothead):]).strip('/')
70 name = util.pconvert(path[len(roothead):]).strip('/')
75 if prefix:
71 if prefix:
76 name = prefix + '/' + name
72 name = prefix + '/' + name
77 self.repos.append((name, path))
73 self.repos.append((name, path))
78 if cp.has_section('collections'):
79 for prefix, root in cp.items('collections'):
74 for prefix, root in cp.items('collections'):
80 for path in util.walkrepos(root, followsym=True):
75 for path in util.walkrepos(root, followsym=True):
81 repo = os.path.normpath(path)
76 repo = os.path.normpath(path)
82 name = repo
77 name = repo
83 if name.startswith(prefix):
78 if name.startswith(prefix):
84 name = name[len(prefix):]
79 name = name[len(prefix):]
85 self.repos.append((name.lstrip(os.sep), repo))
80 self.repos.append((name.lstrip(os.sep), repo))
86 self.repos.sort()
81 self.repos.sort()
87
82
88 def run(self):
83 def run(self):
89 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
84 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
90 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
85 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
91 import mercurial.hgweb.wsgicgi as wsgicgi
86 import mercurial.hgweb.wsgicgi as wsgicgi
92 wsgicgi.launch(self)
87 wsgicgi.launch(self)
93
88
94 def __call__(self, env, respond):
89 def __call__(self, env, respond):
95 req = wsgirequest(env, respond)
90 req = wsgirequest(env, respond)
96 return self.run_wsgi(req)
91 return self.run_wsgi(req)
97
92
98 def read_allowed(self, ui, req):
93 def read_allowed(self, ui, req):
99 """Check allow_read and deny_read config options of a repo's ui object
94 """Check allow_read and deny_read config options of a repo's ui object
100 to determine user permissions. By default, with neither option set (or
95 to determine user permissions. By default, with neither option set (or
101 both empty), allow all users to read the repo. There are two ways a
96 both empty), allow all users to read the repo. There are two ways a
102 user can be denied read access: (1) deny_read is not empty, and the
97 user can be denied read access: (1) deny_read is not empty, and the
103 user is unauthenticated or deny_read contains user (or *), and (2)
98 user is unauthenticated or deny_read contains user (or *), and (2)
104 allow_read is not empty and the user is not in allow_read. Return True
99 allow_read is not empty and the user is not in allow_read. Return True
105 if user is allowed to read the repo, else return False."""
100 if user is allowed to read the repo, else return False."""
106
101
107 user = req.env.get('REMOTE_USER')
102 user = req.env.get('REMOTE_USER')
108
103
109 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
104 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
110 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
105 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
111 return False
106 return False
112
107
113 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
108 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
114 # by default, allow reading if no allow_read option has been set
109 # by default, allow reading if no allow_read option has been set
115 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
110 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
116 return True
111 return True
117
112
118 return False
113 return False
119
114
120 def run_wsgi(self, req):
115 def run_wsgi(self, req):
121
116
122 try:
117 try:
123 try:
118 try:
124
119
125 virtual = req.env.get("PATH_INFO", "").strip('/')
120 virtual = req.env.get("PATH_INFO", "").strip('/')
126 tmpl = self.templater(req)
121 tmpl = self.templater(req)
127 ctype = tmpl('mimetype', encoding=encoding.encoding)
122 ctype = tmpl('mimetype', encoding=encoding.encoding)
128 ctype = templater.stringify(ctype)
123 ctype = templater.stringify(ctype)
129
124
130 # a static file
125 # a static file
131 if virtual.startswith('static/') or 'static' in req.form:
126 if virtual.startswith('static/') or 'static' in req.form:
132 if virtual.startswith('static/'):
127 if virtual.startswith('static/'):
133 fname = virtual[7:]
128 fname = virtual[7:]
134 else:
129 else:
135 fname = req.form['static'][0]
130 fname = req.form['static'][0]
136 static = templater.templatepath('static')
131 static = templater.templatepath('static')
137 return (staticfile(static, fname, req),)
132 return (staticfile(static, fname, req),)
138
133
139 # top-level index
134 # top-level index
140 elif not virtual:
135 elif not virtual:
141 req.respond(HTTP_OK, ctype)
136 req.respond(HTTP_OK, ctype)
142 return self.makeindex(req, tmpl)
137 return self.makeindex(req, tmpl)
143
138
144 # nested indexes and hgwebs
139 # nested indexes and hgwebs
145
140
146 repos = dict(self.repos)
141 repos = dict(self.repos)
147 while virtual:
142 while virtual:
148 real = repos.get(virtual)
143 real = repos.get(virtual)
149 if real:
144 if real:
150 req.env['REPO_NAME'] = virtual
145 req.env['REPO_NAME'] = virtual
151 try:
146 try:
152 repo = hg.repository(self.parentui, real)
147 repo = hg.repository(self.parentui, real)
153 return hgweb(repo).run_wsgi(req)
148 return hgweb(repo).run_wsgi(req)
154 except IOError, inst:
149 except IOError, inst:
155 msg = inst.strerror
150 msg = inst.strerror
156 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
151 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
157 except error.RepoError, inst:
152 except error.RepoError, inst:
158 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
153 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
159
154
160 # browse subdirectories
155 # browse subdirectories
161 subdir = virtual + '/'
156 subdir = virtual + '/'
162 if [r for r in repos if r.startswith(subdir)]:
157 if [r for r in repos if r.startswith(subdir)]:
163 req.respond(HTTP_OK, ctype)
158 req.respond(HTTP_OK, ctype)
164 return self.makeindex(req, tmpl, subdir)
159 return self.makeindex(req, tmpl, subdir)
165
160
166 up = virtual.rfind('/')
161 up = virtual.rfind('/')
167 if up < 0:
162 if up < 0:
168 break
163 break
169 virtual = virtual[:up]
164 virtual = virtual[:up]
170
165
171 # prefixes not found
166 # prefixes not found
172 req.respond(HTTP_NOT_FOUND, ctype)
167 req.respond(HTTP_NOT_FOUND, ctype)
173 return tmpl("notfound", repo=virtual)
168 return tmpl("notfound", repo=virtual)
174
169
175 except ErrorResponse, err:
170 except ErrorResponse, err:
176 req.respond(err, ctype)
171 req.respond(err, ctype)
177 return tmpl('error', error=err.message or '')
172 return tmpl('error', error=err.message or '')
178 finally:
173 finally:
179 tmpl = None
174 tmpl = None
180
175
181 def makeindex(self, req, tmpl, subdir=""):
176 def makeindex(self, req, tmpl, subdir=""):
182
177
183 def archivelist(ui, nodeid, url):
178 def archivelist(ui, nodeid, url):
184 allowed = ui.configlist("web", "allow_archive", untrusted=True)
179 allowed = ui.configlist("web", "allow_archive", untrusted=True)
185 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
180 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
186 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
181 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
187 untrusted=True):
182 untrusted=True):
188 yield {"type" : i[0], "extension": i[1],
183 yield {"type" : i[0], "extension": i[1],
189 "node": nodeid, "url": url}
184 "node": nodeid, "url": url}
190
185
191 def entries(sortcolumn="", descending=False, subdir="", **map):
186 def entries(sortcolumn="", descending=False, subdir="", **map):
192 def sessionvars(**map):
187 def sessionvars(**map):
193 fields = []
188 fields = []
194 if 'style' in req.form:
189 if 'style' in req.form:
195 style = req.form['style'][0]
190 style = req.form['style'][0]
196 if style != get('web', 'style', ''):
191 if style != get('web', 'style', ''):
197 fields.append(('style', style))
192 fields.append(('style', style))
198
193
199 separator = url[-1] == '?' and ';' or '?'
194 separator = url[-1] == '?' and ';' or '?'
200 for name, value in fields:
195 for name, value in fields:
201 yield dict(name=name, value=value, separator=separator)
196 yield dict(name=name, value=value, separator=separator)
202 separator = ';'
197 separator = ';'
203
198
204 rows = []
199 rows = []
205 parity = paritygen(self.stripecount)
200 parity = paritygen(self.stripecount)
206 for name, path in self.repos:
201 for name, path in self.repos:
207 if not name.startswith(subdir):
202 if not name.startswith(subdir):
208 continue
203 continue
209 name = name[len(subdir):]
204 name = name[len(subdir):]
210
205
211 u = ui.ui(parentui=self.parentui)
206 u = ui.ui(parentui=self.parentui)
212 try:
207 try:
213 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
208 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
214 except Exception, e:
209 except Exception, e:
215 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
210 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
216 continue
211 continue
217 def get(section, name, default=None):
212 def get(section, name, default=None):
218 return u.config(section, name, default, untrusted=True)
213 return u.config(section, name, default, untrusted=True)
219
214
220 if u.configbool("web", "hidden", untrusted=True):
215 if u.configbool("web", "hidden", untrusted=True):
221 continue
216 continue
222
217
223 if not self.read_allowed(u, req):
218 if not self.read_allowed(u, req):
224 continue
219 continue
225
220
226 parts = [name]
221 parts = [name]
227 if 'PATH_INFO' in req.env:
222 if 'PATH_INFO' in req.env:
228 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
223 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
229 if req.env['SCRIPT_NAME']:
224 if req.env['SCRIPT_NAME']:
230 parts.insert(0, req.env['SCRIPT_NAME'])
225 parts.insert(0, req.env['SCRIPT_NAME'])
231 url = ('/'.join(parts).replace("//", "/")) + '/'
226 url = ('/'.join(parts).replace("//", "/")) + '/'
232
227
233 # update time with local timezone
228 # update time with local timezone
234 try:
229 try:
235 d = (get_mtime(path), util.makedate()[1])
230 d = (get_mtime(path), util.makedate()[1])
236 except OSError:
231 except OSError:
237 continue
232 continue
238
233
239 contact = get_contact(get)
234 contact = get_contact(get)
240 description = get("web", "description", "")
235 description = get("web", "description", "")
241 name = get("web", "name", name)
236 name = get("web", "name", name)
242 row = dict(contact=contact or "unknown",
237 row = dict(contact=contact or "unknown",
243 contact_sort=contact.upper() or "unknown",
238 contact_sort=contact.upper() or "unknown",
244 name=name,
239 name=name,
245 name_sort=name,
240 name_sort=name,
246 url=url,
241 url=url,
247 description=description or "unknown",
242 description=description or "unknown",
248 description_sort=description.upper() or "unknown",
243 description_sort=description.upper() or "unknown",
249 lastchange=d,
244 lastchange=d,
250 lastchange_sort=d[1]-d[0],
245 lastchange_sort=d[1]-d[0],
251 sessionvars=sessionvars,
246 sessionvars=sessionvars,
252 archives=archivelist(u, "tip", url))
247 archives=archivelist(u, "tip", url))
253 if (not sortcolumn
248 if (not sortcolumn
254 or (sortcolumn, descending) == self.repos_sorted):
249 or (sortcolumn, descending) == self.repos_sorted):
255 # fast path for unsorted output
250 # fast path for unsorted output
256 row['parity'] = parity.next()
251 row['parity'] = parity.next()
257 yield row
252 yield row
258 else:
253 else:
259 rows.append((row["%s_sort" % sortcolumn], row))
254 rows.append((row["%s_sort" % sortcolumn], row))
260 if rows:
255 if rows:
261 rows.sort()
256 rows.sort()
262 if descending:
257 if descending:
263 rows.reverse()
258 rows.reverse()
264 for key, row in rows:
259 for key, row in rows:
265 row['parity'] = parity.next()
260 row['parity'] = parity.next()
266 yield row
261 yield row
267
262
268 sortable = ["name", "description", "contact", "lastchange"]
263 sortable = ["name", "description", "contact", "lastchange"]
269 sortcolumn, descending = self.repos_sorted
264 sortcolumn, descending = self.repos_sorted
270 if 'sort' in req.form:
265 if 'sort' in req.form:
271 sortcolumn = req.form['sort'][0]
266 sortcolumn = req.form['sort'][0]
272 descending = sortcolumn.startswith('-')
267 descending = sortcolumn.startswith('-')
273 if descending:
268 if descending:
274 sortcolumn = sortcolumn[1:]
269 sortcolumn = sortcolumn[1:]
275 if sortcolumn not in sortable:
270 if sortcolumn not in sortable:
276 sortcolumn = ""
271 sortcolumn = ""
277
272
278 sort = [("sort_%s" % column,
273 sort = [("sort_%s" % column,
279 "%s%s" % ((not descending and column == sortcolumn)
274 "%s%s" % ((not descending and column == sortcolumn)
280 and "-" or "", column))
275 and "-" or "", column))
281 for column in sortable]
276 for column in sortable]
282
277
283 if self._baseurl is not None:
278 if self._baseurl is not None:
284 req.env['SCRIPT_NAME'] = self._baseurl
279 req.env['SCRIPT_NAME'] = self._baseurl
285
280
286 return tmpl("index", entries=entries, subdir=subdir,
281 return tmpl("index", entries=entries, subdir=subdir,
287 sortcolumn=sortcolumn, descending=descending,
282 sortcolumn=sortcolumn, descending=descending,
288 **dict(sort))
283 **dict(sort))
289
284
290 def templater(self, req):
285 def templater(self, req):
291
286
292 def header(**map):
287 def header(**map):
293 yield tmpl('header', encoding=encoding.encoding, **map)
288 yield tmpl('header', encoding=encoding.encoding, **map)
294
289
295 def footer(**map):
290 def footer(**map):
296 yield tmpl("footer", **map)
291 yield tmpl("footer", **map)
297
292
298 def motd(**map):
293 def motd(**map):
299 if self.motd is not None:
294 if self.motd is not None:
300 yield self.motd
295 yield self.motd
301 else:
296 else:
302 yield config('web', 'motd', '')
297 yield config('web', 'motd', '')
303
298
304 def config(section, name, default=None, untrusted=True):
299 def config(section, name, default=None, untrusted=True):
305 return self.parentui.config(section, name, default, untrusted)
300 return self.parentui.config(section, name, default, untrusted)
306
301
307 if self._baseurl is not None:
302 if self._baseurl is not None:
308 req.env['SCRIPT_NAME'] = self._baseurl
303 req.env['SCRIPT_NAME'] = self._baseurl
309
304
310 url = req.env.get('SCRIPT_NAME', '')
305 url = req.env.get('SCRIPT_NAME', '')
311 if not url.endswith('/'):
306 if not url.endswith('/'):
312 url += '/'
307 url += '/'
313
308
314 staticurl = config('web', 'staticurl') or url + 'static/'
309 staticurl = config('web', 'staticurl') or url + 'static/'
315 if not staticurl.endswith('/'):
310 if not staticurl.endswith('/'):
316 staticurl += '/'
311 staticurl += '/'
317
312
318 style = self.style
313 style = self.style
319 if style is None:
314 if style is None:
320 style = config('web', 'style', '')
315 style = config('web', 'style', '')
321 if 'style' in req.form:
316 if 'style' in req.form:
322 style = req.form['style'][0]
317 style = req.form['style'][0]
323 if self.stripecount is None:
318 self.stripecount = int(self.stripecount or config('web', 'stripes', 1))
324 self.stripecount = int(config('web', 'stripes', 1))
325 mapfile = templater.stylemap(style)
319 mapfile = templater.stylemap(style)
326 tmpl = templater.templater(mapfile, templatefilters.filters,
320 tmpl = templater.templater(mapfile, templatefilters.filters,
327 defaults={"header": header,
321 defaults={"header": header,
328 "footer": footer,
322 "footer": footer,
329 "motd": motd,
323 "motd": motd,
330 "url": url,
324 "url": url,
331 "staticurl": staticurl})
325 "staticurl": staticurl})
332 return tmpl
326 return tmpl
General Comments 0
You need to be logged in to leave comments. Login now