common: New function compare_version_strings.

* common/stringhelp.c (parse_version_number): New.
(parse_version_string): New.
(compare_version_strings): New.
* common/t-stringhelp.c (test_compare_version_strings): New.
(main): Call test.  Return ERRCOUNT instead of 0.
--

The code for that function is based on code from libgcrypt.  Similar
code is in all GnuPG related libraries this function is
a candidates for inclusion in libgpg-error.

Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
Werner Koch 2016-01-08 08:58:21 +01:00
parent 496643291e
commit 4d7ac43ff7
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
3 changed files with 150 additions and 1 deletions

View File

@ -1329,6 +1329,91 @@ strtokenize (const char *string, const char *delim)
}
/* Version number parsing. */
/* This function parses the first portion of the version number S and
stores it in *NUMBER. On success, this function returns a pointer
into S starting with the first character, which is not part of the
initial number portion; on failure, NULL is returned. */
static const char*
parse_version_number (const char *s, int *number)
{
int val = 0;
if (*s == '0' && digitp (s+1))
return NULL; /* Leading zeros are not allowed. */
for (; digitp (s); s++)
{
val *= 10;
val += *s - '0';
}
*number = val;
return val < 0 ? NULL : s;
}
/* This function breaks up the complete string-representation of the
version number S, which is of the following struture: <major
number>.<minor number>.<micro number><patch level>. The major,
minor and micro number components will be stored in *MAJOR, *MINOR
and *MICRO.
On success, the last component, the patch level, will be returned;
in failure, NULL will be returned. */
static const char *
parse_version_string (const char *s, int *major, int *minor, int *micro)
{
s = parse_version_number (s, major);
if (!s || *s != '.')
return NULL;
s++;
s = parse_version_number (s, minor);
if (!s)
return NULL;
if (*s == '.')
{
s++;
s = parse_version_number (s, micro);
if (!s)
return NULL;
}
else
*micro = 0;
return s; /* Patchlevel. */
}
/* Check that the version string MY_VERSION is greater or equal than
REQ_VERSION. Returns true if the condition is satisfied or false
if not. This works with 3 part and two part version strings; for a
two part version string the micor part is assumed to be 0. */
int
compare_version_strings (const char *my_version, const char *req_version)
{
int my_major, my_minor, my_micro;
int rq_major, rq_minor, rq_micro;
if (!my_version || !req_version)
return 0;
if (!parse_version_string (my_version, &my_major, &my_minor, &my_micro))
return 0;
if (!parse_version_string(req_version, &rq_major, &rq_minor, &rq_micro))
return 0;
if (my_major > rq_major
|| (my_major == rq_major && my_minor > rq_minor)
|| (my_major == rq_major && my_minor == rq_minor
&& my_micro >= rq_micro))
{
return 1;
}
return 0;
}
/* Format a string so that it fits within about TARGET_COLS columns.
If IN_PLACE is 0, then TEXT is copied to a new buffer, which is
returned. Otherwise, TEXT is modified in place and returned.

View File

@ -148,6 +148,9 @@ char **strsplit (char *string, char delim, char replacement, int *count);
/* Tokenize STRING using the set of delimiters in DELIM. */
char **strtokenize (const char *string, const char *delim);
/* Return True if MYVERSION is greater or equal than REQ_VERSION. */
int compare_version_strings (const char *my_version, const char *req_version);
/* Format a string so that it fits within about TARGET_COLS columns. */
char *format_text (char *text, int in_place, int target_cols, int max_cols);

View File

@ -705,6 +705,7 @@ stresc (char *s)
return p;
}
static void
test_format_text (void)
{
@ -813,6 +814,65 @@ test_format_text (void)
fail(0);
}
static void
test_compare_version_strings (void)
{
struct { const char *a; const char *b; int okay; } tests[] = {
{ "1.0.0", "1.0.0", 1 },
{ "1.0.0-", "1.0.0", 1 },
{ "1.0.0-1", "1.0.0", 1 },
{ "1.0.0.1", "1.0.0", 1 },
{ "1.0.0", "1.0.1", 0 },
{ "1.0.0-", "1.0.1", 0 },
{ "1.0.0-1", "1.0.1", 0 },
{ "1.0.0.1", "1.0.1", 0 },
{ "1.0.0", "1.1.0", 0 },
{ "1.0.0-", "1.1.0", 0 },
{ "1.0.0-1", "1.1.0", 0 },
{ "1.0.0.1", "1.1.0", 0 },
{ "1.0.0", "1.0.0-", 1 },
{ "1.0.0", "1.0.0-1", 1 },
{ "1.0.0", "1.0.0.1", 1 },
{ "1.1.0", "1.0.0", 1 },
{ "1.1.1", "1.1.0", 1 },
{ "1.1.2", "1.1.2", 1 },
{ "1.1.2", "1.0.2", 1 },
{ "1.1.2", "0.0.2", 1 },
{ "1.1.2", "1.1.3", 0 },
{ "0.99.1", "0.9.9", 1 },
{ "0.9.1", "0.91.0", 0 },
{ "1.5.3", "1.5", 1 },
{ "1.5.0", "1.5", 1 },
{ "1.4.99", "1.5", 0 },
{ "1.5", "1.4.99", 1 },
{ "1.5", "1.5.0", 1 },
{ "1.5", "1.5.1", 0 },
{ "1.5.3-x17", "1.5-23", 1 },
{ "1.5.3a", "1.5.3", 1 },
{ "1.5.3a", "1.5.3b", 1 },
{ NULL, NULL, 0 }
};
int idx;
int res;
for (idx=0; idx < DIM(tests); idx++)
{
res = compare_version_strings (tests[idx].a, tests[idx].b);
/* printf ("test %d: '%s' '%s' %d -> %d\n", */
/* idx, tests[idx].a, tests[idx].b, tests[idx].okay, res); */
if (res != tests[idx].okay)
fail (idx);
}
}
int
main (int argc, char **argv)
{
@ -827,8 +887,9 @@ main (int argc, char **argv)
test_make_absfilename_try ();
test_strsplit ();
test_strtokenize ();
test_compare_version_strings ();
test_format_text ();
xfree (home_buffer);
return 0;
return !!errcount;
}