diff --git a/libropkg/include/ropkg/version.h b/libropkg/include/ropkg/version.h new file mode 100644 index 0000000..7cbd4c4 --- /dev/null +++ b/libropkg/include/ropkg/version.h @@ -0,0 +1,57 @@ +#ifndef ROPKG_VERSION_H_ +#define ROPKG_VERSION_H_ + +#include +#include +#include + +#define ROPKG_VERSION_UPSTREAM_DIGIT_MAX 5 + +struct ropkg_version; + +enum ropkg_comparison_op { + ROPKG_OP_EQUAL = 0, + ROPKG_OP_NOT_EQUAL = -1, + ROPKG_OP_LESS_THAN = -2, + ROPKG_OP_LESS_EQUAL = -3, + ROPKG_OP_GREATER_THAN = 1, + ROPKG_OP_GREATER_EQUAL = 2, +}; + +enum ropkg_version_release_phase { + ROPKG_VERSION_RELEASE_PHASE_ALPHA = 0, + ROPKG_VERSION_RELEASE_PHASE_BETA, + ROPKG_VERSION_RELEASE_PHASE_RC, + ROPKG_VERSION_RELEASE_PHASE_RELEASE, +}; + +ROPKG_API enum ropkg_status ropkg_version_create(struct ropkg_version **out); +ROPKG_API void ropkg_version_destroy(struct ropkg_version *version); + +ROPKG_API void ropkg_version_set_release_phase( + struct ropkg_version *version, + enum ropkg_version_release_phase phase); +ROPKG_API void ropkg_version_set_upstream_version( + struct ropkg_version *version, + const unsigned int upstream_version[ROPKG_VERSION_UPSTREAM_DIGIT_MAX]); +ROPKG_API void ropkg_version_set_version_release_phase( + struct ropkg_version *version, + enum ropkg_version_release_phase phase, + unsigned int revision); +ROPKG_API void ropkg_version_set_package_revision( + struct ropkg_version *version, + unsigned int revision); + +ROPKG_API b_result +ropkg_version_parse(struct ropkg_version *version, const char *s); +ROPKG_API size_t ropkg_version_to_string( + const struct ropkg_version *version, + char *out, + size_t max); + +ROPKG_API bool ropkg_version_compare( + const struct ropkg_version *left, + const struct ropkg_version *right, + enum ropkg_comparison_op op); + +#endif diff --git a/libropkg/version.c b/libropkg/version.c new file mode 100644 index 0000000..8ef85cb --- /dev/null +++ b/libropkg/version.c @@ -0,0 +1,483 @@ +#include "version.h" + +#include +#include +#include +#include + +enum ropkg_status ropkg_version_create(struct ropkg_version **out) +{ + struct ropkg_version *version = malloc(sizeof *version); + if (!version) { + return ROPKG_ERR_NO_MEMORY; + } + + memset(version, 0x0, sizeof *version); + + version->v_release_phase = ROPKG_VERSION_RELEASE_PHASE_RELEASE; + version->v_upstream_version[0] = 1; + version->v_version_release_phase = ROPKG_VERSION_RELEASE_PHASE_RELEASE; + version->v_version_release_phase_revision = 1; + version->v_package_revision = 1; + + *out = version; + return ROPKG_SUCCESS; +} + +void ropkg_version_destroy(struct ropkg_version *version) +{ + free(version); +} + +static b_result parse_release_phase( + const char *s, + enum ropkg_version_release_phase *out) +{ + if (s[0] == 0) { + *out = ROPKG_VERSION_RELEASE_PHASE_RELEASE; + } else if (!strcmp(s, "alpha")) { + *out = ROPKG_VERSION_RELEASE_PHASE_ALPHA; + } else if (!strcmp(s, "beta")) { + *out = ROPKG_VERSION_RELEASE_PHASE_BETA; + } else { + b_result result = b_error_with_msg_template( + ROPKG_ERROR_VENDOR, + ROPKG_ERR_INVALID_VERSION_FORMAT, + ROPKG_EMSG_INVALID_RELEASE_PHASE, + B_ERROR_PARAM("phase", s)); + b_error_add_submsg( + result, + B_ERROR_SUBMSG_INFO, + ROPKG_EMSG_AVAILABLE_RELEASE_PHASES); + return result; + } + + return B_RESULT_SUCCESS; +} + +static b_result parse_version_release_phase( + const char *s, + enum ropkg_version_release_phase *out_phase, + unsigned int *out_revision, + size_t *nr_read) +{ + size_t i = 0; + + if (*s == '\0') { + *out_phase = ROPKG_VERSION_RELEASE_PHASE_RELEASE; + *out_revision = 1; + *nr_read = i; + return B_RESULT_SUCCESS; + } + + if (*s == '~') { + i++; + s++; + } else { + return b_error_with_msg( + ROPKG_ERROR_VENDOR, + ROPKG_ERR_INVALID_VERSION_FORMAT, + ROPKG_EMSG_INVALID_VERSION_RELEASE_PHASE_UNKNOWN_START); + } + + char temp[64]; + size_t z = 0; + + while (*s && isalpha(*s) && z < sizeof temp - 1) { + temp[z++] = tolower(*s); + temp[z] = 0; + i++; + s++; + } + + if (!strcmp(temp, "alpha")) { + *out_phase = ROPKG_VERSION_RELEASE_PHASE_ALPHA; + } else if (!strcmp(temp, "beta")) { + *out_phase = ROPKG_VERSION_RELEASE_PHASE_BETA; + } else if (!strcmp(temp, "rc")) { + *out_phase = ROPKG_VERSION_RELEASE_PHASE_RC; + } else { + b_result result = b_error_with_msg_template( + ROPKG_ERROR_VENDOR, + ROPKG_ERR_INVALID_VERSION_FORMAT, + ROPKG_EMSG_INVALID_VERSION_RELEASE_PHASE, + B_ERROR_PARAM("phase", temp)); + b_error_add_submsg( + result, + B_ERROR_SUBMSG_INFO, + ROPKG_EMSG_AVAILABLE_VERSION_RELEASE_PHASES); + return result; + } + + z = 0; + while (*s && isdigit(*s)) { + temp[z++] = *s; + temp[z] = 0; + i++; + s++; + } + + if (z > 0) { + char *ep; + *out_revision = strtoul(temp, &ep, 10); + } + + *nr_read = i; + return B_RESULT_SUCCESS; +} + +static b_result parse_package_revision( + const char *s, + unsigned int *out_revision, + size_t *nr_read) +{ + size_t i = 0; + + if (*s == '\0') { + *out_revision = 1; + *nr_read = i; + return B_RESULT_SUCCESS; + } + + if (*s == '-') { + i++; + s++; + } else { + return b_error_with_msg( + ROPKG_ERROR_VENDOR, + ROPKG_ERR_INVALID_VERSION_FORMAT, + ROPKG_EMSG_INVALID_PACKAGE_REVISION_UNKNOWN_START); + } + + char temp[64]; + size_t z = 0; + + while (*s && isdigit(*s) && z < sizeof temp - 1) { + temp[z++] = *s; + temp[z] = 0; + i++; + s++; + } + + char *ep; + *out_revision = strtoul(temp, &ep, 10); + + *nr_read = i; + return B_RESULT_SUCCESS; +} + +static b_result parse_upstream_version( + const char *s, + unsigned int out[ROPKG_VERSION_UPSTREAM_DIGIT_MAX], + size_t *nr_read) +{ + if (!isdigit(*s)) { + return b_error_with_msg( + ROPKG_ERROR_VENDOR, + ROPKG_ERR_INVALID_VERSION_FORMAT, + ROPKG_EMSG_INVALID_UPSTREAM_VERSION_UNKNOWN_START); + } + + char temp[64]; + size_t i = 0; + unsigned int nr_temp = 0; + unsigned int nr_digits = 0; + char last = 0; + char *ep; + + while (1) { + last = s[i]; + + if (isdigit(s[i])) { + temp[nr_temp++] = s[i]; + temp[nr_temp] = 0; + i++; + continue; + } + + if (s[i] == '.') { + if (nr_temp == 0) { + return b_error_with_msg( + ROPKG_ERROR_VENDOR, + ROPKG_ERR_INVALID_VERSION_FORMAT, + ROPKG_EMSG_INVALID_UPSTREAM_VERSION_TWO_DOTS); + } + + if (nr_digits >= ROPKG_VERSION_UPSTREAM_DIGIT_MAX - 1) { + return b_error_with_msg( + ROPKG_ERROR_VENDOR, + ROPKG_ERR_INVALID_VERSION_FORMAT, + ROPKG_EMSG_INVALID_UPSTREAM_VERSION_TOO_MANY_NUMBERS); + } + + out[nr_digits] = strtoul(temp, &ep, 10); + nr_digits++; + nr_temp = 0; + i++; + continue; + } + + if (nr_temp == 0) { + return b_error_with_msg( + ROPKG_ERROR_VENDOR, + ROPKG_ERR_INVALID_VERSION_FORMAT, + ROPKG_EMSG_INVALID_UPSTREAM_VERSION_END_DOT); + } + + out[nr_digits] = strtoul(temp, &ep, 10); + nr_digits++; + nr_temp = 0; + break; + } + + *nr_read = i; + return B_RESULT_SUCCESS; +} + +b_result ropkg_version_parse(struct ropkg_version *version, const char *s) +{ + enum ropkg_version_release_phase release_phase + = ROPKG_VERSION_RELEASE_PHASE_RELEASE; + unsigned int upstream_version[ROPKG_VERSION_UPSTREAM_DIGIT_MAX] + = {0, 0, 0, 0, 0}; + enum ropkg_version_release_phase version_release_phase + = ROPKG_VERSION_RELEASE_PHASE_RELEASE; + unsigned int version_release_phase_revision = 1; + unsigned int package_revision = 1; + + b_result result = B_RESULT_SUCCESS; + + char temp[64] = {0}; + size_t z = 0; + + while (*s && !isdigit(*s) && z < sizeof temp - 1) { + temp[z] = tolower(*s); + z++; + s++; + } + + result = parse_release_phase(temp, &release_phase); + if (b_result_is_error(result)) { + return b_result_propagate(result); + } + + size_t nr_read = 0; + result = parse_upstream_version(s, upstream_version, &nr_read); + if (b_result_is_error(result)) { + return b_result_propagate(result); + } + + s += nr_read; + if (*s == '~' || *s == 0) { + result = parse_version_release_phase( + s, + &version_release_phase, + &version_release_phase_revision, + &nr_read); + + s += nr_read; + if (b_result_is_error(result)) { + return b_result_propagate(result); + } + } + + if (*s == '-' || *s == 0) { + result = parse_package_revision(s, &package_revision, &nr_read); + + s += nr_read; + if (b_result_is_error(result)) { + return b_result_propagate(result); + } + } + + if (*s != 0) { + return b_error_with_msg( + ROPKG_ERROR_VENDOR, + ROPKG_ERR_INVALID_VERSION_FORMAT, + ROPKG_EMSG_INVALID_VERSION_UNKNOWN_END); + } + + version->v_release_phase = release_phase; + memcpy(version->v_upstream_version, + upstream_version, + sizeof upstream_version); + version->v_version_release_phase = version_release_phase; + version->v_version_release_phase_revision + = version_release_phase_revision; + version->v_package_revision = package_revision; + + return B_RESULT_SUCCESS; +} + +void ropkg_version_set_release_phase( + struct ropkg_version *version, + enum ropkg_version_release_phase phase) +{ + if (phase == ROPKG_VERSION_RELEASE_PHASE_RC) { + return; + } + + version->v_release_phase = phase; +} + +void ropkg_version_set_upstream_version( + struct ropkg_version *version, + const unsigned int upstream_version[ROPKG_VERSION_UPSTREAM_DIGIT_MAX]) +{ + memcpy(version->v_upstream_version, + upstream_version, + sizeof version->v_upstream_version); +} + +void ropkg_version_set_version_release_phase( + struct ropkg_version *version, + enum ropkg_version_release_phase phase, + unsigned int revision) +{ + version->v_version_release_phase = phase; + + if (phase == ROPKG_VERSION_RELEASE_PHASE_RELEASE) { + version->v_version_release_phase_revision = 1; + } else { + version->v_version_release_phase_revision = revision; + } +} + +void ropkg_version_set_package_revision( + struct ropkg_version *version, + unsigned int revision) +{ + version->v_package_revision = revision; +} + +static const char *release_phase_to_string( + enum ropkg_version_release_phase phase) +{ + switch (phase) { + case ROPKG_VERSION_RELEASE_PHASE_ALPHA: + return "alpha"; + case ROPKG_VERSION_RELEASE_PHASE_BETA: + return "beta"; + case ROPKG_VERSION_RELEASE_PHASE_RC: + return "rc"; + default: + return ""; + } +} + +static unsigned int upstream_version_length( + const unsigned int version[ROPKG_VERSION_UPSTREAM_DIGIT_MAX]) +{ + unsigned int length = 0; + for (unsigned int i = 0; i < ROPKG_VERSION_UPSTREAM_DIGIT_MAX; i++) { + if (version[i] != 0) { + length = i + 1; + } + } + + if (length < 2) { + length = 2; + } + + return length; +} + +size_t ropkg_version_to_string( + const struct ropkg_version *version, + char *out, + size_t max) +{ + b_stringstream str; + b_stringstream_begin(&str, out, max); + + b_stringstream_add( + &str, + release_phase_to_string(version->v_release_phase)); + + unsigned int version_length + = upstream_version_length(version->v_upstream_version); + + for (unsigned int i = 0; i < version_length; i++) { + if (i > 0) { + b_stringstream_add(&str, "."); + } + + b_stringstream_addf(&str, "%u", version->v_upstream_version[i]); + } + + if (version->v_version_release_phase + != ROPKG_VERSION_RELEASE_PHASE_RELEASE) { + b_stringstream_addf( + &str, + "~%s", + release_phase_to_string( + version->v_version_release_phase)); + + if (version->v_version_release_phase_revision > 1 + || version->v_version_release_phase + == ROPKG_VERSION_RELEASE_PHASE_RC) { + b_stringstream_addf( + &str, + "%u", + version->v_version_release_phase_revision); + } + } + + if (version->v_package_revision > 1) { + b_stringstream_addf(&str, "-%u", version->v_package_revision); + } + + b_stringstream_end(&str); + return b_stringstream_get_length(&str); +} + +static int version_compare( + const struct ropkg_version *left, + const struct ropkg_version *right) +{ +#define COMPARE(left, right, item) \ + do { \ + if ((left)->item < (right)->item) \ + return -1; \ + else if ((left)->item > (right)->item) \ + return 1; \ + } while (0) + + COMPARE(left, right, v_release_phase); + + for (int i = 0; i < ROPKG_VERSION_UPSTREAM_DIGIT_MAX; i++) { + COMPARE(left, right, v_upstream_version[i]); + } + + COMPARE(left, right, v_version_release_phase); + COMPARE(left, right, v_version_release_phase_revision); + COMPARE(left, right, v_package_revision); + +#undef COMPARE + return 0; +} + +bool ropkg_version_compare( + const struct ropkg_version *left, + const struct ropkg_version *right, + enum ropkg_comparison_op op) +{ + int result = version_compare(left, right); + + switch (op) { + case ROPKG_OP_EQUAL: + return result == 0; + case ROPKG_OP_NOT_EQUAL: + return result != 0; + case ROPKG_OP_LESS_THAN: + return result < 0; + case ROPKG_OP_LESS_EQUAL: + return result <= 0; + case ROPKG_OP_GREATER_THAN: + return result > 0; + case ROPKG_OP_GREATER_EQUAL: + return result >= 0; + default: + return false; + } +} diff --git a/libropkg/version.h b/libropkg/version.h new file mode 100644 index 0000000..5f5084b --- /dev/null +++ b/libropkg/version.h @@ -0,0 +1,17 @@ +#ifndef _VERSION_H_ +#define _VERSION_H_ + +#include + +struct ropkg_version { + enum ropkg_version_release_phase v_release_phase; + + unsigned int v_upstream_version[ROPKG_VERSION_UPSTREAM_DIGIT_MAX]; + + enum ropkg_version_release_phase v_version_release_phase; + unsigned int v_version_release_phase_revision; + + unsigned int v_package_revision; +}; + +#endif