1use crate::range_set::RangeSet;
10use std::collections::HashMap;
11use std::fmt;
12use std::str::FromStr;
13use thiserror::Error;
14
15#[derive(Debug, Error)]
20pub enum NodeSetError {
21 #[error("parse error: {msg} (in \"{part}\")")]
22 ParseError { part: String, msg: String },
23
24 #[error("range error: {0}")]
25 RangeError(#[from] crate::range_set::RangeSetError),
26
27 #[error("external error: {0}")]
28 ExternalError(String),
29}
30
31pub type Result<T> = std::result::Result<T, NodeSetError>;
32
33#[derive(Debug, Clone)]
44struct NodeSetBase {
45 patterns: HashMap<String, Option<RangeSet>>,
46 autostep: Option<u32>,
47}
48
49impl NodeSetBase {
50 fn new() -> Self {
51 Self {
52 patterns: HashMap::new(),
53 autostep: None,
54 }
55 }
56
57 fn with_autostep(autostep: Option<u32>) -> Self {
58 Self {
59 patterns: HashMap::new(),
60 autostep,
61 }
62 }
63
64 fn add(&mut self, pat: &str, rangeset: Option<RangeSet>) {
66 if let Some(existing) = self.patterns.get_mut(pat) {
67 if let (Some(existing_rs), Some(new_rs)) = (existing.as_mut(), &rangeset) {
68 existing_rs.update(new_rs);
69 }
70 } else {
73 self.patterns.insert(pat.to_string(), rangeset);
74 }
75 }
76
77 fn len(&self) -> usize {
78 self.patterns
79 .values()
80 .map(|rs| match rs {
81 Some(rs) => rs.len(),
82 None => 1, })
84 .sum()
85 }
86
87 fn is_empty(&self) -> bool {
88 self.patterns.is_empty()
89 }
90
91 fn clear(&mut self) {
92 self.patterns.clear();
93 }
94
95 fn update(&mut self, other: &NodeSetBase) {
97 for (pat, rangeset) in &other.patterns {
98 self.add(pat, rangeset.clone());
99 }
100 }
101
102 fn intersection_update(&mut self, other: &NodeSetBase) {
104 let mut result = HashMap::new();
105 for (pat, other_rs) in &other.patterns {
106 if let Some(self_rs) = self.patterns.get(pat) {
107 match (self_rs, other_rs) {
108 (Some(s), Some(o)) => {
109 let intersection = s.intersection(o);
110 if intersection.len() > 0 {
111 result.insert(pat.clone(), Some(intersection));
112 }
113 }
114 (None, None) => {
115 result.insert(pat.clone(), None);
117 }
118 _ => {} }
120 }
121 }
122 self.patterns = result;
123 }
124
125 fn difference_update(&mut self, other: &NodeSetBase) {
127 let mut purge = Vec::new();
128 for (pat, other_rs) in &other.patterns {
129 if let Some(self_rs) = self.patterns.get_mut(pat) {
130 match (self_rs, other_rs) {
131 (Some(s), Some(o)) => {
132 s.difference_update(o);
133 if s.len() == 0 {
134 purge.push(pat.clone());
135 }
136 }
137 (None, None) => {
138 purge.push(pat.clone());
139 }
140 _ => {} }
142 }
143 }
144 for pat in purge {
145 self.patterns.remove(&pat);
146 }
147 }
148
149 fn symmetric_difference_update(&mut self, other: &NodeSetBase) {
151 let mut purge = Vec::new();
152
153 for (pat, self_rs) in self.patterns.iter_mut() {
155 if let Some(other_rs) = other.patterns.get(pat) {
156 match (self_rs.as_mut(), other_rs) {
157 (Some(s), Some(o)) => {
158 s.symmetric_difference_update(o);
159 if s.len() == 0 {
160 purge.push(pat.clone());
161 }
162 }
163 (None, None) => {
164 purge.push(pat.clone());
166 }
167 _ => {}
168 }
169 }
170 }
171
172 for (pat, other_rs) in &other.patterns {
174 if !self.patterns.contains_key(pat) {
175 self.add(pat, other_rs.clone());
176 }
177 }
178
179 for pat in purge {
180 self.patterns.remove(&pat);
181 }
182 }
183
184 fn contains(&self, node: &str) -> bool {
190 let (pat, indices) = parse_node_indices(node);
191
192 if let Some(rs_opt) = self.patterns.get(&pat) {
194 return match (rs_opt, indices.as_slice()) {
195 (Some(rs), &[(_, _)]) => {
196 let digit_str = extract_last_digit_str(node);
198 rs.contains_str(&digit_str)
199 }
200 (None, &[]) => true,
201 _ => false,
202 };
203 }
204
205 if indices.len() > 1 {
209 let digit_strs = extract_all_digit_strs(node);
211 for n_sub in 1..indices.len() {
213 let mut candidate_pat = pat.clone();
214 for i in 0..n_sub {
215 candidate_pat = candidate_pat.replacen("%s", &digit_strs[i], 1);
216 }
217 if let Some(Some(rs)) = self.patterns.get(&candidate_pat) {
218 let last_digit = &digit_strs[digit_strs.len() - 1];
220 if rs.contains_str(last_digit) {
221 return true;
222 }
223 }
224 }
225 }
226
227 false
228 }
229
230 fn iter(&self) -> Vec<String> {
232 let mut nodes = Vec::new();
233 for (pat, rs_opt) in &self.patterns {
234 match rs_opt {
235 Some(rs) => {
236 for s in rs.striter() {
237 nodes.push(pat.replacen("%s", &s, 1));
238 }
239 }
240 None => {
241 nodes.push(pat.clone());
242 }
243 }
244 }
245 nodes.sort();
246 nodes
247 }
248}
249
250impl fmt::Display for NodeSetBase {
251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252 let mut parts: Vec<String> = Vec::new();
253 let mut sorted_pats: Vec<_> = self.patterns.iter().collect();
255 sorted_pats.sort_by(|a, b| a.0.cmp(b.0));
256
257 for (pat, rs_opt) in sorted_pats {
258 match rs_opt {
259 Some(rs) => {
260 let rs_str = format!("{}", rs);
261 if rs.len() == 1 {
262 parts.push(pat.replacen("%s", &rs_str, 1));
264 } else {
265 let bracketed = format!("[{}]", rs_str);
267 parts.push(pat.replacen("%s", &bracketed, 1));
268 }
269 }
270 None => {
271 parts.push(pat.clone());
272 }
273 }
274 }
275 write!(f, "{}", parts.join(","))
276 }
277}
278
279#[allow(dead_code)]
286fn scan_node_single(node: &str) -> (String, Option<i64>) {
287 let mut pat = String::new();
293 let mut has_index = false;
294 let mut last_value: Option<i64> = None;
295
296 let chars: Vec<char> = node.chars().collect();
297 let len = chars.len();
298 let mut i = 0;
299
300 while i < len {
301 let prefix_start = i;
303 while i < len && !chars[i].is_ascii_digit() {
304 i += 1;
305 }
306 let prefix = &node[prefix_start..i];
307
308 let digit_start = i;
310 while i < len && chars[i].is_ascii_digit() {
311 i += 1;
312 }
313 let digits = &node[digit_start..i];
314
315 if digits.is_empty() {
316 pat.push_str(prefix);
317 } else {
318 pat.push_str(prefix);
319 pat.push_str("%s");
320 has_index = true;
321 last_value = Some(digits.parse().unwrap_or(0));
322 }
323 }
324
325 if has_index {
326 (pat, last_value)
327 } else {
328 (pat, None)
329 }
330}
331
332fn parse_node_indices(node: &str) -> (String, Vec<(i64, u32)>) {
335 let mut pat = String::new();
336 let mut indices = Vec::new();
337
338 let chars: Vec<char> = node.chars().collect();
339 let len = chars.len();
340 let mut i = 0;
341
342 while i < len {
343 let prefix_start = i;
344 while i < len && !chars[i].is_ascii_digit() {
345 i += 1;
346 }
347 pat.push_str(&node[prefix_start..i]);
348
349 let digit_start = i;
350 while i < len && chars[i].is_ascii_digit() {
351 i += 1;
352 }
353 let digits = &node[digit_start..i];
354
355 if !digits.is_empty() {
356 pat.push_str("%s");
357 let value: i64 = digits.parse().unwrap_or(0);
358 let pad = if value != 0 {
359 let stripped = digits.trim_start_matches('0');
360 if digits.len() > stripped.len() {
361 digits.len() as u32
362 } else {
363 0
364 }
365 } else if digits.len() > 1 {
366 digits.len() as u32
367 } else {
368 0
369 };
370 indices.push((value, pad));
371 }
372 }
373
374 (pat, indices)
375}
376
377fn parse_nodeset_string(input: &str, autostep: Option<u32>) -> Result<NodeSetBase> {
386 let input = input.trim();
387 if input.is_empty() {
388 return Ok(NodeSetBase::with_autostep(autostep));
389 }
390
391 let mut result = NodeSetBase::with_autostep(autostep);
392 let mut current_op = Op::Update; let tokens = tokenize(input)?;
397
398 for token in tokens {
399 match token {
400 Token::Operator(op) => {
401 current_op = op;
402 }
403 Token::Pattern(s) => {
404 let parsed = parse_single_pattern(&s, autostep)?;
405 match current_op {
406 Op::Update => result.update(&parsed),
407 Op::Difference => result.difference_update(&parsed),
408 Op::Intersection => result.intersection_update(&parsed),
409 Op::SymmetricDifference => result.symmetric_difference_update(&parsed),
410 }
411 }
412 }
413 }
414
415 Ok(result)
416}
417
418#[derive(Debug, Clone, Copy)]
419enum Op {
420 Update,
421 Difference,
422 Intersection,
423 SymmetricDifference,
424}
425
426#[derive(Debug)]
427enum Token {
428 Operator(Op),
429 Pattern(String),
430}
431
432fn tokenize(input: &str) -> Result<Vec<Token>> {
434 let mut tokens = Vec::new();
435 let mut current = String::new();
436 let mut bracket_depth = 0;
437 let chars: Vec<char> = input.chars().collect();
438 let mut i = 0;
439
440 while i < chars.len() {
441 let ch = chars[i];
442 match ch {
443 '[' => {
444 bracket_depth += 1;
445 current.push(ch);
446 }
447 ']' => {
448 if bracket_depth == 0 {
449 return Err(NodeSetError::ParseError {
450 part: input.to_string(),
451 msg: "illegal closing bracket".to_string(),
452 });
453 }
454 bracket_depth -= 1;
455 current.push(ch);
456 }
457 ',' | '!' | '&' | '^' if bracket_depth == 0 => {
458 if !current.is_empty() {
460 tokens.push(Token::Pattern(current.trim().to_string()));
461 current = String::new();
462 }
463 let op = match ch {
464 ',' => Op::Update,
465 '!' => Op::Difference,
466 '&' => Op::Intersection,
467 '^' => Op::SymmetricDifference,
468 _ => unreachable!(),
469 };
470 tokens.push(Token::Operator(op));
471 }
472 _ => {
473 current.push(ch);
474 }
475 }
476 i += 1;
477 }
478
479 if bracket_depth != 0 {
480 return Err(NodeSetError::ParseError {
481 part: input.to_string(),
482 msg: "missing bracket".to_string(),
483 });
484 }
485
486 if !current.is_empty() {
487 tokens.push(Token::Pattern(current.trim().to_string()));
488 }
489
490 let mut result = Vec::new();
493 let mut expect_pattern = true;
494 for token in tokens {
495 match &token {
496 Token::Pattern(_) => {
497 if expect_pattern {
498 if result.is_empty() {
499 result.push(Token::Operator(Op::Update));
501 }
502 result.push(token);
503 expect_pattern = false;
504 } else {
505 result.push(Token::Operator(Op::Update));
508 result.push(token);
509 expect_pattern = false;
510 }
511 }
512 Token::Operator(_) => {
513 result.push(token);
514 expect_pattern = true;
515 }
516 }
517 }
518
519 Ok(result)
520}
521
522fn parse_single_pattern(pat: &str, autostep: Option<u32>) -> Result<NodeSetBase> {
525 let mut base = NodeSetBase::with_autostep(autostep);
526
527 if pat.contains('[') {
528 parse_bracketed(pat, autostep, &mut base)?;
530 } else {
531 let (pattern, indices) = parse_node_indices(pat);
533 if indices.is_empty() {
534 base.add(&pattern, None);
535 } else {
536 let mut rs = RangeSet::new();
538 rs.set_autostep(autostep);
539 if indices.len() == 1 {
541 let (val, pad) = indices[0];
542 rs.add_int(val, pad);
543 base.add(&pattern, Some(rs));
544 } else {
545 let digit_strs = extract_all_digit_strs(pat);
550 let mut expanded_pat = pattern.clone();
551 for ds in &digit_strs[..digit_strs.len() - 1] {
552 expanded_pat = expanded_pat.replacen("%s", ds, 1);
553 }
554 let (val, pad) = indices[indices.len() - 1];
555 rs.add_int(val, pad);
556 base.add(&expanded_pat, Some(rs));
557 }
558 }
559 }
560
561 Ok(base)
562}
563
564fn parse_bracketed(pat: &str, autostep: Option<u32>, base: &mut NodeSetBase) -> Result<()> {
566 let mut remaining = pat;
568 let mut template = String::new();
569 let mut rangesets: Vec<RangeSet> = Vec::new();
570
571 while let Some(bracket_start) = remaining.find('[') {
572 let prefix = &remaining[..bracket_start];
573
574 let (clean_prefix, leading_digits) = split_trailing_digits(prefix);
577 template.push_str(clean_prefix);
578 template.push_str("%s");
579
580 let after_bracket = &remaining[bracket_start + 1..];
581 let bracket_end = after_bracket
582 .find(']')
583 .ok_or_else(|| NodeSetError::ParseError {
584 part: pat.to_string(),
585 msg: "missing bracket".to_string(),
586 })?;
587
588 let range_str = &after_bracket[..bracket_end];
589 remaining = &after_bracket[bracket_end + 1..];
590
591 let (trailing_digits, clean_remaining) = split_leading_digits(remaining);
594
595 let amended_range = amend_range(range_str, &leading_digits, &trailing_digits)?;
597
598 let rs = RangeSet::parse(&amended_range, autostep)?;
599 rangesets.push(rs);
600
601 remaining = clean_remaining;
602 }
603
604 if !remaining.is_empty() {
606 template.push_str(remaining);
608 }
609
610 if rangesets.len() == 1 {
612 base.add(&template, Some(rangesets.into_iter().next().unwrap()));
613 } else if rangesets.is_empty() {
614 base.add(&template, None);
615 } else {
616 expand_multi_dim(&template, &rangesets, base);
619 }
620
621 Ok(())
622}
623
624fn expand_multi_dim(template: &str, rangesets: &[RangeSet], base: &mut NodeSetBase) {
626 if rangesets.is_empty() {
629 return;
630 }
631
632 if rangesets.len() == 1 {
633 base.add(template, Some(rangesets[0].clone()));
634 return;
635 }
636
637 let last_rs = &rangesets[rangesets.len() - 1];
639 let prefixes = expand_dimensions(&rangesets[..rangesets.len() - 1]);
640
641 for prefix_vals in &prefixes {
643 let mut pat = template.to_string();
644 for val in prefix_vals {
645 pat = pat.replacen("%s", val, 1);
646 }
647 base.add(&pat, Some(last_rs.clone()));
649 }
650}
651
652fn expand_dimensions(rangesets: &[RangeSet]) -> Vec<Vec<String>> {
654 if rangesets.is_empty() {
655 return vec![vec![]];
656 }
657
658 let first_vals: Vec<String> = rangesets[0].striter().collect();
659 let rest = expand_dimensions(&rangesets[1..]);
660
661 let mut result = Vec::new();
662 for val in &first_vals {
663 for suffix in &rest {
664 let mut combined = vec![val.clone()];
665 combined.extend(suffix.iter().cloned());
666 result.push(combined);
667 }
668 }
669 result
670}
671
672fn extract_last_digit_str(node: &str) -> String {
675 let bytes = node.as_bytes();
676 let mut end = bytes.len();
677 while end > 0 && !bytes[end - 1].is_ascii_digit() {
678 end -= 1;
679 }
680 let mut start = end;
681 while start > 0 && bytes[start - 1].is_ascii_digit() {
682 start -= 1;
683 }
684 node[start..end].to_string()
685}
686
687fn extract_all_digit_strs(node: &str) -> Vec<String> {
690 let mut result = Vec::new();
691 let chars: Vec<char> = node.chars().collect();
692 let len = chars.len();
693 let mut i = 0;
694
695 while i < len {
696 while i < len && !chars[i].is_ascii_digit() {
698 i += 1;
699 }
700 let start = i;
701 while i < len && chars[i].is_ascii_digit() {
702 i += 1;
703 }
704 if i > start {
705 result.push(node[start..i].to_string());
706 }
707 }
708 result
709}
710
711fn split_trailing_digits(s: &str) -> (&str, String) {
713 let bytes = s.as_bytes();
714 let mut i = bytes.len();
715 while i > 0 && bytes[i - 1].is_ascii_digit() {
716 i -= 1;
717 }
718 (&s[..i], s[i..].to_string())
719}
720
721fn split_leading_digits(s: &str) -> (String, &str) {
723 let bytes = s.as_bytes();
724 let mut i = 0;
725 while i < bytes.len() && bytes[i].is_ascii_digit() {
726 i += 1;
727 }
728 (s[..i].to_string(), &s[i..])
729}
730
731fn amend_range(range: &str, leading: &str, trailing: &str) -> Result<String> {
736 let mut result = range.to_string();
737
738 if !leading.is_empty() {
739 let rs = RangeSet::parse(&result, None)?;
742 let mut parts = Vec::new();
743 for val_str in rs.striter() {
745 let combined = format!("{}{}", leading, val_str);
746 parts.push(combined);
747 }
748 result = parts.join(",");
750 }
751
752 if !trailing.is_empty() {
753 let rs = RangeSet::parse(&result, None)?;
755 let mut parts = Vec::new();
756 let trail_len = trailing.len();
757 let power = 10i64.pow(trail_len as u32);
758 let trail_val: i64 = trailing.parse().unwrap_or(0);
759
760 for val in rs.intiter() {
761 let combined = val * power + trail_val;
762 parts.push(format!("{}", combined));
763 }
764 result = parts.join(",");
765 }
766
767 Ok(result)
768}
769
770#[derive(Debug, Clone)]
787pub struct NodeSet {
788 base: NodeSetBase,
789}
790
791impl NodeSet {
792 pub fn new() -> Self {
794 Self {
795 base: NodeSetBase::new(),
796 }
797 }
798
799 pub fn parse(pattern: &str) -> Result<Self> {
801 let base = parse_nodeset_string(pattern, None)?;
802 Ok(Self { base })
803 }
804
805 pub fn parse_with_autostep(pattern: &str, autostep: Option<u32>) -> Result<Self> {
807 let base = parse_nodeset_string(pattern, autostep)?;
808 Ok(Self { base })
809 }
810
811 pub fn len(&self) -> usize {
813 self.base.len()
814 }
815
816 pub fn is_empty(&self) -> bool {
818 self.base.is_empty()
819 }
820
821 pub fn contains(&self, node: &str) -> bool {
823 self.base.contains(node)
824 }
825
826 pub fn update(&mut self, other: &NodeSet) {
828 self.base.update(&other.base);
829 }
830
831 pub fn update_str(&mut self, pattern: &str) -> Result<()> {
833 let other = parse_nodeset_string(pattern, self.base.autostep)?;
834 self.base.update(&other);
835 Ok(())
836 }
837
838 pub fn union(&self, other: &NodeSet) -> NodeSet {
840 let mut copy = self.clone();
841 copy.update(other);
842 copy
843 }
844
845 pub fn intersection_update(&mut self, other: &NodeSet) {
847 self.base.intersection_update(&other.base);
848 }
849
850 pub fn intersection(&self, other: &NodeSet) -> NodeSet {
852 let mut copy = self.clone();
853 copy.intersection_update(other);
854 copy
855 }
856
857 pub fn difference_update(&mut self, other: &NodeSet) {
859 self.base.difference_update(&other.base);
860 }
861
862 pub fn difference(&self, other: &NodeSet) -> NodeSet {
864 let mut copy = self.clone();
865 copy.difference_update(other);
866 copy
867 }
868
869 pub fn symmetric_difference_update(&mut self, other: &NodeSet) {
871 self.base.symmetric_difference_update(&other.base);
872 }
873
874 pub fn symmetric_difference(&self, other: &NodeSet) -> NodeSet {
876 let mut copy = self.clone();
877 copy.symmetric_difference_update(other);
878 copy
879 }
880
881 pub fn clear(&mut self) {
883 self.base.clear();
884 }
885
886 pub fn iter(&self) -> impl Iterator<Item = String> + '_ {
888 self.base.iter().into_iter()
889 }
890
891 pub fn split(&self, nbr: usize) -> Vec<NodeSet> {
893 assert!(nbr > 0);
894 let all_nodes: Vec<String> = self.iter().collect();
895 let total = all_nodes.len();
896 let slice_size = total / nbr;
897 let leftover = total % nbr;
898
899 let mut result = Vec::new();
900 let mut begin = 0;
901
902 for i in 0..nbr.min(total) {
903 let length = slice_size + if i < leftover { 1 } else { 0 };
904 let slice = &all_nodes[begin..begin + length];
905 let pattern = slice.join(",");
906 if let Ok(ns) = NodeSet::parse(&pattern) {
907 result.push(ns);
908 }
909 begin += length;
910 }
911
912 result
913 }
914
915 pub fn is_subset(&self, other: &NodeSet) -> bool {
917 self.difference(other).is_empty()
918 }
919
920 pub fn is_superset(&self, other: &NodeSet) -> bool {
922 other.is_subset(self)
923 }
924}
925
926impl Default for NodeSet {
927 fn default() -> Self {
928 Self::new()
929 }
930}
931
932impl fmt::Display for NodeSet {
933 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
934 write!(f, "{}", self.base)
935 }
936}
937
938impl FromStr for NodeSet {
939 type Err = NodeSetError;
940
941 fn from_str(s: &str) -> Result<Self> {
942 NodeSet::parse(s)
943 }
944}
945
946pub fn expand(pat: &str) -> Result<Vec<String>> {
958 let ns = NodeSet::parse(pat)?;
959 Ok(ns.iter().collect())
960}
961
962pub fn fold(pat: &str) -> Result<String> {
970 let ns = NodeSet::parse(pat)?;
971 Ok(format!("{}", ns))
972}
973
974#[cfg(test)]
979mod tests {
980 use super::*;
981
982 #[test]
983 fn test_new_is_empty() {
984 let ns = NodeSet::new();
985 assert!(ns.is_empty());
986 assert_eq!(ns.len(), 0);
987 }
988
989 #[test]
990 fn test_parse_simple_node() {
991 let ns = NodeSet::parse("node1").unwrap();
992 assert_eq!(ns.len(), 1);
993 assert!(ns.contains("node1"));
994 assert!(!ns.contains("node2"));
995 }
996
997 #[test]
998 fn test_parse_range() {
999 let ns = NodeSet::parse("node[1-5]").unwrap();
1000 assert_eq!(ns.len(), 5);
1001 for i in 1..=5 {
1002 assert!(ns.contains(&format!("node{}", i)));
1003 }
1004 assert!(!ns.contains("node0"));
1005 assert!(!ns.contains("node6"));
1006 }
1007
1008 #[test]
1009 fn test_parse_multiple_ranges() {
1010 let ns = NodeSet::parse("node[1-3,7-9]").unwrap();
1011 assert_eq!(ns.len(), 6);
1012 assert!(ns.contains("node1"));
1013 assert!(ns.contains("node3"));
1014 assert!(!ns.contains("node4"));
1015 assert!(ns.contains("node7"));
1016 assert!(ns.contains("node9"));
1017 }
1018
1019 #[test]
1020 fn test_parse_padded() {
1021 let ns = NodeSet::parse("node[001-003]").unwrap();
1022 assert_eq!(ns.len(), 3);
1023 assert!(ns.contains("node001"));
1024 assert!(ns.contains("node002"));
1025 assert!(ns.contains("node003"));
1026 let display = format!("{}", ns);
1027 assert!(display.contains("001"));
1028 }
1029
1030 #[test]
1031 fn test_parse_comma_separated() {
1032 let ns = NodeSet::parse("node1,node2,node3").unwrap();
1033 assert_eq!(ns.len(), 3);
1034 assert!(ns.contains("node1"));
1035 assert!(ns.contains("node2"));
1036 assert!(ns.contains("node3"));
1037 }
1038
1039 #[test]
1040 fn test_parse_difference_operator() {
1041 let ns = NodeSet::parse("node[1-10]!node[5-7]").unwrap();
1042 assert_eq!(ns.len(), 7);
1043 assert!(ns.contains("node1"));
1044 assert!(ns.contains("node4"));
1045 assert!(!ns.contains("node5"));
1046 assert!(!ns.contains("node6"));
1047 assert!(!ns.contains("node7"));
1048 assert!(ns.contains("node8"));
1049 assert!(ns.contains("node10"));
1050 }
1051
1052 #[test]
1053 fn test_parse_intersection_operator() {
1054 let ns = NodeSet::parse("node[1-10]&node[5-15]").unwrap();
1055 assert_eq!(ns.len(), 6);
1056 for i in 5..=10 {
1057 assert!(ns.contains(&format!("node{}", i)));
1058 }
1059 assert!(!ns.contains("node1"));
1060 assert!(!ns.contains("node11"));
1061 }
1062
1063 #[test]
1064 fn test_parse_xor_operator() {
1065 let ns = NodeSet::parse("node[1-5]^node[3-7]").unwrap();
1066 assert_eq!(ns.len(), 4);
1067 assert!(ns.contains("node1"));
1068 assert!(ns.contains("node2"));
1069 assert!(!ns.contains("node3"));
1070 assert!(!ns.contains("node5"));
1071 assert!(ns.contains("node6"));
1072 assert!(ns.contains("node7"));
1073 }
1074
1075 #[test]
1076 fn test_multi_pattern() {
1077 let ns = NodeSet::parse("web[1-3],db[1-2]").unwrap();
1078 assert_eq!(ns.len(), 5);
1079 assert!(ns.contains("web1"));
1080 assert!(ns.contains("web3"));
1081 assert!(ns.contains("db1"));
1082 assert!(ns.contains("db2"));
1083 }
1084
1085 #[test]
1086 fn test_display_fold() {
1087 let ns = NodeSet::parse("node3,node1,node2").unwrap();
1088 let display = format!("{}", ns);
1089 assert_eq!(display, "node[1-3]");
1090 }
1091
1092 #[test]
1093 fn test_set_union() {
1094 let a = NodeSet::parse("node[1-3]").unwrap();
1095 let b = NodeSet::parse("node[5-7]").unwrap();
1096 let c = a.union(&b);
1097 assert_eq!(c.len(), 6);
1098 }
1099
1100 #[test]
1101 fn test_set_intersection() {
1102 let a = NodeSet::parse("node[1-10]").unwrap();
1103 let b = NodeSet::parse("node[5-15]").unwrap();
1104 let c = a.intersection(&b);
1105 assert_eq!(c.len(), 6);
1106 }
1107
1108 #[test]
1109 fn test_set_difference() {
1110 let a = NodeSet::parse("node[1-10]").unwrap();
1111 let b = NodeSet::parse("node[5-7]").unwrap();
1112 let c = a.difference(&b);
1113 assert_eq!(c.len(), 7);
1114 }
1115
1116 #[test]
1117 fn test_set_symmetric_difference() {
1118 let a = NodeSet::parse("node[1-5]").unwrap();
1119 let b = NodeSet::parse("node[3-7]").unwrap();
1120 let c = a.symmetric_difference(&b);
1121 assert_eq!(c.len(), 4);
1122 }
1123
1124 #[test]
1125 fn test_iter_sorted() {
1126 let ns = NodeSet::parse("node[3,1,2]").unwrap();
1127 let nodes: Vec<String> = ns.iter().collect();
1128 assert_eq!(nodes, vec!["node1", "node2", "node3"]);
1129 }
1130
1131 #[test]
1132 fn test_non_indexed_node() {
1133 let ns = NodeSet::parse("switch").unwrap();
1134 assert_eq!(ns.len(), 1);
1135 assert!(ns.contains("switch"));
1136 }
1137
1138 #[test]
1139 fn test_expand_function() {
1140 let nodes = expand("node[1-3]").unwrap();
1141 assert_eq!(nodes, vec!["node1", "node2", "node3"]);
1142 }
1143
1144 #[test]
1145 fn test_fold_function() {
1146 let s = fold("node3,node1,node2").unwrap();
1147 assert_eq!(s, "node[1-3]");
1148 }
1149
1150 #[test]
1151 fn test_from_str() {
1152 let ns: NodeSet = "node[1-3]".parse().unwrap();
1153 assert_eq!(ns.len(), 3);
1154 }
1155
1156 #[test]
1157 fn test_update_str() {
1158 let mut ns = NodeSet::parse("node[1-3]").unwrap();
1159 ns.update_str("node[5-7]").unwrap();
1160 assert_eq!(ns.len(), 6);
1161 }
1162
1163 #[test]
1164 fn test_split() {
1165 let ns = NodeSet::parse("node[1-6]").unwrap();
1166 let parts = ns.split(3);
1167 assert_eq!(parts.len(), 3);
1168 assert_eq!(parts[0].len(), 2);
1169 assert_eq!(parts[1].len(), 2);
1170 assert_eq!(parts[2].len(), 2);
1171 }
1172
1173 #[test]
1174 fn test_subset_superset() {
1175 let a = NodeSet::parse("node[1-5]").unwrap();
1176 let b = NodeSet::parse("node[1-10]").unwrap();
1177 assert!(a.is_subset(&b));
1178 assert!(b.is_superset(&a));
1179 assert!(!b.is_subset(&a));
1180 }
1181
1182 #[test]
1183 fn test_multi_dimensional() {
1184 let ns = NodeSet::parse("rack[1-2]-node[1-3]").unwrap();
1185 assert_eq!(ns.len(), 6);
1186 assert!(ns.contains("rack1-node1"));
1187 assert!(ns.contains("rack1-node3"));
1188 assert!(ns.contains("rack2-node1"));
1189 assert!(ns.contains("rack2-node3"));
1190 assert!(!ns.contains("rack3-node1"));
1191 }
1192
1193 #[test]
1194 fn test_empty_parse() {
1195 let ns = NodeSet::parse("").unwrap();
1196 assert!(ns.is_empty());
1197 }
1198
1199 #[test]
1200 fn test_missing_bracket_error() {
1201 let result = NodeSet::parse("node[1-3");
1202 assert!(result.is_err());
1203 }
1204
1205 #[test]
1206 fn test_clear() {
1207 let mut ns = NodeSet::parse("node[1-5]").unwrap();
1208 assert!(!ns.is_empty());
1209 ns.clear();
1210 assert!(ns.is_empty());
1211 }
1212
1213 #[test]
1216 fn test_dotted_hostname_single() {
1217 let ns = NodeSet::parse("server1.example.com").unwrap();
1218 assert_eq!(ns.len(), 1);
1219 let nodes: Vec<String> = ns.iter().collect();
1220 assert_eq!(nodes, vec!["server1.example.com"]);
1221 assert_eq!(format!("{ns}"), "server1.example.com");
1222 }
1223
1224 #[test]
1225 fn test_dotted_hostname_multiple() {
1226 let ns = NodeSet::parse("server1.local,server2.local").unwrap();
1227 assert_eq!(ns.len(), 2);
1228 let nodes: Vec<String> = ns.iter().collect();
1229 assert_eq!(nodes, vec!["server1.local", "server2.local"]);
1230 }
1231
1232 #[test]
1233 fn test_ip_address_single() {
1234 let ns = NodeSet::parse("192.168.1.29").unwrap();
1235 assert_eq!(ns.len(), 1);
1236 let nodes: Vec<String> = ns.iter().collect();
1237 assert_eq!(nodes, vec!["192.168.1.29"]);
1238 assert_eq!(format!("{ns}"), "192.168.1.29");
1239 }
1240
1241 #[test]
1242 fn test_ip_address_multiple_same_subnet() {
1243 let ns = NodeSet::parse("192.168.1.3,192.168.1.29").unwrap();
1244 assert_eq!(ns.len(), 2);
1245 let mut nodes: Vec<String> = ns.iter().collect();
1246 nodes.sort();
1247 assert_eq!(nodes, vec!["192.168.1.29", "192.168.1.3"]);
1248 assert_eq!(format!("{ns}"), "192.168.1.[3,29]");
1249 }
1250
1251 #[test]
1252 fn test_ip_address_different_subnets() {
1253 let ns = NodeSet::parse("10.0.0.1,192.168.1.1").unwrap();
1254 assert_eq!(ns.len(), 2);
1255 let mut nodes: Vec<String> = ns.iter().collect();
1256 nodes.sort();
1257 assert_eq!(nodes, vec!["10.0.0.1", "192.168.1.1"]);
1258 }
1259
1260 #[test]
1261 fn test_ip_address_contains() {
1262 let ns = NodeSet::parse("192.168.1.3,192.168.1.29").unwrap();
1263 assert!(ns.contains("192.168.1.3"));
1264 assert!(ns.contains("192.168.1.29"));
1265 assert!(!ns.contains("192.168.1.4"));
1266 assert!(!ns.contains("10.0.0.1"));
1267 }
1268
1269 #[test]
1270 fn test_dotted_hostname_with_numbers() {
1271 let ns = NodeSet::parse("node1.rack2.dc3").unwrap();
1273 assert_eq!(ns.len(), 1);
1274 let nodes: Vec<String> = ns.iter().collect();
1275 assert_eq!(nodes, vec!["node1.rack2.dc3"]);
1276 }
1277
1278 #[test]
1279 fn test_mdns_hostname() {
1280 let ns = NodeSet::parse("GN9CFLM92K-MBP.local").unwrap();
1281 assert_eq!(ns.len(), 1);
1282 let nodes: Vec<String> = ns.iter().collect();
1283 assert_eq!(nodes, vec!["GN9CFLM92K-MBP.local"]);
1284 }
1285
1286 #[test]
1287 fn test_mdns_hostname_comma_list() {
1288 let ns = NodeSet::parse("CK2Q9LN7PM-MBA.local,GJHC5VVN49-MBP.local,GN9CFLM92K-MBP.local")
1289 .unwrap();
1290 assert_eq!(ns.len(), 3);
1291 let nodes: Vec<String> = ns.iter().collect();
1292 assert!(nodes.contains(&"CK2Q9LN7PM-MBA.local".to_string()));
1293 assert!(nodes.contains(&"GJHC5VVN49-MBP.local".to_string()));
1294 assert!(nodes.contains(&"GN9CFLM92K-MBP.local".to_string()));
1295 }
1296
1297 #[test]
1298 fn test_thunderbolt_hostname() {
1299 let ns = NodeSet::parse("CK2Q9LN7PM-MBA.tb").unwrap();
1300 assert_eq!(ns.len(), 1);
1301 let nodes: Vec<String> = ns.iter().collect();
1302 assert_eq!(nodes, vec!["CK2Q9LN7PM-MBA.tb"]);
1303 }
1304}