From 693b4bcdb0f22904a521a7c8ac4f13e697dc4d71 Mon Sep 17 00:00:00 2001
From: William Pitcock <nenolod@dereferenced.org>
Date: Sat, 27 May 2017 21:49:53 +0000
Subject: version: add support for fuzzy version matching

This is useful for requirements such as: python3=~3.6, which would match python3-3.6.[0-9].
This implementation should in theory be backwards compatible with the implementation in Adelie.
---
 src/add.c         |  2 +-
 src/apk_package.h |  3 ++-
 src/apk_version.h |  4 +++-
 src/package.c     | 14 ++++++++++----
 src/version.c     | 12 ++++++++++--
 5 files changed, 26 insertions(+), 9 deletions(-)

(limited to 'src')

diff --git a/src/add.c b/src/add.c
index 2df4dc8..742df07 100644
--- a/src/add.c
+++ b/src/add.c
@@ -137,7 +137,7 @@ static int add_main(void *ctx, struct apk_database *db, struct apk_string_array
 
 			apk_blob_pull_dep(&b, db, &dep);
 			if (APK_BLOB_IS_NULL(b) || b.len > 0) {
-				apk_error("'%s' is not a valid dependency, format is name(@tag)([<>=]version)",
+				apk_error("'%s' is not a valid dependency, format is name(@tag)([<>~=]version)",
 					  *parg);
 				return -1;
 			}
diff --git a/src/apk_package.h b/src/apk_package.h
index ab7ab02..c561fc1 100644
--- a/src/apk_package.h
+++ b/src/apk_package.h
@@ -73,7 +73,8 @@ struct apk_dependency {
 	unsigned broken : 1;
 	unsigned repository_tag : 6;
 	unsigned conflict : 1;
-	unsigned result_mask : 3;
+	unsigned result_mask : 4;
+	unsigned fuzzy : 1;
 };
 APK_ARRAY(apk_dependency_array, struct apk_dependency);
 
diff --git a/src/apk_version.h b/src/apk_version.h
index 4275343..47a6b73 100644
--- a/src/apk_version.h
+++ b/src/apk_version.h
@@ -18,14 +18,16 @@
 #define APK_VERSION_EQUAL		1
 #define APK_VERSION_LESS		2
 #define APK_VERSION_GREATER		4
+#define APK_VERSION_FUZZY		8
 
 #define APK_DEPMASK_ANY		(APK_VERSION_EQUAL|APK_VERSION_LESS|\
-				 APK_VERSION_GREATER)
+				 APK_VERSION_GREATER|APK_VERSION_FUZZY)
 #define APK_DEPMASK_CHECKSUM	(APK_VERSION_LESS|APK_VERSION_GREATER)
 
 const char *apk_version_op_string(int result_mask);
 int apk_version_result_mask(const char *str);
 int apk_version_validate(apk_blob_t ver);
+int apk_version_compare_blob_fuzzy(apk_blob_t a, apk_blob_t b, int fuzzy);
 int apk_version_compare_blob(apk_blob_t a, apk_blob_t b);
 int apk_version_compare(const char *str1, const char *str2);
 
