Add a strverscmp(3) function to libc, a GNU extension I implemented by reading its glibc manual page. It orders strings following a much more natural ordering (e.g. "ent1 < ent2 < ent10" as opposed to "ent1 < ent10 < ent2" with strcmp(3)'s lexicographic ordering). Also add versionsort(3) for use as scandir(3)'s compar argument. Update manual page for scandir(3) and add one for strverscmp(3). Reviewed by: pstef, gbe, kib MFC after: 1 week Differential Revision: https://reviews.freebsd.org/D35807
92 lines
2.2 KiB
C
92 lines
2.2 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
* Copyright (c) 2022 Aymeric Wibo <obiwac@gmail.com>
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <stddef.h>
|
|
|
|
int
|
|
strverscmp(const char *s1, const char *s2)
|
|
{
|
|
size_t digit_count_1, digit_count_2;
|
|
size_t zeros_count_1, zeros_count_2;
|
|
const unsigned char *num_1, *num_2;
|
|
const unsigned char *u1 = __DECONST(const unsigned char *, s1);
|
|
const unsigned char *u2 = __DECONST(const unsigned char *, s2);
|
|
|
|
/*
|
|
* If pointers are the same, no need to go through to process of
|
|
* comparing them.
|
|
*/
|
|
if (s1 == s2)
|
|
return (0);
|
|
|
|
while (*u1 != '\0' && *u2 != '\0') {
|
|
/* If either character is not a digit, act like strcmp(3). */
|
|
|
|
if (!isdigit(*u1) || !isdigit(*u2)) {
|
|
if (*u1 != *u2)
|
|
return (*u1 - *u2);
|
|
u1++;
|
|
u2++;
|
|
continue;
|
|
}
|
|
if (*u1 == '0' || *u2 == '0') {
|
|
/*
|
|
* Treat leading zeros as if they were the fractional
|
|
* part of a number, i.e. as if they had a decimal point
|
|
* in front. First, count the leading zeros (more zeros
|
|
* == smaller number).
|
|
*/
|
|
zeros_count_1 = 0;
|
|
zeros_count_2 = 0;
|
|
for (; *u1 == '0'; u1++)
|
|
zeros_count_1++;
|
|
for (; *u2 == '0'; u2++)
|
|
zeros_count_2++;
|
|
if (zeros_count_1 != zeros_count_2)
|
|
return (zeros_count_2 - zeros_count_1);
|
|
|
|
/* Handle the case where 0 < 09. */
|
|
if (!isdigit(*u1) && isdigit(*u2))
|
|
return (1);
|
|
if (!isdigit(*u2) && isdigit(*u1))
|
|
return (-1);
|
|
} else {
|
|
/*
|
|
* No leading zeros; we're simply comparing two numbers.
|
|
* It is necessary to first count how many digits there
|
|
* are before going back to compare each digit, so that
|
|
* e.g. 7 is not considered larger than 60.
|
|
*/
|
|
num_1 = u1;
|
|
num_2 = u2;
|
|
|
|
/* Count digits (more digits == larger number). */
|
|
for (; isdigit(*u1); u1++)
|
|
;
|
|
for (; isdigit(*u2); u2++)
|
|
;
|
|
digit_count_1 = u1 - num_1;
|
|
digit_count_2 = u2 - num_2;
|
|
if (digit_count_1 != digit_count_2)
|
|
return (digit_count_1 - digit_count_2);
|
|
|
|
/*
|
|
* If there are the same number of digits, go back to
|
|
* the start of the number.
|
|
*/
|
|
u1 = num_1;
|
|
u2 = num_2;
|
|
}
|
|
|
|
/* Compare each digit until there are none left. */
|
|
for (; isdigit(*u1) && isdigit(*u2); u1++, u2++) {
|
|
if (*u1 != *u2)
|
|
return (*u1 - *u2);
|
|
}
|
|
}
|
|
return (*u1 - *u2);
|
|
}
|