From 64504e8b8fb097d6e849953eb0e4721321c41a27 Mon Sep 17 00:00:00 2001
From: Remi Collet <remi@remirepo.net>
Date: Mon, 11 Sep 2023 10:21:36 +0200
Subject: add optional operator to rpmcmpver for consistency with
 version_compare

---
 package.xml                     |  2 +-
 rpminfo.c                       | 50 ++++++++++++++++++++++++++++++++++-------
 rpminfo.stub.php                |  2 +-
 rpminfo_arginfo.h               |  5 +++--
 tests/002-rpmvercmp.phpt        | 44 +++++++++++++++++++++++++++++++++++-
 tests/010-rpmvercmp_error7.phpt | 10 ++++++---
 tests/011-rpmvercmp_error8.phpt | 12 +++++++---
 7 files changed, 106 insertions(+), 19 deletions(-)

diff --git a/package.xml b/package.xml
index 38dc60e..4756ce4 100644
--- a/package.xml
+++ b/package.xml
@@ -24,7 +24,7 @@ Documentation: https://www.php.net/rpminfo
   </stability>
   <license uri="https://www.php.net/license/3_01.txt" filesource="LICENSE">PHP-3.01</license>
   <notes>
--
+- add optional operator to rpmcmpver for consistency with version_compare
   </notes>
   <contents>
     <dir name="/">
diff --git a/rpminfo.c b/rpminfo.c
index 53b9c6c..1ba0123 100644
--- a/rpminfo.c
+++ b/rpminfo.c
@@ -37,6 +37,11 @@
 #define ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, type_hint, allow_null, default_value) \
         ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null)
 #endif
+/* only used for rpmvercmp */
+#ifndef ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX
+#define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(name, return_reference, required_num_args, type) \
+        ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, return_reference, required_num_args, IS_LONG, 0)
+#endif
 
 #ifndef RETURN_THROWS
 #define RETURN_THROWS() return
@@ -469,10 +474,11 @@ PHP_FUNCTION(rpmvercmp)
 	char *in_evr1, *evr1, *v1, *r1;
 	char *in_evr2, *evr2, *v2, *r2;
 	char *p, empty[] = "";
+	char *op = NULL;
 	long e1, e2, r;
-	size_t len1, len2;
+	size_t len1, len2, oplen;
 
-	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &in_evr1, &len1, &in_evr2, &len2) == FAILURE) {
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|s", &in_evr1, &len1, &in_evr2, &len2, &op, &oplen) == FAILURE) {
 		RETURN_THROWS();
 	}
 	evr1 = estrdup(in_evr1);
@@ -498,9 +504,9 @@ PHP_FUNCTION(rpmvercmp)
 		e2 = 0;
 	}
 	if (e1 < e2) {
-		RETVAL_LONG(-1);
+		r = -1;
 	} else if (e1 > e2) {
-		RETVAL_LONG(1);
+		r = 1;
 	} else {
 		// Version
 		p = strchr(v1, '-');
@@ -518,16 +524,44 @@ PHP_FUNCTION(rpmvercmp)
 			r2 = empty;
 		}
 		r = rpmvercmp(v1, v2);
-		if (r) {
-			RETVAL_LONG(r);
-		} else {
+		if (!r) {
 			// Release
 			r = rpmvercmp(r1, r2);
-			RETVAL_LONG(r);
 		}
 	}
 	efree(evr1);
 	efree(evr2);
+
+	if (!op) {
+		RETURN_LONG(r);
+	}
+
+	if (!strcmp(op, "<") || !strcmp(op, "lt")) {
+		RETURN_BOOL(r < 0);
+	}
+	if (!strcmp(op, "<=") || !strcmp(op, "le")) {
+		RETURN_BOOL(r <= 0);
+	}
+	if (!strcmp(op, ">") || !strcmp(op, "gt")) {
+		RETURN_BOOL(r > 0);
+	}
+	if (!strcmp(op, ">=") || !strcmp(op, "ge")) {
+		RETURN_BOOL(r >= 0);
+	}
+	if (!strcmp(op, "==") || !strcmp(op, "=") || !strcmp(op, "eq")) {
+		RETURN_BOOL(r == 0);
+	}
+	if (!strcmp(op, "!=") || !strcmp(op, "<>") || !strcmp(op, "ne")) {
+		RETURN_BOOL(r != 0);
+	}
+
+#if PHP_VERSION_ID < 80000
+	php_error_docref(NULL, E_WARNING, "%s is not a valid comparison operator", op);
+	RETURN_NULL();
+#else
+	zend_argument_value_error(3, "must be a valid comparison operator");
+	RETURN_THROWS();
+#endif
 }
 /* }}} */
 
diff --git a/rpminfo.stub.php b/rpminfo.stub.php
index 543cc5f..022ccd4 100644
--- a/rpminfo.stub.php
+++ b/rpminfo.stub.php
@@ -10,6 +10,6 @@ function rpmdbsearch(string $pattern, int $rpmtag = RPMTAG_NAME, int $rpmmire =
 
 function rpminfo(string $path, bool $full = false, ?string &$error = null): Array|null {}
 
-function rpmvercmp(string $evr1, string $evr2): int {}
+function rpmvercmp(string $evr1, string $evr2, ?string $operator = null): int|bool {}
 
 
diff --git a/rpminfo_arginfo.h b/rpminfo_arginfo.h
index 20c501f..03fa650 100644
--- a/rpminfo_arginfo.h
+++ b/rpminfo_arginfo.h
@@ -1,5 +1,5 @@
 /* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 6b4dfeada2b5de5c5453d2b38c1a861e01bf958e */
+ * Stub hash: 8636be19a4c17d1ed16247fb265a923ee7c89104 */
 
 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpmaddtag, 0, 1, _IS_BOOL, 0)
 	ZEND_ARG_TYPE_INFO(0, rpmtag, IS_LONG, 0)
