comparison mercurial/fileset.py @ 27460:11286ac374f3

fileset: use decorator to mark a function as fileset predicate Using decorator can localize changes for adding (or removing) a fileset predicate function in source code. It is also useful to pick predicates up for specific purpose. For example, subsequent patches marks predicates as "call status" or "use existing" via decorator. To avoid (1) redundancy between "predicate name" and (the beginning of) help document, and (2) accidental typo of help document, this patch also makes decorator put predicate declration into the beginning of help.
author FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
date Mon, 21 Dec 2015 22:31:16 +0900
parents 2f15253e415f
children afa76585c955
comparison
equal deleted inserted replaced
27459:2f15253e415f 27460:11286ac374f3
128 return [f for f in xl if f not in yl] 128 return [f for f in xl if f not in yl]
129 129
130 def listset(mctx, a, b): 130 def listset(mctx, a, b):
131 raise error.ParseError(_("can't use a list in this context")) 131 raise error.ParseError(_("can't use a list in this context"))
132 132
133 # symbols are callable like:
134 # fun(mctx, x)
135 # with:
136 # mctx - current matchctx instance
137 # x - argument in tree form
138 symbols = {}
139
140 def predicate(decl):
141 """Return a decorator for fileset predicate function
142
143 'decl' argument is the declaration (including argument list like
144 'adds(pattern)') or the name (for internal use only) of predicate.
145 """
146 def decorator(func):
147 i = decl.find('(')
148 if i > 0:
149 name = decl[:i]
150 else:
151 name = decl
152 symbols[name] = func
153 if func.__doc__:
154 func.__doc__ = "``%s``\n %s" % (decl, func.__doc__.strip())
155 return func
156 return decorator
157
158 @predicate('modified()')
133 def modified(mctx, x): 159 def modified(mctx, x):
134 """``modified()`` 160 """File that is modified according to :hg:`status`.
135 File that is modified according to :hg:`status`.
136 """ 161 """
137 # i18n: "modified" is a keyword 162 # i18n: "modified" is a keyword
138 getargs(x, 0, 0, _("modified takes no arguments")) 163 getargs(x, 0, 0, _("modified takes no arguments"))
139 s = mctx.status().modified 164 s = mctx.status().modified
140 return [f for f in mctx.subset if f in s] 165 return [f for f in mctx.subset if f in s]
141 166
167 @predicate('added()')
142 def added(mctx, x): 168 def added(mctx, x):
143 """``added()`` 169 """File that is added according to :hg:`status`.
144 File that is added according to :hg:`status`.
145 """ 170 """
146 # i18n: "added" is a keyword 171 # i18n: "added" is a keyword
147 getargs(x, 0, 0, _("added takes no arguments")) 172 getargs(x, 0, 0, _("added takes no arguments"))
148 s = mctx.status().added 173 s = mctx.status().added
149 return [f for f in mctx.subset if f in s] 174 return [f for f in mctx.subset if f in s]
150 175
176 @predicate('removed()')
151 def removed(mctx, x): 177 def removed(mctx, x):
152 """``removed()`` 178 """File that is removed according to :hg:`status`.
153 File that is removed according to :hg:`status`.
154 """ 179 """
155 # i18n: "removed" is a keyword 180 # i18n: "removed" is a keyword
156 getargs(x, 0, 0, _("removed takes no arguments")) 181 getargs(x, 0, 0, _("removed takes no arguments"))
157 s = mctx.status().removed 182 s = mctx.status().removed
158 return [f for f in mctx.subset if f in s] 183 return [f for f in mctx.subset if f in s]
159 184
185 @predicate('deleted()')
160 def deleted(mctx, x): 186 def deleted(mctx, x):
161 """``deleted()`` 187 """Alias for ``missing()``.
162 Alias for ``missing()``.
163 """ 188 """
164 # i18n: "deleted" is a keyword 189 # i18n: "deleted" is a keyword
165 getargs(x, 0, 0, _("deleted takes no arguments")) 190 getargs(x, 0, 0, _("deleted takes no arguments"))
166 s = mctx.status().deleted 191 s = mctx.status().deleted
167 return [f for f in mctx.subset if f in s] 192 return [f for f in mctx.subset if f in s]
168 193
194 @predicate('missing()')
169 def missing(mctx, x): 195 def missing(mctx, x):
170 """``missing()`` 196 """File that is missing according to :hg:`status`.
171 File that is missing according to :hg:`status`.
172 """ 197 """
173 # i18n: "missing" is a keyword 198 # i18n: "missing" is a keyword
174 getargs(x, 0, 0, _("missing takes no arguments")) 199 getargs(x, 0, 0, _("missing takes no arguments"))
175 s = mctx.status().deleted 200 s = mctx.status().deleted
176 return [f for f in mctx.subset if f in s] 201 return [f for f in mctx.subset if f in s]
177 202
203 @predicate('unknown()')
178 def unknown(mctx, x): 204 def unknown(mctx, x):
179 """``unknown()`` 205 """File that is unknown according to :hg:`status`. These files will only be
180 File that is unknown according to :hg:`status`. These files will only be
181 considered if this predicate is used. 206 considered if this predicate is used.
182 """ 207 """
183 # i18n: "unknown" is a keyword 208 # i18n: "unknown" is a keyword
184 getargs(x, 0, 0, _("unknown takes no arguments")) 209 getargs(x, 0, 0, _("unknown takes no arguments"))
185 s = mctx.status().unknown 210 s = mctx.status().unknown
186 return [f for f in mctx.subset if f in s] 211 return [f for f in mctx.subset if f in s]
187 212
213 @predicate('ignored()')
188 def ignored(mctx, x): 214 def ignored(mctx, x):
189 """``ignored()`` 215 """File that is ignored according to :hg:`status`. These files will only be
190 File that is ignored according to :hg:`status`. These files will only be
191 considered if this predicate is used. 216 considered if this predicate is used.
192 """ 217 """
193 # i18n: "ignored" is a keyword 218 # i18n: "ignored" is a keyword
194 getargs(x, 0, 0, _("ignored takes no arguments")) 219 getargs(x, 0, 0, _("ignored takes no arguments"))
195 s = mctx.status().ignored 220 s = mctx.status().ignored
196 return [f for f in mctx.subset if f in s] 221 return [f for f in mctx.subset if f in s]
197 222
223 @predicate('clean()')
198 def clean(mctx, x): 224 def clean(mctx, x):
199 """``clean()`` 225 """File that is clean according to :hg:`status`.
200 File that is clean according to :hg:`status`.
201 """ 226 """
202 # i18n: "clean" is a keyword 227 # i18n: "clean" is a keyword
203 getargs(x, 0, 0, _("clean takes no arguments")) 228 getargs(x, 0, 0, _("clean takes no arguments"))
204 s = mctx.status().clean 229 s = mctx.status().clean
205 return [f for f in mctx.subset if f in s] 230 return [f for f in mctx.subset if f in s]
224 l = getlist(x) 249 l = getlist(x)
225 if len(l) < min or len(l) > max: 250 if len(l) < min or len(l) > max:
226 raise error.ParseError(err) 251 raise error.ParseError(err)
227 return l 252 return l
228 253
254 @predicate('binary()')
229 def binary(mctx, x): 255 def binary(mctx, x):
230 """``binary()`` 256 """File that appears to be binary (contains NUL bytes).
231 File that appears to be binary (contains NUL bytes).
232 """ 257 """
233 # i18n: "binary" is a keyword 258 # i18n: "binary" is a keyword
234 getargs(x, 0, 0, _("binary takes no arguments")) 259 getargs(x, 0, 0, _("binary takes no arguments"))
235 return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())] 260 return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())]
236 261
262 @predicate('exec()')
237 def exec_(mctx, x): 263 def exec_(mctx, x):
238 """``exec()`` 264 """File that is marked as executable.
239 File that is marked as executable.
240 """ 265 """
241 # i18n: "exec" is a keyword 266 # i18n: "exec" is a keyword
242 getargs(x, 0, 0, _("exec takes no arguments")) 267 getargs(x, 0, 0, _("exec takes no arguments"))
243 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x'] 268 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
244 269
270 @predicate('symlink()')
245 def symlink(mctx, x): 271 def symlink(mctx, x):
246 """``symlink()`` 272 """File that is marked as a symlink.
247 File that is marked as a symlink.
248 """ 273 """
249 # i18n: "symlink" is a keyword 274 # i18n: "symlink" is a keyword
250 getargs(x, 0, 0, _("symlink takes no arguments")) 275 getargs(x, 0, 0, _("symlink takes no arguments"))
251 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l'] 276 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
252 277
278 @predicate('resolved()')
253 def resolved(mctx, x): 279 def resolved(mctx, x):
254 """``resolved()`` 280 """File that is marked resolved according to :hg:`resolve -l`.
255 File that is marked resolved according to :hg:`resolve -l`.
256 """ 281 """
257 # i18n: "resolved" is a keyword 282 # i18n: "resolved" is a keyword
258 getargs(x, 0, 0, _("resolved takes no arguments")) 283 getargs(x, 0, 0, _("resolved takes no arguments"))
259 if mctx.ctx.rev() is not None: 284 if mctx.ctx.rev() is not None:
260 return [] 285 return []
261 ms = merge.mergestate.read(mctx.ctx.repo()) 286 ms = merge.mergestate.read(mctx.ctx.repo())
262 return [f for f in mctx.subset if f in ms and ms[f] == 'r'] 287 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
263 288
289 @predicate('unresolved()')
264 def unresolved(mctx, x): 290 def unresolved(mctx, x):
265 """``unresolved()`` 291 """File that is marked unresolved according to :hg:`resolve -l`.
266 File that is marked unresolved according to :hg:`resolve -l`.
267 """ 292 """
268 # i18n: "unresolved" is a keyword 293 # i18n: "unresolved" is a keyword
269 getargs(x, 0, 0, _("unresolved takes no arguments")) 294 getargs(x, 0, 0, _("unresolved takes no arguments"))
270 if mctx.ctx.rev() is not None: 295 if mctx.ctx.rev() is not None:
271 return [] 296 return []
272 ms = merge.mergestate.read(mctx.ctx.repo()) 297 ms = merge.mergestate.read(mctx.ctx.repo())
273 return [f for f in mctx.subset if f in ms and ms[f] == 'u'] 298 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
274 299
300 @predicate('hgignore()')
275 def hgignore(mctx, x): 301 def hgignore(mctx, x):
276 """``hgignore()`` 302 """File that matches the active .hgignore pattern.
277 File that matches the active .hgignore pattern.
278 """ 303 """
279 # i18n: "hgignore" is a keyword 304 # i18n: "hgignore" is a keyword
280 getargs(x, 0, 0, _("hgignore takes no arguments")) 305 getargs(x, 0, 0, _("hgignore takes no arguments"))
281 ignore = mctx.ctx.repo().dirstate._ignore 306 ignore = mctx.ctx.repo().dirstate._ignore
282 return [f for f in mctx.subset if ignore(f)] 307 return [f for f in mctx.subset if ignore(f)]
283 308
309 @predicate('portable()')
284 def portable(mctx, x): 310 def portable(mctx, x):
285 """``portable()`` 311 """File that has a portable name. (This doesn't include filenames with case
286 File that has a portable name. (This doesn't include filenames with case
287 collisions.) 312 collisions.)
288 """ 313 """
289 # i18n: "portable" is a keyword 314 # i18n: "portable" is a keyword
290 getargs(x, 0, 0, _("portable takes no arguments")) 315 getargs(x, 0, 0, _("portable takes no arguments"))
291 checkwinfilename = util.checkwinfilename 316 checkwinfilename = util.checkwinfilename
292 return [f for f in mctx.subset if checkwinfilename(f) is None] 317 return [f for f in mctx.subset if checkwinfilename(f) is None]
293 318
319 @predicate('grep(regex)')
294 def grep(mctx, x): 320 def grep(mctx, x):
295 """``grep(regex)`` 321 """File contains the given regular expression.
296 File contains the given regular expression.
297 """ 322 """
298 try: 323 try:
299 # i18n: "grep" is a keyword 324 # i18n: "grep" is a keyword
300 r = re.compile(getstring(x, _("grep requires a pattern"))) 325 r = re.compile(getstring(x, _("grep requires a pattern")))
301 except re.error as e: 326 except re.error as e:
316 # no extension, this is a precise value 341 # no extension, this is a precise value
317 return int(s) 342 return int(s)
318 except ValueError: 343 except ValueError:
319 raise error.ParseError(_("couldn't parse size: %s") % s) 344 raise error.ParseError(_("couldn't parse size: %s") % s)
320 345
346 @predicate('size(expression)')
321 def size(mctx, x): 347 def size(mctx, x):
322 """``size(expression)`` 348 """File size matches the given expression. Examples:
323 File size matches the given expression. Examples:
324 349
325 - 1k (files from 1024 to 2047 bytes) 350 - 1k (files from 1024 to 2047 bytes)
326 - < 20k (files less than 20480 bytes) 351 - < 20k (files less than 20480 bytes)
327 - >= .5MB (files at least 524288 bytes) 352 - >= .5MB (files at least 524288 bytes)
328 - 4k - 1MB (files from 4096 bytes to 1048576 bytes) 353 - 4k - 1MB (files from 4096 bytes to 1048576 bytes)
354 else: 379 else:
355 raise error.ParseError(_("couldn't parse size: %s") % expr) 380 raise error.ParseError(_("couldn't parse size: %s") % expr)
356 381
357 return [f for f in mctx.existing() if m(mctx.ctx[f].size())] 382 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
358 383
384 @predicate('encoding(name)')
359 def encoding(mctx, x): 385 def encoding(mctx, x):
360 """``encoding(name)`` 386 """File can be successfully decoded with the given character
361 File can be successfully decoded with the given character
362 encoding. May not be useful for encodings other than ASCII and 387 encoding. May not be useful for encodings other than ASCII and
363 UTF-8. 388 UTF-8.
364 """ 389 """
365 390
366 # i18n: "encoding" is a keyword 391 # i18n: "encoding" is a keyword
377 continue 402 continue
378 s.append(f) 403 s.append(f)
379 404
380 return s 405 return s
381 406
407 @predicate('eol(style)')
382 def eol(mctx, x): 408 def eol(mctx, x):
383 """``eol(style)`` 409 """File contains newlines of the given style (dos, unix, mac). Binary
384 File contains newlines of the given style (dos, unix, mac). Binary
385 files are excluded, files with mixed line endings match multiple 410 files are excluded, files with mixed line endings match multiple
386 styles. 411 styles.
387 """ 412 """
388 413
389 # i18n: "encoding" is a keyword 414 # i18n: "encoding" is a keyword
400 s.append(f) 425 s.append(f)
401 elif enc == 'mac' and re.search('\r(?!\n)', d): 426 elif enc == 'mac' and re.search('\r(?!\n)', d):
402 s.append(f) 427 s.append(f)
403 return s 428 return s
404 429
430 @predicate('copied()')
405 def copied(mctx, x): 431 def copied(mctx, x):
406 """``copied()`` 432 """File that is recorded as being copied.
407 File that is recorded as being copied.
408 """ 433 """
409 # i18n: "copied" is a keyword 434 # i18n: "copied" is a keyword
410 getargs(x, 0, 0, _("copied takes no arguments")) 435 getargs(x, 0, 0, _("copied takes no arguments"))
411 s = [] 436 s = []
412 for f in mctx.subset: 437 for f in mctx.subset:
413 p = mctx.ctx[f].parents() 438 p = mctx.ctx[f].parents()
414 if p and p[0].path() != f: 439 if p and p[0].path() != f:
415 s.append(f) 440 s.append(f)
416 return s 441 return s
417 442
443 @predicate('subrepo([pattern])')
418 def subrepo(mctx, x): 444 def subrepo(mctx, x):
419 """``subrepo([pattern])`` 445 """Subrepositories whose paths match the given pattern.
420 Subrepositories whose paths match the given pattern.
421 """ 446 """
422 # i18n: "subrepo" is a keyword 447 # i18n: "subrepo" is a keyword
423 getargs(x, 0, 1, _("subrepo takes at most one argument")) 448 getargs(x, 0, 1, _("subrepo takes at most one argument"))
424 ctx = mctx.ctx 449 ctx = mctx.ctx
425 sstate = sorted(ctx.substate) 450 sstate = sorted(ctx.substate)
435 else: 460 else:
436 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx) 461 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
437 return [sub for sub in sstate if m(sub)] 462 return [sub for sub in sstate if m(sub)]
438 else: 463 else:
439 return [sub for sub in sstate] 464 return [sub for sub in sstate]
440
441 symbols = {
442 'added': added,
443 'binary': binary,
444 'clean': clean,
445 'copied': copied,
446 'deleted': deleted,
447 'encoding': encoding,
448 'eol': eol,
449 'exec': exec_,
450 'grep': grep,
451 'ignored': ignored,
452 'hgignore': hgignore,
453 'missing': missing,
454 'modified': modified,
455 'portable': portable,
456 'removed': removed,
457 'resolved': resolved,
458 'size': size,
459 'symlink': symlink,
460 'unknown': unknown,
461 'unresolved': unresolved,
462 'subrepo': subrepo,
463 }
464 465
465 methods = { 466 methods = {
466 'string': stringset, 467 'string': stringset,
467 'symbol': stringset, 468 'symbol': stringset,
468 'and': andset, 469 'and': andset,