##// END OF EJS Templates
fileset: don't suggest private or undocumented queries...
Matt Harbison -
r25633:0f44d357 default
parent child Browse files
Show More
@@ -1,524 +1,528 b''
1 # fileset.py - file set queries for mercurial
1 # fileset.py - file set queries for mercurial
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import re
8 import re
9 import parser, error, util, merge
9 import parser, error, util, merge
10 from i18n import _
10 from i18n import _
11
11
12 elements = {
12 elements = {
13 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
13 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
14 "-": (5, ("negate", 19), ("minus", 5)),
14 "-": (5, ("negate", 19), ("minus", 5)),
15 "not": (10, ("not", 10)),
15 "not": (10, ("not", 10)),
16 "!": (10, ("not", 10)),
16 "!": (10, ("not", 10)),
17 "and": (5, None, ("and", 5)),
17 "and": (5, None, ("and", 5)),
18 "&": (5, None, ("and", 5)),
18 "&": (5, None, ("and", 5)),
19 "or": (4, None, ("or", 4)),
19 "or": (4, None, ("or", 4)),
20 "|": (4, None, ("or", 4)),
20 "|": (4, None, ("or", 4)),
21 "+": (4, None, ("or", 4)),
21 "+": (4, None, ("or", 4)),
22 ",": (2, None, ("list", 2)),
22 ",": (2, None, ("list", 2)),
23 ")": (0, None, None),
23 ")": (0, None, None),
24 "symbol": (0, ("symbol",), None),
24 "symbol": (0, ("symbol",), None),
25 "string": (0, ("string",), None),
25 "string": (0, ("string",), None),
26 "end": (0, None, None),
26 "end": (0, None, None),
27 }
27 }
28
28
29 keywords = set(['and', 'or', 'not'])
29 keywords = set(['and', 'or', 'not'])
30
30
31 globchars = ".*{}[]?/\\_"
31 globchars = ".*{}[]?/\\_"
32
32
33 def tokenize(program):
33 def tokenize(program):
34 pos, l = 0, len(program)
34 pos, l = 0, len(program)
35 while pos < l:
35 while pos < l:
36 c = program[pos]
36 c = program[pos]
37 if c.isspace(): # skip inter-token whitespace
37 if c.isspace(): # skip inter-token whitespace
38 pass
38 pass
39 elif c in "(),-|&+!": # handle simple operators
39 elif c in "(),-|&+!": # handle simple operators
40 yield (c, None, pos)
40 yield (c, None, pos)
41 elif (c in '"\'' or c == 'r' and
41 elif (c in '"\'' or c == 'r' and
42 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
42 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
43 if c == 'r':
43 if c == 'r':
44 pos += 1
44 pos += 1
45 c = program[pos]
45 c = program[pos]
46 decode = lambda x: x
46 decode = lambda x: x
47 else:
47 else:
48 decode = lambda x: x.decode('string-escape')
48 decode = lambda x: x.decode('string-escape')
49 pos += 1
49 pos += 1
50 s = pos
50 s = pos
51 while pos < l: # find closing quote
51 while pos < l: # find closing quote
52 d = program[pos]
52 d = program[pos]
53 if d == '\\': # skip over escaped characters
53 if d == '\\': # skip over escaped characters
54 pos += 2
54 pos += 2
55 continue
55 continue
56 if d == c:
56 if d == c:
57 yield ('string', decode(program[s:pos]), s)
57 yield ('string', decode(program[s:pos]), s)
58 break
58 break
59 pos += 1
59 pos += 1
60 else:
60 else:
61 raise error.ParseError(_("unterminated string"), s)
61 raise error.ParseError(_("unterminated string"), s)
62 elif c.isalnum() or c in globchars or ord(c) > 127:
62 elif c.isalnum() or c in globchars or ord(c) > 127:
63 # gather up a symbol/keyword
63 # gather up a symbol/keyword
64 s = pos
64 s = pos
65 pos += 1
65 pos += 1
66 while pos < l: # find end of symbol
66 while pos < l: # find end of symbol
67 d = program[pos]
67 d = program[pos]
68 if not (d.isalnum() or d in globchars or ord(d) > 127):
68 if not (d.isalnum() or d in globchars or ord(d) > 127):
69 break
69 break
70 pos += 1
70 pos += 1
71 sym = program[s:pos]
71 sym = program[s:pos]
72 if sym in keywords: # operator keywords
72 if sym in keywords: # operator keywords
73 yield (sym, None, s)
73 yield (sym, None, s)
74 else:
74 else:
75 yield ('symbol', sym, s)
75 yield ('symbol', sym, s)
76 pos -= 1
76 pos -= 1
77 else:
77 else:
78 raise error.ParseError(_("syntax error"), pos)
78 raise error.ParseError(_("syntax error"), pos)
79 pos += 1
79 pos += 1
80 yield ('end', None, pos)
80 yield ('end', None, pos)
81
81
82 def parse(expr):
82 def parse(expr):
83 p = parser.parser(tokenize, elements)
83 p = parser.parser(tokenize, elements)
84 tree, pos = p.parse(expr)
84 tree, pos = p.parse(expr)
85 if pos != len(expr):
85 if pos != len(expr):
86 raise error.ParseError(_("invalid token"), pos)
86 raise error.ParseError(_("invalid token"), pos)
87 return tree
87 return tree
88
88
89 def getstring(x, err):
89 def getstring(x, err):
90 if x and (x[0] == 'string' or x[0] == 'symbol'):
90 if x and (x[0] == 'string' or x[0] == 'symbol'):
91 return x[1]
91 return x[1]
92 raise error.ParseError(err)
92 raise error.ParseError(err)
93
93
94 def getset(mctx, x):
94 def getset(mctx, x):
95 if not x:
95 if not x:
96 raise error.ParseError(_("missing argument"))
96 raise error.ParseError(_("missing argument"))
97 return methods[x[0]](mctx, *x[1:])
97 return methods[x[0]](mctx, *x[1:])
98
98
99 def stringset(mctx, x):
99 def stringset(mctx, x):
100 m = mctx.matcher([x])
100 m = mctx.matcher([x])
101 return [f for f in mctx.subset if m(f)]
101 return [f for f in mctx.subset if m(f)]
102
102
103 def andset(mctx, x, y):
103 def andset(mctx, x, y):
104 return getset(mctx.narrow(getset(mctx, x)), y)
104 return getset(mctx.narrow(getset(mctx, x)), y)
105
105
106 def orset(mctx, x, y):
106 def orset(mctx, x, y):
107 # needs optimizing
107 # needs optimizing
108 xl = getset(mctx, x)
108 xl = getset(mctx, x)
109 yl = getset(mctx, y)
109 yl = getset(mctx, y)
110 return xl + [f for f in yl if f not in xl]
110 return xl + [f for f in yl if f not in xl]
111
111
112 def notset(mctx, x):
112 def notset(mctx, x):
113 s = set(getset(mctx, x))
113 s = set(getset(mctx, x))
114 return [r for r in mctx.subset if r not in s]
114 return [r for r in mctx.subset if r not in s]
115
115
116 def minusset(mctx, x, y):
116 def minusset(mctx, x, y):
117 xl = getset(mctx, x)
117 xl = getset(mctx, x)
118 yl = set(getset(mctx, y))
118 yl = set(getset(mctx, y))
119 return [f for f in xl if f not in yl]
119 return [f for f in xl if f not in yl]
120
120
121 def listset(mctx, a, b):
121 def listset(mctx, a, b):
122 raise error.ParseError(_("can't use a list in this context"))
122 raise error.ParseError(_("can't use a list in this context"))
123
123
124 def modified(mctx, x):
124 def modified(mctx, x):
125 """``modified()``
125 """``modified()``
126 File that is modified according to status.
126 File that is modified according to status.
127 """
127 """
128 # i18n: "modified" is a keyword
128 # i18n: "modified" is a keyword
129 getargs(x, 0, 0, _("modified takes no arguments"))
129 getargs(x, 0, 0, _("modified takes no arguments"))
130 s = mctx.status().modified
130 s = mctx.status().modified
131 return [f for f in mctx.subset if f in s]
131 return [f for f in mctx.subset if f in s]
132
132
133 def added(mctx, x):
133 def added(mctx, x):
134 """``added()``
134 """``added()``
135 File that is added according to status.
135 File that is added according to status.
136 """
136 """
137 # i18n: "added" is a keyword
137 # i18n: "added" is a keyword
138 getargs(x, 0, 0, _("added takes no arguments"))
138 getargs(x, 0, 0, _("added takes no arguments"))
139 s = mctx.status().added
139 s = mctx.status().added
140 return [f for f in mctx.subset if f in s]
140 return [f for f in mctx.subset if f in s]
141
141
142 def removed(mctx, x):
142 def removed(mctx, x):
143 """``removed()``
143 """``removed()``
144 File that is removed according to status.
144 File that is removed according to status.
145 """
145 """
146 # i18n: "removed" is a keyword
146 # i18n: "removed" is a keyword
147 getargs(x, 0, 0, _("removed takes no arguments"))
147 getargs(x, 0, 0, _("removed takes no arguments"))
148 s = mctx.status().removed
148 s = mctx.status().removed
149 return [f for f in mctx.subset if f in s]
149 return [f for f in mctx.subset if f in s]
150
150
151 def deleted(mctx, x):
151 def deleted(mctx, x):
152 """``deleted()``
152 """``deleted()``
153 File that is deleted according to status.
153 File that is deleted according to status.
154 """
154 """
155 # i18n: "deleted" is a keyword
155 # i18n: "deleted" is a keyword
156 getargs(x, 0, 0, _("deleted takes no arguments"))
156 getargs(x, 0, 0, _("deleted takes no arguments"))
157 s = mctx.status().deleted
157 s = mctx.status().deleted
158 return [f for f in mctx.subset if f in s]
158 return [f for f in mctx.subset if f in s]
159
159
160 def unknown(mctx, x):
160 def unknown(mctx, x):
161 """``unknown()``
161 """``unknown()``
162 File that is unknown according to status. These files will only be
162 File that is unknown according to status. These files will only be
163 considered if this predicate is used.
163 considered if this predicate is used.
164 """
164 """
165 # i18n: "unknown" is a keyword
165 # i18n: "unknown" is a keyword
166 getargs(x, 0, 0, _("unknown takes no arguments"))
166 getargs(x, 0, 0, _("unknown takes no arguments"))
167 s = mctx.status().unknown
167 s = mctx.status().unknown
168 return [f for f in mctx.subset if f in s]
168 return [f for f in mctx.subset if f in s]
169
169
170 def ignored(mctx, x):
170 def ignored(mctx, x):
171 """``ignored()``
171 """``ignored()``
172 File that is ignored according to status. These files will only be
172 File that is ignored according to status. These files will only be
173 considered if this predicate is used.
173 considered if this predicate is used.
174 """
174 """
175 # i18n: "ignored" is a keyword
175 # i18n: "ignored" is a keyword
176 getargs(x, 0, 0, _("ignored takes no arguments"))
176 getargs(x, 0, 0, _("ignored takes no arguments"))
177 s = mctx.status().ignored
177 s = mctx.status().ignored
178 return [f for f in mctx.subset if f in s]
178 return [f for f in mctx.subset if f in s]
179
179
180 def clean(mctx, x):
180 def clean(mctx, x):
181 """``clean()``
181 """``clean()``
182 File that is clean according to status.
182 File that is clean according to status.
183 """
183 """
184 # i18n: "clean" is a keyword
184 # i18n: "clean" is a keyword
185 getargs(x, 0, 0, _("clean takes no arguments"))
185 getargs(x, 0, 0, _("clean takes no arguments"))
186 s = mctx.status().clean
186 s = mctx.status().clean
187 return [f for f in mctx.subset if f in s]
187 return [f for f in mctx.subset if f in s]
188
188
189 def func(mctx, a, b):
189 def func(mctx, a, b):
190 if a[0] == 'symbol' and a[1] in symbols:
190 if a[0] == 'symbol' and a[1] in symbols:
191 return symbols[a[1]](mctx, b)
191 return symbols[a[1]](mctx, b)
192 raise error.UnknownIdentifier(a[1], symbols.keys())
192
193 keep = lambda fn: getattr(fn, '__doc__', None) is not None
194
195 syms = [s for (s, fn) in symbols.items() if keep(fn)]
196 raise error.UnknownIdentifier(a[1], syms)
193
197
194 def getlist(x):
198 def getlist(x):
195 if not x:
199 if not x:
196 return []
200 return []
197 if x[0] == 'list':
201 if x[0] == 'list':
198 return getlist(x[1]) + [x[2]]
202 return getlist(x[1]) + [x[2]]
199 return [x]
203 return [x]
200
204
201 def getargs(x, min, max, err):
205 def getargs(x, min, max, err):
202 l = getlist(x)
206 l = getlist(x)
203 if len(l) < min or len(l) > max:
207 if len(l) < min or len(l) > max:
204 raise error.ParseError(err)
208 raise error.ParseError(err)
205 return l
209 return l
206
210
207 def binary(mctx, x):
211 def binary(mctx, x):
208 """``binary()``
212 """``binary()``
209 File that appears to be binary (contains NUL bytes).
213 File that appears to be binary (contains NUL bytes).
210 """
214 """
211 # i18n: "binary" is a keyword
215 # i18n: "binary" is a keyword
212 getargs(x, 0, 0, _("binary takes no arguments"))
216 getargs(x, 0, 0, _("binary takes no arguments"))
213 return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())]
217 return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())]
214
218
215 def exec_(mctx, x):
219 def exec_(mctx, x):
216 """``exec()``
220 """``exec()``
217 File that is marked as executable.
221 File that is marked as executable.
218 """
222 """
219 # i18n: "exec" is a keyword
223 # i18n: "exec" is a keyword
220 getargs(x, 0, 0, _("exec takes no arguments"))
224 getargs(x, 0, 0, _("exec takes no arguments"))
221 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
225 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
222
226
223 def symlink(mctx, x):
227 def symlink(mctx, x):
224 """``symlink()``
228 """``symlink()``
225 File that is marked as a symlink.
229 File that is marked as a symlink.
226 """
230 """
227 # i18n: "symlink" is a keyword
231 # i18n: "symlink" is a keyword
228 getargs(x, 0, 0, _("symlink takes no arguments"))
232 getargs(x, 0, 0, _("symlink takes no arguments"))
229 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
233 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
230
234
231 def resolved(mctx, x):
235 def resolved(mctx, x):
232 """``resolved()``
236 """``resolved()``
233 File that is marked resolved according to the resolve state.
237 File that is marked resolved according to the resolve state.
234 """
238 """
235 # i18n: "resolved" is a keyword
239 # i18n: "resolved" is a keyword
236 getargs(x, 0, 0, _("resolved takes no arguments"))
240 getargs(x, 0, 0, _("resolved takes no arguments"))
237 if mctx.ctx.rev() is not None:
241 if mctx.ctx.rev() is not None:
238 return []
242 return []
239 ms = merge.mergestate(mctx.ctx.repo())
243 ms = merge.mergestate(mctx.ctx.repo())
240 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
244 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
241
245
242 def unresolved(mctx, x):
246 def unresolved(mctx, x):
243 """``unresolved()``
247 """``unresolved()``
244 File that is marked unresolved according to the resolve state.
248 File that is marked unresolved according to the resolve state.
245 """
249 """
246 # i18n: "unresolved" is a keyword
250 # i18n: "unresolved" is a keyword
247 getargs(x, 0, 0, _("unresolved takes no arguments"))
251 getargs(x, 0, 0, _("unresolved takes no arguments"))
248 if mctx.ctx.rev() is not None:
252 if mctx.ctx.rev() is not None:
249 return []
253 return []
250 ms = merge.mergestate(mctx.ctx.repo())
254 ms = merge.mergestate(mctx.ctx.repo())
251 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
255 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
252
256
253 def hgignore(mctx, x):
257 def hgignore(mctx, x):
254 """``hgignore()``
258 """``hgignore()``
255 File that matches the active .hgignore pattern.
259 File that matches the active .hgignore pattern.
256 """
260 """
257 # i18n: "hgignore" is a keyword
261 # i18n: "hgignore" is a keyword
258 getargs(x, 0, 0, _("hgignore takes no arguments"))
262 getargs(x, 0, 0, _("hgignore takes no arguments"))
259 ignore = mctx.ctx.repo().dirstate._ignore
263 ignore = mctx.ctx.repo().dirstate._ignore
260 return [f for f in mctx.subset if ignore(f)]
264 return [f for f in mctx.subset if ignore(f)]
261
265
262 def portable(mctx, x):
266 def portable(mctx, x):
263 """``portable()``
267 """``portable()``
264 File that has a portable name. (This doesn't include filenames with case
268 File that has a portable name. (This doesn't include filenames with case
265 collisions.)
269 collisions.)
266 """
270 """
267 # i18n: "portable" is a keyword
271 # i18n: "portable" is a keyword
268 getargs(x, 0, 0, _("portable takes no arguments"))
272 getargs(x, 0, 0, _("portable takes no arguments"))
269 checkwinfilename = util.checkwinfilename
273 checkwinfilename = util.checkwinfilename
270 return [f for f in mctx.subset if checkwinfilename(f) is None]
274 return [f for f in mctx.subset if checkwinfilename(f) is None]
271
275
272 def grep(mctx, x):
276 def grep(mctx, x):
273 """``grep(regex)``
277 """``grep(regex)``
274 File contains the given regular expression.
278 File contains the given regular expression.
275 """
279 """
276 try:
280 try:
277 # i18n: "grep" is a keyword
281 # i18n: "grep" is a keyword
278 r = re.compile(getstring(x, _("grep requires a pattern")))
282 r = re.compile(getstring(x, _("grep requires a pattern")))
279 except re.error, e:
283 except re.error, e:
280 raise error.ParseError(_('invalid match pattern: %s') % e)
284 raise error.ParseError(_('invalid match pattern: %s') % e)
281 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
285 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
282
286
283 def _sizetomax(s):
287 def _sizetomax(s):
284 try:
288 try:
285 s = s.strip()
289 s = s.strip()
286 for k, v in util._sizeunits:
290 for k, v in util._sizeunits:
287 if s.endswith(k):
291 if s.endswith(k):
288 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
292 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
289 n = s[:-len(k)]
293 n = s[:-len(k)]
290 inc = 1.0
294 inc = 1.0
291 if "." in n:
295 if "." in n:
292 inc /= 10 ** len(n.split(".")[1])
296 inc /= 10 ** len(n.split(".")[1])
293 return int((float(n) + inc) * v) - 1
297 return int((float(n) + inc) * v) - 1
294 # no extension, this is a precise value
298 # no extension, this is a precise value
295 return int(s)
299 return int(s)
296 except ValueError:
300 except ValueError:
297 raise error.ParseError(_("couldn't parse size: %s") % s)
301 raise error.ParseError(_("couldn't parse size: %s") % s)
298
302
299 def size(mctx, x):
303 def size(mctx, x):
300 """``size(expression)``
304 """``size(expression)``
301 File size matches the given expression. Examples:
305 File size matches the given expression. Examples:
302
306
303 - 1k (files from 1024 to 2047 bytes)
307 - 1k (files from 1024 to 2047 bytes)
304 - < 20k (files less than 20480 bytes)
308 - < 20k (files less than 20480 bytes)
305 - >= .5MB (files at least 524288 bytes)
309 - >= .5MB (files at least 524288 bytes)
306 - 4k - 1MB (files from 4096 bytes to 1048576 bytes)
310 - 4k - 1MB (files from 4096 bytes to 1048576 bytes)
307 """
311 """
308
312
309 # i18n: "size" is a keyword
313 # i18n: "size" is a keyword
310 expr = getstring(x, _("size requires an expression")).strip()
314 expr = getstring(x, _("size requires an expression")).strip()
311 if '-' in expr: # do we have a range?
315 if '-' in expr: # do we have a range?
312 a, b = expr.split('-', 1)
316 a, b = expr.split('-', 1)
313 a = util.sizetoint(a)
317 a = util.sizetoint(a)
314 b = util.sizetoint(b)
318 b = util.sizetoint(b)
315 m = lambda x: x >= a and x <= b
319 m = lambda x: x >= a and x <= b
316 elif expr.startswith("<="):
320 elif expr.startswith("<="):
317 a = util.sizetoint(expr[2:])
321 a = util.sizetoint(expr[2:])
318 m = lambda x: x <= a
322 m = lambda x: x <= a
319 elif expr.startswith("<"):
323 elif expr.startswith("<"):
320 a = util.sizetoint(expr[1:])
324 a = util.sizetoint(expr[1:])
321 m = lambda x: x < a
325 m = lambda x: x < a
322 elif expr.startswith(">="):
326 elif expr.startswith(">="):
323 a = util.sizetoint(expr[2:])
327 a = util.sizetoint(expr[2:])
324 m = lambda x: x >= a
328 m = lambda x: x >= a
325 elif expr.startswith(">"):
329 elif expr.startswith(">"):
326 a = util.sizetoint(expr[1:])
330 a = util.sizetoint(expr[1:])
327 m = lambda x: x > a
331 m = lambda x: x > a
328 elif expr[0].isdigit or expr[0] == '.':
332 elif expr[0].isdigit or expr[0] == '.':
329 a = util.sizetoint(expr)
333 a = util.sizetoint(expr)
330 b = _sizetomax(expr)
334 b = _sizetomax(expr)
331 m = lambda x: x >= a and x <= b
335 m = lambda x: x >= a and x <= b
332 else:
336 else:
333 raise error.ParseError(_("couldn't parse size: %s") % expr)
337 raise error.ParseError(_("couldn't parse size: %s") % expr)
334
338
335 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
339 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
336
340
337 def encoding(mctx, x):
341 def encoding(mctx, x):
338 """``encoding(name)``
342 """``encoding(name)``
339 File can be successfully decoded with the given character
343 File can be successfully decoded with the given character
340 encoding. May not be useful for encodings other than ASCII and
344 encoding. May not be useful for encodings other than ASCII and
341 UTF-8.
345 UTF-8.
342 """
346 """
343
347
344 # i18n: "encoding" is a keyword
348 # i18n: "encoding" is a keyword
345 enc = getstring(x, _("encoding requires an encoding name"))
349 enc = getstring(x, _("encoding requires an encoding name"))
346
350
347 s = []
351 s = []
348 for f in mctx.existing():
352 for f in mctx.existing():
349 d = mctx.ctx[f].data()
353 d = mctx.ctx[f].data()
350 try:
354 try:
351 d.decode(enc)
355 d.decode(enc)
352 except LookupError:
356 except LookupError:
353 raise util.Abort(_("unknown encoding '%s'") % enc)
357 raise util.Abort(_("unknown encoding '%s'") % enc)
354 except UnicodeDecodeError:
358 except UnicodeDecodeError:
355 continue
359 continue
356 s.append(f)
360 s.append(f)
357
361
358 return s
362 return s
359
363
360 def eol(mctx, x):
364 def eol(mctx, x):
361 """``eol(style)``
365 """``eol(style)``
362 File contains newlines of the given style (dos, unix, mac). Binary
366 File contains newlines of the given style (dos, unix, mac). Binary
363 files are excluded, files with mixed line endings match multiple
367 files are excluded, files with mixed line endings match multiple
364 styles.
368 styles.
365 """
369 """
366
370
367 # i18n: "encoding" is a keyword
371 # i18n: "encoding" is a keyword
368 enc = getstring(x, _("encoding requires an encoding name"))
372 enc = getstring(x, _("encoding requires an encoding name"))
369
373
370 s = []
374 s = []
371 for f in mctx.existing():
375 for f in mctx.existing():
372 d = mctx.ctx[f].data()
376 d = mctx.ctx[f].data()
373 if util.binary(d):
377 if util.binary(d):
374 continue
378 continue
375 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
379 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
376 s.append(f)
380 s.append(f)
377 elif enc == 'unix' and re.search('(?<!\r)\n', d):
381 elif enc == 'unix' and re.search('(?<!\r)\n', d):
378 s.append(f)
382 s.append(f)
379 elif enc == 'mac' and re.search('\r(?!\n)', d):
383 elif enc == 'mac' and re.search('\r(?!\n)', d):
380 s.append(f)
384 s.append(f)
381 return s
385 return s
382
386
383 def copied(mctx, x):
387 def copied(mctx, x):
384 """``copied()``
388 """``copied()``
385 File that is recorded as being copied.
389 File that is recorded as being copied.
386 """
390 """
387 # i18n: "copied" is a keyword
391 # i18n: "copied" is a keyword
388 getargs(x, 0, 0, _("copied takes no arguments"))
392 getargs(x, 0, 0, _("copied takes no arguments"))
389 s = []
393 s = []
390 for f in mctx.subset:
394 for f in mctx.subset:
391 p = mctx.ctx[f].parents()
395 p = mctx.ctx[f].parents()
392 if p and p[0].path() != f:
396 if p and p[0].path() != f:
393 s.append(f)
397 s.append(f)
394 return s
398 return s
395
399
396 def subrepo(mctx, x):
400 def subrepo(mctx, x):
397 """``subrepo([pattern])``
401 """``subrepo([pattern])``
398 Subrepositories whose paths match the given pattern.
402 Subrepositories whose paths match the given pattern.
399 """
403 """
400 # i18n: "subrepo" is a keyword
404 # i18n: "subrepo" is a keyword
401 getargs(x, 0, 1, _("subrepo takes at most one argument"))
405 getargs(x, 0, 1, _("subrepo takes at most one argument"))
402 ctx = mctx.ctx
406 ctx = mctx.ctx
403 sstate = sorted(ctx.substate)
407 sstate = sorted(ctx.substate)
404 if x:
408 if x:
405 # i18n: "subrepo" is a keyword
409 # i18n: "subrepo" is a keyword
406 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
410 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
407
411
408 import match as matchmod # avoid circular import issues
412 import match as matchmod # avoid circular import issues
409 fast = not matchmod.patkind(pat)
413 fast = not matchmod.patkind(pat)
410 if fast:
414 if fast:
411 def m(s):
415 def m(s):
412 return (s == pat)
416 return (s == pat)
413 else:
417 else:
414 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
418 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
415 return [sub for sub in sstate if m(sub)]
419 return [sub for sub in sstate if m(sub)]
416 else:
420 else:
417 return [sub for sub in sstate]
421 return [sub for sub in sstate]
418
422
419 symbols = {
423 symbols = {
420 'added': added,
424 'added': added,
421 'binary': binary,
425 'binary': binary,
422 'clean': clean,
426 'clean': clean,
423 'copied': copied,
427 'copied': copied,
424 'deleted': deleted,
428 'deleted': deleted,
425 'encoding': encoding,
429 'encoding': encoding,
426 'eol': eol,
430 'eol': eol,
427 'exec': exec_,
431 'exec': exec_,
428 'grep': grep,
432 'grep': grep,
429 'ignored': ignored,
433 'ignored': ignored,
430 'hgignore': hgignore,
434 'hgignore': hgignore,
431 'modified': modified,
435 'modified': modified,
432 'portable': portable,
436 'portable': portable,
433 'removed': removed,
437 'removed': removed,
434 'resolved': resolved,
438 'resolved': resolved,
435 'size': size,
439 'size': size,
436 'symlink': symlink,
440 'symlink': symlink,
437 'unknown': unknown,
441 'unknown': unknown,
438 'unresolved': unresolved,
442 'unresolved': unresolved,
439 'subrepo': subrepo,
443 'subrepo': subrepo,
440 }
444 }
441
445
442 methods = {
446 methods = {
443 'string': stringset,
447 'string': stringset,
444 'symbol': stringset,
448 'symbol': stringset,
445 'and': andset,
449 'and': andset,
446 'or': orset,
450 'or': orset,
447 'minus': minusset,
451 'minus': minusset,
448 'list': listset,
452 'list': listset,
449 'group': getset,
453 'group': getset,
450 'not': notset,
454 'not': notset,
451 'func': func,
455 'func': func,
452 }
456 }
453
457
454 class matchctx(object):
458 class matchctx(object):
455 def __init__(self, ctx, subset=None, status=None):
459 def __init__(self, ctx, subset=None, status=None):
456 self.ctx = ctx
460 self.ctx = ctx
457 self.subset = subset
461 self.subset = subset
458 self._status = status
462 self._status = status
459 def status(self):
463 def status(self):
460 return self._status
464 return self._status
461 def matcher(self, patterns):
465 def matcher(self, patterns):
462 return self.ctx.match(patterns)
466 return self.ctx.match(patterns)
463 def filter(self, files):
467 def filter(self, files):
464 return [f for f in files if f in self.subset]
468 return [f for f in files if f in self.subset]
465 def existing(self):
469 def existing(self):
466 if self._status is not None:
470 if self._status is not None:
467 removed = set(self._status[3])
471 removed = set(self._status[3])
468 unknown = set(self._status[4] + self._status[5])
472 unknown = set(self._status[4] + self._status[5])
469 else:
473 else:
470 removed = set()
474 removed = set()
471 unknown = set()
475 unknown = set()
472 return (f for f in self.subset
476 return (f for f in self.subset
473 if (f in self.ctx and f not in removed) or f in unknown)
477 if (f in self.ctx and f not in removed) or f in unknown)
474 def narrow(self, files):
478 def narrow(self, files):
475 return matchctx(self.ctx, self.filter(files), self._status)
479 return matchctx(self.ctx, self.filter(files), self._status)
476
480
477 def _intree(funcs, tree):
481 def _intree(funcs, tree):
478 if isinstance(tree, tuple):
482 if isinstance(tree, tuple):
479 if tree[0] == 'func' and tree[1][0] == 'symbol':
483 if tree[0] == 'func' and tree[1][0] == 'symbol':
480 if tree[1][1] in funcs:
484 if tree[1][1] in funcs:
481 return True
485 return True
482 for s in tree[1:]:
486 for s in tree[1:]:
483 if _intree(funcs, s):
487 if _intree(funcs, s):
484 return True
488 return True
485 return False
489 return False
486
490
487 # filesets using matchctx.existing()
491 # filesets using matchctx.existing()
488 _existingcallers = [
492 _existingcallers = [
489 'binary',
493 'binary',
490 'exec',
494 'exec',
491 'grep',
495 'grep',
492 'size',
496 'size',
493 'symlink',
497 'symlink',
494 ]
498 ]
495
499
496 def getfileset(ctx, expr):
500 def getfileset(ctx, expr):
497 tree = parse(expr)
501 tree = parse(expr)
498
502
499 # do we need status info?
503 # do we need status info?
500 if (_intree(['modified', 'added', 'removed', 'deleted',
504 if (_intree(['modified', 'added', 'removed', 'deleted',
501 'unknown', 'ignored', 'clean'], tree) or
505 'unknown', 'ignored', 'clean'], tree) or
502 # Using matchctx.existing() on a workingctx requires us to check
506 # Using matchctx.existing() on a workingctx requires us to check
503 # for deleted files.
507 # for deleted files.
504 (ctx.rev() is None and _intree(_existingcallers, tree))):
508 (ctx.rev() is None and _intree(_existingcallers, tree))):
505 unknown = _intree(['unknown'], tree)
509 unknown = _intree(['unknown'], tree)
506 ignored = _intree(['ignored'], tree)
510 ignored = _intree(['ignored'], tree)
507
511
508 r = ctx.repo()
512 r = ctx.repo()
509 status = r.status(ctx.p1(), ctx,
513 status = r.status(ctx.p1(), ctx,
510 unknown=unknown, ignored=ignored, clean=True)
514 unknown=unknown, ignored=ignored, clean=True)
511 subset = []
515 subset = []
512 for c in status:
516 for c in status:
513 subset.extend(c)
517 subset.extend(c)
514 else:
518 else:
515 status = None
519 status = None
516 subset = list(ctx.walk(ctx.match([])))
520 subset = list(ctx.walk(ctx.match([])))
517
521
518 return getset(matchctx(ctx, subset, status), tree)
522 return getset(matchctx(ctx, subset, status), tree)
519
523
520 def prettyformat(tree):
524 def prettyformat(tree):
521 return parser.prettyformat(tree, ('string', 'symbol'))
525 return parser.prettyformat(tree, ('string', 'symbol'))
522
526
523 # tell hggettext to extract docstrings from these functions:
527 # tell hggettext to extract docstrings from these functions:
524 i18nfunctions = symbols.values()
528 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now