@@ -23,9 +23,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpminfo, 0, 1, IS_ARRAY, 1)
 	ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(1, error, IS_STRING, 1, "null")
 ZEND_END_ARG_INFO()
 
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_rpmvercmp, 0, 2, IS_LONG, 0)
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_rpmvercmp, 0, 2, MAY_BE_LONG|MAY_BE_BOOL)
 	ZEND_ARG_TYPE_INFO(0, evr1, IS_STRING, 0)
 	ZEND_ARG_TYPE_INFO(0, evr2, IS_STRING, 0)
+	ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, operator, IS_STRING, 1, "null")
 ZEND_END_ARG_INFO()
 
 
diff --git a/tests/002-rpmvercmp.phpt b/tests/002-rpmvercmp.phpt
index d83cf67..c53a183 100644
--- a/tests/002-rpmvercmp.phpt
+++ b/tests/002-rpmvercmp.phpt
@@ -29,11 +29,53 @@ $ok = true;
 foreach ($cases as $case) {
 	list($a,$b,$expected) = $case;
 	$result = rpmvercmp($a,$b);
-	if ($result != $expected) {
+	if ($result !== $expected) {
 		$ok = false;
 		printf("rpmvercmp(%s, %s) = %d when %d expected\n", $a, $b, $result, $expected);
 	}
 }
+
+$cases = [
+	['1', '2', '>',  false],
+	['1', '2', 'gt', false],
+	['1', '2', '>=', false],
+	['1', '2', 'ge', false],
+	['1', '1', '>=', true],
+	['1', '1', 'ge', true],
+
+	['1', '2', '<',  true],
+	['1', '2', 'lt', true],
+	['1', '2', '<=', true],
+	['1', '2', 'le', true],
+	['1', '1', '<=', true],
+	['1', '1', 'le', true],
+
+	['1', '1', '=', true],
+	['1', '1', '==', true],
+	['1', '1', 'eq', true],
+
+	['1', '2', '=',  false],
+	['1', '2', '==', false],
+	['1', '2', 'eq', false],
+
+	['1', '1', '!=', false],
+	['1', '1', '<>', false],
+	['1', '1', 'ne', false],
+
+	['1', '2', '!=', true],
+	['1', '2', '<>', true],
+	['1', '2', 'ne', true],
+];
+foreach ($cases as $case) {
+	list($a,$b,$op,$expected) = $case;
+	$result = rpmvercmp($a,$b,$op);
+	if ($result !== $expected) {
+		$ok = false;
+		printf("rpmvercmp(%s, %s, %s) = %s when %s expected\n",
+			$a, $b, $op, $result ? "true" : "false", $expected ? "true" : "false");
+	}
+}
+
 if ($ok) echo "OK\n";
 ?>
 Done
diff --git a/tests/010-rpmvercmp_error7.phpt b/tests/010-rpmvercmp_error7.phpt
index c9b3875..2d5c220 100644
--- a/tests/010-rpmvercmp_error7.phpt
+++ b/tests/010-rpmvercmp_error7.phpt
@@ -10,16 +10,20 @@ if (PHP_VERSION_ID >= 80000) print "skip only for PHP 7";
 var_dump(rpmvercmp());
 var_dump(rpmvercmp("a"));
 var_dump(rpmvercmp("a", "b", "c"));
+var_dump(rpmvercmp("a", "b", "c", "d"));
 ?>
 Done
 --EXPECTF--
 
-Warning: rpmvercmp() expects exactly 2 parameters, 0 given in %s
+Warning: rpmvercmp() expects at least 2 parameters, 0 given in %s
 NULL
 
-Warning: rpmvercmp() expects exactly 2 parameters, 1 given in %s
+Warning: rpmvercmp() expects at least 2 parameters, 1 given in %s
 NULL
 
-Warning: rpmvercmp() expects exactly 2 parameters, 3 given in %s
+Warning: rpmvercmp(): c is not a valid comparison operator in %s
+NULL
+
+Warning: rpmvercmp() expects at most 3 parameters, 4 given in %s
 NULL
 Done
diff --git a/tests/011-rpmvercmp_error8.phpt b/tests/011-rpmvercmp_error8.phpt
index 4430975..2fe6aa1 100644
--- a/tests/011-rpmvercmp_error8.phpt
+++ b/tests/011-rpmvercmp_error8.phpt
@@ -19,13 +19,19 @@ try {
 }
 try {
 	var_dump(rpmvercmp("a", "b", "c"));
+} catch (ValueError $e) {
+    echo $e->getMessage(), "\n";
+}
+try {
+	var_dump(rpmvercmp("a", "b", "c", "d"));
 } catch (ArgumentCountError $e) {
     echo $e->getMessage(), "\n";
 }
 ?>
 Done
 --EXPECTF--
-rpmvercmp() expects exactly 2 %s, 0 given
-rpmvercmp() expects exactly 2 %s, 1 given
-rpmvercmp() expects exactly 2 %s, 3 given
+rpmvercmp() expects at least 2 %s, 0 given
+rpmvercmp() expects at least 2 %s, 1 given
+rpmvercmp(): Argument #3 ($operator) must be a valid comparison operator
+rpmvercmp() expects at most 3 %s, 4 given
 Done
-- 
cgit