~lattis/muon

Implement version_compare method for string v1 APPLIED

Harley Swick: 1
 Implement version_compare method for string

 4 files changed, 218 insertions(+), 0 deletions(-)
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/23848/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH] Implement version_compare method for string Export this patch

---
 include/functions/string.h  |   4 +
 src/functions/string.c      | 197 ++++++++++++++++++++++++++++++++++++
 tests/meson.build           |   1 +
 tests/version_compare.meson |  16 +++
 4 files changed, 218 insertions(+)
 create mode 100644 tests/version_compare.meson

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

struct version {
	uint32_t 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..e2eb33c 100644
--- a/src/functions/string.c
+++ b/src/functions/string.c
@@ -237,6 +237,202 @@ 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_uint32(struct workspace *wk, uint32_t error_node, const char *s, uint32_t *output)
{
	char *endptr = NULL;
	int32_t n = strtol(s, &endptr, 10);
	if (*endptr) {
		interp_error(wk, error_node, "nondigit in version core: %s", s);
		return false;
	}

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

	*output = n;

	return true;
}

#define MAX_VER_BUF_LEN 64

static bool
str_to_version(struct workspace *wk, uint32_t error_node, struct version *v, const char *str)
{
	char buf[MAX_VER_BUF_LEN];
	uint32_t n = 0;
	size_t j = 0;
	uint32_t num = 0;
	for (size_t i = 0; str[i]; i++) {
		if (!(j < MAX_VER_BUF_LEN)) {
			interp_error(wk, error_node, "exceeded maximum buffer length when parsing semantic version");
			return false;
		}

		if (str[i] == '.') {
			buf[j] = '\0';
			if (!str_to_uint32(wk, error_node, 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_uint32(wk, error_node, buf, &num)) {
		return false;
	}

	v->patch = num;

	return true;
}

static bool
op_gt(uint32_t a, uint32_t b)
{
	return a > b;
}

static bool
op_ge(uint32_t a, uint32_t b)
{
	return a >= b;
}

static bool
op_lt(uint32_t a, uint32_t b)
{
	return a < b;
}

static bool
op_le(uint32_t a, uint32_t b)
{
	return a <= b;
}

static bool
op_eq(uint32_t a, uint32_t b)
{
	return a == b;
}

static bool
op_ne(uint32_t a, uint32_t b)
{
	return a != b;
}

typedef bool ((*comparator)(uint32_t a, uint32_t b));

static bool
string_version_compare(struct workspace *wk, uint32_t rcvr, const char *str, const char *str_arg, uint32_t str_arg_node, bool *output)
{
	struct version v = { 0, 0, 0 };
	if (!str_to_version(wk, rcvr, &v, str)) {
		interp_error(wk, rcvr, "invalid version string");
		return false;
	}

	comparator op = op_eq;

	static struct {
		const char *name;
		comparator op;
	} ops[] = {
		{ ">=", op_ge, },
		{ ">",  op_gt, },
		{ "==", op_eq, },
		{ "!=", op_ne, },
		{ "<=", op_le, },
		{ "<",  op_lt, },
		{ "=", op_eq, },
		NULL
	};

	uint32_t i, op_len = 0;
	for (i = 0; ops[i].name; ++i) {
		op_len = strlen(ops[i].name);

		if (!strncmp(str_arg, ops[i].name, op_len)) {
			str_arg = &str_arg[op_len];
			op = ops[i].op;
			break;
		}
	}

	struct version v_arg = { 0, 0, 0 };

	if (!str_to_version(wk, str_arg_node, &v_arg, str_arg)) {
		interp_error(wk, str_arg_node, "invalid version string");
		return false;
	}

	if (v.major != v_arg.major) {
		*output = op(v.major, v_arg.major);
		goto ret;
	}

	if (v.minor != v_arg.minor) {
		*output = op(v.minor, v_arg.minor);
		goto ret;
	}

	if (v.patch != v_arg.patch) {
		*output = op(v.patch, v_arg.patch);
		goto ret;
	}

	if (op == op_eq || op == op_ge || op == op_le) {
		*output = true;
		goto ret;
	}

	*output = false;

ret:
	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, an, NULL, NULL)) {
		return false;
	}

	const char *str = wk_objstr(wk, rcvr);
	const char *str_arg = wk_objstr(wk, an[0].val);
	struct obj *res = make_obj(wk, obj, obj_bool);
	if (!string_version_compare(wk, rcvr, str, str_arg, an[0].node, &res->dat.boolean)) {
		return false;
	}

	return true;
}

const struct func_impl_name impl_tbl_string[] = {
	{ "strip", func_strip },
	{ "to_upper", func_to_upper },
@@ -244,5 +440,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 71e97aa..cf3b1d3 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -5,6 +5,7 @@ tests = [
	{ 'name': 'join_paths.meson', },
	{ 'name': 'katie.meson', },
	{ 'name': 'join.meson', },
	{ 'name': 'version_compare.meson', },
]

foreach t : tests
diff --git a/tests/version_compare.meson b/tests/version_compare.meson
new file mode 100644
index 0000000..be4b95d
--- /dev/null
+++ b/tests/version_compare.meson
@@ -0,0 +1,16 @@
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(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.3.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
Oops, didn't mean to make a new thread.
Looks good to me!  Thanks for this.

Stone