~lattis/muon

Implement version_compare string method v1 APPLIED

Harley Swick: 1
 Implement version_compare string method

 4 files changed, 273 insertions(+), 0 deletions(-)
Thanks for the great feedback!
Next
I have a working version with your suggested changes, but I want to test
it a bit more. I also noticed some oddities with the error messages.
Next
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~lattis/muon/patches/23804/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH] Implement version_compare string method Export this patch

---

This does not implement comparing versions with pre-release or build tags.
That is something I would like to get to eventually, but I thought it best
to get feedback on current approach before I continued further on that.
 include/functions/string.h  |   4 +
 src/functions/string.c      | 256 ++++++++++++++++++++++++++++++++++++
 tests/meson.build           |   1 +
 tests/version_compare.meson |  12 ++
 4 files changed, 273 insertions(+)
 create mode 100644 tests/version_compare.meson

diff --git a/include/functions/string.h b/include/functions/string.h
index bb02602..3d80b1b 100644
--- a/include/functions/string.h
+++ b/include/functions/string.h
@@ -8,6 +8,10 @@ enum format_cb_result {
	format_cb_error,
};

struct version {
	long major, minor, patch;
};

typedef enum format_cb_result ((*string_format_cb)(struct workspace *wk, uint32_t node, void *ctx, const char *key, uint32_t *elem));

bool string_format(struct workspace *wk, uint32_t node, uint32_t str, uint32_t *out, void *ctx, string_format_cb cb);
diff --git a/src/functions/string.c b/src/functions/string.c
index cec6fb3..a43d73a 100644
--- a/src/functions/string.c
+++ b/src/functions/string.c
@@ -237,6 +237,261 @@ func_join(struct workspace *wk, uint32_t rcvr, uint32_t args_node, uint32_t *obj
	return obj_array_join(wk, an[0].val, rcvr, obj);
}

static bool
str_to_long(struct workspace *wk, uint32_t rcvr, const char *s, long *output)
{
	char *endptr = NULL;
	*output = strtol(s, &endptr, 10);
	if (s == endptr) {
		interp_error(wk, rcvr, "nondigit in version core: %s", s);
		return false;
	}

	if (*output < 0) {
		interp_error(wk, rcvr, "negative digit in version core: %s", s);
		return false;
	}

	return true;
}

#define MAX_VER_BUF_LEN 64

static bool
str_to_version(struct workspace *wk, uint32_t rcvr, struct version *v, const char *str, size_t start)
{
	char buf[MAX_VER_BUF_LEN];
	uint32_t n = 0;
	size_t j = 0;
	long num = 0;
	for (size_t i = start; str[i]; i++) {

		if (!(j < MAX_VER_BUF_LEN)) {
			interp_error(wk, rcvr, "exceeded maximum buffer length when parsing semantic version");
			return false;
		}

		if (str[i] == '.') {
			buf[j] = '\0';
			if (!str_to_long(wk, rcvr, buf, &num)) {
				return false;
			}

			if (n == 0) {
				v->major = num;
			} else if (n == 1) {
				v->minor = num;
			}

			n++;
			buf[0] = '\0';
			j = 0;
			continue;
		}

		buf[j] = str[i];
		j++;
	}

	if (j < MAX_VER_BUF_LEN) {
		buf[j] = '\0';
	}

	if (!str_to_long(wk, rcvr, buf, &num)) {
		return false;
	}

	v->patch = num;

	return true;
}

