##// END OF EJS Templates
fileset: roughly adjust weights of functions...
Yuya Nishihara -
r38866:bfd5def3 default
parent child Browse files
Show More
@@ -1,557 +1,557 b''
1 1 # fileset.py - file set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import re
12 12
13 13 from .i18n import _
14 14 from . import (
15 15 error,
16 16 filesetlang,
17 17 match as matchmod,
18 18 merge,
19 19 pycompat,
20 20 registrar,
21 21 scmutil,
22 22 util,
23 23 )
24 24 from .utils import (
25 25 stringutil,
26 26 )
27 27
28 28 # helpers for processing parsed tree
29 29 getsymbol = filesetlang.getsymbol
30 30 getstring = filesetlang.getstring
31 31 _getkindpat = filesetlang.getkindpat
32 32 getpattern = filesetlang.getpattern
33 33 getargs = filesetlang.getargs
34 34
35 35 def getmatch(mctx, x):
36 36 if not x:
37 37 raise error.ParseError(_("missing argument"))
38 38 return methods[x[0]](mctx, *x[1:])
39 39
40 40 def stringmatch(mctx, x):
41 41 return mctx.matcher([x])
42 42
43 43 def kindpatmatch(mctx, x, y):
44 44 return stringmatch(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
45 45 _("pattern must be a string")))
46 46
47 47 def andmatch(mctx, x, y):
48 48 xm = getmatch(mctx, x)
49 49 ym = getmatch(mctx, y)
50 50 return matchmod.intersectmatchers(xm, ym)
51 51
52 52 def ormatch(mctx, *xs):
53 53 ms = [getmatch(mctx, x) for x in xs]
54 54 return matchmod.unionmatcher(ms)
55 55
56 56 def notmatch(mctx, x):
57 57 m = getmatch(mctx, x)
58 58 return mctx.predicate(lambda f: not m(f), predrepr=('<not %r>', m))
59 59
60 60 def minusmatch(mctx, x, y):
61 61 xm = getmatch(mctx, x)
62 62 ym = getmatch(mctx, y)
63 63 return matchmod.differencematcher(xm, ym)
64 64
65 65 def listmatch(mctx, *xs):
66 66 raise error.ParseError(_("can't use a list in this context"),
67 67 hint=_('see \'hg help "filesets.x or y"\''))
68 68
69 69 def func(mctx, a, b):
70 70 funcname = getsymbol(a)
71 71 if funcname in symbols:
72 72 return symbols[funcname](mctx, b)
73 73
74 74 keep = lambda fn: getattr(fn, '__doc__', None) is not None
75 75
76 76 syms = [s for (s, fn) in symbols.items() if keep(fn)]
77 77 raise error.UnknownIdentifier(funcname, syms)
78 78
79 79 # symbols are callable like:
80 80 # fun(mctx, x)
81 81 # with:
82 82 # mctx - current matchctx instance
83 83 # x - argument in tree form
84 84 symbols = filesetlang.symbols
85 85
86 86 # filesets using matchctx.status()
87 87 _statuscallers = set()
88 88
89 89 predicate = registrar.filesetpredicate()
90 90
91 @predicate('modified()', callstatus=True)
91 @predicate('modified()', callstatus=True, weight=10)
92 92 def modified(mctx, x):
93 93 """File that is modified according to :hg:`status`.
94 94 """
95 95 # i18n: "modified" is a keyword
96 96 getargs(x, 0, 0, _("modified takes no arguments"))
97 97 s = set(mctx.status().modified)
98 98 return mctx.predicate(s.__contains__, predrepr='modified')
99 99
100 @predicate('added()', callstatus=True)
100 @predicate('added()', callstatus=True, weight=10)
101 101 def added(mctx, x):
102 102 """File that is added according to :hg:`status`.
103 103 """
104 104 # i18n: "added" is a keyword
105 105 getargs(x, 0, 0, _("added takes no arguments"))
106 106 s = set(mctx.status().added)
107 107 return mctx.predicate(s.__contains__, predrepr='added')
108 108
109 @predicate('removed()', callstatus=True)
109 @predicate('removed()', callstatus=True, weight=10)
110 110 def removed(mctx, x):
111 111 """File that is removed according to :hg:`status`.
112 112 """
113 113 # i18n: "removed" is a keyword
114 114 getargs(x, 0, 0, _("removed takes no arguments"))
115 115 s = set(mctx.status().removed)
116 116 return mctx.predicate(s.__contains__, predrepr='removed')
117 117
118 @predicate('deleted()', callstatus=True)
118 @predicate('deleted()', callstatus=True, weight=10)
119 119 def deleted(mctx, x):
120 120 """Alias for ``missing()``.
121 121 """
122 122 # i18n: "deleted" is a keyword
123 123 getargs(x, 0, 0, _("deleted takes no arguments"))
124 124 s = set(mctx.status().deleted)
125 125 return mctx.predicate(s.__contains__, predrepr='deleted')
126 126
127 @predicate('missing()', callstatus=True)
127 @predicate('missing()', callstatus=True, weight=10)
128 128 def missing(mctx, x):
129 129 """File that is missing according to :hg:`status`.
130 130 """
131 131 # i18n: "missing" is a keyword
132 132 getargs(x, 0, 0, _("missing takes no arguments"))
133 133 s = set(mctx.status().deleted)
134 134 return mctx.predicate(s.__contains__, predrepr='deleted')
135 135
136 @predicate('unknown()', callstatus=True)
136 @predicate('unknown()', callstatus=True, weight=50)
137 137 def unknown(mctx, x):
138 138 """File that is unknown according to :hg:`status`."""
139 139 # i18n: "unknown" is a keyword
140 140 getargs(x, 0, 0, _("unknown takes no arguments"))
141 141 s = set(mctx.status().unknown)
142 142 return mctx.predicate(s.__contains__, predrepr='unknown')
143 143
144 @predicate('ignored()', callstatus=True)
144 @predicate('ignored()', callstatus=True, weight=50)
145 145 def ignored(mctx, x):
146 146 """File that is ignored according to :hg:`status`."""
147 147 # i18n: "ignored" is a keyword
148 148 getargs(x, 0, 0, _("ignored takes no arguments"))
149 149 s = set(mctx.status().ignored)
150 150 return mctx.predicate(s.__contains__, predrepr='ignored')
151 151
152 @predicate('clean()', callstatus=True)
152 @predicate('clean()', callstatus=True, weight=10)
153 153 def clean(mctx, x):
154 154 """File that is clean according to :hg:`status`.
155 155 """
156 156 # i18n: "clean" is a keyword
157 157 getargs(x, 0, 0, _("clean takes no arguments"))
158 158 s = set(mctx.status().clean)
159 159 return mctx.predicate(s.__contains__, predrepr='clean')
160 160
161 161 @predicate('tracked()')
162 162 def tracked(mctx, x):
163 163 """File that is under Mercurial control."""
164 164 # i18n: "tracked" is a keyword
165 165 getargs(x, 0, 0, _("tracked takes no arguments"))
166 166 return mctx.predicate(mctx.ctx.__contains__, predrepr='tracked')
167 167
168 @predicate('binary()')
168 @predicate('binary()', weight=30)
169 169 def binary(mctx, x):
170 170 """File that appears to be binary (contains NUL bytes).
171 171 """
172 172 # i18n: "binary" is a keyword
173 173 getargs(x, 0, 0, _("binary takes no arguments"))
174 174 return mctx.fpredicate(lambda fctx: fctx.isbinary(),
175 175 predrepr='binary', cache=True)
176 176
177 177 @predicate('exec()')
178 178 def exec_(mctx, x):
179 179 """File that is marked as executable.
180 180 """
181 181 # i18n: "exec" is a keyword
182 182 getargs(x, 0, 0, _("exec takes no arguments"))
183 183 ctx = mctx.ctx
184 184 return mctx.predicate(lambda f: ctx.flags(f) == 'x', predrepr='exec')
185 185
186 186 @predicate('symlink()')
187 187 def symlink(mctx, x):
188 188 """File that is marked as a symlink.
189 189 """
190 190 # i18n: "symlink" is a keyword
191 191 getargs(x, 0, 0, _("symlink takes no arguments"))
192 192 ctx = mctx.ctx
193 193 return mctx.predicate(lambda f: ctx.flags(f) == 'l', predrepr='symlink')
194 194
195 @predicate('resolved()')
195 @predicate('resolved()', weight=10)
196 196 def resolved(mctx, x):
197 197 """File that is marked resolved according to :hg:`resolve -l`.
198 198 """
199 199 # i18n: "resolved" is a keyword
200 200 getargs(x, 0, 0, _("resolved takes no arguments"))
201 201 if mctx.ctx.rev() is not None:
202 202 return mctx.never()
203 203 ms = merge.mergestate.read(mctx.ctx.repo())
204 204 return mctx.predicate(lambda f: f in ms and ms[f] == 'r',
205 205 predrepr='resolved')
206 206
207 @predicate('unresolved()')
207 @predicate('unresolved()', weight=10)
208 208 def unresolved(mctx, x):
209 209 """File that is marked unresolved according to :hg:`resolve -l`.
210 210 """
211 211 # i18n: "unresolved" is a keyword
212 212 getargs(x, 0, 0, _("unresolved takes no arguments"))
213 213 if mctx.ctx.rev() is not None:
214 214 return mctx.never()
215 215 ms = merge.mergestate.read(mctx.ctx.repo())
216 216 return mctx.predicate(lambda f: f in ms and ms[f] == 'u',
217 217 predrepr='unresolved')
218 218
219 @predicate('hgignore()')
219 @predicate('hgignore()', weight=10)
220 220 def hgignore(mctx, x):
221 221 """File that matches the active .hgignore pattern.
222 222 """
223 223 # i18n: "hgignore" is a keyword
224 224 getargs(x, 0, 0, _("hgignore takes no arguments"))
225 225 return mctx.ctx.repo().dirstate._ignore
226 226
227 @predicate('portable()')
227 @predicate('portable()', weight=0.5)
228 228 def portable(mctx, x):
229 229 """File that has a portable name. (This doesn't include filenames with case
230 230 collisions.)
231 231 """
232 232 # i18n: "portable" is a keyword
233 233 getargs(x, 0, 0, _("portable takes no arguments"))
234 234 return mctx.predicate(lambda f: util.checkwinfilename(f) is None,
235 235 predrepr='portable')
236 236
237 @predicate('grep(regex)')
237 @predicate('grep(regex)', weight=30)
238 238 def grep(mctx, x):
239 239 """File contains the given regular expression.
240 240 """
241 241 try:
242 242 # i18n: "grep" is a keyword
243 243 r = re.compile(getstring(x, _("grep requires a pattern")))
244 244 except re.error as e:
245 245 raise error.ParseError(_('invalid match pattern: %s') %
246 246 stringutil.forcebytestr(e))
247 247 return mctx.fpredicate(lambda fctx: r.search(fctx.data()),
248 248 predrepr=('grep(%r)', r.pattern), cache=True)
249 249
250 250 def _sizetomax(s):
251 251 try:
252 252 s = s.strip().lower()
253 253 for k, v in util._sizeunits:
254 254 if s.endswith(k):
255 255 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
256 256 n = s[:-len(k)]
257 257 inc = 1.0
258 258 if "." in n:
259 259 inc /= 10 ** len(n.split(".")[1])
260 260 return int((float(n) + inc) * v) - 1
261 261 # no extension, this is a precise value
262 262 return int(s)
263 263 except ValueError:
264 264 raise error.ParseError(_("couldn't parse size: %s") % s)
265 265
266 266 def sizematcher(expr):
267 267 """Return a function(size) -> bool from the ``size()`` expression"""
268 268 expr = expr.strip()
269 269 if '-' in expr: # do we have a range?
270 270 a, b = expr.split('-', 1)
271 271 a = util.sizetoint(a)
272 272 b = util.sizetoint(b)
273 273 return lambda x: x >= a and x <= b
274 274 elif expr.startswith("<="):
275 275 a = util.sizetoint(expr[2:])
276 276 return lambda x: x <= a
277 277 elif expr.startswith("<"):
278 278 a = util.sizetoint(expr[1:])
279 279 return lambda x: x < a
280 280 elif expr.startswith(">="):
281 281 a = util.sizetoint(expr[2:])
282 282 return lambda x: x >= a
283 283 elif expr.startswith(">"):
284 284 a = util.sizetoint(expr[1:])
285 285 return lambda x: x > a
286 286 else:
287 287 a = util.sizetoint(expr)
288 288 b = _sizetomax(expr)
289 289 return lambda x: x >= a and x <= b
290 290
291 @predicate('size(expression)')
291 @predicate('size(expression)', weight=10)
292 292 def size(mctx, x):
293 293 """File size matches the given expression. Examples:
294 294
295 295 - size('1k') - files from 1024 to 2047 bytes
296 296 - size('< 20k') - files less than 20480 bytes
297 297 - size('>= .5MB') - files at least 524288 bytes
298 298 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
299 299 """
300 300 # i18n: "size" is a keyword
301 301 expr = getstring(x, _("size requires an expression"))
302 302 m = sizematcher(expr)
303 303 return mctx.fpredicate(lambda fctx: m(fctx.size()),
304 304 predrepr=('size(%r)', expr), cache=True)
305 305
306 @predicate('encoding(name)')
306 @predicate('encoding(name)', weight=30)
307 307 def encoding(mctx, x):
308 308 """File can be successfully decoded with the given character
309 309 encoding. May not be useful for encodings other than ASCII and
310 310 UTF-8.
311 311 """
312 312
313 313 # i18n: "encoding" is a keyword
314 314 enc = getstring(x, _("encoding requires an encoding name"))
315 315
316 316 def encp(fctx):
317 317 d = fctx.data()
318 318 try:
319 319 d.decode(pycompat.sysstr(enc))
320 320 return True
321 321 except LookupError:
322 322 raise error.Abort(_("unknown encoding '%s'") % enc)
323 323 except UnicodeDecodeError:
324 324 return False
325 325
326 326 return mctx.fpredicate(encp, predrepr=('encoding(%r)', enc), cache=True)
327 327
328 @predicate('eol(style)')
328 @predicate('eol(style)', weight=30)
329 329 def eol(mctx, x):
330 330 """File contains newlines of the given style (dos, unix, mac). Binary
331 331 files are excluded, files with mixed line endings match multiple
332 332 styles.
333 333 """
334 334
335 335 # i18n: "eol" is a keyword
336 336 enc = getstring(x, _("eol requires a style name"))
337 337
338 338 def eolp(fctx):
339 339 if fctx.isbinary():
340 340 return False
341 341 d = fctx.data()
342 342 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
343 343 return True
344 344 elif enc == 'unix' and re.search('(?<!\r)\n', d):
345 345 return True
346 346 elif enc == 'mac' and re.search('\r(?!\n)', d):
347 347 return True
348 348 return False
349 349 return mctx.fpredicate(eolp, predrepr=('eol(%r)', enc), cache=True)
350 350
351 351 @predicate('copied()')
352 352 def copied(mctx, x):
353 353 """File that is recorded as being copied.
354 354 """
355 355 # i18n: "copied" is a keyword
356 356 getargs(x, 0, 0, _("copied takes no arguments"))
357 357 def copiedp(fctx):
358 358 p = fctx.parents()
359 359 return p and p[0].path() != fctx.path()
360 360 return mctx.fpredicate(copiedp, predrepr='copied', cache=True)
361 361
362 @predicate('revs(revs, pattern)')
362 @predicate('revs(revs, pattern)', weight=10)
363 363 def revs(mctx, x):
364 364 """Evaluate set in the specified revisions. If the revset match multiple
365 365 revs, this will return file matching pattern in any of the revision.
366 366 """
367 367 # i18n: "revs" is a keyword
368 368 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
369 369 # i18n: "revs" is a keyword
370 370 revspec = getstring(r, _("first argument to revs must be a revision"))
371 371 repo = mctx.ctx.repo()
372 372 revs = scmutil.revrange(repo, [revspec])
373 373
374 374 matchers = []
375 375 for r in revs:
376 376 ctx = repo[r]
377 377 matchers.append(getmatch(mctx.switch(ctx, _buildstatus(ctx, x)), x))
378 378 if not matchers:
379 379 return mctx.never()
380 380 if len(matchers) == 1:
381 381 return matchers[0]
382 382 return matchmod.unionmatcher(matchers)
383 383
384 @predicate('status(base, rev, pattern)')
384 @predicate('status(base, rev, pattern)', weight=10)
385 385 def status(mctx, x):
386 386 """Evaluate predicate using status change between ``base`` and
387 387 ``rev``. Examples:
388 388
389 389 - ``status(3, 7, added())`` - matches files added from "3" to "7"
390 390 """
391 391 repo = mctx.ctx.repo()
392 392 # i18n: "status" is a keyword
393 393 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
394 394 # i18n: "status" is a keyword
395 395 baseerr = _("first argument to status must be a revision")
396 396 baserevspec = getstring(b, baseerr)
397 397 if not baserevspec:
398 398 raise error.ParseError(baseerr)
399 399 reverr = _("second argument to status must be a revision")
400 400 revspec = getstring(r, reverr)
401 401 if not revspec:
402 402 raise error.ParseError(reverr)
403 403 basectx, ctx = scmutil.revpair(repo, [baserevspec, revspec])
404 404 return getmatch(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
405 405
406 406 @predicate('subrepo([pattern])')
407 407 def subrepo(mctx, x):
408 408 """Subrepositories whose paths match the given pattern.
409 409 """
410 410 # i18n: "subrepo" is a keyword
411 411 getargs(x, 0, 1, _("subrepo takes at most one argument"))
412 412 ctx = mctx.ctx
413 413 sstate = ctx.substate
414 414 if x:
415 415 pat = getpattern(x, matchmod.allpatternkinds,
416 416 # i18n: "subrepo" is a keyword
417 417 _("subrepo requires a pattern or no arguments"))
418 418 fast = not matchmod.patkind(pat)
419 419 if fast:
420 420 def m(s):
421 421 return (s == pat)
422 422 else:
423 423 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
424 424 return mctx.predicate(lambda f: f in sstate and m(f),
425 425 predrepr=('subrepo(%r)', pat))
426 426 else:
427 427 return mctx.predicate(sstate.__contains__, predrepr='subrepo')
428 428
429 429 methods = {
430 430 'string': stringmatch,
431 431 'symbol': stringmatch,
432 432 'kindpat': kindpatmatch,
433 433 'and': andmatch,
434 434 'or': ormatch,
435 435 'minus': minusmatch,
436 436 'list': listmatch,
437 437 'not': notmatch,
438 438 'func': func,
439 439 }
440 440
441 441 class matchctx(object):
442 442 def __init__(self, ctx, status=None, badfn=None):
443 443 self.ctx = ctx
444 444 self._status = status
445 445 self._badfn = badfn
446 446
447 447 def status(self):
448 448 return self._status
449 449
450 450 def matcher(self, patterns):
451 451 return self.ctx.match(patterns, badfn=self._badfn)
452 452
453 453 def predicate(self, predfn, predrepr=None, cache=False):
454 454 """Create a matcher to select files by predfn(filename)"""
455 455 if cache:
456 456 predfn = util.cachefunc(predfn)
457 457 repo = self.ctx.repo()
458 458 return matchmod.predicatematcher(repo.root, repo.getcwd(), predfn,
459 459 predrepr=predrepr, badfn=self._badfn)
460 460
461 461 def fpredicate(self, predfn, predrepr=None, cache=False):
462 462 """Create a matcher to select files by predfn(fctx) at the current
463 463 revision
464 464
465 465 Missing files are ignored.
466 466 """
467 467 ctx = self.ctx
468 468 if ctx.rev() is None:
469 469 def fctxpredfn(f):
470 470 try:
471 471 fctx = ctx[f]
472 472 except error.LookupError:
473 473 return False
474 474 try:
475 475 fctx.audit()
476 476 except error.Abort:
477 477 return False
478 478 try:
479 479 return predfn(fctx)
480 480 except (IOError, OSError) as e:
481 481 # open()-ing a directory fails with EACCES on Windows
482 482 if e.errno in (errno.ENOENT, errno.EACCES, errno.ENOTDIR,
483 483 errno.EISDIR):
484 484 return False
485 485 raise
486 486 else:
487 487 def fctxpredfn(f):
488 488 try:
489 489 fctx = ctx[f]
490 490 except error.LookupError:
491 491 return False
492 492 return predfn(fctx)
493 493 return self.predicate(fctxpredfn, predrepr=predrepr, cache=cache)
494 494
495 495 def never(self):
496 496 """Create a matcher to select nothing"""
497 497 repo = self.ctx.repo()
498 498 return matchmod.nevermatcher(repo.root, repo.getcwd(),
499 499 badfn=self._badfn)
500 500
501 501 def switch(self, ctx, status=None):
502 502 return matchctx(ctx, status, self._badfn)
503 503
504 504 # filesets using matchctx.switch()
505 505 _switchcallers = [
506 506 'revs',
507 507 'status',
508 508 ]
509 509
510 510 def _intree(funcs, tree):
511 511 if isinstance(tree, tuple):
512 512 if tree[0] == 'func' and tree[1][0] == 'symbol':
513 513 if tree[1][1] in funcs:
514 514 return True
515 515 if tree[1][1] in _switchcallers:
516 516 # arguments won't be evaluated in the current context
517 517 return False
518 518 for s in tree[1:]:
519 519 if _intree(funcs, s):
520 520 return True
521 521 return False
522 522
523 523 def match(ctx, expr, badfn=None):
524 524 """Create a matcher for a single fileset expression"""
525 525 tree = filesetlang.parse(expr)
526 526 tree = filesetlang.analyze(tree)
527 527 tree = filesetlang.optimize(tree)
528 528 mctx = matchctx(ctx, _buildstatus(ctx, tree), badfn=badfn)
529 529 return getmatch(mctx, tree)
530 530
531 531 def _buildstatus(ctx, tree, basectx=None):
532 532 # do we need status info?
533 533
534 534 if _intree(_statuscallers, tree):
535 535 unknown = _intree(['unknown'], tree)
536 536 ignored = _intree(['ignored'], tree)
537 537
538 538 if basectx is None:
539 539 basectx = ctx.p1()
540 540 return basectx.status(ctx, listunknown=unknown, listignored=ignored,
541 541 listclean=True)
542 542 else:
543 543 return None
544 544
545 545 def loadpredicate(ui, extname, registrarobj):
546 546 """Load fileset predicates from specified registrarobj
547 547 """
548 548 for name, func in registrarobj._table.iteritems():
549 549 symbols[name] = func
550 550 if func._callstatus:
551 551 _statuscallers.add(name)
552 552
553 553 # load built-in predicates explicitly to setup _statuscallers
554 554 loadpredicate(None, None, predicate)
555 555
556 556 # tell hggettext to extract docstrings from these functions:
557 557 i18nfunctions = symbols.values()
@@ -1,443 +1,452 b''
1 1 # registrar.py - utilities to register function for specific purpose
2 2 #
3 3 # Copyright FUJIWARA Katsunori <foozy@lares.dti.ne.jp> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 from . import (
11 11 configitems,
12 12 error,
13 13 pycompat,
14 14 util,
15 15 )
16 16
17 17 # unlike the other registered items, config options are neither functions or
18 18 # classes. Registering the option is just small function call.
19 19 #
20 20 # We still add the official API to the registrar module for consistency with
21 21 # the other items extensions want might to register.
22 22 configitem = configitems.getitemregister
23 23
24 24 class _funcregistrarbase(object):
25 25 """Base of decorator to register a function for specific purpose
26 26
27 27 This decorator stores decorated functions into own dict 'table'.
28 28
29 29 The least derived class can be defined by overriding 'formatdoc',
30 30 for example::
31 31
32 32 class keyword(_funcregistrarbase):
33 33 _docformat = ":%s: %s"
34 34
35 35 This should be used as below:
36 36
37 37 keyword = registrar.keyword()
38 38
39 39 @keyword('bar')
40 40 def barfunc(*args, **kwargs):
41 41 '''Explanation of bar keyword ....
42 42 '''
43 43 pass
44 44
45 45 In this case:
46 46
47 47 - 'barfunc' is stored as 'bar' in '_table' of an instance 'keyword' above
48 48 - 'barfunc.__doc__' becomes ":bar: Explanation of bar keyword"
49 49 """
50 50 def __init__(self, table=None):
51 51 if table is None:
52 52 self._table = {}
53 53 else:
54 54 self._table = table
55 55
56 56 def __call__(self, decl, *args, **kwargs):
57 57 return lambda func: self._doregister(func, decl, *args, **kwargs)
58 58
59 59 def _doregister(self, func, decl, *args, **kwargs):
60 60 name = self._getname(decl)
61 61
62 62 if name in self._table:
63 63 msg = 'duplicate registration for name: "%s"' % name
64 64 raise error.ProgrammingError(msg)
65 65
66 66 if func.__doc__ and not util.safehasattr(func, '_origdoc'):
67 67 doc = pycompat.sysbytes(func.__doc__).strip()
68 68 func._origdoc = doc
69 69 func.__doc__ = pycompat.sysstr(self._formatdoc(decl, doc))
70 70
71 71 self._table[name] = func
72 72 self._extrasetup(name, func, *args, **kwargs)
73 73
74 74 return func
75 75
76 76 def _parsefuncdecl(self, decl):
77 77 """Parse function declaration and return the name of function in it
78 78 """
79 79 i = decl.find('(')
80 80 if i >= 0:
81 81 return decl[:i]
82 82 else:
83 83 return decl
84 84
85 85 def _getname(self, decl):
86 86 """Return the name of the registered function from decl
87 87
88 88 Derived class should override this, if it allows more
89 89 descriptive 'decl' string than just a name.
90 90 """
91 91 return decl
92 92
93 93 _docformat = None
94 94
95 95 def _formatdoc(self, decl, doc):
96 96 """Return formatted document of the registered function for help
97 97
98 98 'doc' is '__doc__.strip()' of the registered function.
99 99 """
100 100 return self._docformat % (decl, doc)
101 101
102 102 def _extrasetup(self, name, func):
103 103 """Execute exra setup for registered function, if needed
104 104 """
105 105
106 106 class command(_funcregistrarbase):
107 107 """Decorator to register a command function to table
108 108
109 109 This class receives a command table as its argument. The table should
110 110 be a dict.
111 111
112 112 The created object can be used as a decorator for adding commands to
113 113 that command table. This accepts multiple arguments to define a command.
114 114
115 115 The first argument is the command name (as bytes).
116 116
117 117 The `options` keyword argument is an iterable of tuples defining command
118 118 arguments. See ``mercurial.fancyopts.fancyopts()`` for the format of each
119 119 tuple.
120 120
121 121 The `synopsis` argument defines a short, one line summary of how to use the
122 122 command. This shows up in the help output.
123 123
124 124 There are three arguments that control what repository (if any) is found
125 125 and passed to the decorated function: `norepo`, `optionalrepo`, and
126 126 `inferrepo`.
127 127
128 128 The `norepo` argument defines whether the command does not require a
129 129 local repository. Most commands operate against a repository, thus the
130 130 default is False. When True, no repository will be passed.
131 131
132 132 The `optionalrepo` argument defines whether the command optionally requires
133 133 a local repository. If no repository can be found, None will be passed
134 134 to the decorated function.
135 135
136 136 The `inferrepo` argument defines whether to try to find a repository from
137 137 the command line arguments. If True, arguments will be examined for
138 138 potential repository locations. See ``findrepo()``. If a repository is
139 139 found, it will be used and passed to the decorated function.
140 140
141 141 The `intents` argument defines a set of intended actions or capabilities
142 142 the command is taking. These intents can be used to affect the construction
143 143 of the repository object passed to the command. For example, commands
144 144 declaring that they are read-only could receive a repository that doesn't
145 145 have any methods allowing repository mutation. Other intents could be used
146 146 to prevent the command from running if the requested intent could not be
147 147 fulfilled.
148 148
149 149 The following intents are defined:
150 150
151 151 readonly
152 152 The command is read-only
153 153
154 154 The signature of the decorated function looks like this:
155 155 def cmd(ui[, repo] [, <args>] [, <options>])
156 156
157 157 `repo` is required if `norepo` is False.
158 158 `<args>` are positional args (or `*args`) arguments, of non-option
159 159 arguments from the command line.
160 160 `<options>` are keyword arguments (or `**options`) of option arguments
161 161 from the command line.
162 162
163 163 See the WritingExtensions and MercurialApi documentation for more exhaustive
164 164 descriptions and examples.
165 165 """
166 166
167 167 def _doregister(self, func, name, options=(), synopsis=None,
168 168 norepo=False, optionalrepo=False, inferrepo=False,
169 169 intents=None):
170 170
171 171 func.norepo = norepo
172 172 func.optionalrepo = optionalrepo
173 173 func.inferrepo = inferrepo
174 174 func.intents = intents or set()
175 175 if synopsis:
176 176 self._table[name] = func, list(options), synopsis
177 177 else:
178 178 self._table[name] = func, list(options)
179 179 return func
180 180
181 181 INTENT_READONLY = b'readonly'
182 182
183 183 class revsetpredicate(_funcregistrarbase):
184 184 """Decorator to register revset predicate
185 185
186 186 Usage::
187 187
188 188 revsetpredicate = registrar.revsetpredicate()
189 189
190 190 @revsetpredicate('mypredicate(arg1, arg2[, arg3])')
191 191 def mypredicatefunc(repo, subset, x):
192 192 '''Explanation of this revset predicate ....
193 193 '''
194 194 pass
195 195
196 196 The first string argument is used also in online help.
197 197
198 198 Optional argument 'safe' indicates whether a predicate is safe for
199 199 DoS attack (False by default).
200 200
201 201 Optional argument 'takeorder' indicates whether a predicate function
202 202 takes ordering policy as the last argument.
203 203
204 204 Optional argument 'weight' indicates the estimated run-time cost, useful
205 205 for static optimization, default is 1. Higher weight means more expensive.
206 206 Usually, revsets that are fast and return only one revision has a weight of
207 207 0.5 (ex. a symbol); revsets with O(changelog) complexity and read only the
208 208 changelog have weight 10 (ex. author); revsets reading manifest deltas have
209 209 weight 30 (ex. adds); revset reading manifest contents have weight 100
210 210 (ex. contains). Note: those values are flexible. If the revset has a
211 211 same big-O time complexity as 'contains', but with a smaller constant, it
212 212 might have a weight of 90.
213 213
214 214 'revsetpredicate' instance in example above can be used to
215 215 decorate multiple functions.
216 216
217 217 Decorated functions are registered automatically at loading
218 218 extension, if an instance named as 'revsetpredicate' is used for
219 219 decorating in extension.
220 220
221 221 Otherwise, explicit 'revset.loadpredicate()' is needed.
222 222 """
223 223 _getname = _funcregistrarbase._parsefuncdecl
224 224 _docformat = "``%s``\n %s"
225 225
226 226 def _extrasetup(self, name, func, safe=False, takeorder=False, weight=1):
227 227 func._safe = safe
228 228 func._takeorder = takeorder
229 229 func._weight = weight
230 230
231 231 class filesetpredicate(_funcregistrarbase):
232 232 """Decorator to register fileset predicate
233 233
234 234 Usage::
235 235
236 236 filesetpredicate = registrar.filesetpredicate()
237 237
238 238 @filesetpredicate('mypredicate()')
239 239 def mypredicatefunc(mctx, x):
240 240 '''Explanation of this fileset predicate ....
241 241 '''
242 242 pass
243 243
244 244 The first string argument is used also in online help.
245 245
246 246 Optional argument 'callstatus' indicates whether a predicate
247 247 implies 'matchctx.status()' at runtime or not (False, by
248 248 default).
249 249
250 250 Optional argument 'weight' indicates the estimated run-time cost, useful
251 251 for static optimization, default is 1. Higher weight means more expensive.
252 252
253 ====== =============================================================
254 Weight Description and examples
255 ====== =============================================================
256 0.5 basic match patterns (e.g. a symbol)
257 10 computing status (e.g. added()) or accessing a few files
258 30 reading file content for each (e.g. grep())
259 50 scanning working directory (ignored())
260 ====== =============================================================
261
253 262 'filesetpredicate' instance in example above can be used to
254 263 decorate multiple functions.
255 264
256 265 Decorated functions are registered automatically at loading
257 266 extension, if an instance named as 'filesetpredicate' is used for
258 267 decorating in extension.
259 268
260 269 Otherwise, explicit 'fileset.loadpredicate()' is needed.
261 270 """
262 271 _getname = _funcregistrarbase._parsefuncdecl
263 272 _docformat = "``%s``\n %s"
264 273
265 274 def _extrasetup(self, name, func, callstatus=False, weight=1):
266 275 func._callstatus = callstatus
267 276 func._weight = weight
268 277
269 278 class _templateregistrarbase(_funcregistrarbase):
270 279 """Base of decorator to register functions as template specific one
271 280 """
272 281 _docformat = ":%s: %s"
273 282
274 283 class templatekeyword(_templateregistrarbase):
275 284 """Decorator to register template keyword
276 285
277 286 Usage::
278 287
279 288 templatekeyword = registrar.templatekeyword()
280 289
281 290 # new API (since Mercurial 4.6)
282 291 @templatekeyword('mykeyword', requires={'repo', 'ctx'})
283 292 def mykeywordfunc(context, mapping):
284 293 '''Explanation of this template keyword ....
285 294 '''
286 295 pass
287 296
288 297 # old API
289 298 @templatekeyword('mykeyword')
290 299 def mykeywordfunc(repo, ctx, templ, cache, revcache, **args):
291 300 '''Explanation of this template keyword ....
292 301 '''
293 302 pass
294 303
295 304 The first string argument is used also in online help.
296 305
297 306 Optional argument 'requires' should be a collection of resource names
298 307 which the template keyword depends on. This also serves as a flag to
299 308 switch to the new API. If 'requires' is unspecified, all template
300 309 keywords and resources are expanded to the function arguments.
301 310
302 311 'templatekeyword' instance in example above can be used to
303 312 decorate multiple functions.
304 313
305 314 Decorated functions are registered automatically at loading
306 315 extension, if an instance named as 'templatekeyword' is used for
307 316 decorating in extension.
308 317
309 318 Otherwise, explicit 'templatekw.loadkeyword()' is needed.
310 319 """
311 320
312 321 def _extrasetup(self, name, func, requires=None):
313 322 func._requires = requires
314 323
315 324 class templatefilter(_templateregistrarbase):
316 325 """Decorator to register template filer
317 326
318 327 Usage::
319 328
320 329 templatefilter = registrar.templatefilter()
321 330
322 331 @templatefilter('myfilter', intype=bytes)
323 332 def myfilterfunc(text):
324 333 '''Explanation of this template filter ....
325 334 '''
326 335 pass
327 336
328 337 The first string argument is used also in online help.
329 338
330 339 Optional argument 'intype' defines the type of the input argument,
331 340 which should be (bytes, int, templateutil.date, or None for any.)
332 341
333 342 'templatefilter' instance in example above can be used to
334 343 decorate multiple functions.
335 344
336 345 Decorated functions are registered automatically at loading
337 346 extension, if an instance named as 'templatefilter' is used for
338 347 decorating in extension.
339 348
340 349 Otherwise, explicit 'templatefilters.loadkeyword()' is needed.
341 350 """
342 351
343 352 def _extrasetup(self, name, func, intype=None):
344 353 func._intype = intype
345 354
346 355 class templatefunc(_templateregistrarbase):
347 356 """Decorator to register template function
348 357
349 358 Usage::
350 359
351 360 templatefunc = registrar.templatefunc()
352 361
353 362 @templatefunc('myfunc(arg1, arg2[, arg3])', argspec='arg1 arg2 arg3',
354 363 requires={'ctx'})
355 364 def myfuncfunc(context, mapping, args):
356 365 '''Explanation of this template function ....
357 366 '''
358 367 pass
359 368
360 369 The first string argument is used also in online help.
361 370
362 371 If optional 'argspec' is defined, the function will receive 'args' as
363 372 a dict of named arguments. Otherwise 'args' is a list of positional
364 373 arguments.
365 374
366 375 Optional argument 'requires' should be a collection of resource names
367 376 which the template function depends on.
368 377
369 378 'templatefunc' instance in example above can be used to
370 379 decorate multiple functions.
371 380
372 381 Decorated functions are registered automatically at loading
373 382 extension, if an instance named as 'templatefunc' is used for
374 383 decorating in extension.
375 384
376 385 Otherwise, explicit 'templatefuncs.loadfunction()' is needed.
377 386 """
378 387 _getname = _funcregistrarbase._parsefuncdecl
379 388
380 389 def _extrasetup(self, name, func, argspec=None, requires=()):
381 390 func._argspec = argspec
382 391 func._requires = requires
383 392
384 393 class internalmerge(_funcregistrarbase):
385 394 """Decorator to register in-process merge tool
386 395
387 396 Usage::
388 397
389 398 internalmerge = registrar.internalmerge()
390 399
391 400 @internalmerge('mymerge', internalmerge.mergeonly,
392 401 onfailure=None, precheck=None):
393 402 def mymergefunc(repo, mynode, orig, fcd, fco, fca,
394 403 toolconf, files, labels=None):
395 404 '''Explanation of this internal merge tool ....
396 405 '''
397 406 return 1, False # means "conflicted", "no deletion needed"
398 407
399 408 The first string argument is used to compose actual merge tool name,
400 409 ":name" and "internal:name" (the latter is historical one).
401 410
402 411 The second argument is one of merge types below:
403 412
404 413 ========== ======== ======== =========
405 414 merge type precheck premerge fullmerge
406 415 ========== ======== ======== =========
407 416 nomerge x x x
408 417 mergeonly o x o
409 418 fullmerge o o o
410 419 ========== ======== ======== =========
411 420
412 421 Optional argument 'onfailure' is the format of warning message
413 422 to be used at failure of merging (target filename is specified
414 423 at formatting). Or, None or so, if warning message should be
415 424 suppressed.
416 425
417 426 Optional argument 'precheck' is the function to be used
418 427 before actual invocation of internal merge tool itself.
419 428 It takes as same arguments as internal merge tool does, other than
420 429 'files' and 'labels'. If it returns false value, merging is aborted
421 430 immediately (and file is marked as "unresolved").
422 431
423 432 'internalmerge' instance in example above can be used to
424 433 decorate multiple functions.
425 434
426 435 Decorated functions are registered automatically at loading
427 436 extension, if an instance named as 'internalmerge' is used for
428 437 decorating in extension.
429 438
430 439 Otherwise, explicit 'filemerge.loadinternalmerge()' is needed.
431 440 """
432 441 _docformat = "``:%s``\n %s"
433 442
434 443 # merge type definitions:
435 444 nomerge = None
436 445 mergeonly = 'mergeonly' # just the full merge, no premerge
437 446 fullmerge = 'fullmerge' # both premerge and merge
438 447
439 448 def _extrasetup(self, name, func, mergetype,
440 449 onfailure=None, precheck=None):
441 450 func.mergetype = mergetype
442 451 func.onfailure = onfailure
443 452 func.precheck = precheck
General Comments 0
You need to be logged in to leave comments. Login now