comparison mercurial/templatefilters.py @ 43077:687b865b95ad

formatting: byteify all mercurial/ and hgext/ string literals Done with python3.7 contrib/byteify-strings.py -i $(hg files 'set:mercurial/**.py - mercurial/thirdparty/** + hgext/**.py - hgext/fsmonitor/pywatchman/** - mercurial/__init__.py') black -l 80 -t py33 -S $(hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**" - hgext/fsmonitor/pywatchman/**') # skip-blame mass-reformatting only Differential Revision: https://phab.mercurial-scm.org/D6972
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:48:39 -0400
parents 2372284d9457
children bbcbb82e3589
comparison
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
41 filters = {} 41 filters = {}
42 42
43 templatefilter = registrar.templatefilter(filters) 43 templatefilter = registrar.templatefilter(filters)
44 44
45 45
46 @templatefilter('addbreaks', intype=bytes) 46 @templatefilter(b'addbreaks', intype=bytes)
47 def addbreaks(text): 47 def addbreaks(text):
48 """Any text. Add an XHTML "<br />" tag before the end of 48 """Any text. Add an XHTML "<br />" tag before the end of
49 every line except the last. 49 every line except the last.
50 """ 50 """
51 return text.replace('\n', '<br/>\n') 51 return text.replace(b'\n', b'<br/>\n')
52 52
53 53
54 agescales = [ 54 agescales = [
55 ("year", 3600 * 24 * 365, 'Y'), 55 (b"year", 3600 * 24 * 365, b'Y'),
56 ("month", 3600 * 24 * 30, 'M'), 56 (b"month", 3600 * 24 * 30, b'M'),
57 ("week", 3600 * 24 * 7, 'W'), 57 (b"week", 3600 * 24 * 7, b'W'),
58 ("day", 3600 * 24, 'd'), 58 (b"day", 3600 * 24, b'd'),
59 ("hour", 3600, 'h'), 59 (b"hour", 3600, b'h'),
60 ("minute", 60, 'm'), 60 (b"minute", 60, b'm'),
61 ("second", 1, 's'), 61 (b"second", 1, b's'),
62 ] 62 ]
63 63
64 64
65 @templatefilter('age', intype=templateutil.date) 65 @templatefilter(b'age', intype=templateutil.date)
66 def age(date, abbrev=False): 66 def age(date, abbrev=False):
67 """Date. Returns a human-readable date/time difference between the 67 """Date. Returns a human-readable date/time difference between the
68 given date/time and the current date/time. 68 given date/time and the current date/time.
69 """ 69 """
70 70
71 def plural(t, c): 71 def plural(t, c):
72 if c == 1: 72 if c == 1:
73 return t 73 return t
74 return t + "s" 74 return t + b"s"
75 75
76 def fmt(t, c, a): 76 def fmt(t, c, a):
77 if abbrev: 77 if abbrev:
78 return "%d%s" % (c, a) 78 return b"%d%s" % (c, a)
79 return "%d %s" % (c, plural(t, c)) 79 return b"%d %s" % (c, plural(t, c))
80 80
81 now = time.time() 81 now = time.time()
82 then = date[0] 82 then = date[0]
83 future = False 83 future = False
84 if then > now: 84 if then > now:
85 future = True 85 future = True
86 delta = max(1, int(then - now)) 86 delta = max(1, int(then - now))
87 if delta > agescales[0][1] * 30: 87 if delta > agescales[0][1] * 30:
88 return 'in the distant future' 88 return b'in the distant future'
89 else: 89 else:
90 delta = max(1, int(now - then)) 90 delta = max(1, int(now - then))
91 if delta > agescales[0][1] * 2: 91 if delta > agescales[0][1] * 2:
92 return dateutil.shortdate(date) 92 return dateutil.shortdate(date)
93 93
94 for t, s, a in agescales: 94 for t, s, a in agescales:
95 n = delta // s 95 n = delta // s
96 if n >= 2 or s == 1: 96 if n >= 2 or s == 1:
97 if future: 97 if future:
98 return '%s from now' % fmt(t, n, a) 98 return b'%s from now' % fmt(t, n, a)
99 return '%s ago' % fmt(t, n, a) 99 return b'%s ago' % fmt(t, n, a)
100 100
101 101
102 @templatefilter('basename', intype=bytes) 102 @templatefilter(b'basename', intype=bytes)
103 def basename(path): 103 def basename(path):
104 """Any text. Treats the text as a path, and returns the last 104 """Any text. Treats the text as a path, and returns the last
105 component of the path after splitting by the path separator. 105 component of the path after splitting by the path separator.
106 For example, "foo/bar/baz" becomes "baz" and "foo/bar//" becomes "". 106 For example, "foo/bar/baz" becomes "baz" and "foo/bar//" becomes "".
107 """ 107 """
108 return os.path.basename(path) 108 return os.path.basename(path)
109 109
110 110
111 @templatefilter('cbor') 111 @templatefilter(b'cbor')
112 def cbor(obj): 112 def cbor(obj):
113 """Any object. Serializes the object to CBOR bytes.""" 113 """Any object. Serializes the object to CBOR bytes."""
114 return b''.join(cborutil.streamencode(obj)) 114 return b''.join(cborutil.streamencode(obj))
115 115
116 116
117 @templatefilter('commondir') 117 @templatefilter(b'commondir')
118 def commondir(filelist): 118 def commondir(filelist):
119 """List of text. Treats each list item as file name with / 119 """List of text. Treats each list item as file name with /
120 as path separator and returns the longest common directory 120 as path separator and returns the longest common directory
121 prefix shared by all list items. 121 prefix shared by all list items.
122 Returns the empty string if no common prefix exists. 122 Returns the empty string if no common prefix exists.
140 return a[:i] 140 return a[:i]
141 return a 141 return a
142 142
143 try: 143 try:
144 if not filelist: 144 if not filelist:
145 return "" 145 return b""
146 dirlist = [f.lstrip('/').split('/')[:-1] for f in filelist] 146 dirlist = [f.lstrip(b'/').split(b'/')[:-1] for f in filelist]
147 if len(dirlist) == 1: 147 if len(dirlist) == 1:
148 return '/'.join(dirlist[0]) 148 return b'/'.join(dirlist[0])
149 a = min(dirlist) 149 a = min(dirlist)
150 b = max(dirlist) 150 b = max(dirlist)
151 # The common prefix of a and b is shared with all 151 # The common prefix of a and b is shared with all
152 # elements of the list since Python sorts lexicographical 152 # elements of the list since Python sorts lexicographical
153 # and [1, x] after [1]. 153 # and [1, x] after [1].
154 return '/'.join(common(a, b)) 154 return b'/'.join(common(a, b))
155 except TypeError: 155 except TypeError:
156 raise error.ParseError(_('argument is not a list of text')) 156 raise error.ParseError(_(b'argument is not a list of text'))
157 157
158 158
159 @templatefilter('count') 159 @templatefilter(b'count')
160 def count(i): 160 def count(i):
161 """List or text. Returns the length as an integer.""" 161 """List or text. Returns the length as an integer."""
162 try: 162 try:
163 return len(i) 163 return len(i)
164 except TypeError: 164 except TypeError:
165 raise error.ParseError(_('not countable')) 165 raise error.ParseError(_(b'not countable'))
166 166
167 167
168 @templatefilter('dirname', intype=bytes) 168 @templatefilter(b'dirname', intype=bytes)
169 def dirname(path): 169 def dirname(path):
170 """Any text. Treats the text as a path, and strips the last 170 """Any text. Treats the text as a path, and strips the last
171 component of the path after splitting by the path separator. 171 component of the path after splitting by the path separator.
172 """ 172 """
173 return os.path.dirname(path) 173 return os.path.dirname(path)
174 174
175 175
176 @templatefilter('domain', intype=bytes) 176 @templatefilter(b'domain', intype=bytes)
177 def domain(author): 177 def domain(author):
178 """Any text. Finds the first string that looks like an email 178 """Any text. Finds the first string that looks like an email
179 address, and extracts just the domain component. Example: ``User 179 address, and extracts just the domain component. Example: ``User
180 <user@example.com>`` becomes ``example.com``. 180 <user@example.com>`` becomes ``example.com``.
181 """ 181 """
182 f = author.find('@') 182 f = author.find(b'@')
183 if f == -1: 183 if f == -1:
184 return '' 184 return b''
185 author = author[f + 1 :] 185 author = author[f + 1 :]
186 f = author.find('>') 186 f = author.find(b'>')
187 if f >= 0: 187 if f >= 0:
188 author = author[:f] 188 author = author[:f]
189 return author 189 return author
190 190
191 191
192 @templatefilter('email', intype=bytes) 192 @templatefilter(b'email', intype=bytes)
193 def email(text): 193 def email(text):
194 """Any text. Extracts the first string that looks like an email 194 """Any text. Extracts the first string that looks like an email
195 address. Example: ``User <user@example.com>`` becomes 195 address. Example: ``User <user@example.com>`` becomes
196 ``user@example.com``. 196 ``user@example.com``.
197 """ 197 """
198 return stringutil.email(text) 198 return stringutil.email(text)
199 199
200 200
201 @templatefilter('escape', intype=bytes) 201 @templatefilter(b'escape', intype=bytes)
202 def escape(text): 202 def escape(text):
203 """Any text. Replaces the special XML/XHTML characters "&", "<" 203 """Any text. Replaces the special XML/XHTML characters "&", "<"
204 and ">" with XML entities, and filters out NUL characters. 204 and ">" with XML entities, and filters out NUL characters.
205 """ 205 """
206 return url.escape(text.replace('\0', ''), True) 206 return url.escape(text.replace(b'\0', b''), True)
207 207
208 208
209 para_re = None 209 para_re = None
210 space_re = None 210 space_re = None
211 211
212 212
213 def fill(text, width, initindent='', hangindent=''): 213 def fill(text, width, initindent=b'', hangindent=b''):
214 '''fill many paragraphs with optional indentation.''' 214 '''fill many paragraphs with optional indentation.'''
215 global para_re, space_re 215 global para_re, space_re
216 if para_re is None: 216 if para_re is None:
217 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M) 217 para_re = re.compile(b'(\n\n|\n\\s*[-*]\\s*)', re.M)
218 space_re = re.compile(br' +') 218 space_re = re.compile(br' +')
219 219
220 def findparas(): 220 def findparas():
221 start = 0 221 start = 0
222 while True: 222 while True:
232 ) 232 )
233 break 233 break
234 yield text[start : m.start(0)], m.group(1) 234 yield text[start : m.start(0)], m.group(1)
235 start = m.end(1) 235 start = m.end(1)
236 236
237 return "".join( 237 return b"".join(
238 [ 238 [
239 stringutil.wrap( 239 stringutil.wrap(
240 space_re.sub(' ', stringutil.wrap(para, width)), 240 space_re.sub(b' ', stringutil.wrap(para, width)),
241 width, 241 width,
242 initindent, 242 initindent,
243 hangindent, 243 hangindent,
244 ) 244 )
245 + rest 245 + rest
246 for para, rest in findparas() 246 for para, rest in findparas()
247 ] 247 ]
248 ) 248 )
249 249
250 250
251 @templatefilter('fill68', intype=bytes) 251 @templatefilter(b'fill68', intype=bytes)
252 def fill68(text): 252 def fill68(text):
253 """Any text. Wraps the text to fit in 68 columns.""" 253 """Any text. Wraps the text to fit in 68 columns."""
254 return fill(text, 68) 254 return fill(text, 68)
255 255
256 256
257 @templatefilter('fill76', intype=bytes) 257 @templatefilter(b'fill76', intype=bytes)
258 def fill76(text): 258 def fill76(text):
259 """Any text. Wraps the text to fit in 76 columns.""" 259 """Any text. Wraps the text to fit in 76 columns."""
260 return fill(text, 76) 260 return fill(text, 76)
261 261
262 262
263 @templatefilter('firstline', intype=bytes) 263 @templatefilter(b'firstline', intype=bytes)
264 def firstline(text): 264 def firstline(text):
265 """Any text. Returns the first line of text.""" 265 """Any text. Returns the first line of text."""
266 try: 266 try:
267 return text.splitlines(True)[0].rstrip('\r\n') 267 return text.splitlines(True)[0].rstrip(b'\r\n')
268 except IndexError: 268 except IndexError:
269 return '' 269 return b''
270 270
271 271
272 @templatefilter('hex', intype=bytes) 272 @templatefilter(b'hex', intype=bytes)
273 def hexfilter(text): 273 def hexfilter(text):
274 """Any text. Convert a binary Mercurial node identifier into 274 """Any text. Convert a binary Mercurial node identifier into
275 its long hexadecimal representation. 275 its long hexadecimal representation.
276 """ 276 """
277 return node.hex(text) 277 return node.hex(text)
278 278
279 279
280 @templatefilter('hgdate', intype=templateutil.date) 280 @templatefilter(b'hgdate', intype=templateutil.date)
281 def hgdate(text): 281 def hgdate(text):
282 """Date. Returns the date as a pair of numbers: "1157407993 282 """Date. Returns the date as a pair of numbers: "1157407993
283 25200" (Unix timestamp, timezone offset). 283 25200" (Unix timestamp, timezone offset).
284 """ 284 """
285 return "%d %d" % text 285 return b"%d %d" % text
286 286
287 287
288 @templatefilter('isodate', intype=templateutil.date) 288 @templatefilter(b'isodate', intype=templateutil.date)
289 def isodate(text): 289 def isodate(text):
290 """Date. Returns the date in ISO 8601 format: "2009-08-18 13:00 290 """Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
291 +0200". 291 +0200".
292 """ 292 """
293 return dateutil.datestr(text, '%Y-%m-%d %H:%M %1%2') 293 return dateutil.datestr(text, b'%Y-%m-%d %H:%M %1%2')
294 294
295 295
296 @templatefilter('isodatesec', intype=templateutil.date) 296 @templatefilter(b'isodatesec', intype=templateutil.date)
297 def isodatesec(text): 297 def isodatesec(text):
298 """Date. Returns the date in ISO 8601 format, including 298 """Date. Returns the date in ISO 8601 format, including
299 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date 299 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
300 filter. 300 filter.
301 """ 301 """
302 return dateutil.datestr(text, '%Y-%m-%d %H:%M:%S %1%2') 302 return dateutil.datestr(text, b'%Y-%m-%d %H:%M:%S %1%2')
303 303
304 304
305 def indent(text, prefix): 305 def indent(text, prefix):
306 '''indent each non-empty line of text after first with prefix.''' 306 '''indent each non-empty line of text after first with prefix.'''
307 lines = text.splitlines() 307 lines = text.splitlines()
308 num_lines = len(lines) 308 num_lines = len(lines)
309 endswithnewline = text[-1:] == '\n' 309 endswithnewline = text[-1:] == b'\n'
310 310
311 def indenter(): 311 def indenter():
312 for i in pycompat.xrange(num_lines): 312 for i in pycompat.xrange(num_lines):
313 l = lines[i] 313 l = lines[i]
314 if i and l.strip(): 314 if i and l.strip():
315 yield prefix 315 yield prefix
316 yield l 316 yield l
317 if i < num_lines - 1 or endswithnewline: 317 if i < num_lines - 1 or endswithnewline:
318 yield '\n' 318 yield b'\n'
319 319
320 return "".join(indenter()) 320 return b"".join(indenter())
321 321
322 322
323 @templatefilter('json') 323 @templatefilter(b'json')
324 def json(obj, paranoid=True): 324 def json(obj, paranoid=True):
325 """Any object. Serializes the object to a JSON formatted text.""" 325 """Any object. Serializes the object to a JSON formatted text."""
326 if obj is None: 326 if obj is None:
327 return 'null' 327 return b'null'
328 elif obj is False: 328 elif obj is False:
329 return 'false' 329 return b'false'
330 elif obj is True: 330 elif obj is True:
331 return 'true' 331 return b'true'
332 elif isinstance(obj, (int, long, float)): 332 elif isinstance(obj, (int, long, float)):
333 return pycompat.bytestr(obj) 333 return pycompat.bytestr(obj)
334 elif isinstance(obj, bytes): 334 elif isinstance(obj, bytes):
335 return '"%s"' % encoding.jsonescape(obj, paranoid=paranoid) 335 return b'"%s"' % encoding.jsonescape(obj, paranoid=paranoid)
336 elif isinstance(obj, type(u'')): 336 elif isinstance(obj, type(u'')):
337 raise error.ProgrammingError( 337 raise error.ProgrammingError(
338 'Mercurial only does output with bytes: %r' % obj 338 b'Mercurial only does output with bytes: %r' % obj
339 ) 339 )
340 elif util.safehasattr(obj, 'keys'): 340 elif util.safehasattr(obj, b'keys'):
341 out = [ 341 out = [
342 '"%s": %s' 342 b'"%s": %s'
343 % (encoding.jsonescape(k, paranoid=paranoid), json(v, paranoid)) 343 % (encoding.jsonescape(k, paranoid=paranoid), json(v, paranoid))
344 for k, v in sorted(obj.iteritems()) 344 for k, v in sorted(obj.iteritems())
345 ] 345 ]
346 return '{' + ', '.join(out) + '}' 346 return b'{' + b', '.join(out) + b'}'
347 elif util.safehasattr(obj, '__iter__'): 347 elif util.safehasattr(obj, b'__iter__'):
348 out = [json(i, paranoid) for i in obj] 348 out = [json(i, paranoid) for i in obj]
349 return '[' + ', '.join(out) + ']' 349 return b'[' + b', '.join(out) + b']'
350 raise error.ProgrammingError('cannot encode %r' % obj) 350 raise error.ProgrammingError(b'cannot encode %r' % obj)
351 351
352 352
353 @templatefilter('lower', intype=bytes) 353 @templatefilter(b'lower', intype=bytes)
354 def lower(text): 354 def lower(text):
355 """Any text. Converts the text to lowercase.""" 355 """Any text. Converts the text to lowercase."""
356 return encoding.lower(text) 356 return encoding.lower(text)
357 357
358 358
359 @templatefilter('nonempty', intype=bytes) 359 @templatefilter(b'nonempty', intype=bytes)
360 def nonempty(text): 360 def nonempty(text):
361 """Any text. Returns '(none)' if the string is empty.""" 361 """Any text. Returns '(none)' if the string is empty."""
362 return text or "(none)" 362 return text or b"(none)"
363 363
364 364
365 @templatefilter('obfuscate', intype=bytes) 365 @templatefilter(b'obfuscate', intype=bytes)
366 def obfuscate(text): 366 def obfuscate(text):
367 """Any text. Returns the input text rendered as a sequence of 367 """Any text. Returns the input text rendered as a sequence of
368 XML entities. 368 XML entities.
369 """ 369 """
370 text = unicode(text, pycompat.sysstr(encoding.encoding), r'replace') 370 text = unicode(text, pycompat.sysstr(encoding.encoding), r'replace')
371 return ''.join(['&#%d;' % ord(c) for c in text]) 371 return b''.join([b'&#%d;' % ord(c) for c in text])
372 372
373 373
374 @templatefilter('permissions', intype=bytes) 374 @templatefilter(b'permissions', intype=bytes)
375 def permissions(flags): 375 def permissions(flags):
376 if "l" in flags: 376 if b"l" in flags:
377 return "lrwxrwxrwx" 377 return b"lrwxrwxrwx"
378 if "x" in flags: 378 if b"x" in flags:
379 return "-rwxr-xr-x" 379 return b"-rwxr-xr-x"
380 return "-rw-r--r--" 380 return b"-rw-r--r--"
381 381
382 382
383 @templatefilter('person', intype=bytes) 383 @templatefilter(b'person', intype=bytes)
384 def person(author): 384 def person(author):
385 """Any text. Returns the name before an email address, 385 """Any text. Returns the name before an email address,
386 interpreting it as per RFC 5322. 386 interpreting it as per RFC 5322.
387 """ 387 """
388 return stringutil.person(author) 388 return stringutil.person(author)
389 389
390 390
391 @templatefilter('revescape', intype=bytes) 391 @templatefilter(b'revescape', intype=bytes)
392 def revescape(text): 392 def revescape(text):
393 """Any text. Escapes all "special" characters, except @. 393 """Any text. Escapes all "special" characters, except @.
394 Forward slashes are escaped twice to prevent web servers from prematurely 394 Forward slashes are escaped twice to prevent web servers from prematurely
395 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz". 395 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
396 """ 396 """
397 return urlreq.quote(text, safe='/@').replace('/', '%252F') 397 return urlreq.quote(text, safe=b'/@').replace(b'/', b'%252F')
398 398
399 399
400 @templatefilter('rfc3339date', intype=templateutil.date) 400 @templatefilter(b'rfc3339date', intype=templateutil.date)
401 def rfc3339date(text): 401 def rfc3339date(text):
402 """Date. Returns a date using the Internet date format 402 """Date. Returns a date using the Internet date format
403 specified in RFC 3339: "2009-08-18T13:00:13+02:00". 403 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
404 """ 404 """
405 return dateutil.datestr(text, "%Y-%m-%dT%H:%M:%S%1:%2") 405 return dateutil.datestr(text, b"%Y-%m-%dT%H:%M:%S%1:%2")
406 406
407 407
408 @templatefilter('rfc822date', intype=templateutil.date) 408 @templatefilter(b'rfc822date', intype=templateutil.date)
409 def rfc822date(text): 409 def rfc822date(text):
410 """Date. Returns a date using the same format used in email 410 """Date. Returns a date using the same format used in email
411 headers: "Tue, 18 Aug 2009 13:00:13 +0200". 411 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
412 """ 412 """
413 return dateutil.datestr(text, "%a, %d %b %Y %H:%M:%S %1%2") 413 return dateutil.datestr(text, b"%a, %d %b %Y %H:%M:%S %1%2")
414 414
415 415
416 @templatefilter('short', intype=bytes) 416 @templatefilter(b'short', intype=bytes)
417 def short(text): 417 def short(text):
418 """Changeset hash. Returns the short form of a changeset hash, 418 """Changeset hash. Returns the short form of a changeset hash,
419 i.e. a 12 hexadecimal digit string. 419 i.e. a 12 hexadecimal digit string.
420 """ 420 """
421 return text[:12] 421 return text[:12]
422 422
423 423
424 @templatefilter('shortbisect', intype=bytes) 424 @templatefilter(b'shortbisect', intype=bytes)
425 def shortbisect(label): 425 def shortbisect(label):
426 """Any text. Treats `label` as a bisection status, and 426 """Any text. Treats `label` as a bisection status, and
427 returns a single-character representing the status (G: good, B: bad, 427 returns a single-character representing the status (G: good, B: bad,
428 S: skipped, U: untested, I: ignored). Returns single space if `text` 428 S: skipped, U: untested, I: ignored). Returns single space if `text`
429 is not a valid bisection status. 429 is not a valid bisection status.
430 """ 430 """
431 if label: 431 if label:
432 return label[0:1].upper() 432 return label[0:1].upper()
433 return ' ' 433 return b' '
434 434
435 435
436 @templatefilter('shortdate', intype=templateutil.date) 436 @templatefilter(b'shortdate', intype=templateutil.date)
437 def shortdate(text): 437 def shortdate(text):
438 """Date. Returns a date like "2006-09-18".""" 438 """Date. Returns a date like "2006-09-18"."""
439 return dateutil.shortdate(text) 439 return dateutil.shortdate(text)
440 440
441 441
442 @templatefilter('slashpath', intype=bytes) 442 @templatefilter(b'slashpath', intype=bytes)
443 def slashpath(path): 443 def slashpath(path):
444 """Any text. Replaces the native path separator with slash.""" 444 """Any text. Replaces the native path separator with slash."""
445 return util.pconvert(path) 445 return util.pconvert(path)
446 446
447 447
448 @templatefilter('splitlines', intype=bytes) 448 @templatefilter(b'splitlines', intype=bytes)
449 def splitlines(text): 449 def splitlines(text):
450 """Any text. Split text into a list of lines.""" 450 """Any text. Split text into a list of lines."""
451 return templateutil.hybridlist(text.splitlines(), name='line') 451 return templateutil.hybridlist(text.splitlines(), name=b'line')
452 452
453 453
454 @templatefilter('stringescape', intype=bytes) 454 @templatefilter(b'stringescape', intype=bytes)
455 def stringescape(text): 455 def stringescape(text):
456 return stringutil.escapestr(text) 456 return stringutil.escapestr(text)
457 457
458 458
459 @templatefilter('stringify', intype=bytes) 459 @templatefilter(b'stringify', intype=bytes)
460 def stringify(thing): 460 def stringify(thing):
461 """Any type. Turns the value into text by converting values into 461 """Any type. Turns the value into text by converting values into
462 text and concatenating them. 462 text and concatenating them.
463 """ 463 """
464 return thing # coerced by the intype 464 return thing # coerced by the intype
465 465
466 466
467 @templatefilter('stripdir', intype=bytes) 467 @templatefilter(b'stripdir', intype=bytes)
468 def stripdir(text): 468 def stripdir(text):
469 """Treat the text as path and strip a directory level, if 469 """Treat the text as path and strip a directory level, if
470 possible. For example, "foo" and "foo/bar" becomes "foo". 470 possible. For example, "foo" and "foo/bar" becomes "foo".
471 """ 471 """
472 dir = os.path.dirname(text) 472 dir = os.path.dirname(text)
473 if dir == "": 473 if dir == b"":
474 return os.path.basename(text) 474 return os.path.basename(text)
475 else: 475 else:
476 return dir 476 return dir
477 477
478 478
479 @templatefilter('tabindent', intype=bytes) 479 @templatefilter(b'tabindent', intype=bytes)
480 def tabindent(text): 480 def tabindent(text):
481 """Any text. Returns the text, with every non-empty line 481 """Any text. Returns the text, with every non-empty line
482 except the first starting with a tab character. 482 except the first starting with a tab character.
483 """ 483 """
484 return indent(text, '\t') 484 return indent(text, b'\t')
485 485
486 486
487 @templatefilter('upper', intype=bytes) 487 @templatefilter(b'upper', intype=bytes)
488 def upper(text): 488 def upper(text):
489 """Any text. Converts the text to uppercase.""" 489 """Any text. Converts the text to uppercase."""
490 return encoding.upper(text) 490 return encoding.upper(text)
491 491
492 492
493 @templatefilter('urlescape', intype=bytes) 493 @templatefilter(b'urlescape', intype=bytes)
494 def urlescape(text): 494 def urlescape(text):
495 """Any text. Escapes all "special" characters. For example, 495 """Any text. Escapes all "special" characters. For example,
496 "foo bar" becomes "foo%20bar". 496 "foo bar" becomes "foo%20bar".
497 """ 497 """
498 return urlreq.quote(text) 498 return urlreq.quote(text)
499 499
500 500
501 @templatefilter('user', intype=bytes) 501 @templatefilter(b'user', intype=bytes)
502 def userfilter(text): 502 def userfilter(text):
503 """Any text. Returns a short representation of a user name or email 503 """Any text. Returns a short representation of a user name or email
504 address.""" 504 address."""
505 return stringutil.shortuser(text) 505 return stringutil.shortuser(text)
506 506
507 507
508 @templatefilter('emailuser', intype=bytes) 508 @templatefilter(b'emailuser', intype=bytes)
509 def emailuser(text): 509 def emailuser(text):
510 """Any text. Returns the user portion of an email address.""" 510 """Any text. Returns the user portion of an email address."""
511 return stringutil.emailuser(text) 511 return stringutil.emailuser(text)
512 512
513 513
514 @templatefilter('utf8', intype=bytes) 514 @templatefilter(b'utf8', intype=bytes)
515 def utf8(text): 515 def utf8(text):
516 """Any text. Converts from the local character encoding to UTF-8.""" 516 """Any text. Converts from the local character encoding to UTF-8."""
517 return encoding.fromlocal(text) 517 return encoding.fromlocal(text)
518 518
519 519
520 @templatefilter('xmlescape', intype=bytes) 520 @templatefilter(b'xmlescape', intype=bytes)
521 def xmlescape(text): 521 def xmlescape(text):
522 text = ( 522 text = (
523 text.replace('&', '&amp;') 523 text.replace(b'&', b'&amp;')
524 .replace('<', '&lt;') 524 .replace(b'<', b'&lt;')
525 .replace('>', '&gt;') 525 .replace(b'>', b'&gt;')
526 .replace('"', '&quot;') 526 .replace(b'"', b'&quot;')
527 .replace("'", '&#39;') 527 .replace(b"'", b'&#39;')
528 ) # &apos; invalid in HTML 528 ) # &apos; invalid in HTML
529 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text) 529 return re.sub(b'[\x00-\x08\x0B\x0C\x0E-\x1F]', b' ', text)
530 530
531 531
532 def websub(text, websubtable): 532 def websub(text, websubtable):
533 """:websub: Any text. Only applies to hgweb. Applies the regular 533 """:websub: Any text. Only applies to hgweb. Applies the regular
534 expression replacements defined in the websub section. 534 expression replacements defined in the websub section.