diff --git a/src/package.c b/src/package.c
index c1d7b77..0bb3747 100644
--- a/src/package.c
+++ b/src/package.c
@@ -31,6 +31,7 @@
 
 static const apk_spn_match_def apk_spn_dependency_comparer = {
 	[7] = (1<<4) /*<*/ | (1<<5) /*=*/ | (1<<6) /*<*/,
+	[15] = (1<<6) /*~*/
 };
 
 static const apk_spn_match_def apk_spn_dependency_separator = {
@@ -190,9 +191,9 @@ void apk_blob_pull_dep(apk_blob_t *b, struct apk_database *db, struct apk_depend
 {
 	struct apk_name *name;
 	apk_blob_t bdep, bname, bop, bver = APK_BLOB_NULL, btag;
-	int mask = APK_DEPMASK_ANY, conflict = 0, tag = 0;
+	int mask = APK_DEPMASK_ANY, conflict = 0, tag = 0, fuzzy = 0;
 
-	/* [!]name[<,<=,=,>=,>,><]ver */
+	/* [!]name[<,<=,<~,=,~,>~,>=,>,><]ver */
 	if (APK_BLOB_IS_NULL(*b))
 		goto fail;
 
@@ -231,6 +232,10 @@ void apk_blob_pull_dep(apk_blob_t *b, struct apk_database *db, struct apk_depend
 			case '>':
 				mask |= APK_VERSION_GREATER;
 				break;
+			case '~':
+				mask |= APK_VERSION_FUZZY|APK_VERSION_EQUAL;
+				fuzzy = TRUE;
+				break;
 			case '=':
 				mask |= APK_VERSION_EQUAL;
 				break;
@@ -259,6 +264,7 @@ void apk_blob_pull_dep(apk_blob_t *b, struct apk_database *db, struct apk_depend
 		.repository_tag = tag,
 		.result_mask = mask,
 		.conflict = conflict,
+		.fuzzy = fuzzy,
 	};
 	return;
 fail:
@@ -320,7 +326,7 @@ int apk_dep_is_provided(struct apk_dependency *dep, struct apk_provider *p)
 	default:
 		if (p->version == &apk_null_blob)
 			return dep->conflict;
-		if (apk_version_compare_blob(*p->version, *dep->version)
+		if (apk_version_compare_blob_fuzzy(*p->version, *dep->version, dep->fuzzy)
 		    & dep->result_mask)
 			return !dep->conflict;
 		return dep->conflict;
@@ -341,7 +347,7 @@ int apk_dep_is_materialized(struct apk_dependency *dep, struct apk_package *pkg)
 	case APK_DEPMASK_ANY:
 		return !dep->conflict;
 	default:
-		if (apk_version_compare_blob(*pkg->version, *dep->version)
+		if (apk_version_compare_blob_fuzzy(*pkg->version, *dep->version, dep->fuzzy)
 		    & dep->result_mask)
 			return !dep->conflict;
 		return dep->conflict;
diff --git a/src/version.c b/src/version.c
index eaac762..9bd96cb 100644
--- a/src/version.c
+++ b/src/version.c
@@ -145,6 +145,9 @@ const char *apk_version_op_string(int mask)
 		return "<";
 	case APK_VERSION_LESS|APK_VERSION_EQUAL:
 		return "<=";
+	case APK_VERSION_EQUAL|APK_VERSION_FUZZY:
+	case APK_VERSION_FUZZY:
+		return "~";
 	case APK_VERSION_EQUAL:
 		return "=";
 	case APK_VERSION_GREATER|APK_VERSION_EQUAL:
@@ -186,7 +189,7 @@ int apk_version_validate(apk_blob_t ver)
 	return t == TOKEN_END;
 }
 
-int apk_version_compare_blob(apk_blob_t a, apk_blob_t b)
+int apk_version_compare_blob_fuzzy(apk_blob_t a, apk_blob_t b, int fuzzy)
 {
 	int at = TOKEN_DIGIT, bt = TOKEN_DIGIT, tt;
 	int av = 0, bv = 0;
@@ -215,7 +218,7 @@ int apk_version_compare_blob(apk_blob_t a, apk_blob_t b)
 		return APK_VERSION_GREATER;
 
 	/* both have TOKEN_END or TOKEN_INVALID next? */
-	if (at == bt)
+	if (at == bt || fuzzy)
 		return APK_VERSION_EQUAL;
 
 	/* if only difference is pkgrev, they are equal. */
@@ -240,6 +243,11 @@ int apk_version_compare_blob(apk_blob_t a, apk_blob_t b)
 	return APK_VERSION_EQUAL;
 }
 
+int apk_version_compare_blob(apk_blob_t a, apk_blob_t b)
+{
+	return apk_version_compare_blob_fuzzy(a, b, FALSE);
+}
+
 int apk_version_compare(const char *str1, const char *str2)
 {
 	return apk_version_compare_blob(APK_BLOB_STR(str1), APK_BLOB_STR(str2));
-- 
cgit v1.2.3-70-g09d2