Implementing history parsing.
h3rald h3rald@h3rald.com
Wed, 10 Jun 2026 18:22:51 +0200
2 files changed,
118 insertions(+),
4 deletions(-)
M
src/conver.c
→
src/conver.c
@@ -1,5 +1,8 @@
#include "conver.h" +conver_release_t history[0xFFFF]; +uint16_t history_size = 0; + #if defined(_WIN32) || defined(_WIN64) # define FS_WINDOWS # ifndef WIN32_LEAN_AND_MEAN@@ -137,6 +140,94 @@ conver_result_t fs_read(const char *path, char **out_buf, size_t *out_len) {
return fs_read_all(path, "r", (void **)out_buf, out_len, 1); } +static bool validate_date(conver_release_t *r) { + struct tm t = {0}; + t.tm_year = r->year - 1900; + t.tm_mon = r->month - 1; + t.tm_mday = r->day; + + struct tm original = t; + if (mktime(&t) == (time_t)-1) { return false; } + + // If mktime normalized the fields, then the date is wrong + return t.tm_year == original.tm_year && + t.tm_mon == original.tm_mon && + t.tm_mday == original.tm_mday; +} + +int parse_release(const char *line, conver_release_t *r) { + char sign; + char number[5]; + + /* Parse date, sign, and hex digits */ + if (sscanf(line, "%hd-%hd-%hd: %c%4s", &r->year, &r->month, &r->day, &sign, number) != 5) + { + return print_result(CONVER_ERR_INVALID_RELEASE); + } + if (!validate_date(r)) { + return print_result(CONVER_ERR_INVALID_DATE); + } + uint16_t version = strtoul(number, NULL, 16); + if (!version) { + return print_result(CONVER_ERR_INVALID_DATE); + } + if (sign == '+'){ r->widthdrawn = false; } + else if (sign == '-') { r->widthdrawn = true; } + else { return print_result(CONVER_ERR_INVALID_RELEASE); } + // Extract comment after '#' + r->comment[0] = '\0'; + const char *hash = strchr(line, '#'); + if (hash) { + // Skip the '#' and any leading spaces + hash++; + while (*hash == ' ') { hash++; }; + strncpy(r->comment, hash, sizeof(r->comment) - 1); + r->comment[sizeof(r->comment) - 1] = '\0'; + // Strip trailing newline and whitespace + int len = strlen(r->comment); + while (len > 0 && (r->comment[len-1] == '\n' || + r->comment[len-1] == '\r' || + r->comment[len-1] == ' ')) { + r->comment[--len] = '\0'; + } + } + return 0; +} + +int parse_history() { + char *contents; + size_t contents_len = 0; + int result = fs_read(CONVER_HISTORY_FILE, &contents, &contents_len); + if (result != CONVER_OK) { + return print_result(result); + } + int count = 0; + const char *line = contents; + while (*line && count < sizeof(history)) { + // Find end of line + const char *end = strchr(line, '\n'); + size_t len = end ? (size_t)(end - line) : strlen(line); + // Copy line into a temporary buffer + char buf[512]; + if (len < sizeof(buf)) { + memcpy(buf, line, len); + buf[len] = '\0'; + result = parse_release(buf, &history[count]); + if (result != CONVER_OK) { + fprintf(stderr, "%s - Line %d: ", CONVER_HISTORY_FILE, count); + return print_result(result); + } + count++; + history_size = count; + } + if (!end) { break; }; // last line had no trailing newline + line = end + 1; + } + return 0; /* number of successfully parsed entries */ +} + + + //// Commands int command_init()@@ -177,6 +268,7 @@ }
int command_draft() { + parse_history(); char score[16]; uint16_t version; int valid = 0;@@ -204,6 +296,14 @@ if (!version) {
print_result(CONVER_ERR_INVALID_VALUE); continue; } + if (history_size > 0) + { + uint16_t last_release = history[history_size-1].version; + if ((last_release >> 4) > version) { + print_result(CONVER_ERR_LOW_SCORE); + continue; + } + } valid = 1; } version = version << 4; // left shift to make room for metadata nibble@@ -288,6 +388,7 @@ }
int command_release() { + parse_history(); char *draft_string; size_t draft_len = 0; int result = fs_read(CONVER_DRAFT_FILE, &draft_string, &draft_len);@@ -304,11 +405,11 @@ }
time_t raw_time = time(NULL); struct tm *local_time = localtime(&raw_time); char date[11]; - char comment[255]; + char comment[256]; strftime(date, sizeof(date), "%Y-%m-%d", local_time); printf("> Enter a brief comment/note for this release (max 255 chars): "); fgets(comment, sizeof(comment), stdin); - char release[275]; + char release[280]; snprintf(release, sizeof(release), "%s: +%04X # %s", date, draft, comment); result = fs_append(CONVER_HISTORY_FILE, release); if (result != CONVER_OK) {
M
src/conver.h
→
src/conver.h
@@ -16,7 +16,7 @@ #include <stdint.h>
#include <string.h> #include <ctype.h> #include <time.h> - +#include <stdbool.h> typedef enum { CONVER_OK = 0,@@ -28,9 +28,12 @@ CONVER_ERR_STAT = 5,
CONVER_ERR_MKDIR = 6, CONVER_ERR_INVALID_VALUE = 7, CONVER_ERR_NO_DRAFT = 8, + CONVER_ERR_INVALID_RELEASE = 9, + CONVER_ERR_INVALID_DATE = 10, + CONVER_ERR_LOW_SCORE = 11, } conver_result_t; -char * conver_errors[9] = { +char *conver_errors[12] = { "Success", "Unable to open file.", "Unable to write file",@@ -40,6 +43,16 @@ "Unable to get file/directory information",
"Unable to create directory", "Invalid value", "No draft version configured. Please run: conver draft", + "Invalid release", + "Invalid date", + "Specified score is too low", }; + +typedef struct { + uint16_t year, month, day; + bool widthdrawn; + uint16_t version; + char comment[256]; +} conver_release_t; #endif // CONVER_H