##// END OF EJS Templates
hgweb: prevent loading style map from directories other than specified paths...
Yuya Nishihara -
r24296:b73a22d1 stable
parent child Browse files
Show More
@@ -1,761 +1,765 b''
1 # templater.py - template expansion for output
1 # templater.py - template expansion for output
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 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 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import os, re
9 import os, re
10 import util, config, templatefilters, templatekw, parser, error
10 import util, config, templatefilters, templatekw, parser, error
11 import revset as revsetmod
11 import revset as revsetmod
12 import types
12 import types
13 import minirst
13 import minirst
14
14
15 # template parsing
15 # template parsing
16
16
17 elements = {
17 elements = {
18 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
18 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
19 ",": (2, None, ("list", 2)),
19 ",": (2, None, ("list", 2)),
20 "|": (5, None, ("|", 5)),
20 "|": (5, None, ("|", 5)),
21 "%": (6, None, ("%", 6)),
21 "%": (6, None, ("%", 6)),
22 ")": (0, None, None),
22 ")": (0, None, None),
23 "symbol": (0, ("symbol",), None),
23 "symbol": (0, ("symbol",), None),
24 "string": (0, ("string",), None),
24 "string": (0, ("string",), None),
25 "rawstring": (0, ("rawstring",), None),
25 "rawstring": (0, ("rawstring",), None),
26 "end": (0, None, None),
26 "end": (0, None, None),
27 }
27 }
28
28
29 def tokenizer(data):
29 def tokenizer(data):
30 program, start, end = data
30 program, start, end = data
31 pos = start
31 pos = start
32 while pos < end:
32 while pos < end:
33 c = program[pos]
33 c = program[pos]
34 if c.isspace(): # skip inter-token whitespace
34 if c.isspace(): # skip inter-token whitespace
35 pass
35 pass
36 elif c in "(,)%|": # handle simple operators
36 elif c in "(,)%|": # handle simple operators
37 yield (c, None, pos)
37 yield (c, None, pos)
38 elif (c in '"\'' or c == 'r' and
38 elif (c in '"\'' or c == 'r' and
39 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
39 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
40 if c == 'r':
40 if c == 'r':
41 pos += 1
41 pos += 1
42 c = program[pos]
42 c = program[pos]
43 decode = False
43 decode = False
44 else:
44 else:
45 decode = True
45 decode = True
46 pos += 1
46 pos += 1
47 s = pos
47 s = pos
48 while pos < end: # find closing quote
48 while pos < end: # find closing quote
49 d = program[pos]
49 d = program[pos]
50 if decode and d == '\\': # skip over escaped characters
50 if decode and d == '\\': # skip over escaped characters
51 pos += 2
51 pos += 2
52 continue
52 continue
53 if d == c:
53 if d == c:
54 if not decode:
54 if not decode:
55 yield ('rawstring', program[s:pos], s)
55 yield ('rawstring', program[s:pos], s)
56 break
56 break
57 yield ('string', program[s:pos], s)
57 yield ('string', program[s:pos], s)
58 break
58 break
59 pos += 1
59 pos += 1
60 else:
60 else:
61 raise error.ParseError(_("unterminated string"), s)
61 raise error.ParseError(_("unterminated string"), s)
62 elif c.isalnum() or c in '_':
62 elif c.isalnum() or c in '_':
63 s = pos
63 s = pos
64 pos += 1
64 pos += 1
65 while pos < end: # find end of symbol
65 while pos < end: # find end of symbol
66 d = program[pos]
66 d = program[pos]
67 if not (d.isalnum() or d == "_"):
67 if not (d.isalnum() or d == "_"):
68 break
68 break
69 pos += 1
69 pos += 1
70 sym = program[s:pos]
70 sym = program[s:pos]
71 yield ('symbol', sym, s)
71 yield ('symbol', sym, s)
72 pos -= 1
72 pos -= 1
73 elif c == '}':
73 elif c == '}':
74 pos += 1
74 pos += 1
75 break
75 break
76 else:
76 else:
77 raise error.ParseError(_("syntax error"), pos)
77 raise error.ParseError(_("syntax error"), pos)
78 pos += 1
78 pos += 1
79 yield ('end', None, pos)
79 yield ('end', None, pos)
80
80
81 def compiletemplate(tmpl, context, strtoken="string"):
81 def compiletemplate(tmpl, context, strtoken="string"):
82 parsed = []
82 parsed = []
83 pos, stop = 0, len(tmpl)
83 pos, stop = 0, len(tmpl)
84 p = parser.parser(tokenizer, elements)
84 p = parser.parser(tokenizer, elements)
85 while pos < stop:
85 while pos < stop:
86 n = tmpl.find('{', pos)
86 n = tmpl.find('{', pos)
87 if n < 0:
87 if n < 0:
88 parsed.append((strtoken, tmpl[pos:]))
88 parsed.append((strtoken, tmpl[pos:]))
89 break
89 break
90 if n > 0 and tmpl[n - 1] == '\\':
90 if n > 0 and tmpl[n - 1] == '\\':
91 # escaped
91 # escaped
92 parsed.append((strtoken, (tmpl[pos:n - 1] + "{")))
92 parsed.append((strtoken, (tmpl[pos:n - 1] + "{")))
93 pos = n + 1
93 pos = n + 1
94 continue
94 continue
95 if n > pos:
95 if n > pos:
96 parsed.append((strtoken, tmpl[pos:n]))
96 parsed.append((strtoken, tmpl[pos:n]))
97
97
98 pd = [tmpl, n + 1, stop]
98 pd = [tmpl, n + 1, stop]
99 parseres, pos = p.parse(pd)
99 parseres, pos = p.parse(pd)
100 parsed.append(parseres)
100 parsed.append(parseres)
101
101
102 return [compileexp(e, context) for e in parsed]
102 return [compileexp(e, context) for e in parsed]
103
103
104 def compileexp(exp, context):
104 def compileexp(exp, context):
105 t = exp[0]
105 t = exp[0]
106 if t in methods:
106 if t in methods:
107 return methods[t](exp, context)
107 return methods[t](exp, context)
108 raise error.ParseError(_("unknown method '%s'") % t)
108 raise error.ParseError(_("unknown method '%s'") % t)
109
109
110 # template evaluation
110 # template evaluation
111
111
112 def getsymbol(exp):
112 def getsymbol(exp):
113 if exp[0] == 'symbol':
113 if exp[0] == 'symbol':
114 return exp[1]
114 return exp[1]
115 raise error.ParseError(_("expected a symbol, got '%s'") % exp[0])
115 raise error.ParseError(_("expected a symbol, got '%s'") % exp[0])
116
116
117 def getlist(x):
117 def getlist(x):
118 if not x:
118 if not x:
119 return []
119 return []
120 if x[0] == 'list':
120 if x[0] == 'list':
121 return getlist(x[1]) + [x[2]]
121 return getlist(x[1]) + [x[2]]
122 return [x]
122 return [x]
123
123
124 def getfilter(exp, context):
124 def getfilter(exp, context):
125 f = getsymbol(exp)
125 f = getsymbol(exp)
126 if f not in context._filters:
126 if f not in context._filters:
127 raise error.ParseError(_("unknown function '%s'") % f)
127 raise error.ParseError(_("unknown function '%s'") % f)
128 return context._filters[f]
128 return context._filters[f]
129
129
130 def gettemplate(exp, context):
130 def gettemplate(exp, context):
131 if exp[0] == 'string' or exp[0] == 'rawstring':
131 if exp[0] == 'string' or exp[0] == 'rawstring':
132 return compiletemplate(exp[1], context, strtoken=exp[0])
132 return compiletemplate(exp[1], context, strtoken=exp[0])
133 if exp[0] == 'symbol':
133 if exp[0] == 'symbol':
134 return context._load(exp[1])
134 return context._load(exp[1])
135 raise error.ParseError(_("expected template specifier"))
135 raise error.ParseError(_("expected template specifier"))
136
136
137 def runstring(context, mapping, data):
137 def runstring(context, mapping, data):
138 return data.decode("string-escape")
138 return data.decode("string-escape")
139
139
140 def runrawstring(context, mapping, data):
140 def runrawstring(context, mapping, data):
141 return data
141 return data
142
142
143 def runsymbol(context, mapping, key):
143 def runsymbol(context, mapping, key):
144 v = mapping.get(key)
144 v = mapping.get(key)
145 if v is None:
145 if v is None:
146 v = context._defaults.get(key)
146 v = context._defaults.get(key)
147 if v is None:
147 if v is None:
148 try:
148 try:
149 v = context.process(key, mapping)
149 v = context.process(key, mapping)
150 except TemplateNotFound:
150 except TemplateNotFound:
151 v = ''
151 v = ''
152 if callable(v):
152 if callable(v):
153 return v(**mapping)
153 return v(**mapping)
154 if isinstance(v, types.GeneratorType):
154 if isinstance(v, types.GeneratorType):
155 v = list(v)
155 v = list(v)
156 return v
156 return v
157
157
158 def buildfilter(exp, context):
158 def buildfilter(exp, context):
159 func, data = compileexp(exp[1], context)
159 func, data = compileexp(exp[1], context)
160 filt = getfilter(exp[2], context)
160 filt = getfilter(exp[2], context)
161 return (runfilter, (func, data, filt))
161 return (runfilter, (func, data, filt))
162
162
163 def runfilter(context, mapping, data):
163 def runfilter(context, mapping, data):
164 func, data, filt = data
164 func, data, filt = data
165 try:
165 try:
166 return filt(func(context, mapping, data))
166 return filt(func(context, mapping, data))
167 except (ValueError, AttributeError, TypeError):
167 except (ValueError, AttributeError, TypeError):
168 if isinstance(data, tuple):
168 if isinstance(data, tuple):
169 dt = data[1]
169 dt = data[1]
170 else:
170 else:
171 dt = data
171 dt = data
172 raise util.Abort(_("template filter '%s' is not compatible with "
172 raise util.Abort(_("template filter '%s' is not compatible with "
173 "keyword '%s'") % (filt.func_name, dt))
173 "keyword '%s'") % (filt.func_name, dt))
174
174
175 def buildmap(exp, context):
175 def buildmap(exp, context):
176 func, data = compileexp(exp[1], context)
176 func, data = compileexp(exp[1], context)
177 ctmpl = gettemplate(exp[2], context)
177 ctmpl = gettemplate(exp[2], context)
178 return (runmap, (func, data, ctmpl))
178 return (runmap, (func, data, ctmpl))
179
179
180 def runtemplate(context, mapping, template):
180 def runtemplate(context, mapping, template):
181 for func, data in template:
181 for func, data in template:
182 yield func(context, mapping, data)
182 yield func(context, mapping, data)
183
183
184 def runmap(context, mapping, data):
184 def runmap(context, mapping, data):
185 func, data, ctmpl = data
185 func, data, ctmpl = data
186 d = func(context, mapping, data)
186 d = func(context, mapping, data)
187 if callable(d):
187 if callable(d):
188 d = d()
188 d = d()
189
189
190 lm = mapping.copy()
190 lm = mapping.copy()
191
191
192 for i in d:
192 for i in d:
193 if isinstance(i, dict):
193 if isinstance(i, dict):
194 lm.update(i)
194 lm.update(i)
195 lm['originalnode'] = mapping.get('node')
195 lm['originalnode'] = mapping.get('node')
196 yield runtemplate(context, lm, ctmpl)
196 yield runtemplate(context, lm, ctmpl)
197 else:
197 else:
198 # v is not an iterable of dicts, this happen when 'key'
198 # v is not an iterable of dicts, this happen when 'key'
199 # has been fully expanded already and format is useless.
199 # has been fully expanded already and format is useless.
200 # If so, return the expanded value.
200 # If so, return the expanded value.
201 yield i
201 yield i
202
202
203 def buildfunc(exp, context):
203 def buildfunc(exp, context):
204 n = getsymbol(exp[1])
204 n = getsymbol(exp[1])
205 args = [compileexp(x, context) for x in getlist(exp[2])]
205 args = [compileexp(x, context) for x in getlist(exp[2])]
206 if n in funcs:
206 if n in funcs:
207 f = funcs[n]
207 f = funcs[n]
208 return (f, args)
208 return (f, args)
209 if n in context._filters:
209 if n in context._filters:
210 if len(args) != 1:
210 if len(args) != 1:
211 raise error.ParseError(_("filter %s expects one argument") % n)
211 raise error.ParseError(_("filter %s expects one argument") % n)
212 f = context._filters[n]
212 f = context._filters[n]
213 return (runfilter, (args[0][0], args[0][1], f))
213 return (runfilter, (args[0][0], args[0][1], f))
214 raise error.ParseError(_("unknown function '%s'") % n)
214 raise error.ParseError(_("unknown function '%s'") % n)
215
215
216 def date(context, mapping, args):
216 def date(context, mapping, args):
217 if not (1 <= len(args) <= 2):
217 if not (1 <= len(args) <= 2):
218 # i18n: "date" is a keyword
218 # i18n: "date" is a keyword
219 raise error.ParseError(_("date expects one or two arguments"))
219 raise error.ParseError(_("date expects one or two arguments"))
220
220
221 date = args[0][0](context, mapping, args[0][1])
221 date = args[0][0](context, mapping, args[0][1])
222 if len(args) == 2:
222 if len(args) == 2:
223 fmt = stringify(args[1][0](context, mapping, args[1][1]))
223 fmt = stringify(args[1][0](context, mapping, args[1][1]))
224 return util.datestr(date, fmt)
224 return util.datestr(date, fmt)
225 return util.datestr(date)
225 return util.datestr(date)
226
226
227 def diff(context, mapping, args):
227 def diff(context, mapping, args):
228 if len(args) > 2:
228 if len(args) > 2:
229 # i18n: "diff" is a keyword
229 # i18n: "diff" is a keyword
230 raise error.ParseError(_("diff expects one, two or no arguments"))
230 raise error.ParseError(_("diff expects one, two or no arguments"))
231
231
232 def getpatterns(i):
232 def getpatterns(i):
233 if i < len(args):
233 if i < len(args):
234 s = args[i][1].strip()
234 s = args[i][1].strip()
235 if s:
235 if s:
236 return [s]
236 return [s]
237 return []
237 return []
238
238
239 ctx = mapping['ctx']
239 ctx = mapping['ctx']
240 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
240 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
241
241
242 return ''.join(chunks)
242 return ''.join(chunks)
243
243
244 def fill(context, mapping, args):
244 def fill(context, mapping, args):
245 if not (1 <= len(args) <= 4):
245 if not (1 <= len(args) <= 4):
246 # i18n: "fill" is a keyword
246 # i18n: "fill" is a keyword
247 raise error.ParseError(_("fill expects one to four arguments"))
247 raise error.ParseError(_("fill expects one to four arguments"))
248
248
249 text = stringify(args[0][0](context, mapping, args[0][1]))
249 text = stringify(args[0][0](context, mapping, args[0][1]))
250 width = 76
250 width = 76
251 initindent = ''
251 initindent = ''
252 hangindent = ''
252 hangindent = ''
253 if 2 <= len(args) <= 4:
253 if 2 <= len(args) <= 4:
254 try:
254 try:
255 width = int(stringify(args[1][0](context, mapping, args[1][1])))
255 width = int(stringify(args[1][0](context, mapping, args[1][1])))
256 except ValueError:
256 except ValueError:
257 # i18n: "fill" is a keyword
257 # i18n: "fill" is a keyword
258 raise error.ParseError(_("fill expects an integer width"))
258 raise error.ParseError(_("fill expects an integer width"))
259 try:
259 try:
260 initindent = stringify(_evalifliteral(args[2], context, mapping))
260 initindent = stringify(_evalifliteral(args[2], context, mapping))
261 hangindent = stringify(_evalifliteral(args[3], context, mapping))
261 hangindent = stringify(_evalifliteral(args[3], context, mapping))
262 except IndexError:
262 except IndexError:
263 pass
263 pass
264
264
265 return templatefilters.fill(text, width, initindent, hangindent)
265 return templatefilters.fill(text, width, initindent, hangindent)
266
266
267 def pad(context, mapping, args):
267 def pad(context, mapping, args):
268 """usage: pad(text, width, fillchar=' ', right=False)
268 """usage: pad(text, width, fillchar=' ', right=False)
269 """
269 """
270 if not (2 <= len(args) <= 4):
270 if not (2 <= len(args) <= 4):
271 # i18n: "pad" is a keyword
271 # i18n: "pad" is a keyword
272 raise error.ParseError(_("pad() expects two to four arguments"))
272 raise error.ParseError(_("pad() expects two to four arguments"))
273
273
274 width = int(args[1][1])
274 width = int(args[1][1])
275
275
276 text = stringify(args[0][0](context, mapping, args[0][1]))
276 text = stringify(args[0][0](context, mapping, args[0][1]))
277 if args[0][0] == runstring:
277 if args[0][0] == runstring:
278 text = stringify(runtemplate(context, mapping,
278 text = stringify(runtemplate(context, mapping,
279 compiletemplate(text, context)))
279 compiletemplate(text, context)))
280
280
281 right = False
281 right = False
282 fillchar = ' '
282 fillchar = ' '
283 if len(args) > 2:
283 if len(args) > 2:
284 fillchar = stringify(args[2][0](context, mapping, args[2][1]))
284 fillchar = stringify(args[2][0](context, mapping, args[2][1]))
285 if len(args) > 3:
285 if len(args) > 3:
286 right = util.parsebool(args[3][1])
286 right = util.parsebool(args[3][1])
287
287
288 if right:
288 if right:
289 return text.rjust(width, fillchar)
289 return text.rjust(width, fillchar)
290 else:
290 else:
291 return text.ljust(width, fillchar)
291 return text.ljust(width, fillchar)
292
292
293 def get(context, mapping, args):
293 def get(context, mapping, args):
294 if len(args) != 2:
294 if len(args) != 2:
295 # i18n: "get" is a keyword
295 # i18n: "get" is a keyword
296 raise error.ParseError(_("get() expects two arguments"))
296 raise error.ParseError(_("get() expects two arguments"))
297
297
298 dictarg = args[0][0](context, mapping, args[0][1])
298 dictarg = args[0][0](context, mapping, args[0][1])
299 if not util.safehasattr(dictarg, 'get'):
299 if not util.safehasattr(dictarg, 'get'):
300 # i18n: "get" is a keyword
300 # i18n: "get" is a keyword
301 raise error.ParseError(_("get() expects a dict as first argument"))
301 raise error.ParseError(_("get() expects a dict as first argument"))
302
302
303 key = args[1][0](context, mapping, args[1][1])
303 key = args[1][0](context, mapping, args[1][1])
304 yield dictarg.get(key)
304 yield dictarg.get(key)
305
305
306 def _evalifliteral(arg, context, mapping):
306 def _evalifliteral(arg, context, mapping):
307 t = stringify(arg[0](context, mapping, arg[1]))
307 t = stringify(arg[0](context, mapping, arg[1]))
308 if arg[0] == runstring or arg[0] == runrawstring:
308 if arg[0] == runstring or arg[0] == runrawstring:
309 yield runtemplate(context, mapping,
309 yield runtemplate(context, mapping,
310 compiletemplate(t, context, strtoken='rawstring'))
310 compiletemplate(t, context, strtoken='rawstring'))
311 else:
311 else:
312 yield t
312 yield t
313
313
314 def if_(context, mapping, args):
314 def if_(context, mapping, args):
315 if not (2 <= len(args) <= 3):
315 if not (2 <= len(args) <= 3):
316 # i18n: "if" is a keyword
316 # i18n: "if" is a keyword
317 raise error.ParseError(_("if expects two or three arguments"))
317 raise error.ParseError(_("if expects two or three arguments"))
318
318
319 test = stringify(args[0][0](context, mapping, args[0][1]))
319 test = stringify(args[0][0](context, mapping, args[0][1]))
320 if test:
320 if test:
321 yield _evalifliteral(args[1], context, mapping)
321 yield _evalifliteral(args[1], context, mapping)
322 elif len(args) == 3:
322 elif len(args) == 3:
323 yield _evalifliteral(args[2], context, mapping)
323 yield _evalifliteral(args[2], context, mapping)
324
324
325 def ifcontains(context, mapping, args):
325 def ifcontains(context, mapping, args):
326 if not (3 <= len(args) <= 4):
326 if not (3 <= len(args) <= 4):
327 # i18n: "ifcontains" is a keyword
327 # i18n: "ifcontains" is a keyword
328 raise error.ParseError(_("ifcontains expects three or four arguments"))
328 raise error.ParseError(_("ifcontains expects three or four arguments"))
329
329
330 item = stringify(args[0][0](context, mapping, args[0][1]))
330 item = stringify(args[0][0](context, mapping, args[0][1]))
331 items = args[1][0](context, mapping, args[1][1])
331 items = args[1][0](context, mapping, args[1][1])
332
332
333 # Iterating over items gives a formatted string, so we iterate
333 # Iterating over items gives a formatted string, so we iterate
334 # directly over the raw values.
334 # directly over the raw values.
335 if ((callable(items) and item in [i.values()[0] for i in items()]) or
335 if ((callable(items) and item in [i.values()[0] for i in items()]) or
336 (isinstance(items, str) and item in items)):
336 (isinstance(items, str) and item in items)):
337 yield _evalifliteral(args[2], context, mapping)
337 yield _evalifliteral(args[2], context, mapping)
338 elif len(args) == 4:
338 elif len(args) == 4:
339 yield _evalifliteral(args[3], context, mapping)
339 yield _evalifliteral(args[3], context, mapping)
340
340
341 def ifeq(context, mapping, args):
341 def ifeq(context, mapping, args):
342 if not (3 <= len(args) <= 4):
342 if not (3 <= len(args) <= 4):
343 # i18n: "ifeq" is a keyword
343 # i18n: "ifeq" is a keyword
344 raise error.ParseError(_("ifeq expects three or four arguments"))
344 raise error.ParseError(_("ifeq expects three or four arguments"))
345
345
346 test = stringify(args[0][0](context, mapping, args[0][1]))
346 test = stringify(args[0][0](context, mapping, args[0][1]))
347 match = stringify(args[1][0](context, mapping, args[1][1]))
347 match = stringify(args[1][0](context, mapping, args[1][1]))
348 if test == match:
348 if test == match:
349 yield _evalifliteral(args[2], context, mapping)
349 yield _evalifliteral(args[2], context, mapping)
350 elif len(args) == 4:
350 elif len(args) == 4:
351 yield _evalifliteral(args[3], context, mapping)
351 yield _evalifliteral(args[3], context, mapping)
352
352
353 def join(context, mapping, args):
353 def join(context, mapping, args):
354 if not (1 <= len(args) <= 2):
354 if not (1 <= len(args) <= 2):
355 # i18n: "join" is a keyword
355 # i18n: "join" is a keyword
356 raise error.ParseError(_("join expects one or two arguments"))
356 raise error.ParseError(_("join expects one or two arguments"))
357
357
358 joinset = args[0][0](context, mapping, args[0][1])
358 joinset = args[0][0](context, mapping, args[0][1])
359 if callable(joinset):
359 if callable(joinset):
360 jf = joinset.joinfmt
360 jf = joinset.joinfmt
361 joinset = [jf(x) for x in joinset()]
361 joinset = [jf(x) for x in joinset()]
362
362
363 joiner = " "
363 joiner = " "
364 if len(args) > 1:
364 if len(args) > 1:
365 joiner = stringify(args[1][0](context, mapping, args[1][1]))
365 joiner = stringify(args[1][0](context, mapping, args[1][1]))
366
366
367 first = True
367 first = True
368 for x in joinset:
368 for x in joinset:
369 if first:
369 if first:
370 first = False
370 first = False
371 else:
371 else:
372 yield joiner
372 yield joiner
373 yield x
373 yield x
374
374
375 def label(context, mapping, args):
375 def label(context, mapping, args):
376 if len(args) != 2:
376 if len(args) != 2:
377 # i18n: "label" is a keyword
377 # i18n: "label" is a keyword
378 raise error.ParseError(_("label expects two arguments"))
378 raise error.ParseError(_("label expects two arguments"))
379
379
380 # ignore args[0] (the label string) since this is supposed to be a a no-op
380 # ignore args[0] (the label string) since this is supposed to be a a no-op
381 yield _evalifliteral(args[1], context, mapping)
381 yield _evalifliteral(args[1], context, mapping)
382
382
383 def revset(context, mapping, args):
383 def revset(context, mapping, args):
384 """usage: revset(query[, formatargs...])
384 """usage: revset(query[, formatargs...])
385 """
385 """
386 if not len(args) > 0:
386 if not len(args) > 0:
387 # i18n: "revset" is a keyword
387 # i18n: "revset" is a keyword
388 raise error.ParseError(_("revset expects one or more arguments"))
388 raise error.ParseError(_("revset expects one or more arguments"))
389
389
390 raw = args[0][1]
390 raw = args[0][1]
391 ctx = mapping['ctx']
391 ctx = mapping['ctx']
392 repo = ctx._repo
392 repo = ctx._repo
393
393
394 def query(expr):
394 def query(expr):
395 m = revsetmod.match(repo.ui, expr)
395 m = revsetmod.match(repo.ui, expr)
396 return m(repo, revsetmod.spanset(repo))
396 return m(repo, revsetmod.spanset(repo))
397
397
398 if len(args) > 1:
398 if len(args) > 1:
399 formatargs = list([a[0](context, mapping, a[1]) for a in args[1:]])
399 formatargs = list([a[0](context, mapping, a[1]) for a in args[1:]])
400 revs = query(revsetmod.formatspec(raw, *formatargs))
400 revs = query(revsetmod.formatspec(raw, *formatargs))
401 revs = list([str(r) for r in revs])
401 revs = list([str(r) for r in revs])
402 else:
402 else:
403 revsetcache = mapping['cache'].setdefault("revsetcache", {})
403 revsetcache = mapping['cache'].setdefault("revsetcache", {})
404 if raw in revsetcache:
404 if raw in revsetcache:
405 revs = revsetcache[raw]
405 revs = revsetcache[raw]
406 else:
406 else:
407 revs = query(raw)
407 revs = query(raw)
408 revs = list([str(r) for r in revs])
408 revs = list([str(r) for r in revs])
409 revsetcache[raw] = revs
409 revsetcache[raw] = revs
410
410
411 return templatekw.showlist("revision", revs, **mapping)
411 return templatekw.showlist("revision", revs, **mapping)
412
412
413 def rstdoc(context, mapping, args):
413 def rstdoc(context, mapping, args):
414 if len(args) != 2:
414 if len(args) != 2:
415 # i18n: "rstdoc" is a keyword
415 # i18n: "rstdoc" is a keyword
416 raise error.ParseError(_("rstdoc expects two arguments"))
416 raise error.ParseError(_("rstdoc expects two arguments"))
417
417
418 text = stringify(args[0][0](context, mapping, args[0][1]))
418 text = stringify(args[0][0](context, mapping, args[0][1]))
419 style = stringify(args[1][0](context, mapping, args[1][1]))
419 style = stringify(args[1][0](context, mapping, args[1][1]))
420
420
421 return minirst.format(text, style=style, keep=['verbose'])
421 return minirst.format(text, style=style, keep=['verbose'])
422
422
423 def shortest(context, mapping, args):
423 def shortest(context, mapping, args):
424 """usage: shortest(node, minlength=4)
424 """usage: shortest(node, minlength=4)
425 """
425 """
426 if not (1 <= len(args) <= 2):
426 if not (1 <= len(args) <= 2):
427 # i18n: "shortest" is a keyword
427 # i18n: "shortest" is a keyword
428 raise error.ParseError(_("shortest() expects one or two arguments"))
428 raise error.ParseError(_("shortest() expects one or two arguments"))
429
429
430 node = stringify(args[0][0](context, mapping, args[0][1]))
430 node = stringify(args[0][0](context, mapping, args[0][1]))
431
431
432 minlength = 4
432 minlength = 4
433 if len(args) > 1:
433 if len(args) > 1:
434 minlength = int(args[1][1])
434 minlength = int(args[1][1])
435
435
436 cl = mapping['ctx']._repo.changelog
436 cl = mapping['ctx']._repo.changelog
437 def isvalid(test):
437 def isvalid(test):
438 try:
438 try:
439 try:
439 try:
440 cl.index.partialmatch(test)
440 cl.index.partialmatch(test)
441 except AttributeError:
441 except AttributeError:
442 # Pure mercurial doesn't support partialmatch on the index.
442 # Pure mercurial doesn't support partialmatch on the index.
443 # Fallback to the slow way.
443 # Fallback to the slow way.
444 if cl._partialmatch(test) is None:
444 if cl._partialmatch(test) is None:
445 return False
445 return False
446
446
447 try:
447 try:
448 i = int(test)
448 i = int(test)
449 # if we are a pure int, then starting with zero will not be
449 # if we are a pure int, then starting with zero will not be
450 # confused as a rev; or, obviously, if the int is larger than
450 # confused as a rev; or, obviously, if the int is larger than
451 # the value of the tip rev
451 # the value of the tip rev
452 if test[0] == '0' or i > len(cl):
452 if test[0] == '0' or i > len(cl):
453 return True
453 return True
454 return False
454 return False
455 except ValueError:
455 except ValueError:
456 return True
456 return True
457 except error.RevlogError:
457 except error.RevlogError:
458 return False
458 return False
459
459
460 shortest = node
460 shortest = node
461 startlength = max(6, minlength)
461 startlength = max(6, minlength)
462 length = startlength
462 length = startlength
463 while True:
463 while True:
464 test = node[:length]
464 test = node[:length]
465 if isvalid(test):
465 if isvalid(test):
466 shortest = test
466 shortest = test
467 if length == minlength or length > startlength:
467 if length == minlength or length > startlength:
468 return shortest
468 return shortest
469 length -= 1
469 length -= 1
470 else:
470 else:
471 length += 1
471 length += 1
472 if len(shortest) <= length:
472 if len(shortest) <= length:
473 return shortest
473 return shortest
474
474
475 def strip(context, mapping, args):
475 def strip(context, mapping, args):
476 if not (1 <= len(args) <= 2):
476 if not (1 <= len(args) <= 2):
477 # i18n: "strip" is a keyword
477 # i18n: "strip" is a keyword
478 raise error.ParseError(_("strip expects one or two arguments"))
478 raise error.ParseError(_("strip expects one or two arguments"))
479
479
480 text = stringify(args[0][0](context, mapping, args[0][1]))
480 text = stringify(args[0][0](context, mapping, args[0][1]))
481 if len(args) == 2:
481 if len(args) == 2:
482 chars = stringify(args[1][0](context, mapping, args[1][1]))
482 chars = stringify(args[1][0](context, mapping, args[1][1]))
483 return text.strip(chars)
483 return text.strip(chars)
484 return text.strip()
484 return text.strip()
485
485
486 def sub(context, mapping, args):
486 def sub(context, mapping, args):
487 if len(args) != 3:
487 if len(args) != 3:
488 # i18n: "sub" is a keyword
488 # i18n: "sub" is a keyword
489 raise error.ParseError(_("sub expects three arguments"))
489 raise error.ParseError(_("sub expects three arguments"))
490
490
491 pat = stringify(args[0][0](context, mapping, args[0][1]))
491 pat = stringify(args[0][0](context, mapping, args[0][1]))
492 rpl = stringify(args[1][0](context, mapping, args[1][1]))
492 rpl = stringify(args[1][0](context, mapping, args[1][1]))
493 src = stringify(_evalifliteral(args[2], context, mapping))
493 src = stringify(_evalifliteral(args[2], context, mapping))
494 yield re.sub(pat, rpl, src)
494 yield re.sub(pat, rpl, src)
495
495
496 def startswith(context, mapping, args):
496 def startswith(context, mapping, args):
497 if len(args) != 2:
497 if len(args) != 2:
498 # i18n: "startswith" is a keyword
498 # i18n: "startswith" is a keyword
499 raise error.ParseError(_("startswith expects two arguments"))
499 raise error.ParseError(_("startswith expects two arguments"))
500
500
501 patn = stringify(args[0][0](context, mapping, args[0][1]))
501 patn = stringify(args[0][0](context, mapping, args[0][1]))
502 text = stringify(args[1][0](context, mapping, args[1][1]))
502 text = stringify(args[1][0](context, mapping, args[1][1]))
503 if text.startswith(patn):
503 if text.startswith(patn):
504 return text
504 return text
505 return ''
505 return ''
506
506
507
507
508 def word(context, mapping, args):
508 def word(context, mapping, args):
509 """return nth word from a string"""
509 """return nth word from a string"""
510 if not (2 <= len(args) <= 3):
510 if not (2 <= len(args) <= 3):
511 # i18n: "word" is a keyword
511 # i18n: "word" is a keyword
512 raise error.ParseError(_("word expects two or three arguments, got %d")
512 raise error.ParseError(_("word expects two or three arguments, got %d")
513 % len(args))
513 % len(args))
514
514
515 num = int(stringify(args[0][0](context, mapping, args[0][1])))
515 num = int(stringify(args[0][0](context, mapping, args[0][1])))
516 text = stringify(args[1][0](context, mapping, args[1][1]))
516 text = stringify(args[1][0](context, mapping, args[1][1]))
517 if len(args) == 3:
517 if len(args) == 3:
518 splitter = stringify(args[2][0](context, mapping, args[2][1]))
518 splitter = stringify(args[2][0](context, mapping, args[2][1]))
519 else:
519 else:
520 splitter = None
520 splitter = None
521
521
522 tokens = text.split(splitter)
522 tokens = text.split(splitter)
523 if num >= len(tokens):
523 if num >= len(tokens):
524 return ''
524 return ''
525 else:
525 else:
526 return tokens[num]
526 return tokens[num]
527
527
528 methods = {
528 methods = {
529 "string": lambda e, c: (runstring, e[1]),
529 "string": lambda e, c: (runstring, e[1]),
530 "rawstring": lambda e, c: (runrawstring, e[1]),
530 "rawstring": lambda e, c: (runrawstring, e[1]),
531 "symbol": lambda e, c: (runsymbol, e[1]),
531 "symbol": lambda e, c: (runsymbol, e[1]),
532 "group": lambda e, c: compileexp(e[1], c),
532 "group": lambda e, c: compileexp(e[1], c),
533 # ".": buildmember,
533 # ".": buildmember,
534 "|": buildfilter,
534 "|": buildfilter,
535 "%": buildmap,
535 "%": buildmap,
536 "func": buildfunc,
536 "func": buildfunc,
537 }
537 }
538
538
539 funcs = {
539 funcs = {
540 "date": date,
540 "date": date,
541 "diff": diff,
541 "diff": diff,
542 "fill": fill,
542 "fill": fill,
543 "get": get,
543 "get": get,
544 "if": if_,
544 "if": if_,
545 "ifcontains": ifcontains,
545 "ifcontains": ifcontains,
546 "ifeq": ifeq,
546 "ifeq": ifeq,
547 "join": join,
547 "join": join,
548 "label": label,
548 "label": label,
549 "pad": pad,
549 "pad": pad,
550 "revset": revset,
550 "revset": revset,
551 "rstdoc": rstdoc,
551 "rstdoc": rstdoc,
552 "shortest": shortest,
552 "shortest": shortest,
553 "startswith": startswith,
553 "startswith": startswith,
554 "strip": strip,
554 "strip": strip,
555 "sub": sub,
555 "sub": sub,
556 "word": word,
556 "word": word,
557 }
557 }
558
558
559 # template engine
559 # template engine
560
560
561 stringify = templatefilters.stringify
561 stringify = templatefilters.stringify
562
562
563 def _flatten(thing):
563 def _flatten(thing):
564 '''yield a single stream from a possibly nested set of iterators'''
564 '''yield a single stream from a possibly nested set of iterators'''
565 if isinstance(thing, str):
565 if isinstance(thing, str):
566 yield thing
566 yield thing
567 elif not util.safehasattr(thing, '__iter__'):
567 elif not util.safehasattr(thing, '__iter__'):
568 if thing is not None:
568 if thing is not None:
569 yield str(thing)
569 yield str(thing)
570 else:
570 else:
571 for i in thing:
571 for i in thing:
572 if isinstance(i, str):
572 if isinstance(i, str):
573 yield i
573 yield i
574 elif not util.safehasattr(i, '__iter__'):
574 elif not util.safehasattr(i, '__iter__'):
575 if i is not None:
575 if i is not None:
576 yield str(i)
576 yield str(i)
577 elif i is not None:
577 elif i is not None:
578 for j in _flatten(i):
578 for j in _flatten(i):
579 yield j
579 yield j
580
580
581 def parsestring(s, quoted=True):
581 def parsestring(s, quoted=True):
582 '''parse a string using simple c-like syntax.
582 '''parse a string using simple c-like syntax.
583 string must be in quotes if quoted is True.'''
583 string must be in quotes if quoted is True.'''
584 if quoted:
584 if quoted:
585 if len(s) < 2 or s[0] != s[-1]:
585 if len(s) < 2 or s[0] != s[-1]:
586 raise SyntaxError(_('unmatched quotes'))
586 raise SyntaxError(_('unmatched quotes'))
587 return s[1:-1].decode('string_escape')
587 return s[1:-1].decode('string_escape')
588
588
589 return s.decode('string_escape')
589 return s.decode('string_escape')
590
590
591 class engine(object):
591 class engine(object):
592 '''template expansion engine.
592 '''template expansion engine.
593
593
594 template expansion works like this. a map file contains key=value
594 template expansion works like this. a map file contains key=value
595 pairs. if value is quoted, it is treated as string. otherwise, it
595 pairs. if value is quoted, it is treated as string. otherwise, it
596 is treated as name of template file.
596 is treated as name of template file.
597
597
598 templater is asked to expand a key in map. it looks up key, and
598 templater is asked to expand a key in map. it looks up key, and
599 looks for strings like this: {foo}. it expands {foo} by looking up
599 looks for strings like this: {foo}. it expands {foo} by looking up
600 foo in map, and substituting it. expansion is recursive: it stops
600 foo in map, and substituting it. expansion is recursive: it stops
601 when there is no more {foo} to replace.
601 when there is no more {foo} to replace.
602
602
603 expansion also allows formatting and filtering.
603 expansion also allows formatting and filtering.
604
604
605 format uses key to expand each item in list. syntax is
605 format uses key to expand each item in list. syntax is
606 {key%format}.
606 {key%format}.
607
607
608 filter uses function to transform value. syntax is
608 filter uses function to transform value. syntax is
609 {key|filter1|filter2|...}.'''
609 {key|filter1|filter2|...}.'''
610
610
611 def __init__(self, loader, filters={}, defaults={}):
611 def __init__(self, loader, filters={}, defaults={}):
612 self._loader = loader
612 self._loader = loader
613 self._filters = filters
613 self._filters = filters
614 self._defaults = defaults
614 self._defaults = defaults
615 self._cache = {}
615 self._cache = {}
616
616
617 def _load(self, t):
617 def _load(self, t):
618 '''load, parse, and cache a template'''
618 '''load, parse, and cache a template'''
619 if t not in self._cache:
619 if t not in self._cache:
620 self._cache[t] = compiletemplate(self._loader(t), self)
620 self._cache[t] = compiletemplate(self._loader(t), self)
621 return self._cache[t]
621 return self._cache[t]
622
622
623 def process(self, t, mapping):
623 def process(self, t, mapping):
624 '''Perform expansion. t is name of map element to expand.
624 '''Perform expansion. t is name of map element to expand.
625 mapping contains added elements for use during expansion. Is a
625 mapping contains added elements for use during expansion. Is a
626 generator.'''
626 generator.'''
627 return _flatten(runtemplate(self, mapping, self._load(t)))
627 return _flatten(runtemplate(self, mapping, self._load(t)))
628
628
629 engines = {'default': engine}
629 engines = {'default': engine}
630
630
631 def stylelist():
631 def stylelist():
632 paths = templatepaths()
632 paths = templatepaths()
633 if not paths:
633 if not paths:
634 return _('no templates found, try `hg debuginstall` for more info')
634 return _('no templates found, try `hg debuginstall` for more info')
635 dirlist = os.listdir(paths[0])
635 dirlist = os.listdir(paths[0])
636 stylelist = []
636 stylelist = []
637 for file in dirlist:
637 for file in dirlist:
638 split = file.split(".")
638 split = file.split(".")
639 if split[0] == "map-cmdline":
639 if split[0] == "map-cmdline":
640 stylelist.append(split[1])
640 stylelist.append(split[1])
641 return ", ".join(sorted(stylelist))
641 return ", ".join(sorted(stylelist))
642
642
643 class TemplateNotFound(util.Abort):
643 class TemplateNotFound(util.Abort):
644 pass
644 pass
645
645
646 class templater(object):
646 class templater(object):
647
647
648 def __init__(self, mapfile, filters={}, defaults={}, cache={},
648 def __init__(self, mapfile, filters={}, defaults={}, cache={},
649 minchunk=1024, maxchunk=65536):
649 minchunk=1024, maxchunk=65536):
650 '''set up template engine.
650 '''set up template engine.
651 mapfile is name of file to read map definitions from.
651 mapfile is name of file to read map definitions from.
652 filters is dict of functions. each transforms a value into another.
652 filters is dict of functions. each transforms a value into another.
653 defaults is dict of default map definitions.'''
653 defaults is dict of default map definitions.'''
654 self.mapfile = mapfile or 'template'
654 self.mapfile = mapfile or 'template'
655 self.cache = cache.copy()
655 self.cache = cache.copy()
656 self.map = {}
656 self.map = {}
657 self.base = (mapfile and os.path.dirname(mapfile)) or ''
657 self.base = (mapfile and os.path.dirname(mapfile)) or ''
658 self.filters = templatefilters.filters.copy()
658 self.filters = templatefilters.filters.copy()
659 self.filters.update(filters)
659 self.filters.update(filters)
660 self.defaults = defaults
660 self.defaults = defaults
661 self.minchunk, self.maxchunk = minchunk, maxchunk
661 self.minchunk, self.maxchunk = minchunk, maxchunk
662 self.ecache = {}
662 self.ecache = {}
663
663
664 if not mapfile:
664 if not mapfile:
665 return
665 return
666 if not os.path.exists(mapfile):
666 if not os.path.exists(mapfile):
667 raise util.Abort(_("style '%s' not found") % mapfile,
667 raise util.Abort(_("style '%s' not found") % mapfile,
668 hint=_("available styles: %s") % stylelist())
668 hint=_("available styles: %s") % stylelist())
669
669
670 conf = config.config()
670 conf = config.config()
671 conf.read(mapfile)
671 conf.read(mapfile)
672
672
673 for key, val in conf[''].items():
673 for key, val in conf[''].items():
674 if not val:
674 if not val:
675 raise SyntaxError(_('%s: missing value') % conf.source('', key))
675 raise SyntaxError(_('%s: missing value') % conf.source('', key))
676 if val[0] in "'\"":
676 if val[0] in "'\"":
677 try:
677 try:
678 self.cache[key] = parsestring(val)
678 self.cache[key] = parsestring(val)
679 except SyntaxError, inst:
679 except SyntaxError, inst:
680 raise SyntaxError('%s: %s' %
680 raise SyntaxError('%s: %s' %
681 (conf.source('', key), inst.args[0]))
681 (conf.source('', key), inst.args[0]))
682 else:
682 else:
683 val = 'default', val
683 val = 'default', val
684 if ':' in val[1]:
684 if ':' in val[1]:
685 val = val[1].split(':', 1)
685 val = val[1].split(':', 1)
686 self.map[key] = val[0], os.path.join(self.base, val[1])
686 self.map[key] = val[0], os.path.join(self.base, val[1])
687
687
688 def __contains__(self, key):
688 def __contains__(self, key):
689 return key in self.cache or key in self.map
689 return key in self.cache or key in self.map
690
690
691 def load(self, t):
691 def load(self, t):
692 '''Get the template for the given template name. Use a local cache.'''
692 '''Get the template for the given template name. Use a local cache.'''
693 if t not in self.cache:
693 if t not in self.cache:
694 try:
694 try:
695 self.cache[t] = util.readfile(self.map[t][1])
695 self.cache[t] = util.readfile(self.map[t][1])
696 except KeyError, inst:
696 except KeyError, inst:
697 raise TemplateNotFound(_('"%s" not in template map') %
697 raise TemplateNotFound(_('"%s" not in template map') %
698 inst.args[0])
698 inst.args[0])
699 except IOError, inst:
699 except IOError, inst:
700 raise IOError(inst.args[0], _('template file %s: %s') %
700 raise IOError(inst.args[0], _('template file %s: %s') %
701 (self.map[t][1], inst.args[1]))
701 (self.map[t][1], inst.args[1]))
702 return self.cache[t]
702 return self.cache[t]
703
703
704 def __call__(self, t, **mapping):
704 def __call__(self, t, **mapping):
705 ttype = t in self.map and self.map[t][0] or 'default'
705 ttype = t in self.map and self.map[t][0] or 'default'
706 if ttype not in self.ecache:
706 if ttype not in self.ecache:
707 self.ecache[ttype] = engines[ttype](self.load,
707 self.ecache[ttype] = engines[ttype](self.load,
708 self.filters, self.defaults)
708 self.filters, self.defaults)
709 proc = self.ecache[ttype]
709 proc = self.ecache[ttype]
710
710
711 stream = proc.process(t, mapping)
711 stream = proc.process(t, mapping)
712 if self.minchunk:
712 if self.minchunk:
713 stream = util.increasingchunks(stream, min=self.minchunk,
713 stream = util.increasingchunks(stream, min=self.minchunk,
714 max=self.maxchunk)
714 max=self.maxchunk)
715 return stream
715 return stream
716
716
717 def templatepaths():
717 def templatepaths():
718 '''return locations used for template files.'''
718 '''return locations used for template files.'''
719 pathsrel = ['templates']
719 pathsrel = ['templates']
720 paths = [os.path.normpath(os.path.join(util.datapath, f))
720 paths = [os.path.normpath(os.path.join(util.datapath, f))
721 for f in pathsrel]
721 for f in pathsrel]
722 return [p for p in paths if os.path.isdir(p)]
722 return [p for p in paths if os.path.isdir(p)]
723
723
724 def templatepath(name):
724 def templatepath(name):
725 '''return location of template file. returns None if not found.'''
725 '''return location of template file. returns None if not found.'''
726 for p in templatepaths():
726 for p in templatepaths():
727 f = os.path.join(p, name)
727 f = os.path.join(p, name)
728 if os.path.exists(f):
728 if os.path.exists(f):
729 return f
729 return f
730 return None
730 return None
731
731
732 def stylemap(styles, paths=None):
732 def stylemap(styles, paths=None):
733 """Return path to mapfile for a given style.
733 """Return path to mapfile for a given style.
734
734
735 Searches mapfile in the following locations:
735 Searches mapfile in the following locations:
736 1. templatepath/style/map
736 1. templatepath/style/map
737 2. templatepath/map-style
737 2. templatepath/map-style
738 3. templatepath/map
738 3. templatepath/map
739 """
739 """
740
740
741 if paths is None:
741 if paths is None:
742 paths = templatepaths()
742 paths = templatepaths()
743 elif isinstance(paths, str):
743 elif isinstance(paths, str):
744 paths = [paths]
744 paths = [paths]
745
745
746 if isinstance(styles, str):
746 if isinstance(styles, str):
747 styles = [styles]
747 styles = [styles]
748
748
749 for style in styles:
749 for style in styles:
750 if not style:
750 # only plain name is allowed to honor template paths
751 if (not style
752 or style in (os.curdir, os.pardir)
753 or os.sep in style
754 or os.altsep and os.altsep in style):
751 continue
755 continue
752 locations = [os.path.join(style, 'map'), 'map-' + style]
756 locations = [os.path.join(style, 'map'), 'map-' + style]
753 locations.append('map')
757 locations.append('map')
754
758
755 for path in paths:
759 for path in paths:
756 for location in locations:
760 for location in locations:
757 mapfile = os.path.join(path, location)
761 mapfile = os.path.join(path, location)
758 if os.path.isfile(mapfile):
762 if os.path.isfile(mapfile):
759 return style, mapfile
763 return style, mapfile
760
764
761 raise RuntimeError("No hgweb templates found in %r" % paths)
765 raise RuntimeError("No hgweb templates found in %r" % paths)
@@ -1,615 +1,654 b''
1 #require serve
1 #require serve
2
2
3 Some tests for hgweb. Tests static files, plain files and different 404's.
3 Some tests for hgweb. Tests static files, plain files and different 404's.
4
4
5 $ hg init test
5 $ hg init test
6 $ cd test
6 $ cd test
7 $ mkdir da
7 $ mkdir da
8 $ echo foo > da/foo
8 $ echo foo > da/foo
9 $ echo foo > foo
9 $ echo foo > foo
10 $ hg ci -Ambase
10 $ hg ci -Ambase
11 adding da/foo
11 adding da/foo
12 adding foo
12 adding foo
13 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
13 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
14 $ cat hg.pid >> $DAEMON_PIDS
14 $ cat hg.pid >> $DAEMON_PIDS
15
15
16 manifest
16 manifest
17
17
18 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=raw')
18 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=raw')
19 200 Script output follows
19 200 Script output follows
20
20
21
21
22 drwxr-xr-x da
22 drwxr-xr-x da
23 -rw-r--r-- 4 foo
23 -rw-r--r-- 4 foo
24
24
25
25
26 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/da?style=raw')
26 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/da?style=raw')
27 200 Script output follows
27 200 Script output follows
28
28
29
29
30 -rw-r--r-- 4 foo
30 -rw-r--r-- 4 foo
31
31
32
32
33
33
34 plain file
34 plain file
35
35
36 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?style=raw'
36 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?style=raw'
37 200 Script output follows
37 200 Script output follows
38
38
39 foo
39 foo
40
40
41 should give a 404 - static file that does not exist
41 should give a 404 - static file that does not exist
42
42
43 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'static/bogus'
43 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'static/bogus'
44 404 Not Found
44 404 Not Found
45
45
46 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
46 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
47 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
47 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
48 <head>
48 <head>
49 <link rel="icon" href="/static/hgicon.png" type="image/png" />
49 <link rel="icon" href="/static/hgicon.png" type="image/png" />
50 <meta name="robots" content="index, nofollow" />
50 <meta name="robots" content="index, nofollow" />
51 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
51 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
52 <script type="text/javascript" src="/static/mercurial.js"></script>
52 <script type="text/javascript" src="/static/mercurial.js"></script>
53
53
54 <title>test: error</title>
54 <title>test: error</title>
55 </head>
55 </head>
56 <body>
56 <body>
57
57
58 <div class="container">
58 <div class="container">
59 <div class="menu">
59 <div class="menu">
60 <div class="logo">
60 <div class="logo">
61 <a href="http://mercurial.selenic.com/">
61 <a href="http://mercurial.selenic.com/">
62 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
62 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
63 </div>
63 </div>
64 <ul>
64 <ul>
65 <li><a href="/shortlog">log</a></li>
65 <li><a href="/shortlog">log</a></li>
66 <li><a href="/graph">graph</a></li>
66 <li><a href="/graph">graph</a></li>
67 <li><a href="/tags">tags</a></li>
67 <li><a href="/tags">tags</a></li>
68 <li><a href="/bookmarks">bookmarks</a></li>
68 <li><a href="/bookmarks">bookmarks</a></li>
69 <li><a href="/branches">branches</a></li>
69 <li><a href="/branches">branches</a></li>
70 </ul>
70 </ul>
71 <ul>
71 <ul>
72 <li><a href="/help">help</a></li>
72 <li><a href="/help">help</a></li>
73 </ul>
73 </ul>
74 </div>
74 </div>
75
75
76 <div class="main">
76 <div class="main">
77
77
78 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
78 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
79 <h3>error</h3>
79 <h3>error</h3>
80
80
81 <form class="search" action="/log">
81 <form class="search" action="/log">
82
82
83 <p><input name="rev" id="search1" type="text" size="30"></p>
83 <p><input name="rev" id="search1" type="text" size="30"></p>
84 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
84 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
85 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
85 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
86 </form>
86 </form>
87
87
88 <div class="description">
88 <div class="description">
89 <p>
89 <p>
90 An error occurred while processing your request:
90 An error occurred while processing your request:
91 </p>
91 </p>
92 <p>
92 <p>
93 Not Found
93 Not Found
94 </p>
94 </p>
95 </div>
95 </div>
96 </div>
96 </div>
97 </div>
97 </div>
98
98
99 <script type="text/javascript">process_dates()</script>
99 <script type="text/javascript">process_dates()</script>
100
100
101
101
102 </body>
102 </body>
103 </html>
103 </html>
104
104
105 [1]
105 [1]
106
106
107 should give a 404 - bad revision
107 should give a 404 - bad revision
108
108
109 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/spam/foo?style=raw'
109 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/spam/foo?style=raw'
110 404 Not Found
110 404 Not Found
111
111
112
112
113 error: revision not found: spam
113 error: revision not found: spam
114 [1]
114 [1]
115
115
116 should give a 400 - bad command
116 should give a 400 - bad command
117
117
118 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?cmd=spam&style=raw'
118 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?cmd=spam&style=raw'
119 400* (glob)
119 400* (glob)
120
120
121
121
122 error: no such method: spam
122 error: no such method: spam
123 [1]
123 [1]
124
124
125 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT '?cmd=spam'
125 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT '?cmd=spam'
126 400 no such method: spam
126 400 no such method: spam
127 [1]
127 [1]
128
128
129 should give a 400 - bad command as a part of url path (issue4071)
129 should give a 400 - bad command as a part of url path (issue4071)
130
130
131 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'spam'
131 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'spam'
132 400 no such method: spam
132 400 no such method: spam
133 [1]
133 [1]
134
134
135 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'raw-spam'
135 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'raw-spam'
136 400 no such method: spam
136 400 no such method: spam
137 [1]
137 [1]
138
138
139 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'spam/tip/foo'
139 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'spam/tip/foo'
140 400 no such method: spam
140 400 no such method: spam
141 [1]
141 [1]
142
142
143 should give a 404 - file does not exist
143 should give a 404 - file does not exist
144
144
145 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork?style=raw'
145 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork?style=raw'
146 404 Not Found
146 404 Not Found
147
147
148
148
149 error: bork@2ef0ac749a14: not found in manifest
149 error: bork@2ef0ac749a14: not found in manifest
150 [1]
150 [1]
151 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork'
151 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork'
152 404 Not Found
152 404 Not Found
153
153
154 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
154 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
155 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
155 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
156 <head>
156 <head>
157 <link rel="icon" href="/static/hgicon.png" type="image/png" />
157 <link rel="icon" href="/static/hgicon.png" type="image/png" />
158 <meta name="robots" content="index, nofollow" />
158 <meta name="robots" content="index, nofollow" />
159 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
159 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
160 <script type="text/javascript" src="/static/mercurial.js"></script>
160 <script type="text/javascript" src="/static/mercurial.js"></script>
161
161
162 <title>test: error</title>
162 <title>test: error</title>
163 </head>
163 </head>
164 <body>
164 <body>
165
165
166 <div class="container">
166 <div class="container">
167 <div class="menu">
167 <div class="menu">
168 <div class="logo">
168 <div class="logo">
169 <a href="http://mercurial.selenic.com/">
169 <a href="http://mercurial.selenic.com/">
170 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
170 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
171 </div>
171 </div>
172 <ul>
172 <ul>
173 <li><a href="/shortlog">log</a></li>
173 <li><a href="/shortlog">log</a></li>
174 <li><a href="/graph">graph</a></li>
174 <li><a href="/graph">graph</a></li>
175 <li><a href="/tags">tags</a></li>
175 <li><a href="/tags">tags</a></li>
176 <li><a href="/bookmarks">bookmarks</a></li>
176 <li><a href="/bookmarks">bookmarks</a></li>
177 <li><a href="/branches">branches</a></li>
177 <li><a href="/branches">branches</a></li>
178 </ul>
178 </ul>
179 <ul>
179 <ul>
180 <li><a href="/help">help</a></li>
180 <li><a href="/help">help</a></li>
181 </ul>
181 </ul>
182 </div>
182 </div>
183
183
184 <div class="main">
184 <div class="main">
185
185
186 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
186 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
187 <h3>error</h3>
187 <h3>error</h3>
188
188
189 <form class="search" action="/log">
189 <form class="search" action="/log">
190
190
191 <p><input name="rev" id="search1" type="text" size="30"></p>
191 <p><input name="rev" id="search1" type="text" size="30"></p>
192 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
192 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
193 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
193 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
194 </form>
194 </form>
195
195
196 <div class="description">
196 <div class="description">
197 <p>
197 <p>
198 An error occurred while processing your request:
198 An error occurred while processing your request:
199 </p>
199 </p>
200 <p>
200 <p>
201 bork@2ef0ac749a14: not found in manifest
201 bork@2ef0ac749a14: not found in manifest
202 </p>
202 </p>
203 </div>
203 </div>
204 </div>
204 </div>
205 </div>
205 </div>
206
206
207 <script type="text/javascript">process_dates()</script>
207 <script type="text/javascript">process_dates()</script>
208
208
209
209
210 </body>
210 </body>
211 </html>
211 </html>
212
212
213 [1]
213 [1]
214 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/tip/bork?style=raw'
214 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/tip/bork?style=raw'
215 404 Not Found
215 404 Not Found
216
216
217
217
218 error: bork@2ef0ac749a14: not found in manifest
218 error: bork@2ef0ac749a14: not found in manifest
219 [1]
219 [1]
220
220
221 try bad style
221 try bad style
222
222
223 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=foobar')
223 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=foobar')
224 200 Script output follows
224 200 Script output follows
225
225
226 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
226 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
227 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
227 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
228 <head>
228 <head>
229 <link rel="icon" href="/static/hgicon.png" type="image/png" />
229 <link rel="icon" href="/static/hgicon.png" type="image/png" />
230 <meta name="robots" content="index, nofollow" />
230 <meta name="robots" content="index, nofollow" />
231 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
231 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
232 <script type="text/javascript" src="/static/mercurial.js"></script>
232 <script type="text/javascript" src="/static/mercurial.js"></script>
233
233
234 <title>test: 2ef0ac749a14 /</title>
234 <title>test: 2ef0ac749a14 /</title>
235 </head>
235 </head>
236 <body>
236 <body>
237
237
238 <div class="container">
238 <div class="container">
239 <div class="menu">
239 <div class="menu">
240 <div class="logo">
240 <div class="logo">
241 <a href="http://mercurial.selenic.com/">
241 <a href="http://mercurial.selenic.com/">
242 <img src="/static/hglogo.png" alt="mercurial" /></a>
242 <img src="/static/hglogo.png" alt="mercurial" /></a>
243 </div>
243 </div>
244 <ul>
244 <ul>
245 <li><a href="/shortlog/2ef0ac749a14">log</a></li>
245 <li><a href="/shortlog/2ef0ac749a14">log</a></li>
246 <li><a href="/graph/2ef0ac749a14">graph</a></li>
246 <li><a href="/graph/2ef0ac749a14">graph</a></li>
247 <li><a href="/tags">tags</a></li>
247 <li><a href="/tags">tags</a></li>
248 <li><a href="/bookmarks">bookmarks</a></li>
248 <li><a href="/bookmarks">bookmarks</a></li>
249 <li><a href="/branches">branches</a></li>
249 <li><a href="/branches">branches</a></li>
250 </ul>
250 </ul>
251 <ul>
251 <ul>
252 <li><a href="/rev/2ef0ac749a14">changeset</a></li>
252 <li><a href="/rev/2ef0ac749a14">changeset</a></li>
253 <li class="active">browse</li>
253 <li class="active">browse</li>
254 </ul>
254 </ul>
255 <ul>
255 <ul>
256
256
257 </ul>
257 </ul>
258 <ul>
258 <ul>
259 <li><a href="/help">help</a></li>
259 <li><a href="/help">help</a></li>
260 </ul>
260 </ul>
261 </div>
261 </div>
262
262
263 <div class="main">
263 <div class="main">
264 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
264 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
265 <h3>directory / @ 0:2ef0ac749a14 <span class="tag">tip</span> </h3>
265 <h3>directory / @ 0:2ef0ac749a14 <span class="tag">tip</span> </h3>
266
266
267 <form class="search" action="/log">
267 <form class="search" action="/log">
268
268
269 <p><input name="rev" id="search1" type="text" size="30" /></p>
269 <p><input name="rev" id="search1" type="text" size="30" /></p>
270 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
270 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
271 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
271 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
272 </form>
272 </form>
273
273
274 <table class="bigtable">
274 <table class="bigtable">
275 <tr>
275 <tr>
276 <th class="name">name</th>
276 <th class="name">name</th>
277 <th class="size">size</th>
277 <th class="size">size</th>
278 <th class="permissions">permissions</th>
278 <th class="permissions">permissions</th>
279 </tr>
279 </tr>
280 <tbody class="stripes2">
280 <tbody class="stripes2">
281 <tr class="fileline">
281 <tr class="fileline">
282 <td class="name"><a href="/file/2ef0ac749a14/">[up]</a></td>
282 <td class="name"><a href="/file/2ef0ac749a14/">[up]</a></td>
283 <td class="size"></td>
283 <td class="size"></td>
284 <td class="permissions">drwxr-xr-x</td>
284 <td class="permissions">drwxr-xr-x</td>
285 </tr>
285 </tr>
286
286
287 <tr class="fileline">
287 <tr class="fileline">
288 <td class="name">
288 <td class="name">
289 <a href="/file/2ef0ac749a14/da">
289 <a href="/file/2ef0ac749a14/da">
290 <img src="/static/coal-folder.png" alt="dir."/> da/
290 <img src="/static/coal-folder.png" alt="dir."/> da/
291 </a>
291 </a>
292 <a href="/file/2ef0ac749a14/da/">
292 <a href="/file/2ef0ac749a14/da/">
293
293
294 </a>
294 </a>
295 </td>
295 </td>
296 <td class="size"></td>
296 <td class="size"></td>
297 <td class="permissions">drwxr-xr-x</td>
297 <td class="permissions">drwxr-xr-x</td>
298 </tr>
298 </tr>
299
299
300 <tr class="fileline">
300 <tr class="fileline">
301 <td class="filename">
301 <td class="filename">
302 <a href="/file/2ef0ac749a14/foo">
302 <a href="/file/2ef0ac749a14/foo">
303 <img src="/static/coal-file.png" alt="file"/> foo
303 <img src="/static/coal-file.png" alt="file"/> foo
304 </a>
304 </a>
305 </td>
305 </td>
306 <td class="size">4</td>
306 <td class="size">4</td>
307 <td class="permissions">-rw-r--r--</td>
307 <td class="permissions">-rw-r--r--</td>
308 </tr>
308 </tr>
309 </tbody>
309 </tbody>
310 </table>
310 </table>
311 </div>
311 </div>
312 </div>
312 </div>
313 <script type="text/javascript">process_dates()</script>
313 <script type="text/javascript">process_dates()</script>
314
314
315
315
316 </body>
316 </body>
317 </html>
317 </html>
318
318
319
319
320 stop and restart
320 stop and restart
321
321
322 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
322 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
323 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log
323 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log
324 $ cat hg.pid >> $DAEMON_PIDS
324 $ cat hg.pid >> $DAEMON_PIDS
325
325
326 Test the access/error files are opened in append mode
326 Test the access/error files are opened in append mode
327
327
328 $ $PYTHON -c "print len(file('access.log').readlines()), 'log lines written'"
328 $ $PYTHON -c "print len(file('access.log').readlines()), 'log lines written'"
329 14 log lines written
329 14 log lines written
330
330
331 static file
331 static file
332
332
333 $ "$TESTDIR/get-with-headers.py" --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server
333 $ "$TESTDIR/get-with-headers.py" --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server
334 200 Script output follows
334 200 Script output follows
335 content-length: 5372
335 content-length: 5372
336 content-type: text/css
336 content-type: text/css
337
337
338 body { font-family: sans-serif; font-size: 12px; border:solid #d9d8d1; border-width:1px; margin:10px; }
338 body { font-family: sans-serif; font-size: 12px; border:solid #d9d8d1; border-width:1px; margin:10px; }
339 a { color:#0000cc; }
339 a { color:#0000cc; }
340 a:hover, a:visited, a:active { color:#880000; }
340 a:hover, a:visited, a:active { color:#880000; }
341 div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
341 div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
342 div.page_header a:visited { color:#0000cc; }
342 div.page_header a:visited { color:#0000cc; }
343 div.page_header a:hover { color:#880000; }
343 div.page_header a:hover { color:#880000; }
344 div.page_nav { padding:8px; }
344 div.page_nav { padding:8px; }
345 div.page_nav a:visited { color:#0000cc; }
345 div.page_nav a:visited { color:#0000cc; }
346 div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
346 div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
347 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
347 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
348 div.page_footer_text { float:left; color:#555555; font-style:italic; }
348 div.page_footer_text { float:left; color:#555555; font-style:italic; }
349 div.page_body { padding:8px; }
349 div.page_body { padding:8px; }
350 div.title, a.title {
350 div.title, a.title {
351 display:block; padding:6px 8px;
351 display:block; padding:6px 8px;
352 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
352 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
353 }
353 }
354 a.title:hover { background-color: #d9d8d1; }
354 a.title:hover { background-color: #d9d8d1; }
355 div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
355 div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
356 div.log_body { padding:8px 8px 8px 150px; }
356 div.log_body { padding:8px 8px 8px 150px; }
357 .age { white-space:nowrap; }
357 .age { white-space:nowrap; }
358 span.age { position:relative; float:left; width:142px; font-style:italic; }
358 span.age { position:relative; float:left; width:142px; font-style:italic; }
359 div.log_link {
359 div.log_link {
360 padding:0px 8px;
360 padding:0px 8px;
361 font-size:10px; font-family:sans-serif; font-style:normal;
361 font-size:10px; font-family:sans-serif; font-style:normal;
362 position:relative; float:left; width:136px;
362 position:relative; float:left; width:136px;
363 }
363 }
364 div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
364 div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
365 a.list { text-decoration:none; color:#000000; }
365 a.list { text-decoration:none; color:#000000; }
366 a.list:hover { text-decoration:underline; color:#880000; }
366 a.list:hover { text-decoration:underline; color:#880000; }
367 table { padding:8px 4px; }
367 table { padding:8px 4px; }
368 th { padding:2px 5px; font-size:12px; text-align:left; }
368 th { padding:2px 5px; font-size:12px; text-align:left; }
369 tr.light:hover, .parity0:hover { background-color:#edece6; }
369 tr.light:hover, .parity0:hover { background-color:#edece6; }
370 tr.dark, .parity1 { background-color:#f6f6f0; }
370 tr.dark, .parity1 { background-color:#f6f6f0; }
371 tr.dark:hover, .parity1:hover { background-color:#edece6; }
371 tr.dark:hover, .parity1:hover { background-color:#edece6; }
372 td { padding:2px 5px; font-size:12px; vertical-align:top; }
372 td { padding:2px 5px; font-size:12px; vertical-align:top; }
373 td.closed { background-color: #99f; }
373 td.closed { background-color: #99f; }
374 td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
374 td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
375 td.indexlinks { white-space: nowrap; }
375 td.indexlinks { white-space: nowrap; }
376 td.indexlinks a {
376 td.indexlinks a {
377 padding: 2px 5px; line-height: 10px;
377 padding: 2px 5px; line-height: 10px;
378 border: 1px solid;
378 border: 1px solid;
379 color: #ffffff; background-color: #7777bb;
379 color: #ffffff; background-color: #7777bb;
380 border-color: #aaaadd #333366 #333366 #aaaadd;
380 border-color: #aaaadd #333366 #333366 #aaaadd;
381 font-weight: bold; text-align: center; text-decoration: none;
381 font-weight: bold; text-align: center; text-decoration: none;
382 font-size: 10px;
382 font-size: 10px;
383 }
383 }
384 td.indexlinks a:hover { background-color: #6666aa; }
384 td.indexlinks a:hover { background-color: #6666aa; }
385 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
385 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
386 div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
386 div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
387 div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
387 div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
388 div.search { margin:4px 8px; position:absolute; top:56px; right:12px }
388 div.search { margin:4px 8px; position:absolute; top:56px; right:12px }
389 .linenr { color:#999999; text-decoration:none }
389 .linenr { color:#999999; text-decoration:none }
390 div.rss_logo { float: right; white-space: nowrap; }
390 div.rss_logo { float: right; white-space: nowrap; }
391 div.rss_logo a {
391 div.rss_logo a {
392 padding:3px 6px; line-height:10px;
392 padding:3px 6px; line-height:10px;
393 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
393 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
394 color:#ffffff; background-color:#ff6600;
394 color:#ffffff; background-color:#ff6600;
395 font-weight:bold; font-family:sans-serif; font-size:10px;
395 font-weight:bold; font-family:sans-serif; font-size:10px;
396 text-align:center; text-decoration:none;
396 text-align:center; text-decoration:none;
397 }
397 }
398 div.rss_logo a:hover { background-color:#ee5500; }
398 div.rss_logo a:hover { background-color:#ee5500; }
399 pre { margin: 0; }
399 pre { margin: 0; }
400 span.logtags span {
400 span.logtags span {
401 padding: 0px 4px;
401 padding: 0px 4px;
402 font-size: 10px;
402 font-size: 10px;
403 font-weight: normal;
403 font-weight: normal;
404 border: 1px solid;
404 border: 1px solid;
405 background-color: #ffaaff;
405 background-color: #ffaaff;
406 border-color: #ffccff #ff00ee #ff00ee #ffccff;
406 border-color: #ffccff #ff00ee #ff00ee #ffccff;
407 }
407 }
408 span.logtags span.tagtag {
408 span.logtags span.tagtag {
409 background-color: #ffffaa;
409 background-color: #ffffaa;
410 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
410 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
411 }
411 }
412 span.logtags span.branchtag {
412 span.logtags span.branchtag {
413 background-color: #aaffaa;
413 background-color: #aaffaa;
414 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
414 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
415 }
415 }
416 span.logtags span.inbranchtag {
416 span.logtags span.inbranchtag {
417 background-color: #d5dde6;
417 background-color: #d5dde6;
418 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
418 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
419 }
419 }
420 span.logtags span.bookmarktag {
420 span.logtags span.bookmarktag {
421 background-color: #afdffa;
421 background-color: #afdffa;
422 border-color: #ccecff #46ace6 #46ace6 #ccecff;
422 border-color: #ccecff #46ace6 #46ace6 #ccecff;
423 }
423 }
424 span.difflineplus { color:#008800; }
424 span.difflineplus { color:#008800; }
425 span.difflineminus { color:#cc0000; }
425 span.difflineminus { color:#cc0000; }
426 span.difflineat { color:#990099; }
426 span.difflineat { color:#990099; }
427
427
428 /* Graph */
428 /* Graph */
429 div#wrapper {
429 div#wrapper {
430 position: relative;
430 position: relative;
431 margin: 0;
431 margin: 0;
432 padding: 0;
432 padding: 0;
433 margin-top: 3px;
433 margin-top: 3px;
434 }
434 }
435
435
436 canvas {
436 canvas {
437 position: absolute;
437 position: absolute;
438 z-index: 5;
438 z-index: 5;
439 top: -0.9em;
439 top: -0.9em;
440 margin: 0;
440 margin: 0;
441 }
441 }
442
442
443 ul#nodebgs {
443 ul#nodebgs {
444 list-style: none inside none;
444 list-style: none inside none;
445 padding: 0;
445 padding: 0;
446 margin: 0;
446 margin: 0;
447 top: -0.7em;
447 top: -0.7em;
448 }
448 }
449
449
450 ul#graphnodes li, ul#nodebgs li {
450 ul#graphnodes li, ul#nodebgs li {
451 height: 39px;
451 height: 39px;
452 }
452 }
453
453
454 ul#graphnodes {
454 ul#graphnodes {
455 position: absolute;
455 position: absolute;
456 z-index: 10;
456 z-index: 10;
457 top: -0.8em;
457 top: -0.8em;
458 list-style: none inside none;
458 list-style: none inside none;
459 padding: 0;
459 padding: 0;
460 }
460 }
461
461
462 ul#graphnodes li .info {
462 ul#graphnodes li .info {
463 display: block;
463 display: block;
464 font-size: 100%;
464 font-size: 100%;
465 position: relative;
465 position: relative;
466 top: -3px;
466 top: -3px;
467 font-style: italic;
467 font-style: italic;
468 }
468 }
469
469
470 /* Comparison */
470 /* Comparison */
471 .legend {
471 .legend {
472 padding: 1.5% 0 1.5% 0;
472 padding: 1.5% 0 1.5% 0;
473 }
473 }
474
474
475 .legendinfo {
475 .legendinfo {
476 border: 1px solid #d9d8d1;
476 border: 1px solid #d9d8d1;
477 font-size: 80%;
477 font-size: 80%;
478 text-align: center;
478 text-align: center;
479 padding: 0.5%;
479 padding: 0.5%;
480 }
480 }
481
481
482 .equal {
482 .equal {
483 background-color: #ffffff;
483 background-color: #ffffff;
484 }
484 }
485
485
486 .delete {
486 .delete {
487 background-color: #faa;
487 background-color: #faa;
488 color: #333;
488 color: #333;
489 }
489 }
490
490
491 .insert {
491 .insert {
492 background-color: #ffa;
492 background-color: #ffa;
493 }
493 }
494
494
495 .replace {
495 .replace {
496 background-color: #e8e8e8;
496 background-color: #e8e8e8;
497 }
497 }
498
498
499 .comparison {
499 .comparison {
500 overflow-x: auto;
500 overflow-x: auto;
501 }
501 }
502
502
503 .header th {
503 .header th {
504 text-align: center;
504 text-align: center;
505 }
505 }
506
506
507 .block {
507 .block {
508 border-top: 1px solid #d9d8d1;
508 border-top: 1px solid #d9d8d1;
509 }
509 }
510
510
511 .scroll-loading {
511 .scroll-loading {
512 -webkit-animation: change_color 1s linear 0s infinite alternate;
512 -webkit-animation: change_color 1s linear 0s infinite alternate;
513 -moz-animation: change_color 1s linear 0s infinite alternate;
513 -moz-animation: change_color 1s linear 0s infinite alternate;
514 -o-animation: change_color 1s linear 0s infinite alternate;
514 -o-animation: change_color 1s linear 0s infinite alternate;
515 animation: change_color 1s linear 0s infinite alternate;
515 animation: change_color 1s linear 0s infinite alternate;
516 }
516 }
517
517
518 @-webkit-keyframes change_color {
518 @-webkit-keyframes change_color {
519 from { background-color: #A0CEFF; } to { }
519 from { background-color: #A0CEFF; } to { }
520 }
520 }
521 @-moz-keyframes change_color {
521 @-moz-keyframes change_color {
522 from { background-color: #A0CEFF; } to { }
522 from { background-color: #A0CEFF; } to { }
523 }
523 }
524 @-o-keyframes change_color {
524 @-o-keyframes change_color {
525 from { background-color: #A0CEFF; } to { }
525 from { background-color: #A0CEFF; } to { }
526 }
526 }
527 @keyframes change_color {
527 @keyframes change_color {
528 from { background-color: #A0CEFF; } to { }
528 from { background-color: #A0CEFF; } to { }
529 }
529 }
530
530
531 .scroll-loading-error {
531 .scroll-loading-error {
532 background-color: #FFCCCC !important;
532 background-color: #FFCCCC !important;
533 }
533 }
534 304 Not Modified
534 304 Not Modified
535
535
536
536
537 phase changes are refreshed (issue4061)
537 phase changes are refreshed (issue4061)
538
538
539 $ echo bar >> foo
539 $ echo bar >> foo
540 $ hg ci -msecret --secret
540 $ hg ci -msecret --secret
541 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log?style=raw'
541 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log?style=raw'
542 200 Script output follows
542 200 Script output follows
543
543
544
544
545 # HG changelog
545 # HG changelog
546 # Node ID 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
546 # Node ID 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
547
547
548 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
548 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
549 revision: 0
549 revision: 0
550 user: test
550 user: test
551 date: Thu, 01 Jan 1970 00:00:00 +0000
551 date: Thu, 01 Jan 1970 00:00:00 +0000
552 summary: base
552 summary: base
553 branch: default
553 branch: default
554 tag: tip
554 tag: tip
555
555
556
556
557 $ hg phase --draft tip
557 $ hg phase --draft tip
558 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log?style=raw'
558 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log?style=raw'
559 200 Script output follows
559 200 Script output follows
560
560
561
561
562 # HG changelog
562 # HG changelog
563 # Node ID a084749e708a9c4c0a5b652a2a446322ce290e04
563 # Node ID a084749e708a9c4c0a5b652a2a446322ce290e04
564
564
565 changeset: a084749e708a9c4c0a5b652a2a446322ce290e04
565 changeset: a084749e708a9c4c0a5b652a2a446322ce290e04
566 revision: 1
566 revision: 1
567 user: test
567 user: test
568 date: Thu, 01 Jan 1970 00:00:00 +0000
568 date: Thu, 01 Jan 1970 00:00:00 +0000
569 summary: secret
569 summary: secret
570 branch: default
570 branch: default
571 tag: tip
571 tag: tip
572
572
573 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
573 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
574 revision: 0
574 revision: 0
575 user: test
575 user: test
576 date: Thu, 01 Jan 1970 00:00:00 +0000
576 date: Thu, 01 Jan 1970 00:00:00 +0000
577 summary: base
577 summary: base
578
578
579
579
580
580
581 no style can be loaded from directories other than the specified paths
582
583 $ mkdir -p x/templates/fallback
584 $ cat <<EOF > x/templates/fallback/map
585 > default = 'shortlog'
586 > shortlog = 'fall back to default\n'
587 > mimetype = 'text/plain'
588 > EOF
589 $ cat <<EOF > x/map
590 > default = 'shortlog'
591 > shortlog = 'access to outside of templates directory\n'
592 > mimetype = 'text/plain'
593 > EOF
594
595 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
596 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log \
597 > --config web.style=fallback --config web.templates=x/templates
598 $ cat hg.pid >> $DAEMON_PIDS
599
600 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT "?style=`pwd`/x"
601 200 Script output follows
602
603 fall back to default
604
605 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '?style=..'
606 200 Script output follows
607
608 fall back to default
609
610 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '?style=./..'
611 200 Script output follows
612
613 fall back to default
614
615 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '?style=.../.../'
616 200 Script output follows
617
618 fall back to default
619
581 errors
620 errors
582
621
583 $ cat errors.log
622 $ cat errors.log
584
623
585 Uncaught exceptions result in a logged error and canned HTTP response
624 Uncaught exceptions result in a logged error and canned HTTP response
586
625
587 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
626 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
588 $ hg --config extensions.hgweberror=$TESTDIR/hgweberror.py serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
627 $ hg --config extensions.hgweberror=$TESTDIR/hgweberror.py serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
589 $ cat hg.pid >> $DAEMON_PIDS
628 $ cat hg.pid >> $DAEMON_PIDS
590
629
591 $ $TESTDIR/get-with-headers.py localhost:$HGPORT 'raiseerror' transfer-encoding content-type
630 $ $TESTDIR/get-with-headers.py localhost:$HGPORT 'raiseerror' transfer-encoding content-type
592 500 Internal Server Error
631 500 Internal Server Error
593 transfer-encoding: chunked
632 transfer-encoding: chunked
594
633
595 Internal Server Error (no-eol)
634 Internal Server Error (no-eol)
596 [1]
635 [1]
597
636
598 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
637 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
599 $ head -1 errors.log
638 $ head -1 errors.log
600 .* Exception happened during processing request '/raiseerror': (re)
639 .* Exception happened during processing request '/raiseerror': (re)
601
640
602 Uncaught exception after partial content sent
641 Uncaught exception after partial content sent
603
642
604 $ hg --config extensions.hgweberror=$TESTDIR/hgweberror.py serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
643 $ hg --config extensions.hgweberror=$TESTDIR/hgweberror.py serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
605 $ cat hg.pid >> $DAEMON_PIDS
644 $ cat hg.pid >> $DAEMON_PIDS
606 $ $TESTDIR/get-with-headers.py localhost:$HGPORT 'raiseerror?partialresponse=1' transfer-encoding content-type
645 $ $TESTDIR/get-with-headers.py localhost:$HGPORT 'raiseerror?partialresponse=1' transfer-encoding content-type
607 200 Script output follows
646 200 Script output follows
608 transfer-encoding: chunked
647 transfer-encoding: chunked
609 content-type: text/plain
648 content-type: text/plain
610
649
611 partial content
650 partial content
612 Internal Server Error (no-eol)
651 Internal Server Error (no-eol)
613
652
614 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
653 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
615 $ cd ..
654 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now