##// END OF EJS Templates
py3: factor out byterepr() which returns an asciified value on py3
Yuya Nishihara -
r36279:b44fac3a default
parent child Browse files
Show More
@@ -1,544 +1,544 b''
1 # formatter.py - generic output formatting for mercurial
1 # formatter.py - generic output formatting for mercurial
2 #
2 #
3 # Copyright 2012 Matt Mackall <mpm@selenic.com>
3 # Copyright 2012 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 """Generic output formatting for Mercurial
8 """Generic output formatting for Mercurial
9
9
10 The formatter provides API to show data in various ways. The following
10 The formatter provides API to show data in various ways. The following
11 functions should be used in place of ui.write():
11 functions should be used in place of ui.write():
12
12
13 - fm.write() for unconditional output
13 - fm.write() for unconditional output
14 - fm.condwrite() to show some extra data conditionally in plain output
14 - fm.condwrite() to show some extra data conditionally in plain output
15 - fm.context() to provide changectx to template output
15 - fm.context() to provide changectx to template output
16 - fm.data() to provide extra data to JSON or template output
16 - fm.data() to provide extra data to JSON or template output
17 - fm.plain() to show raw text that isn't provided to JSON or template output
17 - fm.plain() to show raw text that isn't provided to JSON or template output
18
18
19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
20 beforehand so the data is converted to the appropriate data type. Use
20 beforehand so the data is converted to the appropriate data type. Use
21 fm.isplain() if you need to convert or format data conditionally which isn't
21 fm.isplain() if you need to convert or format data conditionally which isn't
22 supported by the formatter API.
22 supported by the formatter API.
23
23
24 To build nested structure (i.e. a list of dicts), use fm.nested().
24 To build nested structure (i.e. a list of dicts), use fm.nested().
25
25
26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
27
27
28 fm.condwrite() vs 'if cond:':
28 fm.condwrite() vs 'if cond:':
29
29
30 In most cases, use fm.condwrite() so users can selectively show the data
30 In most cases, use fm.condwrite() so users can selectively show the data
31 in template output. If it's costly to build data, use plain 'if cond:' with
31 in template output. If it's costly to build data, use plain 'if cond:' with
32 fm.write().
32 fm.write().
33
33
34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
35
35
36 fm.nested() should be used to form a tree structure (a list of dicts of
36 fm.nested() should be used to form a tree structure (a list of dicts of
37 lists of dicts...) which can be accessed through template keywords, e.g.
37 lists of dicts...) which can be accessed through template keywords, e.g.
38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
39 exports a dict-type object to template, which can be accessed by e.g.
39 exports a dict-type object to template, which can be accessed by e.g.
40 "{get(foo, key)}" function.
40 "{get(foo, key)}" function.
41
41
42 Doctest helper:
42 Doctest helper:
43
43
44 >>> def show(fn, verbose=False, **opts):
44 >>> def show(fn, verbose=False, **opts):
45 ... import sys
45 ... import sys
46 ... from . import ui as uimod
46 ... from . import ui as uimod
47 ... ui = uimod.ui()
47 ... ui = uimod.ui()
48 ... ui.verbose = verbose
48 ... ui.verbose = verbose
49 ... ui.pushbuffer()
49 ... ui.pushbuffer()
50 ... try:
50 ... try:
51 ... return fn(ui, ui.formatter(pycompat.sysbytes(fn.__name__),
51 ... return fn(ui, ui.formatter(pycompat.sysbytes(fn.__name__),
52 ... pycompat.byteskwargs(opts)))
52 ... pycompat.byteskwargs(opts)))
53 ... finally:
53 ... finally:
54 ... print(pycompat.sysstr(ui.popbuffer()), end='')
54 ... print(pycompat.sysstr(ui.popbuffer()), end='')
55
55
56 Basic example:
56 Basic example:
57
57
58 >>> def files(ui, fm):
58 >>> def files(ui, fm):
59 ... files = [(b'foo', 123, (0, 0)), (b'bar', 456, (1, 0))]
59 ... files = [(b'foo', 123, (0, 0)), (b'bar', 456, (1, 0))]
60 ... for f in files:
60 ... for f in files:
61 ... fm.startitem()
61 ... fm.startitem()
62 ... fm.write(b'path', b'%s', f[0])
62 ... fm.write(b'path', b'%s', f[0])
63 ... fm.condwrite(ui.verbose, b'date', b' %s',
63 ... fm.condwrite(ui.verbose, b'date', b' %s',
64 ... fm.formatdate(f[2], b'%Y-%m-%d %H:%M:%S'))
64 ... fm.formatdate(f[2], b'%Y-%m-%d %H:%M:%S'))
65 ... fm.data(size=f[1])
65 ... fm.data(size=f[1])
66 ... fm.plain(b'\\n')
66 ... fm.plain(b'\\n')
67 ... fm.end()
67 ... fm.end()
68 >>> show(files)
68 >>> show(files)
69 foo
69 foo
70 bar
70 bar
71 >>> show(files, verbose=True)
71 >>> show(files, verbose=True)
72 foo 1970-01-01 00:00:00
72 foo 1970-01-01 00:00:00
73 bar 1970-01-01 00:00:01
73 bar 1970-01-01 00:00:01
74 >>> show(files, template=b'json')
74 >>> show(files, template=b'json')
75 [
75 [
76 {
76 {
77 "date": [0, 0],
77 "date": [0, 0],
78 "path": "foo",
78 "path": "foo",
79 "size": 123
79 "size": 123
80 },
80 },
81 {
81 {
82 "date": [1, 0],
82 "date": [1, 0],
83 "path": "bar",
83 "path": "bar",
84 "size": 456
84 "size": 456
85 }
85 }
86 ]
86 ]
87 >>> show(files, template=b'path: {path}\\ndate: {date|rfc3339date}\\n')
87 >>> show(files, template=b'path: {path}\\ndate: {date|rfc3339date}\\n')
88 path: foo
88 path: foo
89 date: 1970-01-01T00:00:00+00:00
89 date: 1970-01-01T00:00:00+00:00
90 path: bar
90 path: bar
91 date: 1970-01-01T00:00:01+00:00
91 date: 1970-01-01T00:00:01+00:00
92
92
93 Nested example:
93 Nested example:
94
94
95 >>> def subrepos(ui, fm):
95 >>> def subrepos(ui, fm):
96 ... fm.startitem()
96 ... fm.startitem()
97 ... fm.write(b'reponame', b'[%s]\\n', b'baz')
97 ... fm.write(b'reponame', b'[%s]\\n', b'baz')
98 ... files(ui, fm.nested(b'files'))
98 ... files(ui, fm.nested(b'files'))
99 ... fm.end()
99 ... fm.end()
100 >>> show(subrepos)
100 >>> show(subrepos)
101 [baz]
101 [baz]
102 foo
102 foo
103 bar
103 bar
104 >>> show(subrepos, template=b'{reponame}: {join(files % "{path}", ", ")}\\n')
104 >>> show(subrepos, template=b'{reponame}: {join(files % "{path}", ", ")}\\n')
105 baz: foo, bar
105 baz: foo, bar
106 """
106 """
107
107
108 from __future__ import absolute_import, print_function
108 from __future__ import absolute_import, print_function
109
109
110 import collections
110 import collections
111 import contextlib
111 import contextlib
112 import itertools
112 import itertools
113 import os
113 import os
114
114
115 from .i18n import _
115 from .i18n import _
116 from .node import (
116 from .node import (
117 hex,
117 hex,
118 short,
118 short,
119 )
119 )
120
120
121 from . import (
121 from . import (
122 error,
122 error,
123 pycompat,
123 pycompat,
124 templatefilters,
124 templatefilters,
125 templatekw,
125 templatekw,
126 templater,
126 templater,
127 util,
127 util,
128 )
128 )
129
129
130 pickle = util.pickle
130 pickle = util.pickle
131
131
132 class _nullconverter(object):
132 class _nullconverter(object):
133 '''convert non-primitive data types to be processed by formatter'''
133 '''convert non-primitive data types to be processed by formatter'''
134
134
135 # set to True if context object should be stored as item
135 # set to True if context object should be stored as item
136 storecontext = False
136 storecontext = False
137
137
138 @staticmethod
138 @staticmethod
139 def formatdate(date, fmt):
139 def formatdate(date, fmt):
140 '''convert date tuple to appropriate format'''
140 '''convert date tuple to appropriate format'''
141 return date
141 return date
142 @staticmethod
142 @staticmethod
143 def formatdict(data, key, value, fmt, sep):
143 def formatdict(data, key, value, fmt, sep):
144 '''convert dict or key-value pairs to appropriate dict format'''
144 '''convert dict or key-value pairs to appropriate dict format'''
145 # use plain dict instead of util.sortdict so that data can be
145 # use plain dict instead of util.sortdict so that data can be
146 # serialized as a builtin dict in pickle output
146 # serialized as a builtin dict in pickle output
147 return dict(data)
147 return dict(data)
148 @staticmethod
148 @staticmethod
149 def formatlist(data, name, fmt, sep):
149 def formatlist(data, name, fmt, sep):
150 '''convert iterable to appropriate list format'''
150 '''convert iterable to appropriate list format'''
151 return list(data)
151 return list(data)
152
152
153 class baseformatter(object):
153 class baseformatter(object):
154 def __init__(self, ui, topic, opts, converter):
154 def __init__(self, ui, topic, opts, converter):
155 self._ui = ui
155 self._ui = ui
156 self._topic = topic
156 self._topic = topic
157 self._style = opts.get("style")
157 self._style = opts.get("style")
158 self._template = opts.get("template")
158 self._template = opts.get("template")
159 self._converter = converter
159 self._converter = converter
160 self._item = None
160 self._item = None
161 # function to convert node to string suitable for this output
161 # function to convert node to string suitable for this output
162 self.hexfunc = hex
162 self.hexfunc = hex
163 def __enter__(self):
163 def __enter__(self):
164 return self
164 return self
165 def __exit__(self, exctype, excvalue, traceback):
165 def __exit__(self, exctype, excvalue, traceback):
166 if exctype is None:
166 if exctype is None:
167 self.end()
167 self.end()
168 def _showitem(self):
168 def _showitem(self):
169 '''show a formatted item once all data is collected'''
169 '''show a formatted item once all data is collected'''
170 def startitem(self):
170 def startitem(self):
171 '''begin an item in the format list'''
171 '''begin an item in the format list'''
172 if self._item is not None:
172 if self._item is not None:
173 self._showitem()
173 self._showitem()
174 self._item = {}
174 self._item = {}
175 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
175 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
176 '''convert date tuple to appropriate format'''
176 '''convert date tuple to appropriate format'''
177 return self._converter.formatdate(date, fmt)
177 return self._converter.formatdate(date, fmt)
178 def formatdict(self, data, key='key', value='value', fmt='%s=%s', sep=' '):
178 def formatdict(self, data, key='key', value='value', fmt='%s=%s', sep=' '):
179 '''convert dict or key-value pairs to appropriate dict format'''
179 '''convert dict or key-value pairs to appropriate dict format'''
180 return self._converter.formatdict(data, key, value, fmt, sep)
180 return self._converter.formatdict(data, key, value, fmt, sep)
181 def formatlist(self, data, name, fmt='%s', sep=' '):
181 def formatlist(self, data, name, fmt='%s', sep=' '):
182 '''convert iterable to appropriate list format'''
182 '''convert iterable to appropriate list format'''
183 # name is mandatory argument for now, but it could be optional if
183 # name is mandatory argument for now, but it could be optional if
184 # we have default template keyword, e.g. {item}
184 # we have default template keyword, e.g. {item}
185 return self._converter.formatlist(data, name, fmt, sep)
185 return self._converter.formatlist(data, name, fmt, sep)
186 def context(self, **ctxs):
186 def context(self, **ctxs):
187 '''insert context objects to be used to render template keywords'''
187 '''insert context objects to be used to render template keywords'''
188 ctxs = pycompat.byteskwargs(ctxs)
188 ctxs = pycompat.byteskwargs(ctxs)
189 assert all(k == 'ctx' for k in ctxs)
189 assert all(k == 'ctx' for k in ctxs)
190 if self._converter.storecontext:
190 if self._converter.storecontext:
191 self._item.update(ctxs)
191 self._item.update(ctxs)
192 def data(self, **data):
192 def data(self, **data):
193 '''insert data into item that's not shown in default output'''
193 '''insert data into item that's not shown in default output'''
194 data = pycompat.byteskwargs(data)
194 data = pycompat.byteskwargs(data)
195 self._item.update(data)
195 self._item.update(data)
196 def write(self, fields, deftext, *fielddata, **opts):
196 def write(self, fields, deftext, *fielddata, **opts):
197 '''do default text output while assigning data to item'''
197 '''do default text output while assigning data to item'''
198 fieldkeys = fields.split()
198 fieldkeys = fields.split()
199 assert len(fieldkeys) == len(fielddata)
199 assert len(fieldkeys) == len(fielddata)
200 self._item.update(zip(fieldkeys, fielddata))
200 self._item.update(zip(fieldkeys, fielddata))
201 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
201 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
202 '''do conditional write (primarily for plain formatter)'''
202 '''do conditional write (primarily for plain formatter)'''
203 fieldkeys = fields.split()
203 fieldkeys = fields.split()
204 assert len(fieldkeys) == len(fielddata)
204 assert len(fieldkeys) == len(fielddata)
205 self._item.update(zip(fieldkeys, fielddata))
205 self._item.update(zip(fieldkeys, fielddata))
206 def plain(self, text, **opts):
206 def plain(self, text, **opts):
207 '''show raw text for non-templated mode'''
207 '''show raw text for non-templated mode'''
208 def isplain(self):
208 def isplain(self):
209 '''check for plain formatter usage'''
209 '''check for plain formatter usage'''
210 return False
210 return False
211 def nested(self, field):
211 def nested(self, field):
212 '''sub formatter to store nested data in the specified field'''
212 '''sub formatter to store nested data in the specified field'''
213 self._item[field] = data = []
213 self._item[field] = data = []
214 return _nestedformatter(self._ui, self._converter, data)
214 return _nestedformatter(self._ui, self._converter, data)
215 def end(self):
215 def end(self):
216 '''end output for the formatter'''
216 '''end output for the formatter'''
217 if self._item is not None:
217 if self._item is not None:
218 self._showitem()
218 self._showitem()
219
219
220 def nullformatter(ui, topic):
220 def nullformatter(ui, topic):
221 '''formatter that prints nothing'''
221 '''formatter that prints nothing'''
222 return baseformatter(ui, topic, opts={}, converter=_nullconverter)
222 return baseformatter(ui, topic, opts={}, converter=_nullconverter)
223
223
224 class _nestedformatter(baseformatter):
224 class _nestedformatter(baseformatter):
225 '''build sub items and store them in the parent formatter'''
225 '''build sub items and store them in the parent formatter'''
226 def __init__(self, ui, converter, data):
226 def __init__(self, ui, converter, data):
227 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
227 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
228 self._data = data
228 self._data = data
229 def _showitem(self):
229 def _showitem(self):
230 self._data.append(self._item)
230 self._data.append(self._item)
231
231
232 def _iteritems(data):
232 def _iteritems(data):
233 '''iterate key-value pairs in stable order'''
233 '''iterate key-value pairs in stable order'''
234 if isinstance(data, dict):
234 if isinstance(data, dict):
235 return sorted(data.iteritems())
235 return sorted(data.iteritems())
236 return data
236 return data
237
237
238 class _plainconverter(object):
238 class _plainconverter(object):
239 '''convert non-primitive data types to text'''
239 '''convert non-primitive data types to text'''
240
240
241 storecontext = False
241 storecontext = False
242
242
243 @staticmethod
243 @staticmethod
244 def formatdate(date, fmt):
244 def formatdate(date, fmt):
245 '''stringify date tuple in the given format'''
245 '''stringify date tuple in the given format'''
246 return util.datestr(date, fmt)
246 return util.datestr(date, fmt)
247 @staticmethod
247 @staticmethod
248 def formatdict(data, key, value, fmt, sep):
248 def formatdict(data, key, value, fmt, sep):
249 '''stringify key-value pairs separated by sep'''
249 '''stringify key-value pairs separated by sep'''
250 return sep.join(fmt % (k, v) for k, v in _iteritems(data))
250 return sep.join(fmt % (k, v) for k, v in _iteritems(data))
251 @staticmethod
251 @staticmethod
252 def formatlist(data, name, fmt, sep):
252 def formatlist(data, name, fmt, sep):
253 '''stringify iterable separated by sep'''
253 '''stringify iterable separated by sep'''
254 return sep.join(fmt % e for e in data)
254 return sep.join(fmt % e for e in data)
255
255
256 class plainformatter(baseformatter):
256 class plainformatter(baseformatter):
257 '''the default text output scheme'''
257 '''the default text output scheme'''
258 def __init__(self, ui, out, topic, opts):
258 def __init__(self, ui, out, topic, opts):
259 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
259 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
260 if ui.debugflag:
260 if ui.debugflag:
261 self.hexfunc = hex
261 self.hexfunc = hex
262 else:
262 else:
263 self.hexfunc = short
263 self.hexfunc = short
264 if ui is out:
264 if ui is out:
265 self._write = ui.write
265 self._write = ui.write
266 else:
266 else:
267 self._write = lambda s, **opts: out.write(s)
267 self._write = lambda s, **opts: out.write(s)
268 def startitem(self):
268 def startitem(self):
269 pass
269 pass
270 def data(self, **data):
270 def data(self, **data):
271 pass
271 pass
272 def write(self, fields, deftext, *fielddata, **opts):
272 def write(self, fields, deftext, *fielddata, **opts):
273 self._write(deftext % fielddata, **opts)
273 self._write(deftext % fielddata, **opts)
274 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
274 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
275 '''do conditional write'''
275 '''do conditional write'''
276 if cond:
276 if cond:
277 self._write(deftext % fielddata, **opts)
277 self._write(deftext % fielddata, **opts)
278 def plain(self, text, **opts):
278 def plain(self, text, **opts):
279 self._write(text, **opts)
279 self._write(text, **opts)
280 def isplain(self):
280 def isplain(self):
281 return True
281 return True
282 def nested(self, field):
282 def nested(self, field):
283 # nested data will be directly written to ui
283 # nested data will be directly written to ui
284 return self
284 return self
285 def end(self):
285 def end(self):
286 pass
286 pass
287
287
288 class debugformatter(baseformatter):
288 class debugformatter(baseformatter):
289 def __init__(self, ui, out, topic, opts):
289 def __init__(self, ui, out, topic, opts):
290 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
290 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
291 self._out = out
291 self._out = out
292 self._out.write("%s = [\n" % self._topic)
292 self._out.write("%s = [\n" % self._topic)
293 def _showitem(self):
293 def _showitem(self):
294 self._out.write(' %s,\n' % pycompat.sysbytes(repr(self._item)))
294 self._out.write(' %s,\n' % pycompat.byterepr(self._item))
295 def end(self):
295 def end(self):
296 baseformatter.end(self)
296 baseformatter.end(self)
297 self._out.write("]\n")
297 self._out.write("]\n")
298
298
299 class pickleformatter(baseformatter):
299 class pickleformatter(baseformatter):
300 def __init__(self, ui, out, topic, opts):
300 def __init__(self, ui, out, topic, opts):
301 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
301 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
302 self._out = out
302 self._out = out
303 self._data = []
303 self._data = []
304 def _showitem(self):
304 def _showitem(self):
305 self._data.append(self._item)
305 self._data.append(self._item)
306 def end(self):
306 def end(self):
307 baseformatter.end(self)
307 baseformatter.end(self)
308 self._out.write(pickle.dumps(self._data))
308 self._out.write(pickle.dumps(self._data))
309
309
310 class jsonformatter(baseformatter):
310 class jsonformatter(baseformatter):
311 def __init__(self, ui, out, topic, opts):
311 def __init__(self, ui, out, topic, opts):
312 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
312 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
313 self._out = out
313 self._out = out
314 self._out.write("[")
314 self._out.write("[")
315 self._first = True
315 self._first = True
316 def _showitem(self):
316 def _showitem(self):
317 if self._first:
317 if self._first:
318 self._first = False
318 self._first = False
319 else:
319 else:
320 self._out.write(",")
320 self._out.write(",")
321
321
322 self._out.write("\n {\n")
322 self._out.write("\n {\n")
323 first = True
323 first = True
324 for k, v in sorted(self._item.items()):
324 for k, v in sorted(self._item.items()):
325 if first:
325 if first:
326 first = False
326 first = False
327 else:
327 else:
328 self._out.write(",\n")
328 self._out.write(",\n")
329 u = templatefilters.json(v, paranoid=False)
329 u = templatefilters.json(v, paranoid=False)
330 self._out.write(' "%s": %s' % (k, u))
330 self._out.write(' "%s": %s' % (k, u))
331 self._out.write("\n }")
331 self._out.write("\n }")
332 def end(self):
332 def end(self):
333 baseformatter.end(self)
333 baseformatter.end(self)
334 self._out.write("\n]\n")
334 self._out.write("\n]\n")
335
335
336 class _templateconverter(object):
336 class _templateconverter(object):
337 '''convert non-primitive data types to be processed by templater'''
337 '''convert non-primitive data types to be processed by templater'''
338
338
339 storecontext = True
339 storecontext = True
340
340
341 @staticmethod
341 @staticmethod
342 def formatdate(date, fmt):
342 def formatdate(date, fmt):
343 '''return date tuple'''
343 '''return date tuple'''
344 return date
344 return date
345 @staticmethod
345 @staticmethod
346 def formatdict(data, key, value, fmt, sep):
346 def formatdict(data, key, value, fmt, sep):
347 '''build object that can be evaluated as either plain string or dict'''
347 '''build object that can be evaluated as either plain string or dict'''
348 data = util.sortdict(_iteritems(data))
348 data = util.sortdict(_iteritems(data))
349 def f():
349 def f():
350 yield _plainconverter.formatdict(data, key, value, fmt, sep)
350 yield _plainconverter.formatdict(data, key, value, fmt, sep)
351 return templatekw.hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
351 return templatekw.hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
352 @staticmethod
352 @staticmethod
353 def formatlist(data, name, fmt, sep):
353 def formatlist(data, name, fmt, sep):
354 '''build object that can be evaluated as either plain string or list'''
354 '''build object that can be evaluated as either plain string or list'''
355 data = list(data)
355 data = list(data)
356 def f():
356 def f():
357 yield _plainconverter.formatlist(data, name, fmt, sep)
357 yield _plainconverter.formatlist(data, name, fmt, sep)
358 return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f)
358 return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f)
359
359
360 class templateformatter(baseformatter):
360 class templateformatter(baseformatter):
361 def __init__(self, ui, out, topic, opts):
361 def __init__(self, ui, out, topic, opts):
362 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
362 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
363 self._out = out
363 self._out = out
364 spec = lookuptemplate(ui, topic, opts.get('template', ''))
364 spec = lookuptemplate(ui, topic, opts.get('template', ''))
365 self._tref = spec.ref
365 self._tref = spec.ref
366 self._t = loadtemplater(ui, spec, defaults=templatekw.keywords,
366 self._t = loadtemplater(ui, spec, defaults=templatekw.keywords,
367 resources=templateresources(ui),
367 resources=templateresources(ui),
368 cache=templatekw.defaulttempl)
368 cache=templatekw.defaulttempl)
369 self._parts = templatepartsmap(spec, self._t,
369 self._parts = templatepartsmap(spec, self._t,
370 ['docheader', 'docfooter', 'separator'])
370 ['docheader', 'docfooter', 'separator'])
371 self._counter = itertools.count()
371 self._counter = itertools.count()
372 self._renderitem('docheader', {})
372 self._renderitem('docheader', {})
373
373
374 def _showitem(self):
374 def _showitem(self):
375 item = self._item.copy()
375 item = self._item.copy()
376 item['index'] = index = next(self._counter)
376 item['index'] = index = next(self._counter)
377 if index > 0:
377 if index > 0:
378 self._renderitem('separator', {})
378 self._renderitem('separator', {})
379 self._renderitem(self._tref, item)
379 self._renderitem(self._tref, item)
380
380
381 def _renderitem(self, part, item):
381 def _renderitem(self, part, item):
382 if part not in self._parts:
382 if part not in self._parts:
383 return
383 return
384 ref = self._parts[part]
384 ref = self._parts[part]
385
385
386 # TODO: add support for filectx. probably each template keyword or
386 # TODO: add support for filectx. probably each template keyword or
387 # function will have to declare dependent resources. e.g.
387 # function will have to declare dependent resources. e.g.
388 # @templatekeyword(..., requires=('ctx',))
388 # @templatekeyword(..., requires=('ctx',))
389 props = {}
389 props = {}
390 # explicitly-defined fields precede templatekw
390 # explicitly-defined fields precede templatekw
391 props.update(item)
391 props.update(item)
392 if 'ctx' in item:
392 if 'ctx' in item:
393 # but template resources must be always available
393 # but template resources must be always available
394 props['repo'] = props['ctx'].repo()
394 props['repo'] = props['ctx'].repo()
395 props['revcache'] = {}
395 props['revcache'] = {}
396 props = pycompat.strkwargs(props)
396 props = pycompat.strkwargs(props)
397 g = self._t(ref, **props)
397 g = self._t(ref, **props)
398 self._out.write(templater.stringify(g))
398 self._out.write(templater.stringify(g))
399
399
400 def end(self):
400 def end(self):
401 baseformatter.end(self)
401 baseformatter.end(self)
402 self._renderitem('docfooter', {})
402 self._renderitem('docfooter', {})
403
403
404 templatespec = collections.namedtuple(r'templatespec',
404 templatespec = collections.namedtuple(r'templatespec',
405 r'ref tmpl mapfile')
405 r'ref tmpl mapfile')
406
406
407 def lookuptemplate(ui, topic, tmpl):
407 def lookuptemplate(ui, topic, tmpl):
408 """Find the template matching the given -T/--template spec 'tmpl'
408 """Find the template matching the given -T/--template spec 'tmpl'
409
409
410 'tmpl' can be any of the following:
410 'tmpl' can be any of the following:
411
411
412 - a literal template (e.g. '{rev}')
412 - a literal template (e.g. '{rev}')
413 - a map-file name or path (e.g. 'changelog')
413 - a map-file name or path (e.g. 'changelog')
414 - a reference to [templates] in config file
414 - a reference to [templates] in config file
415 - a path to raw template file
415 - a path to raw template file
416
416
417 A map file defines a stand-alone template environment. If a map file
417 A map file defines a stand-alone template environment. If a map file
418 selected, all templates defined in the file will be loaded, and the
418 selected, all templates defined in the file will be loaded, and the
419 template matching the given topic will be rendered. Aliases won't be
419 template matching the given topic will be rendered. Aliases won't be
420 loaded from user config, but from the map file.
420 loaded from user config, but from the map file.
421
421
422 If no map file selected, all templates in [templates] section will be
422 If no map file selected, all templates in [templates] section will be
423 available as well as aliases in [templatealias].
423 available as well as aliases in [templatealias].
424 """
424 """
425
425
426 # looks like a literal template?
426 # looks like a literal template?
427 if '{' in tmpl:
427 if '{' in tmpl:
428 return templatespec('', tmpl, None)
428 return templatespec('', tmpl, None)
429
429
430 # perhaps a stock style?
430 # perhaps a stock style?
431 if not os.path.split(tmpl)[0]:
431 if not os.path.split(tmpl)[0]:
432 mapname = (templater.templatepath('map-cmdline.' + tmpl)
432 mapname = (templater.templatepath('map-cmdline.' + tmpl)
433 or templater.templatepath(tmpl))
433 or templater.templatepath(tmpl))
434 if mapname and os.path.isfile(mapname):
434 if mapname and os.path.isfile(mapname):
435 return templatespec(topic, None, mapname)
435 return templatespec(topic, None, mapname)
436
436
437 # perhaps it's a reference to [templates]
437 # perhaps it's a reference to [templates]
438 if ui.config('templates', tmpl):
438 if ui.config('templates', tmpl):
439 return templatespec(tmpl, None, None)
439 return templatespec(tmpl, None, None)
440
440
441 if tmpl == 'list':
441 if tmpl == 'list':
442 ui.write(_("available styles: %s\n") % templater.stylelist())
442 ui.write(_("available styles: %s\n") % templater.stylelist())
443 raise error.Abort(_("specify a template"))
443 raise error.Abort(_("specify a template"))
444
444
445 # perhaps it's a path to a map or a template
445 # perhaps it's a path to a map or a template
446 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
446 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
447 # is it a mapfile for a style?
447 # is it a mapfile for a style?
448 if os.path.basename(tmpl).startswith("map-"):
448 if os.path.basename(tmpl).startswith("map-"):
449 return templatespec(topic, None, os.path.realpath(tmpl))
449 return templatespec(topic, None, os.path.realpath(tmpl))
450 with util.posixfile(tmpl, 'rb') as f:
450 with util.posixfile(tmpl, 'rb') as f:
451 tmpl = f.read()
451 tmpl = f.read()
452 return templatespec('', tmpl, None)
452 return templatespec('', tmpl, None)
453
453
454 # constant string?
454 # constant string?
455 return templatespec('', tmpl, None)
455 return templatespec('', tmpl, None)
456
456
457 def templatepartsmap(spec, t, partnames):
457 def templatepartsmap(spec, t, partnames):
458 """Create a mapping of {part: ref}"""
458 """Create a mapping of {part: ref}"""
459 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
459 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
460 if spec.mapfile:
460 if spec.mapfile:
461 partsmap.update((p, p) for p in partnames if p in t)
461 partsmap.update((p, p) for p in partnames if p in t)
462 elif spec.ref:
462 elif spec.ref:
463 for part in partnames:
463 for part in partnames:
464 ref = '%s:%s' % (spec.ref, part) # select config sub-section
464 ref = '%s:%s' % (spec.ref, part) # select config sub-section
465 if ref in t:
465 if ref in t:
466 partsmap[part] = ref
466 partsmap[part] = ref
467 return partsmap
467 return partsmap
468
468
469 def loadtemplater(ui, spec, defaults=None, resources=None, cache=None):
469 def loadtemplater(ui, spec, defaults=None, resources=None, cache=None):
470 """Create a templater from either a literal template or loading from
470 """Create a templater from either a literal template or loading from
471 a map file"""
471 a map file"""
472 assert not (spec.tmpl and spec.mapfile)
472 assert not (spec.tmpl and spec.mapfile)
473 if spec.mapfile:
473 if spec.mapfile:
474 frommapfile = templater.templater.frommapfile
474 frommapfile = templater.templater.frommapfile
475 return frommapfile(spec.mapfile, defaults=defaults, resources=resources,
475 return frommapfile(spec.mapfile, defaults=defaults, resources=resources,
476 cache=cache)
476 cache=cache)
477 return maketemplater(ui, spec.tmpl, defaults=defaults, resources=resources,
477 return maketemplater(ui, spec.tmpl, defaults=defaults, resources=resources,
478 cache=cache)
478 cache=cache)
479
479
480 def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None):
480 def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None):
481 """Create a templater from a string template 'tmpl'"""
481 """Create a templater from a string template 'tmpl'"""
482 aliases = ui.configitems('templatealias')
482 aliases = ui.configitems('templatealias')
483 t = templater.templater(defaults=defaults, resources=resources,
483 t = templater.templater(defaults=defaults, resources=resources,
484 cache=cache, aliases=aliases)
484 cache=cache, aliases=aliases)
485 t.cache.update((k, templater.unquotestring(v))
485 t.cache.update((k, templater.unquotestring(v))
486 for k, v in ui.configitems('templates'))
486 for k, v in ui.configitems('templates'))
487 if tmpl:
487 if tmpl:
488 t.cache[''] = tmpl
488 t.cache[''] = tmpl
489 return t
489 return t
490
490
491 def templateresources(ui, repo=None):
491 def templateresources(ui, repo=None):
492 """Create a dict of template resources designed for the default templatekw
492 """Create a dict of template resources designed for the default templatekw
493 and function"""
493 and function"""
494 return {
494 return {
495 'cache': {}, # for templatekw/funcs to store reusable data
495 'cache': {}, # for templatekw/funcs to store reusable data
496 'ctx': None,
496 'ctx': None,
497 'repo': repo,
497 'repo': repo,
498 'revcache': None, # per-ctx cache; set later
498 'revcache': None, # per-ctx cache; set later
499 'ui': ui,
499 'ui': ui,
500 }
500 }
501
501
502 def formatter(ui, out, topic, opts):
502 def formatter(ui, out, topic, opts):
503 template = opts.get("template", "")
503 template = opts.get("template", "")
504 if template == "json":
504 if template == "json":
505 return jsonformatter(ui, out, topic, opts)
505 return jsonformatter(ui, out, topic, opts)
506 elif template == "pickle":
506 elif template == "pickle":
507 return pickleformatter(ui, out, topic, opts)
507 return pickleformatter(ui, out, topic, opts)
508 elif template == "debug":
508 elif template == "debug":
509 return debugformatter(ui, out, topic, opts)
509 return debugformatter(ui, out, topic, opts)
510 elif template != "":
510 elif template != "":
511 return templateformatter(ui, out, topic, opts)
511 return templateformatter(ui, out, topic, opts)
512 # developer config: ui.formatdebug
512 # developer config: ui.formatdebug
513 elif ui.configbool('ui', 'formatdebug'):
513 elif ui.configbool('ui', 'formatdebug'):
514 return debugformatter(ui, out, topic, opts)
514 return debugformatter(ui, out, topic, opts)
515 # deprecated config: ui.formatjson
515 # deprecated config: ui.formatjson
516 elif ui.configbool('ui', 'formatjson'):
516 elif ui.configbool('ui', 'formatjson'):
517 return jsonformatter(ui, out, topic, opts)
517 return jsonformatter(ui, out, topic, opts)
518 return plainformatter(ui, out, topic, opts)
518 return plainformatter(ui, out, topic, opts)
519
519
520 @contextlib.contextmanager
520 @contextlib.contextmanager
521 def openformatter(ui, filename, topic, opts):
521 def openformatter(ui, filename, topic, opts):
522 """Create a formatter that writes outputs to the specified file
522 """Create a formatter that writes outputs to the specified file
523
523
524 Must be invoked using the 'with' statement.
524 Must be invoked using the 'with' statement.
525 """
525 """
526 with util.posixfile(filename, 'wb') as out:
526 with util.posixfile(filename, 'wb') as out:
527 with formatter(ui, out, topic, opts) as fm:
527 with formatter(ui, out, topic, opts) as fm:
528 yield fm
528 yield fm
529
529
530 @contextlib.contextmanager
530 @contextlib.contextmanager
531 def _neverending(fm):
531 def _neverending(fm):
532 yield fm
532 yield fm
533
533
534 def maybereopen(fm, filename, opts):
534 def maybereopen(fm, filename, opts):
535 """Create a formatter backed by file if filename specified, else return
535 """Create a formatter backed by file if filename specified, else return
536 the given formatter
536 the given formatter
537
537
538 Must be invoked using the 'with' statement. This will never call fm.end()
538 Must be invoked using the 'with' statement. This will never call fm.end()
539 of the given formatter.
539 of the given formatter.
540 """
540 """
541 if filename:
541 if filename:
542 return openformatter(fm._ui, filename, fm._topic, opts)
542 return openformatter(fm._ui, filename, fm._topic, opts)
543 else:
543 else:
544 return _neverending(fm)
544 return _neverending(fm)
@@ -1,351 +1,353 b''
1 # pycompat.py - portability shim for python 3
1 # pycompat.py - portability shim for python 3
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 """Mercurial portability shim for python 3.
6 """Mercurial portability shim for python 3.
7
7
8 This contains aliases to hide python version-specific details from the core.
8 This contains aliases to hide python version-specific details from the core.
9 """
9 """
10
10
11 from __future__ import absolute_import
11 from __future__ import absolute_import
12
12
13 import getopt
13 import getopt
14 import inspect
14 import inspect
15 import os
15 import os
16 import shlex
16 import shlex
17 import sys
17 import sys
18
18
19 ispy3 = (sys.version_info[0] >= 3)
19 ispy3 = (sys.version_info[0] >= 3)
20 ispypy = (r'__pypy__' in sys.builtin_module_names)
20 ispypy = (r'__pypy__' in sys.builtin_module_names)
21
21
22 if not ispy3:
22 if not ispy3:
23 import cookielib
23 import cookielib
24 import cPickle as pickle
24 import cPickle as pickle
25 import httplib
25 import httplib
26 import Queue as _queue
26 import Queue as _queue
27 import SocketServer as socketserver
27 import SocketServer as socketserver
28 import xmlrpclib
28 import xmlrpclib
29 else:
29 else:
30 import http.cookiejar as cookielib
30 import http.cookiejar as cookielib
31 import http.client as httplib
31 import http.client as httplib
32 import pickle
32 import pickle
33 import queue as _queue
33 import queue as _queue
34 import socketserver
34 import socketserver
35 import xmlrpc.client as xmlrpclib
35 import xmlrpc.client as xmlrpclib
36
36
37 empty = _queue.Empty
37 empty = _queue.Empty
38 queue = _queue.Queue
38 queue = _queue.Queue
39
39
40 def identity(a):
40 def identity(a):
41 return a
41 return a
42
42
43 if ispy3:
43 if ispy3:
44 import builtins
44 import builtins
45 import functools
45 import functools
46 import io
46 import io
47 import struct
47 import struct
48
48
49 fsencode = os.fsencode
49 fsencode = os.fsencode
50 fsdecode = os.fsdecode
50 fsdecode = os.fsdecode
51 oslinesep = os.linesep.encode('ascii')
51 oslinesep = os.linesep.encode('ascii')
52 osname = os.name.encode('ascii')
52 osname = os.name.encode('ascii')
53 ospathsep = os.pathsep.encode('ascii')
53 ospathsep = os.pathsep.encode('ascii')
54 ossep = os.sep.encode('ascii')
54 ossep = os.sep.encode('ascii')
55 osaltsep = os.altsep
55 osaltsep = os.altsep
56 if osaltsep:
56 if osaltsep:
57 osaltsep = osaltsep.encode('ascii')
57 osaltsep = osaltsep.encode('ascii')
58 # os.getcwd() on Python 3 returns string, but it has os.getcwdb() which
58 # os.getcwd() on Python 3 returns string, but it has os.getcwdb() which
59 # returns bytes.
59 # returns bytes.
60 getcwd = os.getcwdb
60 getcwd = os.getcwdb
61 sysplatform = sys.platform.encode('ascii')
61 sysplatform = sys.platform.encode('ascii')
62 sysexecutable = sys.executable
62 sysexecutable = sys.executable
63 if sysexecutable:
63 if sysexecutable:
64 sysexecutable = os.fsencode(sysexecutable)
64 sysexecutable = os.fsencode(sysexecutable)
65 stringio = io.BytesIO
65 stringio = io.BytesIO
66 maplist = lambda *args: list(map(*args))
66 maplist = lambda *args: list(map(*args))
67 ziplist = lambda *args: list(zip(*args))
67 ziplist = lambda *args: list(zip(*args))
68 rawinput = input
68 rawinput = input
69 getargspec = inspect.getfullargspec
69 getargspec = inspect.getfullargspec
70
70
71 # TODO: .buffer might not exist if std streams were replaced; we'll need
71 # TODO: .buffer might not exist if std streams were replaced; we'll need
72 # a silly wrapper to make a bytes stream backed by a unicode one.
72 # a silly wrapper to make a bytes stream backed by a unicode one.
73 stdin = sys.stdin.buffer
73 stdin = sys.stdin.buffer
74 stdout = sys.stdout.buffer
74 stdout = sys.stdout.buffer
75 stderr = sys.stderr.buffer
75 stderr = sys.stderr.buffer
76
76
77 # Since Python 3 converts argv to wchar_t type by Py_DecodeLocale() on Unix,
77 # Since Python 3 converts argv to wchar_t type by Py_DecodeLocale() on Unix,
78 # we can use os.fsencode() to get back bytes argv.
78 # we can use os.fsencode() to get back bytes argv.
79 #
79 #
80 # https://hg.python.org/cpython/file/v3.5.1/Programs/python.c#l55
80 # https://hg.python.org/cpython/file/v3.5.1/Programs/python.c#l55
81 #
81 #
82 # TODO: On Windows, the native argv is wchar_t, so we'll need a different
82 # TODO: On Windows, the native argv is wchar_t, so we'll need a different
83 # workaround to simulate the Python 2 (i.e. ANSI Win32 API) behavior.
83 # workaround to simulate the Python 2 (i.e. ANSI Win32 API) behavior.
84 if getattr(sys, 'argv', None) is not None:
84 if getattr(sys, 'argv', None) is not None:
85 sysargv = list(map(os.fsencode, sys.argv))
85 sysargv = list(map(os.fsencode, sys.argv))
86
86
87 bytechr = struct.Struct('>B').pack
87 bytechr = struct.Struct('>B').pack
88 byterepr = b'%r'.__mod__
88
89
89 class bytestr(bytes):
90 class bytestr(bytes):
90 """A bytes which mostly acts as a Python 2 str
91 """A bytes which mostly acts as a Python 2 str
91
92
92 >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1)
93 >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1)
93 ('', 'foo', 'ascii', '1')
94 ('', 'foo', 'ascii', '1')
94 >>> s = bytestr(b'foo')
95 >>> s = bytestr(b'foo')
95 >>> assert s is bytestr(s)
96 >>> assert s is bytestr(s)
96
97
97 __bytes__() should be called if provided:
98 __bytes__() should be called if provided:
98
99
99 >>> class bytesable(object):
100 >>> class bytesable(object):
100 ... def __bytes__(self):
101 ... def __bytes__(self):
101 ... return b'bytes'
102 ... return b'bytes'
102 >>> bytestr(bytesable())
103 >>> bytestr(bytesable())
103 'bytes'
104 'bytes'
104
105
105 There's no implicit conversion from non-ascii str as its encoding is
106 There's no implicit conversion from non-ascii str as its encoding is
106 unknown:
107 unknown:
107
108
108 >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS
109 >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS
109 Traceback (most recent call last):
110 Traceback (most recent call last):
110 ...
111 ...
111 UnicodeEncodeError: ...
112 UnicodeEncodeError: ...
112
113
113 Comparison between bytestr and bytes should work:
114 Comparison between bytestr and bytes should work:
114
115
115 >>> assert bytestr(b'foo') == b'foo'
116 >>> assert bytestr(b'foo') == b'foo'
116 >>> assert b'foo' == bytestr(b'foo')
117 >>> assert b'foo' == bytestr(b'foo')
117 >>> assert b'f' in bytestr(b'foo')
118 >>> assert b'f' in bytestr(b'foo')
118 >>> assert bytestr(b'f') in b'foo'
119 >>> assert bytestr(b'f') in b'foo'
119
120
120 Sliced elements should be bytes, not integer:
121 Sliced elements should be bytes, not integer:
121
122
122 >>> s[1], s[:2]
123 >>> s[1], s[:2]
123 (b'o', b'fo')
124 (b'o', b'fo')
124 >>> list(s), list(reversed(s))
125 >>> list(s), list(reversed(s))
125 ([b'f', b'o', b'o'], [b'o', b'o', b'f'])
126 ([b'f', b'o', b'o'], [b'o', b'o', b'f'])
126
127
127 As bytestr type isn't propagated across operations, you need to cast
128 As bytestr type isn't propagated across operations, you need to cast
128 bytes to bytestr explicitly:
129 bytes to bytestr explicitly:
129
130
130 >>> s = bytestr(b'foo').upper()
131 >>> s = bytestr(b'foo').upper()
131 >>> t = bytestr(s)
132 >>> t = bytestr(s)
132 >>> s[0], t[0]
133 >>> s[0], t[0]
133 (70, b'F')
134 (70, b'F')
134
135
135 Be careful to not pass a bytestr object to a function which expects
136 Be careful to not pass a bytestr object to a function which expects
136 bytearray-like behavior.
137 bytearray-like behavior.
137
138
138 >>> t = bytes(t) # cast to bytes
139 >>> t = bytes(t) # cast to bytes
139 >>> assert type(t) is bytes
140 >>> assert type(t) is bytes
140 """
141 """
141
142
142 def __new__(cls, s=b''):
143 def __new__(cls, s=b''):
143 if isinstance(s, bytestr):
144 if isinstance(s, bytestr):
144 return s
145 return s
145 if (not isinstance(s, (bytes, bytearray))
146 if (not isinstance(s, (bytes, bytearray))
146 and not hasattr(s, u'__bytes__')): # hasattr-py3-only
147 and not hasattr(s, u'__bytes__')): # hasattr-py3-only
147 s = str(s).encode(u'ascii')
148 s = str(s).encode(u'ascii')
148 return bytes.__new__(cls, s)
149 return bytes.__new__(cls, s)
149
150
150 def __getitem__(self, key):
151 def __getitem__(self, key):
151 s = bytes.__getitem__(self, key)
152 s = bytes.__getitem__(self, key)
152 if not isinstance(s, bytes):
153 if not isinstance(s, bytes):
153 s = bytechr(s)
154 s = bytechr(s)
154 return s
155 return s
155
156
156 def __iter__(self):
157 def __iter__(self):
157 return iterbytestr(bytes.__iter__(self))
158 return iterbytestr(bytes.__iter__(self))
158
159
159 def __repr__(self):
160 def __repr__(self):
160 return bytes.__repr__(self)[1:] # drop b''
161 return bytes.__repr__(self)[1:] # drop b''
161
162
162 def iterbytestr(s):
163 def iterbytestr(s):
163 """Iterate bytes as if it were a str object of Python 2"""
164 """Iterate bytes as if it were a str object of Python 2"""
164 return map(bytechr, s)
165 return map(bytechr, s)
165
166
166 def maybebytestr(s):
167 def maybebytestr(s):
167 """Promote bytes to bytestr"""
168 """Promote bytes to bytestr"""
168 if isinstance(s, bytes):
169 if isinstance(s, bytes):
169 return bytestr(s)
170 return bytestr(s)
170 return s
171 return s
171
172
172 def sysbytes(s):
173 def sysbytes(s):
173 """Convert an internal str (e.g. keyword, __doc__) back to bytes
174 """Convert an internal str (e.g. keyword, __doc__) back to bytes
174
175
175 This never raises UnicodeEncodeError, but only ASCII characters
176 This never raises UnicodeEncodeError, but only ASCII characters
176 can be round-trip by sysstr(sysbytes(s)).
177 can be round-trip by sysstr(sysbytes(s)).
177 """
178 """
178 return s.encode(u'utf-8')
179 return s.encode(u'utf-8')
179
180
180 def sysstr(s):
181 def sysstr(s):
181 """Return a keyword str to be passed to Python functions such as
182 """Return a keyword str to be passed to Python functions such as
182 getattr() and str.encode()
183 getattr() and str.encode()
183
184
184 This never raises UnicodeDecodeError. Non-ascii characters are
185 This never raises UnicodeDecodeError. Non-ascii characters are
185 considered invalid and mapped to arbitrary but unique code points
186 considered invalid and mapped to arbitrary but unique code points
186 such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
187 such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
187 """
188 """
188 if isinstance(s, builtins.str):
189 if isinstance(s, builtins.str):
189 return s
190 return s
190 return s.decode(u'latin-1')
191 return s.decode(u'latin-1')
191
192
192 def strurl(url):
193 def strurl(url):
193 """Converts a bytes url back to str"""
194 """Converts a bytes url back to str"""
194 return url.decode(u'ascii')
195 return url.decode(u'ascii')
195
196
196 def bytesurl(url):
197 def bytesurl(url):
197 """Converts a str url to bytes by encoding in ascii"""
198 """Converts a str url to bytes by encoding in ascii"""
198 return url.encode(u'ascii')
199 return url.encode(u'ascii')
199
200
200 def raisewithtb(exc, tb):
201 def raisewithtb(exc, tb):
201 """Raise exception with the given traceback"""
202 """Raise exception with the given traceback"""
202 raise exc.with_traceback(tb)
203 raise exc.with_traceback(tb)
203
204
204 def getdoc(obj):
205 def getdoc(obj):
205 """Get docstring as bytes; may be None so gettext() won't confuse it
206 """Get docstring as bytes; may be None so gettext() won't confuse it
206 with _('')"""
207 with _('')"""
207 doc = getattr(obj, u'__doc__', None)
208 doc = getattr(obj, u'__doc__', None)
208 if doc is None:
209 if doc is None:
209 return doc
210 return doc
210 return sysbytes(doc)
211 return sysbytes(doc)
211
212
212 def _wrapattrfunc(f):
213 def _wrapattrfunc(f):
213 @functools.wraps(f)
214 @functools.wraps(f)
214 def w(object, name, *args):
215 def w(object, name, *args):
215 return f(object, sysstr(name), *args)
216 return f(object, sysstr(name), *args)
216 return w
217 return w
217
218
218 # these wrappers are automagically imported by hgloader
219 # these wrappers are automagically imported by hgloader
219 delattr = _wrapattrfunc(builtins.delattr)
220 delattr = _wrapattrfunc(builtins.delattr)
220 getattr = _wrapattrfunc(builtins.getattr)
221 getattr = _wrapattrfunc(builtins.getattr)
221 hasattr = _wrapattrfunc(builtins.hasattr)
222 hasattr = _wrapattrfunc(builtins.hasattr)
222 setattr = _wrapattrfunc(builtins.setattr)
223 setattr = _wrapattrfunc(builtins.setattr)
223 xrange = builtins.range
224 xrange = builtins.range
224 unicode = str
225 unicode = str
225
226
226 def open(name, mode='r', buffering=-1):
227 def open(name, mode='r', buffering=-1):
227 return builtins.open(name, sysstr(mode), buffering)
228 return builtins.open(name, sysstr(mode), buffering)
228
229
229 def _getoptbwrapper(orig, args, shortlist, namelist):
230 def _getoptbwrapper(orig, args, shortlist, namelist):
230 """
231 """
231 Takes bytes arguments, converts them to unicode, pass them to
232 Takes bytes arguments, converts them to unicode, pass them to
232 getopt.getopt(), convert the returned values back to bytes and then
233 getopt.getopt(), convert the returned values back to bytes and then
233 return them for Python 3 compatibility as getopt.getopt() don't accepts
234 return them for Python 3 compatibility as getopt.getopt() don't accepts
234 bytes on Python 3.
235 bytes on Python 3.
235 """
236 """
236 args = [a.decode('latin-1') for a in args]
237 args = [a.decode('latin-1') for a in args]
237 shortlist = shortlist.decode('latin-1')
238 shortlist = shortlist.decode('latin-1')
238 namelist = [a.decode('latin-1') for a in namelist]
239 namelist = [a.decode('latin-1') for a in namelist]
239 opts, args = orig(args, shortlist, namelist)
240 opts, args = orig(args, shortlist, namelist)
240 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1'))
241 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1'))
241 for a in opts]
242 for a in opts]
242 args = [a.encode('latin-1') for a in args]
243 args = [a.encode('latin-1') for a in args]
243 return opts, args
244 return opts, args
244
245
245 def strkwargs(dic):
246 def strkwargs(dic):
246 """
247 """
247 Converts the keys of a python dictonary to str i.e. unicodes so that
248 Converts the keys of a python dictonary to str i.e. unicodes so that
248 they can be passed as keyword arguments as dictonaries with bytes keys
249 they can be passed as keyword arguments as dictonaries with bytes keys
249 can't be passed as keyword arguments to functions on Python 3.
250 can't be passed as keyword arguments to functions on Python 3.
250 """
251 """
251 dic = dict((k.decode('latin-1'), v) for k, v in dic.iteritems())
252 dic = dict((k.decode('latin-1'), v) for k, v in dic.iteritems())
252 return dic
253 return dic
253
254
254 def byteskwargs(dic):
255 def byteskwargs(dic):
255 """
256 """
256 Converts keys of python dictonaries to bytes as they were converted to
257 Converts keys of python dictonaries to bytes as they were converted to
257 str to pass that dictonary as a keyword argument on Python 3.
258 str to pass that dictonary as a keyword argument on Python 3.
258 """
259 """
259 dic = dict((k.encode('latin-1'), v) for k, v in dic.iteritems())
260 dic = dict((k.encode('latin-1'), v) for k, v in dic.iteritems())
260 return dic
261 return dic
261
262
262 # TODO: handle shlex.shlex().
263 # TODO: handle shlex.shlex().
263 def shlexsplit(s):
264 def shlexsplit(s):
264 """
265 """
265 Takes bytes argument, convert it to str i.e. unicodes, pass that into
266 Takes bytes argument, convert it to str i.e. unicodes, pass that into
266 shlex.split(), convert the returned value to bytes and return that for
267 shlex.split(), convert the returned value to bytes and return that for
267 Python 3 compatibility as shelx.split() don't accept bytes on Python 3.
268 Python 3 compatibility as shelx.split() don't accept bytes on Python 3.
268 """
269 """
269 ret = shlex.split(s.decode('latin-1'))
270 ret = shlex.split(s.decode('latin-1'))
270 return [a.encode('latin-1') for a in ret]
271 return [a.encode('latin-1') for a in ret]
271
272
272 def emailparser(*args, **kwargs):
273 def emailparser(*args, **kwargs):
273 import email.parser
274 import email.parser
274 return email.parser.BytesParser(*args, **kwargs)
275 return email.parser.BytesParser(*args, **kwargs)
275
276
276 else:
277 else:
277 import cStringIO
278 import cStringIO
278
279
279 bytechr = chr
280 bytechr = chr
281 byterepr = repr
280 bytestr = str
282 bytestr = str
281 iterbytestr = iter
283 iterbytestr = iter
282 maybebytestr = identity
284 maybebytestr = identity
283 sysbytes = identity
285 sysbytes = identity
284 sysstr = identity
286 sysstr = identity
285 strurl = identity
287 strurl = identity
286 bytesurl = identity
288 bytesurl = identity
287
289
288 # this can't be parsed on Python 3
290 # this can't be parsed on Python 3
289 exec('def raisewithtb(exc, tb):\n'
291 exec('def raisewithtb(exc, tb):\n'
290 ' raise exc, None, tb\n')
292 ' raise exc, None, tb\n')
291
293
292 def fsencode(filename):
294 def fsencode(filename):
293 """
295 """
294 Partial backport from os.py in Python 3, which only accepts bytes.
296 Partial backport from os.py in Python 3, which only accepts bytes.
295 In Python 2, our paths should only ever be bytes, a unicode path
297 In Python 2, our paths should only ever be bytes, a unicode path
296 indicates a bug.
298 indicates a bug.
297 """
299 """
298 if isinstance(filename, str):
300 if isinstance(filename, str):
299 return filename
301 return filename
300 else:
302 else:
301 raise TypeError(
303 raise TypeError(
302 "expect str, not %s" % type(filename).__name__)
304 "expect str, not %s" % type(filename).__name__)
303
305
304 # In Python 2, fsdecode() has a very chance to receive bytes. So it's
306 # In Python 2, fsdecode() has a very chance to receive bytes. So it's
305 # better not to touch Python 2 part as it's already working fine.
307 # better not to touch Python 2 part as it's already working fine.
306 fsdecode = identity
308 fsdecode = identity
307
309
308 def getdoc(obj):
310 def getdoc(obj):
309 return getattr(obj, '__doc__', None)
311 return getattr(obj, '__doc__', None)
310
312
311 def _getoptbwrapper(orig, args, shortlist, namelist):
313 def _getoptbwrapper(orig, args, shortlist, namelist):
312 return orig(args, shortlist, namelist)
314 return orig(args, shortlist, namelist)
313
315
314 strkwargs = identity
316 strkwargs = identity
315 byteskwargs = identity
317 byteskwargs = identity
316
318
317 oslinesep = os.linesep
319 oslinesep = os.linesep
318 osname = os.name
320 osname = os.name
319 ospathsep = os.pathsep
321 ospathsep = os.pathsep
320 ossep = os.sep
322 ossep = os.sep
321 osaltsep = os.altsep
323 osaltsep = os.altsep
322 stdin = sys.stdin
324 stdin = sys.stdin
323 stdout = sys.stdout
325 stdout = sys.stdout
324 stderr = sys.stderr
326 stderr = sys.stderr
325 if getattr(sys, 'argv', None) is not None:
327 if getattr(sys, 'argv', None) is not None:
326 sysargv = sys.argv
328 sysargv = sys.argv
327 sysplatform = sys.platform
329 sysplatform = sys.platform
328 getcwd = os.getcwd
330 getcwd = os.getcwd
329 sysexecutable = sys.executable
331 sysexecutable = sys.executable
330 shlexsplit = shlex.split
332 shlexsplit = shlex.split
331 stringio = cStringIO.StringIO
333 stringio = cStringIO.StringIO
332 maplist = map
334 maplist = map
333 ziplist = zip
335 ziplist = zip
334 rawinput = raw_input
336 rawinput = raw_input
335 getargspec = inspect.getargspec
337 getargspec = inspect.getargspec
336
338
337 def emailparser(*args, **kwargs):
339 def emailparser(*args, **kwargs):
338 import email.parser
340 import email.parser
339 return email.parser.Parser(*args, **kwargs)
341 return email.parser.Parser(*args, **kwargs)
340
342
341 isjython = sysplatform.startswith('java')
343 isjython = sysplatform.startswith('java')
342
344
343 isdarwin = sysplatform == 'darwin'
345 isdarwin = sysplatform == 'darwin'
344 isposix = osname == 'posix'
346 isposix = osname == 'posix'
345 iswindows = osname == 'nt'
347 iswindows = osname == 'nt'
346
348
347 def getoptb(args, shortlist, namelist):
349 def getoptb(args, shortlist, namelist):
348 return _getoptbwrapper(getopt.getopt, args, shortlist, namelist)
350 return _getoptbwrapper(getopt.getopt, args, shortlist, namelist)
349
351
350 def gnugetoptb(args, shortlist, namelist):
352 def gnugetoptb(args, shortlist, namelist):
351 return _getoptbwrapper(getopt.gnu_getopt, args, shortlist, namelist)
353 return _getoptbwrapper(getopt.gnu_getopt, args, shortlist, namelist)
@@ -1,1145 +1,1145 b''
1 # smartset.py - data structure for revision set
1 # smartset.py - data structure for revision set
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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from . import (
10 from . import (
11 encoding,
11 encoding,
12 error,
12 error,
13 pycompat,
13 pycompat,
14 util,
14 util,
15 )
15 )
16
16
17 def _formatsetrepr(r):
17 def _formatsetrepr(r):
18 """Format an optional printable representation of a set
18 """Format an optional printable representation of a set
19
19
20 ======== =================================
20 ======== =================================
21 type(r) example
21 type(r) example
22 ======== =================================
22 ======== =================================
23 tuple ('<not %r>', other)
23 tuple ('<not %r>', other)
24 bytes '<branch closed>'
24 bytes '<branch closed>'
25 callable lambda: '<branch %r>' % sorted(b)
25 callable lambda: '<branch %r>' % sorted(b)
26 object other
26 object other
27 ======== =================================
27 ======== =================================
28 """
28 """
29 if r is None:
29 if r is None:
30 return ''
30 return ''
31 elif isinstance(r, tuple):
31 elif isinstance(r, tuple):
32 return r[0] % util.rapply(pycompat.maybebytestr, r[1:])
32 return r[0] % util.rapply(pycompat.maybebytestr, r[1:])
33 elif isinstance(r, bytes):
33 elif isinstance(r, bytes):
34 return r
34 return r
35 elif callable(r):
35 elif callable(r):
36 return r()
36 return r()
37 else:
37 else:
38 return pycompat.sysbytes(repr(r))
38 return pycompat.byterepr(r)
39
39
40 def _typename(o):
40 def _typename(o):
41 return pycompat.sysbytes(type(o).__name__).lstrip('_')
41 return pycompat.sysbytes(type(o).__name__).lstrip('_')
42
42
43 class abstractsmartset(object):
43 class abstractsmartset(object):
44
44
45 def __nonzero__(self):
45 def __nonzero__(self):
46 """True if the smartset is not empty"""
46 """True if the smartset is not empty"""
47 raise NotImplementedError()
47 raise NotImplementedError()
48
48
49 __bool__ = __nonzero__
49 __bool__ = __nonzero__
50
50
51 def __contains__(self, rev):
51 def __contains__(self, rev):
52 """provide fast membership testing"""
52 """provide fast membership testing"""
53 raise NotImplementedError()
53 raise NotImplementedError()
54
54
55 def __iter__(self):
55 def __iter__(self):
56 """iterate the set in the order it is supposed to be iterated"""
56 """iterate the set in the order it is supposed to be iterated"""
57 raise NotImplementedError()
57 raise NotImplementedError()
58
58
59 # Attributes containing a function to perform a fast iteration in a given
59 # Attributes containing a function to perform a fast iteration in a given
60 # direction. A smartset can have none, one, or both defined.
60 # direction. A smartset can have none, one, or both defined.
61 #
61 #
62 # Default value is None instead of a function returning None to avoid
62 # Default value is None instead of a function returning None to avoid
63 # initializing an iterator just for testing if a fast method exists.
63 # initializing an iterator just for testing if a fast method exists.
64 fastasc = None
64 fastasc = None
65 fastdesc = None
65 fastdesc = None
66
66
67 def isascending(self):
67 def isascending(self):
68 """True if the set will iterate in ascending order"""
68 """True if the set will iterate in ascending order"""
69 raise NotImplementedError()
69 raise NotImplementedError()
70
70
71 def isdescending(self):
71 def isdescending(self):
72 """True if the set will iterate in descending order"""
72 """True if the set will iterate in descending order"""
73 raise NotImplementedError()
73 raise NotImplementedError()
74
74
75 def istopo(self):
75 def istopo(self):
76 """True if the set will iterate in topographical order"""
76 """True if the set will iterate in topographical order"""
77 raise NotImplementedError()
77 raise NotImplementedError()
78
78
79 def min(self):
79 def min(self):
80 """return the minimum element in the set"""
80 """return the minimum element in the set"""
81 if self.fastasc is None:
81 if self.fastasc is None:
82 v = min(self)
82 v = min(self)
83 else:
83 else:
84 for v in self.fastasc():
84 for v in self.fastasc():
85 break
85 break
86 else:
86 else:
87 raise ValueError('arg is an empty sequence')
87 raise ValueError('arg is an empty sequence')
88 self.min = lambda: v
88 self.min = lambda: v
89 return v
89 return v
90
90
91 def max(self):
91 def max(self):
92 """return the maximum element in the set"""
92 """return the maximum element in the set"""
93 if self.fastdesc is None:
93 if self.fastdesc is None:
94 return max(self)
94 return max(self)
95 else:
95 else:
96 for v in self.fastdesc():
96 for v in self.fastdesc():
97 break
97 break
98 else:
98 else:
99 raise ValueError('arg is an empty sequence')
99 raise ValueError('arg is an empty sequence')
100 self.max = lambda: v
100 self.max = lambda: v
101 return v
101 return v
102
102
103 def first(self):
103 def first(self):
104 """return the first element in the set (user iteration perspective)
104 """return the first element in the set (user iteration perspective)
105
105
106 Return None if the set is empty"""
106 Return None if the set is empty"""
107 raise NotImplementedError()
107 raise NotImplementedError()
108
108
109 def last(self):
109 def last(self):
110 """return the last element in the set (user iteration perspective)
110 """return the last element in the set (user iteration perspective)
111
111
112 Return None if the set is empty"""
112 Return None if the set is empty"""
113 raise NotImplementedError()
113 raise NotImplementedError()
114
114
115 def __len__(self):
115 def __len__(self):
116 """return the length of the smartsets
116 """return the length of the smartsets
117
117
118 This can be expensive on smartset that could be lazy otherwise."""
118 This can be expensive on smartset that could be lazy otherwise."""
119 raise NotImplementedError()
119 raise NotImplementedError()
120
120
121 def reverse(self):
121 def reverse(self):
122 """reverse the expected iteration order"""
122 """reverse the expected iteration order"""
123 raise NotImplementedError()
123 raise NotImplementedError()
124
124
125 def sort(self, reverse=False):
125 def sort(self, reverse=False):
126 """get the set to iterate in an ascending or descending order"""
126 """get the set to iterate in an ascending or descending order"""
127 raise NotImplementedError()
127 raise NotImplementedError()
128
128
129 def __and__(self, other):
129 def __and__(self, other):
130 """Returns a new object with the intersection of the two collections.
130 """Returns a new object with the intersection of the two collections.
131
131
132 This is part of the mandatory API for smartset."""
132 This is part of the mandatory API for smartset."""
133 if isinstance(other, fullreposet):
133 if isinstance(other, fullreposet):
134 return self
134 return self
135 return self.filter(other.__contains__, condrepr=other, cache=False)
135 return self.filter(other.__contains__, condrepr=other, cache=False)
136
136
137 def __add__(self, other):
137 def __add__(self, other):
138 """Returns a new object with the union of the two collections.
138 """Returns a new object with the union of the two collections.
139
139
140 This is part of the mandatory API for smartset."""
140 This is part of the mandatory API for smartset."""
141 return addset(self, other)
141 return addset(self, other)
142
142
143 def __sub__(self, other):
143 def __sub__(self, other):
144 """Returns a new object with the substraction of the two collections.
144 """Returns a new object with the substraction of the two collections.
145
145
146 This is part of the mandatory API for smartset."""
146 This is part of the mandatory API for smartset."""
147 c = other.__contains__
147 c = other.__contains__
148 return self.filter(lambda r: not c(r), condrepr=('<not %r>', other),
148 return self.filter(lambda r: not c(r), condrepr=('<not %r>', other),
149 cache=False)
149 cache=False)
150
150
151 def filter(self, condition, condrepr=None, cache=True):
151 def filter(self, condition, condrepr=None, cache=True):
152 """Returns this smartset filtered by condition as a new smartset.
152 """Returns this smartset filtered by condition as a new smartset.
153
153
154 `condition` is a callable which takes a revision number and returns a
154 `condition` is a callable which takes a revision number and returns a
155 boolean. Optional `condrepr` provides a printable representation of
155 boolean. Optional `condrepr` provides a printable representation of
156 the given `condition`.
156 the given `condition`.
157
157
158 This is part of the mandatory API for smartset."""
158 This is part of the mandatory API for smartset."""
159 # builtin cannot be cached. but do not needs to
159 # builtin cannot be cached. but do not needs to
160 if cache and util.safehasattr(condition, 'func_code'):
160 if cache and util.safehasattr(condition, 'func_code'):
161 condition = util.cachefunc(condition)
161 condition = util.cachefunc(condition)
162 return filteredset(self, condition, condrepr)
162 return filteredset(self, condition, condrepr)
163
163
164 def slice(self, start, stop):
164 def slice(self, start, stop):
165 """Return new smartset that contains selected elements from this set"""
165 """Return new smartset that contains selected elements from this set"""
166 if start < 0 or stop < 0:
166 if start < 0 or stop < 0:
167 raise error.ProgrammingError('negative index not allowed')
167 raise error.ProgrammingError('negative index not allowed')
168 return self._slice(start, stop)
168 return self._slice(start, stop)
169
169
170 def _slice(self, start, stop):
170 def _slice(self, start, stop):
171 # sub classes may override this. start and stop must not be negative,
171 # sub classes may override this. start and stop must not be negative,
172 # but start > stop is allowed, which should be an empty set.
172 # but start > stop is allowed, which should be an empty set.
173 ys = []
173 ys = []
174 it = iter(self)
174 it = iter(self)
175 for x in xrange(start):
175 for x in xrange(start):
176 y = next(it, None)
176 y = next(it, None)
177 if y is None:
177 if y is None:
178 break
178 break
179 for x in xrange(stop - start):
179 for x in xrange(stop - start):
180 y = next(it, None)
180 y = next(it, None)
181 if y is None:
181 if y is None:
182 break
182 break
183 ys.append(y)
183 ys.append(y)
184 return baseset(ys, datarepr=('slice=%d:%d %r', start, stop, self))
184 return baseset(ys, datarepr=('slice=%d:%d %r', start, stop, self))
185
185
186 class baseset(abstractsmartset):
186 class baseset(abstractsmartset):
187 """Basic data structure that represents a revset and contains the basic
187 """Basic data structure that represents a revset and contains the basic
188 operation that it should be able to perform.
188 operation that it should be able to perform.
189
189
190 Every method in this class should be implemented by any smartset class.
190 Every method in this class should be implemented by any smartset class.
191
191
192 This class could be constructed by an (unordered) set, or an (ordered)
192 This class could be constructed by an (unordered) set, or an (ordered)
193 list-like object. If a set is provided, it'll be sorted lazily.
193 list-like object. If a set is provided, it'll be sorted lazily.
194
194
195 >>> x = [4, 0, 7, 6]
195 >>> x = [4, 0, 7, 6]
196 >>> y = [5, 6, 7, 3]
196 >>> y = [5, 6, 7, 3]
197
197
198 Construct by a set:
198 Construct by a set:
199 >>> xs = baseset(set(x))
199 >>> xs = baseset(set(x))
200 >>> ys = baseset(set(y))
200 >>> ys = baseset(set(y))
201 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
201 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
202 [[0, 4, 6, 7, 3, 5], [6, 7], [0, 4]]
202 [[0, 4, 6, 7, 3, 5], [6, 7], [0, 4]]
203 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
203 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
204 ['addset', 'baseset', 'baseset']
204 ['addset', 'baseset', 'baseset']
205
205
206 Construct by a list-like:
206 Construct by a list-like:
207 >>> xs = baseset(x)
207 >>> xs = baseset(x)
208 >>> ys = baseset(i for i in y)
208 >>> ys = baseset(i for i in y)
209 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
209 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
210 [[4, 0, 7, 6, 5, 3], [7, 6], [4, 0]]
210 [[4, 0, 7, 6, 5, 3], [7, 6], [4, 0]]
211 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
211 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
212 ['addset', 'filteredset', 'filteredset']
212 ['addset', 'filteredset', 'filteredset']
213
213
214 Populate "_set" fields in the lists so set optimization may be used:
214 Populate "_set" fields in the lists so set optimization may be used:
215 >>> [1 in xs, 3 in ys]
215 >>> [1 in xs, 3 in ys]
216 [False, True]
216 [False, True]
217
217
218 Without sort(), results won't be changed:
218 Without sort(), results won't be changed:
219 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
219 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
220 [[4, 0, 7, 6, 5, 3], [7, 6], [4, 0]]
220 [[4, 0, 7, 6, 5, 3], [7, 6], [4, 0]]
221 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
221 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
222 ['addset', 'filteredset', 'filteredset']
222 ['addset', 'filteredset', 'filteredset']
223
223
224 With sort(), set optimization could be used:
224 With sort(), set optimization could be used:
225 >>> xs.sort(reverse=True)
225 >>> xs.sort(reverse=True)
226 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
226 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
227 [[7, 6, 4, 0, 5, 3], [7, 6], [4, 0]]
227 [[7, 6, 4, 0, 5, 3], [7, 6], [4, 0]]
228 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
228 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
229 ['addset', 'baseset', 'baseset']
229 ['addset', 'baseset', 'baseset']
230
230
231 >>> ys.sort()
231 >>> ys.sort()
232 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
232 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
233 [[7, 6, 4, 0, 3, 5], [7, 6], [4, 0]]
233 [[7, 6, 4, 0, 3, 5], [7, 6], [4, 0]]
234 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
234 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
235 ['addset', 'baseset', 'baseset']
235 ['addset', 'baseset', 'baseset']
236
236
237 istopo is preserved across set operations
237 istopo is preserved across set operations
238 >>> xs = baseset(set(x), istopo=True)
238 >>> xs = baseset(set(x), istopo=True)
239 >>> rs = xs & ys
239 >>> rs = xs & ys
240 >>> type(rs).__name__
240 >>> type(rs).__name__
241 'baseset'
241 'baseset'
242 >>> rs._istopo
242 >>> rs._istopo
243 True
243 True
244 """
244 """
245 def __init__(self, data=(), datarepr=None, istopo=False):
245 def __init__(self, data=(), datarepr=None, istopo=False):
246 """
246 """
247 datarepr: a tuple of (format, obj, ...), a function or an object that
247 datarepr: a tuple of (format, obj, ...), a function or an object that
248 provides a printable representation of the given data.
248 provides a printable representation of the given data.
249 """
249 """
250 self._ascending = None
250 self._ascending = None
251 self._istopo = istopo
251 self._istopo = istopo
252 if isinstance(data, set):
252 if isinstance(data, set):
253 # converting set to list has a cost, do it lazily
253 # converting set to list has a cost, do it lazily
254 self._set = data
254 self._set = data
255 # set has no order we pick one for stability purpose
255 # set has no order we pick one for stability purpose
256 self._ascending = True
256 self._ascending = True
257 else:
257 else:
258 if not isinstance(data, list):
258 if not isinstance(data, list):
259 data = list(data)
259 data = list(data)
260 self._list = data
260 self._list = data
261 self._datarepr = datarepr
261 self._datarepr = datarepr
262
262
263 @util.propertycache
263 @util.propertycache
264 def _set(self):
264 def _set(self):
265 return set(self._list)
265 return set(self._list)
266
266
267 @util.propertycache
267 @util.propertycache
268 def _asclist(self):
268 def _asclist(self):
269 asclist = self._list[:]
269 asclist = self._list[:]
270 asclist.sort()
270 asclist.sort()
271 return asclist
271 return asclist
272
272
273 @util.propertycache
273 @util.propertycache
274 def _list(self):
274 def _list(self):
275 # _list is only lazily constructed if we have _set
275 # _list is only lazily constructed if we have _set
276 assert r'_set' in self.__dict__
276 assert r'_set' in self.__dict__
277 return list(self._set)
277 return list(self._set)
278
278
279 def __iter__(self):
279 def __iter__(self):
280 if self._ascending is None:
280 if self._ascending is None:
281 return iter(self._list)
281 return iter(self._list)
282 elif self._ascending:
282 elif self._ascending:
283 return iter(self._asclist)
283 return iter(self._asclist)
284 else:
284 else:
285 return reversed(self._asclist)
285 return reversed(self._asclist)
286
286
287 def fastasc(self):
287 def fastasc(self):
288 return iter(self._asclist)
288 return iter(self._asclist)
289
289
290 def fastdesc(self):
290 def fastdesc(self):
291 return reversed(self._asclist)
291 return reversed(self._asclist)
292
292
293 @util.propertycache
293 @util.propertycache
294 def __contains__(self):
294 def __contains__(self):
295 return self._set.__contains__
295 return self._set.__contains__
296
296
297 def __nonzero__(self):
297 def __nonzero__(self):
298 return bool(len(self))
298 return bool(len(self))
299
299
300 __bool__ = __nonzero__
300 __bool__ = __nonzero__
301
301
302 def sort(self, reverse=False):
302 def sort(self, reverse=False):
303 self._ascending = not bool(reverse)
303 self._ascending = not bool(reverse)
304 self._istopo = False
304 self._istopo = False
305
305
306 def reverse(self):
306 def reverse(self):
307 if self._ascending is None:
307 if self._ascending is None:
308 self._list.reverse()
308 self._list.reverse()
309 else:
309 else:
310 self._ascending = not self._ascending
310 self._ascending = not self._ascending
311 self._istopo = False
311 self._istopo = False
312
312
313 def __len__(self):
313 def __len__(self):
314 if r'_list' in self.__dict__:
314 if r'_list' in self.__dict__:
315 return len(self._list)
315 return len(self._list)
316 else:
316 else:
317 return len(self._set)
317 return len(self._set)
318
318
319 def isascending(self):
319 def isascending(self):
320 """Returns True if the collection is ascending order, False if not.
320 """Returns True if the collection is ascending order, False if not.
321
321
322 This is part of the mandatory API for smartset."""
322 This is part of the mandatory API for smartset."""
323 if len(self) <= 1:
323 if len(self) <= 1:
324 return True
324 return True
325 return self._ascending is not None and self._ascending
325 return self._ascending is not None and self._ascending
326
326
327 def isdescending(self):
327 def isdescending(self):
328 """Returns True if the collection is descending order, False if not.
328 """Returns True if the collection is descending order, False if not.
329
329
330 This is part of the mandatory API for smartset."""
330 This is part of the mandatory API for smartset."""
331 if len(self) <= 1:
331 if len(self) <= 1:
332 return True
332 return True
333 return self._ascending is not None and not self._ascending
333 return self._ascending is not None and not self._ascending
334
334
335 def istopo(self):
335 def istopo(self):
336 """Is the collection is in topographical order or not.
336 """Is the collection is in topographical order or not.
337
337
338 This is part of the mandatory API for smartset."""
338 This is part of the mandatory API for smartset."""
339 if len(self) <= 1:
339 if len(self) <= 1:
340 return True
340 return True
341 return self._istopo
341 return self._istopo
342
342
343 def first(self):
343 def first(self):
344 if self:
344 if self:
345 if self._ascending is None:
345 if self._ascending is None:
346 return self._list[0]
346 return self._list[0]
347 elif self._ascending:
347 elif self._ascending:
348 return self._asclist[0]
348 return self._asclist[0]
349 else:
349 else:
350 return self._asclist[-1]
350 return self._asclist[-1]
351 return None
351 return None
352
352
353 def last(self):
353 def last(self):
354 if self:
354 if self:
355 if self._ascending is None:
355 if self._ascending is None:
356 return self._list[-1]
356 return self._list[-1]
357 elif self._ascending:
357 elif self._ascending:
358 return self._asclist[-1]
358 return self._asclist[-1]
359 else:
359 else:
360 return self._asclist[0]
360 return self._asclist[0]
361 return None
361 return None
362
362
363 def _fastsetop(self, other, op):
363 def _fastsetop(self, other, op):
364 # try to use native set operations as fast paths
364 # try to use native set operations as fast paths
365 if (type(other) is baseset and r'_set' in other.__dict__ and r'_set' in
365 if (type(other) is baseset and r'_set' in other.__dict__ and r'_set' in
366 self.__dict__ and self._ascending is not None):
366 self.__dict__ and self._ascending is not None):
367 s = baseset(data=getattr(self._set, op)(other._set),
367 s = baseset(data=getattr(self._set, op)(other._set),
368 istopo=self._istopo)
368 istopo=self._istopo)
369 s._ascending = self._ascending
369 s._ascending = self._ascending
370 else:
370 else:
371 s = getattr(super(baseset, self), op)(other)
371 s = getattr(super(baseset, self), op)(other)
372 return s
372 return s
373
373
374 def __and__(self, other):
374 def __and__(self, other):
375 return self._fastsetop(other, '__and__')
375 return self._fastsetop(other, '__and__')
376
376
377 def __sub__(self, other):
377 def __sub__(self, other):
378 return self._fastsetop(other, '__sub__')
378 return self._fastsetop(other, '__sub__')
379
379
380 def _slice(self, start, stop):
380 def _slice(self, start, stop):
381 # creating new list should be generally cheaper than iterating items
381 # creating new list should be generally cheaper than iterating items
382 if self._ascending is None:
382 if self._ascending is None:
383 return baseset(self._list[start:stop], istopo=self._istopo)
383 return baseset(self._list[start:stop], istopo=self._istopo)
384
384
385 data = self._asclist
385 data = self._asclist
386 if not self._ascending:
386 if not self._ascending:
387 start, stop = max(len(data) - stop, 0), max(len(data) - start, 0)
387 start, stop = max(len(data) - stop, 0), max(len(data) - start, 0)
388 s = baseset(data[start:stop], istopo=self._istopo)
388 s = baseset(data[start:stop], istopo=self._istopo)
389 s._ascending = self._ascending
389 s._ascending = self._ascending
390 return s
390 return s
391
391
392 @encoding.strmethod
392 @encoding.strmethod
393 def __repr__(self):
393 def __repr__(self):
394 d = {None: '', False: '-', True: '+'}[self._ascending]
394 d = {None: '', False: '-', True: '+'}[self._ascending]
395 s = _formatsetrepr(self._datarepr)
395 s = _formatsetrepr(self._datarepr)
396 if not s:
396 if not s:
397 l = self._list
397 l = self._list
398 # if _list has been built from a set, it might have a different
398 # if _list has been built from a set, it might have a different
399 # order from one python implementation to another.
399 # order from one python implementation to another.
400 # We fallback to the sorted version for a stable output.
400 # We fallback to the sorted version for a stable output.
401 if self._ascending is not None:
401 if self._ascending is not None:
402 l = self._asclist
402 l = self._asclist
403 s = pycompat.sysbytes(repr(l))
403 s = pycompat.byterepr(l)
404 return '<%s%s %s>' % (_typename(self), d, s)
404 return '<%s%s %s>' % (_typename(self), d, s)
405
405
406 class filteredset(abstractsmartset):
406 class filteredset(abstractsmartset):
407 """Duck type for baseset class which iterates lazily over the revisions in
407 """Duck type for baseset class which iterates lazily over the revisions in
408 the subset and contains a function which tests for membership in the
408 the subset and contains a function which tests for membership in the
409 revset
409 revset
410 """
410 """
411 def __init__(self, subset, condition=lambda x: True, condrepr=None):
411 def __init__(self, subset, condition=lambda x: True, condrepr=None):
412 """
412 """
413 condition: a function that decide whether a revision in the subset
413 condition: a function that decide whether a revision in the subset
414 belongs to the revset or not.
414 belongs to the revset or not.
415 condrepr: a tuple of (format, obj, ...), a function or an object that
415 condrepr: a tuple of (format, obj, ...), a function or an object that
416 provides a printable representation of the given condition.
416 provides a printable representation of the given condition.
417 """
417 """
418 self._subset = subset
418 self._subset = subset
419 self._condition = condition
419 self._condition = condition
420 self._condrepr = condrepr
420 self._condrepr = condrepr
421
421
422 def __contains__(self, x):
422 def __contains__(self, x):
423 return x in self._subset and self._condition(x)
423 return x in self._subset and self._condition(x)
424
424
425 def __iter__(self):
425 def __iter__(self):
426 return self._iterfilter(self._subset)
426 return self._iterfilter(self._subset)
427
427
428 def _iterfilter(self, it):
428 def _iterfilter(self, it):
429 cond = self._condition
429 cond = self._condition
430 for x in it:
430 for x in it:
431 if cond(x):
431 if cond(x):
432 yield x
432 yield x
433
433
434 @property
434 @property
435 def fastasc(self):
435 def fastasc(self):
436 it = self._subset.fastasc
436 it = self._subset.fastasc
437 if it is None:
437 if it is None:
438 return None
438 return None
439 return lambda: self._iterfilter(it())
439 return lambda: self._iterfilter(it())
440
440
441 @property
441 @property
442 def fastdesc(self):
442 def fastdesc(self):
443 it = self._subset.fastdesc
443 it = self._subset.fastdesc
444 if it is None:
444 if it is None:
445 return None
445 return None
446 return lambda: self._iterfilter(it())
446 return lambda: self._iterfilter(it())
447
447
448 def __nonzero__(self):
448 def __nonzero__(self):
449 fast = None
449 fast = None
450 candidates = [self.fastasc if self.isascending() else None,
450 candidates = [self.fastasc if self.isascending() else None,
451 self.fastdesc if self.isdescending() else None,
451 self.fastdesc if self.isdescending() else None,
452 self.fastasc,
452 self.fastasc,
453 self.fastdesc]
453 self.fastdesc]
454 for candidate in candidates:
454 for candidate in candidates:
455 if candidate is not None:
455 if candidate is not None:
456 fast = candidate
456 fast = candidate
457 break
457 break
458
458
459 if fast is not None:
459 if fast is not None:
460 it = fast()
460 it = fast()
461 else:
461 else:
462 it = self
462 it = self
463
463
464 for r in it:
464 for r in it:
465 return True
465 return True
466 return False
466 return False
467
467
468 __bool__ = __nonzero__
468 __bool__ = __nonzero__
469
469
470 def __len__(self):
470 def __len__(self):
471 # Basic implementation to be changed in future patches.
471 # Basic implementation to be changed in future patches.
472 # until this gets improved, we use generator expression
472 # until this gets improved, we use generator expression
473 # here, since list comprehensions are free to call __len__ again
473 # here, since list comprehensions are free to call __len__ again
474 # causing infinite recursion
474 # causing infinite recursion
475 l = baseset(r for r in self)
475 l = baseset(r for r in self)
476 return len(l)
476 return len(l)
477
477
478 def sort(self, reverse=False):
478 def sort(self, reverse=False):
479 self._subset.sort(reverse=reverse)
479 self._subset.sort(reverse=reverse)
480
480
481 def reverse(self):
481 def reverse(self):
482 self._subset.reverse()
482 self._subset.reverse()
483
483
484 def isascending(self):
484 def isascending(self):
485 return self._subset.isascending()
485 return self._subset.isascending()
486
486
487 def isdescending(self):
487 def isdescending(self):
488 return self._subset.isdescending()
488 return self._subset.isdescending()
489
489
490 def istopo(self):
490 def istopo(self):
491 return self._subset.istopo()
491 return self._subset.istopo()
492
492
493 def first(self):
493 def first(self):
494 for x in self:
494 for x in self:
495 return x
495 return x
496 return None
496 return None
497
497
498 def last(self):
498 def last(self):
499 it = None
499 it = None
500 if self.isascending():
500 if self.isascending():
501 it = self.fastdesc
501 it = self.fastdesc
502 elif self.isdescending():
502 elif self.isdescending():
503 it = self.fastasc
503 it = self.fastasc
504 if it is not None:
504 if it is not None:
505 for x in it():
505 for x in it():
506 return x
506 return x
507 return None #empty case
507 return None #empty case
508 else:
508 else:
509 x = None
509 x = None
510 for x in self:
510 for x in self:
511 pass
511 pass
512 return x
512 return x
513
513
514 @encoding.strmethod
514 @encoding.strmethod
515 def __repr__(self):
515 def __repr__(self):
516 xs = [pycompat.sysbytes(repr(self._subset))]
516 xs = [pycompat.byterepr(self._subset)]
517 s = _formatsetrepr(self._condrepr)
517 s = _formatsetrepr(self._condrepr)
518 if s:
518 if s:
519 xs.append(s)
519 xs.append(s)
520 return '<%s %s>' % (_typename(self), ', '.join(xs))
520 return '<%s %s>' % (_typename(self), ', '.join(xs))
521
521
522 def _iterordered(ascending, iter1, iter2):
522 def _iterordered(ascending, iter1, iter2):
523 """produce an ordered iteration from two iterators with the same order
523 """produce an ordered iteration from two iterators with the same order
524
524
525 The ascending is used to indicated the iteration direction.
525 The ascending is used to indicated the iteration direction.
526 """
526 """
527 choice = max
527 choice = max
528 if ascending:
528 if ascending:
529 choice = min
529 choice = min
530
530
531 val1 = None
531 val1 = None
532 val2 = None
532 val2 = None
533 try:
533 try:
534 # Consume both iterators in an ordered way until one is empty
534 # Consume both iterators in an ordered way until one is empty
535 while True:
535 while True:
536 if val1 is None:
536 if val1 is None:
537 val1 = next(iter1)
537 val1 = next(iter1)
538 if val2 is None:
538 if val2 is None:
539 val2 = next(iter2)
539 val2 = next(iter2)
540 n = choice(val1, val2)
540 n = choice(val1, val2)
541 yield n
541 yield n
542 if val1 == n:
542 if val1 == n:
543 val1 = None
543 val1 = None
544 if val2 == n:
544 if val2 == n:
545 val2 = None
545 val2 = None
546 except StopIteration:
546 except StopIteration:
547 # Flush any remaining values and consume the other one
547 # Flush any remaining values and consume the other one
548 it = iter2
548 it = iter2
549 if val1 is not None:
549 if val1 is not None:
550 yield val1
550 yield val1
551 it = iter1
551 it = iter1
552 elif val2 is not None:
552 elif val2 is not None:
553 # might have been equality and both are empty
553 # might have been equality and both are empty
554 yield val2
554 yield val2
555 for val in it:
555 for val in it:
556 yield val
556 yield val
557
557
558 class addset(abstractsmartset):
558 class addset(abstractsmartset):
559 """Represent the addition of two sets
559 """Represent the addition of two sets
560
560
561 Wrapper structure for lazily adding two structures without losing much
561 Wrapper structure for lazily adding two structures without losing much
562 performance on the __contains__ method
562 performance on the __contains__ method
563
563
564 If the ascending attribute is set, that means the two structures are
564 If the ascending attribute is set, that means the two structures are
565 ordered in either an ascending or descending way. Therefore, we can add
565 ordered in either an ascending or descending way. Therefore, we can add
566 them maintaining the order by iterating over both at the same time
566 them maintaining the order by iterating over both at the same time
567
567
568 >>> xs = baseset([0, 3, 2])
568 >>> xs = baseset([0, 3, 2])
569 >>> ys = baseset([5, 2, 4])
569 >>> ys = baseset([5, 2, 4])
570
570
571 >>> rs = addset(xs, ys)
571 >>> rs = addset(xs, ys)
572 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
572 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
573 (True, True, False, True, 0, 4)
573 (True, True, False, True, 0, 4)
574 >>> rs = addset(xs, baseset([]))
574 >>> rs = addset(xs, baseset([]))
575 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
575 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
576 (True, True, False, 0, 2)
576 (True, True, False, 0, 2)
577 >>> rs = addset(baseset([]), baseset([]))
577 >>> rs = addset(baseset([]), baseset([]))
578 >>> bool(rs), 0 in rs, rs.first(), rs.last()
578 >>> bool(rs), 0 in rs, rs.first(), rs.last()
579 (False, False, None, None)
579 (False, False, None, None)
580
580
581 iterate unsorted:
581 iterate unsorted:
582 >>> rs = addset(xs, ys)
582 >>> rs = addset(xs, ys)
583 >>> # (use generator because pypy could call len())
583 >>> # (use generator because pypy could call len())
584 >>> list(x for x in rs) # without _genlist
584 >>> list(x for x in rs) # without _genlist
585 [0, 3, 2, 5, 4]
585 [0, 3, 2, 5, 4]
586 >>> assert not rs._genlist
586 >>> assert not rs._genlist
587 >>> len(rs)
587 >>> len(rs)
588 5
588 5
589 >>> [x for x in rs] # with _genlist
589 >>> [x for x in rs] # with _genlist
590 [0, 3, 2, 5, 4]
590 [0, 3, 2, 5, 4]
591 >>> assert rs._genlist
591 >>> assert rs._genlist
592
592
593 iterate ascending:
593 iterate ascending:
594 >>> rs = addset(xs, ys, ascending=True)
594 >>> rs = addset(xs, ys, ascending=True)
595 >>> # (use generator because pypy could call len())
595 >>> # (use generator because pypy could call len())
596 >>> list(x for x in rs), list(x for x in rs.fastasc()) # without _asclist
596 >>> list(x for x in rs), list(x for x in rs.fastasc()) # without _asclist
597 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
597 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
598 >>> assert not rs._asclist
598 >>> assert not rs._asclist
599 >>> len(rs)
599 >>> len(rs)
600 5
600 5
601 >>> [x for x in rs], [x for x in rs.fastasc()]
601 >>> [x for x in rs], [x for x in rs.fastasc()]
602 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
602 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
603 >>> assert rs._asclist
603 >>> assert rs._asclist
604
604
605 iterate descending:
605 iterate descending:
606 >>> rs = addset(xs, ys, ascending=False)
606 >>> rs = addset(xs, ys, ascending=False)
607 >>> # (use generator because pypy could call len())
607 >>> # (use generator because pypy could call len())
608 >>> list(x for x in rs), list(x for x in rs.fastdesc()) # without _asclist
608 >>> list(x for x in rs), list(x for x in rs.fastdesc()) # without _asclist
609 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
609 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
610 >>> assert not rs._asclist
610 >>> assert not rs._asclist
611 >>> len(rs)
611 >>> len(rs)
612 5
612 5
613 >>> [x for x in rs], [x for x in rs.fastdesc()]
613 >>> [x for x in rs], [x for x in rs.fastdesc()]
614 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
614 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
615 >>> assert rs._asclist
615 >>> assert rs._asclist
616
616
617 iterate ascending without fastasc:
617 iterate ascending without fastasc:
618 >>> rs = addset(xs, generatorset(ys), ascending=True)
618 >>> rs = addset(xs, generatorset(ys), ascending=True)
619 >>> assert rs.fastasc is None
619 >>> assert rs.fastasc is None
620 >>> [x for x in rs]
620 >>> [x for x in rs]
621 [0, 2, 3, 4, 5]
621 [0, 2, 3, 4, 5]
622
622
623 iterate descending without fastdesc:
623 iterate descending without fastdesc:
624 >>> rs = addset(generatorset(xs), ys, ascending=False)
624 >>> rs = addset(generatorset(xs), ys, ascending=False)
625 >>> assert rs.fastdesc is None
625 >>> assert rs.fastdesc is None
626 >>> [x for x in rs]
626 >>> [x for x in rs]
627 [5, 4, 3, 2, 0]
627 [5, 4, 3, 2, 0]
628 """
628 """
629 def __init__(self, revs1, revs2, ascending=None):
629 def __init__(self, revs1, revs2, ascending=None):
630 self._r1 = revs1
630 self._r1 = revs1
631 self._r2 = revs2
631 self._r2 = revs2
632 self._iter = None
632 self._iter = None
633 self._ascending = ascending
633 self._ascending = ascending
634 self._genlist = None
634 self._genlist = None
635 self._asclist = None
635 self._asclist = None
636
636
637 def __len__(self):
637 def __len__(self):
638 return len(self._list)
638 return len(self._list)
639
639
640 def __nonzero__(self):
640 def __nonzero__(self):
641 return bool(self._r1) or bool(self._r2)
641 return bool(self._r1) or bool(self._r2)
642
642
643 __bool__ = __nonzero__
643 __bool__ = __nonzero__
644
644
645 @util.propertycache
645 @util.propertycache
646 def _list(self):
646 def _list(self):
647 if not self._genlist:
647 if not self._genlist:
648 self._genlist = baseset(iter(self))
648 self._genlist = baseset(iter(self))
649 return self._genlist
649 return self._genlist
650
650
651 def __iter__(self):
651 def __iter__(self):
652 """Iterate over both collections without repeating elements
652 """Iterate over both collections without repeating elements
653
653
654 If the ascending attribute is not set, iterate over the first one and
654 If the ascending attribute is not set, iterate over the first one and
655 then over the second one checking for membership on the first one so we
655 then over the second one checking for membership on the first one so we
656 dont yield any duplicates.
656 dont yield any duplicates.
657
657
658 If the ascending attribute is set, iterate over both collections at the
658 If the ascending attribute is set, iterate over both collections at the
659 same time, yielding only one value at a time in the given order.
659 same time, yielding only one value at a time in the given order.
660 """
660 """
661 if self._ascending is None:
661 if self._ascending is None:
662 if self._genlist:
662 if self._genlist:
663 return iter(self._genlist)
663 return iter(self._genlist)
664 def arbitraryordergen():
664 def arbitraryordergen():
665 for r in self._r1:
665 for r in self._r1:
666 yield r
666 yield r
667 inr1 = self._r1.__contains__
667 inr1 = self._r1.__contains__
668 for r in self._r2:
668 for r in self._r2:
669 if not inr1(r):
669 if not inr1(r):
670 yield r
670 yield r
671 return arbitraryordergen()
671 return arbitraryordergen()
672 # try to use our own fast iterator if it exists
672 # try to use our own fast iterator if it exists
673 self._trysetasclist()
673 self._trysetasclist()
674 if self._ascending:
674 if self._ascending:
675 attr = 'fastasc'
675 attr = 'fastasc'
676 else:
676 else:
677 attr = 'fastdesc'
677 attr = 'fastdesc'
678 it = getattr(self, attr)
678 it = getattr(self, attr)
679 if it is not None:
679 if it is not None:
680 return it()
680 return it()
681 # maybe half of the component supports fast
681 # maybe half of the component supports fast
682 # get iterator for _r1
682 # get iterator for _r1
683 iter1 = getattr(self._r1, attr)
683 iter1 = getattr(self._r1, attr)
684 if iter1 is None:
684 if iter1 is None:
685 # let's avoid side effect (not sure it matters)
685 # let's avoid side effect (not sure it matters)
686 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
686 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
687 else:
687 else:
688 iter1 = iter1()
688 iter1 = iter1()
689 # get iterator for _r2
689 # get iterator for _r2
690 iter2 = getattr(self._r2, attr)
690 iter2 = getattr(self._r2, attr)
691 if iter2 is None:
691 if iter2 is None:
692 # let's avoid side effect (not sure it matters)
692 # let's avoid side effect (not sure it matters)
693 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
693 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
694 else:
694 else:
695 iter2 = iter2()
695 iter2 = iter2()
696 return _iterordered(self._ascending, iter1, iter2)
696 return _iterordered(self._ascending, iter1, iter2)
697
697
698 def _trysetasclist(self):
698 def _trysetasclist(self):
699 """populate the _asclist attribute if possible and necessary"""
699 """populate the _asclist attribute if possible and necessary"""
700 if self._genlist is not None and self._asclist is None:
700 if self._genlist is not None and self._asclist is None:
701 self._asclist = sorted(self._genlist)
701 self._asclist = sorted(self._genlist)
702
702
703 @property
703 @property
704 def fastasc(self):
704 def fastasc(self):
705 self._trysetasclist()
705 self._trysetasclist()
706 if self._asclist is not None:
706 if self._asclist is not None:
707 return self._asclist.__iter__
707 return self._asclist.__iter__
708 iter1 = self._r1.fastasc
708 iter1 = self._r1.fastasc
709 iter2 = self._r2.fastasc
709 iter2 = self._r2.fastasc
710 if None in (iter1, iter2):
710 if None in (iter1, iter2):
711 return None
711 return None
712 return lambda: _iterordered(True, iter1(), iter2())
712 return lambda: _iterordered(True, iter1(), iter2())
713
713
714 @property
714 @property
715 def fastdesc(self):
715 def fastdesc(self):
716 self._trysetasclist()
716 self._trysetasclist()
717 if self._asclist is not None:
717 if self._asclist is not None:
718 return self._asclist.__reversed__
718 return self._asclist.__reversed__
719 iter1 = self._r1.fastdesc
719 iter1 = self._r1.fastdesc
720 iter2 = self._r2.fastdesc
720 iter2 = self._r2.fastdesc
721 if None in (iter1, iter2):
721 if None in (iter1, iter2):
722 return None
722 return None
723 return lambda: _iterordered(False, iter1(), iter2())
723 return lambda: _iterordered(False, iter1(), iter2())
724
724
725 def __contains__(self, x):
725 def __contains__(self, x):
726 return x in self._r1 or x in self._r2
726 return x in self._r1 or x in self._r2
727
727
728 def sort(self, reverse=False):
728 def sort(self, reverse=False):
729 """Sort the added set
729 """Sort the added set
730
730
731 For this we use the cached list with all the generated values and if we
731 For this we use the cached list with all the generated values and if we
732 know they are ascending or descending we can sort them in a smart way.
732 know they are ascending or descending we can sort them in a smart way.
733 """
733 """
734 self._ascending = not reverse
734 self._ascending = not reverse
735
735
736 def isascending(self):
736 def isascending(self):
737 return self._ascending is not None and self._ascending
737 return self._ascending is not None and self._ascending
738
738
739 def isdescending(self):
739 def isdescending(self):
740 return self._ascending is not None and not self._ascending
740 return self._ascending is not None and not self._ascending
741
741
742 def istopo(self):
742 def istopo(self):
743 # not worth the trouble asserting if the two sets combined are still
743 # not worth the trouble asserting if the two sets combined are still
744 # in topographical order. Use the sort() predicate to explicitly sort
744 # in topographical order. Use the sort() predicate to explicitly sort
745 # again instead.
745 # again instead.
746 return False
746 return False
747
747
748 def reverse(self):
748 def reverse(self):
749 if self._ascending is None:
749 if self._ascending is None:
750 self._list.reverse()
750 self._list.reverse()
751 else:
751 else:
752 self._ascending = not self._ascending
752 self._ascending = not self._ascending
753
753
754 def first(self):
754 def first(self):
755 for x in self:
755 for x in self:
756 return x
756 return x
757 return None
757 return None
758
758
759 def last(self):
759 def last(self):
760 self.reverse()
760 self.reverse()
761 val = self.first()
761 val = self.first()
762 self.reverse()
762 self.reverse()
763 return val
763 return val
764
764
765 @encoding.strmethod
765 @encoding.strmethod
766 def __repr__(self):
766 def __repr__(self):
767 d = {None: '', False: '-', True: '+'}[self._ascending]
767 d = {None: '', False: '-', True: '+'}[self._ascending]
768 return '<%s%s %r, %r>' % (_typename(self), d, self._r1, self._r2)
768 return '<%s%s %r, %r>' % (_typename(self), d, self._r1, self._r2)
769
769
770 class generatorset(abstractsmartset):
770 class generatorset(abstractsmartset):
771 """Wrap a generator for lazy iteration
771 """Wrap a generator for lazy iteration
772
772
773 Wrapper structure for generators that provides lazy membership and can
773 Wrapper structure for generators that provides lazy membership and can
774 be iterated more than once.
774 be iterated more than once.
775 When asked for membership it generates values until either it finds the
775 When asked for membership it generates values until either it finds the
776 requested one or has gone through all the elements in the generator
776 requested one or has gone through all the elements in the generator
777
777
778 >>> xs = generatorset([0, 1, 4], iterasc=True)
778 >>> xs = generatorset([0, 1, 4], iterasc=True)
779 >>> assert xs.last() == xs.last()
779 >>> assert xs.last() == xs.last()
780 >>> xs.last() # cached
780 >>> xs.last() # cached
781 4
781 4
782 """
782 """
783 def __new__(cls, gen, iterasc=None):
783 def __new__(cls, gen, iterasc=None):
784 if iterasc is None:
784 if iterasc is None:
785 typ = cls
785 typ = cls
786 elif iterasc:
786 elif iterasc:
787 typ = _generatorsetasc
787 typ = _generatorsetasc
788 else:
788 else:
789 typ = _generatorsetdesc
789 typ = _generatorsetdesc
790
790
791 return super(generatorset, cls).__new__(typ)
791 return super(generatorset, cls).__new__(typ)
792
792
793 def __init__(self, gen, iterasc=None):
793 def __init__(self, gen, iterasc=None):
794 """
794 """
795 gen: a generator producing the values for the generatorset.
795 gen: a generator producing the values for the generatorset.
796 """
796 """
797 self._gen = gen
797 self._gen = gen
798 self._asclist = None
798 self._asclist = None
799 self._cache = {}
799 self._cache = {}
800 self._genlist = []
800 self._genlist = []
801 self._finished = False
801 self._finished = False
802 self._ascending = True
802 self._ascending = True
803
803
804 def __nonzero__(self):
804 def __nonzero__(self):
805 # Do not use 'for r in self' because it will enforce the iteration
805 # Do not use 'for r in self' because it will enforce the iteration
806 # order (default ascending), possibly unrolling a whole descending
806 # order (default ascending), possibly unrolling a whole descending
807 # iterator.
807 # iterator.
808 if self._genlist:
808 if self._genlist:
809 return True
809 return True
810 for r in self._consumegen():
810 for r in self._consumegen():
811 return True
811 return True
812 return False
812 return False
813
813
814 __bool__ = __nonzero__
814 __bool__ = __nonzero__
815
815
816 def __contains__(self, x):
816 def __contains__(self, x):
817 if x in self._cache:
817 if x in self._cache:
818 return self._cache[x]
818 return self._cache[x]
819
819
820 # Use new values only, as existing values would be cached.
820 # Use new values only, as existing values would be cached.
821 for l in self._consumegen():
821 for l in self._consumegen():
822 if l == x:
822 if l == x:
823 return True
823 return True
824
824
825 self._cache[x] = False
825 self._cache[x] = False
826 return False
826 return False
827
827
828 def __iter__(self):
828 def __iter__(self):
829 if self._ascending:
829 if self._ascending:
830 it = self.fastasc
830 it = self.fastasc
831 else:
831 else:
832 it = self.fastdesc
832 it = self.fastdesc
833 if it is not None:
833 if it is not None:
834 return it()
834 return it()
835 # we need to consume the iterator
835 # we need to consume the iterator
836 for x in self._consumegen():
836 for x in self._consumegen():
837 pass
837 pass
838 # recall the same code
838 # recall the same code
839 return iter(self)
839 return iter(self)
840
840
841 def _iterator(self):
841 def _iterator(self):
842 if self._finished:
842 if self._finished:
843 return iter(self._genlist)
843 return iter(self._genlist)
844
844
845 # We have to use this complex iteration strategy to allow multiple
845 # We have to use this complex iteration strategy to allow multiple
846 # iterations at the same time. We need to be able to catch revision
846 # iterations at the same time. We need to be able to catch revision
847 # removed from _consumegen and added to genlist in another instance.
847 # removed from _consumegen and added to genlist in another instance.
848 #
848 #
849 # Getting rid of it would provide an about 15% speed up on this
849 # Getting rid of it would provide an about 15% speed up on this
850 # iteration.
850 # iteration.
851 genlist = self._genlist
851 genlist = self._genlist
852 nextgen = self._consumegen()
852 nextgen = self._consumegen()
853 _len, _next = len, next # cache global lookup
853 _len, _next = len, next # cache global lookup
854 def gen():
854 def gen():
855 i = 0
855 i = 0
856 while True:
856 while True:
857 if i < _len(genlist):
857 if i < _len(genlist):
858 yield genlist[i]
858 yield genlist[i]
859 else:
859 else:
860 try:
860 try:
861 yield _next(nextgen)
861 yield _next(nextgen)
862 except StopIteration:
862 except StopIteration:
863 return
863 return
864 i += 1
864 i += 1
865 return gen()
865 return gen()
866
866
867 def _consumegen(self):
867 def _consumegen(self):
868 cache = self._cache
868 cache = self._cache
869 genlist = self._genlist.append
869 genlist = self._genlist.append
870 for item in self._gen:
870 for item in self._gen:
871 cache[item] = True
871 cache[item] = True
872 genlist(item)
872 genlist(item)
873 yield item
873 yield item
874 if not self._finished:
874 if not self._finished:
875 self._finished = True
875 self._finished = True
876 asc = self._genlist[:]
876 asc = self._genlist[:]
877 asc.sort()
877 asc.sort()
878 self._asclist = asc
878 self._asclist = asc
879 self.fastasc = asc.__iter__
879 self.fastasc = asc.__iter__
880 self.fastdesc = asc.__reversed__
880 self.fastdesc = asc.__reversed__
881
881
882 def __len__(self):
882 def __len__(self):
883 for x in self._consumegen():
883 for x in self._consumegen():
884 pass
884 pass
885 return len(self._genlist)
885 return len(self._genlist)
886
886
887 def sort(self, reverse=False):
887 def sort(self, reverse=False):
888 self._ascending = not reverse
888 self._ascending = not reverse
889
889
890 def reverse(self):
890 def reverse(self):
891 self._ascending = not self._ascending
891 self._ascending = not self._ascending
892
892
893 def isascending(self):
893 def isascending(self):
894 return self._ascending
894 return self._ascending
895
895
896 def isdescending(self):
896 def isdescending(self):
897 return not self._ascending
897 return not self._ascending
898
898
899 def istopo(self):
899 def istopo(self):
900 # not worth the trouble asserting if the two sets combined are still
900 # not worth the trouble asserting if the two sets combined are still
901 # in topographical order. Use the sort() predicate to explicitly sort
901 # in topographical order. Use the sort() predicate to explicitly sort
902 # again instead.
902 # again instead.
903 return False
903 return False
904
904
905 def first(self):
905 def first(self):
906 if self._ascending:
906 if self._ascending:
907 it = self.fastasc
907 it = self.fastasc
908 else:
908 else:
909 it = self.fastdesc
909 it = self.fastdesc
910 if it is None:
910 if it is None:
911 # we need to consume all and try again
911 # we need to consume all and try again
912 for x in self._consumegen():
912 for x in self._consumegen():
913 pass
913 pass
914 return self.first()
914 return self.first()
915 return next(it(), None)
915 return next(it(), None)
916
916
917 def last(self):
917 def last(self):
918 if self._ascending:
918 if self._ascending:
919 it = self.fastdesc
919 it = self.fastdesc
920 else:
920 else:
921 it = self.fastasc
921 it = self.fastasc
922 if it is None:
922 if it is None:
923 # we need to consume all and try again
923 # we need to consume all and try again
924 for x in self._consumegen():
924 for x in self._consumegen():
925 pass
925 pass
926 return self.last()
926 return self.last()
927 return next(it(), None)
927 return next(it(), None)
928
928
929 @encoding.strmethod
929 @encoding.strmethod
930 def __repr__(self):
930 def __repr__(self):
931 d = {False: '-', True: '+'}[self._ascending]
931 d = {False: '-', True: '+'}[self._ascending]
932 return '<%s%s>' % (_typename(self), d)
932 return '<%s%s>' % (_typename(self), d)
933
933
934 class _generatorsetasc(generatorset):
934 class _generatorsetasc(generatorset):
935 """Special case of generatorset optimized for ascending generators."""
935 """Special case of generatorset optimized for ascending generators."""
936
936
937 fastasc = generatorset._iterator
937 fastasc = generatorset._iterator
938
938
939 def __contains__(self, x):
939 def __contains__(self, x):
940 if x in self._cache:
940 if x in self._cache:
941 return self._cache[x]
941 return self._cache[x]
942
942
943 # Use new values only, as existing values would be cached.
943 # Use new values only, as existing values would be cached.
944 for l in self._consumegen():
944 for l in self._consumegen():
945 if l == x:
945 if l == x:
946 return True
946 return True
947 if l > x:
947 if l > x:
948 break
948 break
949
949
950 self._cache[x] = False
950 self._cache[x] = False
951 return False
951 return False
952
952
953 class _generatorsetdesc(generatorset):
953 class _generatorsetdesc(generatorset):
954 """Special case of generatorset optimized for descending generators."""
954 """Special case of generatorset optimized for descending generators."""
955
955
956 fastdesc = generatorset._iterator
956 fastdesc = generatorset._iterator
957
957
958 def __contains__(self, x):
958 def __contains__(self, x):
959 if x in self._cache:
959 if x in self._cache:
960 return self._cache[x]
960 return self._cache[x]
961
961
962 # Use new values only, as existing values would be cached.
962 # Use new values only, as existing values would be cached.
963 for l in self._consumegen():
963 for l in self._consumegen():
964 if l == x:
964 if l == x:
965 return True
965 return True
966 if l < x:
966 if l < x:
967 break
967 break
968
968
969 self._cache[x] = False
969 self._cache[x] = False
970 return False
970 return False
971
971
972 def spanset(repo, start=0, end=None):
972 def spanset(repo, start=0, end=None):
973 """Create a spanset that represents a range of repository revisions
973 """Create a spanset that represents a range of repository revisions
974
974
975 start: first revision included the set (default to 0)
975 start: first revision included the set (default to 0)
976 end: first revision excluded (last+1) (default to len(repo))
976 end: first revision excluded (last+1) (default to len(repo))
977
977
978 Spanset will be descending if `end` < `start`.
978 Spanset will be descending if `end` < `start`.
979 """
979 """
980 if end is None:
980 if end is None:
981 end = len(repo)
981 end = len(repo)
982 ascending = start <= end
982 ascending = start <= end
983 if not ascending:
983 if not ascending:
984 start, end = end + 1, start + 1
984 start, end = end + 1, start + 1
985 return _spanset(start, end, ascending, repo.changelog.filteredrevs)
985 return _spanset(start, end, ascending, repo.changelog.filteredrevs)
986
986
987 class _spanset(abstractsmartset):
987 class _spanset(abstractsmartset):
988 """Duck type for baseset class which represents a range of revisions and
988 """Duck type for baseset class which represents a range of revisions and
989 can work lazily and without having all the range in memory
989 can work lazily and without having all the range in memory
990
990
991 Note that spanset(x, y) behave almost like xrange(x, y) except for two
991 Note that spanset(x, y) behave almost like xrange(x, y) except for two
992 notable points:
992 notable points:
993 - when x < y it will be automatically descending,
993 - when x < y it will be automatically descending,
994 - revision filtered with this repoview will be skipped.
994 - revision filtered with this repoview will be skipped.
995
995
996 """
996 """
997 def __init__(self, start, end, ascending, hiddenrevs):
997 def __init__(self, start, end, ascending, hiddenrevs):
998 self._start = start
998 self._start = start
999 self._end = end
999 self._end = end
1000 self._ascending = ascending
1000 self._ascending = ascending
1001 self._hiddenrevs = hiddenrevs
1001 self._hiddenrevs = hiddenrevs
1002
1002
1003 def sort(self, reverse=False):
1003 def sort(self, reverse=False):
1004 self._ascending = not reverse
1004 self._ascending = not reverse
1005
1005
1006 def reverse(self):
1006 def reverse(self):
1007 self._ascending = not self._ascending
1007 self._ascending = not self._ascending
1008
1008
1009 def istopo(self):
1009 def istopo(self):
1010 # not worth the trouble asserting if the two sets combined are still
1010 # not worth the trouble asserting if the two sets combined are still
1011 # in topographical order. Use the sort() predicate to explicitly sort
1011 # in topographical order. Use the sort() predicate to explicitly sort
1012 # again instead.
1012 # again instead.
1013 return False
1013 return False
1014
1014
1015 def _iterfilter(self, iterrange):
1015 def _iterfilter(self, iterrange):
1016 s = self._hiddenrevs
1016 s = self._hiddenrevs
1017 for r in iterrange:
1017 for r in iterrange:
1018 if r not in s:
1018 if r not in s:
1019 yield r
1019 yield r
1020
1020
1021 def __iter__(self):
1021 def __iter__(self):
1022 if self._ascending:
1022 if self._ascending:
1023 return self.fastasc()
1023 return self.fastasc()
1024 else:
1024 else:
1025 return self.fastdesc()
1025 return self.fastdesc()
1026
1026
1027 def fastasc(self):
1027 def fastasc(self):
1028 iterrange = xrange(self._start, self._end)
1028 iterrange = xrange(self._start, self._end)
1029 if self._hiddenrevs:
1029 if self._hiddenrevs:
1030 return self._iterfilter(iterrange)
1030 return self._iterfilter(iterrange)
1031 return iter(iterrange)
1031 return iter(iterrange)
1032
1032
1033 def fastdesc(self):
1033 def fastdesc(self):
1034 iterrange = xrange(self._end - 1, self._start - 1, -1)
1034 iterrange = xrange(self._end - 1, self._start - 1, -1)
1035 if self._hiddenrevs:
1035 if self._hiddenrevs:
1036 return self._iterfilter(iterrange)
1036 return self._iterfilter(iterrange)
1037 return iter(iterrange)
1037 return iter(iterrange)
1038
1038
1039 def __contains__(self, rev):
1039 def __contains__(self, rev):
1040 hidden = self._hiddenrevs
1040 hidden = self._hiddenrevs
1041 return ((self._start <= rev < self._end)
1041 return ((self._start <= rev < self._end)
1042 and not (hidden and rev in hidden))
1042 and not (hidden and rev in hidden))
1043
1043
1044 def __nonzero__(self):
1044 def __nonzero__(self):
1045 for r in self:
1045 for r in self:
1046 return True
1046 return True
1047 return False
1047 return False
1048
1048
1049 __bool__ = __nonzero__
1049 __bool__ = __nonzero__
1050
1050
1051 def __len__(self):
1051 def __len__(self):
1052 if not self._hiddenrevs:
1052 if not self._hiddenrevs:
1053 return abs(self._end - self._start)
1053 return abs(self._end - self._start)
1054 else:
1054 else:
1055 count = 0
1055 count = 0
1056 start = self._start
1056 start = self._start
1057 end = self._end
1057 end = self._end
1058 for rev in self._hiddenrevs:
1058 for rev in self._hiddenrevs:
1059 if (end < rev <= start) or (start <= rev < end):
1059 if (end < rev <= start) or (start <= rev < end):
1060 count += 1
1060 count += 1
1061 return abs(self._end - self._start) - count
1061 return abs(self._end - self._start) - count
1062
1062
1063 def isascending(self):
1063 def isascending(self):
1064 return self._ascending
1064 return self._ascending
1065
1065
1066 def isdescending(self):
1066 def isdescending(self):
1067 return not self._ascending
1067 return not self._ascending
1068
1068
1069 def first(self):
1069 def first(self):
1070 if self._ascending:
1070 if self._ascending:
1071 it = self.fastasc
1071 it = self.fastasc
1072 else:
1072 else:
1073 it = self.fastdesc
1073 it = self.fastdesc
1074 for x in it():
1074 for x in it():
1075 return x
1075 return x
1076 return None
1076 return None
1077
1077
1078 def last(self):
1078 def last(self):
1079 if self._ascending:
1079 if self._ascending:
1080 it = self.fastdesc
1080 it = self.fastdesc
1081 else:
1081 else:
1082 it = self.fastasc
1082 it = self.fastasc
1083 for x in it():
1083 for x in it():
1084 return x
1084 return x
1085 return None
1085 return None
1086
1086
1087 def _slice(self, start, stop):
1087 def _slice(self, start, stop):
1088 if self._hiddenrevs:
1088 if self._hiddenrevs:
1089 # unoptimized since all hidden revisions in range has to be scanned
1089 # unoptimized since all hidden revisions in range has to be scanned
1090 return super(_spanset, self)._slice(start, stop)
1090 return super(_spanset, self)._slice(start, stop)
1091 if self._ascending:
1091 if self._ascending:
1092 x = min(self._start + start, self._end)
1092 x = min(self._start + start, self._end)
1093 y = min(self._start + stop, self._end)
1093 y = min(self._start + stop, self._end)
1094 else:
1094 else:
1095 x = max(self._end - stop, self._start)
1095 x = max(self._end - stop, self._start)
1096 y = max(self._end - start, self._start)
1096 y = max(self._end - start, self._start)
1097 return _spanset(x, y, self._ascending, self._hiddenrevs)
1097 return _spanset(x, y, self._ascending, self._hiddenrevs)
1098
1098
1099 @encoding.strmethod
1099 @encoding.strmethod
1100 def __repr__(self):
1100 def __repr__(self):
1101 d = {False: '-', True: '+'}[self._ascending]
1101 d = {False: '-', True: '+'}[self._ascending]
1102 return '<%s%s %d:%d>' % (_typename(self), d, self._start, self._end)
1102 return '<%s%s %d:%d>' % (_typename(self), d, self._start, self._end)
1103
1103
1104 class fullreposet(_spanset):
1104 class fullreposet(_spanset):
1105 """a set containing all revisions in the repo
1105 """a set containing all revisions in the repo
1106
1106
1107 This class exists to host special optimization and magic to handle virtual
1107 This class exists to host special optimization and magic to handle virtual
1108 revisions such as "null".
1108 revisions such as "null".
1109 """
1109 """
1110
1110
1111 def __init__(self, repo):
1111 def __init__(self, repo):
1112 super(fullreposet, self).__init__(0, len(repo), True,
1112 super(fullreposet, self).__init__(0, len(repo), True,
1113 repo.changelog.filteredrevs)
1113 repo.changelog.filteredrevs)
1114
1114
1115 def __and__(self, other):
1115 def __and__(self, other):
1116 """As self contains the whole repo, all of the other set should also be
1116 """As self contains the whole repo, all of the other set should also be
1117 in self. Therefore `self & other = other`.
1117 in self. Therefore `self & other = other`.
1118
1118
1119 This boldly assumes the other contains valid revs only.
1119 This boldly assumes the other contains valid revs only.
1120 """
1120 """
1121 # other not a smartset, make is so
1121 # other not a smartset, make is so
1122 if not util.safehasattr(other, 'isascending'):
1122 if not util.safehasattr(other, 'isascending'):
1123 # filter out hidden revision
1123 # filter out hidden revision
1124 # (this boldly assumes all smartset are pure)
1124 # (this boldly assumes all smartset are pure)
1125 #
1125 #
1126 # `other` was used with "&", let's assume this is a set like
1126 # `other` was used with "&", let's assume this is a set like
1127 # object.
1127 # object.
1128 other = baseset(other - self._hiddenrevs)
1128 other = baseset(other - self._hiddenrevs)
1129
1129
1130 other.sort(reverse=self.isdescending())
1130 other.sort(reverse=self.isdescending())
1131 return other
1131 return other
1132
1132
1133 def prettyformat(revs):
1133 def prettyformat(revs):
1134 lines = []
1134 lines = []
1135 rs = pycompat.sysbytes(repr(revs))
1135 rs = pycompat.byterepr(revs)
1136 p = 0
1136 p = 0
1137 while p < len(rs):
1137 while p < len(rs):
1138 q = rs.find('<', p + 1)
1138 q = rs.find('<', p + 1)
1139 if q < 0:
1139 if q < 0:
1140 q = len(rs)
1140 q = len(rs)
1141 l = rs.count('<', 0, p) - rs.count('>', 0, p)
1141 l = rs.count('<', 0, p) - rs.count('>', 0, p)
1142 assert l >= 0
1142 assert l >= 0
1143 lines.append((l, rs[p:q].rstrip()))
1143 lines.append((l, rs[p:q].rstrip()))
1144 p = q
1144 p = q
1145 return '\n'.join(' ' * l + s for l, s in lines)
1145 return '\n'.join(' ' * l + s for l, s in lines)
General Comments 0
You need to be logged in to leave comments. Login now