snix_eval/builtins/
versions.rs1use std::cmp::Ordering;
2use std::iter::{once, Chain, Once};
3use std::ops::RangeInclusive;
4
5use bstr::{BStr, ByteSlice, B};
6
7#[derive(PartialEq, Eq, Clone, Debug)]
11pub enum VersionPart<'a> {
12 Word(&'a BStr),
13 Number(&'a BStr),
14}
15
16impl PartialOrd for VersionPart<'_> {
17 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
18 Some(self.cmp(other))
19 }
20}
21
22impl Ord for VersionPart<'_> {
23 fn cmp(&self, other: &Self) -> Ordering {
24 match (self, other) {
25 (VersionPart::Number(s1), VersionPart::Number(s2)) => {
26 let n1: u64 = s1.to_str_lossy().parse().unwrap();
29 let n2: u64 = s2.to_str_lossy().parse().unwrap();
30 n1.cmp(&n2)
31 }
32
33 (VersionPart::Word(x), VersionPart::Word(y)) if *x == B("pre") && *y == B("pre") => {
35 Ordering::Equal
36 }
37 (VersionPart::Word(x), _) if *x == B("pre") => Ordering::Less,
38 (_, VersionPart::Word(y)) if *y == B("pre") => Ordering::Greater,
39
40 (VersionPart::Number(_), VersionPart::Word(_)) => Ordering::Greater,
42 (VersionPart::Word(_), VersionPart::Number(_)) => Ordering::Less,
43
44 (VersionPart::Word(w1), VersionPart::Word(w2)) => w1.cmp(w2),
45 }
46 }
47}
48
49enum InternalPart {
51 Number { range: RangeInclusive<usize> },
52 Word { range: RangeInclusive<usize> },
53 Break,
54}
55
56pub struct VersionPartsIter<'a> {
60 cached_part: InternalPart,
61 iter: bstr::CharIndices<'a>,
62 version: &'a BStr,
63}
64
65impl<'a> VersionPartsIter<'a> {
66 pub fn new(version: &'a BStr) -> Self {
67 Self {
68 cached_part: InternalPart::Break,
69 iter: version.char_indices(),
70 version,
71 }
72 }
73
74 pub fn new_for_cmp(version: &'a BStr) -> Chain<Self, Once<VersionPart<'a>>> {
85 Self::new(version).chain(once(VersionPart::Word("".into())))
86 }
87}
88
89impl<'a> Iterator for VersionPartsIter<'a> {
90 type Item = VersionPart<'a>;
91
92 fn next(&mut self) -> Option<Self::Item> {
93 let char = self.iter.next();
94
95 if char.is_none() {
96 let cached_part = std::mem::replace(&mut self.cached_part, InternalPart::Break);
97 match cached_part {
98 InternalPart::Break => return None,
99 InternalPart::Number { range } => {
100 return Some(VersionPart::Number(&self.version[range]))
101 }
102 InternalPart::Word { range } => {
103 return Some(VersionPart::Word(&self.version[range]))
104 }
105 }
106 }
107
108 let (start, end, char) = char.unwrap();
109 match char {
110 '.' | '-' => {
112 let cached_part = std::mem::replace(&mut self.cached_part, InternalPart::Break);
113 match cached_part {
114 InternalPart::Number { range } => {
115 Some(VersionPart::Number(&self.version[range]))
116 }
117 InternalPart::Word { range } => Some(VersionPart::Word(&self.version[range])),
118 InternalPart::Break => self.next(),
119 }
120 }
121
122 _ if char.is_ascii_digit() => {
124 let cached_part = std::mem::replace(
125 &mut self.cached_part,
126 InternalPart::Number {
127 range: start..=(end - 1),
128 },
129 );
130 match cached_part {
131 InternalPart::Number { range } => {
132 self.cached_part = InternalPart::Number {
133 range: *range.start()..=*range.end() + 1,
134 };
135 self.next()
136 }
137 InternalPart::Word { range } => Some(VersionPart::Word(&self.version[range])),
138 InternalPart::Break => self.next(),
139 }
140 }
141
142 _ => {
144 let mut cached_part = InternalPart::Word {
145 range: start..=(end - 1),
146 };
147 std::mem::swap(&mut cached_part, &mut self.cached_part);
148 match cached_part {
149 InternalPart::Word { range } => {
150 self.cached_part = InternalPart::Word {
151 range: *range.start()..=*range.end() + char.len_utf8(),
152 };
153 self.next()
154 }
155 InternalPart::Number { range } => {
156 Some(VersionPart::Number(&self.version[range]))
157 }
158 InternalPart::Break => self.next(),
159 }
160 }
161 }
162 }
163}