interface_rs/helper/
sort.rs

1use std::cmp::Ordering;
2
3/// Compares two strings using natural sort order.
4/// Natural sort order compares numbers in a way humans expect,
5/// so "swp10" comes after "swp2" instead of "swp10" being less than "swp2" as in ASCII sorting.
6///
7/// # Arguments
8/// - `a`: The first string to compare.
9/// - `b`: The second string to compare.
10///
11/// # Returns
12/// An `Ordering` indicating the result of the comparison.
13pub fn natural(a: &str, b: &str) -> Ordering {
14    let mut a_iter = a.chars().peekable();
15    let mut b_iter = b.chars().peekable();
16
17    loop {
18        match (a_iter.peek(), b_iter.peek()) {
19            (Some(a_c), Some(b_c)) => {
20                if a_c.is_ascii_digit() && b_c.is_ascii_digit() {
21                    // Extract full numbers
22                    let mut a_num = String::new();
23                    while let Some(c) = a_iter.peek() {
24                        if c.is_ascii_digit() {
25                            a_num.push(*c);
26                            a_iter.next();
27                        } else {
28                            break;
29                        }
30                    }
31
32                    let mut b_num = String::new();
33                    while let Some(c) = b_iter.peek() {
34                        if c.is_ascii_digit() {
35                            b_num.push(*c);
36                            b_iter.next();
37                        } else {
38                            break;
39                        }
40                    }
41
42                    // Safe to unwrap: we only collected digit characters above
43                    let a_int = a_num.parse::<u64>().expect("a_num contains only digits");
44                    let b_int = b_num.parse::<u64>().expect("b_num contains only digits");
45
46                    match a_int.cmp(&b_int) {
47                        Ordering::Equal => continue,
48                        other => return other,
49                    }
50                } else {
51                    // Compare characters
52                    match a_c.cmp(b_c) {
53                        Ordering::Equal => {
54                            a_iter.next();
55                            b_iter.next();
56                            continue;
57                        }
58                        other => return other,
59                    }
60                }
61            }
62            (Some(_), None) => return Ordering::Greater,
63            (None, Some(_)) => return Ordering::Less,
64            (None, None) => return Ordering::Equal,
65        }
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_sort_natural_basic() {
75        assert_eq!(natural("swp1", "swp2"), Ordering::Less);
76        assert_eq!(natural("swp10", "swp2"), Ordering::Greater);
77        assert_eq!(natural("swp10", "swp10"), Ordering::Equal);
78        assert_eq!(natural("swp100", "swp20"), Ordering::Greater);
79        assert_eq!(natural("swp2", "swp2"), Ordering::Equal);
80        assert_eq!(natural("swp10s1", "swp10s2"), Ordering::Less);
81        assert_eq!(natural("swp10", "swp9s1"), Ordering::Greater);
82    }
83
84    #[test]
85    fn test_sort_natural_different_prefixes() {
86        assert_eq!(natural("eth0", "swp0"), Ordering::Less);
87        assert_eq!(natural("lo", "eth0"), Ordering::Greater);
88        assert_eq!(natural("bridge", "bond0"), Ordering::Greater);
89    }
90
91    #[test]
92    fn test_sort_natural_vlan_interfaces() {
93        assert_eq!(natural("vlan1", "vlan2"), Ordering::Less);
94        assert_eq!(natural("vlan10", "vlan9"), Ordering::Greater);
95        assert_eq!(natural("vlan100", "vlan1000"), Ordering::Less);
96    }
97
98    #[test]
99    fn test_sort_natural_empty_and_numeric() {
100        assert_eq!(natural("", ""), Ordering::Equal);
101        assert_eq!(natural("a", ""), Ordering::Greater);
102        assert_eq!(natural("", "a"), Ordering::Less);
103        assert_eq!(natural("1", "2"), Ordering::Less);
104        assert_eq!(natural("10", "2"), Ordering::Greater);
105    }
106
107    #[test]
108    fn test_sort_natural_leading_zeros() {
109        // Leading zeros should be treated as the same number
110        assert_eq!(natural("swp01", "swp1"), Ordering::Equal);
111        assert_eq!(natural("swp001", "swp1"), Ordering::Equal);
112    }
113
114    #[test]
115    fn test_sort_vec() {
116        let mut interfaces = vec!["swp10", "swp2", "swp1", "eth0", "lo", "swp20"];
117        interfaces.sort_by(|a, b| natural(a, b));
118        assert_eq!(interfaces, vec!["eth0", "lo", "swp1", "swp2", "swp10", "swp20"]);
119    }
120}