From 268809869b5edce73fa205ca3d9798ee43d73d33 Mon Sep 17 00:00:00 2001
From: EuAndreh <eu@euandre.org>
Date: Thu, 4 Mar 2021 00:22:23 -0300
Subject: WIP: work on C implementation

---
 src/remembering.c | 469 ++++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 331 insertions(+), 138 deletions(-)

(limited to 'src')

diff --git a/src/remembering.c b/src/remembering.c
index 36c63f5..516cc01 100644
--- a/src/remembering.c
+++ b/src/remembering.c
@@ -8,9 +8,17 @@ int main() { return 0; }
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <libgen.h>
 
 #ifdef TEST
 #include <assert.h>
+static void testing(const char *message) {
+  fprintf(stderr, "testing: %s...", message);
+}
+
+void test_ok() { fprintf(stderr, " OK.\n"); }
 #endif
 
 #ifdef FALLIBLE
@@ -42,17 +50,12 @@ static void print_missing(FILE *stream, char *text) {
   fprintf(stream, "Missing option: %s\n", text);
 }
 
-struct Pair {
-  char *first;
-  char *second;
-};
-
-static int get_options(int argc, char *argv[], struct Pair *pair) {
+static int get_options(int argc, char *argv[], char **command, char **profile) {
   for (int i = 0; i < argc; i++) {
     if (!strcmp(argv[i], "--help")) {
       print_usage(stdout, argv[0]);
       print_help(stdout);
-      exit(0);
+      exit(EXIT_SUCCESS);
     }
   }
 
@@ -70,10 +73,10 @@ static int get_options(int argc, char *argv[], struct Pair *pair) {
     case 'h':
       print_usage(stdout, argv[0]);
       print_help(stdout);
-      exit(0);
+      exit(EXIT_SUCCESS);
     case 'V':
       fprintf(stdout, "remembering-%s %s\n", VERSION, DATE);
-      exit(0);
+      exit(EXIT_SUCCESS);
     }
   }
 
@@ -89,17 +92,36 @@ static int get_options(int argc, char *argv[], struct Pair *pair) {
     return 2;
   }
 
-  pair->first = malloc(strlen(commandarg) + 1);
-  pair->second = malloc(strlen(profilearg) + 1);
-  if (!pair->first || !pair->second) {
+  *command = malloc(strlen(commandarg) + 1);
+  if (!*command) {
+    return 1;
+  }
+  *profile = malloc(strlen(profilearg) + 1);
+  if (!*profile) {
+    free(*command);
     return 1;
   }
-  strcpy(pair->first, commandarg);
-  strcpy(pair->second, profilearg);
+  strcpy(*command, commandarg);
+  strcpy(*profile, profilearg);
 
   return 0;
 }
 
+static int mkdir_p(char *path, mode_t mode) {
+  struct stat s;
+  if (stat(path, &s) == 0 && S_ISDIR(s.st_mode)) {
+    return 0;
+  }
+
+  char *parent = dirname(path);
+  int ret = mkdir_p(parent, mode);
+  if (ret) {
+    return ret;
+  }
+  return mkdir(path, mode);
+}
+
+// add fallible variation? where are the tests to stress it?
 static char *expand_profile_name(const char *const profile_name) {
   char *prefix = NULL;
   char *env_prefix = getenv("XDG_DATA_HOME");
@@ -131,80 +153,22 @@ static char *expand_profile_name(const char *const profile_name) {
 
   free(prefix);
 
-  return expanded_profile;
-}
-
-#ifdef TEST
-char *set_test_home(const char *new_home) {
-  size_t size = _PC_PATH_MAX == -1 ? 4096 : _PC_PATH_MAX;
-  char *buf = malloc(size + 1);
-  if (!buf) {
-    return NULL;
-  }
-
-  char *cwd = getcwd(buf, size);
-  if (!cwd) {
-    free(buf);
-    return NULL;
-  }
-
-  const char *suffix = "/tests/test-profiles/";
-  char *home = malloc(strlen(cwd) + strlen(suffix) + 1);
-  if (!new_home) {
-    free(buf);
+  errno = 0;
+  int ret = mkdir_p(dirname(expanded_profile), 0755);
+  if (ret) {
     return NULL;
   }
-
-  strcpy(new_home, buf);
-  strcat(new_home, suffix);
-
-  char *previous_home = getenv("HOME");
-  setenv("HOME", new_home, 1);
-  return previous_home;
-}
-
-void expand_profile_name_test() {
-  {
-    testing("expanding profile name without setting $XDG_DATA_HOME");
-    // FIXME: this can fail
-    const char *new_home = sharedc_random_string("test-expand-profile-name-");
-    const char *previous_home = set_test_home(new_home);
-
-    int ret = setenv("HOME", previous_home, 1);
-    assert(ret == 0);
-
-    char *n = expand_profile_name(new_home);
-    assert(n);
-    test_ok();
-  }
-}
-#endif
-
-/*
-static void ensure_profile(char *profile) {
-  char *prefix = dirname(profile);
-  struct stat sb;
-  if (!(stat(prefix, &sb) == 0 && S_ISDIR(sb.st_mode))) {
-    mkdir_p(prefix, 0755);
-  }
-  // FILE *f = fopen(profile, "w+");
-  // fclose(f);
-}
-
-#ifdef TEST
-static void test_ensure_profile() {
-  {
-    testing("xablau");
-    test_ok();
-  }
+  return expanded_profile;
 }
-#endif
 
-static const char RANKING_DELIMITER = ':';
+static const size_t RANKINGS_INITIAL_SIZE = 100;
+static const int RANKINGS_GROWTH_MULTIPLIER = 2;
 
 struct Rankings {
-  struct Vector *values;
-  struct Vector *ranks;
+  size_t count;
+  size_t size;
+  char **values;
+  int *ranks;
 };
 
 static struct Rankings *rankings_new() {
@@ -212,48 +176,98 @@ static struct Rankings *rankings_new() {
   if (!r) {
     return NULL;
   }
-  r->values = vector_new();
+  r->count = 0;
+  r->size = RANKINGS_INITIAL_SIZE;
+  r->values = malloc(r->size * sizeof(char *));
   if (!r->values) {
     free(r);
     return NULL;
   }
-  r->ranks = vector_new();
+  r->ranks = malloc(r->size * sizeof(int));
   if (!r->ranks) {
-    vector_free(r->values);
+    free(r->values);
     free(r);
     return NULL;
   }
   return r;
 }
 
+#ifdef FALLIBLE
+static struct Rankings *fallible_rankings_new(const char *const filename, int lineno) {
+  if (fallible_should_fail(filename, lineno)) {
+    return NULL;
+  }
+  return rankings_new();
+}
+
+#define rankings_new() fallible_rankings_new(__FILE__, __LINE__)
+#endif
+
 static void rankings_free(struct Rankings *r) {
-  vector_free(r->values);
-  vector_free(r->ranks);
+  if (!r) {
+    return;
+  }
+  for (size_t i = 0; i < r->count; i++) {
+    free(r->values[i]);
+  }
+  free(r->values);
+  free(r->ranks);
   free(r);
 }
 
-static bool rankings_insert(struct Rankings *r, char *value, int *rank) {
-  return vector_push_back(r->values, value) && vector_push_back(r->ranks, rank);
+static int rankings_insert(struct Rankings *r, char *value, int rank) {
+  if (r->count == r->size) {
+    r->size *= RANKINGS_GROWTH_MULTIPLIER;
+    char **new_values = realloc(r->values, r->size * sizeof(char *));
+    if (!new_values) {
+      return -1;
+    }
+    r->values = new_values;
+    int *new_ranks = realloc(r->ranks, r->size * sizeof(int));
+    if (!new_ranks) {
+      return -1;
+    }
+    r->ranks = new_ranks;
+  }
+  r->values[r->count] = value;
+  r->ranks[r->count] = rank;
+  r->count++;
+  return 0;
+}
+
+#ifdef FALLIBLE
+static int fallible_rankings_insert(struct Rankings *r, char *value, int rank, const char *const filename, int lineno) {
+  if (fallible_should_fail(filename, lineno)) {
+    return -1;
+  }
+  return rankings_insert(r, value, rank);
 }
 
+#define rankings_insert(r, value, rank) fallible_rankings_insert(r, value, rank, __FILE__, __LINE__)
+#endif
+
 #ifdef TEST
-static void test_rankings() {
+static void rankings_test() {
   {
-    testing("struct Rankings grows values and ranks at the same rate");
+    testing("struct Rankings expands its size when needed");
     struct Rankings *r = rankings_new();
-    int some_limit = 123;
-    for (int i = 0; i < some_limit; i++) {
-      char *s = strdup("some text");
-
-      int *p = malloc(sizeof(int));
-      assert(p);
-      *p = i;
-
-      rankings_insert(r, s, p);
+    size_t some_limit = RANKINGS_INITIAL_SIZE + 1;
+    int ret;
+    for (size_t i = 0; i < some_limit; i++) {
+      ret = rankings_insert(r, strdup("some string"), i);
+      /*
+      char *s = strdup("some string");
+      ret = rankings_insert(r, s, i);
+      if (ret) {
+        free(s);
+        rankings_free(r);
+        exit(EXIT_FAILURE);
+      }
+      */
     }
 
-    assert(r->values->count == r->ranks->count);
-    assert(r->values->size == r->ranks->size);
+    assert(r->size == RANKINGS_INITIAL_SIZE * RANKINGS_GROWTH_MULTIPLIER);
+    assert(r->count == some_limit);
 
     rankings_free(r);
     test_ok();
@@ -262,55 +276,207 @@ static void test_rankings() {
   {
     testing("an empty Rankings doesn't leak");
     struct Rankings *r = rankings_new();
+
+    assert(r->size == RANKINGS_INITIAL_SIZE);
+    assert(r->count == 0);
+
     rankings_free(r);
     test_ok();
   }
 }
 #endif
 
-/ *
-
-struct GetlineParams {
-  char *line;
-  size_t len;
-  ssize_t read;
-  FILE *stream;
-};
+static const char RANKING_DELIMITER = ':';
 
-int parse_ranked_line(char *entry, struct Tuple *t) {
+int parse_ranked_line(FILE *stream, const char *entry, char **value, int *rank) {
   char *value_substr = strchr(entry, RANKING_DELIMITER);
   if (value_substr == NULL) {
-    fprintf(stderr, "Missing delimiter ('%c') in line:\n%s\n",
+    fprintf(stream, "WARN: Missing delimiter ('%c') in line: %s\n",
             RANKING_DELIMITER, entry);
-    return 1;
+    return -1;
   }
 
-  int ranking_chars_count = value_substr - entry;
-  char *ranking_chars = malloc(ranking_chars_count + 1);
-  strncpy(ranking_chars, entry, ranking_chars_count);
-  int *ranking = malloc(sizeof(int)); // how to handle fail to parse?
-  *ranking = atoi(ranking_chars);
-  free(ranking_chars);
+  int rank_strlen = value_substr - entry;
+  char *rank_str = malloc(rank_strlen + 1);
+  strncpy(rank_str, entry, rank_strlen);
+  rank_str[rank_strlen] = '\0';
+  *rank = atoi(rank_str);
+  free(rank_str);
 
-  value_substr++; // drop included delimiter
-  char *value_copy = malloc(strlen(value_substr) + 1);
-  strcpy(value_copy, entry);
-
-  t->first = value_copy;
-  t->second = ranking;
+  *value = malloc(strlen(value_substr) + 1);
+  strcpy(*value, entry + (rank_strlen + 1 /* RANKING_DELIMITER */));
 
   return 0;
 }
 
+#ifdef FALLIBLE
+int fallible_parse_ranked_line(FILE *stream, const char *entry, char **value, int *rank, const char *const filename, int lineno) {
+  if (fallible_should_fail(filename, lineno)) {
+    return -1;
+  }
+  return parse_ranked_line(stream, entry, value, rank);
+}
+
+#define parse_ranked_line(stream, entry, value, rank) fallible_parse_ranked_line(stream, entry, value, rank, __FILE__, __LINE__)
+#endif
+
+#ifdef TEST
+void parse_ranked_line_test() {
+  {
+    testing("parse_ranked_line with an empty string");
+    char *value;
+    int rank;
+
+    char filename[] = "remembering-test.XXXXXX";
+    int fd;
+    if ((fd = mkstemp(filename)) == -1) {
+      fprintf(stderr, "\nERR: Can't create test tempfile.\n");
+      exit(EXIT_FAILURE);
+    }
+
+    FILE *f = fdopen(fd, "w");
+    int ret = parse_ranked_line(f, "", &value, &rank);
+    assert(ret == -1);
+    fclose(f);
+    test_ok();
+  }
+  {
+    testing("parse_ranked_line when RANKING DELIMITER is missing");
+    char *value;
+    int rank;
+
+    char filename[] = "remembering-test.XXXXXX";
+    int fd;
+    if ((fd = mkstemp(filename)) == -1) {
+      fprintf(stderr, "\nERR: Can't create test tempfile.\n");
+      exit(EXIT_FAILURE);
+    }
+
+    FILE *f = fdopen(fd, "w");
+    int ret = parse_ranked_line(f, "0 command", &value, &rank);
+    assert(ret == -1);
+    fclose(f);
+    test_ok();
+  }
+  {
+    testing("parse_ranked_line with a happy path examples");
+    char *value;
+    int rank;
+    int ret;
+
+    ret = parse_ranked_line(stderr, "0:command", &value, &rank);
+    assert(ret == 0);
+    assert(strcmp(value, "command") == 0);
+    assert(rank == 0);
+    free(value);
+
+    ret = parse_ranked_line(stderr, "10:another command", &value, &rank);
+    assert(ret == 0);
+    assert(strcmp(value, "another command") == 0);
+    assert(rank == 10);
+    free(value);
+
+    ret = parse_ranked_line(stderr, "123:123", &value, &rank);
+    assert(ret == 0);
+    assert(strcmp(value, "123") == 0);
+    assert(rank == 123);
+    free(value);
+
+    ret = parse_ranked_line(stderr, "0:0", &value, &rank);
+    assert(ret == 0);
+    assert(strcmp(value, "0") == 0);
+    assert(rank == 0);
+    free(value);
+
+    ret = parse_ranked_line(stderr, "0:command with : in the middle", &value, &rank);
+    assert(ret == 0);
+    assert(strcmp(value, "command with : in the middle") == 0);
+    assert(rank == 0);
+    free(value);
+
+    ret = parse_ranked_line(stderr, "0:::command:with:multiple:::in:the:middle:", &value, &rank);
+    assert(ret == 0);
+    assert(strcmp(value, "::command:with:multiple:::in:the:middle:") == 0);
+    assert(rank == 0);
+    free(value);
+
+    test_ok();
+  }
+  {
+    testing("parse_ranked_line with an empty command");
+    char *value;
+    int rank;
+
+    int ret = parse_ranked_line(stderr, "0:", &value, &rank);
+    assert(ret == 0);
+    assert(strcmp(value, "") == 0);
+    assert(rank == 0);
+    free(value);
+    test_ok();
+  }
+  {
+    testing("parse_ranked_line with a bad rank numbers");
+    char *value;
+    int rank;
+    int ret;
+
+    ret = parse_ranked_line(stderr, ":command", &value, &rank);
+    assert(ret == 0);
+    assert(strcmp(value, "command") == 0);
+    assert(rank == 0);
+    free(value);
+
+    ret = parse_ranked_line(stderr, "1 2 3:command", &value, &rank);
+    assert(ret == 0);
+    assert(strcmp(value, "command") == 0);
+    assert(rank == 1);
+    free(value);
+
+    ret = parse_ranked_line(stderr, ".1:command", &value, &rank);
+    assert(ret == 0);
+    assert(strcmp(value, "command") == 0);
+    assert(rank == 0);
+    free(value);
+
+    ret = parse_ranked_line(stderr, ":5:command", &value, &rank);
+    assert(ret == 0);
+    assert(strcmp(value, "5:command") == 0);
+    assert(rank == 0);
+    free(value);
+
+    ret = parse_ranked_line(stderr, "command:command", &value, &rank);
+    assert(ret == 0);
+    assert(strcmp(value, "command") == 0);
+    assert(rank == 0);
+    free(value);
+
+    test_ok();
+  }
+}
+#endif
+
+struct GetlineParams {
+  char *line;
+  size_t len;
+  ssize_t read;
+  FILE *stream;
+};
+
 int get_stdin(struct GetlineParams *s_params, struct GetlineParams *p_params,
               struct Rankings *p_rankings) {
   s_params->read = getline(&s_params->line, &s_params->len, s_params->stream);
   if (s_params->read == -1) {
     while ((p_params->read = getline(&p_params->line, &p_params->len,
                                      p_params->stream)) != -1) {
-      struct Tuple t;
-      parse_ranked_line(p_params->line, &t);
-      rankings_insert(p_rankings, t.first, t.second);
+      char *value;
+      int rank;
+      int ret;
+      if ((ret = parse_ranked_line(stderr, p_params->line, &value, &rank)) == -1) {
+        return ret;
+      }
+      if ((ret = rankings_insert(p_rankings, value, rank)) == -1) {
+        return ret;
+      }
     }
     return 1;
   }
@@ -323,19 +489,39 @@ int get_profile(struct GetlineParams *s_params, struct GetlineParams *p_params,
   if (p_params->read == -1) {
     while ((s_params->read = getline(&s_params->line, &s_params->len,
                                      s_params->stream)) != -1) {
-      char *value_copy = malloc(strlen(s_params->line) + 1);
-      strcpy(value_copy, s_params->line);
-
-      int *rank = malloc(sizeof(int));
-      *rank = 0;
-      rankings_insert(s_rankings, value_copy, rank);
-      rankings_insert(p_rankings, value_copy, rank);
+      int len = strlen(s_params->line) + 1;
+      char *s_value_copy = malloc(len);
+      if (!s_value_copy) {
+        return -1;
+      }
+      char *p_value_copy = malloc(len);
+      if (!p_value_copy) {
+        free(s_value_copy);
+        return -1;
+      }
+      strcpy(s_value_copy, s_params->line);
+      strcpy(p_value_copy, s_params->line);
+
+      int ret;
+      if ((ret = rankings_insert(s_rankings, s_value_copy, 0)) == -1) {
+        free(s_value_copy);
+        free(p_value_copy);
+        return -1;
+      }
+
+      // profile grows with stdin entries
+      if ((ret = rankings_insert(p_rankings, p_value_copy, 0)) == -1) {
+        free(s_value_copy);
+        free(p_value_copy);
+        return -1;
+      }
     }
     return 1;
   }
   return 0;
 }
 
+/*
 int merge_stdin_with_profile(char *profile_name, struct Rankings *s_rankings,
                              struct Rankings *p_rankings) {
   FILE *profile = fopen(profile_name, "r");
@@ -388,7 +574,10 @@ int merge_stdin_with_profile(char *profile_name, struct Rankings *s_rankings,
 */
 
 #ifdef TEST
-void unit_tests() {}
+void unit_tests() {
+  rankings_test();
+  parse_ranked_line_test();
+}
 #endif
 
 int main(int argc, char *argv[]) {
@@ -398,12 +587,16 @@ int main(int argc, char *argv[]) {
 #endif
 
   int ret;
-  struct Pair p;
-  ret = get_options(argc, argv, &p);
+  char *command;
+  char *profile;
+  // FIXME: single line
+  ret = get_options(argc, argv, &command, &profile);
   if (ret) {
     goto err;
+    expand_profile_name("oijoI");
   }
 
+
 err:
   return ret;
 }
-- 
cgit v1.2.3