1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 """
36 Cluster node set.
37
38 A module to deal efficiently with pdsh-like rangesets and nodesets.
39 Instances of RangeSet and NodeSet both provide similar operations than
40 the builtin set() type and Set object.
41 [ See http://www.python.org/doc/lib/set-objects.html ]
42
43 Usage example:
44
45 # Import NodeSet class
46 from ClusterShell.NodeSet import NodeSet
47
48 # Create a new nodeset from pdsh-like pattern
49 nodeset = NodeSet("cluster[1-30]")
50
51 # Add cluster32 to nodeset
52 nodeset.update("cluster32")
53
54 # Remove from nodeset
55 nodeset.difference_update("cluster[2-5]")
56
57 # Print nodeset as a pdsh-like pattern
58 print nodeset
59
60 # Iterate over node names in nodeset
61 for node in nodeset:
62 print node
63 """
64
65 import copy
66 import re
67
69 """used by RangeSet"""
72
75
77 """used by RangeSet when a parse cannot be done"""
79
80 self.msg = "%s : \"%s\"" % (msg, subrange)
81
83 """used by RangeSet when a fatal padding incoherency occurs"""
84
85
87 """used by NodeSet"""
90
93
95 """used by NodeSet when a parse cannot be done"""
97
98 self.part = part
99 self.msg = msg
100
102 """used by NodeSet when bad range is encountered during a a parse"""
104
105 self.msg = rset_exc.msg
106
107
109 """
110 Advanced range sets.
111
112 RangeSet creation examples:
113 rset = RangeSet() # empty RangeSet
114 rset = RangeSet("5,10-42") # contains 5, 10 to 42
115 rset = RangeSet("0-10/2") # contains 0, 2, 4, 6, 8, 10
116
117 Also, RangeSet provides methods like update(), intersection_update()
118 or difference_update(), which conform to the Python Set API.
119 """
120 - def __init__(self, pattern=None, autostep=None):
121 """
122 Initialize RangeSet with optional pdsh-like string pattern and
123 autostep threshold.
124 """
125 if autostep is None:
126
127
128 self._autostep = 1E100
129 else:
130
131
132 self._autostep = int(autostep) - 1
133
134 self._length = 0
135 self._ranges = []
136
137 if pattern is not None:
138
139
140 if pattern.find(',') < 0:
141 subranges = [pattern]
142 else:
143 subranges = pattern.split(',')
144
145 for subrange in subranges:
146 if subrange.find('/') < 0:
147 step = 1
148 baserange = subrange
149 else:
150 baserange, step = subrange.split('/', 1)
151
152 try:
153 step = int(step)
154 except ValueError:
155 raise RangeSetParseError(subrange,
156 "cannot convert string to integer")
157
158 if baserange.find('-') < 0:
159 if step != 1:
160 raise RangeSetParseError(subrange,
161 "invalid step usage")
162 begin = end = baserange
163 else:
164 begin, end = baserange.split('-', 1)
165
166
167 try:
168 pad = 0
169 if int(begin) != 0:
170 begins = begin.lstrip("0")
171 if len(begin) - len(begins) > 0:
172 pad = len(begin)
173 start = int(begins)
174 else:
175 if len(begin) > 1:
176 pad = len(begin)
177 start = 0
178 if int(end) != 0:
179 ends = end.lstrip("0")
180 else:
181 ends = end
182 stop = int(ends)
183 except ValueError:
184 raise RangeSetParseError(subrange,
185 "cannot convert string to integer")
186
187
188 if start > stop or step < 1:
189 raise RangeSetParseError(subrange,
190 "invalid values in range")
191
192 self.add_range(start, stop, step, pad)
193
195 """
196 Class method that returns a new RangeSet with ranges from
197 provided list.
198 """
199 inst = RangeSet(autostep=autostep)
200 for rg in l:
201 if isinstance(rg, RangeSet):
202 inst.update(rg)
203 else:
204 inst.update(RangeSet(rg))
205 return inst
206 fromlist = classmethod(fromlist)
207
209 """
210 Iterate over each item in RangeSet.
211 """
212 for start, stop, step, pad in self._ranges:
213 for i in range(start, stop + 1, step):
214 yield "%*d" % (pad, i)
215
217 """
218 Get the number of items in RangeSet.
219 """
220 return self._length
221
223 """
224 Get range-based string.
225 """
226 cnt = 0
227 s = ""
228 for start, stop, step, pad in self._ranges:
229 assert pad != None
230 if cnt > 0:
231 s += ","
232 if start == stop:
233 s += "%0*d" % (pad, start)
234 else:
235 assert step >= 0, "Internal error: step < 0"
236 if step == 1:
237 s += "%0*d-%0*d" % (pad, start, pad, stop)
238 else:
239 s += "%0*d-%0*d/%d" % (pad, start, pad, stop, step)
240 cnt += stop - start + 1
241 return s
242
243
244
245 __repr__ = __str__
246
248 """
249 Is element contained in RangeSet? Element can be either a
250 string with optional padding (eg. "002") or an integer
251 (obviously, no padding check is performed for integer).
252 """
253
254 if type(elem) is str:
255 pad = 0
256 if int(elem) != 0:
257 selem = elem.lstrip("0")
258 if len(elem) - len(selem) > 0:
259 pad = len(elem)
260 ielem = int(selem)
261 else:
262 if len(elem) > 1:
263 pad = len(elem)
264 ielem = 0
265 return self._contains_with_padding(ielem, pad)
266
267
268 return self._contains(int(elem))
269
271 """
272 Contains subroutine that takes an integer.
273 """
274 for rgstart, rgstop, rgstep, rgpad in self._ranges:
275 if ielem >= rgstart and ielem <= rgstop and \
276 (ielem - rgstart) % rgstep == 0:
277 return True
278 return False
279
281 """
282 Contains subroutine that takes an integer and a padding value.
283 """
284 for rgstart, rgstop, rgstep, rgpad in self._ranges:
285
286
287 if ielem >= rgstart and ielem <= rgstop and \
288 (pad == rgpad or (pad == 0 and len(str(ielem)) >= rgpad)) and \
289 (ielem - rgstart) % rgstep == 0:
290 return True
291 return False
292
294
295
296 if not isinstance(other, RangeSet):
297 raise TypeError, "Binary operation only permitted between RangeSets"
298
300 """
301 Report whether another rangeset contains this rangeset.
302 """
303 self._binary_sanity_check(rangeset)
304
305 for start, stop, step, pad in self._ranges:
306 for i in range(start, stop + 1, step):
307 if not rangeset._contains_with_padding(i, pad):
308 return False
309 return True
310
312 """
313 Report whether this rangeset contains another rangeset.
314 """
315 self._binary_sanity_check(rangeset)
316 return rangeset.issubset(self)
317
318
319 __le__ = issubset
320 __ge__ = issuperset
321
328
335
337 """
338 Return the element at index i.
339 """
340 length = 0
341 for start, stop, step, pad in self._ranges:
342 cnt = (stop - start) / step + 1
343 if i < length + cnt:
344 return start + (i - length) * step
345 length += cnt
346
348 """
349 Expand all items. Internal use.
350 """
351 items = []
352 pad = 0
353 for rgstart, rgstop, rgstep, rgpad in self._ranges:
354 items += range(rgstart, rgstop + 1, rgstep)
355 pad = pad or rgpad
356 return items, pad
357
358 - def _fold(self, items, pad):
359 """
360 Fold items as ranges and group them by step.
361 Return: (ranges, total_length)
362 """
363 cnt, k, m, istart, rg = 0, -1, 0, None, []
364
365
366 for i in items:
367 if i > k:
368 cnt += 1
369 if istart is None:
370 istart = k = i
371 elif m > 0:
372 if m != i - k:
373 if m == 1 or k - istart >= self._autostep * m:
374
375 rg.append((istart, k, m, pad))
376 istart = k = i
377 elif k - istart > m:
378
379
380 for j in range(istart, k, m):
381 rg.append((j, j, 1, pad))
382 istart = k
383 else:
384 rg.append((istart, istart, 1, pad))
385 istart = k
386 m = i - k
387 k = i
388
389
390 if istart is not None:
391 if m > 0:
392 if m == 1 or k - istart >= self._autostep * m:
393
394 rg.append((istart, k, m, pad))
395 elif k - istart > m:
396
397 for j in range(istart, k + m, m):
398 rg.append((j, j, 1, pad))
399 else:
400 rg.append((istart, istart, 1, pad))
401 rg.append((k, k, 1, pad))
402 else:
403 rg.append((istart, istart, 1, pad))
404
405 return rg, cnt
406
407 - def add_range(self, start, stop, step=1, pad=0):
408 """
409 Add a range (start, stop, step and padding length) to RangeSet.
410 """
411 assert start <= stop, "please provide ordered node index ranges"
412 assert step != None
413 assert step > 0
414 assert pad != None
415 assert pad >= 0
416 assert stop - start < 1e9, "range too large"
417
418 if self._length == 0:
419
420 stop_adjust = stop - (stop - start) % step
421 if step == 1 or stop_adjust - start >= self._autostep * step:
422 self._ranges = [ (start, stop_adjust, step, pad) ]
423 else:
424 for j in range(start, stop_adjust + step, step):
425 self._ranges.append((j, j, step, pad))
426 self._length = (stop_adjust - start) / step + 1
427 else:
428 self._ranges, self._length = self._add_range_exfold(start, stop, \
429 step, pad)
430
432 """
433 Add range expanding then folding all items.
434 """
435 assert start <= stop, "please provide ordered node index ranges"
436 assert step > 0
437 assert pad != None
438 assert pad >= 0
439
440 items, rgpad = self._expand()
441 items += range(start, stop + 1, step)
442 items.sort()
443
444 return self._fold(items, pad or rgpad)
445
447 """
448 s.union(t) returns a new rangeset with elements from both s and t.
449 """
450 self_copy = copy.deepcopy(self)
451 self_copy.update(other)
452 return self_copy
453
455 """
456 Implements the | operator. So s | t returns a new rangeset with
457 elements from both s and t.
458 """
459 self._binary_sanity_check(other)
460 return self.union(other)
461
462 - def add(self, elem):
463 """
464 Add element to RangeSet.
465 """
466 self.add_range(elem, elem, step=1, pad=0)
467
469 """
470 Update a rangeset with the union of itself and another.
471 """
472 for start, stop, step, pad in rangeset._ranges:
473 self.add_range(start, stop, step, pad)
474
476 """
477 Remove all ranges from this rangeset.
478 """
479 self._ranges = []
480 self._length = 0
481
483 """
484 Implements the |= operator. So s |= t returns rangeset s with
485 elements added from t. (Python version 2.5+ required)
486 """
487 self._binary_sanity_check(other)
488 return self.update(other)
489
491 """
492 s.intersection(t) returns a new rangeset with elements common
493 to s and t.
494 """
495 self_copy = copy.deepcopy(self)
496 self_copy.intersection_update(rangeset)
497 return self_copy
498
500 """
501 Implements the & operator. So s & t returns a new rangeset with
502 elements common to s and t.
503 """
504 self._binary_sanity_check(other)
505 return self.intersection(other)
506
508 """
509 Intersection with provided RangeSet.
510 """
511 self._ranges, self._length = self._intersect_exfold(rangeset)
512
514 """
515 Implements the &= operator. So s &= t returns rangeset s keeping
516 only elements also found in t. (Python version 2.5+ required)
517 """
518 self._binary_sanity_check(other)
519 return self.intersection_update(other)
520
522 """
523 Calc intersection with the expand/fold method.
524 """
525
526 items1, pad1 = self._expand()
527 items2, pad2 = rangeset._expand()
528
529
530 iset = dict.fromkeys(items2)
531
532
533 return self._fold([e for e in items1 if e in iset], pad1 or pad2)
534
536 """
537 s.difference(t) returns a new rangeset with elements in s but
538 not in t.
539 in t.
540 """
541 self_copy = copy.deepcopy(self)
542 self_copy.difference_update(rangeset)
543 return self_copy
544
546 """
547 Implement the - operator. So s - t returns a new rangeset with
548 elements in s but not in t.
549 """
550 self._binary_sanity_check(other)
551 return self.difference(other)
552
554 """
555 s.difference_update(t) returns rangeset s after removing
556 elements found in t. If strict is True, raise KeyError
557 if an element cannot be removed.
558 """
559 self._ranges, self._length = self._sub_exfold(rangeset, strict)
560
562 """
563 Implement the -= operator. So s -= t returns rangeset s after
564 removing elements found in t. (Python version 2.5+ required)
565 """
566 self._binary_sanity_check(other)
567 return self.difference_update(other)
568
570 """
571 Remove element elem from the RangeSet. Raise KeyError if elem
572 is not contained in the RangeSet.
573 """
574 items1, pad1 = self._expand()
575
576 try:
577 items1.remove(elem)
578 except ValueError, e:
579 raise KeyError, elem
580
581 self._ranges, self._length = self._fold(items1, pad1)
582
584 """
585 Calc sub/exclusion with the expand/fold method. If strict is
586 True, raise KeyError if the rangeset is not included.
587 """
588
589 items1, pad1 = self._expand()
590 items2, pad2 = rangeset._expand()
591
592
593 iset = dict.fromkeys(items2)
594
595 if strict:
596
597 lst = []
598 for e in items1:
599 if e not in iset:
600 lst.append(e)
601 else:
602 del iset[e]
603
604
605 if len(iset) > 0:
606
607 missing = RangeSet()
608 missing._ranges, missing._length = self._fold(iset.keys(), pad2)
609
610 raise KeyError, missing
611
612 return self._fold(lst, pad1 or pad2)
613 else:
614
615 return self._fold([e for e in items1 if e not in iset], pad1 or pad2)
616
618 """
619 s.symmetric_difference(t) returns the symmetric difference of
620 two rangesets as a new RangeSet.
621
622 (ie. all elements that are in exactly one of the rangesets.)
623 """
624 self_copy = copy.deepcopy(self)
625 self_copy.symmetric_difference_update(other)
626 return self_copy
627
629 """
630 Implement the ^ operator. So s ^ t returns a new rangeset with
631 elements that are in exactly one of the rangesets.
632 """
633 self._binary_sanity_check(other)
634 return self.symmetric_difference(other)
635
637 """
638 s.symmetric_difference_update(t) returns rangeset s keeping all
639 elements that are in exactly one of the rangesets.
640 """
641 self._ranges, self._length = self._xor_exfold(rangeset)
642
644 """
645 Implement the ^= operator. So s ^= t returns rangeset s after
646 keeping all elements that are in exactly one of the rangesets.
647 (Python version 2.5+ required)
648 """
649 self._binary_sanity_check(other)
650 return self.symmetric_difference_update(other)
651
653 """
654 Calc symmetric difference (xor).
655 """
656
657 items1, pad1 = self._expand()
658 items2, pad2 = rangeset._expand()
659
660 if pad1 != pad2:
661 raise RangeSetPaddingError()
662
663
664
665 iset1 = dict.fromkeys(items1)
666 iset2 = dict.fromkeys(items2)
667
668
669 allitems = items1 + items2
670 lst = [e for e in allitems if not e in iset1 or not e in iset2]
671 lst.sort()
672
673 return self._fold(lst, pad1)
674
675
677 """
678 Internal RangeSet generator for NodeSet or nodeset string pattern
679 parsing.
680 """
681
682 if isinstance(ns, NodeSet):
683 for pat, rangeset in ns._patterns.iteritems():
684 yield pat, rangeset
685
686 elif type(ns) is str:
687 single_node_re = None
688 pat = str(ns)
689
690 if pat.find('%') >= 0:
691 pat = pat.replace('%', '%%')
692 while pat is not None:
693
694 pat = pat.lstrip()
695
696
697 comma_idx = pat.find(',')
698 bracket_idx = pat.find('[')
699
700
701
702 if bracket_idx >= 0 and (comma_idx > bracket_idx or comma_idx < 0):
703
704
705
706
707
708
709 pfx, sfx = pat.split('[', 1)
710
711 try:
712 rg, sfx = sfx.split(']', 1)
713 except ValueError:
714 raise NodeSetParseError(pat, "missing bracket")
715
716
717 if sfx.find(',') < 0:
718 pat = None
719 else:
720 sfx, pat = sfx.split(',', 1)
721
722
723 sfx = sfx.rstrip()
724
725
726 if len(pfx) + len(sfx) == 0:
727 raise NodeSetParseError(pat, "empty node name")
728
729
730 try:
731 rset = RangeSet(rg, autostep)
732 except RangeSetParseError, e:
733 raise NodeSetParseRangeError(e)
734
735 yield "%s%%s%s" % (pfx, sfx), rset
736 else:
737
738
739
740 if comma_idx < 0:
741 node = pat
742 pat = None
743 else:
744 node, pat = pat.split(',', 1)
745
746 node = node.strip()
747
748 if len(node) == 0:
749 raise NodeSetParseError(pat, "empty node name")
750
751
752 if single_node_re is None:
753 single_node_re = re.compile("(\D*)(\d*)(.*)")
754
755 mo = single_node_re.match(node)
756 if not mo:
757 raise NodeSetParseError(pat, "parse error")
758 pfx, idx, sfx = mo.groups()
759 pfx, sfx = pfx or "", sfx or ""
760
761
762 if len(pfx) + len(sfx) == 0:
763 raise NodeSetParseError(pat, "empty node name")
764
765 if idx:
766 try:
767 rset = RangeSet(idx, autostep)
768 except RangeSetParseError, e:
769 raise NodeSetParseRangeError(e)
770 p = "%s%%s%s" % (pfx, sfx)
771 yield p, rset
772 else:
773
774 yield pfx, None
775
776
778 """
779 Iterable class of nodes with node ranges support.
780
781 NodeSet creation examples:
782 nodeset = NodeSet() # empty NodeSet
783 nodeset = NodeSet("clustername3") # contains only clustername3
784 nodeset = NodeSet("clustername[5,10-42]")
785 nodeset = NodeSet("clustername[0-10/2]")
786 nodeset = NodeSet("clustername[0-10/2],othername[7-9,120-300]")
787
788 NodeSet provides methods like update(), intersection_update() or
789 difference_update() methods, which conform to the Python Set API.
790 However, unlike RangeSet or standard Set, NodeSet is somewhat not
791 so strict for convenience, and understands NodeSet instance or
792 NodeSet string as argument. Also, there is no strict definition of
793 one element, for example, it IS allowed to do:
794 nodeset.remove("blue[36-40]").
795 """
796 - def __init__(self, pattern=None, autostep=None):
797 """
798 Initialize a NodeSet. If no pattern is specified, an empty
799 NodeSet is created.
800 """
801 self._autostep = autostep
802 self._length = 0
803 self._patterns = {}
804 if pattern is not None:
805 self.update(pattern)
806
808 """
809 Class method that returns a new NodeSet with nodes from
810 provided list.
811 """
812 inst = NodeSet(autostep=autostep)
813 for pat in l:
814 inst.update(pat)
815 return inst
816 fromlist = classmethod(fromlist)
817
819 """
820 Iterate over concret nodes.
821 """
822 for pat, rangeset in self._patterns.iteritems():
823 if rangeset:
824 for start, stop, step, pad in rangeset._ranges:
825 while start <= stop:
826 yield pat % ("%0*d" % (pad, start))
827 start += step
828 else:
829 yield pat
830
832 """
833 Get the number of nodes in NodeSet.
834 """
835 cnt = 0
836 for rangeset in self._patterns.itervalues():
837 if rangeset:
838 cnt += len(rangeset)
839 else:
840 cnt += 1
841 return cnt
842
844 """
845 Get pdsh-like, ranges-based pattern of node list.
846 """
847 result = ""
848 for pat, rangeset in self._patterns.iteritems():
849 if rangeset:
850 s = str(rangeset)
851 cnt = len(rangeset)
852 if cnt > 1:
853 s = "[" + s + "]"
854 result += pat % s
855 else:
856 result += pat
857 result += ","
858 return result[:-1]
859
861 """
862 Is node contained in NodeSet ?
863 """
864 return self.issuperset(other)
865
867
868
869 if not isinstance(other, NodeSet):
870 raise TypeError, "Binary operation only permitted between NodeSets"
871
873 """
874 Report whether another nodeset contains this nodeset.
875 """
876 binary = None
877
878
879 if isinstance(other, NodeSet):
880 binary = other
881 elif type(other) is str:
882 binary = NodeSet(other)
883 else:
884 raise TypeError, "Binary operation only permitted between NodeSets or string"
885
886 return binary.issuperset(self)
887
889 """
890 Report whether this nodeset contains another nodeset.
891 """
892 status = False
893 for pat, erangeset in _NodeSetParse(other, self._autostep):
894 rangeset = self._patterns.get(pat)
895 if rangeset:
896 status = rangeset.issuperset(erangeset)
897 else:
898
899 status = self._patterns.has_key(pat)
900 return status
901
902
903 __le__ = issubset
904 __ge__ = issuperset
905
907 """
908 x.__lt__(y) <==> x<y
909 """
910 return len(self) < len(other) and self.issubset(other)
911
913 """
914 x.__gt__(y) <==> x>y
915 """
916 return len(self) > len(other) and self.issuperset(other)
917
919 """
920 Return the node at index i. For convenience only, not
921 optimized as of version 1.0.
922 """
923 return list(self)[i]
924
926 """
927 Return the slice from index i to index j-1. For convenience
928 only, not optimized as of version 1.0.
929 """
930 return NodeSet.fromlist(list(self)[i:j])
931
933 """
934 Add a rangeset to a new or existing pattern.
935 """
936
937 pat_e = self._patterns.get(pat)
938
939 if pat_e:
940
941
942 assert rangeset != None
943
944
945 pat_e.update(rangeset)
946 else:
947
948 self._patterns[pat] = copy.copy(rangeset)
949
951 """
952 s.union(t) returns a new set with elements from both s and t.
953 """
954 self_copy = copy.deepcopy(self)
955 self_copy.update(other)
956 return self_copy
957
959 """
960 Implements the | operator. So s | t returns a new nodeset with
961 elements from both s and t.
962 """
963 self._binary_sanity_check(other)
964 return self.union(other)
965
966 - def add(self, other):
967 """
968 Add node to NodeSet.
969 """
970 self.update(other)
971
973 """
974 s.update(t) returns nodeset s with elements added from t.
975 """
976 for pat, rangeset in _NodeSetParse(other, self._autostep):
977 self._add_rangeset(pat, rangeset)
978
980 """
981 Remove all nodes from this nodeset.
982 """
983 self._patterns.clear()
984 self._length = 0
985
987 """
988 Implements the |= operator. So s |= t returns nodeset s with
989 elements added from t. (Python version 2.5+ required)
990 """
991 self._binary_sanity_check(other)
992 return self.update(other)
993
995 """
996 s.intersection(t) returns a new set with elements common to s
997 and t.
998 """
999 self_copy = copy.deepcopy(self)
1000 self_copy.intersection_update(other)
1001 return self_copy
1002
1004 """
1005 Implements the & operator. So s & t returns a new nodeset with
1006 elements common to s and t.
1007 """
1008 self._binary_sanity_check(other)
1009 return self.intersection(other)
1010
1012 """
1013 s.intersection_update(t) returns nodeset s keeping only
1014 elements also found in t.
1015 """
1016 if other is self:
1017 return
1018
1019 tmp_ns = NodeSet()
1020
1021 for pat, irangeset in _NodeSetParse(other, self._autostep):
1022 rangeset = self._patterns.get(pat)
1023 if rangeset:
1024 rs = copy.copy(rangeset)
1025 rs.intersection_update(irangeset)
1026
1027 if len(rs) > 0:
1028 tmp_ns._add_rangeset(pat, rs)
1029 elif not irangeset and pat in self._patterns:
1030
1031 tmp_ns._add_rangeset(pat, None)
1032
1033
1034 self._patterns = tmp_ns._patterns
1035
1037 """
1038 Implements the &= operator. So s &= t returns nodeset s keeping
1039 only elements also found in t. (Python version 2.5+ required)
1040 """
1041 self._binary_sanity_check(other)
1042 return self.intersection_update(other)
1043
1045 """
1046 s.difference(t) returns a new NodeSet with elements in s but not
1047 in t.
1048 """
1049 self_copy = copy.deepcopy(self)
1050 self_copy.difference_update(other)
1051 return self_copy
1052
1054 """
1055 Implement the - operator. So s - t returns a new nodeset with
1056 elements in s but not in t.
1057 """
1058 self._binary_sanity_check(other)
1059 return self.difference(other)
1060
1062 """
1063 s.difference_update(t) returns nodeset s after removing
1064 elements found in t. If strict is True, raise KeyError
1065 if an element cannot be removed.
1066 """
1067
1068 purge_patterns = []
1069
1070
1071 for pat, erangeset in _NodeSetParse(other, self._autostep):
1072
1073 rangeset = self._patterns.get(pat)
1074 if rangeset:
1075
1076 rangeset.difference_update(erangeset, strict)
1077
1078
1079 if len(rangeset) == 0:
1080 purge_patterns.append(pat)
1081 else:
1082
1083 if self._patterns.has_key(pat):
1084 purge_patterns.append(pat)
1085 elif strict:
1086 raise KeyError, pat
1087
1088 for pat in purge_patterns:
1089 del self._patterns[pat]
1090
1092 """
1093 Implement the -= operator. So s -= t returns nodeset s after
1094 removing elements found in t. (Python version 2.5+ required)
1095 """
1096 self._binary_sanity_check(other)
1097 return self.difference_update(other)
1098
1100 """
1101 Remove element elem from the nodeset. Raise KeyError if elem
1102 is not contained in the nodeset.
1103 """
1104 self.difference_update(elem, True)
1105
1107 """
1108 s.symmetric_difference(t) returns the symmetric difference of
1109 two nodesets as a new NodeSet.
1110
1111 (ie. all nodes that are in exactly one of the nodesets.)
1112 """
1113 self_copy = copy.deepcopy(self)
1114 self_copy.symmetric_difference_update(other)
1115 return self_copy
1116
1118 """
1119 Implement the ^ operator. So s ^ t returns a new NodeSet with
1120 nodes that are in exactly one of the nodesets.
1121 """
1122 self._binary_sanity_check(other)
1123 return self.symmetric_difference(other)
1124
1126 """
1127 s.symmetric_difference_update(t) returns nodeset s keeping all
1128 nodes that are in exactly one of the nodesets.
1129 """
1130 binary = None
1131
1132
1133 if isinstance(other, NodeSet):
1134 binary = other
1135 elif type(other) is str:
1136 binary = NodeSet(other)
1137 else:
1138 raise TypeError, "Binary operation only permitted between NodeSets or string"
1139
1140 purge_patterns = []
1141
1142
1143 for pat, rangeset in self._patterns.iteritems():
1144 brangeset = binary._patterns.get(pat)
1145 if brangeset:
1146 rangeset.symmetric_difference_update(brangeset)
1147 else:
1148 if binary._patterns.has_key(pat):
1149 purge_patterns.append(pat)
1150
1151
1152 for pat, brangeset in binary._patterns.iteritems():
1153 rangeset = self._patterns.get(pat)
1154 if not rangeset and not self._patterns.has_key(pat):
1155 self._add_rangeset(pat, brangeset)
1156
1157
1158 for pat, rangeset in self._patterns.iteritems():
1159 if rangeset is not None and len(rangeset) == 0:
1160 purge_patterns.append(pat)
1161
1162
1163 for pat in purge_patterns:
1164 del self._patterns[pat]
1165
1167 """
1168 Implement the ^= operator. So s ^= t returns nodeset s after
1169 keeping all nodes that are in exactly one of the nodesets.
1170 (Python version 2.5+ required)
1171 """
1172 self._binary_sanity_check(other)
1173 return self.symmetric_difference_update(other)
1174
1175
1177 """
1178 Commodity function that expands a pdsh-like pattern into a list of
1179 nodes.
1180 """
1181 return list(NodeSet(pat))
1182
1184 """
1185 Commodity function that clean dups and fold provided pattern with
1186 ranges and "/step" support.
1187 """
1188 return str(NodeSet(pat))
1189
1190
1191
1192
1194 import doctest
1195 doctest.testmod()
1196
1197 if __name__ == '__main__':
1198 _test()
1199