deb_version7/
version.rs

1// Rust port of lib/dpkg/version.c
2//
3// Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
4// Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
5// Copyright © 2024 Kunal Mehta <legoktm@debian.org>
6//
7// This is free software; you can redistribute it and/or modify
8// it under the terms of the GNU General Public License as published by
9// the Free Software Foundation; either version 3 of the License, or
10// (at your option) any later version.
11//
12// This is distributed in the hope that it will be useful,
13// but WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15// GNU General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this program.  If not, see <https://www.gnu.org/licenses/>.
19
20use crate::Error;
21use std::{cmp::Ordering, fmt::Display, str::FromStr};
22
23#[derive(Debug, Clone)]
24pub struct DebVersion {
25    pub(crate) epoch: u32,
26    // note: dpkg has this field as optional, but we require it
27    pub(crate) version: String,
28    pub(crate) revision: Option<String>,
29}
30
31impl DebVersion {
32    /// Epoch of the version, if not specified it is 0.
33    pub fn epoch(&self) -> u32 {
34        self.epoch
35    }
36
37    /// The upstream version
38    pub fn version(&self) -> &str {
39        &self.version
40    }
41
42    /// The Debian revision, if it is a non-native package
43    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
89/// Give a weight to the character to order in the version comparison.
90fn 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
163/// Compares two Debian versions.
164///
165/// This function follows the convention of the comparator functions used by
166/// qsort().
167///
168/// See `deb-version(5)`
169pub(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}