##// END OF EJS Templates
webutil: some %d instead of %s love on ints...
Augie Fackler -
r36728:a2fa5141 default
parent child Browse files
Show More
@@ -1,663 +1,665 b''
1 1 # hgweb/webutil.py - utility library for the web interface.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import copy
12 12 import difflib
13 13 import os
14 14 import re
15 15
16 16 from ..i18n import _
17 17 from ..node import hex, nullid, short
18 18
19 19 from .common import (
20 20 ErrorResponse,
21 21 HTTP_BAD_REQUEST,
22 22 HTTP_NOT_FOUND,
23 23 paritygen,
24 24 )
25 25
26 26 from .. import (
27 27 context,
28 28 error,
29 29 match,
30 30 mdiff,
31 31 patch,
32 32 pathutil,
33 33 pycompat,
34 34 templatefilters,
35 35 templatekw,
36 36 ui as uimod,
37 37 util,
38 38 )
39 39
40 40 def up(p):
41 41 if p[0] != "/":
42 42 p = "/" + p
43 43 if p[-1] == "/":
44 44 p = p[:-1]
45 45 up = os.path.dirname(p)
46 46 if up == "/":
47 47 return "/"
48 48 return up + "/"
49 49
50 50 def _navseq(step, firststep=None):
51 51 if firststep:
52 52 yield firststep
53 53 if firststep >= 20 and firststep <= 40:
54 54 firststep = 50
55 55 yield firststep
56 56 assert step > 0
57 57 assert firststep > 0
58 58 while step <= firststep:
59 59 step *= 10
60 60 while True:
61 61 yield 1 * step
62 62 yield 3 * step
63 63 step *= 10
64 64
65 65 class revnav(object):
66 66
67 67 def __init__(self, repo):
68 68 """Navigation generation object
69 69
70 70 :repo: repo object we generate nav for
71 71 """
72 72 # used for hex generation
73 73 self._revlog = repo.changelog
74 74
75 75 def __nonzero__(self):
76 76 """return True if any revision to navigate over"""
77 77 return self._first() is not None
78 78
79 79 __bool__ = __nonzero__
80 80
81 81 def _first(self):
82 82 """return the minimum non-filtered changeset or None"""
83 83 try:
84 84 return next(iter(self._revlog))
85 85 except StopIteration:
86 86 return None
87 87
88 88 def hex(self, rev):
89 89 return hex(self._revlog.node(rev))
90 90
91 91 def gen(self, pos, pagelen, limit):
92 92 """computes label and revision id for navigation link
93 93
94 94 :pos: is the revision relative to which we generate navigation.
95 95 :pagelen: the size of each navigation page
96 96 :limit: how far shall we link
97 97
98 98 The return is:
99 99 - a single element tuple
100 100 - containing a dictionary with a `before` and `after` key
101 101 - values are generator functions taking arbitrary number of kwargs
102 102 - yield items are dictionaries with `label` and `node` keys
103 103 """
104 104 if not self:
105 105 # empty repo
106 106 return ({'before': (), 'after': ()},)
107 107
108 108 targets = []
109 109 for f in _navseq(1, pagelen):
110 110 if f > limit:
111 111 break
112 112 targets.append(pos + f)
113 113 targets.append(pos - f)
114 114 targets.sort()
115 115
116 116 first = self._first()
117 117 navbefore = [("(%i)" % first, self.hex(first))]
118 118 navafter = []
119 119 for rev in targets:
120 120 if rev not in self._revlog:
121 121 continue
122 122 if pos < rev < limit:
123 123 navafter.append(("+%d" % abs(rev - pos), self.hex(rev)))
124 124 if 0 < rev < pos:
125 125 navbefore.append(("-%d" % abs(rev - pos), self.hex(rev)))
126 126
127 127
128 128 navafter.append(("tip", "tip"))
129 129
130 130 data = lambda i: {"label": i[0], "node": i[1]}
131 131 return ({'before': lambda **map: (data(i) for i in navbefore),
132 132 'after': lambda **map: (data(i) for i in navafter)},)
133 133
134 134 class filerevnav(revnav):
135 135
136 136 def __init__(self, repo, path):
137 137 """Navigation generation object
138 138
139 139 :repo: repo object we generate nav for
140 140 :path: path of the file we generate nav for
141 141 """
142 142 # used for iteration
143 143 self._changelog = repo.unfiltered().changelog
144 144 # used for hex generation
145 145 self._revlog = repo.file(path)
146 146
147 147 def hex(self, rev):
148 148 return hex(self._changelog.node(self._revlog.linkrev(rev)))
149 149
150 150 class _siblings(object):
151 151 def __init__(self, siblings=None, hiderev=None):
152 152 if siblings is None:
153 153 siblings = []
154 154 self.siblings = [s for s in siblings if s.node() != nullid]
155 155 if len(self.siblings) == 1 and self.siblings[0].rev() == hiderev:
156 156 self.siblings = []
157 157
158 158 def __iter__(self):
159 159 for s in self.siblings:
160 160 d = {
161 161 'node': s.hex(),
162 162 'rev': s.rev(),
163 163 'user': s.user(),
164 164 'date': s.date(),
165 165 'description': s.description(),
166 166 'branch': s.branch(),
167 167 }
168 168 if util.safehasattr(s, 'path'):
169 169 d['file'] = s.path()
170 170 yield d
171 171
172 172 def __len__(self):
173 173 return len(self.siblings)
174 174
175 175 def difffeatureopts(req, ui, section):
176 176 diffopts = patch.difffeatureopts(ui, untrusted=True,
177 177 section=section, whitespace=True)
178 178
179 179 for k in ('ignorews', 'ignorewsamount', 'ignorewseol', 'ignoreblanklines'):
180 180 v = req.form.get(k, [None])[0]
181 181 if v is not None:
182 182 v = util.parsebool(v)
183 183 setattr(diffopts, k, v if v is not None else True)
184 184
185 185 return diffopts
186 186
187 187 def annotate(req, fctx, ui):
188 188 diffopts = difffeatureopts(req, ui, 'annotate')
189 189 return fctx.annotate(follow=True, linenumber=True, diffopts=diffopts)
190 190
191 191 def parents(ctx, hide=None):
192 192 if isinstance(ctx, context.basefilectx):
193 193 introrev = ctx.introrev()
194 194 if ctx.changectx().rev() != introrev:
195 195 return _siblings([ctx.repo()[introrev]], hide)
196 196 return _siblings(ctx.parents(), hide)
197 197
198 198 def children(ctx, hide=None):
199 199 return _siblings(ctx.children(), hide)
200 200
201 201 def renamelink(fctx):
202 202 r = fctx.renamed()
203 203 if r:
204 204 return [{'file': r[0], 'node': hex(r[1])}]
205 205 return []
206 206
207 207 def nodetagsdict(repo, node):
208 208 return [{"name": i} for i in repo.nodetags(node)]
209 209
210 210 def nodebookmarksdict(repo, node):
211 211 return [{"name": i} for i in repo.nodebookmarks(node)]
212 212
213 213 def nodebranchdict(repo, ctx):
214 214 branches = []
215 215 branch = ctx.branch()
216 216 # If this is an empty repo, ctx.node() == nullid,
217 217 # ctx.branch() == 'default'.
218 218 try:
219 219 branchnode = repo.branchtip(branch)
220 220 except error.RepoLookupError:
221 221 branchnode = None
222 222 if branchnode == ctx.node():
223 223 branches.append({"name": branch})
224 224 return branches
225 225
226 226 def nodeinbranch(repo, ctx):
227 227 branches = []
228 228 branch = ctx.branch()
229 229 try:
230 230 branchnode = repo.branchtip(branch)
231 231 except error.RepoLookupError:
232 232 branchnode = None
233 233 if branch != 'default' and branchnode != ctx.node():
234 234 branches.append({"name": branch})
235 235 return branches
236 236
237 237 def nodebranchnodefault(ctx):
238 238 branches = []
239 239 branch = ctx.branch()
240 240 if branch != 'default':
241 241 branches.append({"name": branch})
242 242 return branches
243 243
244 244 def showtag(repo, tmpl, t1, node=nullid, **args):
245 245 for t in repo.nodetags(node):
246 246 yield tmpl(t1, tag=t, **args)
247 247
248 248 def showbookmark(repo, tmpl, t1, node=nullid, **args):
249 249 for t in repo.nodebookmarks(node):
250 250 yield tmpl(t1, bookmark=t, **args)
251 251
252 252 def branchentries(repo, stripecount, limit=0):
253 253 tips = []
254 254 heads = repo.heads()
255 255 parity = paritygen(stripecount)
256 256 sortkey = lambda item: (not item[1], item[0].rev())
257 257
258 258 def entries(**map):
259 259 count = 0
260 260 if not tips:
261 261 for tag, hs, tip, closed in repo.branchmap().iterbranches():
262 262 tips.append((repo[tip], closed))
263 263 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
264 264 if limit > 0 and count >= limit:
265 265 return
266 266 count += 1
267 267 if closed:
268 268 status = 'closed'
269 269 elif ctx.node() not in heads:
270 270 status = 'inactive'
271 271 else:
272 272 status = 'open'
273 273 yield {
274 274 'parity': next(parity),
275 275 'branch': ctx.branch(),
276 276 'status': status,
277 277 'node': ctx.hex(),
278 278 'date': ctx.date()
279 279 }
280 280
281 281 return entries
282 282
283 283 def cleanpath(repo, path):
284 284 path = path.lstrip('/')
285 285 return pathutil.canonpath(repo.root, '', path)
286 286
287 287 def changeidctx(repo, changeid):
288 288 try:
289 289 ctx = repo[changeid]
290 290 except error.RepoError:
291 291 man = repo.manifestlog._revlog
292 292 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
293 293
294 294 return ctx
295 295
296 296 def changectx(repo, req):
297 297 changeid = "tip"
298 298 if 'node' in req.form:
299 299 changeid = req.form['node'][0]
300 300 ipos = changeid.find(':')
301 301 if ipos != -1:
302 302 changeid = changeid[(ipos + 1):]
303 303 elif 'manifest' in req.form:
304 304 changeid = req.form['manifest'][0]
305 305
306 306 return changeidctx(repo, changeid)
307 307
308 308 def basechangectx(repo, req):
309 309 if 'node' in req.form:
310 310 changeid = req.form['node'][0]
311 311 ipos = changeid.find(':')
312 312 if ipos != -1:
313 313 changeid = changeid[:ipos]
314 314 return changeidctx(repo, changeid)
315 315
316 316 return None
317 317
318 318 def filectx(repo, req):
319 319 if 'file' not in req.form:
320 320 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
321 321 path = cleanpath(repo, req.form['file'][0])
322 322 if 'node' in req.form:
323 323 changeid = req.form['node'][0]
324 324 elif 'filenode' in req.form:
325 325 changeid = req.form['filenode'][0]
326 326 else:
327 327 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
328 328 try:
329 329 fctx = repo[changeid][path]
330 330 except error.RepoError:
331 331 fctx = repo.filectx(path, fileid=changeid)
332 332
333 333 return fctx
334 334
335 335 def linerange(req):
336 336 linerange = req.form.get('linerange')
337 337 if linerange is None:
338 338 return None
339 339 if len(linerange) > 1:
340 340 raise ErrorResponse(HTTP_BAD_REQUEST,
341 341 'redundant linerange parameter')
342 342 try:
343 343 fromline, toline = map(int, linerange[0].split(':', 1))
344 344 except ValueError:
345 345 raise ErrorResponse(HTTP_BAD_REQUEST,
346 346 'invalid linerange parameter')
347 347 try:
348 348 return util.processlinerange(fromline, toline)
349 349 except error.ParseError as exc:
350 350 raise ErrorResponse(HTTP_BAD_REQUEST, pycompat.bytestr(exc))
351 351
352 352 def formatlinerange(fromline, toline):
353 353 return '%d:%d' % (fromline + 1, toline)
354 354
355 355 def succsandmarkers(context, mapping):
356 356 repo = context.resource(mapping, 'repo')
357 357 for item in templatekw.showsuccsandmarkers(context, mapping):
358 358 item['successors'] = _siblings(repo[successor]
359 359 for successor in item['successors'])
360 360 yield item
361 361
362 362 # teach templater succsandmarkers is switched to (context, mapping) API
363 363 succsandmarkers._requires = {'repo', 'ctx', 'templ'}
364 364
365 365 def commonentry(repo, ctx):
366 366 node = ctx.node()
367 367 return {
368 368 # TODO: perhaps ctx.changectx() should be assigned if ctx is a
369 369 # filectx, but I'm not pretty sure if that would always work because
370 370 # fctx.parents() != fctx.changectx.parents() for example.
371 371 'ctx': ctx,
372 372 'revcache': {},
373 373 'rev': ctx.rev(),
374 374 'node': hex(node),
375 375 'author': ctx.user(),
376 376 'desc': ctx.description(),
377 377 'date': ctx.date(),
378 378 'extra': ctx.extra(),
379 379 'phase': ctx.phasestr(),
380 380 'obsolete': ctx.obsolete(),
381 381 'succsandmarkers': succsandmarkers,
382 382 'instabilities': [{"instability": i} for i in ctx.instabilities()],
383 383 'branch': nodebranchnodefault(ctx),
384 384 'inbranch': nodeinbranch(repo, ctx),
385 385 'branches': nodebranchdict(repo, ctx),
386 386 'tags': nodetagsdict(repo, node),
387 387 'bookmarks': nodebookmarksdict(repo, node),
388 388 'parent': lambda **x: parents(ctx),
389 389 'child': lambda **x: children(ctx),
390 390 }
391 391
392 392 def changelistentry(web, ctx, tmpl):
393 393 '''Obtain a dictionary to be used for entries in a changelist.
394 394
395 395 This function is called when producing items for the "entries" list passed
396 396 to the "shortlog" and "changelog" templates.
397 397 '''
398 398 repo = web.repo
399 399 rev = ctx.rev()
400 400 n = ctx.node()
401 401 showtags = showtag(repo, tmpl, 'changelogtag', n)
402 402 files = listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
403 403
404 404 entry = commonentry(repo, ctx)
405 405 entry.update(
406 406 allparents=lambda **x: parents(ctx),
407 407 parent=lambda **x: parents(ctx, rev - 1),
408 408 child=lambda **x: children(ctx, rev + 1),
409 409 changelogtag=showtags,
410 410 files=files,
411 411 )
412 412 return entry
413 413
414 414 def symrevorshortnode(req, ctx):
415 415 if 'node' in req.form:
416 416 return templatefilters.revescape(req.form['node'][0])
417 417 else:
418 418 return short(ctx.node())
419 419
420 420 def changesetentry(web, req, tmpl, ctx):
421 421 '''Obtain a dictionary to be used to render the "changeset" template.'''
422 422
423 423 showtags = showtag(web.repo, tmpl, 'changesettag', ctx.node())
424 424 showbookmarks = showbookmark(web.repo, tmpl, 'changesetbookmark',
425 425 ctx.node())
426 426 showbranch = nodebranchnodefault(ctx)
427 427
428 428 files = []
429 429 parity = paritygen(web.stripecount)
430 430 for blockno, f in enumerate(ctx.files()):
431 431 template = 'filenodelink' if f in ctx else 'filenolink'
432 432 files.append(tmpl(template,
433 433 node=ctx.hex(), file=f, blockno=blockno + 1,
434 434 parity=next(parity)))
435 435
436 436 basectx = basechangectx(web.repo, req)
437 437 if basectx is None:
438 438 basectx = ctx.p1()
439 439
440 440 style = web.config('web', 'style')
441 441 if 'style' in req.form:
442 442 style = req.form['style'][0]
443 443
444 444 diff = diffs(web, tmpl, ctx, basectx, None, style)
445 445
446 446 parity = paritygen(web.stripecount)
447 447 diffstatsgen = diffstatgen(ctx, basectx)
448 448 diffstats = diffstat(tmpl, ctx, diffstatsgen, parity)
449 449
450 450 return dict(
451 451 diff=diff,
452 452 symrev=symrevorshortnode(req, ctx),
453 453 basenode=basectx.hex(),
454 454 changesettag=showtags,
455 455 changesetbookmark=showbookmarks,
456 456 changesetbranch=showbranch,
457 457 files=files,
458 458 diffsummary=lambda **x: diffsummary(diffstatsgen),
459 459 diffstat=diffstats,
460 460 archives=web.archivelist(ctx.hex()),
461 461 **pycompat.strkwargs(commonentry(web.repo, ctx)))
462 462
463 463 def listfilediffs(tmpl, files, node, max):
464 464 for f in files[:max]:
465 465 yield tmpl('filedifflink', node=hex(node), file=f)
466 466 if len(files) > max:
467 467 yield tmpl('fileellipses')
468 468
469 469 def diffs(web, tmpl, ctx, basectx, files, style, linerange=None,
470 470 lineidprefix=''):
471 471
472 472 def prettyprintlines(lines, blockno):
473 473 for lineno, l in enumerate(lines, 1):
474 474 difflineno = "%d.%d" % (blockno, lineno)
475 475 if l.startswith('+'):
476 476 ltype = "difflineplus"
477 477 elif l.startswith('-'):
478 478 ltype = "difflineminus"
479 479 elif l.startswith('@'):
480 480 ltype = "difflineat"
481 481 else:
482 482 ltype = "diffline"
483 483 yield tmpl(ltype,
484 484 line=l,
485 485 lineno=lineno,
486 486 lineid=lineidprefix + "l%s" % difflineno,
487 487 linenumber="% 8s" % difflineno)
488 488
489 489 repo = web.repo
490 490 if files:
491 491 m = match.exact(repo.root, repo.getcwd(), files)
492 492 else:
493 493 m = match.always(repo.root, repo.getcwd())
494 494
495 495 diffopts = patch.diffopts(repo.ui, untrusted=True)
496 496 node1 = basectx.node()
497 497 node2 = ctx.node()
498 498 parity = paritygen(web.stripecount)
499 499
500 500 diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
501 501 for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
502 502 if style != 'raw':
503 503 header = header[1:]
504 504 lines = [h + '\n' for h in header]
505 505 for hunkrange, hunklines in hunks:
506 506 if linerange is not None and hunkrange is not None:
507 507 s1, l1, s2, l2 = hunkrange
508 508 if not mdiff.hunkinrange((s2, l2), linerange):
509 509 continue
510 510 lines.extend(hunklines)
511 511 if lines:
512 512 yield tmpl('diffblock', parity=next(parity), blockno=blockno,
513 513 lines=prettyprintlines(lines, blockno))
514 514
515 515 def compare(tmpl, context, leftlines, rightlines):
516 516 '''Generator function that provides side-by-side comparison data.'''
517 517
518 518 def compline(type, leftlineno, leftline, rightlineno, rightline):
519 lineid = leftlineno and ("l%s" % leftlineno) or ''
520 lineid += rightlineno and ("r%s" % rightlineno) or ''
519 lineid = leftlineno and ("l%d" % leftlineno) or ''
520 lineid += rightlineno and ("r%d" % rightlineno) or ''
521 llno = '%d' % leftlineno if leftlineno else ''
522 rlno = '%d' % rightlineno if rightlineno else ''
521 523 return tmpl('comparisonline',
522 524 type=type,
523 525 lineid=lineid,
524 526 leftlineno=leftlineno,
525 leftlinenumber="% 6s" % (leftlineno or ''),
527 leftlinenumber="% 6s" % llno,
526 528 leftline=leftline or '',
527 529 rightlineno=rightlineno,
528 rightlinenumber="% 6s" % (rightlineno or ''),
530 rightlinenumber="% 6s" % rlno,
529 531 rightline=rightline or '')
530 532
531 533 def getblock(opcodes):
532 534 for type, llo, lhi, rlo, rhi in opcodes:
533 535 len1 = lhi - llo
534 536 len2 = rhi - rlo
535 537 count = min(len1, len2)
536 538 for i in xrange(count):
537 539 yield compline(type=type,
538 540 leftlineno=llo + i + 1,
539 541 leftline=leftlines[llo + i],
540 542 rightlineno=rlo + i + 1,
541 543 rightline=rightlines[rlo + i])
542 544 if len1 > len2:
543 545 for i in xrange(llo + count, lhi):
544 546 yield compline(type=type,
545 547 leftlineno=i + 1,
546 548 leftline=leftlines[i],
547 549 rightlineno=None,
548 550 rightline=None)
549 551 elif len2 > len1:
550 552 for i in xrange(rlo + count, rhi):
551 553 yield compline(type=type,
552 554 leftlineno=None,
553 555 leftline=None,
554 556 rightlineno=i + 1,
555 557 rightline=rightlines[i])
556 558
557 559 s = difflib.SequenceMatcher(None, leftlines, rightlines)
558 560 if context < 0:
559 561 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
560 562 else:
561 563 for oc in s.get_grouped_opcodes(n=context):
562 564 yield tmpl('comparisonblock', lines=getblock(oc))
563 565
564 566 def diffstatgen(ctx, basectx):
565 567 '''Generator function that provides the diffstat data.'''
566 568
567 569 stats = patch.diffstatdata(
568 570 util.iterlines(ctx.diff(basectx, noprefix=False)))
569 571 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
570 572 while True:
571 573 yield stats, maxname, maxtotal, addtotal, removetotal, binary
572 574
573 575 def diffsummary(statgen):
574 576 '''Return a short summary of the diff.'''
575 577
576 578 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
577 579 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
578 580 len(stats), addtotal, removetotal)
579 581
580 582 def diffstat(tmpl, ctx, statgen, parity):
581 583 '''Return a diffstat template for each file in the diff.'''
582 584
583 585 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
584 586 files = ctx.files()
585 587
586 588 def pct(i):
587 589 if maxtotal == 0:
588 590 return 0
589 591 return (float(i) / maxtotal) * 100
590 592
591 593 fileno = 0
592 594 for filename, adds, removes, isbinary in stats:
593 595 template = 'diffstatlink' if filename in files else 'diffstatnolink'
594 596 total = adds + removes
595 597 fileno += 1
596 598 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
597 599 total=total, addpct=pct(adds), removepct=pct(removes),
598 600 parity=next(parity))
599 601
600 602 class sessionvars(object):
601 603 def __init__(self, vars, start='?'):
602 604 self.start = start
603 605 self.vars = vars
604 606 def __getitem__(self, key):
605 607 return self.vars[key]
606 608 def __setitem__(self, key, value):
607 609 self.vars[key] = value
608 610 def __copy__(self):
609 611 return sessionvars(copy.copy(self.vars), self.start)
610 612 def __iter__(self):
611 613 separator = self.start
612 614 for key, value in sorted(self.vars.iteritems()):
613 615 yield {'name': key,
614 616 'value': pycompat.bytestr(value),
615 617 'separator': separator,
616 618 }
617 619 separator = '&'
618 620
619 621 class wsgiui(uimod.ui):
620 622 # default termwidth breaks under mod_wsgi
621 623 def termwidth(self):
622 624 return 80
623 625
624 626 def getwebsubs(repo):
625 627 websubtable = []
626 628 websubdefs = repo.ui.configitems('websub')
627 629 # we must maintain interhg backwards compatibility
628 630 websubdefs += repo.ui.configitems('interhg')
629 631 for key, pattern in websubdefs:
630 632 # grab the delimiter from the character after the "s"
631 633 unesc = pattern[1:2]
632 634 delim = re.escape(unesc)
633 635
634 636 # identify portions of the pattern, taking care to avoid escaped
635 637 # delimiters. the replace format and flags are optional, but
636 638 # delimiters are required.
637 639 match = re.match(
638 640 br'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
639 641 % (delim, delim, delim), pattern)
640 642 if not match:
641 643 repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
642 644 % (key, pattern))
643 645 continue
644 646
645 647 # we need to unescape the delimiter for regexp and format
646 648 delim_re = re.compile(br'(?<!\\)\\%s' % delim)
647 649 regexp = delim_re.sub(unesc, match.group(1))
648 650 format = delim_re.sub(unesc, match.group(2))
649 651
650 652 # the pattern allows for 6 regexp flags, so set them if necessary
651 653 flagin = match.group(3)
652 654 flags = 0
653 655 if flagin:
654 656 for flag in flagin.upper():
655 657 flags |= re.__dict__[flag]
656 658
657 659 try:
658 660 regexp = re.compile(regexp, flags)
659 661 websubtable.append((regexp, format))
660 662 except re.error:
661 663 repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
662 664 % (key, regexp))
663 665 return websubtable
General Comments 0
You need to be logged in to leave comments. Login now