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