static bool
func_version_compare(struct workspace *wk, uint32_t rcvr, uint32_t args_node, uint32_t *obj)
{
	struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL };

	if (!interp_args(wk, args_node, NULL, an, NULL)) {
		return false;
	}

	const char *str = wk_objstr(wk, rcvr);

	struct version v = { 0, 0, 0 };
	if (!str_to_version(wk, rcvr, &v, str, 0)) {
		interp_error(wk, rcvr, "invalid version string");
		return false;
	}

	const char *str_arg = wk_objstr(wk, an[0].val);

	struct version v_arg = { 0, 0, 0 };
	struct obj *res = make_obj(wk, obj, obj_bool);

	if (!strncmp(">=", str_arg, 2)) {
		if (!str_to_version(wk, rcvr, &v_arg, str_arg, 2)) {
			interp_error(wk, rcvr, "invalid version string");
			return false;
		}

		if (v.major > v_arg.major) {
			res->dat.boolean = true;
			goto ret;
		} else if (v.major < v_arg.major) {
			res->dat.boolean = false;
			goto ret;
		}

		if (v.minor > v_arg.minor) {
			res->dat.boolean = true;
			goto ret;
		} else if (v.minor < v_arg.minor) {
			res->dat.boolean = false;
			goto ret;
		}

		if (v.patch > v_arg.patch) {
			res->dat.boolean = true;
			goto ret;
		} else if (v.patch < v_arg.patch) {
			res->dat.boolean = false;
			goto ret;
		}

		res->dat.boolean = true;
	} else if (!strncmp("<=", str_arg, 2)) {
		if (!str_to_version(wk, rcvr, &v_arg, str_arg, 2)) {
			interp_error(wk, rcvr, "invalid version string");
			return false;
		}

		if (v.major < v_arg.major) {
			res->dat.boolean = true;
			goto ret;
		} else if (v.major > v_arg.major) {
			res->dat.boolean = false;
			goto ret;
		}

		if (v.minor < v_arg.minor) {
			res->dat.boolean = true;
			goto ret;
		} else if (v.minor > v_arg.minor) {
			res->dat.boolean = false;
			goto ret;
		}

		if (v.patch < v_arg.patch) {
			res->dat.boolean = true;
			goto ret;
		} else if (v.patch > v_arg.patch) {
			res->dat.boolean = false;
			goto ret;
		}

		res->dat.boolean = true;
	} else if (!strncmp("==", str_arg, 2)) {
		if (!str_to_version(wk, rcvr, &v_arg, str_arg, 2)) {
			interp_error(wk, rcvr, "invalid version string");
			return false;
		}

		res->dat.boolean = v.major == v_arg.major &&
			v.minor == v_arg.minor &&
			v.patch == v_arg.patch;
	} else if (!strncmp("!=", str_arg, 2)) {
		if (!str_to_version(wk, rcvr, &v_arg, str_arg, 2)) {
			interp_error(wk, rcvr, "invalid version string");
			return false;
		}

		if (v.major != v_arg.major) {
			res->dat.boolean = true;
			goto ret;
		}

		if (v.minor != v_arg.minor) {
			res->dat.boolean = true;
			goto ret;
		}

		if (v.patch != v_arg.patch) {
			res->dat.boolean = true;
			goto ret;
		}

		res->dat.boolean = false;
	} else if (!strncmp(">", str_arg, 1)) {
		if (!str_to_version(wk, rcvr, &v_arg, str_arg, 1)) {
			interp_error(wk, rcvr, "invalid version string");
			return false;
		}

		if (v.major > v_arg.major) {
			res->dat.boolean = true;
			goto ret;
		} else if (v.major < v_arg.major) {
			res->dat.boolean = false;
			goto ret;
		}

		if (v.minor > v_arg.minor) {
			res->dat.boolean = true;
			goto ret;
		} else if (v.minor < v_arg.minor) {
			res->dat.boolean = false;
			goto ret;
		}

		if (v.patch > v_arg.patch) {
			res->dat.boolean = true;
			goto ret;
		} else if (v.patch < v_arg.patch) {
			res->dat.boolean = false;
			goto ret;
		}

		res->dat.boolean = false;
	} else if (!strncmp("<", str_arg, 1)) {
		if (!str_to_version(wk, rcvr, &v_arg, str_arg, 1)) {
			interp_error(wk, rcvr, "invalid version string");
			return false;
		}

		if (v.major < v_arg.major) {
			res->dat.boolean = true;
			goto ret;
		} else if (v.major > v_arg.major) {
			res->dat.boolean = false;
			goto ret;
		}

		if (v.minor < v_arg.minor) {
			res->dat.boolean = true;
			goto ret;
		} else if (v.minor > v_arg.minor) {
			res->dat.boolean = false;
			goto ret;
		}

		if (v.patch < v_arg.patch) {
			res->dat.boolean = true;
			goto ret;
		} else if (v.patch > v_arg.patch) {
			res->dat.boolean = false;
			goto ret;
		}

		res->dat.boolean = false;
	} else {
		interp_error(wk, rcvr, "invalid comparison operator");
		return false;
	}

ret:
	return true;
}

const struct func_impl_name impl_tbl_string[] = {
	{ "strip", func_strip },
	{ "to_upper", func_to_upper },
@@ -244,5 +499,6 @@ const struct func_impl_name impl_tbl_string[] = {
	{ "underscorify", func_underscorify },
	{ "split", func_split },
	{ "join", func_join },
	{ "version_compare", func_version_compare },
	{ NULL, NULL },
};
diff --git a/tests/meson.build b/tests/meson.build
index db89f5a..f0df7b6 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -4,6 +4,7 @@ tests = [
	'join_paths.meson',
	'katie.meson',
	'join.meson',
	'version_compare.meson'
]

foreach t : tests
diff --git a/tests/version_compare.meson b/tests/version_compare.meson
new file mode 100644
index 0000000..51bebcd
--- /dev/null
+++ b/tests/version_compare.meson
@@ -0,0 +1,12 @@
version_number = '1.2.8'

assert(version_number.version_compare('>=1.2.8'))
assert(not version_number.version_compare('>1.2.8'))
assert(not version_number.version_compare('<1.2.8'))
assert(version_number.version_compare('<=1.2.8'))
assert(version_number.version_compare('==1.2.8'))
assert(not version_number.version_compare('!=1.2.8'))
assert(not version_number.version_compare('==1.2.9'))

assert(version_number.version_compare('<2.0'))
assert(version_number.version_compare('>0.9'))
-- 
2.32.0