rune/library/filevercmp.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
use std::cmp::{Ord, Ordering};
/// Return the length of a prefix of S that corresponds to the suffix
/// defined by this extended regular expression in the C locale:
/// (\.[A-Za-z~][A-Za-z0-9~]*)*$
/// Use the longest suffix matching this regular expression,
/// except do not use all of S as a suffix if S is nonempty.
/// If *LEN is -1, S is a string; set *LEN to S's length.
/// Otherwise, *LEN should be nonnegative, S is a char array,
/// and *LEN does not change.
fn prefix_len(s: &[u8]) -> (usize, usize) {
let mut prefix_len = 0;
let mut i = 0;
while i < s.len() {
i += 1;
prefix_len = i;
while i + 1 < s.len()
&& s[i] == b'.'
&& (s[i + 1].is_ascii_alphabetic() || s[i + 1] == b'~')
{
i += 2;
while i < s.len() && (s[i].is_ascii_alphanumeric() || s[i] == b'~') {
i += 1;
}
}
}
(prefix_len, i)
}
/// Return a version sort comparison value for S's byte at position POS. If POS
/// == LEN, sort before all non-'~' bytes.
fn order(s: &[u8], pos: usize) -> i32 {
if pos == s.len() {
return -1;
}
let c = s[pos];
match c {
b'0'..=b'9' => 0,
b'A'..=b'Z' | b'a'..=b'z' => c as i32,
b'~' => -2,
_ => c as i32 + 256,
}
}
/// slightly modified verrevcmp function from dpkg
/// S1, S2 - compared char array
///
/// This implements the algorithm for comparison of version strings
/// specified by Debian and now widely adopted. The detailed
/// specification can be found in the Debian Policy Manual in the
/// section on the 'Version' control field. This version of the code
/// implements that from s5.6.12 of [Debian Policy v3.8.0.1](https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version)
fn verrevcmp(s1: &[u8], s2: &[u8]) -> i32 {
let mut s1_pos = 0;
let mut s2_pos = 0;
while s1_pos < s1.len() && s2_pos < s2.len() {
let mut first_diff: i32 = 0;
while (s1_pos < s1.len() && !s1[s1_pos].is_ascii_digit())
|| (s2_pos < s2.len() && !s2[s2_pos].is_ascii_digit())
{
let s1_c = order(s1, s1_pos);
let s2_c = order(s2, s2_pos);
if s1_c != s2_c {
return s1_c - s2_c;
}
s1_pos += 1;
s2_pos += 1;
}
while s1_pos < s1.len() && s1[s1_pos] == b'0' {
s1_pos += 1;
}
while s2_pos < s2.len() && s2[s2_pos] == b'0' {
s2_pos += 1;
}
while s1_pos < s1.len()
&& s2_pos < s2.len()
&& s1[s1_pos].is_ascii_digit()
&& s2[s2_pos].is_ascii_digit()
{
if first_diff == 0 {
first_diff = s1[s1_pos] as i32 - s2[s2_pos] as i32;
}
s1_pos += 1;
s2_pos += 1;
}
if s1_pos < s1.len() && s1[s1_pos].is_ascii_digit() {
return 1;
}
if s2_pos < s2.len() && s2[s2_pos].is_ascii_digit() {
return -1;
}
if first_diff != 0 {
return first_diff;
}
}
0
}
/// Compare strings A and B as file names containing version numbers,
/// and return an integer that is negative, zero, or positive depending
/// on whether A compares less than, equal to, or greater than B.
///
/// Use the following version sort algorithm:
///
/// 1. Compare the strings' maximal-length non-digit prefixes lexically.
/// If there is a difference return that difference.
/// Otherwise discard the prefixes and continue with the next step.
///
/// 2. Compare the strings' maximal-length digit prefixes, using
/// numeric comparison of the numbers represented by each prefix.
/// (Treat an empty prefix as zero; this can happen only at string end.)
/// If there is a difference, return that difference.
/// Otherwise discard the prefixes and continue with the next step.
///
/// 3. If both strings are empty, return 0. Otherwise continue with step 1.
///
/// In version sort, lexical comparison is left to right, byte by byte,
/// using the byte's numeric value (0-255), except that:
///
/// 1. ASCII letters sort before other bytes.
/// 2. A tilde sorts before anything, even an empty string.
///
/// In addition to the version sort rules, the following strings have
/// special priority and sort before all other strings (listed in order):
///
/// 1. The empty string.
/// 2. ".".
/// 3. "..".
/// 4. Strings starting with "." sort before other strings.
///
/// Before comparing two strings where both begin with non-".",
/// or where both begin with "." but neither is "." or "..",
/// suffixes matching the C-locale extended regular expression
/// (\.[A-Za-z~][A-Za-z0-9~]*)*$ are removed and the strings compared
/// without them, using version sort without special priority;
/// if they do not compare equal, this comparison result is used and
/// the suffixes are effectively ignored. Otherwise, the entire
/// strings are compared using version sort. When removing a suffix
/// from a nonempty string, remove the maximal-length suffix such that
/// the remaining string is nonempty.
///
/// This function is intended to be a replacement for strverscmp.
pub(crate) fn filevercmp(a: &[u8], b: &[u8]) -> Ordering {
const D: u8 = b'.';
match (a, b) {
([], []) | ([D], [D]) | ([D, D], [D, D]) => return Ordering::Equal,
([], [..]) | ([D], [_, ..]) | ([D, D], [_, _, ..]) => return Ordering::Less,
([..], []) | ([_, ..], [D]) | ([_, _, ..], [D, D]) => return Ordering::Greater,
_ => {}
}
let (a_prefix_len, a_len) = prefix_len(a);
let (b_prefix_len, b_len) = prefix_len(b);
let one_pass_only = a_prefix_len == a_len && b_prefix_len == b_len;
let result = verrevcmp(&a[..a_prefix_len], &b[..b_prefix_len]);
if result != 0 || one_pass_only {
result.cmp(&0)
} else {
let result = verrevcmp(&a[..a_len], &b[..b_len]);
result.cmp(&0)
}
}