##// END OF EJS Templates
smartset: use native string when peeking in __dict__...
Augie Fackler -
r35856:f484b9d9 default
parent child Browse files
Show More
@@ -1,1136 +1,1136 b''
1 1 # smartset.py - data structure for revision set
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 from . import (
11 11 error,
12 12 util,
13 13 )
14 14
15 15 def _formatsetrepr(r):
16 16 """Format an optional printable representation of a set
17 17
18 18 ======== =================================
19 19 type(r) example
20 20 ======== =================================
21 21 tuple ('<not %r>', other)
22 22 str '<branch closed>'
23 23 callable lambda: '<branch %r>' % sorted(b)
24 24 object other
25 25 ======== =================================
26 26 """
27 27 if r is None:
28 28 return ''
29 29 elif isinstance(r, tuple):
30 30 return r[0] % r[1:]
31 31 elif isinstance(r, str):
32 32 return r
33 33 elif callable(r):
34 34 return r()
35 35 else:
36 36 return repr(r)
37 37
38 38 class abstractsmartset(object):
39 39
40 40 def __nonzero__(self):
41 41 """True if the smartset is not empty"""
42 42 raise NotImplementedError()
43 43
44 44 __bool__ = __nonzero__
45 45
46 46 def __contains__(self, rev):
47 47 """provide fast membership testing"""
48 48 raise NotImplementedError()
49 49
50 50 def __iter__(self):
51 51 """iterate the set in the order it is supposed to be iterated"""
52 52 raise NotImplementedError()
53 53
54 54 # Attributes containing a function to perform a fast iteration in a given
55 55 # direction. A smartset can have none, one, or both defined.
56 56 #
57 57 # Default value is None instead of a function returning None to avoid
58 58 # initializing an iterator just for testing if a fast method exists.
59 59 fastasc = None
60 60 fastdesc = None
61 61
62 62 def isascending(self):
63 63 """True if the set will iterate in ascending order"""
64 64 raise NotImplementedError()
65 65
66 66 def isdescending(self):
67 67 """True if the set will iterate in descending order"""
68 68 raise NotImplementedError()
69 69
70 70 def istopo(self):
71 71 """True if the set will iterate in topographical order"""
72 72 raise NotImplementedError()
73 73
74 74 def min(self):
75 75 """return the minimum element in the set"""
76 76 if self.fastasc is None:
77 77 v = min(self)
78 78 else:
79 79 for v in self.fastasc():
80 80 break
81 81 else:
82 82 raise ValueError('arg is an empty sequence')
83 83 self.min = lambda: v
84 84 return v
85 85
86 86 def max(self):
87 87 """return the maximum element in the set"""
88 88 if self.fastdesc is None:
89 89 return max(self)
90 90 else:
91 91 for v in self.fastdesc():
92 92 break
93 93 else:
94 94 raise ValueError('arg is an empty sequence')
95 95 self.max = lambda: v
96 96 return v
97 97
98 98 def first(self):
99 99 """return the first element in the set (user iteration perspective)
100 100
101 101 Return None if the set is empty"""
102 102 raise NotImplementedError()
103 103
104 104 def last(self):
105 105 """return the last element in the set (user iteration perspective)
106 106
107 107 Return None if the set is empty"""
108 108 raise NotImplementedError()
109 109
110 110 def __len__(self):
111 111 """return the length of the smartsets
112 112
113 113 This can be expensive on smartset that could be lazy otherwise."""
114 114 raise NotImplementedError()
115 115
116 116 def reverse(self):
117 117 """reverse the expected iteration order"""
118 118 raise NotImplementedError()
119 119
120 120 def sort(self, reverse=False):
121 121 """get the set to iterate in an ascending or descending order"""
122 122 raise NotImplementedError()
123 123
124 124 def __and__(self, other):
125 125 """Returns a new object with the intersection of the two collections.
126 126
127 127 This is part of the mandatory API for smartset."""
128 128 if isinstance(other, fullreposet):
129 129 return self
130 130 return self.filter(other.__contains__, condrepr=other, cache=False)
131 131
132 132 def __add__(self, other):
133 133 """Returns a new object with the union of the two collections.
134 134
135 135 This is part of the mandatory API for smartset."""
136 136 return addset(self, other)
137 137
138 138 def __sub__(self, other):
139 139 """Returns a new object with the substraction of the two collections.
140 140
141 141 This is part of the mandatory API for smartset."""
142 142 c = other.__contains__
143 143 return self.filter(lambda r: not c(r), condrepr=('<not %r>', other),
144 144 cache=False)
145 145
146 146 def filter(self, condition, condrepr=None, cache=True):
147 147 """Returns this smartset filtered by condition as a new smartset.
148 148
149 149 `condition` is a callable which takes a revision number and returns a
150 150 boolean. Optional `condrepr` provides a printable representation of
151 151 the given `condition`.
152 152
153 153 This is part of the mandatory API for smartset."""
154 154 # builtin cannot be cached. but do not needs to
155 155 if cache and util.safehasattr(condition, 'func_code'):
156 156 condition = util.cachefunc(condition)
157 157 return filteredset(self, condition, condrepr)
158 158
159 159 def slice(self, start, stop):
160 160 """Return new smartset that contains selected elements from this set"""
161 161 if start < 0 or stop < 0:
162 162 raise error.ProgrammingError('negative index not allowed')
163 163 return self._slice(start, stop)
164 164
165 165 def _slice(self, start, stop):
166 166 # sub classes may override this. start and stop must not be negative,
167 167 # but start > stop is allowed, which should be an empty set.
168 168 ys = []
169 169 it = iter(self)
170 170 for x in xrange(start):
171 171 y = next(it, None)
172 172 if y is None:
173 173 break
174 174 for x in xrange(stop - start):
175 175 y = next(it, None)
176 176 if y is None:
177 177 break
178 178 ys.append(y)
179 179 return baseset(ys, datarepr=('slice=%d:%d %r', start, stop, self))
180 180
181 181 class baseset(abstractsmartset):
182 182 """Basic data structure that represents a revset and contains the basic
183 183 operation that it should be able to perform.
184 184
185 185 Every method in this class should be implemented by any smartset class.
186 186
187 187 This class could be constructed by an (unordered) set, or an (ordered)
188 188 list-like object. If a set is provided, it'll be sorted lazily.
189 189
190 190 >>> x = [4, 0, 7, 6]
191 191 >>> y = [5, 6, 7, 3]
192 192
193 193 Construct by a set:
194 194 >>> xs = baseset(set(x))
195 195 >>> ys = baseset(set(y))
196 196 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
197 197 [[0, 4, 6, 7, 3, 5], [6, 7], [0, 4]]
198 198 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
199 199 ['addset', 'baseset', 'baseset']
200 200
201 201 Construct by a list-like:
202 202 >>> xs = baseset(x)
203 203 >>> ys = baseset(i for i in y)
204 204 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
205 205 [[4, 0, 7, 6, 5, 3], [7, 6], [4, 0]]
206 206 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
207 207 ['addset', 'filteredset', 'filteredset']
208 208
209 209 Populate "_set" fields in the lists so set optimization may be used:
210 210 >>> [1 in xs, 3 in ys]
211 211 [False, True]
212 212
213 213 Without sort(), results won't be changed:
214 214 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
215 215 [[4, 0, 7, 6, 5, 3], [7, 6], [4, 0]]
216 216 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
217 217 ['addset', 'filteredset', 'filteredset']
218 218
219 219 With sort(), set optimization could be used:
220 220 >>> xs.sort(reverse=True)
221 221 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
222 222 [[7, 6, 4, 0, 5, 3], [7, 6], [4, 0]]
223 223 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
224 224 ['addset', 'baseset', 'baseset']
225 225
226 226 >>> ys.sort()
227 227 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
228 228 [[7, 6, 4, 0, 3, 5], [7, 6], [4, 0]]
229 229 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
230 230 ['addset', 'baseset', 'baseset']
231 231
232 232 istopo is preserved across set operations
233 233 >>> xs = baseset(set(x), istopo=True)
234 234 >>> rs = xs & ys
235 235 >>> type(rs).__name__
236 236 'baseset'
237 237 >>> rs._istopo
238 238 True
239 239 """
240 240 def __init__(self, data=(), datarepr=None, istopo=False):
241 241 """
242 242 datarepr: a tuple of (format, obj, ...), a function or an object that
243 243 provides a printable representation of the given data.
244 244 """
245 245 self._ascending = None
246 246 self._istopo = istopo
247 247 if isinstance(data, set):
248 248 # converting set to list has a cost, do it lazily
249 249 self._set = data
250 250 # set has no order we pick one for stability purpose
251 251 self._ascending = True
252 252 else:
253 253 if not isinstance(data, list):
254 254 data = list(data)
255 255 self._list = data
256 256 self._datarepr = datarepr
257 257
258 258 @util.propertycache
259 259 def _set(self):
260 260 return set(self._list)
261 261
262 262 @util.propertycache
263 263 def _asclist(self):
264 264 asclist = self._list[:]
265 265 asclist.sort()
266 266 return asclist
267 267
268 268 @util.propertycache
269 269 def _list(self):
270 270 # _list is only lazily constructed if we have _set
271 271 assert r'_set' in self.__dict__
272 272 return list(self._set)
273 273
274 274 def __iter__(self):
275 275 if self._ascending is None:
276 276 return iter(self._list)
277 277 elif self._ascending:
278 278 return iter(self._asclist)
279 279 else:
280 280 return reversed(self._asclist)
281 281
282 282 def fastasc(self):
283 283 return iter(self._asclist)
284 284
285 285 def fastdesc(self):
286 286 return reversed(self._asclist)
287 287
288 288 @util.propertycache
289 289 def __contains__(self):
290 290 return self._set.__contains__
291 291
292 292 def __nonzero__(self):
293 293 return bool(len(self))
294 294
295 295 __bool__ = __nonzero__
296 296
297 297 def sort(self, reverse=False):
298 298 self._ascending = not bool(reverse)
299 299 self._istopo = False
300 300
301 301 def reverse(self):
302 302 if self._ascending is None:
303 303 self._list.reverse()
304 304 else:
305 305 self._ascending = not self._ascending
306 306 self._istopo = False
307 307
308 308 def __len__(self):
309 if '_list' in self.__dict__:
309 if r'_list' in self.__dict__:
310 310 return len(self._list)
311 311 else:
312 312 return len(self._set)
313 313
314 314 def isascending(self):
315 315 """Returns True if the collection is ascending order, False if not.
316 316
317 317 This is part of the mandatory API for smartset."""
318 318 if len(self) <= 1:
319 319 return True
320 320 return self._ascending is not None and self._ascending
321 321
322 322 def isdescending(self):
323 323 """Returns True if the collection is descending order, False if not.
324 324
325 325 This is part of the mandatory API for smartset."""
326 326 if len(self) <= 1:
327 327 return True
328 328 return self._ascending is not None and not self._ascending
329 329
330 330 def istopo(self):
331 331 """Is the collection is in topographical order or not.
332 332
333 333 This is part of the mandatory API for smartset."""
334 334 if len(self) <= 1:
335 335 return True
336 336 return self._istopo
337 337
338 338 def first(self):
339 339 if self:
340 340 if self._ascending is None:
341 341 return self._list[0]
342 342 elif self._ascending:
343 343 return self._asclist[0]
344 344 else:
345 345 return self._asclist[-1]
346 346 return None
347 347
348 348 def last(self):
349 349 if self:
350 350 if self._ascending is None:
351 351 return self._list[-1]
352 352 elif self._ascending:
353 353 return self._asclist[-1]
354 354 else:
355 355 return self._asclist[0]
356 356 return None
357 357
358 358 def _fastsetop(self, other, op):
359 359 # try to use native set operations as fast paths
360 360 if (type(other) is baseset and r'_set' in other.__dict__ and r'_set' in
361 361 self.__dict__ and self._ascending is not None):
362 362 s = baseset(data=getattr(self._set, op)(other._set),
363 363 istopo=self._istopo)
364 364 s._ascending = self._ascending
365 365 else:
366 366 s = getattr(super(baseset, self), op)(other)
367 367 return s
368 368
369 369 def __and__(self, other):
370 370 return self._fastsetop(other, '__and__')
371 371
372 372 def __sub__(self, other):
373 373 return self._fastsetop(other, '__sub__')
374 374
375 375 def _slice(self, start, stop):
376 376 # creating new list should be generally cheaper than iterating items
377 377 if self._ascending is None:
378 378 return baseset(self._list[start:stop], istopo=self._istopo)
379 379
380 380 data = self._asclist
381 381 if not self._ascending:
382 382 start, stop = max(len(data) - stop, 0), max(len(data) - start, 0)
383 383 s = baseset(data[start:stop], istopo=self._istopo)
384 384 s._ascending = self._ascending
385 385 return s
386 386
387 387 def __repr__(self):
388 388 d = {None: '', False: '-', True: '+'}[self._ascending]
389 389 s = _formatsetrepr(self._datarepr)
390 390 if not s:
391 391 l = self._list
392 392 # if _list has been built from a set, it might have a different
393 393 # order from one python implementation to another.
394 394 # We fallback to the sorted version for a stable output.
395 395 if self._ascending is not None:
396 396 l = self._asclist
397 397 s = repr(l)
398 398 return '<%s%s %s>' % (type(self).__name__, d, s)
399 399
400 400 class filteredset(abstractsmartset):
401 401 """Duck type for baseset class which iterates lazily over the revisions in
402 402 the subset and contains a function which tests for membership in the
403 403 revset
404 404 """
405 405 def __init__(self, subset, condition=lambda x: True, condrepr=None):
406 406 """
407 407 condition: a function that decide whether a revision in the subset
408 408 belongs to the revset or not.
409 409 condrepr: a tuple of (format, obj, ...), a function or an object that
410 410 provides a printable representation of the given condition.
411 411 """
412 412 self._subset = subset
413 413 self._condition = condition
414 414 self._condrepr = condrepr
415 415
416 416 def __contains__(self, x):
417 417 return x in self._subset and self._condition(x)
418 418
419 419 def __iter__(self):
420 420 return self._iterfilter(self._subset)
421 421
422 422 def _iterfilter(self, it):
423 423 cond = self._condition
424 424 for x in it:
425 425 if cond(x):
426 426 yield x
427 427
428 428 @property
429 429 def fastasc(self):
430 430 it = self._subset.fastasc
431 431 if it is None:
432 432 return None
433 433 return lambda: self._iterfilter(it())
434 434
435 435 @property
436 436 def fastdesc(self):
437 437 it = self._subset.fastdesc
438 438 if it is None:
439 439 return None
440 440 return lambda: self._iterfilter(it())
441 441
442 442 def __nonzero__(self):
443 443 fast = None
444 444 candidates = [self.fastasc if self.isascending() else None,
445 445 self.fastdesc if self.isdescending() else None,
446 446 self.fastasc,
447 447 self.fastdesc]
448 448 for candidate in candidates:
449 449 if candidate is not None:
450 450 fast = candidate
451 451 break
452 452
453 453 if fast is not None:
454 454 it = fast()
455 455 else:
456 456 it = self
457 457
458 458 for r in it:
459 459 return True
460 460 return False
461 461
462 462 __bool__ = __nonzero__
463 463
464 464 def __len__(self):
465 465 # Basic implementation to be changed in future patches.
466 466 # until this gets improved, we use generator expression
467 467 # here, since list comprehensions are free to call __len__ again
468 468 # causing infinite recursion
469 469 l = baseset(r for r in self)
470 470 return len(l)
471 471
472 472 def sort(self, reverse=False):
473 473 self._subset.sort(reverse=reverse)
474 474
475 475 def reverse(self):
476 476 self._subset.reverse()
477 477
478 478 def isascending(self):
479 479 return self._subset.isascending()
480 480
481 481 def isdescending(self):
482 482 return self._subset.isdescending()
483 483
484 484 def istopo(self):
485 485 return self._subset.istopo()
486 486
487 487 def first(self):
488 488 for x in self:
489 489 return x
490 490 return None
491 491
492 492 def last(self):
493 493 it = None
494 494 if self.isascending():
495 495 it = self.fastdesc
496 496 elif self.isdescending():
497 497 it = self.fastasc
498 498 if it is not None:
499 499 for x in it():
500 500 return x
501 501 return None #empty case
502 502 else:
503 503 x = None
504 504 for x in self:
505 505 pass
506 506 return x
507 507
508 508 def __repr__(self):
509 509 xs = [repr(self._subset)]
510 510 s = _formatsetrepr(self._condrepr)
511 511 if s:
512 512 xs.append(s)
513 513 return '<%s %s>' % (type(self).__name__, ', '.join(xs))
514 514
515 515 def _iterordered(ascending, iter1, iter2):
516 516 """produce an ordered iteration from two iterators with the same order
517 517
518 518 The ascending is used to indicated the iteration direction.
519 519 """
520 520 choice = max
521 521 if ascending:
522 522 choice = min
523 523
524 524 val1 = None
525 525 val2 = None
526 526 try:
527 527 # Consume both iterators in an ordered way until one is empty
528 528 while True:
529 529 if val1 is None:
530 530 val1 = next(iter1)
531 531 if val2 is None:
532 532 val2 = next(iter2)
533 533 n = choice(val1, val2)
534 534 yield n
535 535 if val1 == n:
536 536 val1 = None
537 537 if val2 == n:
538 538 val2 = None
539 539 except StopIteration:
540 540 # Flush any remaining values and consume the other one
541 541 it = iter2
542 542 if val1 is not None:
543 543 yield val1
544 544 it = iter1
545 545 elif val2 is not None:
546 546 # might have been equality and both are empty
547 547 yield val2
548 548 for val in it:
549 549 yield val
550 550
551 551 class addset(abstractsmartset):
552 552 """Represent the addition of two sets
553 553
554 554 Wrapper structure for lazily adding two structures without losing much
555 555 performance on the __contains__ method
556 556
557 557 If the ascending attribute is set, that means the two structures are
558 558 ordered in either an ascending or descending way. Therefore, we can add
559 559 them maintaining the order by iterating over both at the same time
560 560
561 561 >>> xs = baseset([0, 3, 2])
562 562 >>> ys = baseset([5, 2, 4])
563 563
564 564 >>> rs = addset(xs, ys)
565 565 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
566 566 (True, True, False, True, 0, 4)
567 567 >>> rs = addset(xs, baseset([]))
568 568 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
569 569 (True, True, False, 0, 2)
570 570 >>> rs = addset(baseset([]), baseset([]))
571 571 >>> bool(rs), 0 in rs, rs.first(), rs.last()
572 572 (False, False, None, None)
573 573
574 574 iterate unsorted:
575 575 >>> rs = addset(xs, ys)
576 576 >>> # (use generator because pypy could call len())
577 577 >>> list(x for x in rs) # without _genlist
578 578 [0, 3, 2, 5, 4]
579 579 >>> assert not rs._genlist
580 580 >>> len(rs)
581 581 5
582 582 >>> [x for x in rs] # with _genlist
583 583 [0, 3, 2, 5, 4]
584 584 >>> assert rs._genlist
585 585
586 586 iterate ascending:
587 587 >>> rs = addset(xs, ys, ascending=True)
588 588 >>> # (use generator because pypy could call len())
589 589 >>> list(x for x in rs), list(x for x in rs.fastasc()) # without _asclist
590 590 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
591 591 >>> assert not rs._asclist
592 592 >>> len(rs)
593 593 5
594 594 >>> [x for x in rs], [x for x in rs.fastasc()]
595 595 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
596 596 >>> assert rs._asclist
597 597
598 598 iterate descending:
599 599 >>> rs = addset(xs, ys, ascending=False)
600 600 >>> # (use generator because pypy could call len())
601 601 >>> list(x for x in rs), list(x for x in rs.fastdesc()) # without _asclist
602 602 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
603 603 >>> assert not rs._asclist
604 604 >>> len(rs)
605 605 5
606 606 >>> [x for x in rs], [x for x in rs.fastdesc()]
607 607 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
608 608 >>> assert rs._asclist
609 609
610 610 iterate ascending without fastasc:
611 611 >>> rs = addset(xs, generatorset(ys), ascending=True)
612 612 >>> assert rs.fastasc is None
613 613 >>> [x for x in rs]
614 614 [0, 2, 3, 4, 5]
615 615
616 616 iterate descending without fastdesc:
617 617 >>> rs = addset(generatorset(xs), ys, ascending=False)
618 618 >>> assert rs.fastdesc is None
619 619 >>> [x for x in rs]
620 620 [5, 4, 3, 2, 0]
621 621 """
622 622 def __init__(self, revs1, revs2, ascending=None):
623 623 self._r1 = revs1
624 624 self._r2 = revs2
625 625 self._iter = None
626 626 self._ascending = ascending
627 627 self._genlist = None
628 628 self._asclist = None
629 629
630 630 def __len__(self):
631 631 return len(self._list)
632 632
633 633 def __nonzero__(self):
634 634 return bool(self._r1) or bool(self._r2)
635 635
636 636 __bool__ = __nonzero__
637 637
638 638 @util.propertycache
639 639 def _list(self):
640 640 if not self._genlist:
641 641 self._genlist = baseset(iter(self))
642 642 return self._genlist
643 643
644 644 def __iter__(self):
645 645 """Iterate over both collections without repeating elements
646 646
647 647 If the ascending attribute is not set, iterate over the first one and
648 648 then over the second one checking for membership on the first one so we
649 649 dont yield any duplicates.
650 650
651 651 If the ascending attribute is set, iterate over both collections at the
652 652 same time, yielding only one value at a time in the given order.
653 653 """
654 654 if self._ascending is None:
655 655 if self._genlist:
656 656 return iter(self._genlist)
657 657 def arbitraryordergen():
658 658 for r in self._r1:
659 659 yield r
660 660 inr1 = self._r1.__contains__
661 661 for r in self._r2:
662 662 if not inr1(r):
663 663 yield r
664 664 return arbitraryordergen()
665 665 # try to use our own fast iterator if it exists
666 666 self._trysetasclist()
667 667 if self._ascending:
668 668 attr = 'fastasc'
669 669 else:
670 670 attr = 'fastdesc'
671 671 it = getattr(self, attr)
672 672 if it is not None:
673 673 return it()
674 674 # maybe half of the component supports fast
675 675 # get iterator for _r1
676 676 iter1 = getattr(self._r1, attr)
677 677 if iter1 is None:
678 678 # let's avoid side effect (not sure it matters)
679 679 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
680 680 else:
681 681 iter1 = iter1()
682 682 # get iterator for _r2
683 683 iter2 = getattr(self._r2, attr)
684 684 if iter2 is None:
685 685 # let's avoid side effect (not sure it matters)
686 686 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
687 687 else:
688 688 iter2 = iter2()
689 689 return _iterordered(self._ascending, iter1, iter2)
690 690
691 691 def _trysetasclist(self):
692 692 """populate the _asclist attribute if possible and necessary"""
693 693 if self._genlist is not None and self._asclist is None:
694 694 self._asclist = sorted(self._genlist)
695 695
696 696 @property
697 697 def fastasc(self):
698 698 self._trysetasclist()
699 699 if self._asclist is not None:
700 700 return self._asclist.__iter__
701 701 iter1 = self._r1.fastasc
702 702 iter2 = self._r2.fastasc
703 703 if None in (iter1, iter2):
704 704 return None
705 705 return lambda: _iterordered(True, iter1(), iter2())
706 706
707 707 @property
708 708 def fastdesc(self):
709 709 self._trysetasclist()
710 710 if self._asclist is not None:
711 711 return self._asclist.__reversed__
712 712 iter1 = self._r1.fastdesc
713 713 iter2 = self._r2.fastdesc
714 714 if None in (iter1, iter2):
715 715 return None
716 716 return lambda: _iterordered(False, iter1(), iter2())
717 717
718 718 def __contains__(self, x):
719 719 return x in self._r1 or x in self._r2
720 720
721 721 def sort(self, reverse=False):
722 722 """Sort the added set
723 723
724 724 For this we use the cached list with all the generated values and if we
725 725 know they are ascending or descending we can sort them in a smart way.
726 726 """
727 727 self._ascending = not reverse
728 728
729 729 def isascending(self):
730 730 return self._ascending is not None and self._ascending
731 731
732 732 def isdescending(self):
733 733 return self._ascending is not None and not self._ascending
734 734
735 735 def istopo(self):
736 736 # not worth the trouble asserting if the two sets combined are still
737 737 # in topographical order. Use the sort() predicate to explicitly sort
738 738 # again instead.
739 739 return False
740 740
741 741 def reverse(self):
742 742 if self._ascending is None:
743 743 self._list.reverse()
744 744 else:
745 745 self._ascending = not self._ascending
746 746
747 747 def first(self):
748 748 for x in self:
749 749 return x
750 750 return None
751 751
752 752 def last(self):
753 753 self.reverse()
754 754 val = self.first()
755 755 self.reverse()
756 756 return val
757 757
758 758 def __repr__(self):
759 759 d = {None: '', False: '-', True: '+'}[self._ascending]
760 760 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
761 761
762 762 class generatorset(abstractsmartset):
763 763 """Wrap a generator for lazy iteration
764 764
765 765 Wrapper structure for generators that provides lazy membership and can
766 766 be iterated more than once.
767 767 When asked for membership it generates values until either it finds the
768 768 requested one or has gone through all the elements in the generator
769 769
770 770 >>> xs = generatorset([0, 1, 4], iterasc=True)
771 771 >>> assert xs.last() == xs.last()
772 772 >>> xs.last() # cached
773 773 4
774 774 """
775 775 def __new__(cls, gen, iterasc=None):
776 776 if iterasc is None:
777 777 typ = cls
778 778 elif iterasc:
779 779 typ = _generatorsetasc
780 780 else:
781 781 typ = _generatorsetdesc
782 782
783 783 return super(generatorset, cls).__new__(typ)
784 784
785 785 def __init__(self, gen, iterasc=None):
786 786 """
787 787 gen: a generator producing the values for the generatorset.
788 788 """
789 789 self._gen = gen
790 790 self._asclist = None
791 791 self._cache = {}
792 792 self._genlist = []
793 793 self._finished = False
794 794 self._ascending = True
795 795
796 796 def __nonzero__(self):
797 797 # Do not use 'for r in self' because it will enforce the iteration
798 798 # order (default ascending), possibly unrolling a whole descending
799 799 # iterator.
800 800 if self._genlist:
801 801 return True
802 802 for r in self._consumegen():
803 803 return True
804 804 return False
805 805
806 806 __bool__ = __nonzero__
807 807
808 808 def __contains__(self, x):
809 809 if x in self._cache:
810 810 return self._cache[x]
811 811
812 812 # Use new values only, as existing values would be cached.
813 813 for l in self._consumegen():
814 814 if l == x:
815 815 return True
816 816
817 817 self._cache[x] = False
818 818 return False
819 819
820 820 def __iter__(self):
821 821 if self._ascending:
822 822 it = self.fastasc
823 823 else:
824 824 it = self.fastdesc
825 825 if it is not None:
826 826 return it()
827 827 # we need to consume the iterator
828 828 for x in self._consumegen():
829 829 pass
830 830 # recall the same code
831 831 return iter(self)
832 832
833 833 def _iterator(self):
834 834 if self._finished:
835 835 return iter(self._genlist)
836 836
837 837 # We have to use this complex iteration strategy to allow multiple
838 838 # iterations at the same time. We need to be able to catch revision
839 839 # removed from _consumegen and added to genlist in another instance.
840 840 #
841 841 # Getting rid of it would provide an about 15% speed up on this
842 842 # iteration.
843 843 genlist = self._genlist
844 844 nextgen = self._consumegen()
845 845 _len, _next = len, next # cache global lookup
846 846 def gen():
847 847 i = 0
848 848 while True:
849 849 if i < _len(genlist):
850 850 yield genlist[i]
851 851 else:
852 852 try:
853 853 yield _next(nextgen)
854 854 except StopIteration:
855 855 return
856 856 i += 1
857 857 return gen()
858 858
859 859 def _consumegen(self):
860 860 cache = self._cache
861 861 genlist = self._genlist.append
862 862 for item in self._gen:
863 863 cache[item] = True
864 864 genlist(item)
865 865 yield item
866 866 if not self._finished:
867 867 self._finished = True
868 868 asc = self._genlist[:]
869 869 asc.sort()
870 870 self._asclist = asc
871 871 self.fastasc = asc.__iter__
872 872 self.fastdesc = asc.__reversed__
873 873
874 874 def __len__(self):
875 875 for x in self._consumegen():
876 876 pass
877 877 return len(self._genlist)
878 878
879 879 def sort(self, reverse=False):
880 880 self._ascending = not reverse
881 881
882 882 def reverse(self):
883 883 self._ascending = not self._ascending
884 884
885 885 def isascending(self):
886 886 return self._ascending
887 887
888 888 def isdescending(self):
889 889 return not self._ascending
890 890
891 891 def istopo(self):
892 892 # not worth the trouble asserting if the two sets combined are still
893 893 # in topographical order. Use the sort() predicate to explicitly sort
894 894 # again instead.
895 895 return False
896 896
897 897 def first(self):
898 898 if self._ascending:
899 899 it = self.fastasc
900 900 else:
901 901 it = self.fastdesc
902 902 if it is None:
903 903 # we need to consume all and try again
904 904 for x in self._consumegen():
905 905 pass
906 906 return self.first()
907 907 return next(it(), None)
908 908
909 909 def last(self):
910 910 if self._ascending:
911 911 it = self.fastdesc
912 912 else:
913 913 it = self.fastasc
914 914 if it is None:
915 915 # we need to consume all and try again
916 916 for x in self._consumegen():
917 917 pass
918 918 return self.last()
919 919 return next(it(), None)
920 920
921 921 def __repr__(self):
922 922 d = {False: '-', True: '+'}[self._ascending]
923 923 return '<%s%s>' % (type(self).__name__.lstrip('_'), d)
924 924
925 925 class _generatorsetasc(generatorset):
926 926 """Special case of generatorset optimized for ascending generators."""
927 927
928 928 fastasc = generatorset._iterator
929 929
930 930 def __contains__(self, x):
931 931 if x in self._cache:
932 932 return self._cache[x]
933 933
934 934 # Use new values only, as existing values would be cached.
935 935 for l in self._consumegen():
936 936 if l == x:
937 937 return True
938 938 if l > x:
939 939 break
940 940
941 941 self._cache[x] = False
942 942 return False
943 943
944 944 class _generatorsetdesc(generatorset):
945 945 """Special case of generatorset optimized for descending generators."""
946 946
947 947 fastdesc = generatorset._iterator
948 948
949 949 def __contains__(self, x):
950 950 if x in self._cache:
951 951 return self._cache[x]
952 952
953 953 # Use new values only, as existing values would be cached.
954 954 for l in self._consumegen():
955 955 if l == x:
956 956 return True
957 957 if l < x:
958 958 break
959 959
960 960 self._cache[x] = False
961 961 return False
962 962
963 963 def spanset(repo, start=0, end=None):
964 964 """Create a spanset that represents a range of repository revisions
965 965
966 966 start: first revision included the set (default to 0)
967 967 end: first revision excluded (last+1) (default to len(repo))
968 968
969 969 Spanset will be descending if `end` < `start`.
970 970 """
971 971 if end is None:
972 972 end = len(repo)
973 973 ascending = start <= end
974 974 if not ascending:
975 975 start, end = end + 1, start + 1
976 976 return _spanset(start, end, ascending, repo.changelog.filteredrevs)
977 977
978 978 class _spanset(abstractsmartset):
979 979 """Duck type for baseset class which represents a range of revisions and
980 980 can work lazily and without having all the range in memory
981 981
982 982 Note that spanset(x, y) behave almost like xrange(x, y) except for two
983 983 notable points:
984 984 - when x < y it will be automatically descending,
985 985 - revision filtered with this repoview will be skipped.
986 986
987 987 """
988 988 def __init__(self, start, end, ascending, hiddenrevs):
989 989 self._start = start
990 990 self._end = end
991 991 self._ascending = ascending
992 992 self._hiddenrevs = hiddenrevs
993 993
994 994 def sort(self, reverse=False):
995 995 self._ascending = not reverse
996 996
997 997 def reverse(self):
998 998 self._ascending = not self._ascending
999 999
1000 1000 def istopo(self):
1001 1001 # not worth the trouble asserting if the two sets combined are still
1002 1002 # in topographical order. Use the sort() predicate to explicitly sort
1003 1003 # again instead.
1004 1004 return False
1005 1005
1006 1006 def _iterfilter(self, iterrange):
1007 1007 s = self._hiddenrevs
1008 1008 for r in iterrange:
1009 1009 if r not in s:
1010 1010 yield r
1011 1011
1012 1012 def __iter__(self):
1013 1013 if self._ascending:
1014 1014 return self.fastasc()
1015 1015 else:
1016 1016 return self.fastdesc()
1017 1017
1018 1018 def fastasc(self):
1019 1019 iterrange = xrange(self._start, self._end)
1020 1020 if self._hiddenrevs:
1021 1021 return self._iterfilter(iterrange)
1022 1022 return iter(iterrange)
1023 1023
1024 1024 def fastdesc(self):
1025 1025 iterrange = xrange(self._end - 1, self._start - 1, -1)
1026 1026 if self._hiddenrevs:
1027 1027 return self._iterfilter(iterrange)
1028 1028 return iter(iterrange)
1029 1029
1030 1030 def __contains__(self, rev):
1031 1031 hidden = self._hiddenrevs
1032 1032 return ((self._start <= rev < self._end)
1033 1033 and not (hidden and rev in hidden))
1034 1034
1035 1035 def __nonzero__(self):
1036 1036 for r in self:
1037 1037 return True
1038 1038 return False
1039 1039
1040 1040 __bool__ = __nonzero__
1041 1041
1042 1042 def __len__(self):
1043 1043 if not self._hiddenrevs:
1044 1044 return abs(self._end - self._start)
1045 1045 else:
1046 1046 count = 0
1047 1047 start = self._start
1048 1048 end = self._end
1049 1049 for rev in self._hiddenrevs:
1050 1050 if (end < rev <= start) or (start <= rev < end):
1051 1051 count += 1
1052 1052 return abs(self._end - self._start) - count
1053 1053
1054 1054 def isascending(self):
1055 1055 return self._ascending
1056 1056
1057 1057 def isdescending(self):
1058 1058 return not self._ascending
1059 1059
1060 1060 def first(self):
1061 1061 if self._ascending:
1062 1062 it = self.fastasc
1063 1063 else:
1064 1064 it = self.fastdesc
1065 1065 for x in it():
1066 1066 return x
1067 1067 return None
1068 1068
1069 1069 def last(self):
1070 1070 if self._ascending:
1071 1071 it = self.fastdesc
1072 1072 else:
1073 1073 it = self.fastasc
1074 1074 for x in it():
1075 1075 return x
1076 1076 return None
1077 1077
1078 1078 def _slice(self, start, stop):
1079 1079 if self._hiddenrevs:
1080 1080 # unoptimized since all hidden revisions in range has to be scanned
1081 1081 return super(_spanset, self)._slice(start, stop)
1082 1082 if self._ascending:
1083 1083 x = min(self._start + start, self._end)
1084 1084 y = min(self._start + stop, self._end)
1085 1085 else:
1086 1086 x = max(self._end - stop, self._start)
1087 1087 y = max(self._end - start, self._start)
1088 1088 return _spanset(x, y, self._ascending, self._hiddenrevs)
1089 1089
1090 1090 def __repr__(self):
1091 1091 d = {False: '-', True: '+'}[self._ascending]
1092 1092 return '<%s%s %d:%d>' % (type(self).__name__.lstrip('_'), d,
1093 1093 self._start, self._end)
1094 1094
1095 1095 class fullreposet(_spanset):
1096 1096 """a set containing all revisions in the repo
1097 1097
1098 1098 This class exists to host special optimization and magic to handle virtual
1099 1099 revisions such as "null".
1100 1100 """
1101 1101
1102 1102 def __init__(self, repo):
1103 1103 super(fullreposet, self).__init__(0, len(repo), True,
1104 1104 repo.changelog.filteredrevs)
1105 1105
1106 1106 def __and__(self, other):
1107 1107 """As self contains the whole repo, all of the other set should also be
1108 1108 in self. Therefore `self & other = other`.
1109 1109
1110 1110 This boldly assumes the other contains valid revs only.
1111 1111 """
1112 1112 # other not a smartset, make is so
1113 1113 if not util.safehasattr(other, 'isascending'):
1114 1114 # filter out hidden revision
1115 1115 # (this boldly assumes all smartset are pure)
1116 1116 #
1117 1117 # `other` was used with "&", let's assume this is a set like
1118 1118 # object.
1119 1119 other = baseset(other - self._hiddenrevs)
1120 1120
1121 1121 other.sort(reverse=self.isdescending())
1122 1122 return other
1123 1123
1124 1124 def prettyformat(revs):
1125 1125 lines = []
1126 1126 rs = repr(revs)
1127 1127 p = 0
1128 1128 while p < len(rs):
1129 1129 q = rs.find('<', p + 1)
1130 1130 if q < 0:
1131 1131 q = len(rs)
1132 1132 l = rs.count('<', 0, p) - rs.count('>', 0, p)
1133 1133 assert l >= 0
1134 1134 lines.append((l, rs[p:q].rstrip()))
1135 1135 p = q
1136 1136 return '\n'.join(' ' * l + s for l, s in lines)
General Comments 0
You need to be logged in to leave comments. Login now