Mercurial > public > mercurial-scm > hg-stable
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, |