1use crate::Error;
21use std::{cmp::Ordering, fmt::Display, str::FromStr};
22
23#[derive(Debug, Clone)]
24pub struct DebVersion {
25 pub(crate) epoch: u32,
26 pub(crate) version: String,
28 pub(crate) revision: Option<String>,
29}
30
31impl DebVersion {
32 pub fn epoch(&self) -> u32 {
34 self.epoch
35 }
36
37 pub fn version(&self) -> &str {
39 &self.version
40 }
41
42 pub fn revision(&self) -> Option<&str> {
44 self.revision.as_deref()
45 }
46}
47
48impl PartialEq for DebVersion {
49 fn eq(&self, other: &Self) -> bool {
50 dpkg_version_compare(self, other) == Ordering::Equal
51 }
52}
53
54impl Eq for DebVersion {}
55
56impl PartialOrd for DebVersion {
57 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
58 Some(self.cmp(other))
59 }
60}
61
62impl Ord for DebVersion {
63 fn cmp(&self, other: &Self) -> Ordering {
64 dpkg_version_compare(self, other)
65 }
66}
67
68impl FromStr for DebVersion {
69 type Err = Error;
70
71 fn from_str(input: &str) -> Result<Self, Self::Err> {
72 crate::parsehelp::parseversion(input)
73 }
74}
75
76impl Display for DebVersion {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 if self.epoch > 0 {
79 write!(f, "{}:", self.epoch)?;
80 }
81 write!(f, "{}", self.version)?;
82 if let Some(revision) = &self.revision {
83 write!(f, "-{revision}")?;
84 }
85 Ok(())
86 }
87}
88
89fn order(char: char) -> i32 {
91 match char {
92 '0'..='9' => 0,
93 'A'..='Z' | 'a'..='z' => char as i32,
94 '~' => -1,
95 val => val as i32 + 256,
96 }
97}
98
99fn verrevcmp(a: &str, b: &str) -> i32 {
100 let mut index_a = 0;
101 let mut index_b = 0;
102 let chars_a = a.chars().collect::<Vec<_>>();
103 let chars_b = b.chars().collect::<Vec<_>>();
104
105 while index_a < a.len() || index_b < b.len() {
106 let mut first_diff = 0;
107
108 while (index_a < a.len() && !chars_a[index_a].is_ascii_digit())
109 || (index_b < b.len() && !chars_b[index_b].is_ascii_digit())
110 {
111 let ac = if index_a < a.len() {
112 order(chars_a[index_a])
113 } else {
114 0
115 };
116 let bc = if index_b < b.len() {
117 order(chars_b[index_b])
118 } else {
119 0
120 };
121
122 if ac != bc {
123 return ac - bc;
124 }
125
126 index_a += 1;
127 index_b += 1;
128 }
129
130 while index_a < a.len() && chars_a[index_a] == '0' {
131 index_a += 1;
132 }
133 while index_b < b.len() && chars_b[index_b] == '0' {
134 index_b += 1;
135 }
136
137 while index_a < a.len()
138 && chars_a[index_a].is_ascii_digit()
139 && index_b < b.len()
140 && chars_b[index_b].is_ascii_digit()
141 {
142 if first_diff == 0 {
143 first_diff = chars_a[index_a] as i32 - chars_b[index_b] as i32;
144 }
145 index_a += 1;
146 index_b += 1;
147 }
148
149 if index_a < a.len() && chars_a[index_a].is_ascii_digit() {
150 return 1;
151 }
152 if index_b < b.len() && chars_b[index_b].is_ascii_digit() {
153 return -1;
154 }
155 if first_diff != 0 {
156 return first_diff;
157 }
158 }
159
160 0
161}
162
163pub(crate) fn dpkg_version_compare(a: &DebVersion, b: &DebVersion) -> Ordering {
170 if a.epoch > b.epoch {
171 return Ordering::Greater;
172 }
173 if a.epoch < b.epoch {
174 return Ordering::Less;
175 }
176
177 let rc = verrevcmp(&a.version, &b.version);
178 if rc != 0 {
179 return rc.cmp(&0);
180 }
181
182 match (&a.revision, &b.revision) {
183 (Some(a_revision), Some(b_revision)) => verrevcmp(a_revision, b_revision).cmp(&0),
184 (None, None) => Ordering::Equal,
185 (None, Some(_)) => Ordering::Less,
186 (Some(_), None) => Ordering::Greater,
187 }
188}